commit 0d7517fbb2cb7c58a2cc6ea4e8b0f4cd2b6296c9 Author: archive Date: Mon Oct 9 00:00:00 2006 +0000 as released 2006-10-09 diff --git a/EULA.Development Kit.rtf b/EULA.Development Kit.rtf new file mode 100644 index 0000000..79293f3 --- /dev/null +++ b/EULA.Development Kit.rtf @@ -0,0 +1,58 @@ +{\rtf1\ansi\ansicpg1252\deff0\deflang1033\deflangfe1033{\fonttbl{\f0\froman\fprq2\fcharset0 Times New Roman;}{\f1\fmodern\fprq1\fcharset0 Courier New;}} +{\*\generator Msftedit 5.41.15.1507;}\viewkind4\uc1\pard\qc\b\f0\fs22 PREY SOFTWARE DEVELOPMENT KIT\par +LIMITED USE LICENSE AGREEMENT\par +\pard\qj\b0\par +\pard\fi720\qj This \i PREY\i0 Software Development Kit Limited Use License Agreement (this "Agreement") is a legal agreement among you, the end-user, and Human Head Studios, Inc. ("Human Head Studios"). \b BY CONTINUING THE DOWNLOAD OR INSTALLATION OF THIS SOFTWARE DEVELOPMENT KIT (THE "SOFTWARE") FOR THE GAME PROGRAM ENTITLED \i PREY\i0 , 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 HUMAN HEAD STUDIOS GAME ENTITLED \i PREY\i0 INSTALLED ON YOUR COMPUTER.\b0\par +\par +\b 1.\tab Grant of License.\b0 Subject to the terms and provisions of this Agreement and so long as you fully comply at all times with this Agreement, Human Head Studios 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. Human Head Studios reserves all rights not granted in this Agreement, including, without limitation, all rights to Human Head Studios' trademarks.\f1\par +\par +\b\f0 2.\tab Permitted New Creations.\b0 Subject to the terms and provisions of this Agreement and so long as you fully comply at all times with this Agreement, Human Head Studios grants to you the non-exclusive and limited right to use the Software to create for the software game \i PREY\i0 your own modifications (the "New Creations") that shall operate only with \i PREY\i0 (but not any demo, test, or other version of \i PREY\i0 ). 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.\par +\par +\b 3.\tab Prohibitions with Regard to the Software.\b0 You, whether directly or indirectly, shall \b not\b0 do any of the following acts:\par +\par +\pard\fi-720\li1840\qj a.\tab rent the Software;\par +\par +b.\tab sell the Software;\par +\par +c.\tab lease or lend the Software;\par +\par +d.\tab offer the Software on a pay-per-play basis;\par +\par +e.\tab distribute the Software (except as permitted under Section 5 hereinbelow);\par +\par +f.\tab in any other manner and through any medium whatsoever commercially exploit the Software or use the Software for any commercial purpose;\par +\par +g.\tab disassemble, reverse engineer, decompile, modify (except as permitted under Section\~2 hereinabove) or alter the Software;\par +\par +h.\tab translate the Software;\par +\par +i.\tab reproduce or copy the Software (except as permitted under Section 5 hereinbelow);\par +\par +j.\tab publicly display the Software;\par +\par +k.\tab prepare or develop derivative works based upon the Software;\par +\par +l.\tab remove or alter any notices or other markings or legends, such as trademark or copyright notices, affixed on or within the Software; or\par +\par +m.\tab remove, alter, modify, disable, or reduce any of the anti-piracy measures contained in the Software or in \i PREY\i0 , including, without limitation, measures relating to multiplayer play.\par +\pard\qj\par +\pard\fi720\qj\b 4.\tab Prohibition against Cheat Programs.\b0 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,\b \b0 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.\par +\par +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.\par +\b\par +5.\tab Permitted Distribution and Copying.\b0 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, Human Head Studios 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 \b not \b0 copy or distribute the Software in any infringing manner or in any manner that violates any law or third-party right, and you shall \b not\b0 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. Human Head Studios reserves all rights not granted in this Agreement. \b You shall not distribute the Software commercially unless you first enter into a separate contract with Human Head Studios, on terms and conditions determined in Human Head Studios' sole discretion, and only upon your receipt of a written agreement executed by an authorized officer of Human Head Studios.\b0\par +\par +\b 6.\tab Intellectual Property Rights.\b0 The Software and all copyrights, trademarks, and all other conceivable intellectual property rights related to the Software are owned by Human Head Studios 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. \'a7 101 \i et seq.\i0 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. \'a7 109 does not apply to your receipt or use of the Software. This Section shall survive the cancellation or termination of this Agreement.\par +\par +\b 7.\tab NO HUMAN HEAD STUDIOS WARRANTIES.\b0 \b HUMAN HEAD STUDIOS 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. HUMAN HEAD STUDIOS 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 HUMAN HEAD STUDIOS AND SHOULD NOT BE RELIED UPON.\b0 This Section shall survive the cancellation or termination of this Agreement.\par +\par +\b 8.\tab Governing Law, Venue, Indemnity, and Liability Limitation.\b0 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 Human Head Studios and Human Head Studios' 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 Human Head Studios such that Human Head Studios could not be adequately compensated solely by a monetary award, and in such event, at Human Head Studios' option, that Human Head Studios 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 Human Head Studios posting bond or other security. \b IN ANY CASE, HUMAN HEAD STUDIOS AND HUMAN HEAD STUDIOS' 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 HUMAN HEAD STUDIOS 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.\b0 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.\par +\par +\b 9.\tab United States Government Restricted Rights.\b0 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. \'a7\'a7 227.7201 through 227.7204, inclusive.\par +\par +\b 10.\tab General Provisions.\b0 Neither this Agreement nor any part or portion hereof shall be assigned or sublicensed by you. Human Head Studios 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 Human Head Studios 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. \b 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 HUMAN HEAD STUDIOS MAY PURSUE ALL RELIEF AND REMEDIES AGAINST YOU THAT ARE AVAILABLE UNDER APPLICABLE LAW AND/OR THIS AGREEMENT.\b0 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 Human Head Studios.\par +\par +\b 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.\b0\par +\par +} + \ No newline at end of file diff --git a/ReadMe.rtf b/ReadMe.rtf new file mode 100644 index 0000000..6a49f53 Binary files /dev/null and b/ReadMe.rtf differ diff --git a/examples/01 - Basic Mod/ReadMe.txt b/examples/01 - Basic Mod/ReadMe.txt new file mode 100644 index 0000000..f93445a --- /dev/null +++ b/examples/01 - Basic Mod/ReadMe.txt @@ -0,0 +1,53 @@ +This example demonstrates a simple mod which adds a console command to print some text. + +Fire up the codebase and open up SysCmds.cpp. Around line 2730, you will see some text that looks like this: + + 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( "notarget", Cmd_Notarget_f, CMD_FL_GAME|CMD_FL_CHEAT, "disables the player as a target" ); + +Let's turn that into: + + cmdSystem->AddCommand( "centerview", Cmd_CenterView_f, CMD_FL_GAME, "centers the view" ); + //mymod begin + cmdSystem->AddCommand( "myCommand", Cmd_MyCommand_f, CMD_FL_GAME, "my brand new command" ); + //mymod end + cmdSystem->AddCommand( "god", Cmd_God_f, CMD_FL_GAME|CMD_FL_CHEAT, "enables god mode" ); + cmdSystem->AddCommand( "notarget", Cmd_Notarget_f, CMD_FL_GAME|CMD_FL_CHEAT, "disables the player as a target" ); + +Now let's go to line 458, above the void Cmd_God_f( const idCmdArgs &args ) function, and let's add: + +//mymod begin +void Cmd_MyCommand_f( const idCmdArgs &args ) { + gameLocal.Printf("I am a banana!\n"); +} +//mymod end + +Now compile, and you should end up with a ./releasedll/gamex86.dll file. Using a program like WinRAR or WinZip, +take that file and put it in a zip file. Now you will want to create a file called "binary.conf". The contents +of that file should look like this: + +// add flags for supported operating systems in this pak +// one id per line +// name the file binary.conf and place it in the game pak +// 0 win-x86 +// 1 mac-ppc +// 2 linux-x86 +0 + +Add that binary.conf file into the zip file with the gamex86.dll. You should now have a zip file with those two +files in it. Rename the zip file to game00.pk4, and head over to your Prey folder. Under your Prey folder you will +have a base folder. Likewise, you want to create your mod folder next to base (not in base!), so let's make a folder +called mymod, and put the game00.pk4 we just made in there. Now you should have a file called something like: +C:\Program Files\Prey\mymod\game00.pk4 +So, in the mymod folder, create a file called description.txt. In that file, just put something like: +My Mod! +And save it. Now start Prey up. In the lower-right of the main menu you'll see the "Mods" option. Click it, and +you should see your "My Mod!" mod on the list. Click it and then click "Load MOD". Now bring down the console +(ctrl+alt+~), and type myCommand and hit enter. If all is well, your message will be displayed. + +For an example of the final package, check out the mymod folder (in the same folder as this text file). + + +Rich Whitehouse +( http://www.telefragged.com/thefatal/ ) diff --git a/examples/01 - Basic Mod/mymod/description.txt b/examples/01 - Basic Mod/mymod/description.txt new file mode 100644 index 0000000..3beff25 --- /dev/null +++ b/examples/01 - Basic Mod/mymod/description.txt @@ -0,0 +1 @@ +My Mod! diff --git a/examples/01 - Basic Mod/mymod/game00.pk4 b/examples/01 - Basic Mod/mymod/game00.pk4 new file mode 100644 index 0000000..9e22f0f Binary files /dev/null and b/examples/01 - Basic Mod/mymod/game00.pk4 differ diff --git a/examples/02 - Feedback/ReadMe.txt b/examples/02 - Feedback/ReadMe.txt new file mode 100644 index 0000000..49356eb --- /dev/null +++ b/examples/02 - Feedback/ReadMe.txt @@ -0,0 +1,27 @@ +Now that we've accomplished everything involved in getting a mod up and running with Basic Mod, let's do something +that produces more immediate feedback and actually changes the game behavior. We'll start out doing something very +simple, before we move onto something more advanced in the next example. Fire up the codebase again, and open up +game_player.cpp. Head to the function hhPlayer::GetViewPos, and you'll see a line that looks like this: + + axis = TransformToPlayerSpace( (GetUntransformedViewAngles() + viewBobAngles + playerView.AngleOffset(kickSpring, kickDamping)).ToMat3() ); + +Let's replace it with this chunk of code: + + //viewswagger begin + idAngles swaggerOffset; + const float swagger = 10.0f; + swaggerOffset.roll = 0.0f; + swaggerOffset.pitch = idMath::Sin(MS2SEC(gameLocal.time))*swagger; + swaggerOffset.yaw = idMath::Cos(MS2SEC(gameLocal.time))*swagger*0.5f; + axis = TransformToPlayerSpace( (swaggerOffset + GetUntransformedViewAngles() + viewBobAngles + playerView.AngleOffset(kickSpring, kickDamping)).ToMat3() ); + //viewswagger end + +Now compile and take the gamex86.dll, and do the same thing we did with it in the Basic Mod example (though you will +probably want to use something other than mymod, such as, say, viewswagger). When you load a map up, you will now +notice that Tommy has a little view swagger going on. This is accomplished by adding some simple wave-based angles to +the final view axis in the code above. Now that we have observed our first changes in game behavior, it's time to +move onto the next example, where we actually get to add our own new type of entity. + + +Rich Whitehouse +( http://www.telefragged.com/thefatal/ ) diff --git a/examples/03 - New Entities/ReadMe.txt b/examples/03 - New Entities/ReadMe.txt new file mode 100644 index 0000000..e643abe --- /dev/null +++ b/examples/03 - New Entities/ReadMe.txt @@ -0,0 +1,40 @@ +In this example, we'll be adding a new trigger, which adds health to the player while he's standing in it. The basic +framework is fairly barebones (you could dress it up a lot, by adding, perhaps, a debounce time and some sounds), but +the intent is to demonstrate how to create a new type of entity and place it in your map. + +Included in the "healthzone" subdirectory are 3 files. 2 of them are replacements for their ./src/Prey/ counterparts +(game_zone.cpp and game_zone.h), with the changes already made that we will be making in this example. The +healthzone.pk4 file contains new def and map files. The def file contains an entity entry for the new class we will +be creating, and the map has the health trigger placed and ready to test. + +So, to sum up what we will be doing to get this example all up and running: + +1) Merge the included game_zone.cpp and game_zone.h into your copy of the Prey SDK source. +2) Compile, and create a game00.pk4 as described in the Basic Mod example. +3) Create a mod folder as described in Basic Mod, something like "healthzone". Don't forget to create a +description.txt file in that folder as well, as per the Basic Mod example. +4) Copy the included healthzone.pk4 into that mod folder, along with your game00.pk4. +5) Start Prey up and activate your healthzone mod through the menu. Bring down the console and type "map healthzone". +Now pick up the autocannon and some grenades and hurt yourself. Then, step onto the platform in the room and observe +as your health shoots back up. + +The actual code changes involved here are minimal. You should take a look at the contents in the new game_zone.cpp +and game_zone.h files, and search for the //healthzone begin and //healthzone end comments. These mark all of the +changes that were necessary to add this healthzone entity. + +Note that the hhHealthZone inherits from the hhZone class. The hhZone class handles all of the main functionality for +determining if an entity is within it, and giving us callbacks. All we have to do is override the ValidEntity and +EntityEncroaching functions, in order to perform our own logic there. This is generally possible with many types of +entities that you will want to add. You should note, however, that you can go so far as to inherit directly from +idEntity or even idClass when you are creating drastically new types of objects or game systems. + +You should also examine the contents of the def/mymoddefs.def file in the healthzone.pk4 file. It demonstrates how +you can add a new entity entry without modifying existing files (you should always do this when possible for +distributing maps with custom entity types - it allows your map to work without interfering with other custom maps +or the normal game), as well as demonstrating how the entityDef hooks up to the new hhHealthZone class that we added. + +Hopefully this will get you well on your way to creating new game content. Good luck. + + +Rich Whitehouse +( http://www.telefragged.com/thefatal/ ) diff --git a/examples/03 - New Entities/healthzone/game_zone.cpp b/examples/03 - New Entities/healthzone/game_zone.cpp new file mode 100644 index 0000000..5e5cfab --- /dev/null +++ b/examples/03 - New Entities/healthzone/game_zone.cpp @@ -0,0 +1,1223 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_SetGravityVector("setgravity", "v"); +const idEventDef EV_SetGravityFactor("setgravityfactor", "f"); +const idEventDef EV_DeactivateZone("", NULL); + +//----------------------------------------------------------------------- +// +// hhZone +// +// NOTE: You do not get a EntityLeaving() callback for entities that are +// removed. Could possibly use hhSafeEntitys to tell when they are removed +// but what's the point of calling EntityLeaving() with an invalid pointer. +//----------------------------------------------------------------------- + +ABSTRACT_DECLARATION(hhTrigger, hhZone) + EVENT( EV_DeactivateZone, hhZone::Event_TurnOff ) + EVENT( EV_Enable, hhZone::Event_Enable ) + EVENT( EV_Disable, hhZone::Event_Disable ) + EVENT( EV_Touch, hhZone::Event_Touch ) +END_CLASS + +//NOTE: If this works, this entity can cease inheriting from trigger, just need to take the +// tracemodel creation logic, isSimpleBox variable, make our own enable/disable functions +// touch goes away, triggeraction goes away, much simpler interface +#define ZONES_ALWAYS_ACTIVE 1 // testing: want to be able to use dormancy to turn off --pdm + +void hhZone::Spawn(void) { + slop = 0.0f; // Extra slop for bounds check +#if !ZONES_ALWAYS_ACTIVE + fl.neverDormant = true; +#endif + +#if ZONES_ALWAYS_ACTIVE + fl.neverDormant = false; + BecomeActive(TH_THINK); + bActive = true; + bEnabled = true; +#endif +} + +void hhZone::Save(idSaveGame *savefile) const { + savefile->WriteInt(zoneList.Num()); // idList + for (int i=0; iWriteInt(zoneList[i]); + } + + savefile->WriteFloat(slop); +} + +void hhZone::Restore( idRestoreGame *savefile ) { + int num; + + zoneList.Clear(); // idList + savefile->ReadInt(num); + zoneList.SetNum(num); + for (int i=0; iReadInt(zoneList[i]); + } + + savefile->ReadFloat(slop); +} + +bool hhZone::ValidEntity(idEntity *ent) { + return (ent && ent!=this && + ent->GetPhysics() && + !ent->GetPhysics()->IsType(idPhysics_Static::Type) && + ent->GetPhysics()->GetContents() != 0); +} + +void hhZone::Empty() { +} + +bool hhZone::ContainsEntityOfType(const idTypeInfo &t) { + idEntity *touch[ MAX_GENTITIES ]; + idBounds clipBounds; + + clipBounds.FromTransformedBounds( GetPhysics()->GetBounds(), GetOrigin(), GetAxis() ); + int num = gameLocal.clip.EntitiesTouchingBounds( clipBounds.Expand(slop), MASK_SHOT_BOUNDINGBOX, touch, MAX_GENTITIES ); + for (int i=0; iIsType(t)) { + gameLocal.Printf("Contains a %s\n", t.classname); + return true; + } + } + gameLocal.Printf("Doesn't contain a %s\n", t.classname); + return false; +} + +bool PointerInList(idEntity *target, idEntity **list, int num) { + for (int j=0; j < num; j++ ) { + if (list[j] == target) { + return true; + } + } + return false; +} + +void hhZone::ResetZoneList() { + // Call Leaving for anything previously entered + idEntity *previouslyInZone; + for (int i=0; i < zoneList.Num(); i++ ) { + previouslyInZone = gameLocal.entities[zoneList[i]]; + + if (previouslyInZone) { + EntityLeaving(previouslyInZone); + } + } + zoneList.Clear(); +} + +void hhZone::TriggerAction(idEntity *activator) { + CancelEvents(&EV_DeactivateZone); + // Turn on until all encroachers are gone + BecomeActive(TH_THINK); +} + +void hhZone::ApplyToEncroachers() { + idEntity *touch[ MAX_GENTITIES ]; + idEntity *previouslyInZone; + idEntity *encroacher; + int i, num; + + idBounds clipBounds; + clipBounds.FromTransformedBounds( GetPhysics()->GetBounds(), GetOrigin(), GetAxis() ); + + // Find all encroachers + if (isSimpleBox) { + num = gameLocal.clip.EntitiesTouchingBounds( clipBounds.Expand(slop), MASK_SHOT_BOUNDINGBOX | CONTENTS_PROJECTILE | CONTENTS_TRIGGER, touch, MAX_GENTITIES ); // CONTENTS_TRIGGER for walkthrough movables + } + else { + num = hhUtils::EntitiesTouchingClipmodel( GetPhysics()->GetClipModel(), touch, MAX_GENTITIES, MASK_SHOT_BOUNDINGBOX | CONTENTS_TRIGGER ); + } + + // for anything previously applied, but no longer encroaching, call EntityLeaving() + for (i=0; i < zoneList.Num(); i++ ) { + previouslyInZone = gameLocal.entities[zoneList[i]]; + + if (previouslyInZone) { + if (!ValidEntity(previouslyInZone) || !PointerInList(previouslyInZone, touch, num)) { + // We've applied before, but it's no longer encroaching + EntityLeaving(previouslyInZone); + + // NOTE: Rather than removing and dealing with the list shifting, we reconstruct the list + // from the touch list later + } + } + } + + // Check touch list for any newly entered encroachers + for (i = 0; i < num; i++ ) { + encroacher = touch[i]; + if (ValidEntity(encroacher)) { + if (zoneList.FindIndex(encroacher->entityNumber) == -1) { + EntityEntered(encroacher); + } + } + } + + // Call all encroachers and rebuild list + zoneList.Clear(); //fixme: could make a version of clear() that doesn't deallocate the memory + for (i = 0; i < num; i++ ) { + encroacher = touch[i]; + if (ValidEntity(encroacher)) { + zoneList.Append(encroacher->entityNumber); + EntityEncroaching(encroacher); + } + } + + // Deactivate if no encroachers left + if (!zoneList.Num()) { + Empty(); +#if !ZONES_ALWAYS_ACTIVE + PostEventMS(&EV_DeactivateZone, 0); +#endif + } +} + +void hhZone::Think() { + if (thinkFlags & TH_THINK) { + ApplyToEncroachers(); + } +} + +void hhZone::Event_TurnOff() { + BecomeInactive(TH_THINK); + bActive = false; +} + +void hhZone::Event_Enable( void ) { + hhTrigger::Event_Enable(); + TriggerAction(this); +} + +void hhZone::Event_Disable( void ) { + BecomeInactive(TH_THINK); + ResetZoneList(); + hhTrigger::Event_Disable(); +} + +void hhZone::Event_Touch( idEntity *other, trace_t *trace ) { + CancelEvents(&EV_DeactivateZone); + // Turn on until all encroachers are gone + BecomeActive(TH_THINK); + + bActive = true; +} + +//healthzone begin + +//let's specify our new class with its parent class. this hooks us in with the rest of +//the idClasses. +CLASS_DECLARATION(hhZone, hhHealthZone) +END_CLASS + +//our spawn function is called in sequence with spawn functions for any parent classes. +//in here, we can optionally parse any extra spawn args and do other on-spawn logic. +void hhHealthZone::Spawn() { + regenAmount = spawnArgs.GetInt("regenAmount"); //the amount to regen each entity, each frame +} + +//write all of our necessary state data for savegames +void hhHealthZone::Save( idSaveGame *savefile ) const { + savefile->WriteInt(regenAmount); +} + +//loading a savegame, so load state data back in +void hhHealthZone::Restore( idRestoreGame *savefile ) { + savefile->ReadInt(regenAmount); +} + +//let's only accept players who aren't dead +bool hhHealthZone::ValidEntity(idEntity *ent) { + if (!ent || !ent->IsType(hhPlayer::Type) || ent->health <= 0) { + return false; + } + + return true; +} + +//a player is in the zone, let's give him some health +void hhHealthZone::EntityEncroaching(idEntity *ent) { + if ((ent->health+regenAmount) > 100) { + return; //let's not go over 100 health + } + + ent->health += regenAmount; +} +//healthzone end + +//----------------------------------------------------------------------- +// +// hhTriggerZone +// +// Zone used for precise trigger/untrigger mechanic. Fires trigger once +// upon a valid entity entering, and again when a valid entity leaves. Also, +// optionally calls a function for each entity in the volume each tick. +//----------------------------------------------------------------------- + +CLASS_DECLARATION(hhZone, hhTriggerZone) +END_CLASS + +void hhTriggerZone::Spawn() { + funcRefInfo.ParseFunctionKeyValue( spawnArgs.GetString("inCallRef") ); +} + +void hhTriggerZone::Save(idSaveGame *savefile) const { + savefile->WriteStaticObject( funcRefInfo ); +} + +void hhTriggerZone::Restore( idRestoreGame *savefile ) { + savefile->ReadStaticObject( funcRefInfo ); +} + +bool hhTriggerZone::ValidEntity(idEntity *ent) { + return (hhZone::ValidEntity(ent) && !IsType(hhProjectile::Type)); +} + +void hhTriggerZone::EntityEntered(idEntity *ent) { + ActivateTargets(ent); +} + +void hhTriggerZone::EntityLeaving(idEntity *ent) { + ActivateTargets(ent); +} + +void hhTriggerZone::EntityEncroaching( idEntity *ent ) { + if (funcRefInfo.GetFunction() != NULL) { + funcRefInfo.SetParm_Entity( ent, 0 ); + funcRefInfo.Verify(); + funcRefInfo.CallFunction( spawnArgs ); + } +} + +//----------------------------------------------------------------------- +// +// hhGravityZoneBase +// +//----------------------------------------------------------------------- + +ABSTRACT_DECLARATION(hhZone, hhGravityZoneBase) +END_CLASS + +void hhGravityZoneBase::Spawn(void) { + bReorient = spawnArgs.GetBool("reorient"); + bShowVector = spawnArgs.GetBool("showVector"); + bKillsMonsters = spawnArgs.GetBool("killmonsters"); + + //rww - avoid dictionary lookup post-spawn + gravityOriginOffset = vec3_origin; + if (spawnArgs.GetVector("override_origin", gravityOriginOffset.ToString(), gravityOriginOffset)) { + gravityOriginOffset -= GetOrigin(); + } + + //rww - sync over network + fl.networkSync = true; +} + +void hhGravityZoneBase::Save(idSaveGame *savefile) const { + savefile->WriteBool(bReorient); + savefile->WriteBool(bKillsMonsters); + savefile->WriteBool(bShowVector); + savefile->WriteVec3(gravityOriginOffset); +} + +void hhGravityZoneBase::Restore( idRestoreGame *savefile ) { + savefile->ReadBool(bReorient); + savefile->ReadBool(bKillsMonsters); + savefile->ReadBool(bShowVector); + savefile->ReadVec3(gravityOriginOffset); +} + +//rww - network code +void hhGravityZoneBase::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteBits(bReorient, 1); + msg.WriteFloat(slop); +} + +void hhGravityZoneBase::ReadFromSnapshot( const idBitMsgDelta &msg ) { + bReorient = !!msg.ReadBits(1); + slop = msg.ReadFloat(); +} + +void hhGravityZoneBase::ClientPredictionThink( void ) { + Think(); +} +//rww - end network code + +const idVec3 hhGravityZoneBase::GetGravityOrigin() const { + return GetOrigin()+gravityOriginOffset; +} + +bool hhGravityZoneBase::ValidEntity(idEntity *ent) { + if (!ent) { + return false; + } + if (ent->fl.ignoreGravityZones) { + return false; + } + + if (ent->IsType(hhProjectile::Type) && ent->GetPhysics()->GetGravity() == vec3_origin) { + return false; // Projectiles with zero gravity + } + if (ent->IsType(hhPlayer::Type)) { + hhPlayer *pl = static_cast(ent); + if (pl->noclip) { + return false; // Noclipping players + } + else if (pl->spectating) { + return false; //spectating players + } + else if (gameLocal.isMultiplayer && ent->health <= 0) { + return false; //dead mp players (only the prox ragdoll needs gravity) + } + } + if (ent->IsType(hhVehicle::Type)) { + if (static_cast(ent)->IsNoClipping()) { + return false; // Noclipping vehicles + } + if (ent->IsType(hhShuttle::Type) && static_cast(ent)->IsConsole()) { + return false; // unpiloted shuttles + } + } + if (ent->IsType(hhPortal::Type) ) { + return true; // Portals are always valid entities in zones + } + + if (!hhZone::ValidEntity( ent )) { + return false; + } + + return true; +} + +bool hhGravityZoneBase::TouchingOtherZones(idEntity *ent, bool traceCheck, idVec3 &otherInfluence) { //rww + if (!ent->GetPhysics()) { + return false; + } + + bool hitAny = false; + + otherInfluence.Zero(); + + idBounds clipBounds; + + idEntity *touch[ MAX_GENTITIES ]; + clipBounds.FromTransformedBounds( ent->GetPhysics()->GetBounds(), ent->GetOrigin(), ent->GetAxis() ); + int num = gameLocal.clip.EntitiesTouchingBounds( clipBounds, GetPhysics()->GetContents(), touch, MAX_GENTITIES ); + for (int i = 0; i < num; i++) { + if (touch[i] && touch[i]->entityNumber != entityNumber && touch[i]->IsType(hhGravityZoneBase::Type)) { + //touching the object, isn't me, and seems to be another gravity zone + bool touchValid = false; + + if (traceCheck) { //let's perform a trace from the ent's origin to see which zone is hit first. (this is not an ideal solution, but it works) + trace_t tr; + const int checkContents = GetPhysics()->GetContents(); + const idVec3 &start = ent->GetOrigin(); + const float testLength = 512.0f; + idVec3 end; + + //first trace against the other + end = (touch[i]->GetPhysics()->GetBounds().GetCenter()-start).Normalize()*testLength; + gameLocal.clip.TracePoint(tr, start, end, checkContents, ent); + if (tr.c.entityNum == touch[i]->entityNumber) { //if the trace actually hit the other one + float otherFrac = tr.fraction; + + //now trace against me + end = (GetPhysics()->GetBounds().GetCenter()-start).Normalize()*testLength; + gameLocal.clip.TracePoint(tr, start, GetPhysics()->GetBounds().GetCenter(), checkContents, ent); + if (tr.c.entityNum != entityNumber || tr.fraction >= otherFrac) { //if the impact was further away (or same, don't want fighting), i lose. + touchValid = true; + } + } + } + else { + touchValid = true; + } + + if (touchValid) { + //accumulate force from other zones + hhGravityZoneBase *zone = static_cast(touch[i]); + if (zone->isSimpleBox || ent->GetPhysics()->ClipContents(zone->GetPhysics()->GetClipModel())) { //if not simple box perform a clip check + idVec3 grav = zone->GetCurrentGravity(ent->GetOrigin()); + hitAny = true; + + grav.Normalize(); + otherInfluence += grav; + + otherInfluence.Normalize(); + } + } + } + } + + return hitAny; +} + +void hhGravityZoneBase::EntityEntered( idEntity *ent ) { + if( ent->RespondsTo(EV_ShouldRemainAlignedToAxial) ) { + ent->ProcessEvent( &EV_ShouldRemainAlignedToAxial, (int)false ); + } + if( ent->RespondsTo(EV_OrientToGravity) ) { + ent->ProcessEvent( &EV_OrientToGravity, (int)bReorient ); + } +} + +void hhGravityZoneBase::EntityLeaving( idEntity *ent ) { + if( ent->RespondsTo(EV_ShouldRemainAlignedToAxial) ) { + ent->ProcessEvent( &EV_ShouldRemainAlignedToAxial, (int)true ); + } + + // Instead of reseting gravity here, post a message to do it, so if we are transitioning + // to another gravity zone or wallwalk, there won't be any discontinuities + if (gameLocal.isClient && !ent->fl.clientEvents && !ent->fl.clientEntity && ent->IsType(hhProjectile::Type)) { + ent->fl.clientEvents = true; //hackery to let normal projectiles reset their gravity for prediction + ent->PostEventMS( &EV_ResetGravity, 200 ); + ent->fl.clientEvents = false; + } + else { + ent->PostEventMS( &EV_ResetGravity, 200 ); + } +} + +void hhGravityZoneBase::EntityEncroaching( idEntity *ent ) { + // Cancel any pending gravity resets from other zones + ent->CancelEvents( &EV_ResetGravity ); + + idVec3 curGravity = GetCurrentGravity( ent->GetOrigin() ); + idVec3 otherGravity; + if (TouchingOtherZones(ent, false, otherGravity)) { //factor in gravity for all other zones being touched to avoid back-and-forth behaviour + float l = curGravity.Normalize(); + curGravity += otherGravity; + curGravity *= l*0.5f; + } + if (ent->GetPhysics()->IsAtRest() && ent->GetGravity() != curGravity) { + ent->SetGravity( curGravity ); + ent->GetPhysics()->Activate(); + } + else { + ent->SetGravity( curGravity ); + } + + if (ent->IsType( hhMonsterAI::Type )) { + if (bKillsMonsters && ent->health > 0 && + !static_cast(ent)->OverrideKilledByGravityZones() && + !ent->IsType(hhCrawler::Type) && + (idMath::Fabs(curGravity.x) > 0.01f || idMath::Fabs(curGravity.y) > 0.01f || curGravity.z >= 0.0f) && + static_cast(ent)->IsActive() ) { + + const char *monsterDamageType = spawnArgs.GetString("def_monsterdamage"); + ent->Damage(this, NULL, vec3_origin, monsterDamageType, 1.0f, 0); + } + } +} + + +//----------------------------------------------------------------------- +// +// hhGravityZone +// +//----------------------------------------------------------------------- + +CLASS_DECLARATION(hhGravityZoneBase, hhGravityZone) + EVENT( EV_SetGravityVector, hhGravityZone::Event_SetNewGravity ) +END_CLASS + +void hhGravityZone::Spawn(void) { + zeroGravOnChange = spawnArgs.GetBool("zeroGravOnChange"); + idVec3 startGravity( spawnArgs.GetVector("gravity") ); + interpolationTime = SEC2MS(spawnArgs.GetFloat("interpTime")); + gravityInterpolator.Init( gameLocal.time, 0, startGravity, startGravity ); + + if (startGravity != gameLocal.GetGravity()) { + if (!gameLocal.isMultiplayer) { //don't play sound in mp + StartSound("snd_gravity_loop_on", SND_CHANNEL_MISC1, 0, true); + } + } +} + +void hhGravityZone::Save(idSaveGame *savefile) const { + savefile->WriteFloat( gravityInterpolator.GetStartTime() ); // idInterpolate + savefile->WriteFloat( gravityInterpolator.GetDuration() ); + savefile->WriteVec3( gravityInterpolator.GetStartValue() ); + savefile->WriteVec3( gravityInterpolator.GetEndValue() ); + + savefile->WriteInt(interpolationTime); + savefile->WriteBool(zeroGravOnChange); +} + +void hhGravityZone::Restore( idRestoreGame *savefile ) { + float set; + idVec3 vec; + + savefile->ReadFloat( set ); // idInterpolate + gravityInterpolator.SetStartTime( set ); + savefile->ReadFloat( set ); + gravityInterpolator.SetDuration( set ); + savefile->ReadVec3( vec ); + gravityInterpolator.SetStartValue( vec ); + savefile->ReadVec3( vec ); + gravityInterpolator.SetEndValue( vec ); + + savefile->ReadInt(interpolationTime); + savefile->ReadBool(zeroGravOnChange); +} + +void hhGravityZone::Think() { + hhGravityZoneBase::Think(); + if (thinkFlags & TH_THINK) { + if (bShowVector) { + gameRenderWorld->DebugArrow(colorGreen, renderEntity.origin, renderEntity.origin + GetCurrentGravity(vec3_origin), 10); + } + } +} + +const idVec3 hhGravityZone::GetDestinationGravity() const { + return gravityInterpolator.GetEndValue(); +} + +const idVec3 hhGravityZone::GetCurrentGravity(const idVec3 &location) const { + return gravityInterpolator.GetCurrentValue( gameLocal.time ); +} + +void hhGravityZone::SetGravityOnZone( idVec3 &newGravity ) { + idVec3 startGrav; + + if (!gameLocal.isMultiplayer) { //don't play sound in mp + if ( newGravity.Compare(gameLocal.GetGravity(), VECTOR_EPSILON) ) { + StartSound("snd_gravity_off", SND_CHANNEL_ANY); + StopSound(SND_CHANNEL_MISC1, true); + StartSound("snd_gravity_loop_off", SND_CHANNEL_MISC1, 0, true); + } + else { + StartSound("snd_gravity_on", SND_CHANNEL_ANY); + StopSound(SND_CHANNEL_MISC1, true); + StartSound("snd_gravity_loop_on", SND_CHANNEL_MISC1, 0, true); + } + } + + if ( zeroGravOnChange ) { // nla + startGrav = vec3_origin; + } + else { + startGrav = GetCurrentGravity(vec3_origin); + } + // Interpolate to new gravity + gravityInterpolator.Init( gameLocal.time, interpolationTime, startGrav, newGravity ); +} + +//rww - network code +void hhGravityZone::WriteToSnapshot( idBitMsgDelta &msg ) const { + hhGravityZoneBase::WriteToSnapshot(msg); + + msg.WriteFloat(gravityInterpolator.GetStartTime()); + msg.WriteFloat(gravityInterpolator.GetDuration()); + idVec3 vecStart = gravityInterpolator.GetStartValue(); + msg.WriteFloat(vecStart.x); + msg.WriteFloat(vecStart.y); + msg.WriteFloat(vecStart.z); + idVec3 vecEnd = gravityInterpolator.GetEndValue(); + msg.WriteDeltaFloat(vecStart.x, vecEnd.x); + msg.WriteDeltaFloat(vecStart.y, vecEnd.y); + msg.WriteDeltaFloat(vecStart.z, vecEnd.z); +} + +void hhGravityZone::ReadFromSnapshot( const idBitMsgDelta &msg ) { + hhGravityZoneBase::ReadFromSnapshot(msg); + gravityInterpolator.SetStartTime(msg.ReadFloat()); + gravityInterpolator.SetDuration(msg.ReadFloat()); + idVec3 vecStart; + vecStart.x = msg.ReadFloat(); + vecStart.y = msg.ReadFloat(); + vecStart.z = msg.ReadFloat(); + gravityInterpolator.SetStartValue(vecStart); + idVec3 vecEnd; + vecEnd.x = msg.ReadDeltaFloat(vecStart.x); + vecEnd.y = msg.ReadDeltaFloat(vecStart.y); + vecEnd.z = msg.ReadDeltaFloat(vecStart.z); + gravityInterpolator.SetEndValue(vecEnd); +} + +void hhGravityZone::ClientPredictionThink( void ) { + hhGravityZoneBase::ClientPredictionThink(); +} +//rww - end network code + +void hhGravityZone::Event_SetNewGravity( idVec3 &newGravity ) { + SetGravityOnZone( newGravity ); +} + + +//----------------------------------------------------------------------- +// +// hhGravityZoneInward +// +//----------------------------------------------------------------------- + +CLASS_DECLARATION(hhGravityZoneBase, hhGravityZoneInward) + EVENT( EV_SetGravityFactor, hhGravityZoneInward::Event_SetNewGravityFactor ) +END_CLASS + +void hhGravityZoneInward::Spawn(void) { + float startFactor = spawnArgs.GetFloat("factor", "50000"); + monsterGravityFactor = spawnArgs.GetFloat("monsterGravFactor", "1"); + interpolationTime = SEC2MS(spawnArgs.GetFloat("interpTime")); + factorInterpolator.Init( gameLocal.time, 0, startFactor, startFactor ); +} + +void hhGravityZoneInward::Save(idSaveGame *savefile) const { + savefile->WriteFloat( factorInterpolator.GetStartTime() ); // idInterpolate + savefile->WriteFloat( factorInterpolator.GetDuration() ); + savefile->WriteFloat( factorInterpolator.GetStartValue() ); + savefile->WriteFloat( factorInterpolator.GetEndValue() ); + + savefile->WriteInt(interpolationTime); + savefile->WriteFloat(monsterGravityFactor); +} + +void hhGravityZoneInward::Restore( idRestoreGame *savefile ) { + float set; + + savefile->ReadFloat( set ); // idInterpolate + factorInterpolator.SetStartTime( set ); + savefile->ReadFloat( set ); + factorInterpolator.SetDuration( set ); + savefile->ReadFloat( set ); + factorInterpolator.SetStartValue(set); + savefile->ReadFloat( set ); + factorInterpolator.SetEndValue( set ); + + savefile->ReadInt(interpolationTime); + savefile->ReadFloat(monsterGravityFactor); +} + +void hhGravityZoneInward::EntityEntered(idEntity *ent) { + hhGravityZoneBase::EntityEntered(ent); + if ( ent && ent->IsType( hhMonsterAI::Type ) ) { + static_cast(ent)->GravClipModelAxis( true ); + } + // Disallow slope checking, it makes us stutter when walking on convex surfaces + + // aob - commented this because it allows the player to walk up vertical walls while in inward gravity zone + //Didn't see any studdering when thia was commented out. Do we still need it? + //if (ent->IsType( hhPlayer::Type ) && ent->GetPhysics()->IsType(hhPhysics_Player::Type) ) { + // static_cast(ent->GetPhysics())->SetSlopeCheck(false); + //} + //now done constantly while in a gravity zone + /* + if (ent->IsType( hhPlayer::Type ) && ent->GetPhysics()->IsType(hhPhysics_Player::Type) ) { + static_cast(ent->GetPhysics())->SetInwardGravity(1); + } + */ +} + +void hhGravityZoneInward::EntityLeaving(idEntity *ent) { + hhGravityZoneBase::EntityLeaving(ent); + if ( ent && ent->IsType( hhMonsterAI::Type ) ) { + static_cast(ent)->GravClipModelAxis( false ); + } + // Re-enable slope checking + + // aob - commented this because it allows the player to walk up vertical walls while in inward gravity zone + //Didn't see any studdering when thia was commented out. Do we still need it? + //if (ent->IsType( hhPlayer::Type ) && ent->GetPhysics()->IsType(hhPhysics_Player::Type) ) { + // static_cast(ent->GetPhysics())->SetSlopeCheck(true); + //} + if (ent->IsType( hhPlayer::Type ) && ent->GetPhysics()->IsType(hhPhysics_Player::Type) ) { + static_cast(ent->GetPhysics())->SetInwardGravity(0); + } +} + +// This is actually called each tick if for entities inside +void hhGravityZoneInward::EntityEncroaching( idEntity *ent ) { + + // Cancel any pending gravity resets from other zones + ent->CancelEvents( &EV_ResetGravity ); + + idVec3 curGravity = GetCurrentGravity( ent->GetOrigin() ); + idVec3 otherGravity; + if (TouchingOtherZones(ent, false, otherGravity)) { //factor in gravity for all other zones being touched to avoid back-and-forth behaviour + float l = curGravity.Normalize(); + curGravity += otherGravity; + curGravity *= l*0.5f; + } + if (ent->GetPhysics()->IsAtRest() && ent->GetGravity() != curGravity) { + ent->SetGravity( curGravity ); + ent->GetPhysics()->Activate(); + } + else { + ent->SetGravity( curGravity ); + } + if (ent->IsType( idAI::Type )) { + if (bKillsMonsters && ent->health > 0 && + !ent->IsType(hhCrawler::Type) && + (curGravity.x != 0.0f || curGravity.y != 0.0f || curGravity.z >= 0.0f) && + !static_cast(ent)->OverrideKilledByGravityZones() && + static_cast(ent)->IsActive() ) { + const char *monsterDamageType = spawnArgs.GetString("def_monsterdamage"); + ent->Damage(this, NULL, vec3_origin, monsterDamageType, 1.0f, 0); + } + } + //rww + else if (ent->IsType( hhPlayer::Type ) && ent->GetPhysics()->IsType(hhPhysics_Player::Type) ) { + static_cast(ent->GetPhysics())->SetInwardGravity(1); + } + + + if( ent->IsType(idAI::Type) && ent->health > 0 ) { + ent->GetPhysics()->SetGravity( ent->GetPhysics()->GetGravity() * monsterGravityFactor ); + ent->GetPhysics()->Activate(); + } + + if( bShowVector ) { + hhUtils::DebugCross( colorBlue, GetOrigin(), 100, 10 ); + idVec3 newGrav = GetCurrentGravity( ent->GetOrigin() ); + gameRenderWorld->DebugArrow( colorGreen, ent->GetRenderEntity()->origin, ent->GetRenderEntity()->origin + newGrav, 10 ); + } +} + +const idVec3 hhGravityZoneInward::GetCurrentGravity( const idVec3 &location ) const { + idVec3 grav; + idVec3 origin = GetGravityOrigin(); + float factor = factorInterpolator.GetCurrentValue( gameLocal.GetTime() ); + idVec3 inward = origin - location; + inward.Normalize(); + grav = inward * DEFAULT_GRAVITY * factor; + return grav; +} + +void hhGravityZoneInward::Event_SetNewGravityFactor( float newFactor ) { + // Interpolate to new gravity factor + float curFactor = factorInterpolator.GetCurrentValue( gameLocal.GetTime() ); + factorInterpolator.Init( gameLocal.GetTime(), interpolationTime, curFactor, newFactor ); +} + +//----------------------------------------------------------------------- +// +// hhAIWallwalkZone +// +//----------------------------------------------------------------------- + +CLASS_DECLARATION(hhGravityZone, hhAIWallwalkZone) +END_CLASS + +bool hhAIWallwalkZone::ValidEntity(idEntity *ent) { + // allow AI that isnt dead + return ent->IsType(idAI::Type) && ent->health > 0; +} + +void hhAIWallwalkZone::EntityEncroaching( idEntity *ent ) { + // Cancel any pending gravity resets from other zones + ent->CancelEvents( &EV_ResetGravity ); + + trace_t TraceInfo; + gameLocal.clip.TracePoint(TraceInfo, ent->GetOrigin(), ent->GetOrigin() + (idVec3(0,0,-300)*ent->GetRenderEntity()->axis), ent->GetPhysics()->GetClipMask(), ent); + if( TraceInfo.fraction < 1.0f ) { // && ent->health > 0 ) { + ent->SetGravity( -TraceInfo.c.normal ); + ent->GetPhysics()->Activate(); + } +} + +//----------------------------------------------------------------------- +// +// hhGravityZoneSinkhole +// +//----------------------------------------------------------------------- + +CLASS_DECLARATION(hhGravityZoneInward, hhGravityZoneSinkhole) + EVENT( EV_SetGravityFactor, hhGravityZoneSinkhole::Event_SetNewGravityFactor ) +END_CLASS + +void hhGravityZoneSinkhole::Spawn(void) { + bReorient = false; + maxMagnitude = spawnArgs.GetFloat("maxMagnitude", "10000"); + minMagnitude = spawnArgs.GetFloat("minMagnitude", "0"); +} + +void hhGravityZoneSinkhole::Save(idSaveGame *savefile) const { + savefile->WriteFloat(maxMagnitude); + savefile->WriteFloat(minMagnitude); +} + +void hhGravityZoneSinkhole::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat(maxMagnitude); + savefile->ReadFloat(minMagnitude); +} + +// Still have this in case we want to do something mass based +const idVec3 hhGravityZoneSinkhole::GetCurrentGravityEntity(const idEntity *ent) const { + idVec3 grav = vec3_origin; + if (ent) { + // precalc mass product / G as a constant and expose that as the fudge factor + idVec3 origin = GetGravityOrigin(); + float factor = factorInterpolator.GetCurrentValue( gameLocal.time ); + idVec3 inward = origin - ent->GetOrigin(); + float distance = inward.Normalize(); + float distanceSquared = distance*distance; + + // Some different gravitational fields + // float gravMag = (mass * ent->GetPhysics()->GetMass() * GRAVITATIONAL_CONSTANT) / distanceSquared; + // float gravMag = factor*factor / distanceSquared; // Inverse squared distance + // float gravMag = factor / sqrt(distance); // Inverse sqrt distance + // float gravMag = factor * sqrt(distance); // sqrt distance + float gravMag = factor*factor / 2 + 0.2f * distanceSquared; // Inverse squared distance + //gameLocal.Printf("factor=%.0f gravity magnitude=%.2f\n", factor, gravMag); + + gravMag = hhMath::ClampFloat(minMagnitude, maxMagnitude, gravMag); // This will cut off extremely large forces + grav = inward * gravMag; + } + return grav; +} + +const idVec3 hhGravityZoneSinkhole::GetCurrentGravity(const idVec3 &location) const { + idVec3 grav; + + // precalc mass product / G as a constant and expose that as the fudge factor + idVec3 origin = GetGravityOrigin(); + float factor = factorInterpolator.GetCurrentValue( gameLocal.time ); + idVec3 inward = origin - location; + float distance = inward.Normalize(); + float distanceSquared = distance*distance; + + // Some different gravitational fields + float gravMag = factor*factor / 2 + 0.2f * distanceSquared; // Inverse squared distance + gravMag = hhMath::ClampFloat(minMagnitude, maxMagnitude, gravMag); // This will cut off extremely large forces + grav = inward * gravMag; + return grav; +} + +void hhGravityZoneSinkhole::Event_SetNewGravityFactor( float newFactor ) { + // Interpolate to new gravity factor + float curFactor = factorInterpolator.GetCurrentValue(gameLocal.time); + factorInterpolator.Init( gameLocal.time, interpolationTime, curFactor, newFactor ); +} + + +//----------------------------------------------------------------------- +// +// hhVelocityZone +// +//----------------------------------------------------------------------- + +const idEventDef EV_SetVelocityVector("setvelocity", "v"); + +CLASS_DECLARATION(hhZone, hhVelocityZone) + EVENT( EV_SetVelocityVector, hhVelocityZone::Event_SetNewVelocity ) +END_CLASS + +void hhVelocityZone::Spawn(void) { + bReorient = spawnArgs.GetBool("reorient"); + interpolationTime = SEC2MS(spawnArgs.GetFloat("interpTime")); + idVec3 startVelocity = spawnArgs.GetVector("velocity"); + //slop = 25.0f; // we use a slightly larger bounds to catch things that are rotated by bReorient + bShowVector = spawnArgs.GetBool("showVector"); + bKillsMonsters = spawnArgs.GetBool("killmonsters"); + + velocityInterpolator.Init(gameLocal.time, 0, startVelocity, startVelocity); +} + +void hhVelocityZone::Save(idSaveGame *savefile) const { + savefile->WriteFloat( velocityInterpolator.GetStartTime() ); // idInterpolate + savefile->WriteFloat( velocityInterpolator.GetDuration() ); + savefile->WriteVec3( velocityInterpolator.GetStartValue() ); + savefile->WriteVec3( velocityInterpolator.GetEndValue() ); + + savefile->WriteBool(bKillsMonsters); + savefile->WriteBool(bReorient); + savefile->WriteBool(bShowVector); + savefile->WriteInt(interpolationTime); +} + +void hhVelocityZone::Restore( idRestoreGame *savefile ) { + float set; + idVec3 vec; + + savefile->ReadFloat( set ); // idInterpolate + velocityInterpolator.SetStartTime( set ); + savefile->ReadFloat( set ); + velocityInterpolator.SetDuration( set ); + savefile->ReadVec3( vec ); + velocityInterpolator.SetStartValue( vec ); + savefile->ReadVec3( vec ); + velocityInterpolator.SetEndValue( vec ); + + savefile->ReadBool(bKillsMonsters); + savefile->ReadBool(bReorient); + savefile->ReadBool(bShowVector); + savefile->ReadInt(interpolationTime); +} + +void hhVelocityZone::EntityLeaving(idEntity *ent) { + ent->GetPhysics()->SetLinearVelocity(idVec3(0, 0, 0)); + if( ent->RespondsTo(EV_OrientToGravity) ) { + ent->ProcessEvent( &EV_OrientToGravity, (int)bReorient ); + } +} + +void hhVelocityZone::EntityEncroaching(idEntity *ent) { + idVec3 baseVelocity = velocityInterpolator.GetCurrentValue(gameLocal.time); + idVec3 baseVelocityDirection = baseVelocity; + baseVelocityDirection.Normalize(); + + idVec3 curVelocity; + curVelocity = ent->GetPhysics()->GetLinearVelocity(); + curVelocity.ProjectOntoPlane(baseVelocityDirection); + + ent->GetPhysics()->SetLinearVelocity( curVelocity + baseVelocity ); + if( ent->RespondsTo(EV_OrientToGravity) ) { + ent->ProcessEvent( &EV_OrientToGravity, (int)bReorient ); + } + else if (ent->IsType( idAI::Type )) { + if (bKillsMonsters && ent->health > 0 && + !static_cast(ent)->IsFlying()) { + const char *monsterDamageType = spawnArgs.GetString("def_monsterdamage"); + ent->Damage(this, NULL, vec3_origin, monsterDamageType, 1.0f, 0); + } + } +} + +void hhVelocityZone::Think() { + hhZone::Think(); + if (thinkFlags & TH_THINK) { + if (bShowVector) { + idVec3 baseVelocity = velocityInterpolator.GetCurrentValue(gameLocal.time); + gameRenderWorld->DebugArrow(colorGreen, renderEntity.origin, renderEntity.origin+baseVelocity, 10); + } + } +} + +void hhVelocityZone::Event_SetNewVelocity( idVec3 &newVelocity ) { + // Interpolate to new velocity + idVec3 currentVelocity = velocityInterpolator.GetCurrentValue(gameLocal.time); + velocityInterpolator.Init(gameLocal.time, interpolationTime, currentVelocity, newVelocity); +} + + +//----------------------------------------------------------------------- +// +// hhShuttleRecharge +// +//----------------------------------------------------------------------- + +CLASS_DECLARATION(hhZone, hhShuttleRecharge) +END_CLASS + +void hhShuttleRecharge::Spawn(void) { + amountHealth = spawnArgs.GetInt("amounthealth"); + amountPower = spawnArgs.GetInt("amountpower"); +} + +void hhShuttleRecharge::Save(idSaveGame *savefile) const { + savefile->WriteInt(amountHealth); + savefile->WriteInt(amountPower); +} + +void hhShuttleRecharge::Restore( idRestoreGame *savefile ) { + savefile->ReadInt(amountHealth); + savefile->ReadInt(amountPower); +} + +bool hhShuttleRecharge::ValidEntity(idEntity *ent) { + return ent && ent->IsType(hhShuttle::Type); +} + +void hhShuttleRecharge::EntityEntered(idEntity *ent) { + //static_cast(ent)->SetRecharging(true); +} + +void hhShuttleRecharge::EntityLeaving(idEntity *ent) { + //static_cast(ent)->SetRecharging(false); +} + +void hhShuttleRecharge::EntityEncroaching(idEntity *ent) { + if (ent->IsType(hhVehicle::Type)) { + hhVehicle *vehicle = static_cast(ent); + + //HUMANHEAD bjk PCF (4-27-06) - shuttle recharge was slow + if(USERCMD_HZ == 30) { + vehicle->GiveHealth(2*amountHealth); + vehicle->GivePower(2*amountPower); + } else { + vehicle->GiveHealth(amountHealth); + vehicle->GivePower(amountPower); + } + } +} + + +//----------------------------------------------------------------------- +// +// hhDockingZone +// +//----------------------------------------------------------------------- + +CLASS_DECLARATION(hhZone, hhDockingZone) +END_CLASS + +void hhDockingZone::Spawn(void) { + dock = NULL; + triggerBehavior = TB_PLAYER_MONSTERS_FRIENDLIES; // Allow all actors to trigger it + + fl.networkSync = true; +} + +void hhDockingZone::Save(idSaveGame *savefile) const { + dock.Save(savefile); +} + +void hhDockingZone::Restore( idRestoreGame *savefile ) { + dock.Restore(savefile); +} + +void hhDockingZone::RegisterDock(hhDock *d) { + dock = d; +} + +bool hhDockingZone::ValidEntity(idEntity *ent) { + if (ent) { + if (dock.IsValid() && dock->ValidEntity(ent)) { + return true; + } + if (ent->IsType(idActor::Type)) { //FIXME: Is this causing the shuttleCount to go wrong + return hhShuttle::ValidPilot(static_cast(ent)); + } + } + return false; +} + +void hhDockingZone::EntityEncroaching(idEntity *ent) { + if (dock.IsValid()) { + dock->EntityEncroaching(ent); + } +} + +void hhDockingZone::EntityEntered(idEntity *ent) { + if (dock.IsValid()) { + dock->EntityEntered(ent); + } +} + +void hhDockingZone::EntityLeaving(idEntity *ent) { + if (dock.IsValid()) { + dock->EntityLeaving(ent); + } +} + +void hhDockingZone::WriteToSnapshot( idBitMsgDelta &msg ) const { + GetPhysics()->WriteToSnapshot(msg); + msg.WriteBits(dock.GetSpawnId(), 32); +} + +void hhDockingZone::ReadFromSnapshot( const idBitMsgDelta &msg ) { + GetPhysics()->ReadFromSnapshot(msg); + dock.SetSpawnId(msg.ReadBits(32)); +} + +void hhDockingZone::ClientPredictionThink( void ) { + if (!gameLocal.isNewFrame) { + return; + } + Think(); +} + +//----------------------------------------------------------------------- +// +// hhShuttleDisconnect +// +//----------------------------------------------------------------------- + +CLASS_DECLARATION(hhZone, hhShuttleDisconnect) +END_CLASS + +void hhShuttleDisconnect::Spawn(void) { +} + +bool hhShuttleDisconnect::ValidEntity(idEntity *ent) { + return ent && ent->IsType(hhShuttle::Type); +} + +void hhShuttleDisconnect::EntityEntered(idEntity *ent) { + static_cast(ent)->AllowTractor(false); +} + +void hhShuttleDisconnect::EntityEncroaching(idEntity *ent) { +} + +void hhShuttleDisconnect::EntityLeaving(idEntity *ent) { + static_cast(ent)->AllowTractor(true); +} + + +//----------------------------------------------------------------------- +// +// hhShuttleSlingshot +// +//----------------------------------------------------------------------- + +CLASS_DECLARATION(hhZone, hhShuttleSlingshot) +END_CLASS + +void hhShuttleSlingshot::Spawn(void) { +} + +bool hhShuttleSlingshot::ValidEntity(idEntity *ent) { + return ent && ent->IsType(hhShuttle::Type); +} + +void hhShuttleSlingshot::EntityEntered(idEntity *ent) { +} + +void hhShuttleSlingshot::EntityEncroaching(idEntity *ent) { + float factor = spawnArgs.GetFloat("BoostFactor"); + + hhShuttle *shuttle = static_cast(ent); + shuttle->ApplyBoost( 255.0f * factor); + + // CJR: Alter the player's view when zooming through a slingshot zone + shuttle->GetPilot()->PostEventMS( &EV_SetOverlayMaterial, 0, spawnArgs.GetString( "mtr_speedView" ), -1, false ); + +} + +void hhShuttleSlingshot::EntityLeaving(idEntity *ent) { + // CJR: Reset the player's view after zooming through a slingshot zone + hhShuttle *shuttle = static_cast(ent); + shuttle->GetPilot()->PostEventMS( &EV_SetOverlayMaterial, 0, "", -1, false ); +} + + + +//----------------------------------------------------------------------- +// +// hhRemovalVolume +// +//----------------------------------------------------------------------- + +CLASS_DECLARATION(hhZone, hhRemovalVolume) +END_CLASS + +void hhRemovalVolume::Spawn(void) { +} + +bool hhRemovalVolume::ValidEntity(idEntity *ent) { + return ent && ( + ent->IsType(idMoveable::Type) || + ent->IsType(idItem::Type) || + ent->IsType(hhAFEntity::Type) || + ent->IsType(hhAFEntity_WithAttachedHead::Type) || + (ent->IsType(hhMonsterAI::Type) && ent->health<=0 && !ent->fl.isTractored) ); +} + +void hhRemovalVolume::EntityEntered(idEntity *ent) { + ent->PostEventMS(&EV_Remove, 0); +} + +void hhRemovalVolume::EntityEncroaching(idEntity *ent) { +} + +void hhRemovalVolume::EntityLeaving(idEntity *ent) { +} diff --git a/examples/03 - New Entities/healthzone/game_zone.h b/examples/03 - New Entities/healthzone/game_zone.h new file mode 100644 index 0000000..e71c4e8 --- /dev/null +++ b/examples/03 - New Entities/healthzone/game_zone.h @@ -0,0 +1,282 @@ + +#ifndef __GAME_GRAVITYZONE_H__ +#define __GAME_GRAVITYZONE_H__ + +class hhDock; + +class hhZone : public hhTrigger { +public: + ABSTRACT_PROTOTYPE( hhZone ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + virtual void Present( void ) { } // HUMANHEAD mdl: Not used by zones + + // hhTrigger interface + void TriggerAction(idEntity *activator); + + // hhZone interface + void ResetZoneList(); + void ApplyToEncroachers(); + bool ContainsEntityOfType(const idTypeInfo &t); + virtual void EntityEntered(idEntity *ent) {} + virtual void EntityLeaving(idEntity *ent) {} + virtual void EntityEncroaching(idEntity *ent) {} + virtual bool ValidEntity(idEntity *ent); + virtual void Empty(); + +protected: + void Event_TurnOff(); + void Event_Enable( void ); + void Event_Disable( void ); + void Event_Touch( idEntity *other, trace_t *trace ); + +protected: + idList zoneList; // List of valid entities in zone last frame + float slop; +}; + +//healthzone begin +class hhHealthZone : public hhZone { +public: + CLASS_PROTOTYPE( hhHealthZone ); //the necessary idClass prototypes + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool ValidEntity(idEntity *ent); + virtual void EntityEncroaching(idEntity *ent); +protected: + int regenAmount; +}; +//healthzone end + +class hhTriggerZone : public hhZone { +public: + CLASS_PROTOTYPE( hhTriggerZone ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool ValidEntity(idEntity *ent); + virtual void EntityEntered(idEntity *ent); + virtual void EntityLeaving(idEntity *ent); + virtual void EntityEncroaching(idEntity *ent); + + hhFuncParmAccessor funcRefInfo; +}; + +class hhGravityZoneBase : public hhZone { +public: + ABSTRACT_PROTOTYPE( hhGravityZoneBase ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool ValidEntity(idEntity *ent); + virtual void EntityEntered(idEntity *ent); + virtual void EntityEncroaching(idEntity *ent); + virtual void EntityLeaving(idEntity *ent); + virtual const idVec3 GetGravityOrigin() const; + virtual const idVec3 GetCurrentGravity(const idVec3 &location) const = 0; + + virtual bool TouchingOtherZones(idEntity *ent, bool traceCheck, idVec3 &otherInfluence); + + //rww - network code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + +protected: + bool bReorient; + bool bKillsMonsters; + bool bShowVector; + idVec3 gravityOriginOffset; //rww - avoid dictionary lookup +}; + +class hhGravityZone : public hhGravityZoneBase { +public: + CLASS_PROTOTYPE( hhGravityZone ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + virtual void Think( void ); + virtual const idVec3 GetDestinationGravity() const; + virtual const idVec3 GetCurrentGravity(const idVec3 &location) const; + virtual void SetGravityOnZone( idVec3 &newGravity ); + + //rww - network code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + +protected: + void Event_SetNewGravity( idVec3 &newgrav ); + +protected: + idInterpolate gravityInterpolator; + int interpolationTime; + bool zeroGravOnChange; +}; + +class hhAIWallwalkZone : public hhGravityZone { +public: + CLASS_PROTOTYPE( hhAIWallwalkZone ); + virtual void EntityEncroaching(idEntity *ent); + virtual bool ValidEntity(idEntity *ent); +}; + +class hhGravityZoneInward : public hhGravityZoneBase { +public: + CLASS_PROTOTYPE( hhGravityZoneInward ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void EntityEntered(idEntity *ent); + virtual void EntityLeaving(idEntity *ent); + virtual void EntityEncroaching(idEntity *ent); + + virtual const idVec3 GetCurrentGravity(const idVec3 &location) const; + +protected: + virtual void Event_SetNewGravityFactor( float newFactor ); + +protected: + idInterpolate factorInterpolator; + int interpolationTime; + float monsterGravityFactor; +}; + + +#define GRAVITATIONAL_CONSTANT 1.03416206832413664e-7f + +class hhGravityZoneSinkhole : public hhGravityZoneInward { +public: + CLASS_PROTOTYPE( hhGravityZoneSinkhole ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual const idVec3 GetCurrentGravity(const idVec3 &location) const; + const idVec3 GetCurrentGravityEntity(const idEntity *ent) const; + +protected: + void Event_SetNewGravityFactor( float newFactor ); + +protected: + float maxMagnitude; + float minMagnitude; +}; + + +class hhVelocityZone : public hhZone { +public: + CLASS_PROTOTYPE( hhVelocityZone ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + virtual void Think( void ); + virtual void EntityEncroaching(idEntity *ent); + virtual void EntityLeaving(idEntity *ent); + +protected: + void Event_SetNewVelocity( idVec3 &newvel ); + +protected: + idInterpolate velocityInterpolator; + bool bKillsMonsters; + bool bReorient; + bool bShowVector; + int interpolationTime; +}; + +class hhShuttleRecharge : public hhZone { +public: + CLASS_PROTOTYPE( hhShuttleRecharge ); + + void Spawn(void); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + virtual bool ValidEntity(idEntity *ent); + virtual void EntityEntered(idEntity *ent); + virtual void EntityLeaving(idEntity *ent); + virtual void EntityEncroaching(idEntity *ent); + +protected: + int amountHealth; + int amountPower; +}; + +class hhDockingZone : public hhZone { +public: + CLASS_PROTOTYPE( hhDockingZone ); + + void Spawn(void); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + virtual bool ValidEntity(idEntity *ent); + virtual void EntityEntered(idEntity *ent); + virtual void EntityEncroaching(idEntity *ent); + virtual void EntityLeaving(idEntity *ent); + void RegisterDock(hhDock *d); + + //rww - network code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + +protected: + idEntityPtr dock; +}; + +class hhShuttleDisconnect : public hhZone { +public: + CLASS_PROTOTYPE( hhShuttleDisconnect ); + + void Spawn(void); + virtual bool ValidEntity(idEntity *ent); + virtual void EntityEntered(idEntity *ent); + virtual void EntityEncroaching(idEntity *ent); + virtual void EntityLeaving(idEntity *ent); + +protected: +}; + +class hhShuttleSlingshot : public hhZone { +public: + CLASS_PROTOTYPE( hhShuttleSlingshot ); + + void Spawn(void); + virtual bool ValidEntity(idEntity *ent); + virtual void EntityEntered(idEntity *ent); + virtual void EntityEncroaching(idEntity *ent); + virtual void EntityLeaving(idEntity *ent); + +protected: +}; + +class hhRemovalVolume : public hhZone { +public: + CLASS_PROTOTYPE( hhRemovalVolume ); + + void Spawn(void); + virtual bool ValidEntity(idEntity *ent); + virtual void EntityEntered(idEntity *ent); + virtual void EntityEncroaching(idEntity *ent); + virtual void EntityLeaving(idEntity *ent); + +protected: +}; + +#endif /* __GAME_GRAVITYZONE_H__ */ diff --git a/examples/03 - New Entities/healthzone/healthzone.pk4 b/examples/03 - New Entities/healthzone/healthzone.pk4 new file mode 100644 index 0000000..95744c4 Binary files /dev/null and b/examples/03 - New Entities/healthzone/healthzone.pk4 differ diff --git a/src/2005MayaImport.vcproj b/src/2005MayaImport.vcproj new file mode 100644 index 0000000..ab7a3ba --- /dev/null +++ b/src/2005MayaImport.vcproj @@ -0,0 +1,3722 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/2005game.vcproj b/src/2005game.vcproj new file mode 100644 index 0000000..d8ada14 --- /dev/null +++ b/src/2005game.vcproj @@ -0,0 +1,6529 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/2005idlib.vcproj b/src/2005idlib.vcproj new file mode 100644 index 0000000..1826141 --- /dev/null +++ b/src/2005idlib.vcproj @@ -0,0 +1,1124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/MayaImport/Maya4.5/maya.h b/src/MayaImport/Maya4.5/maya.h new file mode 100644 index 0000000..73b47e0 --- /dev/null +++ b/src/MayaImport/Maya4.5/maya.h @@ -0,0 +1,47 @@ +#ifdef _WIN32 + +#define _BOOL + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#undef _BOOL + +#endif // _WIN32 diff --git a/src/MayaImport/Maya6.0/maya.h b/src/MayaImport/Maya6.0/maya.h new file mode 100644 index 0000000..73b47e0 --- /dev/null +++ b/src/MayaImport/Maya6.0/maya.h @@ -0,0 +1,47 @@ +#ifdef _WIN32 + +#define _BOOL + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#undef _BOOL + +#endif // _WIN32 diff --git a/src/MayaImport/exporter.h b/src/MayaImport/exporter.h new file mode 100644 index 0000000..1e40946 --- /dev/null +++ b/src/MayaImport/exporter.h @@ -0,0 +1,434 @@ +#define MAYA_DEFAULT_CAMERA "camera1" + +#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 { + WRITE_MESH, + WRITE_ANIM, + WRITE_CAMERA +} exportType_t; + +typedef struct { + idCQuat q; + idVec3 t; +} jointFrame_t; + +typedef struct { + idCQuat q; + idVec3 t; + float fov; +} cameraFrame_t; + +/* +============================================================================================== + + idTokenizer + +============================================================================================== +*/ + +class idTokenizer { +private: + int currentToken; + idStrList tokens; + +public: + idTokenizer() { Clear(); }; + void Clear( void ) { currentToken = 0; tokens.Clear(); }; + + int SetTokens( const char *buffer ); + const char *NextToken( const char *errorstring = NULL ); + + bool TokenAvailable( void ) { return currentToken < tokens.Num(); }; + int Num( void ) { return tokens.Num(); }; + void UnGetToken( void ) { if ( currentToken > 0 ) { currentToken--; } }; + const char *GetToken( int index ) { if ( ( index >= 0 ) && ( index < tokens.Num() ) ) { return tokens[ index ]; } else { return NULL; } }; + const char *CurrentToken( void ) { return GetToken( currentToken ); }; +}; + +/* +============================================================================================== + + idExportOptions + +============================================================================================== +*/ + +class idNamePair { +public: + idStr from; + idStr to; +}; + +class idAnimGroup { +public: + idStr name; + idStrList joints; +}; + +class idExportOptions { +private: + idTokenizer tokens; + + void Reset( const char *commandline ); + +public: + idStr commandLine; + idStr src; + idStr dest; + idStr game; + idStr prefix; + float scale; + exportType_t type; + bool ignoreMeshes; + bool clearOrigin; + bool clearOriginAxis; + bool ignoreScale; + int startframe; + int endframe; + int framerate; + float xyzPrecision; + float quatPrecision; + idStr align; + idList renamejoints; + idList remapjoints; + idStrList keepjoints; + idStrList skipmeshes; + idStrList keepmeshes; + idList exportgroups; + idList groups; + float rotate; + float jointThreshold; + int cycleStart; + + // HUMANHEAD pdm: Allow bounds expansion (shared animations calculate bounds based on a different mesh, so give some slop) + float boundsExpansion; + // HUMANHEAD END + + idExportOptions( const char *commandline, const char *ospath ); + + bool jointInExportGroup( const char *jointname ); +}; + +/* +============================================================================== + +idExportJoint + +============================================================================== +*/ + +class idExportJoint { +public: + idStr name; + idStr realname; + idStr longname; + int index; + int exportNum; + bool keep; + + float scale; + float invscale; + + MFnDagNode *dagnode; + + idHierarchy mayaNode; + idHierarchy exportNode; + + idVec3 t; + idMat3 wm; + + idVec3 idt; + idMat3 idwm; + + idVec3 bindpos; + idMat3 bindmat; + + int animBits; + int firstComponent; + jointFrame_t baseFrame; + int depth; + + idExportJoint(); + idExportJoint &operator=( const idExportJoint &other ); +}; + +/* +============================================================================== + +misc structures + +============================================================================== +*/ + +typedef struct { + idExportJoint *joint; + float jointWeight; + idVec3 offset; +} exportWeight_t; + +typedef struct { + idVec3 pos; + idVec2 texCoords; + int startweight; + int numWeights; +} exportVertex_t; + +typedef struct { + int indexes[ 3 ]; +} exportTriangle_t; + +typedef struct { + idVec2 uv[ 3 ]; +} exportUV_t; + +ID_INLINE int operator==( exportVertex_t a, exportVertex_t b ) { + if ( a.pos != b.pos ) { + return false; + } + + if ( ( a.texCoords[ 0 ] != b.texCoords[ 0 ] ) || ( a.texCoords[ 1 ] != b.texCoords[ 1 ] ) ) { + return false; + } + + if ( ( a.startweight != b.startweight ) || ( a.numWeights != b.numWeights ) ) { + return false; + } + + return true; +} + +/* +======================================================================== + +.MD3 triangle model file format + +======================================================================== +*/ + +#define MD3_IDENT (('3'<<24)+('P'<<16)+('D'<<8)+'I') +#define MD3_VERSION 15 + +// limits +#define MD3_MAX_LODS 4 +#define MD3_MAX_TRIANGLES 8192 // per surface +#define MD3_MAX_VERTS 4096 // per surface +#define MD3_MAX_SHADERS 256 // per surface +#define MD3_MAX_FRAMES 1024 // per model +#define MD3_MAX_SURFACES 32 // per model +#define MD3_MAX_TAGS 16 // per frame + +// vertex scales +#define MD3_XYZ_SCALE (1.0/64) + +// surface geometry should not exceed these limits +#define SHADER_MAX_VERTEXES 1000 +#define SHADER_MAX_INDEXES (6*SHADER_MAX_VERTEXES) + + +// the maximum size of game reletive pathnames +#define MAX_Q3PATH 64 + +typedef struct md3Frame_s { + idVec3 bounds[2]; + idVec3 localOrigin; + float radius; + char name[16]; +} md3Frame_t; + +typedef struct md3Tag_s { + char name[MAX_Q3PATH]; // tag name + idVec3 origin; + idVec3 axis[3]; +} md3Tag_t; + +/* +** md3Surface_t +** +** CHUNK SIZE +** header sizeof( md3Surface_t ) +** shaders sizeof( md3Shader_t ) * numShaders +** triangles[0] sizeof( md3Triangle_t ) * numTriangles +** st sizeof( md3St_t ) * numVerts +** XyzNormals sizeof( md3XyzNormal_t ) * numVerts * numFrames +*/ +typedef struct { + int ident; // + + char name[MAX_Q3PATH]; // polyset name + + int flags; + int numFrames; // all surfaces in a model should have the same + + int numShaders; // all surfaces in a model should have the same + int numVerts; + + int numTriangles; + int ofsTriangles; + + int ofsShaders; // offset from start of md3Surface_t + int ofsSt; // texture coords are common for all frames + int ofsXyzNormals; // numVerts * numFrames + + int ofsEnd; // next surface follows +} md3Surface_t; + +typedef struct { + char name[MAX_Q3PATH]; + int shaderIndex; // for in-game use +} md3Shader_t; + +typedef struct { + int indexes[3]; +} md3Triangle_t; + +typedef struct { + float st[2]; +} md3St_t; + +typedef struct { + short xyz[3]; + short normal; +} md3XyzNormal_t; + +typedef struct { + int ident; + int version; + + char name[MAX_Q3PATH]; // model name + + int flags; + + int numFrames; + int numTags; + int numSurfaces; + + int numSkins; + + int ofsFrames; // offset for first frame + int ofsTags; // numFrames * numTags + int ofsSurfaces; // first surface, others follow + + int ofsEnd; // end of file +} md3Header_t; + +/* +============================================================================== + +idExportMesh + +============================================================================== +*/ + +class idExportMesh { +public: + + idStr name; + idStr shader; + + bool keep; + + idList verts; + idList tris; + idList weights; + idList uv; + + idExportMesh() { keep = true; }; + void ShareVerts( void ); + void GetBounds( idBounds &bounds ) const; + void Merge( idExportMesh *mesh ); +}; + +/* +============================================================================== + +idExportModel + +============================================================================== +*/ + +class idExportModel { +public: + idExportJoint *exportOrigin; + idList joints; + idHierarchy mayaHead; + idHierarchy exportHead; + idList cameraCuts; + idList camera; + idList bounds; + idList jointFrames; + idList frames; + int frameRate; + int numFrames; + int skipjoints; + int export_joints; + idList meshes; + + idExportModel(); + ~idExportModel(); + idExportJoint *FindJointReal( const char *name ); + idExportJoint *FindJoint( const char *name ); + bool WriteMesh( const char *filename, idExportOptions &options ); + bool WriteAnim( const char *filename, idExportOptions &options ); + bool WriteCamera( const char *filename, idExportOptions &options ); +}; + +/* +============================================================================== + +Maya + +============================================================================== +*/ + +class idMayaExport { +private: + idExportModel model; + idExportOptions &options; + + void FreeDagNodes( void ); + + float TimeForFrame( int num ) const; + int GetMayaFrameNum( int num ) const; + void SetFrame( int num ); + + + void GetBindPose( MObject &jointNode, idExportJoint *joint, float scale ); + void GetLocalTransform( idExportJoint *joint, idVec3 &pos, idMat3 &mat ); + void GetWorldTransform( idExportJoint *joint, idVec3 &pos, idMat3 &mat, float scale ); + + void CreateJoints( float scale ); + void PruneJoints( idStrList &keepjoints, idStr &prefix ); + void RenameJoints( idList &renamejoints, idStr &prefix ); + bool RemapParents( idList &remapjoints ); + + MObject FindShader( MObject& setNode ); + void GetTextureForMesh( idExportMesh *mesh, MFnDagNode &dagNode ); + + idExportMesh *CopyMesh( MFnSkinCluster &skinCluster, float scale ); + void CreateMesh( float scale ); + void CombineMeshes( void ); + + void GetAlignment( idStr &alignName, idMat3 &align, float rotate, int startframe ); + + const char *GetObjectType( MObject object ); + + float GetCameraFov( idExportJoint *joint ); + void GetCameraFrame( idExportJoint *camera, idMat3 &align, cameraFrame_t *cam ); + void CreateCameraAnim( idMat3 &align ); + + void GetDefaultPose( idMat3 &align ); + void CreateAnimation( idMat3 &align ); + +public: + idMayaExport( idExportOptions &exportOptions ) : options( exportOptions ) { }; + ~idMayaExport(); + + void ConvertModel( void ); + void ConvertToMD3( void ); +}; diff --git a/src/MayaImport/maya5.0/maya.h b/src/MayaImport/maya5.0/maya.h new file mode 100644 index 0000000..672a5bc --- /dev/null +++ b/src/MayaImport/maya5.0/maya.h @@ -0,0 +1,47 @@ +#ifdef _WIN32 + +#define _BOOL + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#undef _BOOL + +#endif // _WIN32 diff --git a/src/MayaImport/maya_main.cpp b/src/MayaImport/maya_main.cpp new file mode 100644 index 0000000..8fc0107 --- /dev/null +++ b/src/MayaImport/maya_main.cpp @@ -0,0 +1,3152 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +// HUMANHEAD CJR : Set-up for Maya 6.0 +#define USE_MAYA_6 1 + +#if USE_MAYA_6 +#undef _GLOBAL_USING +#define _GLOBAL_USING 0 +#include "Maya6.0/maya.h" // must also change include directory in project from "MayaImport\Maya4.5\include" to "MayaImport\Maya6.0\include" (requires MSDev 7.1) +#else +#include "Maya5.0/maya.h" +#endif +// HUMANHEAD END + +#include "exporter.h" +#include "maya_main.h" + +idStr errorMessage; +bool initialized = false; + +#define DEFAULT_ANIM_EPSILON 0.125f +#define DEFAULT_QUAT_EPSILON ( 1.0f / 8192.0f ) + +#define SLOP_VERTEX 0.01f // merge xyz coordinates this far apart +#define SLOP_TEXCOORD 0.001f // merge texture coordinates this far apart + +const char *componentNames[ 6 ] = { "Tx", "Ty", "Tz", "Qx", "Qy", "Qz" }; + +idSys * sys = NULL; +idCommon * common = NULL; +idCVarSystem * cvarSystem = NULL; + +idCVar * idCVar::staticVars = NULL; + +// HUMANHEAD pdm: Output all the animation text with Green color +#define Printf APrintf +// HUMANHEAD END + +/* +================= +MayaError +================= +*/ +void MayaError( const char *fmt, ... ) { + va_list argptr; + char text[ 8192 ]; + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + throw idException( text ); +} + +/* +================= +FS_WriteFloatString +================= +*/ +#define MAX_PRINT_MSG 4096 +static int WriteFloatString( FILE *file, const char *fmt, ... ) { + long i; + unsigned long u; + double f; + char *str; + int index; + idStr tmp, format; + va_list argPtr; + + va_start( argPtr, fmt ); + + index = 0; + + while( *fmt ) { + switch( *fmt ) { + case '%': + format = ""; + format += *fmt++; + while ( (*fmt >= '0' && *fmt <= '9') || + *fmt == '.' || *fmt == '-' || *fmt == '+' || *fmt == '#') { + format += *fmt++; + } + format += *fmt; + switch( *fmt ) { + case 'f': + case 'e': + case 'E': + case 'g': + case 'G': + f = va_arg( argPtr, double ); + if ( format.Length() <= 2 ) { + // high precision floating point number without trailing zeros + sprintf( tmp, "%1.10f", f ); + tmp.StripTrailing( '0' ); + tmp.StripTrailing( '.' ); + index += fprintf( file, "%s", tmp.c_str() ); + } + else { + index += fprintf( file, format.c_str(), f ); + } + break; + case 'd': + case 'i': + i = va_arg( argPtr, long ); + index += fprintf( file, format.c_str(), i ); + break; + case 'u': + u = va_arg( argPtr, unsigned long ); + index += fprintf( file, format.c_str(), u ); + break; + case 'o': + u = va_arg( argPtr, unsigned long ); + index += fprintf( file, format.c_str(), u ); + break; + case 'x': + u = va_arg( argPtr, unsigned long ); + index += fprintf( file, format.c_str(), u ); + break; + case 'X': + u = va_arg( argPtr, unsigned long ); + index += fprintf( file, format.c_str(), u ); + break; + case 'c': + i = va_arg( argPtr, long ); + index += fprintf( file, format.c_str(), (char) i ); + break; + case 's': + str = va_arg( argPtr, char * ); + index += fprintf( file, format.c_str(), str ); + break; + case '%': + index += fprintf( file, format.c_str() ); + break; + default: + MayaError( "WriteFloatString: invalid format %s", format.c_str() ); + break; + } + fmt++; + break; + case '\\': + fmt++; + switch( *fmt ) { + case 't': + index += fprintf( file, "\t" ); + break; + case 'n': + index += fprintf( file, "\n" ); + default: + MayaError( "WriteFloatString: unknown escape character \'%c\'", *fmt ); + break; + } + fmt++; + break; + default: + index += fprintf( file, "%c", *fmt ); + fmt++; + break; + } + } + + va_end( argPtr ); + + return index; +} + +/* +================ +OSPathToRelativePath + +takes a full OS path, as might be found in data from a media creation +program, and converts it to a qpath by stripping off directories + +Returns false if the osPath tree doesn't match any of the existing +search paths. +================ +*/ +bool OSPathToRelativePath( const char *osPath, idStr &qpath, const char *game ) { + char *s, *base; + + // skip a drive letter? + + // search for anything with BASE_GAMEDIR in it + // Ase files from max may have the form of: + // "//Purgatory/purgatory/doom/base/models/mapobjects/bitch/hologirl.tga" + // which won't match any of our drive letter based search paths + base = (char *)strstr( osPath, BASE_GAMEDIR ); //HUMANHEAD rww vs2005 - strstr now returns const char + + // _D3XP added mod support + if ( base == NULL && strlen(game) > 0 ) { + + base = s = (char *)strstr( osPath, game ); //HUMANHEAD rww vs2005 - strstr now returns const char + + while( s = strstr( s, game ) ) { + s += strlen( game ); + if ( s[0] == '/' || s[0] == '\\' ) { + base = s; + } + } + } + + if ( base ) { + s = strstr( base, "/" ); + if ( !s ) { + s = strstr( base, "\\" ); + } + if ( s ) { + qpath = s + 1; + return true; + } + } + + common->Printf( "OSPathToRelativePath failed on %s\n", osPath ); + qpath = osPath; + + return false; +} + +/* +=============== +ConvertFromIdSpace +=============== +*/ +idMat3 ConvertFromIdSpace( const idMat3 &idmat ) { + idMat3 mat; + + mat[ 0 ][ 0 ] = idmat[ 0 ][ 0 ]; + mat[ 0 ][ 2 ] = -idmat[ 0 ][ 1 ]; + mat[ 0 ][ 1 ] = idmat[ 0 ][ 2 ]; + + mat[ 1 ][ 0 ] = idmat[ 1 ][ 0 ]; + mat[ 1 ][ 2 ] = -idmat[ 1 ][ 1 ]; + mat[ 1 ][ 1 ] = idmat[ 1 ][ 2 ]; + + mat[ 2 ][ 0 ] = idmat[ 2 ][ 0 ]; + mat[ 2 ][ 2 ] = -idmat[ 2 ][ 1 ]; + mat[ 2 ][ 1 ] = idmat[ 2 ][ 2 ]; + + return mat; +} + +/* +=============== +ConvertFromIdSpace +=============== +*/ +idVec3 ConvertFromIdSpace( const idVec3 &idpos ) { + idVec3 pos; + + pos.x = idpos.x; + pos.z = -idpos.y; + pos.y = idpos.z; + + return pos; +} + +/* +=============== +ConvertToIdSpace +=============== +*/ +idMat3 ConvertToIdSpace( const idMat3 &mat ) { + idMat3 idmat; + + idmat[ 0 ][ 0 ] = mat[ 0 ][ 0 ]; + idmat[ 0 ][ 1 ] = -mat[ 0 ][ 2 ]; + idmat[ 0 ][ 2 ] = mat[ 0 ][ 1 ]; + + idmat[ 1 ][ 0 ] = mat[ 1 ][ 0 ]; + idmat[ 1 ][ 1 ] = -mat[ 1 ][ 2 ]; + idmat[ 1 ][ 2 ] = mat[ 1 ][ 1 ]; + + idmat[ 2 ][ 0 ] = mat[ 2 ][ 0 ]; + idmat[ 2 ][ 1 ] = -mat[ 2 ][ 2 ]; + idmat[ 2 ][ 2 ] = mat[ 2 ][ 1 ]; + + return idmat; +} + +/* +=============== +ConvertToIdSpace +=============== +*/ +idVec3 ConvertToIdSpace( const idVec3 &pos ) { + idVec3 idpos; + + idpos.x = pos.x; + idpos.y = -pos.z; + idpos.z = pos.y; + + return idpos; +} + +/* +=============== +idVec +=============== +*/ +idVec3 idVec( const MFloatPoint &point ) { + return idVec3( point[ 0 ], point[ 1 ], point[ 2 ] ); +} + +/* +=============== +idVec +=============== +*/ +idVec3 idVec( const MMatrix &matrix ) { + return idVec3( matrix[ 3 ][ 0 ], matrix[ 3 ][ 1 ], matrix[ 3 ][ 2 ] ); +} + +/* +=============== +idMat +=============== +*/ +idMat3 idMat( const MMatrix &matrix ) { + int j, k; + idMat3 mat; + + for( j = 0; j < 3; j++ ) { + for( k = 0; k < 3; k++ ) { + mat[ j ][ k ] = matrix[ j ][ k ]; + } + } + + return mat; +} + +/* +=============== +GetParent +=============== +*/ +MFnDagNode *GetParent( MFnDagNode *joint ) { + MStatus status; + MObject parentObject; + + parentObject = joint->parent( 0, &status ); + if ( !status && status.statusCode() == MStatus::kInvalidParameter ) { + return NULL; + } + + while( !parentObject.hasFn( MFn::kTransform ) ) { + MFnDagNode parentNode( parentObject, &status ); + if ( !status ) { + return NULL; + } + + parentObject = parentNode.parent( 0, &status ); + if ( !status && status.statusCode() == MStatus::kInvalidParameter ) { + return NULL; + } + } + + MFnDagNode *parentNode; + + parentNode = new MFnDagNode( parentObject, &status ); + if ( !status ) { + delete parentNode; + return NULL; + } + + return parentNode; +} + +/* +============================================================================================== + + idTokenizer + +============================================================================================== +*/ + +/* +==================== +idTokenizer::SetTokens +==================== +*/ +int idTokenizer::SetTokens( const char *buffer ) { + const char *cmd; + + Clear(); + + // tokenize commandline + cmd = buffer; + while ( *cmd ) { + // skip whitespace + while( *cmd && isspace( *cmd ) ) { + cmd++; + } + + if ( !*cmd ) { + break; + } + + idStr ¤t = tokens.Alloc(); + while( *cmd && !isspace( *cmd ) ) { + current += *cmd; + cmd++; + } + } + + return tokens.Num(); +} + +/* +==================== +idTokenizer::NextToken +==================== +*/ +const char *idTokenizer::NextToken( const char *errorstring ) { + if ( currentToken < tokens.Num() ) { + return tokens[ currentToken++ ]; + } + + if ( errorstring ) { + MayaError( "Error: %s", errorstring ); + } + + return NULL; +} + +/* +============================================================================================== + + idExportOptions + +============================================================================================== +*/ + +/* +==================== +idExportOptions::Reset +==================== +*/ +void idExportOptions::Reset( const char *commandline ) { + scale = 1.0f; + type = WRITE_MESH; + startframe = -1; + endframe = -1; + ignoreMeshes = false; + clearOrigin = false; + clearOriginAxis = false; + framerate = 24; + align = ""; + rotate = 0.0f; + commandLine = commandline; + prefix = ""; + jointThreshold = 0.05f; + ignoreScale = false; + xyzPrecision = DEFAULT_ANIM_EPSILON; + quatPrecision = DEFAULT_QUAT_EPSILON; + cycleStart = -1; + + // HUMANHEAD pdm: Allow bounds expansion (shared animations calculate bounds based on a different mesh, so give some slop) + boundsExpansion = 0.0f; + // HUMANHEAD END + + src.Clear(); + dest.Clear(); + + tokens.SetTokens( commandline ); + + keepjoints.Clear(); + renamejoints.Clear(); + remapjoints.Clear(); + exportgroups.Clear(); + skipmeshes.Clear(); + keepmeshes.Clear(); + groups.Clear(); +} + +/* +==================== +idExportOptions::idExportOptions +==================== +*/ +idExportOptions::idExportOptions( const char *commandline, const char *ospath ) { + idStr token; + idNamePair joints; + int i; + idAnimGroup *group; + idStr sourceDir; + idStr destDir; + + Reset( commandline ); + + token = tokens.NextToken( "Missing export command" ); + if ( token == "mesh" ) { + type = WRITE_MESH; + } else if ( token == "anim" ) { + type = WRITE_ANIM; + } else if ( token == "camera" ) { + type = WRITE_CAMERA; + } else { + MayaError( "Unknown export command '%s'", token.c_str() ); + } + + src = tokens.NextToken( "Missing source filename" ); + dest = src; + + for( token = tokens.NextToken(); token != ""; token = tokens.NextToken() ) { + if ( token == "-force" ) { + // skip + } else if ( token == "-game" ) { + // parse game name + game = tokens.NextToken( "Expecting game name after -game" ); + + } else if ( token == "-rename" ) { + // parse joint to rename + joints.from = tokens.NextToken( "Missing joint name for -rename. Usage: -rename [joint name] [new name]" ); + joints.to = tokens.NextToken( "Missing new name for -rename. Usage: -rename [joint name] [new name]" ); + renamejoints.Append( joints ); + + } else if ( token == "-prefix" ) { + prefix = tokens.NextToken( "Missing name for -prefix. Usage: -prefix [joint prefix]" ); + + } else if ( token == "-parent" ) { + // parse joint to reparent + joints.from = tokens.NextToken( "Missing joint name for -parent. Usage: -parent [joint name] [new parent]" ); + joints.to = tokens.NextToken( "Missing new parent for -parent. Usage: -parent [joint name] [new parent]" ); + remapjoints.Append( joints ); + + } else if ( !token.Icmp( "-sourcedir" ) ) { + // parse source directory + sourceDir = tokens.NextToken( "Missing filename for -sourcedir. Usage: -sourcedir [directory]" ); + + } else if ( !token.Icmp( "-destdir" ) ) { + // parse destination directory + destDir = tokens.NextToken( "Missing filename for -destdir. Usage: -destdir [directory]" ); + + } else if ( token == "-dest" ) { + // parse destination filename + dest = tokens.NextToken( "Missing filename for -dest. Usage: -dest [filename]" ); + + } else if ( token == "-range" ) { + // parse frame range to export + token = tokens.NextToken( "Missing start frame for -range. Usage: -range [start frame] [end frame]" ); + startframe = atoi( token ); + token = tokens.NextToken( "Missing end frame for -range. Usage: -range [start frame] [end frame]" ); + endframe = atoi( token ); + + if ( startframe > endframe ) { + MayaError( "Start frame is greater than end frame." ); + } + + } else if ( !token.Icmp( "-cycleStart" ) ) { + // parse start frame of cycle + token = tokens.NextToken( "Missing cycle start frame for -cycleStart. Usage: -cycleStart [first frame of cycle]" ); + cycleStart = atoi( token ); + + } else if ( token == "-scale" ) { + // parse scale + token = tokens.NextToken( "Missing scale amount for -scale. Usage: -scale [scale amount]" ); + scale = atof( token ); + + } else if ( token == "-align" ) { + // parse align joint + align = tokens.NextToken( "Missing joint name for -align. Usage: -align [joint name]" ); + + // HUMANHEAD pdm: Allow bounds expansion (shared animations calculate bounds based on a different mesh, so give some slop) + } else if ( token == "-expandbounds" ) { + // parse boundsexpansion + token = tokens.NextToken( "Missing expansion amount for -expandbounds. Usage: -expandbounds [expansion amount]" ); + boundsExpansion = atof( token ); + // HUMANHEAD END + + } else if ( token == "-rotate" ) { + // parse angle rotation + token = tokens.NextToken( "Missing value for -rotate. Usage: -rotate [yaw]" ); + rotate = -atof( token ); + + } else if ( token == "-nomesh" ) { + ignoreMeshes = true; + + } else if ( token == "-clearorigin" ) { + clearOrigin = true; + clearOriginAxis = true; + + } else if ( token == "-clearoriginaxis" ) { + clearOriginAxis = true; + + } else if ( token == "-ignorescale" ) { + ignoreScale = true; + + } else if ( token == "-xyzprecision" ) { + // parse quaternion precision + token = tokens.NextToken( "Missing value for -xyzprecision. Usage: -xyzprecision [precision]" ); + xyzPrecision = atof( token ); + if ( xyzPrecision < 0.0f ) { + MayaError( "Invalid value for -xyzprecision. Must be >= 0" ); + } + + } else if ( token == "-quatprecision" ) { + // parse quaternion precision + token = tokens.NextToken( "Missing value for -quatprecision. Usage: -quatprecision [precision]" ); + quatPrecision = atof( token ); + if ( quatPrecision < 0.0f ) { + MayaError( "Invalid value for -quatprecision. Must be >= 0" ); + } + + } else if ( token == "-jointthreshold" ) { + // parse joint threshold + token = tokens.NextToken( "Missing weight for -jointthreshold. Usage: -jointthreshold [minimum joint weight]" ); + jointThreshold = atof( token ); + + } else if ( token == "-skipmesh" ) { + token = tokens.NextToken( "Missing name for -skipmesh. Usage: -skipmesh [name of mesh to skip]" ); + skipmeshes.AddUnique( token ); + + } else if ( token == "-keepmesh" ) { + token = tokens.NextToken( "Missing name for -keepmesh. Usage: -keepmesh [name of mesh to keep]" ); + keepmeshes.AddUnique( token ); + + } else if ( token == "-jointgroup" ) { + token = tokens.NextToken( "Missing name for -jointgroup. Usage: -jointgroup [group name] [joint1] [joint2]...[joint n]" ); + group = groups.Ptr(); + for( i = 0; i < groups.Num(); i++, group++ ) { + if ( group->name == token ) { + break; + } + } + + if ( i >= groups.Num() ) { + // create a new group + group = &groups.Alloc(); + group->name = token; + } + + while( tokens.TokenAvailable() ) { + token = tokens.NextToken(); + if ( token[ 0 ] == '-' ) { + tokens.UnGetToken(); + break; + } + + group->joints.AddUnique( token ); + } + } else if ( token == "-group" ) { + // add the list of groups to export (these don't affect the hierarchy) + while( tokens.TokenAvailable() ) { + token = tokens.NextToken(); + if ( token[ 0 ] == '-' ) { + tokens.UnGetToken(); + break; + } + + group = groups.Ptr(); + for( i = 0; i < groups.Num(); i++, group++ ) { + if ( group->name == token ) { + break; + } + } + + if ( i >= groups.Num() ) { + MayaError( "Unknown group '%s'", token.c_str() ); + } + + exportgroups.AddUnique( group ); + } + } else if ( token == "-keep" ) { + // add joints that are kept whether they're used by a mesh or not + while( tokens.TokenAvailable() ) { + token = tokens.NextToken(); + if ( token[ 0 ] == '-' ) { + tokens.UnGetToken(); + break; + } + keepjoints.AddUnique( token ); + } + } else { + MayaError( "Unknown option '%s'", token.c_str() ); + } + } + + token = src; + src = ospath; + src.BackSlashesToSlashes(); + src.AppendPath( sourceDir ); + src.AppendPath( token ); + + token = dest; + dest = ospath; + dest.BackSlashesToSlashes(); + dest.AppendPath( destDir ); + dest.AppendPath( token ); + + // Maya only accepts unix style path separators + src.BackSlashesToSlashes(); + dest.BackSlashesToSlashes(); + + if ( skipmeshes.Num() && keepmeshes.Num() ) { + MayaError( "Can't use -keepmesh and -skipmesh together." ); + } +} + +/* +==================== +idExportOptions::jointInExportGroup +==================== +*/ +bool idExportOptions::jointInExportGroup( const char *jointname ) { + int i; + int j; + idAnimGroup *group; + + if ( !exportgroups.Num() ) { + // if we don't have any groups specified as export then export every joint + return true; + } + + // search through all exported groups to see if this joint is exported + for( i = 0; i < exportgroups.Num(); i++ ) { + group = exportgroups[ i ]; + for( j = 0; j < group->joints.Num(); j++ ) { + if ( group->joints[ j ] == jointname ) { + return true; + } + } + } + + return false; +} + +/* +============================================================================== + +idExportJoint + +============================================================================== +*/ + +idExportJoint::idExportJoint() { + index = 0; + exportNum = 0; + + mayaNode.SetOwner( this ); + exportNode.SetOwner( this ); + + dagnode = NULL; + + t = vec3_zero; + wm = mat3_default; + bindpos = vec3_zero; + bindmat = mat3_default; + keep = false; + scale = 1.0f; + invscale = 1.0f; + animBits = 0; + firstComponent = 0; + baseFrame.q.Set( 0.0f, 0.0f, 0.0f ); + baseFrame.t.Zero(); +} + +idExportJoint &idExportJoint::operator=( const idExportJoint &other ) { + name = other.name; + realname = other.realname; + longname = other.longname; + index = other.index; + exportNum = other.exportNum; + keep = other.keep; + + scale = other.scale; + invscale = other.invscale; + + dagnode = other.dagnode; + + mayaNode = other.mayaNode; + exportNode = other.exportNode; + + t = other.t; + idt = other.idt; + wm = other.wm; + idwm = other.idwm; + bindpos = other.bindpos; + bindmat = other.bindmat; + + animBits = other.animBits; + firstComponent = other.firstComponent; + baseFrame = other.baseFrame; + + mayaNode.SetOwner( this ); + exportNode.SetOwner( this ); + + return *this; +} + +/* +============================================================================== + +idExportMesh + +============================================================================== +*/ + +void idExportMesh::ShareVerts( void ) { + int i, j, k; + exportVertex_t vert; + idList v; + + v = verts; + verts.Clear(); + for( i = 0; i < tris.Num(); i++ ) { + for( j = 0; j < 3; j++ ) { + vert = v[ tris[ i ].indexes[ j ] ]; + vert.texCoords[ 0 ] = uv[ i ].uv[ j ][ 0 ]; + vert.texCoords[ 1 ] = 1.0f - uv[ i ].uv[ j ][ 1 ]; + + for( k = 0; k < verts.Num(); k++ ) { + if ( vert.numWeights != verts[ k ].numWeights ) { + continue; + } + if ( vert.startweight != verts[ k ].startweight ) { + continue; + } + if ( !vert.pos.Compare( verts[ k ].pos, SLOP_VERTEX ) ) { + continue; + } + if ( !vert.texCoords.Compare( verts[ k ].texCoords, SLOP_TEXCOORD ) ) { + continue; + } + + break; + } + + if ( k < verts.Num() ) { + tris[ i ].indexes[ j ] = k; + } else { + tris[ i ].indexes[ j ] = verts.Append( vert ); + } + } + } +} + +void idExportMesh::Merge( idExportMesh *mesh ) { + int i; + int numverts; + int numtris; + int numweights; + int numuvs; + + // merge name + sprintf( name, "%s, %s", name.c_str(), mesh->name.c_str() ); + + // merge verts + numverts = verts.Num(); + verts.SetNum( numverts + mesh->verts.Num() ); + for( i = 0; i < mesh->verts.Num(); i++ ) { + verts[ numverts + i ] = mesh->verts[ i ]; + verts[ numverts + i ].startweight += weights.Num(); + } + + // merge triangles + numtris = tris.Num(); + tris.SetNum( numtris + mesh->tris.Num() ); + for( i = 0; i < mesh->tris.Num(); i++ ) { + tris[ numtris + i ].indexes[ 0 ] = mesh->tris[ i ].indexes[ 0 ] + numverts; + tris[ numtris + i ].indexes[ 1 ] = mesh->tris[ i ].indexes[ 1 ] + numverts; + tris[ numtris + i ].indexes[ 2 ] = mesh->tris[ i ].indexes[ 2 ] + numverts; + } + + // merge weights + numweights = weights.Num(); + weights.SetNum( numweights + mesh->weights.Num() ); + for( i = 0; i < mesh->weights.Num(); i++ ) { + weights[ numweights + i ] = mesh->weights[ i ]; + } + + // merge uvs + numuvs = uv.Num(); + uv .SetNum( numuvs + mesh->uv.Num() ); + for( i = 0; i < mesh->uv.Num(); i++ ) { + uv[ numuvs + i ] = mesh->uv[ i ]; + } +} + +void idExportMesh::GetBounds( idBounds &bounds ) const { + int i; + int j; + idVec3 pos; + const exportWeight_t *weight; + const exportVertex_t *vert; + + bounds.Clear(); + + weight = weights.Ptr(); + vert = verts.Ptr(); + for( i = 0; i < verts.Num(); i++, vert++ ) { + pos.Zero(); + weight = &weights[ vert->startweight ]; + for( j = 0; j < vert->numWeights; j++, weight++ ) { + pos += weight->jointWeight * ( weight->joint->idwm * weight->offset + weight->joint->idt ); + } + bounds.AddPoint( pos ); + } +} + +/* +============================================================================== + +idExportModel + +============================================================================== +*/ + +/* +==================== +idExportModel::idExportModel +==================== +*/ +ID_INLINE idExportModel::idExportModel() { + export_joints = 0; + skipjoints = 0; + frameRate = 24; + numFrames = 0; + exportOrigin = NULL; +} + +/* +==================== +idExportModel::~idExportModel +==================== +*/ +ID_INLINE idExportModel::~idExportModel() { + meshes.DeleteContents( true ); +} + +idExportJoint *idExportModel::FindJointReal( const char *name ) { + idExportJoint *joint; + int i; + + joint = joints.Ptr(); + for( i = 0; i < joints.Num(); i++, joint++ ) { + if ( joint->realname == name ) { + return joint; + } + } + + return NULL; +} + +idExportJoint *idExportModel::FindJoint( const char *name ) { + idExportJoint *joint; + int i; + + joint = joints.Ptr(); + for( i = 0; i < joints.Num(); i++, joint++ ) { + if ( joint->name == name ) { + return joint; + } + } + + return NULL; +} + +bool idExportModel::WriteMesh( const char *filename, idExportOptions &options ) { + int i, j; + int numMeshes; + idExportMesh *mesh; + idExportJoint *joint; + idExportJoint *parent; + idExportJoint *sibling; + FILE *file; + const char *parentName; + int parentNum; + idList jointList; + + file = fopen( filename, "w" ); + if ( !file ) { + return false; + } + + for( joint = exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext() ) { + jointList.Append( joint ); + } + + for( i = 0; i < jointList.Num(); i++ ) { + joint = jointList[ i ]; + sibling = joint->exportNode.GetSibling(); + while( sibling ) { + if ( idStr::Cmp( joint->name, sibling->name ) > 0 ) { + joint->exportNode.MakeSiblingAfter( sibling->exportNode ); + sibling = joint->exportNode.GetSibling(); + } else { + sibling = sibling->exportNode.GetSibling(); + } + } + } + + jointList.Clear(); + for( joint = exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext() ) { + joint->exportNum = jointList.Append( joint ); + } + + numMeshes = 0; + if ( !options.ignoreMeshes ) { + for( i = 0; i < meshes.Num(); i++ ) { + if ( meshes[ i ]->keep ) { + numMeshes++; + } + } + } + + // write version info + WriteFloatString( file, MD5_VERSION_STRING " %d\n", MD5_VERSION ); + WriteFloatString( file, "commandline \"%s\"\n\n", options.commandLine.c_str() ); + + // write joints + WriteFloatString( file, "numJoints %d\n", jointList.Num() ); + WriteFloatString( file, "numMeshes %d\n\n", numMeshes ); + + WriteFloatString( file, "joints {\n" ); + for( i = 0; i < jointList.Num(); i++ ) { + joint = jointList[ i ]; + parent = joint->exportNode.GetParent(); + if ( parent ) { + parentNum = parent->exportNum; + parentName = parent->name.c_str(); + } else { + parentNum = -1; + parentName = ""; + } + + idCQuat bindQuat = joint->bindmat.ToQuat().ToCQuat(); + WriteFloatString( file, "\t\"%s\"\t%d ( %f %f %f ) ( %f %f %f )\t\t// %s\n", joint->name.c_str(), parentNum, + joint->bindpos.x, joint->bindpos.y, joint->bindpos.z, bindQuat[ 0 ], bindQuat[ 1 ], bindQuat[ 2 ], parentName ); + } + WriteFloatString( file, "}\n" ); + + // write meshes + for( i = 0; i < meshes.Num(); i++ ) { + mesh = meshes[ i ]; + if ( !mesh->keep ) { + continue; + } + + WriteFloatString( file, "\nmesh {\n" ); + WriteFloatString( file, "\t// meshes: %s\n", mesh->name.c_str() ); + WriteFloatString( file, "\tshader \"%s\"\n", mesh->shader.c_str() ); + + WriteFloatString( file, "\n\tnumverts %d\n", mesh->verts.Num() ); + for( j = 0; j < mesh->verts.Num(); j++ ) { + WriteFloatString( file, "\tvert %d ( %f %f ) %d %d\n", j, mesh->verts[ j ].texCoords[ 0 ], mesh->verts[ j ].texCoords[ 1 ], + mesh->verts[ j ].startweight, mesh->verts[ j ].numWeights ); + } + + WriteFloatString( file, "\n\tnumtris %d\n", mesh->tris.Num() ); + for( j = 0; j < mesh->tris.Num(); j++ ) { + WriteFloatString( file, "\ttri %d %d %d %d\n", j, mesh->tris[ j ].indexes[ 2 ], mesh->tris[ j ].indexes[ 1 ], mesh->tris[ j ].indexes[ 0 ] ); + } + + WriteFloatString( file, "\n\tnumweights %d\n", mesh->weights.Num() ); + for( j = 0; j < mesh->weights.Num(); j++ ) { + exportWeight_t *weight; + + weight = &mesh->weights[ j ]; + WriteFloatString( file, "\tweight %d %d %f ( %f %f %f )\n", j, + weight->joint->exportNum, weight->jointWeight, weight->offset.x, weight->offset.y, weight->offset.z ); + } + + WriteFloatString( file, "}\n" ); + } + + fclose( file ); + + return true; +} + +bool idExportModel::WriteAnim( const char *filename, idExportOptions &options ) { + int i, j; + idExportJoint *joint; + idExportJoint *parent; + idExportJoint *sibling; + jointFrame_t *frame; + FILE *file; + int numAnimatedComponents; + idList jointList; + + file = fopen( filename, "w" ); + if ( !file ) { + return false; + } + + for( joint = exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext() ) { + jointList.Append( joint ); + } + + for( i = 0; i < jointList.Num(); i++ ) { + joint = jointList[ i ]; + sibling = joint->exportNode.GetSibling(); + while( sibling ) { + if ( idStr::Cmp( joint->name, sibling->name ) > 0 ) { + joint->exportNode.MakeSiblingAfter( sibling->exportNode ); + sibling = joint->exportNode.GetSibling(); + } else { + sibling = sibling->exportNode.GetSibling(); + } + } + } + + jointList.Clear(); + for( joint = exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext() ) { + joint->exportNum = jointList.Append( joint ); + } + + numAnimatedComponents = 0; + for( i = 0; i < jointList.Num(); i++ ) { + joint = jointList[ i ]; + joint->exportNum = i; + joint->baseFrame = frames[ 0 ][ joint->index ]; + joint->animBits = 0; + for( j = 1; j < numFrames; j++ ) { + frame = &frames[ j ][ joint->index ]; + if ( fabs( frame->t[ 0 ] - joint->baseFrame.t[ 0 ] ) > options.xyzPrecision ) { + joint->animBits |= ANIM_TX; + } + if ( fabs( frame->t[ 1 ] - joint->baseFrame.t[ 1 ] ) > options.xyzPrecision ) { + joint->animBits |= ANIM_TY; + } + if ( fabs( frame->t[ 2 ] - joint->baseFrame.t[ 2 ] ) > options.xyzPrecision ) { + joint->animBits |= ANIM_TZ; + } + if ( fabs( frame->q[ 0 ] - joint->baseFrame.q[ 0 ] ) > options.quatPrecision ) { + joint->animBits |= ANIM_QX; + } + if ( fabs( frame->q[ 1 ] - joint->baseFrame.q[ 1 ] ) > options.quatPrecision ) { + joint->animBits |= ANIM_QY; + } + if ( fabs( frame->q[ 2 ] - joint->baseFrame.q[ 2 ] ) > options.quatPrecision ) { + joint->animBits |= ANIM_QZ; + } + if ( ( joint->animBits & 63 ) == 63 ) { + break; + } + } + if ( joint->animBits ) { + joint->firstComponent = numAnimatedComponents; + for( j = 0; j < 6; j++ ) { + if ( joint->animBits & BIT( j ) ) { + numAnimatedComponents++; + } + } + } + } + + // write version info + WriteFloatString( file, MD5_VERSION_STRING " %d\n", MD5_VERSION ); + WriteFloatString( file, "commandline \"%s\"\n\n", options.commandLine.c_str() ); + + WriteFloatString( file, "numFrames %d\n", numFrames ); + WriteFloatString( file, "numJoints %d\n", jointList.Num() ); + WriteFloatString( file, "frameRate %d\n", frameRate ); + WriteFloatString( file, "numAnimatedComponents %d\n", numAnimatedComponents ); + + // write out the hierarchy + WriteFloatString( file, "\nhierarchy {\n" ); + for( i = 0; i < jointList.Num(); i++ ) { + joint = jointList[ i ]; + parent = joint->exportNode.GetParent(); + if ( parent ) { + WriteFloatString( file, "\t\"%s\"\t%d %d %d\t// %s", joint->name.c_str(), parent->exportNum, joint->animBits, joint->firstComponent, parent->name.c_str() ); + } else { + WriteFloatString( file, "\t\"%s\"\t-1 %d %d\t//", joint->name.c_str(), joint->animBits, joint->firstComponent ); + } + + if ( !joint->animBits ) { + WriteFloatString( file, "\n" ); + } else { + WriteFloatString( file, " ( " ); + for( j = 0; j < 6; j++ ) { + if ( joint->animBits & BIT( j ) ) { + WriteFloatString( file, "%s ", componentNames[ j ] ); + } + } + WriteFloatString( file, ")\n" ); + } + } + WriteFloatString( file, "}\n" ); + + // write the frame bounds + WriteFloatString( file, "\nbounds {\n" ); + for( i = 0; i < numFrames; i++ ) { +#if 1 // HUMANHEAD pdm: Allow bounds expansion (shared animations calculate bounds based on a different mesh, so give some slop) + idBounds expandedBounds = bounds[i].Expand( options.boundsExpansion ); + WriteFloatString( file, "\t( %f %f %f ) ( %f %f %f )\n", expandedBounds[ 0 ].x, expandedBounds[ 0 ].y, expandedBounds[ 0 ].z, expandedBounds[ 1 ].x, expandedBounds[ 1 ].y, expandedBounds[ 1 ].z ); +#else // HUMANHEAD END + WriteFloatString( file, "\t( %f %f %f ) ( %f %f %f )\n", bounds[ i ][ 0 ].x, bounds[ i ][ 0 ].y, bounds[ i ][ 0 ].z, bounds[ i ][ 1 ].x, bounds[ i ][ 1 ].y, bounds[ i ][ 1 ].z ); +#endif + } + WriteFloatString( file, "}\n" ); + + // write the base frame + WriteFloatString( file, "\nbaseframe {\n" ); + for( i = 0; i < jointList.Num(); i++ ) { + joint = jointList[ i ]; + WriteFloatString( file, "\t( %f %f %f ) ( %f %f %f )\n", joint->baseFrame.t[ 0 ], joint->baseFrame.t[ 1 ], joint->baseFrame.t[ 2 ], + joint->baseFrame.q[ 0 ], joint->baseFrame.q[ 1 ], joint->baseFrame.q[ 2 ] ); + } + WriteFloatString( file, "}\n" ); + + // write the frames + for( i = 0; i < numFrames; i++ ) { + WriteFloatString( file, "\nframe %d {\n", i ); + for( j = 0; j < jointList.Num(); j++ ) { + joint = jointList[ j ]; + frame = &frames[ i ][ joint->index ]; + if ( joint->animBits ) { + WriteFloatString( file, "\t" ); + if ( joint->animBits & ANIM_TX ) { + WriteFloatString( file, " %f", frame->t[ 0 ] ); + } + if ( joint->animBits & ANIM_TY ) { + WriteFloatString( file, " %f", frame->t[ 1 ] ); + } + if ( joint->animBits & ANIM_TZ ) { + WriteFloatString( file, " %f", frame->t[ 2 ] ); + } + if ( joint->animBits & ANIM_QX ) { + WriteFloatString( file, " %f", frame->q[ 0 ] ); + } + if ( joint->animBits & ANIM_QY ) { + WriteFloatString( file, " %f", frame->q[ 1 ] ); + } + if ( joint->animBits & ANIM_QZ ) { + WriteFloatString( file, " %f", frame->q[ 2 ] ); + } + WriteFloatString( file, "\n" ); + } + } + WriteFloatString( file, "}\n" ); + } + + fclose( file ); + + return true; +} + +bool idExportModel::WriteCamera( const char *filename, idExportOptions &options ) { + int i; + FILE *file; + + file = fopen( filename, "w" ); + if ( !file ) { + return false; + } + + // write version info + WriteFloatString( file, MD5_VERSION_STRING " %d\n", MD5_VERSION ); + WriteFloatString( file, "commandline \"%s\"\n\n", options.commandLine.c_str() ); + + WriteFloatString( file, "numFrames %d\n", camera.Num() ); + WriteFloatString( file, "frameRate %d\n", frameRate ); + WriteFloatString( file, "numCuts %d\n", cameraCuts.Num() ); + + // write out the cuts + WriteFloatString( file, "\ncuts {\n" ); + for( i = 0; i < cameraCuts.Num(); i++ ) { + WriteFloatString( file, "\t%d\n", cameraCuts[ i ] ); + } + WriteFloatString( file, "}\n" ); + + // write out the frames + WriteFloatString( file, "\ncamera {\n" ); + cameraFrame_t *frame = camera.Ptr(); + for( i = 0; i < camera.Num(); i++, frame++ ) { + WriteFloatString( file, "\t( %f %f %f ) ( %f %f %f ) %f\n", frame->t.x, frame->t.y, frame->t.z, frame->q[ 0 ], frame->q[ 1 ], frame->q[ 2 ], frame->fov ); + } + WriteFloatString( file, "}\n" ); + + fclose( file ); + + return true; +} + +/* +============================================================================== + +Maya + +============================================================================== +*/ + +/* +=============== +idMayaExport::~idMayaExport + +=============== +*/ +idMayaExport::~idMayaExport() { + FreeDagNodes(); + + // free up the file in Maya + MFileIO::newFile( true ); +} + +/* +=============== +idMayaExport::TimeForFrame +=============== +*/ +float idMayaExport::TimeForFrame( int num ) const { + MTime time; + + // set time unit to 24 frames per second + time.setUnit( MTime::kFilm ); + time.setValue( num ); + return time.as( MTime::kSeconds ); +} + +/* +=============== +idMayaExport::GetMayaFrameNum +=============== +*/ +int idMayaExport::GetMayaFrameNum( int num ) const { + int frameNum; + + if ( options.cycleStart > options.startframe ) { + // in cycles, the last frame is a duplicate of the first frame, so with cycleStart we need to + // duplicate one of the interior frames instead and chop off the first frame. + frameNum = options.cycleStart + num; + if ( frameNum > options.endframe ) { + frameNum -= options.endframe - options.startframe; + } + if ( frameNum < options.startframe ) { + frameNum = options.startframe + 1; + } + } else { + frameNum = options.startframe + num; + if ( frameNum > options.endframe ) { + frameNum -= options.endframe + 1 - options.startframe; + } + if ( frameNum < options.startframe ) { + frameNum = options.startframe; + } + } + + return frameNum; +} + +/* +=============== +idMayaExport::SetFrame +=============== +*/ +void idMayaExport::SetFrame( int num ) { + MTime time; + int frameNum; + + frameNum = GetMayaFrameNum( num ); + + // set time unit to 24 frames per second + time.setUnit( MTime::kFilm ); + time.setValue( frameNum ); + MGlobal::viewFrame( time ); +} + +/* +=============== +idMayaExport::PruneJoints +=============== +*/ +void idMayaExport::PruneJoints( idStrList &keepjoints, idStr &prefix ) { + int i; + int j; + idExportMesh *mesh; + idExportJoint *joint; + idExportJoint *joint2; + idExportJoint *parent; + int num_weights; + + // if we don't have any joints specified to keep, mark the ones used by the meshes as keep + if ( !keepjoints.Num() && !prefix.Length() ) { + if ( !model.meshes.Num() || options.ignoreMeshes ) { + // export all joints + joint = model.joints.Ptr(); + for( i = 0; i < model.joints.Num(); i++, joint++ ) { + joint->keep = true; + } + } else { + for( i = 0; i < model.meshes.Num(); i++, mesh++ ) { + mesh = model.meshes[ i ]; + for( j = 0; j < mesh->weights.Num(); j++ ) { + mesh->weights[ j ].joint->keep = true; + } + } + } + } else { + // mark the joints to keep + for( i = 0; i < keepjoints.Num(); i++ ) { + joint = model.FindJoint( keepjoints[ i ] ); + if ( joint ) { + joint->keep = true; + } + } + + // count valid meshes + for( i = 0; i < model.meshes.Num(); i++ ) { + mesh = model.meshes[ i ]; + num_weights = 0; + for( j = 0; j < mesh->weights.Num(); j++ ) { + if ( mesh->weights[ j ].joint->keep ) { + num_weights++; + } else if ( prefix.Length() && !mesh->weights[ j ].joint->realname.Cmpn( prefix, prefix.Length() ) ) { + // keep the joint if it's used by the mesh and it has the right prefix + mesh->weights[ j ].joint->keep = true; + num_weights++; + } + } + + if ( num_weights != mesh->weights.Num() ) { + mesh->keep = false; + } + } + } + + // find all joints aren't exported and reparent joint's children + model.export_joints = 0; + joint = model.joints.Ptr(); + for( i = 0; i < model.joints.Num(); i++, joint++ ) { + if ( !joint->keep ) { + joint->exportNode.RemoveFromHierarchy(); + } else { + joint->index = model.export_joints; + model.export_joints++; + + // make sure we are parented to an exported joint + for( parent = joint->exportNode.GetParent(); parent != NULL; parent = parent->exportNode.GetParent() ) { + if ( parent->keep ) { + break; + } + } + + if ( parent != NULL ) { + joint->exportNode.ParentTo( parent->exportNode ); + } else { + joint->exportNode.ParentTo( model.exportHead ); + } + } + } + + // check if we have any duplicate joint names + for( joint = model.exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext() ) { + if ( !joint->keep ) { + MayaError( "Non-kept joint in export tree ('%s')", joint->name.c_str() ); + } + + for( joint2 = model.exportHead.GetNext(); joint2 != NULL; joint2 = joint2->exportNode.GetNext() ) { + if ( ( joint2 != joint ) && ( joint2->name == joint->name ) ) { + MayaError( "Two joints found with the same name ('%s')", joint->name.c_str() ); + } + } + } +} + +/* +=============== +idMayaExport::FreeDagNodes +=============== +*/ +void idMayaExport::FreeDagNodes( void ) { + int i; + + for( i = 0; i < model.joints.Num(); i++ ) { + delete model.joints[ i ].dagnode; + model.joints[ i ].dagnode = NULL; + } +} + +/* +=============== +idMayaExport::GetBindPose +=============== +*/ +void idMayaExport::GetBindPose( MObject &jointNode, idExportJoint *joint, float scale ) { + MStatus status; + MFnDependencyNode fnJoint( jointNode ); + MObject aBindPose = fnJoint.attribute( "bindPose", &status ); + + joint->bindpos = vec3_zero; + joint->bindmat = mat3_default; + + if ( MS::kSuccess == status ) { + unsigned ii; + unsigned jointIndex; + unsigned connLength; + MPlugArray connPlugs; + MPlug pBindPose( jointNode, aBindPose ); + + pBindPose.connectedTo( connPlugs, false, true ); + connLength = connPlugs.length(); + for( ii = 0; ii < connLength; ++ii ) { + if ( connPlugs[ ii ].node().apiType() == MFn::kDagPose ) { + MObject aMember = connPlugs[ ii ].attribute(); + MFnAttribute fnAttr( aMember ); + + if ( fnAttr.name() == "worldMatrix" ) { + jointIndex = connPlugs[ ii ].logicalIndex(); + + MFnDependencyNode nDagPose( connPlugs[ ii ].node() ); + + // construct plugs for this joint's world matrix + MObject aWorldMatrix = nDagPose.attribute( "worldMatrix" ); + MPlug pWorldMatrix( connPlugs[ ii ].node(), aWorldMatrix ); + + pWorldMatrix.selectAncestorLogicalIndex( jointIndex, aWorldMatrix ); + + // get the world matrix data + MObject worldMatrix; + MStatus status = pWorldMatrix.getValue( worldMatrix ); + if ( MS::kSuccess != status ) { + // Problem retrieving world matrix + return; + } + + MFnMatrixData dMatrix( worldMatrix ); + MMatrix wMatrix = dMatrix.matrix( &status ); + + joint->bindmat = ConvertToIdSpace( idMat( wMatrix ) ); + joint->bindpos = ConvertToIdSpace( idVec( wMatrix ) ) * scale; + if ( !options.ignoreScale ) { + joint->bindpos *= joint->scale; + } else { + joint->bindmat[ 0 ].Normalize(); + joint->bindmat[ 1 ].Normalize(); + joint->bindmat[ 2 ].Normalize(); + } + + return; + } + } + } + } +} + +/* +=============== +idMayaExport::GetLocalTransform +=============== +*/ +void idMayaExport::GetLocalTransform( idExportJoint *joint, idVec3 &pos, idMat3 &mat ) { + MStatus status; + MDagPath dagPath; + + pos.Zero(); + mat.Identity(); + + if ( !joint->dagnode ) { + return; + } + + status = joint->dagnode->getPath( dagPath ); + if ( !status ) { + return; + } + + MObject transformNode = dagPath.transform( &status ); + if ( !status && ( status.statusCode () == MStatus::kInvalidParameter ) ) { + return; + } + + MFnDagNode transform( transformNode, &status ); + if ( !status ) { + return; + } + + pos = idVec( transform.transformationMatrix() ); + mat = idMat( transform.transformationMatrix() ); +} + +/* +=============== +idMayaExport::GetWorldTransform +=============== +*/ +void idMayaExport::GetWorldTransform( idExportJoint *joint, idVec3 &pos, idMat3 &mat, float scale ) { + idExportJoint *parent; + + GetLocalTransform( joint, pos, mat ); + mat.OrthoNormalizeSelf(); + pos *= scale; + + parent = joint->mayaNode.GetParent(); + if ( parent ) { + idVec3 parentpos; + idMat3 parentmat; + + GetWorldTransform( parent, parentpos, parentmat, scale ); + + pos = parentpos + ( parentmat * ( pos * parent->scale ) ); + mat = mat * parentmat; + } +} + +/* +=============== +idMayaExport::CreateJoints +=============== +*/ +void idMayaExport::CreateJoints( float scale ) { + int i; + int j; + idExportJoint *joint; + idExportJoint *parent; + MStatus status; + MDagPath dagPath; + MFnDagNode *parentNode; + + SetFrame( 0 ); + + // create an initial list with all of the transformable nodes in the scene + MItDag dagIterator( MItDag::kDepthFirst, MFn::kTransform, &status ); + for ( ; !dagIterator.isDone(); dagIterator.next() ) { + status = dagIterator.getPath( dagPath ); + if ( !status ) { + MayaError( "CreateJoints: MItDag::getPath failed (%s)", status.errorString().asChar() ); + continue; + } + + joint = &model.joints.Alloc(); + joint->index = model.joints.Num() - 1; + joint->dagnode = new MFnDagNode( dagPath, &status ); + if ( !status ) { + MayaError( "CreateJoints: MFnDagNode constructor failed (%s)", status.errorString().asChar() ); + continue; + } + + joint->name = joint->dagnode->name().asChar(); + joint->realname = joint->name; + } + + // allocate an extra joint in case we need to add an origin later + model.exportOrigin = &model.joints.Alloc(); + model.exportOrigin->index = model.joints.Num() - 1; + + // create scene hierarchy + joint = model.joints.Ptr(); + for( i = 0; i < model.joints.Num(); i++, joint++ ) { + if ( !joint->dagnode ) { + continue; + } + joint->mayaNode.ParentTo( model.mayaHead ); + joint->exportNode.ParentTo( model.exportHead ); + + parentNode = GetParent( joint->dagnode ); + if ( parentNode ) { + // find the parent joint in our jointlist + for( j = 0; j < model.joints.Num(); j++ ) { + if ( !model.joints[ j ].dagnode ) { + continue; + } + if ( model.joints[ j ].dagnode->name() == parentNode->name() ) { + joint->mayaNode.ParentTo( model.joints[ j ].mayaNode ); + joint->exportNode.ParentTo( model.joints[ j ].exportNode ); + break; + } + } + + delete parentNode; + } + + // create long name + parent = joint->mayaNode.GetParent(); + if ( parent ) { + joint->longname = parent->longname + "/" + joint->name; + } else { + joint->longname = joint->name; + } + + // get the joint's scale + GetLocalTransform( &model.joints[ i ], joint->t, joint->wm ); + joint->scale = joint->wm[ 0 ].Length(); + + if ( parent ) { + joint->scale *= parent->scale; + if ( joint->scale != 0 ) { + joint->invscale = 1.0f / joint->scale; + } else { + joint->invscale = 0; + } + } + + joint->dagnode->getPath( dagPath ); + GetBindPose( dagPath.node( &status ), joint, scale ); + } +} + +/* +=============== +idMayaExport::RenameJoints +=============== +*/ +void idMayaExport::RenameJoints( idList &renamejoints, idStr &prefix ) { + int i; + idExportJoint *joint; + + // rename joints that match the prefix + if ( prefix.Length() ) { + joint = model.joints.Ptr(); + for( i = 0; i < model.joints.Num(); i++, joint++ ) { + if ( !joint->name.Cmpn( prefix, prefix.Length() ) ) { + // remove the prefix from the name + joint->name = joint->name.Right( joint->name.Length() - prefix.Length() ); + } + } + } + + // rename joints if necessary + for( i = 0; i < renamejoints.Num(); i++ ) { + joint = model.FindJoint( renamejoints[ i ].from ); + if ( joint ) { + joint->name = renamejoints[ i ].to; + } + } +} + +/* +=============== +idMayaExport::RemapParents +=============== +*/ +bool idMayaExport::RemapParents( idList &remapjoints ) { + int i; + idExportJoint *joint; + idExportJoint *parent; + idExportJoint *origin; + idExportJoint *sibling; + + for( i = 0; i < remapjoints.Num(); i++ ) { + // find joint to reparent + joint = model.FindJoint( remapjoints[ i ].from ); + if ( !joint ) { + // couldn't find joint, fail + MayaError( "Couldn't find joint '%s' to reparent\n", remapjoints[ i ].from.c_str() ); + } + + // find new parent joint + parent = model.FindJoint( remapjoints[ i ].to ); + if ( !parent ) { + // couldn't find parent, fail + MayaError( "Couldn't find joint '%s' to be new parent for '%s'\n", remapjoints[ i ].to.c_str(), remapjoints[ i ].from.c_str() ); + } + + if ( parent->exportNode.ParentedBy( joint->exportNode ) ) { + MayaError( "Joint '%s' is a child of joint '%s' and can't become the parent.", joint->name.c_str(), parent->name.c_str() ); + } + joint->exportNode.ParentTo( parent->exportNode ); + } + + // if we have an origin, make it the first node in the export list, otherwise add one + origin = model.FindJoint( "origin" ); + if ( !origin ) { + origin = model.exportOrigin; + origin->dagnode = NULL; + origin->name = "origin"; + origin->realname = "origin"; + origin->bindmat.Identity(); + origin->bindpos.Zero(); + } + + origin->exportNode.ParentTo( model.exportHead ); + + // force the joint to be kept + origin->keep = true; + + // make all root joints children of the origin joint + joint = model.exportHead.GetChild(); + while( joint ) { + sibling = joint->exportNode.GetSibling(); + if ( joint != origin ) { + joint->exportNode.ParentTo( origin->exportNode ); + } + joint = sibling; + } + + return true; +} + +/* +=============== +idMayaExport::FindShader + +Find the shading node for the given shading group set node. +=============== +*/ +MObject idMayaExport::FindShader( MObject& setNode ) { + MStatus status; + MFnDependencyNode fnNode( setNode ); + MPlug shaderPlug; + + shaderPlug = fnNode.findPlug( "surfaceShader" ); + if ( !shaderPlug.isNull() ) { + MPlugArray connectedPlugs; + bool asSrc = false; + bool asDst = true; + shaderPlug.connectedTo( connectedPlugs, asDst, asSrc, &status ); + + if ( connectedPlugs.length() != 1 ) { + MayaError( "FindShader: Error getting shader (%s)", status.errorString().asChar() ); + } else { + return connectedPlugs[ 0 ].node(); + } + } + + return MObject::kNullObj; +} + +/* +=============== +idMayaExport::GetTextureForMesh + +Find the texture files that apply to the color of each polygon of +a selected shape if the shape has its polygons organized into sets. +=============== +*/ +void idMayaExport::GetTextureForMesh( idExportMesh *mesh, MFnDagNode &dagNode ) { + MStatus status; + MDagPath path; + int i; + int instanceNum; + + status = dagNode.getPath( path ); + if ( !status ) { + return; + } + + path.extendToShape(); + + // If the shape is instanced then we need to determine which + // instance this path refers to. + // + instanceNum = 0; + if ( path.isInstanced() ) { + instanceNum = path.instanceNumber(); + } + + // Get a list of all sets pertaining to the selected shape and the + // members of those sets. + // + MFnMesh fnMesh( path ); + MObjectArray sets; + MObjectArray comps; + status = fnMesh.getConnectedSetsAndMembers( instanceNum, sets, comps, true ); + if ( !status ) { + MayaError( "GetTextureForMesh: MFnMesh::getConnectedSetsAndMembers failed (%s)", status.errorString().asChar() ); + } + + // Loop through all the sets. If the set is a polygonal set, find the + // shader attached to the and print out the texture file name for the + // set along with the polygons in the set. + // + for ( i = 0; i < ( int )sets.length(); i++ ) { + MObject set = sets[i]; + MObject comp = comps[i]; + + MFnSet fnSet( set, &status ); + if ( status == MS::kFailure ) { + MayaError( "GetTextureForMesh: MFnSet constructor failed (%s)", status.errorString().asChar() ); + continue; + } + + // Make sure the set is a polygonal set. If not, continue. + MItMeshPolygon piter(path, comp, &status); + if (status == MS::kFailure) { + continue; + } + + // Find the texture that is applied to this set. First, get the + // shading node connected to the set. Then, if there is an input + // attribute called "color", search upstream from it for a texture + // file node. + // + MObject shaderNode = FindShader( set ); + if ( shaderNode == MObject::kNullObj ) { + continue; + } + + MPlug colorPlug = MFnDependencyNode(shaderNode).findPlug("color", &status); + if ( status == MS::kFailure ) { + continue; + } + + MItDependencyGraph dgIt(colorPlug, MFn::kFileTexture, + MItDependencyGraph::kUpstream, + MItDependencyGraph::kBreadthFirst, + MItDependencyGraph::kNodeLevel, + &status); + + if ( status == MS::kFailure ) { + continue; + } + + dgIt.disablePruningOnFilter(); + + // If no texture file node was found, just continue. + // + if ( dgIt.isDone() ) { + continue; + } + + // Print out the texture node name and texture file that it references. + // + MObject textureNode = dgIt.thisNode(); + MPlug filenamePlug = MFnDependencyNode( textureNode ).findPlug( "fileTextureName" ); + MString textureName; + filenamePlug.getValue( textureName ); + + // remove the OS path and save it in the mesh + OSPathToRelativePath( textureName.asChar(), mesh->shader, options.game ); + mesh->shader.StripFileExtension(); + + return; + } +} + +/* +=============== +idMayaExport::CopyMesh +=============== +*/ +idExportMesh *idMayaExport::CopyMesh( MFnSkinCluster &skinCluster, float scale ) { + MStatus status; + MObjectArray objarray; + MObjectArray outputarray; + int nGeom; + int i, j, k; + idExportMesh *mesh; + float uv_u, uv_v; + idStr name, altname; + int pos; + + status = skinCluster.getInputGeometry( objarray ); + if ( !status ) { + MayaError( "CopyMesh: Error getting input geometry (%s)", status.errorString().asChar() ); + return NULL; + } + + nGeom = objarray.length(); + for( i = 0; i < nGeom; i++ ) { + MFnDagNode dagNode( objarray[ i ], &status ); + if ( !status ) { + common->Printf( "CopyMesh: MFnDagNode Constructor failed (%s)", status.errorString().asChar() ); + continue; + } + + MFnMesh fnmesh( objarray[ i ], &status ); + if ( !status ) { + // object isn't an MFnMesh + continue; + } + + status = skinCluster.getOutputGeometry( outputarray ); + if ( !status ) { + common->Printf( "CopyMesh: Error getting output geometry (%s)", status.errorString().asChar() ); + return NULL; + } + + if ( outputarray.length() < 1 ) { + return NULL; + } + + name = fnmesh.name().asChar(); + if ( options.prefix.Length() ) { + if ( !name.Cmpn( options.prefix, options.prefix.Length() ) ) { + // remove the prefix from the name + name = name.Right( name.Length() - options.prefix.Length() ); + } else { + // name doesn't match prefix, so don't use this mesh + //return NULL; + } + } + + pos = name.Find( "ShapeOrig" ); + if ( pos >= 0 ) { + name.CapLength( pos ); + } + + MFnDagNode dagNode2( outputarray[ 0 ], &status ); + if ( !status ) { + common->Printf( "CopyMesh: MFnDagNode Constructor failed (%s)", status.errorString().asChar() ); + continue; + } + + altname = name; + MObject parent = fnmesh.parent( 0, &status ); + if ( status ) { + MFnDagNode parentNode( parent, &status ); + if ( status ) { + altname = parentNode.name().asChar(); + } + } + + name.StripLeadingOnce( options.prefix ); + altname.StripLeadingOnce( options.prefix ); + if ( options.keepmeshes.Num() ) { + if ( !options.keepmeshes.Find( name ) && !options.keepmeshes.Find( altname ) ) { + if ( altname != name ) { + common->Printf( "Skipping mesh '%s' ('%s')\n", name.c_str(), altname.c_str() ); + } else { + common->Printf( "Skipping mesh '%s'\n", name.c_str() ); + } + return NULL; + } + } + + if ( options.skipmeshes.Find( name ) || options.skipmeshes.Find( altname ) ) { + common->Printf( "Skipping mesh '%s' ('%s')\n", name.c_str(), altname.c_str() ); + return NULL; + } + + mesh = new idExportMesh(); + model.meshes.Append( mesh ); + + if ( altname.Length() ) { + mesh->name = altname; + } else { + mesh->name = name; + } + GetTextureForMesh( mesh, dagNode2 ); + + int v = fnmesh.numVertices( &status ); + mesh->verts.SetNum( v ); + + MFloatPointArray vertexArray; + + fnmesh.getPoints( vertexArray, MSpace::kPreTransform ); + + for( j = 0; j < v; j++ ) { + memset( &mesh->verts[ j ], 0, sizeof( mesh->verts[ j ] ) ); + mesh->verts[ j ].pos = ConvertToIdSpace( idVec( vertexArray[ j ] ) ) * scale; + } + + MIntArray vertexList; + int p; + + p = fnmesh.numPolygons( &status ); + mesh->tris.SetNum( p ); + mesh->uv.SetNum( p ); + + MString setName; + + status = fnmesh.getCurrentUVSetName( setName ); + if ( !status ) { + MayaError( "CopyMesh: MFnMesh::getCurrentUVSetName failed (%s)", status.errorString().asChar() ); + } + + for( j = 0; j < p; j++ ) { + fnmesh.getPolygonVertices( j, vertexList ); + if ( vertexList.length() != 3 ) { + MayaError( "CopyMesh: Too many vertices on a face (%d)\n", vertexList.length() ); + } + + for( k = 0; k < 3; k++ ) { + mesh->tris[ j ].indexes[ k ] = vertexList[ k ]; + + status = fnmesh.getPolygonUV( j, k, uv_u, uv_v, &setName ); + if ( !status ) { + MayaError( "CopyMesh: MFnMesh::getPolygonUV failed (%s)", status.errorString().asChar() ); + } + + mesh->uv[ j ].uv[ k ][ 0 ] = uv_u; + mesh->uv[ j ].uv[ k ][ 1 ] = uv_v; + } + } + + return mesh; + } + + return NULL; +} + +/* +=============== +idMayaExport::CreateMesh +=============== +*/ +void idMayaExport::CreateMesh( float scale ) { + size_t count; + idExportMesh *mesh; + MStatus status; + exportWeight_t weight; + unsigned int nGeoms; + + // Iterate through graph and search for skinCluster nodes + MItDependencyNodes iter( MFn::kSkinClusterFilter ); + count = 0; + for ( ; !iter.isDone(); iter.next() ) { + MObject object = iter.item(); + + count++; + + // For each skinCluster node, get the list of influence objects + MFnSkinCluster skinCluster( object, &status ); + if ( !status ) { + MayaError( "%s: Error getting skin cluster (%s)", object.apiTypeStr(), status.errorString().asChar() ); + } + + mesh = CopyMesh( skinCluster, scale ); + if ( !mesh ) { + continue; + } + + MDagPathArray infs; + unsigned int nInfs = skinCluster.influenceObjects(infs, &status); + if ( !status ) { + MayaError( "Mesh '%s': Error getting influence objects (%s)", mesh->name.c_str(), status.errorString().asChar() ); + } + + if ( 0 == nInfs ) { + MayaError( "Mesh '%s': No influence objects found", mesh->name.c_str() ); + } + + // loop through the geometries affected by this cluster + nGeoms = skinCluster.numOutputConnections(); + for (size_t ii = 0; ii < nGeoms; ++ii) { + unsigned int index = skinCluster.indexForOutputConnection(ii,&status); + + if ( !status ) { + MayaError( "Mesh '%s': Error getting geometry index (%s)", mesh->name.c_str(), status.errorString().asChar() ); + } + + // get the dag path of the ii'th geometry + MDagPath skinPath; + status = skinCluster.getPathAtIndex(index,skinPath); + if ( !status ) { + MayaError( "Mesh '%s': Error getting geometry path (%s)", mesh->name.c_str(), status.errorString().asChar() ); + } + + // iterate through the components of this geometry + MItGeometry gIter( skinPath ); + + // print out the influence objects + idList joints; + idExportJoint *joint; + exportVertex_t *vert; + + joints.Resize( nInfs ); + for (size_t kk = 0; kk < nInfs; ++kk) { + const char *c; + MString s; + + s = infs[kk].partialPathName(); + c = s.asChar(); + joint = model.FindJointReal( c ); + if ( !joint ) { + MayaError( "Mesh '%s': joint %s not found", mesh->name.c_str(), c ); + } + + joints.Append( joint ); + } + + for ( /* nothing */ ; !gIter.isDone(); gIter.next() ) { + MObject comp = gIter.component( &status ); + if ( !status ) { + MayaError( "Mesh '%s': Error getting component (%s)", mesh->name.c_str(), status.errorString().asChar() ); + } + + // Get the weights for this vertex (one per influence object) + MFloatArray wts; + unsigned infCount; + status = skinCluster.getWeights(skinPath,comp,wts,infCount); + if ( !status ) { + MayaError( "Mesh '%s': Error getting weights (%s)", mesh->name.c_str(), status.errorString().asChar() ); + } + if (0 == infCount) { + MayaError( "Mesh '%s': Error: 0 influence objects.", mesh->name.c_str() ); + } + + int num = gIter.index(); + vert = &mesh->verts[ num ]; + vert->startweight = mesh->weights.Num(); + + float totalweight = 0.0f; + + // copy the weight data for this vertex + int numNonZeroWeights = 0; + int jj; + for ( jj = 0; jj < (int)infCount ; ++jj ) { + float w = ( float )wts[ jj ]; + if ( w > 0.0f ) { + numNonZeroWeights++; + } + if ( w > options.jointThreshold ) { + weight.joint = joints[ jj ]; + weight.jointWeight = wts[ jj ]; + + if ( !options.ignoreScale ) { + weight.joint->bindmat.ProjectVector( vert->pos - ( weight.joint->bindpos * weight.joint->invscale ), weight.offset ); + weight.offset *= weight.joint->scale; + } else { + weight.joint->bindmat.ProjectVector( vert->pos - weight.joint->bindpos, weight.offset ); + } + mesh->weights.Append( weight ); + totalweight += weight.jointWeight; + } + } + + vert->numWeights = mesh->weights.Num() - vert->startweight; + if ( !vert->numWeights ) { + if ( numNonZeroWeights ) { + MayaError( "Error on mesh '%s': Vertex %d doesn't have any joint weights exceeding jointThreshold (%f).", mesh->name.c_str(), num, options.jointThreshold ); + } else { + MayaError( "Error on mesh '%s': Vertex %d doesn't have any joint weights.", mesh->name.c_str(), num ); + } + } else if ( !totalweight ) { + MayaError( "Error on mesh '%s': Combined weight of 0 on vertex %d.", mesh->name.c_str(), num ); + } + //if ( numNonZeroWeights ) { + // common->Printf( "Mesh '%s': skipped %d out of %d weights on vertex %d\n", mesh->name.c_str(), numNonZeroWeights, numNonZeroWeights + vert->numWeights, num ); + //} + + // normalize the joint weights + for( jj = 0; jj < vert->numWeights; jj++ ) { + mesh->weights[ vert->startweight + jj ].jointWeight /= totalweight; + } + } + break; + } + } + + if ( !count && !options.ignoreMeshes ) { + MayaError( "CreateMesh: No skinClusters found in this scene.\n" ); + } +} + +/* +=============== +idMayaExport::CombineMeshes + +combine surfaces with the same shader. +=============== +*/ +void idMayaExport::CombineMeshes( void ) { + int i, j; + int count; + idExportMesh *mesh; + idExportMesh *combine; + idList oldmeshes; + + oldmeshes = model.meshes; + model.meshes.Clear(); + + count = 0; + for( i = 0; i < oldmeshes.Num(); i++ ) { + mesh = oldmeshes[ i ]; + if ( !mesh->keep ) { + delete mesh; + continue; + } + + combine = NULL; + for( j = 0; j < model.meshes.Num(); j++ ) { + if ( model.meshes[ j ]->shader == mesh->shader ) { + combine = model.meshes[ j ]; + break; + } + } + + if ( combine ) { + combine->Merge( mesh ); + delete mesh; + count++; + } else { + model.meshes.Append( mesh ); + } + } + + // share verts + for( i = 0; i < model.meshes.Num(); i++ ) { + model.meshes[ i ]->ShareVerts(); + } + + common->Printf( "Merged %d meshes\n", count ); +} + +/* +=============== +idMayaExport::GetAlignment +=============== +*/ +void idMayaExport::GetAlignment( idStr &alignName, idMat3 &align, float rotate, int startframe ) { + idVec3 pos; + idExportJoint *joint; + idAngles ang( 0, rotate, 0 ); + idMat3 mat; + + align.Identity(); + + if ( alignName.Length() ) { + SetFrame( 0 ); + + joint = model.FindJoint( alignName ); + if ( !joint ) { + MayaError( "could not find joint '%s' to align model to.\n", alignName.c_str() ); + } + + // found it + GetWorldTransform( joint, pos, mat, 1.0f ); + align[ 0 ][ 0 ] = mat[ 2 ][ 0 ]; + align[ 0 ][ 1 ] = -mat[ 2 ][ 2 ]; + align[ 0 ][ 2 ] = mat[ 2 ][ 1 ]; + + align[ 1 ][ 0 ] = mat[ 0 ][ 0 ]; + align[ 1 ][ 1 ] = -mat[ 0 ][ 2 ]; + align[ 1 ][ 2 ] = mat[ 0 ][ 1 ]; + + align[ 2 ][ 0 ] = mat[ 1 ][ 0 ]; + align[ 2 ][ 1 ] = -mat[ 1 ][ 2 ]; + align[ 2 ][ 2 ] = mat[ 1 ][ 1 ]; + + if ( rotate ) { + align *= ang.ToMat3(); + } + } else if ( rotate ) { + align = ang.ToMat3(); + } + + align.TransposeSelf(); +} + +/* +=============== +idMayaExport::GetObjectType + +return the type of the object +=============== +*/ +const char *idMayaExport::GetObjectType( MObject object ) { + if( object.isNull() ) { + return "(Null)"; + } + + MStatus stat; + MFnDependencyNode dgNode; + MString typeName; + + stat = dgNode.setObject( object ); + typeName = dgNode.typeName( &stat ); + if( MS::kSuccess != stat ) { + // can not get the type name of this object + return "(Unknown)"; + } + + return typeName.asChar(); +} + +/* +=============== +idMayaExport::GetCameraFov +=============== +*/ +float idMayaExport::GetCameraFov( idExportJoint *joint ) { + int childCount; + int j; + double horiz; + double focal; + MStatus status; + const char *n1, *n2; + MFnDagNode *dagnode; + float fov; + + dagnode = joint->dagnode; + + MObject cameraNode = dagnode->object(); + childCount = dagnode->childCount(); + + fov = 90.0f; + for( j = 0; j < childCount; j++ ) { + MObject childNode = dagnode->child( j ); + + n1 = GetObjectType( cameraNode ); + n2 = GetObjectType( childNode ); + if ( ( !strcmp( "transform", n1 ) ) && ( !strcmp( "camera", n2 ) ) ) { + MFnCamera camera( childNode ); + focal = camera.focalLength(); + horiz = camera.horizontalFilmAperture(); + fov = RAD2DEG( 2 * atan( ( horiz * 0.5 ) / ( focal / 25.4 ) ) ); + break; + } + } + + return fov; +} + +/* +=============== +idMayaExport::GetCameraFrame +=============== +*/ +void idMayaExport::GetCameraFrame( idExportJoint *camera, idMat3 &align, cameraFrame_t *cam ) { + idMat3 mat; + idMat3 axis; + idVec3 pos; + + // get the worldspace positions of the joint + GetWorldTransform( camera, pos, axis, 1.0f ); + + // convert to id coordinates + cam->t = ConvertToIdSpace( pos ) * align; + + // correct the orientation for the game + axis = ConvertToIdSpace( axis ) * align; + mat[ 0 ] = -axis[ 2 ]; + mat[ 1 ] = -axis[ 0 ]; + mat[ 2 ] = axis[ 1 ]; + cam->q = mat.ToQuat().ToCQuat(); + + // get it's fov + cam->fov = GetCameraFov( camera ); +} + +/* +=============== +idMayaExport::CreateCameraAnim +=============== +*/ +void idMayaExport::CreateCameraAnim( idMat3 &align ) { + float start, end; + MDagPath dagPath; + int frameNum; + short v; + MStatus status; + cameraFrame_t cam; + idExportJoint *refCam; + idExportJoint *camJoint; + idStr currentCam; + idStr newCam; + MPlug plug; + MFnEnumAttribute cameraAttribute; + + start = TimeForFrame( options.startframe ); + end = TimeForFrame( options.endframe ); + +#if 0 + options.framerate = 60; + model.numFrames = ( int )( ( end - start ) * ( float )options.framerate ) + 1; + model.frameRate = options.framerate; +#else + model.numFrames = options.endframe + 1 - options.startframe; + model.frameRate = options.framerate; +#endif + + common->Printf( "start frame = %d\n end frame = %d\n", options.startframe, options.endframe ); + common->Printf( " start time = %f\n end time = %f\n total time = %f\n", start, end, end - start ); + + if ( start > end ) { + MayaError( "Start frame is greater than end frame." ); + } + + refCam = model.FindJoint( "refcam" ); + if ( refCam == NULL ) { + currentCam = MAYA_DEFAULT_CAMERA; + } else { + MObject cameraNode = refCam->dagnode->object(); + MFnDependencyNode cameraDG( cameraNode, &status ); + if( MS::kSuccess != status ) { + MayaError( "Can't find 'refcam' dependency node." ); + return; + } + + MObject attr = cameraDG.attribute( MString( "Camera" ), &status ); + if( MS::kSuccess != status ) { + MayaError( "Can't find 'Camera' attribute on 'refcam'." ); + return; + } + + plug = MPlug( cameraNode, attr ); + status = cameraAttribute.setObject( attr ); + if( MS::kSuccess != status ) { + MayaError( "Bad 'Camera' attribute on 'refcam'." ); + return; + } + + model.camera.Clear(); + model.cameraCuts.Clear(); + + SetFrame( 0 ); + status = plug.getValue( v ); + currentCam = cameraAttribute.fieldName( v, &status ).asChar(); + if( MS::kSuccess != status ) { + MayaError( "Error getting camera name on frame %d", GetMayaFrameNum( 0 ) ); + } + } + + camJoint = model.FindJoint( currentCam ); + if ( !camJoint ) { + MayaError( "Couldn't find camera '%s'", currentCam.c_str() ); + } + + for( frameNum = 0; frameNum < model.numFrames; frameNum++ ) { + common->Printf( "\rFrame %d/%d...", options.startframe + frameNum, options.endframe ); + +#if 0 + MTime time; + time.setUnit( MTime::kSeconds ); + time.setValue( start + ( ( float )frameNum / ( float )options.framerate ) ); + MGlobal::viewFrame( time ); +#else + SetFrame( frameNum ); +#endif + + // get the position for this frame + GetCameraFrame( camJoint, align, &model.camera.Alloc() ); + + if ( refCam != NULL ) { + status = plug.getValue( v ); + newCam = cameraAttribute.fieldName( v, &status ).asChar(); + if( MS::kSuccess != status ) { + MayaError( "Error getting camera name on frame %d", GetMayaFrameNum( frameNum ) ); + } + + if ( newCam != currentCam ) { + // place a cut at our last frame + model.cameraCuts.Append( model.camera.Num() - 1 ); + + currentCam = newCam; + camJoint = model.FindJoint( currentCam ); + if ( !camJoint ) { + MayaError( "Couldn't find camera '%s'", currentCam.c_str() ); + } + + // get the position for this frame + GetCameraFrame( camJoint, align, &model.camera.Alloc() ); + } + } + } + + common->Printf( "\n" ); +} + +/* +=============== +idMayaExport::GetDefaultPose +=============== +*/ +void idMayaExport::GetDefaultPose( idMat3 &align ) { + float start; + MDagPath dagPath; + idMat3 jointaxis; + idVec3 jointpos; + idExportJoint *joint, *parent; + idBounds bnds; + idBounds meshBounds; + idList frame; + + start = TimeForFrame( options.startframe ); + + common->Printf( "default pose frame = %d\n", options.startframe ); + common->Printf( " default pose time = %f\n", start ); + + frame.SetNum( model.joints.Num() ); + SetFrame( 0 ); + + // convert joints into local coordinates and save in channels + for( joint = model.exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext() ) { + if ( !joint->dagnode ) { + // custom origin joint + joint->idwm.Identity(); + joint->idt.Zero(); + frame[ joint->index ].t.Zero(); + frame[ joint->index ].q.Set( 0.0f, 0.0f, 0.0f ); + continue; + } + + // get the worldspace positions of the joint + GetWorldTransform( joint, jointpos, jointaxis, options.scale ); + + // convert to id coordinates + jointaxis = ConvertToIdSpace( jointaxis ) * align; + jointpos = ConvertToIdSpace( jointpos ) * align; + + // save worldspace position of joint for children + joint->idwm = jointaxis; + joint->idt = jointpos; + + parent = joint->exportNode.GetParent(); + if ( parent ) { + // convert to local coordinates + jointpos = ( jointpos - parent->idt ) * parent->idwm.Transpose(); + jointaxis = jointaxis * parent->idwm.Transpose(); + } else if ( joint->name == "origin" ) { + if ( options.clearOrigin ) { + jointpos.Zero(); + } + if ( options.clearOriginAxis ) { + jointaxis.Identity(); + } + } + + frame[ joint->index ].t = jointpos; + frame[ joint->index ].q = jointaxis.ToQuat().ToCQuat(); + } + + // relocate origin to start at 0, 0, 0 for first frame + joint = model.FindJoint( "origin" ); + if ( joint ) { + frame[ joint->index ].t.Zero(); + } + + // transform the hierarchy + for( joint = model.exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext() ) { + jointpos = frame[ joint->index ].t; + jointaxis = frame[ joint->index ].q.ToQuat().ToMat3(); + + parent = joint->exportNode.GetParent(); + if ( parent ) { + joint->idwm = jointaxis * parent->idwm; + joint->idt = parent->idt + jointpos * parent->idwm; + } else { + joint->idwm = jointaxis; + joint->idt = jointpos; + } + + joint->bindmat = joint->idwm; + joint->bindpos = joint->idt; + } + + common->Printf( "\n" ); +} + +/* +=============== +idMayaExport::CreateAnimation +=============== +*/ +void idMayaExport::CreateAnimation( idMat3 &align ) { + int i; + float start, end; + MDagPath dagPath; + idMat3 jointaxis; + idVec3 jointpos; + int frameNum; + idExportJoint *joint, *parent; + idBounds bnds; + idBounds meshBounds; + jointFrame_t *frame; + int cycleStart; + idVec3 totalDelta; + idList copyFrames; + + start = TimeForFrame( options.startframe ); + end = TimeForFrame( options.endframe ); + + model.numFrames = options.endframe + 1 - options.startframe; + model.frameRate = options.framerate; + + common->Printf( "start frame = %d\n end frame = %d\n", options.startframe, options.endframe ); + common->Printf( " start time = %f\n end time = %f\n total time = %f\n", start, end, end - start ); + + if ( start > end ) { + MayaError( "Start frame is greater than end frame." ); + } + + model.bounds.SetNum( model.numFrames ); + model.jointFrames.SetNum( model.numFrames * model.joints.Num() ); + model.frames.SetNum( model.numFrames ); + for( i = 0; i < model.numFrames; i++ ) { + model.frames[ i ] = &model.jointFrames[ model.joints.Num() * i ]; + } + + // *sigh*. cyclestart doesn't work nicely with the anims. + // may just want to not do it in SetTime anymore. + cycleStart = options.cycleStart; + options.cycleStart = options.startframe; + + for( frameNum = 0; frameNum < model.numFrames; frameNum++ ) { + common->Printf( "\rFrame %d/%d...", options.startframe + frameNum, options.endframe ); + + frame = model.frames[ frameNum ]; + SetFrame( frameNum ); + + // convert joints into local coordinates and save in channels + for( joint = model.exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext() ) { + if ( !joint->dagnode ) { + // custom origin joint + joint->idwm.Identity(); + joint->idt.Zero(); + frame[ joint->index ].t.Zero(); + frame[ joint->index ].q.Set( 0.0f, 0.0f, 0.0f ); + continue; + } + + // get the worldspace positions of the joint + GetWorldTransform( joint, jointpos, jointaxis, options.scale ); + + // convert to id coordinates + jointaxis = ConvertToIdSpace( jointaxis ) * align; + jointpos = ConvertToIdSpace( jointpos ) * align; + + // save worldspace position of joint for children + joint->idwm = jointaxis; + joint->idt = jointpos; + + parent = joint->exportNode.GetParent(); + if ( parent ) { + // convert to local coordinates + jointpos = ( jointpos - parent->idt ) * parent->idwm.Transpose(); + jointaxis = jointaxis * parent->idwm.Transpose(); + } else if ( joint->name == "origin" ) { + if ( options.clearOrigin ) { + jointpos.Zero(); + } + if ( options.clearOriginAxis ) { + jointaxis.Identity(); + } + } + + frame[ joint->index ].t = jointpos; + frame[ joint->index ].q = jointaxis.ToQuat().ToCQuat(); + } + } + + options.cycleStart = cycleStart; + totalDelta.Zero(); + + joint = model.FindJoint( "origin" ); + if ( joint ) { + frame = model.frames[ 0 ]; + idVec3 origin = frame[ joint->index ].t; + + frame = model.frames[ model.numFrames - 1 ]; + totalDelta = frame[ joint->index ].t - origin; + } + + // shift the frames when cycleStart is used + if ( options.cycleStart > options.startframe ) { + copyFrames = model.jointFrames; + for( i = 0; i < model.numFrames; i++ ) { + bool shiftorigin = false; + frameNum = i + ( options.cycleStart - options.startframe ); + if ( frameNum >= model.numFrames ) { + // wrap around, skipping the first frame, since it's a dupe of the last frame + frameNum -= model.numFrames - 1; + shiftorigin = true; + } + + memcpy( &model.jointFrames[ model.joints.Num() * i ], ©Frames[ model.joints.Num() * frameNum ], model.joints.Num() * sizeof( copyFrames[ 0 ] ) ); + + if ( joint && shiftorigin ) { + model.frames[ i ][ joint->index ].t += totalDelta; + } + } + } + + if ( joint ) { + // relocate origin to start at 0, 0, 0 for first frame + frame = model.frames[ 0 ]; + idVec3 origin = frame[ joint->index ].t; + for( i = 0; i < model.numFrames; i++ ) { + frame = model.frames[ i ]; + frame[ joint->index ].t -= origin; + } + } + + // get the bounds for each frame + for( frameNum = 0; frameNum < model.numFrames; frameNum++ ) { + frame = model.frames[ frameNum ]; + + // transform the hierarchy + for( joint = model.exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext() ) { + jointpos = frame[ joint->index ].t; + jointaxis = frame[ joint->index ].q.ToQuat().ToMat3(); + + parent = joint->exportNode.GetParent(); + if ( parent ) { + joint->idwm = jointaxis * parent->idwm; + joint->idt = parent->idt + jointpos * parent->idwm; + } else { + joint->idwm = jointaxis; + joint->idt = jointpos; + } + } + + // get bounds for this frame + bnds.Clear(); + for( i = 0; i < model.meshes.Num(); i++ ) { + if ( model.meshes[ i ]->keep ) { + model.meshes[ i ]->GetBounds( meshBounds ); + bnds.AddBounds( meshBounds ); + } + } + model.bounds[ frameNum ][ 0 ] = bnds[ 0 ]; + model.bounds[ frameNum ][ 1 ] = bnds[ 1 ]; + } + + common->Printf( "\n" ); +} + +/* +=============== +idMayaExport::ConvertModel +=============== +*/ +void idMayaExport::ConvertModel( void ) { + MStatus status; + idMat3 align; + + common->Printf( "Converting %s to %s...\n", options.src.c_str(), options.dest.c_str() ); + + // see if the destination file exists + FILE *file = fopen( options.dest, "r" ); + if ( file ) { + fclose( file ); + + // make sure we can write to the destination + FILE *file = fopen( options.dest, "r+" ); + if ( !file ) { + MayaError( "Unable to write to the file '%s'", options.dest.c_str() ); + } + fclose( file ); + } + + MString filename( options.src ); + MFileIO::newFile( true ); + + // Load the file into Maya + common->Printf( "Loading file...\n" ); + status = MFileIO::open( filename, NULL, true ); + if ( !status ) { + MayaError( "Error loading '%s': '%s'\n", filename.asChar(), status.errorString().asChar() ); + } + + // force Maya to update the frame. When using references, sometimes + // the model is posed the way it is in the source. Since Maya only + // updates it when the frame time changes to a value other than the + // current, just setting the time to the min time doesn't guarantee + // that the model gets updated. + MGlobal::viewFrame( MAnimControl::maxTime() ); + MGlobal::viewFrame( MAnimControl::minTime() ); + + if ( options.startframe < 0 ) { + options.startframe = MAnimControl::minTime().as( MTime::kFilm ); + } + + if ( options.endframe < 0 ) { + options.endframe = MAnimControl::maxTime().as( MTime::kFilm ); + } + if ( options.cycleStart < 0 ) { + options.cycleStart = options.startframe; + } else if ( ( options.cycleStart < options.startframe ) || ( options.cycleStart > options.endframe ) ) { + MayaError( "cycleStart (%d) out of frame range (%d to %d)\n", options.cycleStart, options.startframe, options.endframe ); + } else if ( options.cycleStart == options.endframe ) { + // end frame is a duplicate of the first frame in cycles, so just disable cycleStart + options.cycleStart = options.startframe; + } + + // create a list of the transform nodes that will make up our heirarchy + common->Printf( "Creating joints...\n" ); + CreateJoints( options.scale ); + if ( options.type != WRITE_CAMERA ) { + common->Printf( "Creating meshes...\n" ); + CreateMesh( options.scale ); + common->Printf( "Renaming joints...\n" ); + RenameJoints( options.renamejoints, options.prefix ); + common->Printf( "Remapping parents...\n" ); + RemapParents( options.remapjoints ); + common->Printf( "Pruning joints...\n" ); + PruneJoints( options.keepjoints, options.prefix ); + common->Printf( "Combining meshes...\n" ); + CombineMeshes(); + } + + common->Printf( "Align model...\n" ); + GetAlignment( options.align, align, options.rotate, 0 ); + + switch( options.type ) { + case WRITE_MESH : + common->Printf( "Grabbing default pose:\n" ); + GetDefaultPose( align ); + common->Printf( "Writing file...\n" ); + if ( !model.WriteMesh( options.dest, options ) ) { + MayaError( "error writing to '%s'", options.dest.c_str() ); + } + break; + + case WRITE_ANIM : + common->Printf( "Creating animation frames:\n" ); + CreateAnimation( align ); + common->Printf( "Writing file...\n" ); + if ( !model.WriteAnim( options.dest, options ) ) { + MayaError( "error writing to '%s'", options.dest.c_str() ); + } + break; + + case WRITE_CAMERA : + common->Printf( "Creating camera frames:\n" ); + CreateCameraAnim( align ); + + common->Printf( "Writing file...\n" ); + if ( !model.WriteCamera( options.dest, options ) ) { + MayaError( "error writing to '%s'", options.dest.c_str() ); + } + break; + } + + common->Printf( "done\n\n" ); +} + +/* +=============== +idMayaExport::ConvertToMD3 +=============== +*/ +void idMayaExport::ConvertToMD3( void ) { +#if 0 + int i, j; + md3Header_t *pinmodel; + md3Frame_t *frame; + md3Surface_t *surf; + md3Shader_t *shader; + md3Triangle_t *tri; + md3St_t *st; + md3XyzNormal_t *xyz; + md3Tag_t *tag; + int version; + int size; + + //model_t *mod, int lod, void *buffer, const char *mod_name + + pinmodel = (md3Header_t *)buffer; + + version = LittleLong (pinmodel->version); + if (version != MD3_VERSION) { + common->Printf( "R_LoadMD3: %s has wrong version (%i should be %i)\n", + mod_name, version, MD3_VERSION); + return qfalse; + } + + mod->type = MOD_MESH; + size = LittleLong(pinmodel->ofsEnd); + mod->dataSize += size; + mod->md3[lod] = ri.Hunk_Alloc( size ); + + memcpy (mod->md3[lod], buffer, LittleLong(pinmodel->ofsEnd) ); + + LL(mod->md3[lod]->ident); + LL(mod->md3[lod]->version); + LL(mod->md3[lod]->numFrames); + LL(mod->md3[lod]->numTags); + LL(mod->md3[lod]->numSurfaces); + LL(mod->md3[lod]->ofsFrames); + LL(mod->md3[lod]->ofsTags); + LL(mod->md3[lod]->ofsSurfaces); + LL(mod->md3[lod]->ofsEnd); + + if ( mod->md3[lod]->numFrames < 1 ) { + common->Printf( "R_LoadMD3: %s has no frames\n", mod_name ); + return qfalse; + } + + // swap all the frames + frame = (md3Frame_t *) ( (byte *)mod->md3[lod] + mod->md3[lod]->ofsFrames ); + for ( i = 0 ; i < mod->md3[lod]->numFrames ; i++, frame++) { + frame->radius = LittleFloat( frame->radius ); + for ( j = 0 ; j < 3 ; j++ ) { + frame->bounds[0][j] = LittleFloat( frame->bounds[0][j] ); + frame->bounds[1][j] = LittleFloat( frame->bounds[1][j] ); + frame->localOrigin[j] = LittleFloat( frame->localOrigin[j] ); + } + } + + // swap all the tags + tag = (md3Tag_t *) ( (byte *)mod->md3[lod] + mod->md3[lod]->ofsTags ); + for ( i = 0 ; i < mod->md3[lod]->numTags * mod->md3[lod]->numFrames ; i++, tag++) { + for ( j = 0 ; j < 3 ; j++ ) { + tag->origin[j] = LittleFloat( tag->origin[j] ); + tag->axis[0][j] = LittleFloat( tag->axis[0][j] ); + tag->axis[1][j] = LittleFloat( tag->axis[1][j] ); + tag->axis[2][j] = LittleFloat( tag->axis[2][j] ); + } + } + + // swap all the surfaces + surf = (md3Surface_t *) ( (byte *)mod->md3[lod] + mod->md3[lod]->ofsSurfaces ); + for ( i = 0 ; i < mod->md3[lod]->numSurfaces ; i++) { + + LL(surf->ident); + LL(surf->flags); + LL(surf->numFrames); + LL(surf->numShaders); + LL(surf->numTriangles); + LL(surf->ofsTriangles); + LL(surf->numVerts); + LL(surf->ofsShaders); + LL(surf->ofsSt); + LL(surf->ofsXyzNormals); + LL(surf->ofsEnd); + + if ( surf->numVerts > SHADER_MAX_VERTEXES ) { + ri.Error (ERR_DROP, "R_LoadMD3: %s has more than %i verts on a surface (%i)", + mod_name, SHADER_MAX_VERTEXES, surf->numVerts ); + } + if ( surf->numTriangles*3 > SHADER_MAX_INDEXES ) { + ri.Error (ERR_DROP, "R_LoadMD3: %s has more than %i triangles on a surface (%i)", + mod_name, SHADER_MAX_INDEXES / 3, surf->numTriangles ); + } + + // change to surface identifier + surf->ident = SF_MD3; + + // lowercase the surface name so skin compares are faster + Q_strlwr( surf->name ); + + // strip off a trailing _1 or _2 + // this is a crutch for q3data being a mess + j = strlen( surf->name ); + if ( j > 2 && surf->name[j-2] == '_' ) { + surf->name[j-2] = 0; + } + + // register the shaders + shader = (md3Shader_t *) ( (byte *)surf + surf->ofsShaders ); + for ( j = 0 ; j < surf->numShaders ; j++, shader++ ) { + shader_t *sh; + + sh = R_FindShader( shader->name, LIGHTMAP_NONE, qtrue ); + if ( sh->defaultShader ) { + shader->shaderIndex = 0; + } else { + shader->shaderIndex = sh->index; + } + } + + // swap all the triangles + tri = (md3Triangle_t *) ( (byte *)surf + surf->ofsTriangles ); + for ( j = 0 ; j < surf->numTriangles ; j++, tri++ ) { + LL(tri->indexes[0]); + LL(tri->indexes[1]); + LL(tri->indexes[2]); + } + + // swap all the ST + st = (md3St_t *) ( (byte *)surf + surf->ofsSt ); + for ( j = 0 ; j < surf->numVerts ; j++, st++ ) { + st->st[0] = LittleFloat( st->st[0] ); + st->st[1] = LittleFloat( st->st[1] ); + } + + // swap all the XyzNormals + xyz = (md3XyzNormal_t *) ( (byte *)surf + surf->ofsXyzNormals ); + for ( j = 0 ; j < surf->numVerts * surf->numFrames ; j++, xyz++ ) + { + xyz->xyz[0] = LittleShort( xyz->xyz[0] ); + xyz->xyz[1] = LittleShort( xyz->xyz[1] ); + xyz->xyz[2] = LittleShort( xyz->xyz[2] ); + + xyz->normal = LittleShort( xyz->normal ); + } + + + // find the next surface + surf = (md3Surface_t *)( (byte *)surf + surf->ofsEnd ); + } + return true; +#endif +} + +/* +============================================================================== + +dll setup + +============================================================================== +*/ + +/* +=============== +Maya_Shutdown +=============== +*/ +void Maya_Shutdown( void ) { + if ( initialized ) { + errorMessage.Clear(); + initialized = false; + + // This shuts down the entire app somehow, so just ignore it. + //MLibrary::cleanup(); + } +} + +/* +=============== +Maya_ConvertModel +=============== +*/ +const char *Maya_ConvertModel( const char *ospath, const char *commandline ) { + + errorMessage = "Ok"; + + try { + idExportOptions options( commandline, ospath ); + idMayaExport export( options ); + + export.ConvertModel(); + } + + catch( idException &exception ) { + errorMessage = exception.error; + } + + return errorMessage; +} + +/* +=============== +dllEntry +=============== +*/ +bool dllEntry( int version, idCommon *common, idSys *sys ) { + + if ( !common || !sys ) { + return false; + } + + ::common = common; + ::sys = sys; + ::cvarSystem = NULL; + + idLib::sys = sys; + idLib::common = common; + idLib::cvarSystem = NULL; + idLib::fileSystem = NULL; + + idLib::Init(); + + if ( version != MD5_VERSION ) { + common->Printf( "Error initializing Maya exporter: DLL version %d different from .exe version %d\n", MD5_VERSION, version ); + return false; + } + + if ( !initialized ) { + MStatus status; + + status = MLibrary::initialize( GAME_NAME, true ); + if ( !status ) { + common->Printf( "Error calling MLibrary::initialize (%s)\n", status.errorString().asChar() ); + return false; + } + + initialized = true; + } + + return true; +} + +// Force type checking on the interface functions to help ensure that they match the ones in the .exe +const exporterDLLEntry_t ValidateEntry = &dllEntry; +const exporterInterface_t ValidateConvert = &Maya_ConvertModel; +const exporterShutdown_t ValidateShutdown = &Maya_Shutdown; diff --git a/src/MayaImport/maya_main.h b/src/MayaImport/maya_main.h new file mode 100644 index 0000000..af94187 --- /dev/null +++ b/src/MayaImport/maya_main.h @@ -0,0 +1,18 @@ + +#ifndef __MAYA_MAIN_H__ +#define __MAYA_MAIN_H__ + +/* +============================================================== + + Maya Import + +============================================================== +*/ + + +typedef bool ( *exporterDLLEntry_t )( int version, idCommon *common, idSys *sys ); +typedef const char *( *exporterInterface_t )( const char *ospath, const char *commandline ); +typedef void ( *exporterShutdown_t )( void ); + +#endif /* !__MAYA_MAIN_H__ */ diff --git a/src/MayaImport/mayaimport.def b/src/MayaImport/mayaimport.def new file mode 100644 index 0000000..adb9528 --- /dev/null +++ b/src/MayaImport/mayaimport.def @@ -0,0 +1,4 @@ +EXPORTS + dllEntry + Maya_ConvertModel + Maya_Shutdown diff --git a/src/PREY.sln b/src/PREY.sln new file mode 100644 index 0000000..019bcdf --- /dev/null +++ b/src/PREY.sln @@ -0,0 +1,38 @@ +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual Studio 2005 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Game", "2005game.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", "2005idlib.vcproj", "{49BEC5C6-B964-417A-851E-808886B57400}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug with inlines and memory log|Win32 = Debug with inlines and memory log|Win32 + Debug with inlines|Win32 = Debug with inlines|Win32 + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {49BEC5C6-B964-417A-851E-808886B57430}.Debug with inlines and memory log|Win32.ActiveCfg = Debug|Win32 + {49BEC5C6-B964-417A-851E-808886B57430}.Debug with inlines and memory log|Win32.Build.0 = Debug|Win32 + {49BEC5C6-B964-417A-851E-808886B57430}.Debug with inlines|Win32.ActiveCfg = Debug with inlines|Win32 + {49BEC5C6-B964-417A-851E-808886B57430}.Debug with inlines|Win32.Build.0 = Debug with inlines|Win32 + {49BEC5C6-B964-417A-851E-808886B57430}.Debug|Win32.ActiveCfg = Debug|Win32 + {49BEC5C6-B964-417A-851E-808886B57430}.Debug|Win32.Build.0 = Debug|Win32 + {49BEC5C6-B964-417A-851E-808886B57430}.Release|Win32.ActiveCfg = Release|Win32 + {49BEC5C6-B964-417A-851E-808886B57430}.Release|Win32.Build.0 = Release|Win32 + {49BEC5C6-B964-417A-851E-808886B57400}.Debug with inlines and memory log|Win32.ActiveCfg = Debug|Win32 + {49BEC5C6-B964-417A-851E-808886B57400}.Debug with inlines and memory log|Win32.Build.0 = Debug|Win32 + {49BEC5C6-B964-417A-851E-808886B57400}.Debug with inlines|Win32.ActiveCfg = Debug with inlines|Win32 + {49BEC5C6-B964-417A-851E-808886B57400}.Debug with inlines|Win32.Build.0 = Debug with inlines|Win32 + {49BEC5C6-B964-417A-851E-808886B57400}.Debug|Win32.ActiveCfg = Debug|Win32 + {49BEC5C6-B964-417A-851E-808886B57400}.Debug|Win32.Build.0 = Debug|Win32 + {49BEC5C6-B964-417A-851E-808886B57400}.Release|Win32.ActiveCfg = Release|Win32 + {49BEC5C6-B964-417A-851E-808886B57400}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/Prey/ai_Navigator.cpp b/src/Prey/ai_Navigator.cpp new file mode 100644 index 0000000..9a2cd71 --- /dev/null +++ b/src/Prey/ai_Navigator.cpp @@ -0,0 +1,268 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + + +/* +================= +hhNavigator::hhNavigator() +================= +*/ +hhNavigator::hhNavigator(void) { + + + hhSelf = NULL; + +// followStateFunction = FollowAllyStay; + +} //. hhNavigator::hhNavigator(void) + + +/* +======================== +hhNavigator::Spawn(void) +======================== +*/ + +// Required for CLASS_DECLARATION +void hhNavigator::Spawn(void) { + + +} //. hhNavigator::hhNavigator(void) + + +/* +======================================= +hhNavigator::SetOwner(idAI *owner) +======================================= +*/ +void hhNavigator::SetOwner(idAI *owner) { + + idNavigator::SetOwner(owner); + + if (owner->IsType(hhAI::Type)) { + hhSelf = static_cast(owner); + } + +} //. hhNavigator::SetOwner(idAI *) + +/* JRM- Removed because brain stuff handles this logic now +// +//=============================== +//hhNavigator::SetAlly(idActor *) +//=============================== +// +void hhNavigator::SetAlly(idActor *ally) { + + if (ally != NULL) { + FollowAlly(ally); + } + +} //. hhNavigator::SetAlly(idActor *) + + +// +//============================= +//hhNavigator::FollowAlly(ally) +//============================= +// +void hhNavigator::FollowAlly(idActor *ally) { + + // If we aren't on an hhAI, we can't follow, so leave + if (hhSelf == NULL) { + return; + } + + + + (this->*followStateFunction)(ally); + + + +} //. hhNavigator::FollowAlly(idActor *ally) +*/ + + + + + +/* HUMANHEAD JRM - REMOVED +// +//=================================== +//hhNavigator::FollowAllyStay(void) +//=================================== +// +void hhNavigator::FollowAllyStay(idActor *ally) { + static bool first = true; + + + if (first) { + if (ai_debug->integer > 0) { + gameLocal.Printf("in FollowAllyStay\n"); + } + first = false; + } + + if (hhSelf->AI_ALLY_FAR || + !hhSelf->AI_ALLY_VISIBLE) { + followStateFunction = FollowAllyFollow; + first = true; + } + else if (hhSelf->AI_ALLY_TOUCHED) { + followStateFunction = FollowAllyLead; + first = true; + } + else { // Just stay still + moveType = MOVE_NONE; + } + + +} //. hhNavigator::FollowAllyStay(void) + + +// +//=================================== +//hhNavigator::FollowAllyFollow(void) +//=================================== +// +void hhNavigator::FollowAllyFollow(idActor *ally) { + static bool first = true; + + + hhSelf->AI_FOLLOW_ALLY = true; + + if (first) { + if (ai_debug->integer > 0) { + gameLocal.Printf("in FollowAllyFollow\n"); + } + first = false; + } + + if (hhSelf->AI_ALLY_NEAR) { + StopMove(); + + followStateFunction = FollowAllyStay; + hhSelf->AI_FOLLOW_ALLY = false; + first = true; + } + // In case the min_dist is too close + else if (hhSelf->AI_ALLY_TOUCHED) { + followStateFunction = FollowAllyLead; + hhSelf->AI_FOLLOW_ALLY = false; + first = true; + } + else { + moveDest = ally->GetFloorPos(); + goal = ally; + moveType = MOVE_TO_ALLY; + + if (aas) { + toAreaNum = aas->PointReachableAreaNum( moveDest ); + toAreaEnemy = false; + } + } //. Follow the player + +} //. hhNavigator::FollowAllyFollow(void) + + +// +//=================================== +//hhNavigator::FollowAllyLead(void) +//=================================== +// +void hhNavigator::FollowAllyLead(idActor *ally) { + static bool first = true; + static int nextUpdateTime; + idVec3 leadPosition; + + + hhSelf->AI_LEAD_ALLY = true; + + if (first) { + if (ai_debug->integer > 0) { + gameLocal.Printf("in FollowAllyLead\n"); + } + first = false; + } + + // Reset this variable, as it was used to get here. + if (hhSelf->AI_ALLY_TOUCHED) { + hhSelf->AI_ALLY_TOUCHED = false; + nextUpdateTime = 0; + } + + if (gameLocal.time * 1000.0f >= nextUpdateTime) { + //if (nextUpdateTime == 0) { + + leadPosition = FindNewLeadPosition(ally); + + if (leadPosition != vec3_origin) { + moveDest = leadPosition; + moveType = MOVE_TO_ALLY; + goal = NULL; + + if (aas) { + toAreaNum = aas->PointReachableAreaNum(moveDest); + toAreaEnemy = false; + } + + nextUpdateTime = gameLocal.time * 1000.0f + + hhSelf->follow_lead_update_time; + } + + } //. Time to update position + + if (!hhSelf->AI_ALLY_NEAR) { + followStateFunction = FollowAllyStay; + hhSelf->AI_LEAD_ALLY = false; + first = true; + } + + +} //. hhNavigator::FollowAllyLead(void) + + +// +//================================ +//hhNavigator::FindNewLeadPosition +//================================ +// +idVec3 hhNavigator::FindNewLeadPosition(idActor *ally) { + idVec3 direction; + aasTrace_t trace; + + + direction = hhSelf->GetFloorPos() - ally->GetFloorPos(); + direction.Normalize(); + direction *= hhSelf->follow_min_dist; + + if (aas) { + idVec3 bestPos; + + bestPos = aas->FindNearestPoint(ally->GetFloorPos(), + hhSelf->GetFloorPos(), + hhSelf->follow_min_dist); + if (bestPos != vec3_origin) { + return(bestPos); + } + + // Didn't find a good point + + // Just move as far as we can in the direction nudged + aas->Trace(trace, hhSelf->GetPhysics()->GetOrigin(), + hhSelf->GetPhysics()->GetOrigin() + direction); + + //? How does this work on slopes? + //! Do we want to use the floor position? + return(ally->GetFloorPos() + direction * trace.fraction); + } + + return(ally->GetFloorPos() + direction); + + + +} //. hhNavigator::FindNewLeadPosition(idActor *) +*/ diff --git a/src/Prey/ai_Navigator.h b/src/Prey/ai_Navigator.h new file mode 100644 index 0000000..a379653 --- /dev/null +++ b/src/Prey/ai_Navigator.h @@ -0,0 +1,41 @@ + +#ifndef __PREY_AI_NAVIGATOR_H__ +#define __PREY_AI_NAVIGATOR_H__ + +// Forward declaration for hhAI +class hhAI; + +class hhNavigator : public idNavigator { + +public: + + hhNavigator(void); + + void Spawn(void); + + //virtual void SetAlly(idActor *ally); JRM removed + + virtual void SetOwner(idAI *owner); + + virtual boolean IsNearDest( void ); + +protected: + + hhAI * hhSelf; + + //virtual void FollowAlly(idActor *ally); JRM removed + + /* JRM removed + void FollowAllyStay(idActor *ally); + void FollowAllyFollow(idActor *ally); + void FollowAllyLead(idActor *ally); + */ + +// void (hhNavigator::* followStateFunction) (idActor *ally); JRM removed + + //idVec3 FindNewLeadPosition(idActor *ally); JRM removed + +}; + + +#endif /* __PREY_AI_NAVIGATOR_H__ */ diff --git a/src/Prey/ai_centurion.cpp b/src/Prey/ai_centurion.cpp new file mode 100644 index 0000000..5d04c40 --- /dev/null +++ b/src/Prey/ai_centurion.cpp @@ -0,0 +1,529 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef AI_CenturionLaunch("centurionFire", "d"); //called from anims to handle firing for centurion +const idEventDef AI_CenturionRoar("centurionRoar"); //called from level-script to cause centurion to roar +const idEventDef AI_CenturionArmChop("centurionArmChop"); //called from level-script to actually cause centurion to loose arm +const idEventDef AI_CenturionLooseArm("centurionLooseArm"); //internal: used to actually 'loose' arm +const idEventDef AI_CenturionForceFieldNotify("preForcefieldNotify"); +const idEventDef AI_CenturionForceFieldToggle("forcefieldToggle", "d"); +const idEventDef AI_CenturionInTunnel("playerInBox", "dE"); +const idEventDef AI_CenturionReachedTunnel("reachedTunnel"); +const idEventDef AI_CenturionMoveToTunnel("moveToTunnel", NULL, 'd'); +const idEventDef AI_CheckForObstructions("checkForObstructions", "d", 'd'); +const idEventDef AI_DestroyObstruction("destroyObstruction"); +const idEventDef AI_MoveToObstruction("moveToObstruction"); +const idEventDef AI_ReachedObstruction("reachedObstruction"); +const idEventDef AI_CloseToObstruction("closeToObstruction", 0, 'd'); +const idEventDef AI_BackhandImpulse("", "e"); +const idEventDef AI_EnemyCloseToObstruction("enemyCloseToObstruction", NULL, 'd'); +const idEventDef AI_TakeDamage("takeDamage", "d"); +const idEventDef AI_FindNearbyEnemy("findNearbyEnemy", "f", 'e'); + +CLASS_DECLARATION( hhMonsterAI, hhCenturion ) + EVENT( AI_CenturionLaunch, hhCenturion::Event_CenturionLaunch ) + EVENT( AI_CenturionRoar, hhCenturion::Event_ScriptedRoar ) + EVENT( AI_CenturionArmChop, hhCenturion::Event_ScriptedArmChop ) + EVENT( AI_CenturionLooseArm, hhCenturion::Event_CenturionLooseArm ) + EVENT( AI_CenturionInTunnel, hhCenturion::Event_PlayerInTunnel ) + EVENT( AI_CenturionReachedTunnel, hhCenturion::Event_ReachedTunnel ) + EVENT( AI_CenturionMoveToTunnel, hhCenturion::Event_MoveToTunnel ) + EVENT( AI_CenturionForceFieldNotify, hhCenturion::Event_ForceFieldNotify ) + EVENT( AI_CenturionForceFieldToggle, hhCenturion::Event_ForceFieldToggle ) + EVENT( AI_CheckForObstructions, hhCenturion::Event_CheckForObstruction ) + EVENT( AI_MoveToObstruction, hhCenturion::Event_MoveToObstruction ) + EVENT( AI_DestroyObstruction, hhCenturion::Event_DestroyObstruction ) + EVENT( AI_ReachedObstruction, hhCenturion::Event_ReachedObstruction ) + EVENT( AI_CloseToObstruction, hhCenturion::Event_CloseToObstruction ) + EVENT( AI_BackhandImpulse, hhCenturion::Event_BackhandImpulse ) + EVENT( AI_EnemyCloseToObstruction, hhCenturion::Event_EnemyCloseToObstruction ) + EVENT( AI_TakeDamage, hhCenturion::Event_TakeDamage ) + EVENT( AI_FindNearbyEnemy, hhCenturion::Event_FindNearbyEnemy ) +END_CLASS + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + +void hhCenturion::Spawn() { + AI_CENTURION_ARM_MISSING = false; + AI_CENTURION_REQUIRE_ROAR = false; + AI_CENTURION_ARM_TUNNEL = false; + AI_CENTURION_SCRIPTED_ROAR = 0; + AI_CENTURION_FORCEFIELD_WAIT = false; + AI_CENTURION_SCRIPTED_TUNNEL = 0; +} + +void hhCenturion::Event_PostSpawn( void ) { + hhMonsterAI::Event_PostSpawn(); + + const idKeyValue *kv = spawnArgs.MatchPrefix( "aasObstacle" ); + idEntityPtr ent; + while ( kv ) { + ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent.IsValid() ) { + obstacles.AddUnique( ent ); + } + kv = spawnArgs.MatchPrefix( "aasObstacle", kv ); + } +} + +#define LinkScriptVariable( name ) name.LinkTo( scriptObject, #name ) +void hhCenturion::LinkScriptVariables( void ) { + hhMonsterAI::LinkScriptVariables(); + + LinkScriptVariable(AI_CENTURION_ARM_MISSING); + LinkScriptVariable(AI_CENTURION_REQUIRE_ROAR); + LinkScriptVariable(AI_CENTURION_ARM_TUNNEL); + LinkScriptVariable(AI_CENTURION_SCRIPTED_ROAR); + LinkScriptVariable(AI_CENTURION_SCRIPTED_TUNNEL); + LinkScriptVariable(AI_CENTURION_FORCEFIELD_WAIT); +} + + +void hhCenturion::Event_ForceFieldNotify() { + if( !armchop_Target.GetEntity() ) { + return; //must not be in arm-chop mode, so we don't care yet + } + AI_CENTURION_REQUIRE_ROAR = true; + AI_CENTURION_FORCEFIELD_WAIT = true; +} + +void hhCenturion::Event_ForceFieldToggle( int toggle ) { + if( !armchop_Target.GetEntity() ) { + return; + } + AI_CENTURION_FORCEFIELD_WAIT = false; + if (!toggle) { + AI_CENTURION_REQUIRE_ROAR = false; + } else if( AI_CENTURION_ARM_TUNNEL ) { + Event_ScriptedArmChop(); + return; + } +} + +void hhCenturion::Event_PlayerInTunnel( int toggle, idEntity* ent ) { + if( AI_CENTURION_ARM_MISSING ) { //don't care if the player goes back in tunnel once we already lost our arm + return; + } + + if( toggle ) { + if( !ent ) { + gameLocal.DWarning( "entity needs to be present for tunnel event" ); + return; + } + armchop_Target = ent; + AI_CENTURION_SCRIPTED_TUNNEL = 1; + } + else { + armchop_Target = NULL; + AI_CENTURION_SCRIPTED_TUNNEL = 0; + } +} + +void hhCenturion::Event_MoveToTunnel() { + if (!armchop_Target.IsValid()) { + idThread::ReturnInt(0); + return; + } + + StopMove(MOVE_STATUS_DONE); + MoveToPosition(armchop_Target->GetOrigin()); + idThread::ReturnInt(1); +} + +void hhCenturion::Event_ReachedTunnel() { + if( armchop_Target.IsValid() ) { + idAngles faceAngles = armchop_Target->GetAxis()[0].ToAngles(); + ideal_yaw = faceAngles.yaw; + current_yaw = faceAngles.yaw; + SetAxis( armchop_Target->GetAxis() ); + } +} + +void hhCenturion::Event_ScriptedRoar() { + if ( AI_CENTURION_SCRIPTED_ROAR > 0 ) { + gameLocal.Warning( "centurionRoar() called more than once!\n"); + } else { + AI_CENTURION_SCRIPTED_ROAR = 1; + } +} + +void hhCenturion::Event_ScriptedArmChop() { + const function_t* newstate = NULL; + newstate = GetScriptFunction( "state_ScriptedArmChop" ); + if( newstate ) { + SetState( newstate ); + } + else { + gameLocal.Warning( "Unable to find 'state_ScriptedArmChop' on centurion" ); + } +} + +void hhCenturion::Event_CenturionLooseArm() { + idDict args; + idEntity* ent = NULL; + idVec3 jointLoc; + idMat3 jointAxis; + + animator.GetJointTransform( spawnArgs.GetString("arm_severjoint", ""), gameLocal.time, jointLoc, jointAxis ); + jointLoc = renderEntity.origin + jointLoc * renderEntity.axis; + args.SetVector( "origin", jointLoc ); + args.SetMatrix( "rotation", renderEntity.axis ); + args.SetBool( "spin", 0 ); + args.SetFloat( "triggersize", 48.f ); + args.SetBool( "enablePickup", true ); + args.SetFloat( "respawn", 0.f ); + ent = gameLocal.SpawnObject( spawnArgs.GetString("def_arm_weaponclass", ""), &args ); + + SetSkinByName( spawnArgs.GetString( "skin_arm_gone" ) ); + + hhFxInfo fx; + fx.SetEntity( this ); + fx.RemoveWhenDone( true ); + SpawnFxLocal( spawnArgs.GetString( "fx_armchop" ), jointLoc, mat3_identity, &fx ); + + //drop any stuck arrows + idEntity *next; + for( ent = teamChain; ent != NULL; ent = next ) { + next = ent->GetTeamChain(); + if ( ent && ent->IsType( hhProjectile::Type ) ) { + ent->Unbind(); + ent->Hide(); + ent->PostEventSec( &EV_Remove, 5 ); + next = teamChain; + } + } + + AI_CENTURION_ARM_MISSING = true; +} + +void hhCenturion::Event_CenturionLaunch( const idList* parmList ) { + const idDict* projDef = NULL; + const idSoundShader* soundShader = NULL; + + // parms: joint, projectileDef, sound, fx + if( !parmList || parmList->Num() != 5 ) { + gameLocal.Warning( "Incorrect paramater number" ); + return; + } +//Rbarrel_A projectile_centurion_autocannon snd_fire fx_muzzleFlash + const char* jointName = (*parmList)[ 0 ].c_str(); + const char* projectileDefName = (*parmList)[ 1 ].c_str(); + const char* soundName = (*parmList)[ 2 ].c_str(); + const char* fxName = (*parmList)[ 3 ].c_str(); + int autoAim = atoi( (*parmList)[ 4 ].c_str() ); + + if( AI_CENTURION_ARM_MISSING ) { //skip firing if joint is on the severed arm + if( !idStr::Icmp(jointName, spawnArgs.GetString("severed_jointA", "")) || !idStr::Icmp(jointName, spawnArgs.GetString("severed_jointB", "")) ) { + return; + } + } + + projDef = gameLocal.FindEntityDefDict( projectileDefName, false ); + HH_ASSERT( !shootTarget.IsValid() ); + // If autoAim is true and we're not blending, and we're facing the enemy, auto-aim + if ( ( autoAim == 1 || ( autoAim == 2 && gameLocal.random.RandomInt(100) < 50 ) ) && torsoAnim.animBlendFrames == 0 && FacingEnemy( 5.0f ) ) { + AimedAttackMissile( jointName, projDef ); + } else { // Otherwise do a normal missile attack + Event_AttackMissile( jointName, projDef, 1 ); + } + + if(idStr::Cmpn( soundName, "snd_", 4)) { + soundShader = declManager->FindSound( soundName ); + if( soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", soundName ); + } + StartSoundShader( soundShader, SND_CHANNEL_WEAPON, 0, false, NULL ); + } + else { + if( !StartSound(soundName, SND_CHANNEL_WEAPON, 0, false, NULL) ) { + gameLocal.Warning( "Framecommand 'centurionFire' on entity '%s' could not find sound '%s'", GetName(), soundName ); + } + } + BroadcastFxInfoAlongBone( spawnArgs.GetString(fxName), jointName ); +} + + +void hhCenturion::Event_DestroyObstruction() { + if( pillarEntity.IsValid() ) { + pillarEntity->PostEventMS( &EV_Activate, 0.f, this ); + } +} + +void hhCenturion::Event_CheckForObstruction( int checkPathToPillar ) { + bool obstacle = false; + predictedPath_t path; + if( enemy.IsValid() ) { + idVec3 end = enemy->GetPhysics()->GetOrigin(); + if ( !checkPathToPillar ) { + trace_t tr; + idVec3 toPos, eye = GetEyePosition(); + + if ( enemy->IsType( idActor::Type ) ) { + toPos = ( ( idActor * )enemy.GetEntity() )->GetEyePosition(); + } else { + toPos = enemy->GetPhysics()->GetOrigin(); + } + + gameLocal.clip.TracePoint( tr, eye, toPos, MASK_SHOT_BOUNDINGBOX, this ); + + idEntity *traceEnt = gameLocal.GetTraceEntity( tr ); + if ( traceEnt && ( tr.fraction < 1.0f || traceEnt != enemy.GetEntity() ) ) { + //check to see if the other object is an pillar... + if( traceEnt->spawnArgs.GetInt("centurion_pillar", "0") == 1 ) { + pillarEntity = traceEnt; + obstacle = true; + } + } + + } else if ( pillarEntity.IsValid() ) { + idAI::PredictPath( this, this->aas, physicsObj.GetOrigin(), enemy->GetPhysics()->GetOrigin() - physicsObj.GetOrigin(), 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : (SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path ); + if( path.endEvent != 0 && path.blockingEntity ) { + //check to see if the other object is an pillar... + if( path.blockingEntity->spawnArgs.GetInt("centurion_pillar", "0") == 1 ) { + //check to see if we can path to the obstacle clearly + pillarEntity = path.blockingEntity; + //idAI::PredictPath( this, this->aas, physicsObj.GetOrigin(), pillarEntity->GetPhysics()->GetOrigin() - physicsObj.GetOrigin(), 1000, 1000, (SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA), path ); + //if( path.endEvent != 0 && path.blockingEntity == pillarEntity.GetEntity() ) { + obstacle = true; + //} + } + } + } + } + idThread::ReturnInt( (int)obstacle ); +} + +void hhCenturion::Event_CloseToObstruction() { + if( physicsObj.GetAbsBounds().IntersectsBounds(pillarEntity->GetPhysics()->GetAbsBounds()) ) { + idThread::ReturnInt( 1 ); + return; + } + idThread::ReturnInt( 0 ); +} + +void hhCenturion::Event_ReachedObstruction() { + FaceEntity( pillarEntity.GetEntity() ); +} + +void hhCenturion::Event_MoveToObstruction() { + idVec3 temp; + if ( aas ) { + int toAreaNum = PointReachableAreaNum( pillarEntity.GetEntity()->GetOrigin() ); + temp = pillarEntity.GetEntity()->GetOrigin(); + aas->PushPointIntoAreaNum( toAreaNum, temp ); + MoveToPosition( temp ); + } else { + gameLocal.Warning( "Centurion has no aas for MoveToObstruction\n" ); + } +} + +void hhCenturion::Think() { + PROFILE_SCOPE("AI", PROFMASK_NORMAL|PROFMASK_AI); + if (ai_skipThink.GetBool()) { + return; + } + + hhMonsterAI::Think(); + + if(ai_debugBrain.GetInteger() > 0 && state) { + if ( enemy.IsValid() && enemy->GetHealth() > 0 ) { + float dist = ( GetPhysics()->GetOrigin() - enemy->GetPhysics()->GetOrigin() ).LengthFast(); + gameRenderWorld->DrawText( va("%f", dist), this->GetEyePosition() + idVec3(0.0f, 0.0f, 40.0f), 0.75f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3()); + } + gameRenderWorld->DrawText(state->Name(), this->GetEyePosition() + idVec3(0.0f, 0.0f, 40.0f), 0.75f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3()); + gameRenderWorld->DrawText(torsoAnim.state, this->GetEyePosition() + idVec3(0.0f, 0.0f, 20.0f), 0.75f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3()); + gameRenderWorld->DrawText(legsAnim.state, this->GetEyePosition() + idVec3(0.0f, 0.0f, 0.0f), 0.75f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3()); + } +} + +bool hhCenturion::AttackMelee( const char *meleeDefName ) { + const idDict *meleeDef; + idActor *enemyEnt = enemy.GetEntity(); + const char *p; + const idSoundShader *shader; + + meleeDef = gameLocal.FindEntityDefDict( meleeDefName, false ); + if ( !meleeDef ) { + gameLocal.Error( "Unknown melee '%s'", meleeDefName ); + } + + if ( !enemyEnt ) { + p = meleeDef->GetString( "snd_miss" ); + if ( p && *p ) { + shader = declManager->FindSound( p ); + StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); + } + return false; + } + + // make sure the trace can actually hit the enemy + if ( !TestMelee() ) { + // missed + p = meleeDef->GetString( "snd_miss" ); + if ( p && *p ) { + shader = declManager->FindSound( p ); + StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); + } + return false; + } + + // + // 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; + + enemyEnt->Damage( this, this, globalKickDir, meleeDefName, 1.0f, INVALID_JOINT ); + if ( enemyEnt->IsActiveAF() && !enemyEnt->IsType( idPlayer::Type ) ) { + PostEventMS( &AI_BackhandImpulse, 0, enemyEnt ); + } + + lastAttackTime = gameLocal.time; + + return true; +} + +void hhCenturion::Event_BackhandImpulse( idEntity* ent ) { + const idDict *meleeDef = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_impulse_damage"), false ); + if ( !meleeDef || !ent ) { + return; + } + idVec3 kickDir; + meleeDef->GetVector( "kickDir", "0 0 0", kickDir ); + idVec3 globalKickDir = ( viewAxis * physicsObj.GetGravityAxis() ) * kickDir; + globalKickDir *= ent->spawnArgs.GetFloat( "kick_scale", "1.0" ); + ent->ApplyImpulse( this, 0, ent->GetOrigin(), meleeDef->GetFloat( "push_ragdoll" ) * globalKickDir ); +} + +/* +===================== +hhCenturion::Save +===================== +*/ +void hhCenturion::Save( idSaveGame *savefile ) const { + armchop_Target.Save( savefile ); + pillarEntity.Save( savefile ); +} + +/* +===================== +hhCenturion::Restore +===================== +*/ +void hhCenturion::Restore( idRestoreGame *savefile ) { + armchop_Target.Restore( savefile ); + pillarEntity.Restore( savefile ); + + // Restore the obstacle list + const idKeyValue *kv = spawnArgs.MatchPrefix( "aasObstacle" ); + idEntityPtr ent; + while ( kv ) { + ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent.IsValid() ) { + obstacles.AddUnique( ent ); + } + kv = spawnArgs.MatchPrefix( "aasObstacle", kv ); + } +} + +void hhCenturion::Event_EnemyCloseToObstruction(void) { + bool close = false; + if ( pillarEntity.IsValid() && enemy.IsValid() ) { + float obs_dist = ( pillarEntity->GetOrigin() - GetOrigin() ).Length(); + float dist = ( enemy->GetOrigin() - GetOrigin() ).Length(); + + // If the enemy is further from me than the pillar... + if ( dist > obs_dist) { + dist = ( pillarEntity->GetOrigin() - enemy->GetOrigin() ).Length(); + // If the enemy is less than 400 units from the pillar, then he's 'close' + if ( dist < 400.0f ) { + close = true; + } + } + } + idThread::ReturnInt( (int) close ); +} + +void hhCenturion::AimedAttackMissile( const char *jointname, const idDict *projDef) { + idProjectile *proj; + idVec3 target, origin = GetOrigin(); + bool inShuttle = false; + + if ( shootTarget.IsValid() ) { + target = shootTarget->GetOrigin(); + } else if ( enemy.IsValid() ) { + target = enemy->GetOrigin(); + if ( enemy->IsType( idActor::Type ) ) { + target.z += enemy->EyeHeight() / 4.0f; + } + } else { + // No target? Do the default attack + Event_AttackMissile( jointname, projDef, 1 ); + return; + } + + // If target is too close do a non-aimed attack + if ( fabsf( origin.x - target.x ) < 256 && + fabsf( origin.y - target.y ) < 256 ) { + Event_AttackMissile( jointname, projDef, 1 ); + return; + } + + idVec3 dist = origin - target; + + if ( shootTarget.IsValid() ) { + proj = LaunchProjectile( jointname, shootTarget.GetEntity(), true, projDef ); + } else { + proj = LaunchProjectile( jointname, enemy.GetEntity(), true, projDef ); + } +} + +void hhCenturion::Event_TakeDamage(int takeDamage) { + // Set the takedamage flag + fl.takedamage = (takeDamage != 0); +} + +void hhCenturion::Event_FindNearbyEnemy( float distance ) { + // Search for the monster nearest to us + idAI *nearest = NULL; + float dist, nearDist = idMath::INFINITY; + idAI *ai = reinterpret_cast (gameLocal.FindEntityOfType( idAI::Type, NULL )); + while (ai) { + if (ai != this) { // Don't target yourself + dist = (ai->GetOrigin() - GetOrigin()).Length(); + if (dist < nearDist) { + nearDist = dist; + nearest = ai; + } + } + ai = reinterpret_cast (gameLocal.FindEntityOfType( idAI::Type, ai )); + } + + // If we found one near us and it's near enough, return it + if (nearest && nearDist < distance) { + idThread::ReturnEntity(nearest); + } else { + idThread::ReturnEntity(NULL); + } +} + +void hhCenturion::Event_Touch( idEntity *other, trace_t *trace ) { + if ( (!enemy.GetEntity() || other->IsType( hhPlayer::Type )) && !other->fl.notarget && ( ReactionTo( other ) & ATTACK_ON_ACTIVATE ) ) { + Activate( other ); + SetEnemy( static_cast ( other ) ); + } + AI_PUSHED = true; +} + +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/src/Prey/ai_centurion.h b/src/Prey/ai_centurion.h new file mode 100644 index 0000000..b640f64 --- /dev/null +++ b/src/Prey/ai_centurion.h @@ -0,0 +1,84 @@ +#ifndef __PREY_AI_CENTURION_H__ +#define __PREY_AI_CENTURION_H__ + +/*********************************************************************** + hhCenturion. + Centurion AI. +***********************************************************************/ +class hhCenturion : public hhMonsterAI { + +public: + CLASS_PROTOTYPE(hhCenturion); + +#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + void Event_CenturionLaunch( const idList* parmList ) {}; + void Event_ScriptedRoar() {}; + void Event_ScriptedArmChop() {}; + void Event_CenturionLooseArm() {}; + void Event_ForceFieldNotify() {}; + void Event_ForceFieldToggle( int toggle ) {}; + void Event_PlayerInTunnel( int toggle, idEntity* ent ) {}; + void Event_ReachedTunnel() {}; + void Event_MoveToTunnel() {}; + + bool AttackMelee( const char *meleeDefName ) { return true; }; + void Event_CheckForObstruction( int checkPathToPillar ) {}; + void Event_MoveToObstruction() {}; + void Event_DestroyObstruction() {}; + void Event_ReachedObstruction() {}; + void Event_CloseToObstruction() {}; + void Event_EnemyCloseToObstruction() {}; + void Event_BackhandImpulse( idEntity* ent ) {}; + virtual void Event_PostSpawn( void ) {}; + void Event_TakeDamage( int takeDamage ) {}; + void Event_FindNearbyEnemy( float distance ) {}; + virtual void Event_Touch( idEntity *other, trace_t *trace ) {}; +#else + +public: + void Spawn(); + void Think(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); +protected: + void Event_CenturionLaunch( const idList* parmList ); + void Event_ScriptedRoar(); + void Event_ScriptedArmChop(); + void Event_CenturionLooseArm(); + void Event_ForceFieldNotify(); + void Event_ForceFieldToggle( int toggle ); + void Event_PlayerInTunnel( int toggle, idEntity* ent ); + void Event_ReachedTunnel(); + void Event_MoveToTunnel(); + + void LinkScriptVariables( void ); + bool AttackMelee( const char *meleeDefName ); + void Event_CheckForObstruction( int checkPathToPillar ); + void Event_MoveToObstruction(); + void Event_DestroyObstruction(); + void Event_ReachedObstruction(); + void Event_CloseToObstruction(); + void Event_EnemyCloseToObstruction(); + void Event_BackhandImpulse( idEntity* ent ); + virtual void Event_PostSpawn( void ); + void Event_TakeDamage( int takeDamage ); + void Event_FindNearbyEnemy( float distance ); + virtual void Event_Touch( idEntity *other, trace_t *trace ); + + void AimedAttackMissile( const char *jointname, const idDict *projDef ); + + idEntityPtr armchop_Target; + idEntityPtr pillarEntity; + + idScriptBool AI_CENTURION_ARM_MISSING; + idScriptBool AI_CENTURION_REQUIRE_ROAR; + idScriptBool AI_CENTURION_ARM_TUNNEL; + idScriptBool AI_CENTURION_FORCEFIELD_WAIT; + idScriptFloat AI_CENTURION_SCRIPTED_ROAR; + idScriptFloat AI_CENTURION_SCRIPTED_TUNNEL; + + idList< idEntityPtr > obstacles; +#endif +}; + +#endif //__PREY_AI_CENTURION_H__ \ No newline at end of file diff --git a/src/Prey/ai_crawler.cpp b/src/Prey/ai_crawler.cpp new file mode 100644 index 0000000..e367a87 --- /dev/null +++ b/src/Prey/ai_crawler.cpp @@ -0,0 +1,115 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +CLASS_DECLARATION( hhMonsterAI, hhCrawler ) + EVENT( EV_Touch, hhCrawler::Event_Touch ) +END_CLASS + + +void hhCrawler::Spawn() { + if( spawnArgs.FindKey("def_pickup") ) { + if ( gameLocal.isMultiplayer ) { + GetPhysics()->SetContents( CONTENTS_TRIGGER | CONTENTS_MONSTERCLIP ); + } else { + GetPhysics()->SetContents( CONTENTS_TRIGGER ); + } + } +} + +void hhCrawler::Event_Touch( idEntity *other, trace_t *trace ) { + hhPlayer *player; + + if( !spawnArgs.FindKey("def_pickup") || !other->IsType(idPlayer::Type) ) { + return; + } + + player = static_cast(other); + + Pickup( player ); +} + +void hhCrawler::Pickup( hhPlayer *player ) { + + if( !GiveToPlayer( player ) ) { + return; + } + + // play pickup sound + StartSoundShader( refSound.shader, SND_CHANNEL_ITEM, false ); // play what's defined in the entity + + // trigger our targets + ActivateTargets( player ); + + // clear our contents so the object isn't picked up twice + GetPhysics()->SetContents( 0 ); + + // hide the model + Hide(); + + if (player->hud) { + player->hud->SetStateInt("item", 1); + player->hud->SetStateString("itemtext", spawnArgs.GetString("inv_name")); + player->hud->SetStateString("itemicon", spawnArgs.GetString("inv_icon")); + } + + idStr str; + spawnArgs.GetString("inv_name", "Item", str); + + if (player == gameLocal.GetClientByNum(gameLocal.localClientNum)) { + gameLocal.Printf("Picked up a %s\n", str.c_str()); + } + + + PostEventMS( &EV_Remove, 2000 ); +} + +bool hhCrawler::GiveToPlayer( hhPlayer* player ) { + const char *pickupName = spawnArgs.GetString("def_pickup", NULL); + bool pickedUp = false; + + if ( player && pickupName ) { + idEntity *ent = gameLocal.SpawnObject(pickupName, NULL); + if (ent->IsType(hhItem::Type)) { + pickedUp = player->GiveItem( static_cast(ent) ); + } + ent->Hide(); + ent->PostEventMS(&EV_Remove, 2000); + } + + return pickedUp; +} + +void hhCrawler::Think( void ) { + PROFILE_SCOPE("AI", PROFMASK_NORMAL|PROFMASK_AI); + if (ai_skipThink.GetBool()) { //HUMANHEAD rww + return; + } + + if ( thinkFlags & TH_THINK ) { + current_yaw += deltaViewAngles.yaw; + ideal_yaw = idMath::AngleNormalize180( ideal_yaw + deltaViewAngles.yaw ); + deltaViewAngles.Zero(); + viewAxis = idAngles( 0, current_yaw, 0 ).ToMat3(); + + // HUMANHEAD NLA + physicsObj.ResetNumTouchEnt(0); + // HUMANHEAD END + // animation based movement + UpdateAIScript(); + AnimMove(); + } else if ( thinkFlags & TH_PHYSICS ) { + RunPhysics(); + } + + UpdateAnimation(); + Present(); + LinkCombat(); +} + +bool hhCrawler::UpdateAnimationControllers() { + //do nothing + return false; +} \ No newline at end of file diff --git a/src/Prey/ai_crawler.h b/src/Prey/ai_crawler.h new file mode 100644 index 0000000..1fc7178 --- /dev/null +++ b/src/Prey/ai_crawler.h @@ -0,0 +1,24 @@ +#ifndef __PREY_AI_CRAWLER_H__ +#define __PREY_AI_CRAWLER_H__ + +/*********************************************************************** + hhCrawler. + Crawler AI. +***********************************************************************/ +class hhCrawler : public hhMonsterAI { + +public: + CLASS_PROTOTYPE(hhCrawler); + +public: + void Spawn(); + +protected: + void Event_Touch( idEntity *other, trace_t *trace ); + void Pickup( hhPlayer *player ); + bool GiveToPlayer( hhPlayer* player ); + void Think(); + bool UpdateAnimationControllers(); +}; + +#endif //__PREY_AI_CRAWLER_H__ \ No newline at end of file diff --git a/src/Prey/ai_creaturex.cpp b/src/Prey/ai_creaturex.cpp new file mode 100644 index 0000000..b6787ff --- /dev/null +++ b/src/Prey/ai_creaturex.cpp @@ -0,0 +1,1669 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef CX_LaserOn("sunBeamOn", NULL, 'd'); +const idEventDef CX_LaserOff("sunBeamOff"); +const idEventDef MA_UpdateLasers(""); +const idEventDef MA_AssignLeftMuzzleFx( "", "e" ); +const idEventDef MA_AssignRightMuzzleFx( "", "e" ); +const idEventDef MA_AssignLeftImpactFx( "", "e" ); +const idEventDef MA_AssignRightImpactFx( "", "e" ); +const idEventDef MA_AssignLeftRechargeFx( "", "e" ); +const idEventDef MA_AssignRightRechargeFx( "", "e" ); +const idEventDef MA_SetGunOffset( "setGunOffset", "v" ); +const idEventDef MA_EndLeftBeams( "" ); +const idEventDef MA_EndRightBeams( "" ); +const idEventDef MA_HudEvent( "hudEvent", "s" ); +const idEventDef MA_GunRecharge( "gunRecharge", "d" ); +const idEventDef MA_EndRecharge( "endRecharge" ); +const idEventDef MA_ResetRechargeBeam( "" ); +const idEventDef MA_SparkLeft( "" ); +const idEventDef MA_SparkRight( "" ); +const idEventDef MA_LeftGunDeath( "" ); +const idEventDef MA_RightGunDeath( "" ); +const idEventDef MA_StartRechargeBeams( "" ); + +CLASS_DECLARATION(hhMonsterAI, hhCreatureX) + EVENT(CX_LaserOn, hhCreatureX::Event_LaserOn ) + EVENT(CX_LaserOff, hhCreatureX::Event_LaserOff ) + EVENT(MA_UpdateLasers, hhCreatureX::Event_UpdateLasers ) + EVENT(MA_AssignRightMuzzleFx, hhCreatureX::Event_AssignRightMuzzleFx ) + EVENT(MA_AssignLeftMuzzleFx, hhCreatureX::Event_AssignLeftMuzzleFx ) + EVENT(MA_AssignRightImpactFx, hhCreatureX::Event_AssignRightImpactFx ) + EVENT(MA_AssignLeftImpactFx, hhCreatureX::Event_AssignLeftImpactFx ) + EVENT(MA_SetGunOffset, hhCreatureX::Event_SetGunOffset ) + EVENT(MA_EndLeftBeams, hhCreatureX::Event_EndLeftBeams ) + EVENT(MA_EndRightBeams, hhCreatureX::Event_EndRightBeams ) + EVENT(MA_HudEvent, hhCreatureX::Event_HudEvent ) + EVENT(MA_GunRecharge, hhCreatureX::Event_GunRecharge ) + EVENT(MA_EndRecharge, hhCreatureX::Event_EndRecharge ) + EVENT(MA_ResetRechargeBeam, hhCreatureX::Event_ResetRechargeBeam ) + EVENT(MA_SparkLeft, hhCreatureX::Event_SparkLeft ) + EVENT(MA_SparkRight, hhCreatureX::Event_SparkRight ) + EVENT(MA_LeftGunDeath, hhCreatureX::Event_LeftGunDeath ) + EVENT(MA_RightGunDeath, hhCreatureX::Event_RightGunDeath ) + EVENT(MA_StartRechargeBeams, hhCreatureX::Event_StartRechargeBeams ) +END_CLASS + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +void hhCreatureX::Spawn() { + idVec3 boneOrigin; + idMat3 boneAxis; + targetStart_L = vec3_zero; + targetStart_R = vec3_zero; + targetEnd_L = vec3_zero; + targetEnd_R = vec3_zero; + targetCurrent_L = vec3_zero; + targetCurrent_R = vec3_zero; + bLaserLeftActive = false; + bLaserRightActive = false; + nextBeamTime = 0; + nextLeftZapTime = 0; + nextRightZapTime = 0; + nextLaserLeft = 0; + nextLaserRight = 0; + nextHealthTick = 0; + rightGunLives = spawnArgs.GetInt( "gun_lives", "3" ); + leftGunLives = spawnArgs.GetInt( "gun_lives", "3" ); + rightGunHealth = spawnArgs.GetInt( "gun_health", "20" ); + leftGunHealth = spawnArgs.GetInt( "gun_health", "20" ); + bScripted = spawnArgs.GetBool( "scripted", "0" ); + numBurstBeams = 0; + + if ( bScripted ) { + //don't spawn any combat related entities if scripted + return; + } + + laserRight = hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamLaser" ) ); + if( laserRight.IsValid() ) { + GetJointWorldTransform( spawnArgs.GetString("laser_bone_right"), boneOrigin, boneAxis ); + + idVec3 junkOrigin = boneOrigin + viewAxis[0] * 60; //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(junkOrigin.x)); //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(junkOrigin.y)); //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(junkOrigin.z)); //Test for bad origin + + laserRight->SetOrigin( boneOrigin + viewAxis[0] * 60 ); + laserRight->Activate( false ); + } + laserLeft = hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamLaser" ) ); + if( laserLeft.IsValid() ) { + GetJointWorldTransform( spawnArgs.GetString("laser_bone_left"), boneOrigin, boneAxis ); + + idVec3 junkOrigin = boneOrigin + viewAxis[0] * 60; //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(junkOrigin.x)); //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(junkOrigin.y)); //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(junkOrigin.z)); //Test for bad origin + + laserLeft->SetOrigin( boneOrigin + viewAxis[0] * 60 ); + laserLeft->Activate( false ); + } + preLaserRight = hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamLaser" ) ); + if( preLaserRight.IsValid() ) { + GetJointWorldTransform( spawnArgs.GetString("laser_bone_right"), boneOrigin, boneAxis ); + + idVec3 junkOrigin = boneOrigin; //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(junkOrigin.x)); //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(junkOrigin.y)); //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(junkOrigin.z)); //Test for bad origin + + preLaserRight->SetOrigin( boneOrigin ); + preLaserRight->BindToJoint( this, spawnArgs.GetString("laser_bone_right"), false ); + preLaserRight->Activate( false ); + } + preLaserLeft = hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamLaser" ) ); + if( preLaserLeft.IsValid() ) { + GetJointWorldTransform( spawnArgs.GetString("laser_bone_left"), boneOrigin, boneAxis ); + + idVec3 junkOrigin = boneOrigin; //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(junkOrigin.x)); //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(junkOrigin.y)); //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(junkOrigin.z)); //Test for bad origin + + preLaserLeft->SetOrigin( boneOrigin ); + preLaserLeft->BindToJoint( this, spawnArgs.GetString("laser_bone_left"), false ); + preLaserLeft->Activate( false ); + } + PostEventSec( &MA_UpdateLasers, spawnArgs.GetFloat( "laser_freq", "0.1" ) ); + + leftBeamList.Clear(); + rightBeamList.Clear(); + leftRechargeBeam.Clear(); + rightRechargeBeam.Clear(); + leftRecharger.Clear(); + rightRecharger.Clear(); + if ( spawnArgs.GetBool( "use_recharge" ) ) { + numBurstBeams = spawnArgs.GetInt( "num_burst_beams", "4" ); + for ( int i = 0; i < numBurstBeams; i ++ ) { + leftBeamList.Append( hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamBurst" ) ) ); + if( leftBeamList[i].IsValid() ) { + leftBeamList[i]->Activate( false ); + GetJointWorldTransform( spawnArgs.GetString("damage_bone_left"), boneOrigin, boneAxis ); + leftBeamList[i]->SetOrigin( boneOrigin ); + leftBeamList[i]->BindToJoint( this, spawnArgs.GetString("damage_bone_left"), false ); + } + rightBeamList.Append( hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamBurst" ) ) ); + if( rightBeamList[i].IsValid() ) { + rightBeamList[i]->Activate( false ); + GetJointWorldTransform( spawnArgs.GetString("damage_bone_right"), boneOrigin, boneAxis ); + rightBeamList[i]->SetOrigin( boneOrigin ); + rightBeamList[i]->BindToJoint( this, spawnArgs.GetString("damage_bone_right"), false ); + } + } + + leftRechargeBeam = hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamRecharge" ) ); + if( leftRechargeBeam.IsValid() ) { + leftRechargeBeam->Activate( false ); + GetJointWorldTransform( spawnArgs.GetString("laser_bone_left"), boneOrigin, boneAxis ); + leftRechargeBeam->SetOrigin( boneOrigin ); + leftRechargeBeam->BindToJoint( this, spawnArgs.GetString("laser_bone_left"), false ); + leftRechargeBeam->Hide(); + } + rightRechargeBeam = hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamRecharge" ) ); + if( rightRechargeBeam.IsValid() ) { + rightRechargeBeam->Activate( false ); + GetJointWorldTransform( spawnArgs.GetString("laser_bone_right"), boneOrigin, boneAxis ); + rightRechargeBeam->SetOrigin( boneOrigin ); + rightRechargeBeam->BindToJoint( this, spawnArgs.GetString("laser_bone_right"), false ); + rightRechargeBeam->Hide(); + } + leftDamageBeam = hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamDamage" ) ); + if( leftDamageBeam.IsValid() ) { + leftDamageBeam->Activate( false ); + GetJointWorldTransform( spawnArgs.GetString("laser_bone_left"), boneOrigin, boneAxis ); + leftDamageBeam->SetOrigin( boneOrigin ); + leftDamageBeam->BindToJoint( this, spawnArgs.GetString("laser_bone_left"), false ); + leftDamageBeam->Hide(); + } + rightDamageBeam = hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamDamage" ) ); + if( rightDamageBeam.IsValid() ) { + rightDamageBeam->Activate( false ); + GetJointWorldTransform( spawnArgs.GetString("laser_bone_right"), boneOrigin, boneAxis ); + rightDamageBeam->SetOrigin( boneOrigin ); + rightDamageBeam->BindToJoint( this, spawnArgs.GetString("laser_bone_right"), false ); + rightDamageBeam->Hide(); + } + leftRetractBeam = hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamRetract" ) ); + if( leftRetractBeam.IsValid() ) { + leftRetractBeam->Activate( false ); + GetJointWorldTransform( spawnArgs.GetString("laser_bone_left"), boneOrigin, boneAxis ); + leftRetractBeam->SetOrigin( boneOrigin ); + leftRetractBeam->BindToJoint( this, spawnArgs.GetString("laser_bone_left"), false ); + leftRetractBeam->Hide(); + } + rightRetractBeam = hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamRetract" ) ); + if( rightRetractBeam.IsValid() ) { + rightRetractBeam->Activate( false ); + GetJointWorldTransform( spawnArgs.GetString("laser_bone_right"), boneOrigin, boneAxis ); + rightRetractBeam->SetOrigin( boneOrigin ); + rightRetractBeam->BindToJoint( this, spawnArgs.GetString("laser_bone_right"), false ); + rightRetractBeam->Hide(); + } + + idEntity *ent; + idDict dict; + const idDict *rechargeDict = gameLocal.FindEntityDefDict(spawnArgs.GetString("def_recharger")); + if ( !rechargeDict ) { + gameLocal.Error( "Unknown def_recharger: %s\n", spawnArgs.GetString("def_recharger") ); + } + dict.Copy(*rechargeDict); + gameLocal.SpawnEntityDef( dict, &ent ); + if ( ent ) { + leftRecharger.Assign( ent ); + leftRecharger->GetPhysics()->DisableClip(); + leftRecharger->GetPhysics()->SetClipMask( 0 ); + leftRecharger->HideNoDormant(); + leftRecharger->SetOrigin( GetOrigin() ); + leftRecharger->SetEnemy( this ); + leftRecharger->spawnArgs.Set( "left_recharger", "1" ); //set key for left or right + } + ent = NULL; + gameLocal.SpawnEntityDef( dict, &ent ); + if ( ent ) { + rightRecharger.Assign( ent ); + rightRecharger->GetPhysics()->DisableClip(); + rightRecharger->GetPhysics()->SetClipMask( 0 ); + rightRecharger->HideNoDormant(); + rightRecharger->SetOrigin( GetOrigin() ); + rightRecharger->SetEnemy( this ); + rightRecharger->spawnArgs.Set( "right_recharger", "1" ); //set key for left or right + } + } +} + +hhCreatureX::~hhCreatureX() { + MuzzleLeftOff(); + MuzzleRightOff(); + + SAFE_REMOVE( laserRight ); + SAFE_REMOVE( laserLeft ); + SAFE_REMOVE( preLaserLeft ); + SAFE_REMOVE( preLaserRight ); +} + +void hhCreatureX::Event_LaserOn() { + if ( !enemy.IsValid() ) { + idThread::ReturnInt( false ); + return; + } + if ( !AI_RIGHT_DAMAGED ) { + bLaserRightActive = true; + } + if ( !AI_LEFT_DAMAGED ) { + bLaserLeftActive = true; + } + idVec3 toEnemy = GetEnemy()->GetOrigin() - GetOrigin(); + targetStart_L = GetOrigin() + spawnArgs.GetFloat( "test_1", "0.5" ) * toEnemy; + targetStart_L.z = GetOrigin().z; + float distToStart = (targetStart_L - GetOrigin()).LengthFast(); + if ( distToStart < 200 ) { + targetStart_L = GetOrigin() + toEnemy; + targetStart_L.z = GetOrigin().z; + } + targetStart_R = GetOrigin() + spawnArgs.GetFloat( "test_1", "0.5" ) * toEnemy; + targetStart_R.z = GetOrigin().z; + distToStart = (targetStart_R - GetOrigin()).LengthFast(); + if ( distToStart < 200 ) { + targetStart_R = GetOrigin() + toEnemy; + targetStart_R.z = GetOrigin().z; + } + targetEnd_L = GetOrigin() + spawnArgs.GetFloat( "test_2", "1.4" ) * toEnemy; + targetEnd_L.z = GetOrigin().z + spawnArgs.GetFloat( "test_4", "50" ); + targetEnd_R = GetOrigin() + spawnArgs.GetFloat( "test_2", "1.4" ) * toEnemy; + targetEnd_R.z = GetOrigin().z + spawnArgs.GetFloat( "test_4", "50" ); + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorRed, targetStart_L, targetEnd_L, 10, 10000 ); + } + targetAlpha_L = 0.0f; + targetAlpha_R = 0.0f; + if ( !bLaserLeftActive && !bLaserRightActive ) { + idThread::ReturnInt( false ); + } + idThread::ReturnInt( true ); +} + +void hhCreatureX::Event_LaserOff() { + bLaserLeftActive = false; + bLaserRightActive = false; + targetStart_L = vec3_zero; + targetStart_R = vec3_zero; + targetEnd_L = vec3_zero; + targetEnd_R = vec3_zero; + MuzzleLeftOff(); + MuzzleRightOff(); +} + +void hhCreatureX::Event_UpdateLasers() { + if ( !enemy.IsValid() ) { + PostEventSec( &MA_UpdateLasers, spawnArgs.GetFloat( "laser_freq", "0.1" ) ); + return; + } + trace_t trace; + float dist = 0; + idVec3 boneOrigin, traceEnd; + idMat3 boneAxis; + idEntity *hitEntity = NULL; + int hitCount = 0; + if ( laserRight.IsValid() && bLaserRightActive ) { + MuzzleRightOn(); + traceEnd = targetCurrent_R + 2000 * (targetCurrent_R - laserRight->GetOrigin()).ToNormal(); + GetJointWorldTransform( spawnArgs.GetString("laser_bone_right"), boneOrigin, boneAxis ); + gameLocal.clip.TracePoint( trace, laserRight->GetOrigin(), traceEnd, MASK_SHOT_RENDERMODEL, this ); + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorRed, laserRight->GetOrigin(), traceEnd, 10, 200 ); + } + if ( trace.fraction < 1.0f ) { + dist = (trace.endpos - laserRight->GetOrigin()).Length(); + hitEntity = gameLocal.GetTraceEntity( trace ); + if ( hitEntity ) { + hitEntity->Damage( this, this, hitEntity->GetOrigin() - GetOrigin().ToNormal(), spawnArgs.GetString( "def_laserDamage" ), 1.0, 0 ); + } + } + } else { + MuzzleRightOff(); + } + if ( laserLeft.IsValid() && bLaserLeftActive ) { + MuzzleLeftOn(); + traceEnd = targetCurrent_L + 2000 * (targetCurrent_L - laserLeft->GetOrigin()).ToNormal(); + GetJointWorldTransform( spawnArgs.GetString("laser_bone_left"), boneOrigin, boneAxis ); + gameLocal.clip.TracePoint( trace, laserLeft->GetOrigin(), traceEnd, MASK_SHOT_RENDERMODEL, this ); + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorRed, laserLeft->GetOrigin(), traceEnd, 10, 200 ); + } + if ( trace.fraction < 1.0f ) { + dist = (trace.endpos - laserLeft->GetOrigin()).Length(); + hitEntity = gameLocal.GetTraceEntity( trace ); + if ( hitEntity ) { + hitEntity->Damage( this, this, hitEntity->GetOrigin() - GetOrigin().ToNormal(), spawnArgs.GetString( "def_laserDamage" ), 1.0, 0 ); + } + } + } else { + MuzzleLeftOff(); + } + PostEventSec( &MA_UpdateLasers, spawnArgs.GetFloat( "laser_freq", "0.1" ) ); +} + +void hhCreatureX::Event_ResetRechargeBeam() { + if ( !AI_RECHARGING ) { + return; + } + if ( leftRecharger.IsValid() ) { + leftRecharger->SetShaderParm( 6, 0.0f ); + } + if ( rightRecharger.IsValid() ) { + rightRecharger->SetShaderParm( 6, 0.0f ); + } + if ( rechargeLeftFx.IsValid() ) { + rechargeLeftFx->SetParticleShaderParm( 6, 0.0f ); + } + if ( rechargeRightFx.IsValid() ) { + rechargeRightFx->SetParticleShaderParm( 6, 0.0f ); + } + if ( leftRechargeBeam.IsValid() ) { + leftRechargeBeam->Activate( true ); + leftRechargeBeam->Show(); + } + if ( rightRechargeBeam.IsValid() ) { + rightRechargeBeam->Activate( true ); + rightRechargeBeam->Show(); + } + if ( leftDamageBeam.IsValid() ) { + leftDamageBeam->Activate( false ); + } + if ( rightDamageBeam.IsValid() ) { + rightDamageBeam->Activate( false ); + } + if ( leftRetractBeam.IsValid() ) { + leftRetractBeam->Activate( false ); + } + if ( rightRetractBeam.IsValid() ) { + rightRetractBeam->Activate( false ); + } +} + +void hhCreatureX::Think() { + PROFILE_SCOPE("AI", PROFMASK_NORMAL|PROFMASK_AI); + if (ai_skipThink.GetBool()) { + return; + } + + idVec3 pastEnemy; + hhMonsterAI::Think(); + if ( AI_RECHARGING ) { + bool damaged = false; + if ( leftRecharger.IsValid() ) { + //recharger damage. switch out beams + if ( leftRechargeBeam.IsValid() && leftRecharger->GetHealth() < leftRechargerHealth ) { + if ( leftRechargerHealth > 0 && !leftRecharger->IsDead() ) { + damaged = true; + } + leftRechargeBeam->Activate( false ); + leftDamageBeam->Show(); + leftDamageBeam->Activate( true ); + leftRecharger->SetShaderParm( 6, 1.0f ); + if ( rechargeLeftFx.IsValid() ) { + rechargeLeftFx->SetParticleShaderParm( 6, 1.0f ); + } + } + leftRechargerHealth = leftRecharger->GetHealth(); + } + if ( rightRecharger.IsValid() ) { + //recharger damage. switch out beams + if ( rightRechargeBeam.IsValid() && rightRecharger->GetHealth() < rightRechargerHealth ) { + if ( rightRechargerHealth > 0 && !rightRecharger->IsDead() ) { + damaged = true; + } + rightRechargeBeam->Activate( false ); + rightDamageBeam->Show(); + rightDamageBeam->Activate( true ); + rightRecharger->SetShaderParm( 6, 1.0f ); + if ( rechargeRightFx.IsValid() ) { + rechargeRightFx->SetParticleShaderParm( 6, 1.0f ); + } + } + rightRechargerHealth = rightRecharger->GetHealth(); + } + if ( damaged ) { + PostEventSec( &MA_ResetRechargeBeam, spawnArgs.GetFloat( "damage_beam_duration", "0.8" ) ); + } + if ( !AI_LEFT_DAMAGED && leftRecharger.IsValid() && leftRecharger->GetHealth() <= 0 ) { + //recharger has died so start retracting the beams and schedule the gun's death + StartSound( "snd_gun_predeath", SND_CHANNEL_ANY ); + SAFE_REMOVE( leftRechargeBeam ); + PostEventSec( &MA_LeftGunDeath, spawnArgs.GetFloat("retract_delay", "0.9") ); + const char *defName = spawnArgs.GetString( "fx_retract" ); + if ( defName && defName[0] && leftDamageBeam.IsValid() ) { + hhFxInfo fxInfo; + fxInfo.RemoveWhenDone( true ); + retractLeftFx = SpawnFxLocal( defName, leftDamageBeam->GetTargetLocation(), mat3_identity, &fxInfo, gameLocal.isClient ); + } + if ( leftRetractBeam.IsValid() ) { + leftRetractBeam->Show(); + leftRetractBeam->Activate( true ); + } + if ( leftDamageBeam.IsValid() ) { + leftDamageBeam->Activate( false ); + leftDamageBeam->Hide(); + } + AI_LEFT_DAMAGED = true; + } + if ( !AI_RIGHT_DAMAGED && rightRecharger.IsValid() && rightRecharger->GetHealth() <= 0 ) { + //recharger has died so start retracting the beams and schedule the gun's death + StartSound( "snd_gun_predeath", SND_CHANNEL_ANY ); + SAFE_REMOVE( rightRechargeBeam ); + PostEventSec( &MA_RightGunDeath, spawnArgs.GetFloat("retract_delay", "1") ); + const char *defName = spawnArgs.GetString( "fx_retract" ); + if ( defName && defName[0] && leftDamageBeam.IsValid() ) { + hhFxInfo fxInfo; + fxInfo.RemoveWhenDone( true ); + retractRightFx = SpawnFxLocal( defName, rightDamageBeam->GetTargetLocation(), mat3_identity, &fxInfo, gameLocal.isClient ); + } + if ( rightRetractBeam.IsValid() ) { + rightRetractBeam->Show(); + rightRetractBeam->Activate( true ); + } + if ( rightDamageBeam.IsValid() ) { + rightDamageBeam->Activate( false ); + rightDamageBeam->Hide(); + } + AI_RIGHT_DAMAGED = true; + } + if ( AI_HEALTH_TICK && gameLocal.time >= nextHealthTick ) { + health += spawnArgs.GetInt( "recharge_delta", "1" ); + nextHealthTick += spawnArgs.GetInt( "recharge_period" ); + if ( health > spawnArgs.GetInt( "health" ) ) { + AI_HEALTH_TICK = false; + health = spawnArgs.GetInt( "health" ); + } + } + } else { + if ( leftRechargeBeam.IsValid() ) { + leftRechargeBeam->Activate( false ); + } + if ( rightRechargeBeam.IsValid() ) { + rightRechargeBeam->Activate( false ); + } + } + + if ( AI_LEFT_DAMAGED && gameLocal.time > nextLeftZapTime ) { + nextLeftZapTime = gameLocal.time + SEC2MS(spawnArgs.GetFloat( "zap_period", "0.3" )); + for ( int i = 0; i < numBurstBeams; i ++ ) { + if ( leftBeamList[i].IsValid() && !leftBeamList[i]->IsActivated() ) { + leftBeamList[i]->Activate( true ); + leftBeamList[i]->SetTargetLocation( leftBeamList[i]->GetOrigin() + spawnArgs.GetFloat( "zap_length", "50" ) * hhUtils::RandomSpreadDir( hhUtils::RandomVector().ToMat3(), 16.0f ) ); + PostEventSec(&MA_EndLeftBeams, spawnArgs.GetFloat( "beam_duration_1", "0.2" ) ); + break; + } + } + } + if ( AI_RIGHT_DAMAGED && gameLocal.time > nextRightZapTime ) { + nextRightZapTime = gameLocal.time + SEC2MS(spawnArgs.GetFloat( "zap_period", "0.3" )); + for ( int i = 0; i < numBurstBeams; i ++ ) { + if ( rightBeamList[i].IsValid() && !rightBeamList[i]->IsActivated() ) { + rightBeamList[i]->Activate( true ); + rightBeamList[i]->SetTargetLocation( rightBeamList[i]->GetOrigin() + spawnArgs.GetFloat( "zap_length", "50" ) * hhUtils::RandomSpreadDir( hhUtils::RandomVector().ToMat3(), 16.0f ) ); + PostEventSec(&MA_EndRightBeams, spawnArgs.GetFloat( "beam_duration_1", "0.2" ) ); + break; + } + } + } + + if ( enemy.IsValid() ) { + //left laser + //HUMANHEAD jsh PCF 5/12/06 30hz issue with beam movement + targetAlpha_L += spawnArgs.GetFloat( "laser_move_delta", "0.1" ) * (60.0f * USERCMD_ONE_OVER_HZ); + if ( targetAlpha_L > 1.0f ) { + targetAlpha_L = 1.0f; + } + targetCurrent_L = targetStart_L + targetAlpha_L * ( targetEnd_L - targetStart_L ); + pastEnemy = GetEnemy()->GetOrigin() + idVec3( 0,0,30 ); + //HUMANHEAD jsh PCF 5/12/06 30hz issue with beam movement + targetEnd_L += spawnArgs.GetFloat( "laser_track_delta", "0.1" ) * ( pastEnemy - targetEnd_L ) * (60.0f * USERCMD_ONE_OVER_HZ); + + //right laser + //HUMANHEAD jsh PCF 5/12/06 30hz issue with beam movement + targetAlpha_R += spawnArgs.GetFloat( "laser_move_delta", "0.1" ) * (60.0f * USERCMD_ONE_OVER_HZ); + if ( targetAlpha_R > 1.0f ) { + targetAlpha_R = 1.0f; + } + targetCurrent_R = targetStart_R + targetAlpha_R * ( targetEnd_R - targetStart_R ); + pastEnemy = GetEnemy()->GetOrigin() + idVec3( 0,0,30 ); + //HUMANHEAD jsh PCF 5/12/06 30hz issue with beam movement + targetEnd_R += spawnArgs.GetFloat( "laser_track_delta", "0.1" ) * ( pastEnemy - targetEnd_R ) * (60.0f * USERCMD_ONE_OVER_HZ); + } + + if ( gameLocal.time > nextBeamTime ) { + nextBeamTime = gameLocal.time + SEC2MS(spawnArgs.GetFloat( "beam_period", "0.3" )); + for ( int i = 0; i < leftBeamList.Num(); i ++ ) { + if ( leftBeamList[i].IsValid() && leftBeamList[i]->IsActivated() ) { + leftBeamList[i]->SetTargetLocation( leftBeamList[i]->GetOrigin() + spawnArgs.GetFloat( "beam_length", "100" ) * hhUtils::RandomSpreadDir( hhUtils::RandomVector().ToMat3(), 16.0f ) ); + } + } + for ( int i = 0; i < rightBeamList.Num(); i ++ ) { + if ( rightBeamList[i].IsValid() && rightBeamList[i]->IsActivated() ) { + rightBeamList[i]->SetTargetLocation( rightBeamList[i]->GetOrigin() + spawnArgs.GetFloat( "beam_length", "100" ) * hhUtils::RandomSpreadDir( hhUtils::RandomVector().ToMat3(), 16.0f ) ); + } + } + } + + idVec3 boneOrigin, traceEnd; + idMat3 boneAxis; + trace_t trace; + memset(&trace, 0, sizeof(trace)); + if ( !enemy.IsValid() ) { + return; + } + + if ( laserRight.IsValid() && bLaserRightActive ) { + traceEnd = targetCurrent_R + 2000 * (targetCurrent_R - laserRight->GetOrigin()).ToNormal(); + GetJointWorldTransform( spawnArgs.GetString("laser_bone_right"), boneOrigin, boneAxis ); + gameLocal.clip.TracePoint( trace, laserRight->GetOrigin(), traceEnd, MASK_SHOT_RENDERMODEL, this ); + if ( trace.fraction < 1.0f ) { + if ( preLaserRight.IsValid() ) { + if ( preLaserRight->IsHidden() ) { + preLaserRight->Show(); + } + preLaserRight->Activate( true ); + preLaserRight->SetTargetEntity( laserRight.GetEntity() ); + } + if ( laserRight->IsHidden() ) { + laserRight->Show(); + } + laserRight->SetOrigin( boneOrigin + viewAxis[0] * 60 ); + laserRight->Activate( true ); + laserRight->SetTargetLocation( trace.endpos - viewAxis[1] * 10 ); + } + float dist = (laserRight->GetTargetLocation() - laserRight->GetOrigin()).Length(); + if ( dist > 100.0f && impactRightFx.IsValid() ) { + impactRightFx->SetOrigin( laserRight->GetTargetLocation() ); + } + laserEndRight = trace.endpos; + } else { + if ( preLaserRight.IsValid() ) { + preLaserRight->Activate( false ); + } + if ( laserRight.IsValid() ) { + laserRight->Activate( false ); + } + } + + if ( laserLeft.IsValid() && bLaserLeftActive ) { + traceEnd = targetCurrent_L + 2000 * (targetCurrent_L - laserLeft->GetOrigin()).ToNormal(); + GetJointWorldTransform( spawnArgs.GetString("laser_bone_left"), boneOrigin, boneAxis ); + gameLocal.clip.TracePoint( trace, laserLeft->GetOrigin(), traceEnd, MASK_SHOT_RENDERMODEL, this ); + if ( trace.fraction < 1.0f ) { + if ( preLaserLeft.IsValid() ) { + if ( preLaserLeft->IsHidden() ) { + preLaserLeft->Show(); + } + preLaserLeft->Activate( true ); + preLaserLeft->SetTargetEntity( laserLeft.GetEntity() ); + } + if ( laserLeft->IsHidden() ) { + laserLeft->Show(); + } + laserLeft->SetOrigin( boneOrigin + viewAxis[0] * 60 ); + laserLeft->Activate( true ); + laserLeft->SetTargetLocation( trace.endpos + viewAxis[1] * 10 ); + } + float dist = (laserLeft->GetTargetLocation() - laserLeft->GetOrigin()).Length(); + if ( dist > 100.0f && impactLeftFx.IsValid() ) { + impactLeftFx->SetOrigin( laserLeft->GetTargetLocation() ); + } + laserEndLeft = trace.endpos; + } else { + if ( preLaserLeft.IsValid() ) { + preLaserLeft->Activate( false ); + } + if ( laserLeft.IsValid() ) { + laserLeft->Activate( false ); + } + } +} + +#define LinkScriptVariable( name ) name.LinkTo( scriptObject, #name ) +void hhCreatureX::LinkScriptVariables() { + hhMonsterAI::LinkScriptVariables(); + LinkScriptVariable( AI_GUN_TRACKING ); + LinkScriptVariable( AI_RECHARGING ); + LinkScriptVariable( AI_LEFT_FIRE ); + LinkScriptVariable( AI_RIGHT_FIRE ); + LinkScriptVariable( AI_LEFT_DAMAGED ); + LinkScriptVariable( AI_RIGHT_DAMAGED ); + LinkScriptVariable( AI_FIRING_LASER ); + LinkScriptVariable( AI_HEALTH_TICK ); + LinkScriptVariable( AI_GUN_EXPLODE ); +} + +void hhCreatureX::Event_RightGunDeath() { + bool explodeGun = false; + if ( rightRetractBeam.IsValid() ) { + rightRetractBeam->Show(); + rightRetractBeam->Activate( true ); + + //retract the damage beam a little bit + idVec3 bonePos; + idMat3 boneAxis; + GetJointWorldTransform( spawnArgs.GetString( "gun_bone_right" ), bonePos, boneAxis ); + idVec3 lastTargetLocation = rightRetractBeam->GetTargetLocation(); + rightRetractBeam->SetTargetEntity( NULL ); + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorRed, lastTargetLocation, bonePos, 10, 1 ); + } + rightRetractBeam->SetTargetLocation( lastTargetLocation + spawnArgs.GetFloat("retract_speed", "50") * (bonePos - lastTargetLocation).ToNormal() ); + if ( retractRightFx.IsValid() ) { + retractRightFx->SetOrigin( rightRetractBeam->GetTargetLocation() ); + } + if ( (lastTargetLocation - bonePos).LengthFast() < 30.0f ) { + explodeGun = true; + rightRetractBeam->Activate( false ); + } else { + PostEventSec( &MA_RightGunDeath, 0.01f ); + } + } + + if ( explodeGun || !rightRetractBeam.IsValid() ) { + CancelEvents( &MA_ResetRechargeBeam ); + SAFE_REMOVE( rightRechargeBeam ); + SAFE_REMOVE( retractRightFx ); + for ( int i=0;iActivate( true ); + rightBeamList[i]->SetTargetLocation( rightBeamList[i]->GetOrigin() + spawnArgs.GetFloat( "beam_length", "100" ) * hhUtils::RandomSpreadDir( GetEnemy()->GetOrigin().ToMat3(), 16.0f ) ); + } + } + StartSound( "snd_jenny_scream", SND_CHANNEL_ANY ); + PostEventSec(&MA_EndRightBeams, spawnArgs.GetFloat( "beam_duration_3", "1.0" ) ); + BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_smoke"), spawnArgs.GetString("damage_bone_right"), NULL ); + BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_gun_death"), spawnArgs.GetString("damage_bone_right"), NULL ); + AI_RIGHT_DAMAGED = true; + PostEventSec(&MA_SparkLeft, spawnArgs.GetFloat( "spark_freq", "1" ) + gameLocal.random.RandomFloat() ); + if ( AI_LEFT_DAMAGED ) { + SetSkinByName( spawnArgs.GetString( "skin_nogun_both" ) ); + } else { + SetSkinByName( spawnArgs.GetString( "skin_nogun_right" ) ); + } + } +} + +void hhCreatureX::Event_LeftGunDeath() { + bool explodeGun = false; + if ( leftRetractBeam.IsValid() ) { + leftRetractBeam->Show(); + leftRetractBeam->Activate( true ); + + //retract the damage beam a little bit + idVec3 bonePos; + idMat3 boneAxis; + GetJointWorldTransform( spawnArgs.GetString( "gun_bone_left" ), bonePos, boneAxis ); + idVec3 lastTargetLocation = leftRetractBeam->GetTargetLocation(); + leftRetractBeam->SetTargetEntity( NULL ); + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorRed, lastTargetLocation, bonePos, 10, 1 ); + } + leftRetractBeam->SetTargetLocation( lastTargetLocation + spawnArgs.GetFloat("retract_speed", "50") * (bonePos - lastTargetLocation).ToNormal() ); + if ( retractLeftFx.IsValid() ) { + retractLeftFx->SetOrigin( leftRetractBeam->GetTargetLocation() ); + } + if ( (lastTargetLocation - bonePos).LengthFast() < 30.0f ) { + explodeGun = true; + leftRetractBeam->Activate( false ); + } else { + PostEventSec( &MA_LeftGunDeath, 0.01f ); + } + } + + if ( explodeGun || !leftRetractBeam.IsValid() ) { + CancelEvents( &MA_ResetRechargeBeam ); + SAFE_REMOVE( leftRechargeBeam ); + SAFE_REMOVE( retractLeftFx ); + for ( int i=0;iActivate( true ); + leftBeamList[i]->SetTargetLocation( leftBeamList[i]->GetOrigin() + spawnArgs.GetFloat( "beam_length", "100" ) * hhUtils::RandomSpreadDir( GetEnemy()->GetOrigin().ToMat3(), 16.0f ) ); + } + } + StartSound( "snd_jenny_scream", SND_CHANNEL_ANY ); + PostEventSec(&MA_EndLeftBeams, spawnArgs.GetFloat( "beam_duration_3", "1.0" ) ); + BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_smoke"), spawnArgs.GetString("damage_bone_left"), NULL ); + BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_gun_death"), spawnArgs.GetString("damage_bone_left"), NULL ); + AI_LEFT_DAMAGED = true; + PostEventSec(&MA_SparkLeft, spawnArgs.GetFloat( "spark_freq", "1" ) + gameLocal.random.RandomFloat() ); + if ( AI_RIGHT_DAMAGED ) { + SetSkinByName( spawnArgs.GetString( "skin_nogun_both" ) ); + } else { + SetSkinByName( spawnArgs.GetString( "skin_nogun_left" ) ); + } + } +} + + +bool hhCreatureX::UpdateAnimationControllers( void ) { + idVec3 local; + idVec3 focusPos; + idVec3 left; + idVec3 dir; + idVec3 orientationJointPos; + idVec3 localDir; + idAngles newLookAng; + idAngles diff; + idMat3 mat; + idMat3 axis; + idMat3 orientationJointAxis; + idAFAttachment *headEnt = head.GetEntity(); + idVec3 eyepos; + idVec3 pos; + int i; + idAngles jointAng; + float orientationJointYaw; + + if ( AI_DEAD ) { + + for (int i = 0; i < jawFlapList.Num(); i++) { + jawFlapInfo_t &flapInfo = jawFlapList[i]; + animator.ClearJoint( flapInfo.bone ); + } + animator.ClearJoint( leftEyeJoint ); + animator.ClearJoint( rightEyeJoint ); + + return idActor::UpdateAnimationControllers(); + } + + if ( orientationJoint == INVALID_JOINT ) { + orientationJointAxis = viewAxis; + orientationJointPos = physicsObj.GetOrigin(); + orientationJointYaw = current_yaw; + } else { + GetJointWorldTransform( orientationJoint, gameLocal.time, orientationJointPos, orientationJointAxis ); + orientationJointYaw = orientationJointAxis[ 2 ].ToYaw(); + orientationJointAxis = idAngles( 0.0f, orientationJointYaw, 0.0f ).ToMat3(); + } + + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorCyan, orientationJointPos, orientationJointPos + orientationJointAxis[0] * 64.0, 10, 1 ); + } + + if ( focusJoint != INVALID_JOINT ) { + if ( headEnt ) { + headEnt->GetJointWorldTransform( focusJoint, gameLocal.time, eyepos, axis ); + } else { + // JRMMERGE_GRAVAXIS - What about GetGravAxis() are we still using/needing that? + GetJointWorldTransform( focusJoint, gameLocal.time, eyepos, axis ); + } + eyeOffset.z = eyepos.z - physicsObj.GetOrigin().z; + } else { + eyepos = GetEyePosition(); + } + + if ( headEnt ) { + CopyJointsFromBodyToHead(); + } + + // 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(); + + idEntity *focusEnt = focusEntity.GetEntity(); + //HUMANHEAD jsh allow eyefocus independent from allowJointMod + if ( ( !allowJointMod && !allowEyeFocus ) || ( gameLocal.time >= focusTime && focusTime != -1 ) || GetPhysics()->GetGravityNormal() != idVec3( 0,0,-1) ) { + focusPos = GetEyePosition() + orientationJointAxis[ 0 ] * 512.0f; + } else if ( focusEnt == NULL ) { + // keep looking at last position until focusTime is up + focusPos = currentFocusPos; + } else if ( focusEnt == enemy.GetEntity() ) { + focusPos = lastVisibleEnemyPos + lastVisibleEnemyEyeOffset - eyeVerticalOffset * enemy.GetEntity()->GetPhysics()->GetGravityNormal(); + } else if ( focusEnt->IsType( idActor::Type ) ) { + focusPos = static_cast( focusEnt )->GetEyePosition() - eyeVerticalOffset * focusEnt->GetPhysics()->GetGravityNormal(); + } else { + focusPos = focusEnt->GetPhysics()->GetOrigin(); + } + + currentFocusPos = currentFocusPos + ( focusPos - currentFocusPos ) * eyeFocusRate; + + // determine yaw from origin instead of from focus joint since joint may be offset, which can cause us to bounce between two angles + dir = focusPos - orientationJointPos; + newLookAng.yaw = idMath::AngleNormalize180( dir.ToYaw() - orientationJointYaw ); + newLookAng.roll = 0.0f; + newLookAng.pitch = 0.0f; + + newLookAng += lookOffset; + +#if 0 + gameRenderWorld->DebugLine( colorRed, orientationJointPos, focusPos, gameLocal.msec ); + gameRenderWorld->DebugLine( colorYellow, orientationJointPos, orientationJointPos + orientationJointAxis[ 0 ] * 32.0f, gameLocal.msec ); + gameRenderWorld->DebugLine( colorGreen, orientationJointPos, orientationJointPos + newLookAng.ToForward() * 48.0f, gameLocal.msec ); +#endif + +//JRMMERGE_GRAVAXIS: This changed to much to merge, see if you can get your monsters on planets changes back in here. I'll leave both versions +#if OLD_CODE + GetGravViewAxis().ProjectVector( dir, localDir ); // HUMANHEAD JRM: VIEWAXIS_TO_GETGRAVVIEWAXIS + lookAng.yaw = idMath::AngleNormalize180( localDir.ToYaw() ); + lookAng.pitch = -idMath::AngleNormalize180( localDir.ToPitch() ); + lookAng.roll = 0.0f; +#else + // determine pitch from joint position + dir = focusPos - eyepos; + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorYellow, eyepos, eyepos + dir, 10, 1 ); + } + dir.NormalizeFast(); + orientationJointAxis.ProjectVector( dir, localDir ); + newLookAng.pitch = -idMath::AngleNormalize180( localDir.ToPitch() ) + lookOffset.pitch; + newLookAng.roll = 0.0f; +#endif + + diff = newLookAng - lookAng; + + if ( eyeAng != diff ) { + eyeAng = diff; + eyeAng.Clamp( eyeMin, eyeMax ); + idAngles angDelta = diff - eyeAng; + if ( !angDelta.Compare( ang_zero, 0.1f ) ) { + alignHeadTime = gameLocal.time; + } else { + alignHeadTime = gameLocal.time + ( 0.5f + 0.5f * gameLocal.random.RandomFloat() ) * focusAlignTime; + } + } + + if ( idMath::Fabs( newLookAng.yaw ) < 0.1f ) { + alignHeadTime = gameLocal.time; + } + + if ( ( gameLocal.time >= alignHeadTime ) || ( gameLocal.time < forceAlignHeadTime ) ) { + alignHeadTime = gameLocal.time + ( 0.5f + 0.5f * gameLocal.random.RandomFloat() ) * focusAlignTime; + destLookAng = newLookAng; + destLookAng.Clamp( lookMin, lookMax ); + } + + diff = destLookAng - lookAng; + if ( ( lookMin.pitch == -180.0f ) && ( lookMax.pitch == 180.0f ) ) { + if ( ( diff.pitch > 180.0f ) || ( diff.pitch <= -180.0f ) ) { + diff.pitch = 360.0f - diff.pitch; + } + } + if ( ( lookMin.yaw == -180.0f ) && ( lookMax.yaw == 180.0f ) ) { + if ( diff.yaw > 180.0f ) { + diff.yaw -= 360.0f; + } else if ( diff.yaw <= -180.0f ) { + diff.yaw += 360.0f; + } + } + lookAng = lookAng + diff * headFocusRate; + lookAng.Normalize180(); + + jointAng.roll = 0.0f; + if ( allowJointMod ) { + 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() ); + } + } + + if ( move.moveType == MOVETYPE_FLY ) { + // lean into turns + AdjustFlyingAngles(); + } + + if ( headEnt ) { + idAnimator *headAnimator = headEnt->GetAnimator(); + + // HUMANHEAD pdm: Added support for look joints in head entities + if ( allowJointMod ) { + for( i = 0; i < headLookJoints.Num(); i++ ) { + jointAng.pitch = lookAng.pitch * headLookJointAngles[ i ].pitch; + jointAng.yaw = lookAng.yaw * headLookJointAngles[ i ].yaw; + headAnimator->SetJointAxis( headLookJoints[ i ], JOINTMOD_WORLD, jointAng.ToMat3() ); + } + } + // HUMANHEAD END + + if ( allowEyeFocus ) { + idMat3 eyeAxis = ( lookAng + eyeAng ).ToMat3(); idMat3 headTranspose = headEnt->GetPhysics()->GetAxis().Transpose(); + axis = eyeAxis * orientationJointAxis; + left = axis[ 1 ] * eyeHorizontalOffset; + eyepos -= headEnt->GetPhysics()->GetOrigin(); + headAnimator->SetJointPos( leftEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + ( axis[ 0 ] * 64.0f + left ) * headTranspose ); + headAnimator->SetJointPos( rightEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + ( axis[ 0 ] * 64.0f - left ) * headTranspose ); + + //if ( ai_debugMove.GetBool() ) { + // gameRenderWorld->DebugLine( colorRed, orientationJointPos, eyepos + ( axis[ 0 ] * 64.0f + left ) * headTranspose, gameLocal.msec ); + //} + } else { + headAnimator->ClearJoint( leftEyeJoint ); + headAnimator->ClearJoint( rightEyeJoint ); + } + } else { + if ( allowEyeFocus ) { + idMat3 eyeAxis = ( lookAng + eyeAng ).ToMat3(); + axis = eyeAxis * orientationJointAxis; + left = axis[ 1 ] * eyeHorizontalOffset; + eyepos += axis[ 0 ] * 64.0f - physicsObj.GetOrigin(); + animator.SetJointPos( leftEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + left ); + animator.SetJointPos( rightEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos - left ); + } else { + animator.ClearJoint( leftEyeJoint ); + animator.ClearJoint( rightEyeJoint ); + } + } + + //HUMANHEAD pdm jawflap + hhAnimator *theAnimator; + if (head.IsValid()) { + theAnimator = head->GetAnimator(); + } + else { + theAnimator = GetAnimator(); + } + JawFlap(theAnimator); + //END HUMANHEAD + + //update guns + //HUMANHEAD jsh PCF 4/27/06 no gun tracking + //if ( enemy.IsValid() ) { + // if ( AI_GUN_TRACKING && !AI_DEAD && !AI_RECHARGING ) { + // idVec3 focusPos, bonePos; + // idMat3 boneAxis; + + // idAngles ang = ang_zero; + // ang.pitch = (bonePos - enemy->GetOrigin()).ToPitch(); + // ang.pitch += gunShake.pitch; + // ang.yaw = (targetCurrent_L-bonePos).ToYaw() - viewAxis.ToAngles().yaw; + // ang.yaw += gunShake.yaw; + // GetJointWorldTransform( spawnArgs.GetString( "gun_bone_left" ), bonePos, boneAxis ); + // animator.SetJointAxis( animator.GetJointHandle( spawnArgs.GetString( "gun_bone_left" ) ), JOINTMOD_WORLD, idAngles( (bonePos - targetCurrent_L).ToPitch(), (targetCurrent_L-bonePos).ToYaw() - viewAxis.ToAngles().yaw, 0.0f ).ToMat3() ); + + // ang.yaw = (targetCurrent_R-bonePos).ToYaw() - viewAxis.ToAngles().yaw; + // ang.yaw += gunShake.yaw; + // GetJointWorldTransform( spawnArgs.GetString( "gun_bone_right" ), bonePos, boneAxis ); + // animator.SetJointAxis( animator.GetJointHandle( spawnArgs.GetString( "gun_bone_right" ) ), JOINTMOD_WORLD, idAngles( (bonePos - targetCurrent_R).ToPitch(), (targetCurrent_R-bonePos).ToYaw() - viewAxis.ToAngles().yaw, 0.0f ).ToMat3() ); + // } else { + // animator.SetJointAxis( animator.GetJointHandle( spawnArgs.GetString( "gun_bone_left" ) ), JOINTMOD_WORLD, mat3_identity ); + // animator.SetJointAxis( animator.GetJointHandle( spawnArgs.GetString( "gun_bone_right" ) ), JOINTMOD_WORLD, mat3_identity ); + // } + //} + + return true; +} + +void hhCreatureX::Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + HandleNoGore(); + + //stop sparking upon death + CancelEvents( &MA_SparkLeft ); + CancelEvents( &MA_SparkRight ); + SAFE_REMOVE( preLaserLeft ); + SAFE_REMOVE( preLaserRight ); + AI_LEFT_DAMAGED = false; + AI_RIGHT_DAMAGED = false; + + if ( laserLeft.IsValid() ) { + laserLeft->Hide(); + } + if ( laserRight.IsValid() ) { + laserRight->Hide(); + } + + if ( spawnArgs.GetBool( "use_death_point", "0" ) ) { + if ( !AI_DEAD ) { + fl.takedamage = false; + AI_DEAD = true; + state = GetScriptFunction( "state_Predeath" ); + SetState( state ); + SetWaitState( "" ); + return; + } + } + + if ( AI_DEAD ) { + AI_DAMAGE = true; + return; + } + + fl.takedamage = false; + idAngles ang; + const char *modelDeath; + + // make sure the monster is activated + EndAttack(); + + if ( g_debugDamage.GetBool() ) { + gameLocal.Printf( "Damage: joint: '%s', zone '%s'\n", animator.GetJointName( ( jointHandle_t )location ), + GetDamageGroup( location ) ); + } + + if ( inflictor ) { + AI_SPECIAL_DAMAGE = inflictor->spawnArgs.GetInt( "special_damage" ); + } else { + AI_SPECIAL_DAMAGE = 0; + } + + if ( AI_DEAD ) { + AI_PAIN = true; + AI_DAMAGE = true; + return; + } + + // stop all voice sounds + StopSound( SND_CHANNEL_VOICE, false ); + if ( head.GetEntity() ) { + head.GetEntity()->StopSound( SND_CHANNEL_VOICE, false ); + head.GetEntity()->GetAnimator()->ClearAllAnims( gameLocal.time, 100 ); + } + + disableGravity = false; + move.moveType = MOVETYPE_DEAD; + af_push_moveables = false; + + physicsObj.UseFlyMove( false ); + physicsObj.ForceDeltaMove( false ); + + // end our looping ambient sound + StopSound( SND_CHANNEL_AMBIENT, false ); + + if ( attacker && attacker->IsType( idActor::Type ) ) { + gameLocal.AlertAI( ( idActor * )attacker ); + } + + // activate targets + ActivateTargets( attacker ); + + RemoveAttachments(); + RemoveProjectile(); + StopMove( MOVE_STATUS_DONE ); + + ClearEnemy(); + AI_DEAD = true; + + // HUMANHEAD jsh commented out for girlfriendx + // make monster nonsolid + if ( spawnArgs.GetBool( "boss" ) ) { + physicsObj.SetContents( 0 ); + physicsObj.GetClipModel()->Unlink(); + } + + Unbind(); + + // spawn death clip model + if ( spawnArgs.GetBool( "boss" ) ) { + idDict dict; + const idDict *torsoDict = gameLocal.FindEntityDefDict(spawnArgs.GetString("def_deathclip")); + dict.Copy(*torsoDict); + dict.SetVector("origin", GetOrigin() + modelOffset * GetAxis() ); + dict.SetMatrix("rotation", GetAxis()); + idEntity *e; + gameLocal.SpawnEntityDef(dict, &e); + if ( e ) { + e->GetPhysics()->SetContents( CONTENTS_PLAYERCLIP ); + } + } + + if ( StartRagdoll() ) { + StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL ); + // HUMANHEAD JRM - some monsters are removed, but always need to play sound + } else if(spawnArgs.GetBool("death_sound_always","0")) { + StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL ); + } + // HUMANHEAD JRM - end + + 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 + 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(); + } + + restartParticles = false; + + state = GetScriptFunction( "state_Killed" ); + SetState( state ); + SetWaitState( "" ); + + if ( attacker && attacker->IsType( idPlayer::Type ) ) { + static_cast< idPlayer* >( attacker )->AddAIKill(); + } + + // General non-item dropping (for monsters, souls, etc.) + const idKeyValue *kv = NULL; + kv = spawnArgs.MatchPrefix( "def_drops", NULL ); + while ( kv ) { + + idStr drops = kv->GetValue(); + idDict args; + + idStr last5 = kv->GetKey().Right(5); + if ( drops.Length() && idStr::Icmp( last5, "Joint" ) != 0) { + args.Set( "classname", drops ); + + // HUMANHEAD pdm: specify monster so souls can call back to remove body when picked up + args.Set("monsterSpawnedBy", name.c_str()); + + idVec3 origin; + idMat3 axis; + idStr jointKey = kv->GetKey() + idStr("Joint"); + idStr jointName = spawnArgs.GetString( jointKey ); + idStr joint2JointKey = kv->GetKey() + idStr("Joint2Joint"); + idStr j2jName = spawnArgs.GetString( joint2JointKey ); + + idEntity *newEnt = NULL; + gameLocal.SpawnEntityDef( args, &newEnt ); + HH_ASSERT(newEnt != NULL); + + if(jointName.Length()) { + jointHandle_t joint = GetAnimator()->GetJointHandle( jointName ); + if (!GetAnimator()->GetJointTransform( joint, gameLocal.time, origin, axis ) ) { + gameLocal.Printf( "%s refers to invalid joint '%s' on entity '%s'\n", (const char*)jointKey.c_str(), (const char*)jointName, (const char*)name ); + origin = renderEntity.origin; + axis = renderEntity.axis; + } + axis *= renderEntity.axis; + origin = renderEntity.origin + origin * renderEntity.axis; + newEnt->SetAxis(axis); + newEnt->SetOrigin(origin); + } + else { + + newEnt->SetAxis(viewAxis); + newEnt->SetOrigin(GetOrigin()); + } + + } + + kv = spawnArgs.MatchPrefix( "def_drops", kv ); + } +} + +/* +===================== +hhCreatureX::Save +===================== +*/ +void hhCreatureX::Save( idSaveGame *savefile ) const { + laserRight.Save( savefile ); + laserLeft.Save( savefile ); + savefile->WriteBool( bLaserLeftActive ); + savefile->WriteBool( bLaserRightActive ); + + int i, num = leftBeamList.Num(); + savefile->WriteInt( num ); + for ( i = 0; i < num; i++ ) { + leftBeamList[i].Save( savefile ); + } + + num = rightBeamList.Num(); + savefile->WriteInt( num ); + for ( i = 0; i < num; i++ ) { + rightBeamList[i].Save( savefile ); + } + + savefile->WriteInt( numBurstBeams ); + savefile->WriteInt( leftGunHealth ); + savefile->WriteInt( rightGunHealth ); + savefile->WriteAngles( gunShake ); + savefile->WriteInt( nextBeamTime ); + savefile->WriteInt( nextLeftZapTime ); + savefile->WriteInt( nextRightZapTime ); + savefile->WriteInt( leftGunLives ); + savefile->WriteInt( rightGunLives ); + + savefile->WriteVec3( targetStart_L ); + savefile->WriteVec3( targetEnd_L ); + savefile->WriteVec3( targetCurrent_L ); + savefile->WriteVec3( targetStart_R ); + savefile->WriteVec3( targetEnd_R ); + savefile->WriteVec3( targetCurrent_R ); + + savefile->WriteFloat( targetAlpha_L ); + savefile->WriteFloat( targetAlpha_R ); + + savefile->WriteVec3( laserEndLeft ); + savefile->WriteVec3( laserEndRight ); + + savefile->WriteInt( nextLaserLeft ); + savefile->WriteInt( nextLaserRight ); + savefile->WriteInt( nextHealthTick ); + + leftRecharger.Save( savefile ); + rightRecharger.Save( savefile ); + + leftRechargeBeam.Save( savefile ); + rightRechargeBeam.Save( savefile ); + + leftDamageBeam.Save( savefile ); + rightDamageBeam.Save( savefile ); + + leftRetractBeam.Save( savefile ); + rightRetractBeam.Save( savefile ); + + preLaserLeft.Save( savefile ); + preLaserRight.Save( savefile ); + + muzzleLeftFx.Save( savefile ); + muzzleRightFx.Save( savefile ); + impactLeftFx.Save( savefile ); + impactRightFx.Save( savefile ); + rechargeLeftFx.Save( savefile ); + rechargeRightFx.Save( savefile ); + retractLeftFx.Save( savefile ); + retractRightFx.Save( savefile ); + + savefile->WriteInt( leftRechargerHealth ); + savefile->WriteInt( rightRechargerHealth ); + savefile->WriteBool( bScripted ); +} + +/* +===================== +hhCreatureX::Restore +===================== +*/ +void hhCreatureX::Restore( idRestoreGame *savefile ) { + laserRight.Restore( savefile ); + laserLeft.Restore( savefile ); + savefile->ReadBool( bLaserLeftActive ); + savefile->ReadBool( bLaserRightActive ); + + int i, num; + savefile->ReadInt( num ); + leftBeamList.SetNum( num ); + for ( i = 0; i < num; i++ ) { + leftBeamList[i].Restore( savefile ); + } + + savefile->ReadInt( num ); + rightBeamList.SetNum( num ); + for ( int i = 0; i < num; i++ ) { + rightBeamList[i].Restore( savefile ); + } + + savefile->ReadInt( numBurstBeams ); + savefile->ReadInt( leftGunHealth ); + savefile->ReadInt( rightGunHealth ); + savefile->ReadAngles( gunShake ); + savefile->ReadInt( nextBeamTime ); + savefile->ReadInt( nextLeftZapTime ); + savefile->ReadInt( nextRightZapTime ); + savefile->ReadInt( leftGunLives ); + savefile->ReadInt( rightGunLives ); + + savefile->ReadVec3( targetStart_L ); + savefile->ReadVec3( targetEnd_L ); + savefile->ReadVec3( targetCurrent_L ); + savefile->ReadVec3( targetStart_R ); + savefile->ReadVec3( targetEnd_R ); + savefile->ReadVec3( targetCurrent_R ); + + savefile->ReadFloat( targetAlpha_L ); + savefile->ReadFloat( targetAlpha_R ); + + savefile->ReadVec3( laserEndLeft ); + savefile->ReadVec3( laserEndRight ); + + savefile->ReadInt( nextLaserLeft ); + savefile->ReadInt( nextLaserRight ); + savefile->ReadInt( nextHealthTick ); + + leftRecharger.Restore( savefile ); + rightRecharger.Restore( savefile ); + + leftRechargeBeam.Restore( savefile ); + rightRechargeBeam.Restore( savefile ); + + leftDamageBeam.Restore( savefile ); + rightDamageBeam.Restore( savefile ); + + leftRetractBeam.Restore( savefile ); + rightRetractBeam.Restore( savefile ); + + preLaserLeft.Restore( savefile ); + preLaserRight.Restore( savefile ); + + muzzleLeftFx.Restore( savefile ); + muzzleRightFx.Restore( savefile ); + impactLeftFx.Restore( savefile ); + impactRightFx.Restore( savefile ); + rechargeLeftFx.Restore( savefile ); + rechargeRightFx.Restore( savefile ); + retractLeftFx.Restore( savefile ); + retractRightFx.Restore( savefile ); + + savefile->ReadInt( leftRechargerHealth ); + savefile->ReadInt( rightRechargerHealth ); + savefile->ReadBool( bScripted ); +} + + +void hhCreatureX::MuzzleLeftOn() { + if ( !muzzleLeftFx.IsValid() ) { + BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_muzzle"), spawnArgs.GetString("muzzle_bone_left"), NULL, &MA_AssignLeftMuzzleFx, false ); + } + if ( !impactLeftFx.IsValid() ) { + BroadcastFxInfo( spawnArgs.GetString("fx_impact"), GetOrigin(), GetAxis(), NULL, &MA_AssignLeftImpactFx, false ); + } +} + +void hhCreatureX::MuzzleLeftOff() { + SAFE_REMOVE( muzzleLeftFx ); + SAFE_REMOVE( impactLeftFx ); +} + +void hhCreatureX::Event_AssignLeftMuzzleFx( hhEntityFx* fx ) { + muzzleLeftFx = fx; +} + +void hhCreatureX::Event_AssignRightMuzzleFx( hhEntityFx* fx ) { + muzzleRightFx = fx; +} + +void hhCreatureX::Event_AssignRightImpactFx( hhEntityFx* fx ) { + impactRightFx = fx; +} + +void hhCreatureX::Event_AssignLeftImpactFx( hhEntityFx* fx ) { + impactLeftFx = fx; +} + + +void hhCreatureX::Event_AssignLeftRechargeFx( hhEntityFx* fx ) { + rechargeLeftFx = fx; +} + +void hhCreatureX::Event_AssignRightRechargeFx( hhEntityFx* fx ) { + rechargeRightFx= fx; +} + +void hhCreatureX::MuzzleRightOn() { + if ( !muzzleRightFx.IsValid() ) { + BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_muzzle"), spawnArgs.GetString("muzzle_bone_right"), NULL, &MA_AssignRightMuzzleFx, false ); + } + if ( !impactRightFx.IsValid() ) { + BroadcastFxInfo( spawnArgs.GetString("fx_impact"), GetOrigin(), GetAxis(), NULL, &MA_AssignRightImpactFx, false ); + } +} + +void hhCreatureX::MuzzleRightOff() { + SAFE_REMOVE( muzzleRightFx ); + SAFE_REMOVE( impactRightFx ); +} + +void hhCreatureX::Event_SetGunOffset( const idAngles &ang ) { + gunShake = ang; +} + +void hhCreatureX::Event_EndLeftBeams() { + for ( int i = 0; i < numBurstBeams; i ++ ) { + if( leftBeamList[i].IsValid() ) { + leftBeamList[i]->Activate( false ); + } + } +} + +void hhCreatureX::Event_EndRightBeams() { + for ( int i = 0; i < numBurstBeams; i ++ ) { + if( rightBeamList[i].IsValid() ) { + rightBeamList[i]->Activate( false ); + } + } +} + +void hhCreatureX::Event_StartRechargeBeams() { + StartSound( "snd_recharge_beam_start", SND_CHANNEL_ANY ); + if ( leftRecharger.IsValid() ) { + const char *defName = spawnArgs.GetString( "fx_recharge_beam_start" ); + if (defName && defName[0]) { + hhFxInfo fxInfo; + fxInfo.RemoveWhenDone( true ); + idEntityFx *rechargeFx = SpawnFxLocal( defName, leftRecharger->GetOrigin(), leftRecharger->GetAxis(), &fxInfo, gameLocal.isClient ); + if ( rechargeFx ) { + rechargeFx->Bind( leftRecharger.GetEntity(), true ); + } + } + if ( leftRechargeBeam.IsValid() ) { + leftRechargeBeam->Activate( true ); + leftRechargeBeam->SetTargetEntity( leftRecharger.GetEntity(), 0, leftRecharger->spawnArgs.GetVector( "offsetModel" ) ); + } + } + if ( rightRecharger.IsValid() ) { + const char *defName = spawnArgs.GetString( "fx_recharge_beam_start" ); + if (defName && defName[0]) { + hhFxInfo fxInfo; + fxInfo.RemoveWhenDone( true ); + idEntityFx *rechargeFx = SpawnFxLocal( defName, rightRecharger->GetOrigin(), rightRecharger->GetAxis(), &fxInfo, gameLocal.isClient ); + if ( rechargeFx ) { + rechargeFx->Bind( rightRecharger.GetEntity(), true ); + } + } + + if ( rightRechargeBeam.IsValid() ) { + rightRechargeBeam->Activate( true ); + rightRechargeBeam->SetTargetEntity( rightRecharger.GetEntity(), 0, rightRecharger->spawnArgs.GetVector( "offsetModel" ) ); + } + } +} + +void hhCreatureX::Event_GunRecharge( int onOff ) { + if ( onOff ) { + idVec3 boneOrigin; + idMat3 boneAxis; + GetJointWorldTransform( spawnArgs.GetString("laser_bone_right"), boneOrigin, boneAxis ); + BroadcastFxInfo( spawnArgs.GetString("fx_recharger_enter"), boneOrigin, boneAxis, NULL, NULL, false ); + + GetJointWorldTransform( spawnArgs.GetString("laser_bone_left"), boneOrigin, boneAxis ); + BroadcastFxInfo( spawnArgs.GetString("fx_recharger_enter"), boneOrigin, boneAxis, NULL, NULL, false ); + + PostEventSec( &MA_StartRechargeBeams, spawnArgs.GetFloat( "recharge_delay", "0.9" ) ); + if ( leftRecharger.IsValid() ) { + if ( leftRetractBeam.IsValid() ) { + leftRetractBeam->SetTargetEntity( leftRecharger.GetEntity(), 0, leftRecharger->spawnArgs.GetVector( "offsetModel" ) ); + } + leftRechargerHealth = leftRecharger->GetHealth(); + if ( leftDamageBeam.IsValid() ) { + leftDamageBeam->SetTargetEntity( leftRecharger.GetEntity(), 0, leftRecharger->spawnArgs.GetVector( "offsetModel" ) ); + } + leftRecharger->Show(); + leftRecharger->SetOrigin( GetOrigin() ); + idVec3 offset = spawnArgs.GetVector( "healer_offset", "0 80 60" ); + leftRecharger->MoveToPosition( GetOrigin() + viewAxis * offset ); + leftRecharger->SetState( leftRecharger->GetScriptFunction( "state_Healer" ) ); + leftRecharger->SetWaitState( "" ); + } + if ( rightRecharger.IsValid() ) { + if ( rightRetractBeam.IsValid() ) { + rightRetractBeam->SetTargetEntity( rightRecharger.GetEntity(), 0, rightRecharger->spawnArgs.GetVector( "offsetModel" ) ); + } + rightRechargerHealth = rightRecharger->GetHealth(); + if ( rightDamageBeam.IsValid() ) { + rightDamageBeam->SetTargetEntity( rightRecharger.GetEntity(), 0, rightRecharger->spawnArgs.GetVector( "offsetModel" ) ); + } + rightRecharger->Show(); + idVec3 offset = spawnArgs.GetVector( "healer_offset", "0 80 60" ); + offset.y *= -1; + rightRecharger->SetOrigin( GetOrigin() ); + rightRecharger->MoveToPosition( GetOrigin() + viewAxis * offset ); + rightRecharger->SetState( rightRecharger->GetScriptFunction( "state_Healer" ) ); + rightRecharger->SetWaitState( "" ); + } + if ( !AI_LEFT_DAMAGED ) { + BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_weakpoint"), spawnArgs.GetString("recharge_bone_left"), NULL, &MA_AssignLeftRechargeFx, false ); + } + if ( !AI_RIGHT_DAMAGED ) { + BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_weakpoint"), spawnArgs.GetString("recharge_bone_right"), NULL, &MA_AssignRightRechargeFx, false ); + } + } else { + SAFE_REMOVE( rechargeLeftFx ); + SAFE_REMOVE( rechargeRightFx ); + } +} + +void hhCreatureX::Event_EndRecharge() { + if ( leftRechargeBeam.IsValid() ) { + leftRechargeBeam->Activate( false ); + } + if ( leftRecharger.IsValid() ) { + leftRecharger->SetState( leftRecharger->GetScriptFunction( "state_Return" ) ); + } + if ( rightRechargeBeam.IsValid() ) { + rightRechargeBeam->Activate( false ); + } + if ( rightRecharger.IsValid() ) { + rightRecharger->SetState( rightRecharger->GetScriptFunction( "state_Return" ) ); + } + if ( rightDamageBeam.IsValid() ) { + rightDamageBeam->Activate( false ); + } + if ( leftDamageBeam.IsValid() ) { + leftDamageBeam->Activate( false ); + } + CancelEvents( &MA_ResetRechargeBeam ); + CancelEvents( &MA_StartRechargeBeams ); +} + +//HUMANHEAD jsh PCF 4/27/06 initialized proj and made sure ReturnEntity is called +void hhCreatureX::Event_AttackMissile( const char *jointname, const idDict *projDef, int boneDir ) { + idProjectile *proj = NULL; + + // Bonedir launch? + if((BOOL)boneDir) { + proj = hhProjectile::SpawnProjectile(projDef); + if ( proj ) { + idMat3 axis; + idVec3 muzzle; + GetMuzzle( jointname, muzzle, axis ); + proj->Create(this, muzzle, axis); + proj->Launch(muzzle, axis, vec3_zero); + } + } + else { + if ( shootTarget.IsValid() ) { + proj = LaunchProjectile( jointname, shootTarget.GetEntity(), false, projDef ); //HUMANHEAD mdc - pass projDef on for multiple proj support + } else { + proj = LaunchProjectile( jointname, enemy.GetEntity(), false, projDef ); //HUMANHEAD mdc - pass projDef on for multiple proj support + } + } + + idThread::ReturnEntity( proj ); +} + +void hhCreatureX::Event_SparkLeft() { + BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_spark"), spawnArgs.GetString("damage_bone_left"), NULL ); + PostEventSec(&MA_SparkLeft, spawnArgs.GetFloat( "spark_freq", "1" ) + gameLocal.random.RandomFloat() ); +} + +void hhCreatureX::Event_SparkRight() { + BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_spark"), spawnArgs.GetString("damage_bone_right"), NULL ); + PostEventSec(&MA_SparkRight, spawnArgs.GetFloat( "spark_freq", "1" ) + gameLocal.random.RandomFloat() ); +} + +void hhCreatureX::Event_HudEvent( const char* eventName ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( player && player->hud ) { + gameLocal.GetLocalPlayer()->hud->HandleNamedEvent( eventName ); + gameLocal.GetLocalPlayer()->hud->StateChanged(gameLocal.time); + } +} + +void hhCreatureX::Activate( idEntity *activator ) { + if ( preLaserLeft.IsValid() ) { + HH_ASSERT(!FLOAT_IS_NAN(preLaserLeft->GetOrigin().x)); //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(preLaserLeft->GetOrigin().y)); //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(preLaserLeft->GetOrigin().z)); //Test for bad origin + } + if ( preLaserRight.IsValid() ) { + HH_ASSERT(!FLOAT_IS_NAN(preLaserRight->GetOrigin().x)); //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(preLaserRight->GetOrigin().y)); //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(preLaserRight->GetOrigin().z)); //Test for bad origin + } + if ( laserLeft.IsValid() ) { + HH_ASSERT(!FLOAT_IS_NAN(laserLeft->GetOrigin().x)); //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(laserLeft->GetOrigin().y)); //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(laserLeft->GetOrigin().z)); //Test for bad origin + } + if ( laserRight.IsValid() ) { + HH_ASSERT(!FLOAT_IS_NAN(laserRight->GetOrigin().x)); //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(laserRight->GetOrigin().y)); //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(laserRight->GetOrigin().z)); //Test for bad origin + } + + hhMonsterAI::Activate( activator ); +} + +void hhCreatureX::Show() { + hhMonsterAI::Show(); + if ( preLaserLeft.IsValid() ) { + preLaserLeft->Hide(); + preLaserLeft->Activate( false ); + } + if ( preLaserRight.IsValid() ) { + preLaserRight->Hide(); + preLaserRight->Activate( false ); + } + if ( laserLeft.IsValid() ) { + laserLeft->Hide(); + laserLeft->Activate( false ); + } + if ( laserRight.IsValid() ) { + laserRight->Hide(); + laserRight->Activate( false ); + } + if ( leftRechargeBeam.IsValid() ) { + leftRechargeBeam->Hide(); + leftRechargeBeam->Activate( false ); + } + if ( rightRechargeBeam.IsValid() ) { + rightRechargeBeam->Hide(); + rightRechargeBeam->Activate( false ); + } + if ( leftDamageBeam.IsValid() ) { + leftDamageBeam->Hide(); + leftDamageBeam->Activate( false ); + } + if ( rightDamageBeam.IsValid() ) { + rightDamageBeam->Hide(); + rightDamageBeam->Activate( false ); + } + if ( leftRetractBeam.IsValid() ) { + leftRetractBeam->Hide(); + leftRetractBeam->Activate( false ); + } + if ( rightRetractBeam.IsValid() ) { + rightRetractBeam->Hide(); + rightRetractBeam->Activate( false ); + } +} + +void hhCreatureX::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + if ( spawnArgs.GetBool( "boss" ) ) { + if ( attacker && attacker->IsType( idPlayer::Type ) && idStr::Icmp( damageDefName, spawnArgs.GetString( "def_damageTelefrag", "damage_telefrag" ) ) == 0 ) { + //telefragged so move somewhere nice + SetState( GetScriptFunction( "state_Telefragged" ) ); + return; + } + } + + if ( !fl.takedamage ) { + return; + } + //check for splash damage or jenny-specific damage based on location + if ( AI_DEAD && spawnArgs.GetBool( "boss" ) ) { + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); + if ( ( damageDef && damageDef->GetFloat( "radius" ) > 0 ) || + ( location && strcmp( GetDamageGroup( location ), "jenny" ) == 0 ) ) { + SetState( GetScriptFunction( "state_RealDeath" ) ); + fl.takedamage = false; + } + } + hhMonsterAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/src/Prey/ai_creaturex.h b/src/Prey/ai_creaturex.h new file mode 100644 index 0000000..af4b5b7 --- /dev/null +++ b/src/Prey/ai_creaturex.h @@ -0,0 +1,139 @@ + +#ifndef __PREY_AI_CREATURE_H__ +#define __PREY_AI_CREATURE_H__ + + +class hhCreatureX : public hhMonsterAI { + +public: + + CLASS_PROTOTYPE(hhCreatureX); +#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + void Event_GunRecharge( int onOff ) {}; + void Event_LaserOn() {}; + void Event_LaserOff() {}; + void Event_UpdateLasers() {}; + void Event_SetGunOffset( const idAngles &ang ) {}; + void Event_EndLeftBeams() {}; + void Event_EndRightBeams() {}; + void Event_AssignRightMuzzleFx( hhEntityFx* fx ) {}; + void Event_AssignLeftMuzzleFx( hhEntityFx* fx ) {}; + void Event_AssignRightImpactFx( hhEntityFx* fx ) {}; + void Event_AssignLeftImpactFx( hhEntityFx* fx ) {}; + void Event_AssignLeftRechargeFx( hhEntityFx* fx ) {}; + void Event_AssignRightRechargeFx( hhEntityFx* fx ) {}; + void Event_AttackMissile( const char *jointname, const idDict *projDef, int boneDir ) {}; + void Event_HudEvent( const char *eventName ) {}; + void Event_RechargeHealth() {}; + void Event_EndRecharge() {}; + void Event_ResetRechargeBeam() {}; + void Event_SparkLeft() {}; + void Event_SparkRight() {}; + void Event_LeftGunDeath() {}; + void Event_RightGunDeath() {}; + void Event_StartRechargeBeams() {}; +#else + + ~hhCreatureX(); + void Spawn(); + void LinkScriptVariables( void ); + void Think(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + + idScriptBool AI_GUN_TRACKING; + idScriptBool AI_RECHARGING; + idScriptBool AI_LEFT_FIRE; + idScriptBool AI_RIGHT_FIRE; + idScriptBool AI_LEFT_DAMAGED; + idScriptBool AI_RIGHT_DAMAGED; + idScriptBool AI_FIRING_LASER; + idScriptBool AI_HEALTH_TICK; + idScriptBool AI_GUN_EXPLODE; +protected: + void Event_GunRecharge( int onOff ); + void Event_LaserOn(); + void Event_LaserOff(); + void Event_UpdateLasers(); + void Event_SetGunOffset( const idAngles &ang ); + void Event_EndLeftBeams(); + void Event_EndRightBeams(); + void Event_AssignRightMuzzleFx( hhEntityFx* fx ); + void Event_AssignLeftMuzzleFx( hhEntityFx* fx ); + void Event_AssignRightImpactFx( hhEntityFx* fx ); + void Event_AssignLeftImpactFx( hhEntityFx* fx ); + void Event_AssignLeftRechargeFx( hhEntityFx* fx ); + void Event_AssignRightRechargeFx( hhEntityFx* fx ); + void Event_AttackMissile( const char *jointname, const idDict *projDef, int boneDir ); + void Event_HudEvent( const char *eventName ); + void Event_RechargeHealth(); + void Event_EndRecharge(); + void Event_ResetRechargeBeam(); + void Event_SparkLeft(); + void Event_SparkRight(); + void Event_LeftGunDeath(); + void Event_RightGunDeath(); + void Event_StartRechargeBeams(); + + void MuzzleLeftOn(); + void MuzzleRightOn(); + void MuzzleLeftOff(); + void MuzzleRightOff(); + bool UpdateAnimationControllers( void ); + virtual void Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + void Show(); + void Activate( idEntity *activator ); + idEntityPtr laserRight; + idEntityPtr laserLeft; + idEntityPtr preLaserRight; + idEntityPtr preLaserLeft; + idList< idEntityPtr > leftBeamList; + idList< idEntityPtr > rightBeamList; + idEntityPtr leftRechargeBeam; + idEntityPtr rightRechargeBeam; + idEntityPtr leftDamageBeam; + idEntityPtr rightDamageBeam; + idEntityPtr leftRetractBeam; + idEntityPtr rightRetractBeam; + idEntityPtr muzzleLeftFx; + idEntityPtr muzzleRightFx; + idEntityPtr impactLeftFx; + idEntityPtr impactRightFx; + idEntityPtr rechargeLeftFx; + idEntityPtr rechargeRightFx; + idEntityPtr retractLeftFx; + idEntityPtr retractRightFx; + int numBurstBeams; + bool bLaserLeftActive; + bool bLaserRightActive; + idVec3 targetStart_L; + idVec3 targetEnd_L; + idVec3 targetCurrent_L; + idVec3 targetStart_R; + idVec3 targetEnd_R; + idVec3 targetCurrent_R; + float targetAlpha_L; + float targetAlpha_R; + int leftGunHealth; + int rightGunHealth; + int leftGunLives; + int rightGunLives; + idAngles gunShake; + int nextBeamTime; + int nextLeftZapTime; + int nextRightZapTime; + idVec3 laserEndLeft; + idVec3 laserEndRight; + int nextLaserLeft; + int nextLaserRight; + int nextHealthTick; + int leftRechargerHealth; + int rightRechargerHealth; + idEntityPtr leftRecharger; + idEntityPtr rightRecharger; + bool bScripted; +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +}; + +#endif /* __PREY_AI_CREATURE_H__ */ diff --git a/src/Prey/ai_droid.cpp b/src/Prey/ai_droid.cpp new file mode 100644 index 0000000..a183212 --- /dev/null +++ b/src/Prey/ai_droid.cpp @@ -0,0 +1,660 @@ + + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_StartBurst("startBurst"); +const idEventDef EV_EndBurst("endBurst"); +const idEventDef EV_StartStaticBeam("startStaticBeam"); +const idEventDef EV_EndStaticBeam("endStaticBeam"); +const idEventDef EV_StartChargeShot("startChargeShot"); +const idEventDef EV_EndChargeShot("endChargeShot"); +const idEventDef EV_StartPathing("startPathing"); +const idEventDef EV_EndPathing("endPathing"); +const idEventDef EV_EndZipBeams(""); +const idEventDef EV_HealerReset(""); + +CLASS_DECLARATION(hhMonsterAI, hhDroid) + EVENT(EV_StartStaticBeam, hhDroid::Event_StartStaticBeam) + EVENT(EV_EndStaticBeam, hhDroid::Event_EndStaticBeam) + EVENT(EV_StartBurst, hhDroid::Event_StartBurst) + EVENT(EV_EndBurst, hhDroid::Event_EndBurst) + EVENT(EV_StartChargeShot, hhDroid::Event_StartChargeShot) + EVENT(EV_EndChargeShot, hhDroid::Event_EndChargeShot) + EVENT(EV_StartPathing, hhDroid::Event_StartPathing) + EVENT(EV_EndPathing, hhDroid::Event_EndPathing) + EVENT(EV_EndZipBeams, hhDroid::Event_EndZipBeams) + EVENT(EV_HealerReset, hhDroid::Event_HealerReset) +END_CLASS + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +hhDroid::hhDroid() { + burstLength = 0.0; + burstSpread = 0.0; + burstDuration = 0.0; + staticDuration = 0.0; + staticRange = 0.0; + numBurstBeams = 0; + beamOffset = vec3_zero; + chargeShotSize = 0.0; + old_fly_bob_strength = 0.0; + spinAngle = 0.0f; +} + +hhDroid::~hhDroid() { + for ( int i = 0; i < numBurstBeams; i ++ ) { + SAFE_REMOVE( beamBurstList[i] ); + } +} + +void hhDroid::Show( void ) { + hhMonsterAI::Show(); + + PostEventSec(&EV_StartStaticBeam, gameLocal.random.RandomFloat() * spawnArgs.GetFloat( "staticFreq", "2.5" ) ); +} + +void hhDroid::Spawn(void) +{ + Event_SetMoveType(MOVETYPE_FLY); + lives = spawnArgs.GetInt( "lives", "3" ); + numBurstBeams = spawnArgs.GetInt( "numBurstBeams", "5" ); + burstLength = spawnArgs.GetFloat( "burstLength", "250" ); + staticDuration = spawnArgs.GetFloat( "staticDuration", "2.0" ); + burstDuration = spawnArgs.GetFloat( "burstDuration", "2.0" ); + beamOffset = spawnArgs.GetVector( "beamOffset", "0 0 0" ); + burstSpread = spawnArgs.GetFloat( "burstSpread", "1.0" ); + enemyRange = spawnArgs.GetFloat( "enemy_range", "3000" ); + flyDampening = spawnArgs.GetFloat( "fly_dampening", "0.01" ); + bHealer = spawnArgs.GetBool( "healer" ); + + beamZip = hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamZip" ) ); + if( beamZip.IsValid() ) { + beamZip->Activate( false ); + beamZip->SetOrigin( GetPhysics()->GetOrigin() + beamOffset ); + beamZip->Bind( this, false ); + } + + for ( int i = 0; i < numBurstBeams; i ++ ) { + if ( gameLocal.random.RandomFloat() > 0.5f ) { + beamBurstList.Append( hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamBurst1" ) ) ); + } else { + beamBurstList.Append( hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamBurst2" ) ) ); + } + if( beamBurstList[i].IsValid() ) { + beamBurstList[i]->Activate( false ); + beamBurstList[i]->SetOrigin( GetPhysics()->GetOrigin() + beamOffset ); + beamBurstList[i]->Bind( this, false ); + } + } + + PostEventSec(&EV_StartStaticBeam, gameLocal.random.RandomFloat() * spawnArgs.GetFloat( "staticFreq", "2.5" ) ); +} + +bool hhDroid::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + bool bPain = hhMonsterAI::Pain( inflictor, attacker, damage, dir, location ); + if ( !bPain ) { + return bPain; + } + + //activate some beams for damage fx + for ( int i = 0; i < beamBurstList.Num(); i ++ ) { + if ( beamBurstList[i].IsValid() ) { + beamBurstList[i]->Activate( true ); + beamBurstList[i]->SetTargetLocation( beamBurstList[i]->GetOrigin() + burstLength * hhUtils::RandomSpreadDir( dir.ToMat3(), burstSpread ) ); + PostEventSec(&EV_EndBurst, staticDuration); + } + } + return bPain; +} + +void hhDroid::Think() { + PROFILE_SCOPE("AI", PROFMASK_NORMAL|PROFMASK_AI); + if (ai_skipThink.GetBool()) { + return; + } + + hhMonsterAI::Think(); + + //update burst beams + idMat3 dir; + if ( enemy.IsValid() ) { + dir = (enemy->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin()).ToMat3(); + } else { + dir = GetPhysics()->GetAxis(); + } + for ( int i = 0; i < beamBurstList.Num(); i ++ ) { + if ( beamBurstList[i].IsValid() && beamBurstList[i]->IsActivated() ) { + beamBurstList[i]->SetTargetLocation( beamBurstList[i]->GetOrigin() + burstLength * hhUtils::RandomSpreadDir( hhUtils::RandomVector().ToMat3(), burstSpread ) ); + } + } + + if ( staticPoint.IsValid() && beamBurstList[0].IsValid() ) { + beamBurstList[0]->SetTargetLocation( staticPoint->GetPhysics()->GetOrigin() ); + } + + if ( chargeShot.IsValid() ) { + chargeShot->SetShaderParm( 9, 1 ); + chargeShot->SetShaderParm( 10, chargeShotSize ); + chargeShotSize += spawnArgs.GetFloat( "chargeSizeDelta" ); + chargeShot->GetPhysics()->DisableClip(); + } +} + +void hhDroid::Event_StartChargeShot(void) { + //spawn chargeshot projectile + const idDict *projectileDef = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_chargeshot") ); + if ( projectileDef ) { + chargeShot = hhProjectile::SpawnProjectile( projectileDef ); + if ( chargeShot.IsValid() ) { + chargeShotSize = spawnArgs.GetFloat( "chargeSizeStart", "1.0" ); + idVec3 launchStart = GetPhysics()->GetOrigin() + spawnArgs.GetVector( "chargeOffset", "15 0 0" ) * GetRenderEntity()->axis; + chargeShot->Create(this, launchStart, GetRenderEntity()->axis); + chargeShot->Launch(chargeShot->GetPhysics()->GetOrigin(), chargeShot->GetPhysics()->GetAxis(), vec3_zero ); + chargeShot->Bind(this, true); + } + } +} + +void hhDroid::Event_EndChargeShot(void) { + //launch projectile + if ( chargeShot.IsValid() ) { + chargeShot->Unbind(); + chargeShot->StartTracking(); + chargeShot.Clear(); + } +} + +void hhDroid::Event_StartBurst(void) { + gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), this, this, this, this, spawnArgs.GetString("def_burstdamage") ); + + //burst effect + idMat3 dir; + if ( enemy.IsValid() ) { + dir = (enemy->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin()).ToMat3(); + } else { + dir = GetPhysics()->GetAxis(); + } + for ( int i = 0; i < beamBurstList.Num(); i++ ) { + if ( beamBurstList[i].IsValid() ) { + beamBurstList[i]->Activate( true ); + beamBurstList[i]->SetTargetLocation( beamBurstList[i]->GetOrigin() + burstLength * hhUtils::RandomSpreadDir( hhUtils::RandomVector().ToMat3(), burstSpread ) ); + } + } + PostEventSec(&EV_EndBurst, burstDuration); +} + +void hhDroid::Event_EndBurst(void) { + for ( int i = 0; i < beamBurstList.Num(); i ++ ) { + if ( beamBurstList[i].IsValid() ) { + beamBurstList[i]->Activate( false ); + } + } +} + +void hhDroid::Event_EndStaticBeam(void) { + if ( beamBurstList[0].IsValid() ) { + beamBurstList[0]->Activate( false ); + } + if ( staticPoint.IsValid() ) { + staticPoint.Clear(); + } + + //wait random time until next static beam + if ( !IsHidden() && !AI_DEAD ) { + PostEventSec(&EV_StartStaticBeam, gameLocal.random.RandomFloat() * spawnArgs.GetFloat( "staticFreq", "2.5" ) ); + } +} + +void hhDroid::Event_StartStaticBeam(void) { + idVec3 targetPoint; + idEntity *entityList[ MAX_GENTITIES ]; + + if ( IsHidden() || AI_DEAD ) { + return; + } + + //find nearest staticPoint + float radius = spawnArgs.GetFloat( "staticRange" ); + int listedEntities = gameLocal.EntitiesWithinRadius( GetPhysics()->GetOrigin(), radius, entityList, MAX_GENTITIES ); + for( int i = 0; i < listedEntities; i++ ) { + if ( entityList[ i ] && entityList[ i ]->spawnArgs.GetInt( "droidBeam", "0" ) ) { + staticPoint = entityList[ i ]; + break; + } + } + + //activate one beam + if ( staticPoint.IsValid() && beamBurstList[0].IsValid() ) { + StartSound( "snd_staticBeam", SND_CHANNEL_BODY, 0, true, NULL ); + beamBurstList[0]->Activate( true ); + beamBurstList[0]->SetTargetLocation( beamBurstList[0]->GetOrigin() + burstLength * hhUtils::RandomVector() ); + staticPoint->ActivateTargets( this ); + PostEventSec(&EV_EndStaticBeam, staticDuration); + } +} + +void hhDroid::FlyTurn( void ) { + if ( AI_PATHING ) { + hhMonsterAI::FlyTurn(); + return; + } + if ( AI_ENEMY_VISIBLE || move.moveCommand == MOVE_FACE_ENEMY ) { + TurnToward( lastVisibleEnemyPos ); + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + } else if ( move.speed > 0.0f ) { + const idVec3 &vel = physicsObj.GetLinearVelocity(); + if ( vel.ToVec2().LengthSqr() > 0.1f ) { + TurnToward( vel.ToYaw() ); + } + } + Turn(); +} + +void hhDroid::Event_StartPathing(void) { + AI_PATHING = true; + old_fly_bob_strength = fly_bob_strength; + fly_bob_strength = 0.0; + ignore_obstacles = true; +} + +void hhDroid::Event_EndPathing(void) { + AI_PATHING = false; + fly_bob_strength = old_fly_bob_strength; + ignore_obstacles = false; +} + +void hhDroid::FlyMove( void ) { + idVec3 goalPos; + idVec3 oldorigin; + idVec3 newDest; + + AI_BLOCKED = false; + if ( ( move.moveCommand != MOVE_NONE ) && ReachedPos( move.moveDest, move.moveCommand ) ) { + if ( AI_PATHING ) { + physicsObj.SetLinearVelocity( idVec3(0,0,0) ); + } + StopMove( MOVE_STATUS_DONE ); + } + + if ( move.moveCommand != MOVE_TO_POSITION_DIRECT ) { + idVec3 vel = physicsObj.GetLinearVelocity(); + + if ( GetMovePos( goalPos ) ) { + move.obstacle = NULL; + } + + if ( move.speed ) { + FlySeekGoal( vel, goalPos ); + } + + // add in bobbing + AddFlyBob( vel ); + + if ( enemy.GetEntity() && ( move.moveCommand != MOVE_TO_POSITION ) ) { + float dist = ( GetPhysics()->GetOrigin() - enemy->GetPhysics()->GetOrigin() ).LengthFast(); + if ( dist < enemyRange && enemyRange > 0 ) { + AdjustFlyHeight( vel, goalPos ); + } + } + + AdjustFlySpeed( vel ); + + 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( disableGravity ); + RunPhysics(); + + monsterMoveResult_t moveResult = physicsObj.GetMoveResult(); + if ( !af_push_moveables && attack.Length() && TestMelee() ) { + DirectDamage( attack, enemy.GetEntity() ); + } else { + idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); + if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) { + KickObstacles( viewAxis[ 0 ], kickForce, blockEnt ); + } else if ( moveResult == MM_BLOCKED ) { + move.blockTime = gameLocal.time + 500; + AI_BLOCKED = true; + } + } + + idVec3 org = physicsObj.GetOrigin(); + if ( oldorigin != org ) { + TouchTriggers(); + } + + if ( ai_debugMove.GetBool() ) { + 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, goalPos, gameLocal.msec, true ); + gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true ); + DrawRoute(); + } +} + +idProjectile *hhDroid::LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone, const idDict* desiredProjectileDef ) { + //jsh overridden to allow per-projectile accuracy + idVec3 muzzle; + idVec3 dir; + idVec3 start; + trace_t tr; + idBounds projBounds; + float distance; + const idClipModel *projClip; + float attack_accuracy; + float attack_cone; + float projectile_spread; + float diff; + float angle; + float spin; + idAngles ang; + int num_projectiles; + int i; + idMat3 axis; + idVec3 tmp; + idProjectile *lastProjectile; + + //HUMANHEAD mdc - added to support multiple projectiles + if( desiredProjectileDef ) { //try to set our projectile to the desiredProjectile + int projIndex = FindProjectileInfo( desiredProjectileDef ); + if( projIndex >= 0 ) { + SetCurrentProjectile( projIndex ); + } + } + //HUMANHEAD END + + if ( !projectileDef ) { + gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() ); + return NULL; + } + + if ( projectileDef->GetFloat( "attack_accuracy" ) ) { + attack_accuracy = projectileDef->GetFloat( "attack_accuracy", "7" ); + } else { + attack_accuracy = spawnArgs.GetFloat( "attack_accuracy", "7" ); + } + attack_cone = spawnArgs.GetFloat( "attack_cone", "70" ); + if ( projectileDef->GetFloat( "projectile_spread" ) ) { + projectile_spread = projectileDef->GetFloat( "projectile_spread", "0" ); + } else { + projectile_spread = spawnArgs.GetFloat( "projectile_spread", "0" ); + } + if ( projectileDef->GetFloat( "num_projectiles" ) ) { + num_projectiles = projectileDef->GetFloat( "num_projectiles", "1" ); + } else { + num_projectiles = spawnArgs.GetFloat( "num_projectiles", "1" ); + } + + + GetMuzzle( jointname, muzzle, axis ); + + if ( !projectile.GetEntity() ) { + CreateProjectile( muzzle, axis[ 0 ] ); + } + + lastProjectile = projectile.GetEntity(); + + if ( target != NULL ) { + tmp = target->GetPhysics()->GetAbsBounds().GetCenter() - muzzle; + tmp.Normalize(); + axis = tmp.ToMat3(); + } else { + axis = viewAxis; + } + + // rotate it because the cone points up by default + tmp = axis[2]; + axis[2] = axis[0]; + axis[0] = -tmp; + + // make sure the projectile starts inside the monster bounding box + const idBounds &ownerBounds = physicsObj.GetAbsBounds(); + projClip = lastProjectile->GetPhysics()->GetClipModel(); + projBounds = projClip->GetBounds().Rotate( axis ); + + // 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( muzzle, viewAxis[ 0 ], distance ) ) { + start = muzzle + 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(); + } + + gameLocal.clip.Translation( tr, start, muzzle, projClip, axis, MASK_SHOT_RENDERMODEL, this ); + muzzle = tr.endpos; + + // set aiming direction + GetAimDir( muzzle, target, this, dir ); + ang = dir.ToAngles(); + + // adjust his aim so it's not perfect. uses sine based movement so the tracers appear less random in their spread. + float t = MS2SEC( gameLocal.time + entityNumber * 497 ); + ang.pitch += idMath::Sin16( t * 5.1 ) * attack_accuracy; + ang.yaw += idMath::Sin16( t * 6.7 ) * attack_accuracy; + + if ( !AI_WALLWALK && clampToAttackCone ) { + // 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, current_yaw ); + if ( diff > attack_cone ) { + ang.yaw = current_yaw + attack_cone; + } else if ( diff < -attack_cone ) { + ang.yaw = current_yaw - attack_cone; + } + } + + axis = ang.ToMat3(); + + float spreadRad = DEG2RAD( projectile_spread ); + for( i = 0; i < num_projectiles; i++ ) { + // spread the projectiles out + angle = idMath::Sin( spreadRad * 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(); + + // launch the projectile + if ( !projectile.GetEntity() ) { + CreateProjectile( muzzle, dir ); + } + lastProjectile = projectile.GetEntity(); + lastProjectile->Launch( muzzle, dir, vec3_origin ); + projectile = NULL; + } + + TriggerWeaponEffects( muzzle, axis ); + + lastAttackTime = gameLocal.time; + +//HUMANHEAD mdc - added to support multiple projectiles + projectile = NULL; + SetCurrentProjectile( projectileDefaultDefIndex ); //set back to our default projectile to be on the safe side +//HUMANHEAD END + + return lastProjectile; +} + +void hhDroid::Event_FlyZip() { + idVec3 old_origin = GetOrigin(); + hhMonsterAI::Event_FlyZip(); + if ( old_origin != GetOrigin() ) { + if ( beamZip.IsValid() ) { + beamZip->SetTargetLocation( old_origin ); + } + if ( beamZip.IsValid() ) { + beamZip->Activate( true ); + } + PostEventSec( &EV_EndZipBeams, spawnArgs.GetFloat( "zip_beam_duration", "1.0" ) ); + } +} + +void hhDroid::Event_EndZipBeams() { + if ( beamZip.IsValid() ) { + beamZip->Activate( false ); + } +} + +void hhDroid::Event_HealerReset() { + fl.takedamage = true; +} + +void hhDroid::Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( bHealer ) { + lives--; + if ( lives <= 0 ) { + hhMonsterAI::Killed( inflictor, attacker, damage, dir, location ); + } else { + const char *defName = spawnArgs.GetString( "fx_pain" ); + if (defName && defName[0]) { + hhFxInfo fxInfo; + fxInfo.RemoveWhenDone( true ); + idEntityFx *painFx = SpawnFxLocal( defName, GetOrigin(), GetAxis(), &fxInfo, gameLocal.isClient ); + if ( painFx ) { + painFx->Bind( this, true ); + } + } + health = spawnArgs.GetInt( "health" ); + fl.takedamage = false; + PostEventSec( &EV_HealerReset, spawnArgs.GetFloat( "reset_delay", "0.8" ) ); + } + return; + } + if ( AI_DEAD ) { + AI_DAMAGE = true; + return; + } + hhMonsterAI::Killed( inflictor, attacker, damage, dir, location ); + for ( int i = 0; i < beamBurstList.Num(); i ++ ) { + if ( beamBurstList[i].IsValid() ) { + beamBurstList[i]->Activate( true ); + beamBurstList[i]->SetTargetLocation( beamBurstList[i]->GetOrigin() + burstLength * hhUtils::RandomSpreadDir( dir.ToMat3(), burstSpread ) ); + PostEventSec(&EV_EndBurst, staticDuration); + } + } +} + +void hhDroid::AdjustFlySpeed( idVec3 &vel ) { + float speed; + + // apply dampening + vel -= vel * flyDampening * MS2SEC( gameLocal.msec ); + + // gradually speed up/slow down to desired speed + speed = vel.Normalize(); + speed += ( move.speed - speed ) * MS2SEC( gameLocal.msec ); + if ( speed < 0.0f ) { + speed = 0.0f; + } else if ( move.speed && ( speed > move.speed ) ) { + speed = move.speed; + } + + vel *= speed; +} + +void hhDroid::UpdateModelTransform( void ) { + idVec3 origin; + idMat3 axis; + + if ( GetPhysicsToVisualTransform( origin, axis ) ) { + renderEntity.axis = axis * GetPhysics()->GetAxis(); + renderEntity.origin = GetPhysics()->GetOrigin() + origin * renderEntity.axis; + + if ( bHealer ) { + renderEntity.axis = idAngles( 0, 0, spinAngle ).ToMat3() * renderEntity.axis; + spinAngle += 10; + if ( spinAngle > 360.0f ) { + spinAngle = 0.0f; + } + } + } else { + renderEntity.axis = GetPhysics()->GetAxis(); + renderEntity.origin = GetPhysics()->GetOrigin(); + } +} + +/* +===================== +hhDroid::Save +===================== +*/ +void hhDroid::Save( idSaveGame *savefile ) const { + int num = beamBurstList.Num(); + savefile->WriteInt( num ); + for ( int i = 0; i < num; i++ ) { + beamBurstList[i].Save( savefile ); + } + + staticPoint.Save( savefile ); + chargeShot.Save( savefile ); + beamZip.Save( savefile ); + + savefile->WriteVec3( savedGravity ); + savefile->WriteFloat( chargeShotSize ); + savefile->WriteFloat( burstLength ); + savefile->WriteFloat( burstSpread ); + savefile->WriteFloat( burstDuration ); + savefile->WriteFloat( staticDuration ); + savefile->WriteFloat( staticRange ); + savefile->WriteInt( numBurstBeams ); + savefile->WriteVec3( beamOffset ); + savefile->WriteFloat( old_fly_bob_strength ); + savefile->WriteFloat( enemyRange ); + savefile->WriteFloat( flyDampening ); + savefile->WriteFloat( spinAngle ); + savefile->WriteBool( bHealer ); + savefile->WriteInt( lives ); +} + +/* +===================== +hhDroid::Restore +===================== +*/ +void hhDroid::Restore( idRestoreGame *savefile ) { + int num; + savefile->ReadInt( num ); + beamBurstList.SetNum( num ); + for ( int i = 0; i < num; i++ ) { + beamBurstList[i].Restore( savefile ); + } + + staticPoint.Restore( savefile ); + chargeShot.Restore( savefile ); + beamZip.Restore( savefile ); + + savefile->ReadVec3( savedGravity ); + savefile->ReadFloat( chargeShotSize ); + savefile->ReadFloat( burstLength ); + savefile->ReadFloat( burstSpread ); + savefile->ReadFloat( burstDuration ); + savefile->ReadFloat( staticDuration ); + savefile->ReadFloat( staticRange ); + savefile->ReadInt( numBurstBeams ); + savefile->ReadVec3( beamOffset ); + savefile->ReadFloat( old_fly_bob_strength ); + savefile->ReadFloat( enemyRange ); + savefile->ReadFloat( flyDampening ); + savefile->ReadFloat( spinAngle ); + savefile->ReadBool( bHealer ); + savefile->ReadInt( lives ); +} +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/src/Prey/ai_droid.h b/src/Prey/ai_droid.h new file mode 100644 index 0000000..d483ef8 --- /dev/null +++ b/src/Prey/ai_droid.h @@ -0,0 +1,73 @@ + +#ifndef __PREY_AI_DROID_H__ +#define __PREY_AI_DROID_H__ + +class hhDroid : public hhMonsterAI { + +public: + + CLASS_PROTOTYPE(hhDroid); + +#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + void Event_StartBurst(void) {}; + void Event_EndBurst(void) {}; + void Event_StartStaticBeam(void) {}; + void Event_EndStaticBeam(void) {}; + void Event_StartChargeShot(void) {}; + void Event_EndChargeShot(void) {}; + void Event_StartPathing(void) {}; + void Event_EndPathing(void) {}; + void Event_FlyZip() {}; + void Event_EndZipBeams() {}; + void Event_HealerReset() {} +#else + hhDroid(); + ~hhDroid(); + void Spawn( void ); + virtual void Think( void ); + void FlyTurn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void AdjustFlySpeed( idVec3 &vel ); +protected: + idList< idEntityPtr > beamBurstList; + idEntityPtr beamZip; + idEntityPtr staticPoint; + idEntityPtr chargeShot; + void Event_StartBurst(void); + void Event_EndBurst(void); + void Event_StartStaticBeam(void); + void Event_EndStaticBeam(void); + void Event_StartChargeShot(void); + void Event_EndChargeShot(void); + void Event_StartPathing(void); + void Event_EndPathing(void); + void Event_FlyZip(); + void Event_EndZipBeams(); + void Event_HealerReset(); + idProjectile *LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone, const idDict* desiredProjectileDef ); + virtual bool Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void Show( void ); + void FlyMove( void ); + void Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + void UpdateModelTransform(); + + idVec3 savedGravity; + float chargeShotSize; + float burstLength; + float burstSpread; + float burstDuration; + float staticDuration; //how long staticbeam lasts + float staticRange; //distance to check for staticbeam points + int numBurstBeams; + idVec3 beamOffset; + float old_fly_bob_strength; + float enemyRange; + float flyDampening; + int lives; + float spinAngle; + bool bHealer; +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +}; + +#endif \ No newline at end of file diff --git a/src/Prey/ai_gasbag_simple.cpp b/src/Prey/ai_gasbag_simple.cpp new file mode 100644 index 0000000..bb38090 --- /dev/null +++ b/src/Prey/ai_gasbag_simple.cpp @@ -0,0 +1,801 @@ + + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_AcidBlast("acidBlast"); +const idEventDef EV_AcidDrip(""); +const idEventDef EV_DeathCloud(""); +const idEventDef EV_LaunchPod("launchPod"); +const idEventDef EV_NewPod("", "e"); +const idEventDef EV_SpawnBlastDebris(""); +const idEventDef EV_ChargeEnemy("chargeEnemy"); +const idEventDef EV_GrabEnemy("grabEnemy", NULL, 'f'); +const idEventDef EV_BiteEnemy("biteEnemy"); +const idEventDef EV_DirectMoveToPosition("directMoveToPosition", "v"); +const idEventDef EV_BindUnfroze("", "e"); +const idEventDef EV_GrabCheck("grabCheck", "", 'd'); +const idEventDef EV_MoveToGrabPosition("moveToGrabPosition"); +const idEventDef EV_CheckRange("checkRange", "", 'd'); +const idEventDef EV_EnemyRangeZ("enemyRangeZ", "", 'f'); + +CLASS_DECLARATION(hhMonsterAI, hhGasbagSimple) + EVENT(EV_AcidBlast, hhGasbagSimple::Event_AcidBlast) + EVENT(EV_AcidDrip, hhGasbagSimple::Event_AcidDrip) + EVENT(EV_DeathCloud, hhGasbagSimple::Event_DeathCloud) + EVENT(EV_LaunchPod, hhGasbagSimple::Event_LaunchPod) + EVENT(EV_NewPod, hhGasbagSimple::Event_NewPod) + EVENT(EV_SpawnBlastDebris, hhGasbagSimple::Event_SpawnBlastDebris) + EVENT(EV_ChargeEnemy, hhGasbagSimple::Event_ChargeEnemy) + EVENT(EV_GrabEnemy, hhGasbagSimple::Event_GrabEnemy) + EVENT(EV_BiteEnemy, hhGasbagSimple::Event_BiteEnemy) + EVENT(EV_DirectMoveToPosition, hhGasbagSimple::Event_DirectMoveToPosition) + EVENT(EV_BindUnfroze, hhGasbagSimple::Event_BindUnfroze) + EVENT(EV_GrabCheck, hhGasbagSimple::Event_GrabCheck) + EVENT(EV_MoveToGrabPosition, hhGasbagSimple::Event_MoveToGrabPosition) + EVENT(EV_CheckRange, hhGasbagSimple::Event_CheckRange) + EVENT(EV_EnemyRangeZ, hhGasbagSimple::Event_EnemyRangeZ) +END_CLASS + +static const idEventDef EV_Unfreeze( "" ); + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +// +// ~hhGasbagSimple() +// +hhGasbagSimple::~hhGasbagSimple(void) { + // Make sure to unbind the player if we've grabbed him to prevent deleting him + if (enemy.IsValid() && enemy->IsBoundTo(this) && enemy->IsType(hhPlayer::Type)) { + enemy->Unbind(); + enemy->PostEventMS(&EV_Unfreeze, 0); + } +} + +// +// Spawn() +// +void hhGasbagSimple::Spawn(void) { + Event_SetMoveType(MOVETYPE_FLY); // So we ignore gravity zones from the beginning + podOffset = spawnArgs.GetVector("pod_offset", "0 0 64"); + podRange = spawnArgs.GetFloat("podRange", "250"); + dripCount = 0; + nextWoundTime = 0; + + bindController = static_cast( gameLocal.SpawnClientObject(spawnArgs.GetString("def_bindController"), NULL) ); + HH_ASSERT( bindController.IsValid() ); + bindController->fl.networkSync = false; + + float yawLimit = spawnArgs.GetFloat("yawlimit", "180"); + const char *handName = spawnArgs.GetString("def_tractorhand"); + const char *animName = spawnArgs.GetString("boundanim"); + + bindController->SetRiderParameters(animName, handName, yawLimit, 0.0f); + bindController->Bind(this, true); + bindController->SetOrigin(spawnArgs.GetVector("grab_offset")); + bindController->fl.neverDormant = true; +} + +// +// Ticker() +// +void hhGasbagSimple::Ticker(void) { + for (int i = 0; i < podList.Num(); i++) { + if (!podList[i].IsValid()) { + podList.RemoveIndex(i); + i--; // This index is gone, so don't increment + continue; + } + } + + // Update the podcount + AI_PODCOUNT = podList.Num(); + + if (podList.Num() == 0) { + BecomeInactive(TH_TICKER); + } +} + +// +// Save() +// +void hhGasbagSimple::Save(idSaveGame *savefile) const { + savefile->WriteInt(dripCount); + + savefile->WriteInt(podList.Num()); + for (int i = 0; i < podList.Num(); i++) { + podList[i].Save(savefile); + } + bindController.Save(savefile); +} + +// +// Restore() +// +void hhGasbagSimple::Restore(idRestoreGame *savefile) { + Spawn(); + savefile->ReadInt(dripCount); + + int num; + savefile->ReadInt(num); + podList.SetNum(num); + for (int i = 0; i < num; i++) { + podList[i].Restore(savefile); + } + bindController.Restore(savefile); + + nextWoundTime = 0; +} + +// +// Event_AcidBlast() +// +void hhGasbagSimple::Event_AcidBlast(void) { + if (health <= 0) { // Don't launch a pod if we're dead + return; + } + + if (!enemy.IsValid()) { + return; + } + + idEntity *target = enemy.GetEntity(); + idVec3 targetOrigin = target->GetOrigin(); + float len, nearest = podRange; + + // See if we have a pod near the enemy. If we do, target it instead. + idEntity *entList[100]; + int num = gameLocal.EntitiesWithinRadius(targetOrigin, podRange, entList, 100); + for (int i = 0; i < num; i++) { + if (!entList[i]->IsType(hhPod::Type)) { + continue; + } + + len = (entList[i]->GetOrigin() - targetOrigin).Length(); + + // Always target the pod nearest to the enemy + if (len < nearest) { + target = entList[i]; + nearest = len; + } + } + + // Spawn a "muzzle flash"-like fx + hhFxInfo fx; + fx.SetEntity(this); + fx.RemoveWhenDone(true); + SpawnFxLocal(spawnArgs.GetString("fx_fire"), GetOrigin() + idVec3(0, 0, -400), enemy->GetPhysics()->GetGravityNormal().ToMat3(), &fx); + + // Spawn an acid blob in the player's general direction + const idDict *projDef = gameLocal.FindEntityDefDict(spawnArgs.GetString("def_projectile"), false); + LaunchProjectile(spawnArgs.GetString("acidbone", "LmbRt"), target, false, projDef); + + PostEventMS(&EV_SpawnBlastDebris, 100 + gameLocal.random.RandomInt(100)); + + dripCount = gameLocal.random.RandomInt(3) + 1; + PostEventMS(&EV_AcidDrip, 200 + gameLocal.random.RandomInt(100)); // Drip acid after firing +} + +// +// Event_SpawnBlastDebris() +// +void hhGasbagSimple::Event_SpawnBlastDebris(void) { + idDict args; + args.Clear(); + args.Set("origin", (GetPhysics()->GetOrigin() + spawnArgs.GetVector("blast_debris_offset", "0 0 128")).ToString()); + gameLocal.SpawnObject("debrisSpawner_gasbag_fire", &args); +} + +// +// Event_AcidDrip() +// +void hhGasbagSimple::Event_AcidDrip(void) { + assert(dripCount > 0); + dripCount--; + + idVec3 target = GetPhysics()->GetOrigin(); + target.z -= 128; + + idVec3 rndVec = hhUtils::RandomVector() * gameLocal.random.RandomInt(20); + const idDict *projDef = gameLocal.FindEntityDefDict(spawnArgs.GetString("def_drip_projectile", "projectile_acidspray_gasbag"), false); + LaunchProjectileAtVec(spawnArgs.GetString("acidbone", "LmbRt"), target + rndVec, false, projDef); + + if (dripCount > 0) { + PostEventMS(&EV_AcidDrip, 200 + gameLocal.random.RandomInt(100)); // Fire again in 200-300ms + } +} + +// +// Event_DeathCloud() +// +void hhGasbagSimple::Event_DeathCloud(void) { + const char *fx = spawnArgs.GetString("fx_death"); + if (!fx || !fx[0]) { + return; + } + hhFxInfo fxInfo; + + fxInfo.RemoveWhenDone(true); + BroadcastFxInfo(fx, GetOrigin(), GetAxis(), &fxInfo); +} + +// +// Event_LaunchPod() +// +void hhGasbagSimple::Event_LaunchPod() { + if (health <= 0) { // Don't launch a pod if we're dying + return; + } + + idVec3 target, origin, offset = vec3_origin; + if (enemy.IsValid()) { + // Target a random area around the enemy + origin = enemy->GetEyePosition(); + if (gameLocal.random.RandomInt(100) > 30) { // Possibly target the enemy exactly + offset.x = (gameLocal.random.RandomFloat() * 100.0f) - 50.0f; + offset.y = (gameLocal.random.RandomFloat() * 100.0f) - 50.0f; + } + } else { + // Target a random area beneath us + origin = GetOrigin(); + offset.x = gameLocal.random.RandomFloat() * 10.0f; + offset.y = gameLocal.random.RandomFloat() * 10.0f; + offset.z = -10.0f; + } + target = origin + offset; + + const idDict *projDef = gameLocal.FindEntityDefDict(spawnArgs.GetString("def_pod_projectile", "projectile_gasbag_pod"), false); + LaunchProjectileAtVec(spawnArgs.GetString("acidbone", "LmbRt"), target, false, projDef); +} + +// +// Event_NewPod +// +void hhGasbagSimple::Event_NewPod(hhPod *pod) { + HH_ASSERT(pod && pod->IsType(hhPod::Type)); + + podList.Append(idEntityPtr(pod)); + + // Update the podcount + AI_PODCOUNT = podList.Num(); + + BecomeActive(TH_TICKER); +} + +// +// Killed +// +void hhGasbagSimple::Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location) { + bool wasDead = (AI_DEAD != 0); + + hhMonsterAI::Killed(inflictor, attacker, damage, dir, location); + + if (wasDead) { + return; + } + + CancelEvents(&EV_AcidDrip); + CancelEvents(&EV_AcidBlast); + CancelEvents(&EV_LaunchPod); + dripCount = 0; + + // Release any grabbed enemy + idEntity *rider; + if ((rider = bindController->GetRider()) != NULL) { + bindController->Detach(); + if (rider->IsType(hhPlayer::Type)) { + rider->ProcessEvent(&EV_Unfreeze); + } + } + + int deathAnim = GetAnimator()->GetAnim("death"); + GetAnimator()->PlayAnim(ANIMCHANNEL_TORSO, deathAnim, gameLocal.time, 300.0f); + int ttl = GetAnimator()->AnimLength(deathAnim); + // Freeze + physicsObj.EnableGravity(false); + physicsObj.PutToRest(); + + SetSkinByName( spawnArgs.GetString( "skin_death" ) ); + SetDeformation(DEFORMTYPE_DEATHEFFECT, gameLocal.time + 2500, 8000); // starttime, duration + PostEventSec( &EV_StartSound, 1.5f, "snd_acid", SND_CHANNEL_ANY, 1 ); + + // Spawn gas cloud + PostEventMS(&EV_DeathCloud, ttl); + PostEventMS(&EV_Dispose, 0); +} + +// +// LaunchProjectile +// +idProjectile *hhGasbagSimple::LaunchProjectileAtVec( const char *jointname, const idVec3 &target, bool clampToAttackCone, const idDict* desiredProjectileDef ) { //HUMANHEAD mdc - added desiredProjectileDef for supporting multiple projs. + idVec3 muzzle; + idVec3 dir; + idVec3 start; + trace_t tr; + idBounds projBounds; + float distance; + const idClipModel *projClip; + float attack_accuracy; + float attack_cone; + float projectile_spread; + float diff; + float angle; + float spin; + idAngles ang; + int num_projectiles; + int i; + idMat3 axis; + idVec3 tmp; + idProjectile *lastProjectile; + + //HUMANHEAD mdc - added to support multiple projectiles + if( desiredProjectileDef ) { //try to set our projectile to the desiredProjectile + int projIndex = FindProjectileInfo( desiredProjectileDef ); + if( projIndex >= 0 ) { + SetCurrentProjectile( projIndex ); + } + } + //HUMANHEAD END + + + if ( !projectileDef ) { + gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() ); + return NULL; + } + + attack_accuracy = spawnArgs.GetFloat( "attack_accuracy", "7" ); + attack_cone = spawnArgs.GetFloat( "attack_cone", "70" ); + projectile_spread = spawnArgs.GetFloat( "projectile_spread", "0" ); + num_projectiles = spawnArgs.GetInt( "num_projectiles", "1" ); + + GetMuzzle( jointname, muzzle, axis ); + + if ( !projectile.GetEntity() ) { + CreateProjectile( muzzle, axis[ 0 ] ); + } + + lastProjectile = projectile.GetEntity(); + + tmp = target - muzzle; + tmp.Normalize(); + axis = tmp.ToMat3(); + + // rotate it because the cone points up by default + tmp = axis[2]; + axis[2] = axis[0]; + axis[0] = -tmp; + + // make sure the projectile starts inside the monster bounding box + const idBounds &ownerBounds = physicsObj.GetAbsBounds(); + projClip = lastProjectile->GetPhysics()->GetClipModel(); + projBounds = projClip->GetBounds().Rotate( axis ); + + // 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( muzzle, viewAxis[ 0 ], distance ) ) { + start = muzzle + 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(); + } + + gameLocal.clip.Translation( tr, start, muzzle, projClip, axis, MASK_SHOT_RENDERMODEL, this ); + muzzle = tr.endpos; + + // set aiming direction + //GetAimDir( muzzle, target, this, dir ); + dir = target - muzzle; + ang = dir.ToAngles(); + + // adjust his aim so it's not perfect. uses sine based movement so the tracers appear less random in their spread. + float t = MS2SEC( gameLocal.time + entityNumber * 497 ); + ang.pitch += idMath::Sin16( t * 5.1 ) * attack_accuracy; + ang.yaw += idMath::Sin16( t * 6.7 ) * attack_accuracy; + + if ( clampToAttackCone ) { + // 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, current_yaw ); + if ( diff > attack_cone ) { + ang.yaw = current_yaw + attack_cone; + } else if ( diff < -attack_cone ) { + ang.yaw = current_yaw - attack_cone; + } + } + + axis = ang.ToMat3(); + + float spreadRad = DEG2RAD( projectile_spread ); + for( i = 0; i < num_projectiles; i++ ) { + // spread the projectiles out + angle = idMath::Sin( spreadRad * 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(); + + // launch the projectile + if ( !projectile.GetEntity() ) { + CreateProjectile( muzzle, dir ); + } + lastProjectile = projectile.GetEntity(); + lastProjectile->Launch( muzzle, dir, vec3_origin ); + projectile = NULL; + } + + TriggerWeaponEffects( muzzle, axis ); + + lastAttackTime = gameLocal.time; + +//HUMANHEAD mdc - added to support multiple projectiles + projectile = NULL; + SetCurrentProjectile( projectileDefaultDefIndex ); //set back to our default projectile to be on the safe side +//HUMANHEAD END + + return lastProjectile; +} + +// +// LaunchProjectile +// +idProjectile *hhGasbagSimple::LaunchProjectileAtVec( const idVec3 &startOrigin, const idMat3 &startAxis, const idVec3 &target, bool clampToAttackCone, const idDict* desiredProjectileDef ) { //HUMANHEAD mdc - added desiredProjectileDef for supporting multiple projs. + idVec3 muzzle; + idVec3 dir; + idVec3 start; + trace_t tr; + idBounds projBounds; + float distance; + const idClipModel *projClip; + //float attack_accuracy; + //float attack_cone; + float projectile_spread; + float angle; + float spin; + idAngles ang; + int num_projectiles; + int i; + idMat3 axis; + idVec3 tmp; + idProjectile *lastProjectile; + + //HUMANHEAD mdc - added to support multiple projectiles + if( desiredProjectileDef ) { //try to set our projectile to the desiredProjectile + int projIndex = FindProjectileInfo( desiredProjectileDef ); + if( projIndex >= 0 ) { + SetCurrentProjectile( projIndex ); + } + } + //HUMANHEAD END + + + if ( !projectileDef ) { + gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() ); + return NULL; + } + + //attack_accuracy = spawnArgs.GetFloat( "attack_accuracy", "7" ); + //attack_cone = spawnArgs.GetFloat( "attack_cone", "70" ); + projectile_spread = spawnArgs.GetFloat( "projectile_spread", "0" ); + num_projectiles = spawnArgs.GetInt( "num_projectiles", "1" ); + + muzzle = startOrigin; + axis = startAxis; + + if ( !projectile.GetEntity() ) { + CreateProjectile( muzzle, axis[ 0 ] ); + } + + lastProjectile = projectile.GetEntity(); + + // make sure the projectile starts inside the monster bounding box + const idBounds &ownerBounds = physicsObj.GetAbsBounds(); + projClip = lastProjectile->GetPhysics()->GetClipModel(); + projBounds = projClip->GetBounds().Rotate( axis ); + + // 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( muzzle, viewAxis[ 0 ], distance ) ) { + start = muzzle + 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(); + } + + gameLocal.clip.Translation( tr, start, muzzle, projClip, axis, MASK_SHOT_RENDERMODEL, this ); + muzzle = tr.endpos; + + float spreadRad = DEG2RAD( projectile_spread ); + for( i = 0; i < num_projectiles; i++ ) { + // spread the projectiles out + angle = idMath::Sin( spreadRad * 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(); + + // launch the projectile + if ( !projectile.GetEntity() ) { + CreateProjectile( muzzle, dir ); + } + lastProjectile = projectile.GetEntity(); + lastProjectile->Launch( muzzle, dir, vec3_origin ); + projectile = NULL; + } + + TriggerWeaponEffects( muzzle, axis ); + + lastAttackTime = gameLocal.time; + +//HUMANHEAD mdc - added to support multiple projectiles + projectile = NULL; + SetCurrentProjectile( projectileDefaultDefIndex ); //set back to our default projectile to be on the safe side +//HUMANHEAD END + + return lastProjectile; +} + +#define LinkScriptVariable( name ) name.LinkTo( scriptObject, #name ) +void hhGasbagSimple::LinkScriptVariables(void) { + hhMonsterAI::LinkScriptVariables(); + + LinkScriptVariable(AI_PODCOUNT); + LinkScriptVariable(AI_CHARGEDONE); + LinkScriptVariable(AI_SWOOP); + LinkScriptVariable(AI_DODGEDAMAGE); +} + +// +// Event_GrabEnemy() +// +void hhGasbagSimple::Event_GrabEnemy(void) { + idThread::ReturnFloat( GrabEnemy() ); +} + +// +// GrabEnemy() +// +bool hhGasbagSimple::GrabEnemy(void) { + if (!enemy.IsValid() || enemy->IsBound() || bindController->GetRider()) { + return false; + } + + // Don't grab Talon + if (enemy->IsType(hhTalon::Type)) { + return false; + } + + if (enemy->IsType(hhPlayer::Type)) { + hhPlayer *player = reinterpret_cast (enemy.GetEntity()); + if (player->IsSpiritOrDeathwalking() || player->InGravityZone()) { // Refuse to grab spirit players, or players affected by a gravity zone + return false; + } + player->Freeze(); + } + + bindController->Attach(enemy.GetEntity()); + enemy->SetOrigin(vec3_origin); + return true; +} + +// +// Event_BiteEnemy() +// +void hhGasbagSimple::Event_BiteEnemy(void) { + if (!enemy.IsValid() || bindController->GetRider() != enemy.GetEntity()) { + return; + } + + if (enemy->IsType(hhSpiritProxy::Type)) { + float diff = gameLocal.GetTime() - reinterpret_cast (enemy.GetEntity())->GetActivationTime(); + if (diff < 1150.0f) { + // Don't allow the spirit proxy to ignore damage + scriptThread->WaitMS(diff + 250.0f); + CancelEvents(&EV_BiteEnemy); + PostEventMS(&EV_BiteEnemy, diff + 100.0f); + return; + } + } + + // Direct damage + bindController->Detach(); + enemy->Damage(this, this, vec3_origin, spawnArgs.GetString("def_damage_bite"), 1.0f, INVALID_JOINT); +} + +// +// Damage() +// +void hhGasbagSimple::Damage(idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location) { + int curHealth = health; + idEntity *enemyEnt = enemy.GetEntity(); + hhMonsterAI::Damage(inflictor, attacker, dir, damageDefName, damageScale, location); + + if (health < curHealth) { + if (gameLocal.time > nextWoundTime && location != INVALID_JOINT) { + hhFxInfo fx; + fx.SetEntity(this); + fx.RemoveWhenDone(true); + SpawnFxLocal(spawnArgs.GetString("fx_damage"), inflictor->GetOrigin(), dir.ToMat3(), &fx); + + nextWoundTime = gameLocal.time + 250; + } + + // Only release enemy if we actually took damage + idEntity *rider; + if ((rider = bindController->GetRider()) != NULL) { + bindController->Detach(); + if (rider->IsType(hhPlayer::Type)) { + rider->PostEventMS(&EV_Unfreeze, 0); + } + } + + AI_DODGEDAMAGE = AI_DODGEDAMAGE + (curHealth - health); + } +} + +// +// Event_DirectMoveToPosition() +// +void hhGasbagSimple::Event_DirectMoveToPosition(const idVec3 &pos) { + StopMove(MOVE_STATUS_DONE); + DirectMoveToPosition(pos); +} + +// +// Event_ChargeEnemy +// +void hhGasbagSimple::Event_ChargeEnemy(void) { + AI_CHARGEDONE = true; + StopMove(MOVE_STATUS_DEST_NOT_FOUND); + if (!enemy.IsValid() || enemy->IsBound()) { + return; + } + + idVec3 enemyOrg; + + fly_offset = 0.0f; + + // position destination so that we're in the enemy's view + enemyOrg = enemy->GetEyePosition(); + enemyOrg -= enemy->GetPhysics()->GetGravityNormal() * fly_offset; + + DirectMoveToPosition(enemyOrg); + HH_ASSERT(move.bEnemyBlocks == false); +} + +void hhGasbagSimple::Event_EnemyIsSpirit( hhPlayer *player, hhSpiritProxy *proxy ) { + hhMonsterAI::Event_EnemyIsSpirit(player, proxy); + if (bindController->GetRider() == player) { + bindController->Detach(); + player->Unfreeze(); + GrabEnemy(); + } +} + +void hhGasbagSimple::Event_EnemyIsPhysical( hhPlayer *player, hhSpiritProxy *proxy ) { + hhMonsterAI::Event_EnemyIsPhysical(player, proxy); + if (bindController->GetRider() == player) { + player->Freeze(); + } +} + +// +// Event_BindUnfroze +// +void hhGasbagSimple::Event_BindUnfroze(idEntity *unfrozenBind) { + if (!enemy.IsValid() || unfrozenBind != enemy.GetEntity() || bindController->GetRider() != enemy.GetEntity()) { + gameLocal.Warning("Gasbag received BindUnfroze event but enemy is not bound to it.\n"); + return; + } + + bindController->Detach(); +} + +// +// FlyTurn +// +void hhGasbagSimple::FlyTurn(void) { + if (!AI_SWOOP && (AI_ENEMY_VISIBLE || move.moveCommand == MOVE_FACE_ENEMY)) { + TurnToward( lastVisibleEnemyPos ); + } else if ((move.moveCommand == MOVE_FACE_ENTITY) && move.goalEntity.GetEntity()) { + TurnToward(move.goalEntity.GetEntity()->GetPhysics()->GetOrigin()); + } else if (move.speed > 0.1f) { + const idVec3 &vel = physicsObj.GetLinearVelocity(); + if (vel.ToVec2().LengthSqr() > 0.1f) { + TurnToward(vel.ToYaw()); + } + } + Turn(); +} + +// +// Event_GrabCheck() +// +void hhGasbagSimple::Event_GrabCheck(void) { + idThread::ReturnInt(enemy.IsValid() && enemy->IsBoundTo(this)); +} + + +void hhGasbagSimple::Event_MoveToGrabPosition(void) { + StopMove(MOVE_STATUS_DEST_NOT_FOUND); + if (!enemy.IsValid() || enemy->IsBound()) { + return; + } + + fly_offset = 0.0f; + + // position destination so that we're right on the enemy when we grab him + DirectMoveToPosition(enemy->GetOrigin() - spawnArgs.GetVector( "grab_offset" )); + HH_ASSERT(move.bEnemyBlocks == false); +} + +void hhGasbagSimple::SetEnemy(idActor *newEnemy) { + idEntity *rider; + if ((rider = bindController->GetRider())) { // This is usually caused by Talon + if (rider->IsType(hhPlayer::Type)) { + rider->PostEventMS(&EV_Unfreeze, 0); + } + bindController->Detach(); + } + + hhMonsterAI::SetEnemy(newEnemy); +} + +void hhGasbagSimple::Event_CheckRange(void) { + if (!enemy.IsValid()) { + idThread::ReturnInt(0); + return; + } + + int retval; + float dist = ( enemy->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ).Length(); + + if (enemy->IsType(hhPlayer::Type)) { + float min = spawnArgs.GetFloat( "dda_range_min" ); + float max = spawnArgs.GetFloat( "dda_range_max" ); + float player_dist = min + (max - min) * gameLocal.GetDDAValue(); + + retval = (int) (dist <= player_dist); + } else { + retval = (int) (dist <= 100.0f); + } + + idThread::ReturnInt(retval); +} + +void hhGasbagSimple::Gib( const idVec3 &dir, const char *damageDefName ) { + // Bypass idActor::Gib() + idAFEntity_Gibbable::Gib( dir, damageDefName ); +} + +int hhGasbagSimple::ReactionTo( const idEntity *ent ) { + //gasbags skip the spirit stuff in hhMonsterAI::ReactionTo() + if ( bNoCombat ) { + return ATTACK_IGNORE; + } + const idActor *actor = static_cast( ent ); + if( actor && actor->IsType(hhDeathProxy::Type) ) { + return ATTACK_IGNORE; + } + if ( ent->IsType( hhMonsterAI::Type ) ) { + const hhMonsterAI *entAI = static_cast( ent ); + if ( entAI && entAI->bNeverTarget ) { + return ATTACK_IGNORE; + } + } + + return idAI::ReactionTo( ent ); +} + +void hhGasbagSimple::Event_EnemyRangeZ( void ) { + idThread::ReturnFloat( lastVisibleEnemyPos.z - GetOrigin().z ); +} +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/src/Prey/ai_gasbag_simple.h b/src/Prey/ai_gasbag_simple.h new file mode 100644 index 0000000..6c5ffeb --- /dev/null +++ b/src/Prey/ai_gasbag_simple.h @@ -0,0 +1,90 @@ +#ifndef __PREY_AI_GASBAG_SIMPLE_H__ +#define __PREY_AI_GASBAG_SIMPLE_H__ + +extern const idEventDef EV_NewPod; + +class hhPod; + +class hhGasbagSimple : public hhMonsterAI { +public: + CLASS_PROTOTYPE(hhGasbagSimple); + +#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + void Event_AcidBlast(void) {}; + void Event_AcidDrip(void) {}; + void Event_DeathCloud(void) {}; + void Event_NewPod(hhPod *pod) {}; + void Event_LaunchPod(void) {}; + void Event_SpawnBlastDebris(void) {}; + void Event_ChargeEnemy(void) {}; + void Event_GrabEnemy(void) {}; + void Event_BiteEnemy(void) {}; + void Event_DirectMoveToPosition(const idVec3 &pos) {}; + void Event_BindUnfroze(idEntity *unfrozenBind) {}; + virtual void Event_EnemyIsSpirit( hhPlayer *player, hhSpiritProxy *proxy ) {}; + virtual void Event_EnemyIsPhysical( hhPlayer *player, hhSpiritProxy *proxy ) {}; + void Event_GrabCheck(void) {}; + void Event_MoveToGrabPosition(void) {}; + void Event_CheckRange(void) {}; + void Event_EnemyRangeZ(void) {}; +#else + virtual ~hhGasbagSimple(void); + + void Spawn(void); + void Save(idSaveGame *savefile) const; + void Restore(idRestoreGame *savefile); + + virtual void FlyTurn(void); + + virtual void Gib(const idVec3 &dir, const char *damageDefName); + int ReactionTo( const idEntity *ent ); + +protected: + void Event_AcidBlast(void); + void Event_AcidDrip(void); + void Event_DeathCloud(void); + void Event_NewPod(hhPod *pod); + void Event_LaunchPod(void); + void Event_SpawnBlastDebris(void); + void Event_ChargeEnemy(void); + void Event_GrabEnemy(void); + void Event_BiteEnemy(void); + void Event_DirectMoveToPosition(const idVec3 &pos); + void Event_BindUnfroze(idEntity *unfrozenBind); + virtual void Event_EnemyIsSpirit( hhPlayer *player, hhSpiritProxy *proxy ); + virtual void Event_EnemyIsPhysical( hhPlayer *player, hhSpiritProxy *proxy ); + void Event_GrabCheck(void); + void Event_MoveToGrabPosition(void); + void Event_CheckRange(void); + void Event_EnemyRangeZ(void); + + bool GrabEnemy(void); + + virtual void LinkScriptVariables(void); + + virtual void Ticker(void); + virtual idProjectile *LaunchProjectileAtVec(const char *jointname, const idVec3 &target, bool clampToAttackCone, const idDict* desiredProjectileDef); + virtual idProjectile *LaunchProjectileAtVec(const idVec3 &startOrigin, const idMat3 &startAxis, const idVec3 &target, bool clampToAttackCone, const idDict* desiredProjectileDef); + virtual void Killed(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); + virtual void SetEnemy(idActor *newEnemy); + + idVec3 podOffset; + float podRange; + int dripCount; + + idList< idEntityPtr > podList; + + idScriptInt AI_PODCOUNT; + idScriptBool AI_CHARGEDONE; + idScriptBool AI_SWOOP; + idScriptInt AI_DODGEDAMAGE; + + int nextWoundTime; + + idEntityPtr bindController; // Bind controller for tractor beam +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +}; + +#endif + diff --git a/src/Prey/ai_harvester_simple.cpp b/src/Prey/ai_harvester_simple.cpp new file mode 100644 index 0000000..941d4de --- /dev/null +++ b/src/Prey/ai_harvester_simple.cpp @@ -0,0 +1,838 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef MA_AntiProjectileAttack("", "e"); +const idEventDef MA_StartPreDeath("" ); +const idEventDef MA_HandlePassageway("", NULL); +const idEventDef MA_EnterPassageway("", "e",NULL); +const idEventDef MA_ExitPassageway("", "e",NULL); +const idEventDef MA_UseThisPassageway("useThisPassageway", "ed",NULL); +const idEventDef MA_GibOnDeath("gibOnDeath", "d"); + +CLASS_DECLARATION( hhMonsterAI, hhHarvesterSimple ) + EVENT( MA_OnProjectileLaunch, hhHarvesterSimple::Event_OnProjectileLaunch ) + EVENT( MA_AntiProjectileAttack, hhHarvesterSimple::Event_AntiProjectileAttack ) + EVENT( MA_StartPreDeath, hhHarvesterSimple::Event_StartPreDeath ) + EVENT( MA_HandlePassageway, hhHarvesterSimple::Event_HandlePassageway ) + EVENT( MA_EnterPassageway, hhHarvesterSimple::Event_EnterPassageway ) + EVENT( MA_ExitPassageway, hhHarvesterSimple::Event_ExitPassageway ) + EVENT( MA_UseThisPassageway, hhHarvesterSimple::Event_UseThisPassageway ) + EVENT( EV_Broadcast_AppendFxToList, hhHarvesterSimple::Event_AppendFxToList ) + EVENT( MA_GibOnDeath, hhHarvesterSimple::Event_GibOnDeath ) +END_CLASS + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +// +// Spawn() +// +void hhHarvesterSimple::Spawn(void) { + allowPreDeath = true; + lastAntiProjectileAttack = 0; + lastPassagewayTime = 0; + passageCount = spawnArgs.GetInt("passage_count", "0"); // Used for passing to and from torso + bSmokes = spawnArgs.GetBool("smokes", "0"); + bGibOnDeath = false; +} + +// +// Event_OnProjectileLaunch() +// +void hhHarvesterSimple::Event_OnProjectileLaunch(hhProjectile *proj) { + + // Can't launch again yet + float min = spawnArgs.GetFloat( "dda_delay_min" ); + float max = spawnArgs.GetFloat( "dda_delay_max" ); + float delay = min + (max - min) * (1.0f - gameLocal.GetDDAValue()); + + if(gameLocal.GetTime() - lastAntiProjectileAttack < delay) + return; + + // The person who launched this projectile wasn't someone to worry about + if(proj->GetOwner() && !(ReactionTo(proj->GetOwner()) & (ATTACK_ON_SIGHT | ATTACK_ON_DAMAGE))) + return; + + // TODO: more intelligent checks for if we should launch the anti-projectile chaff + idVec3 fw = viewAxis[0]; + idVec3 projFw = proj->GetAxis()[0]; + if(proj->GetOwner()) + projFw = proj->GetOwner()->GetAxis()[0]; + float dot = fw * projFw; + if(dot > -.7f) + return; + + ProcessEvent(&MA_AntiProjectileAttack, proj); +} + + +// +// Event_AntiProjectileAttack() +// +void hhHarvesterSimple::Event_AntiProjectileAttack(hhProjectile *proj) { + + if(!spawnArgs.GetBool("block_projectiles", "1")) { + return; + } + + const idDict *projectileDef = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_chaff") ); + if ( !projectileDef ) { + gameLocal.Error( "Unknown def_chaff: %s\n", spawnArgs.GetString("def_chaff") ); + } + hhProjectile* projectile = NULL; + + //bjk: new parms + int numProj = spawnArgs.GetInt("shieldNumProj", "10"); + float spread = DEG2RAD( spawnArgs.GetFloat("shieldSpread", "10") ); + float yaw = DEG2RAD( spawnArgs.GetFloat("shieldYawSpread", "10") ); + + //StartSound("snd_fire_chaff", SND_CHANNEL_ANY, 0, true, NULL); + torsoAnim.PlayAnim( GetAnimator()->GetAnim( "antiprojectile_attack" ) ); + + idVec3 dir = proj->GetOrigin() - GetOrigin(); + dir.Normalize(); + dir = dir+idVec3(0.f, 0.f, -.3f); // bjk: bias towards ground + dir.Normalize(); + idMat3 muzzleAxis = dir.ToMat3(); + // todo: read this in from a bone + idVec3 launchPos = GetOrigin() + idVec3(0.0f, 0.0f, 64.0f); + + for(int i=0; iCreate(this, launchPos, dir); + projectile->Launch(launchPos, dir, idVec3(0.0f, 0.0f, 0.0f)); + } + + lastAntiProjectileAttack = gameLocal.GetTime(); +} + +bool hhHarvesterSimple::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + int preDeathThresh = spawnArgs.GetInt("predeath_thresh", "-1"); + if(allowPreDeath && !bGibOnDeath && preDeathThresh > 0 && health < preDeathThresh) { + allowPreDeath = false; // We've tried, cannot try anymore + float preDeathFreq = spawnArgs.GetFloat("predeath_freq", "1"); + if(gameLocal.random.RandomFloat() < preDeathFreq) { + ProcessEvent(&MA_StartPreDeath); + } + } + + if ( bSmokes ) { + if ( !fxSmoke[0].IsValid() && health <= AI_PASSAGEWAY_HEALTH ) { + SpawnSmoke(); + } else if (fxSmoke[0].IsValid() && health > AI_PASSAGEWAY_HEALTH) { + ClearSmoke(); + } + } + + return( idAI::Pain(inflictor, attacker, damage, dir, location) ); +} + +void hhHarvesterSimple::Event_StartPreDeath(void) { + int i; + + // No torso? Just kill myself ..... + if(!spawnArgs.GetString("def_torso")) { + ProcessEvent(&AI_Kill); + return; + } + + // Spawn death fx, if any + const char *fx = spawnArgs.GetString("fx_death"); + if (fx && fx[0]) { + hhFxInfo fxInfo; + + fxInfo.RemoveWhenDone(true); + BroadcastFxInfo(fx, GetOrigin(), GetAxis(), &fxInfo); + } + + // do blood splats + float size = spawnArgs.GetFloat("decal_torso_pop_size","96"); + + // copy my target keys to the keys that will spawn the torso + idDict dict; + const idDict *torsoDict = gameLocal.FindEntityDefDict(spawnArgs.GetString("def_torso")); + if ( !torsoDict ) { + gameLocal.Error("Unknown def_torso: %s\n", spawnArgs.GetString("def_torso")); + } + dict.Copy(*torsoDict); + for(i=0;i 0) { + sprintf(key, "target%i", i); + } + + dict.Set((const char*)key, (const char*)targets[i].GetEntity()->name); + } + + // Passageway variables + dict.SetInt("passage_health", AI_PASSAGEWAY_HEALTH); + dict.SetInt("passage_count", passageCount); + dict.SetVector("origin", GetOrigin()); + dict.SetMatrix("rotation", GetAxis()); + + // remove my target keys (so they don't get fired) + targets.Clear(); + + // Spawn the torso + idEntity *e; + hhMonsterAI *aiTorso; + if(!gameLocal.SpawnEntityDef(dict, &e)) + gameLocal.Error("Failed to spawn ai torso."); + HH_ASSERT(e && e->IsType(hhMonsterAI::Type)); + aiTorso = static_cast(e); + HH_ASSERT(aiTorso != NULL); + + // Throw gibs + idStr debrisDef = spawnArgs.GetString("def_gibDebrisSpawnerPreDeath"); + if(debrisDef.Length() > 0) { + hhUtils::SpawnDebrisMass(debrisDef.c_str(), this, 1.0f); + } + + health = 0; + + // remove myself + Hide(); + PostEventMS(&EV_Remove, 3000); +} + +void hhHarvesterSimple::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + if ( attacker == this ) { + return; + } + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); + if ( !AI_DEAD ) { + if ( damageDef && damageDef->GetBool( "ice" ) && spawnArgs.GetBool( "can_freeze", "0" ) ) { + spawnArgs.Set( "bAlwaysGib", "0" ); + allowPreDeath = false; + } else { + allowPreDeath = true; + } + if ( damageDef && damageDef->GetBool( "no_special_death" ) ) { + allowPreDeath = false; + } + } + hhMonsterAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} + +int hhHarvesterSimple::EvaluateReaction( const hhReaction *react ) { + // Let the script limit the distance of Climb reactions we consider + if ( react->desc->effect == hhReactionDesc::Effect_Climb && AI_CLIMB_RANGE >= 0.0f && ( react->causeEntity->GetOrigin() - GetOrigin()).LengthSqr() > AI_CLIMB_RANGE ) { + return 0; + } + + // If we're supposed to use a specific passageway, only accept it and reject the others + if ( nextPassageway.IsValid() && react->desc->effect == hhReactionDesc::Effect_Passageway ) { + if (react->causeEntity.GetEntity() == nextPassageway.GetEntity()) { + return 100; + } else { + return 0; + } + } + + int rank = hhMonsterAI::EvaluateReaction( react ); + if ( react->desc->effect == hhReactionDesc::Effect_Passageway && + react->causeEntity.GetEntity() == lastPassageway.GetEntity() ) { + if ( lastPassagewayTime + 10000 > gameLocal.time ) { + if (rank > 10) { + // Discourage entering the same passageway we just left + return 10; + } + } else { + // Ignore previous passageway for 10 seconds + return 0; + } + } + return rank; +} + +#define CheckPassageFreqInitial 6000 +void hhHarvesterSimple::Event_EnterPassageway(hhAIPassageway *pn) { + CancelEvents( &MA_OnProjectileLaunch ); // Clear any anti-projectile attacks + CancelEvents( &MA_AntiProjectileAttack ); // Clear any anti-projectile attacks + if (nextPassageway.IsValid()) { + if (nextPassageway.GetEntity() != pn) { + gameLocal.Warning("hhAIPassageway entered was not the specified one!\n"); + } + nextPassageway.Clear(); + } + + if(currPassageway != NULL) { + gameLocal.Error("%s tried to ENTER a passageway but already had a passageway!", (const char*)name); + } + + HH_ASSERT(currPassageway == NULL); + Hide(); // Should already be hidden but just to make sure + + // Only consider passageCount if damaged (ignores scripted passage sequences) or if it's the torso + if (health < spawnHealth || !idStr::Icmp(spawnArgs.GetString("classname"), "monster_harvester_torso") ) { + passageCount++; + if (passageCount > 1) { + //TODO make this affected by the DDA system + AI_PASSAGEWAY_HEALTH = AI_PASSAGEWAY_HEALTH * 0.75f; // Reduce passageway seeking health level to 3/4 of it's original value + } + } + + GetAnimator()->ClearAllAnims(gameLocal.GetTime(), 0); + Event_AnimState(ANIMCHANNEL_LEGS, "Legs_Idle", (int)0); + Event_AnimState(ANIMCHANNEL_TORSO, "Torso_Idle", (int)0); + torsoAnim.UpdateState(); + legsAnim.UpdateState(); + + StartSound("snd_inside_passage", SND_CHANNEL_VOICE, SSF_LOOPING, true, NULL); + + currPassageway = pn; + if(GetPhysics()) + GetPhysics()->SetContents(0); + + // Does this passage way give us health? + int h = 0; + pn->spawnArgs.GetInt("give_health", "0", h); + health += h; + health = idMath::ClampInt(0, spawnArgs.GetInt("health"), health); + + + // Should we transform to another monster when entering? + idStr transTo; + if(spawnArgs.GetString("passage_transform_to", "", transTo)) { + const idDict *def = gameLocal.FindEntityDefDict( transTo ); + if(!def) { + gameLocal.Error("Unknown def : %s", (const char*)transTo); + } + + // copy my target keys to the keys of the transformed-to entity + idDict dict; + dict.Copy(*def); + for(int i=0;i 0) + sprintf(key, "target%i", i); + + dict.Set((const char*)key, (const char*)targets[i].GetEntity()->name); + } + + // Passageway variables + dict.SetInt("passage_health", AI_PASSAGEWAY_HEALTH); + dict.SetInt("passage_count", passageCount); + + idEntity *e = NULL; + gameLocal.SpawnEntityDef(dict, &e); + HH_ASSERT( e->IsType( hhHarvesterSimple::Type ) ); + hhHarvesterSimple *ai = static_cast(e); + ai->SetOrigin(GetOrigin()); + ai->SetAxis(GetAxis()); + ai->Event_EnterPassageway(pn); + targets.Clear(); + PostEventSec(&EV_Remove, 0.1f); + return; + } + + + PostEventMS(&MA_HandlePassageway, CheckPassageFreqInitial); +} + +void hhHarvesterSimple::Event_ExitPassageway(hhAIPassageway *pn) { + if(currPassageway == NULL) { + gameLocal.Error("%s tried to EXIT a passageway but did NOT have a curr passageway!", (const char*)name); + } + + HH_ASSERT(currPassageway != NULL); + + // Store this passageway so we don't jump right back into it. + lastPassageway = idEntityPtr (pn); + lastPassagewayTime = gameLocal.time; + + if(GetPhysics()) + GetPhysics()->SetContents(CONTENTS_BODY); + currPassageway = NULL; + AI_ACTIVATED = true; // Sometimes this is NOT true?!?! + StopSound(SND_CHANNEL_VOICE, true); + + // Teleport to new pos + idAngles angs = pn->GetAxis().ToAngles(); + idVec3 pos = pn->GetExitPos(); + Teleport( pos, angs, pn ); + Show(); + + current_yaw = angs.yaw; + ideal_yaw = angs.yaw; + turnVel = 0.0f; + HH_ASSERT(FacingIdeal()); + + // Exit anim to play? + idStr exitAnim = pn->spawnArgs.GetString("exit_anim"); + if(exitAnim.Length() && GetAnimator()->HasAnim(exitAnim)) { + GetAnimator()->ClearAllAnims(gameLocal.GetTime(), 0); + torsoAnim.UpdateState(); + legsAnim.UpdateState(); + torsoAnim.PlayAnim( GetAnimator()->GetAnim( exitAnim ) ); + legsAnim.PlayAnim( GetAnimator()->GetAnim( exitAnim ) ); + } +} + +// +// Event_HandlePassageway() +// +#define MaxExits 32 +#define CheckPassageFreq 3000 +void hhHarvesterSimple::Event_HandlePassageway(void) { + + if(currPassageway == NULL) { + gameLocal.Error("%s tried to handle a passageway but did NOT have a passageway!", (const char*)name); + } + + HH_ASSERT(currPassageway != NULL); + hhAIPassageway *exitNode = NULL; + + // CATCH 'went inside hole - never came out' bug + if(currPassageway.GetEntity()->lastEntered == this && gameLocal.GetTime() - currPassageway.GetEntity()->timeLastEntered > 30000) { + gameLocal.Warning("%s has been stuck in a passageway for >30 secs, killing it off.", (const char*)name); + fl.takedamage = true; + // Use hhMonsterAI::Damage because our Damage returns if we're the attacker + hhMonsterAI::Damage(this, this, vec3_origin, "damage_gib", 1.0f, INVALID_JOINT); + return; + } + + // No other way out - gotta go out the way we came in + if(currPassageway->targets.Num() <= 0) { + exitNode = currPassageway.GetEntity(); + } + // We can go out another way + else { + hhAIPassageway *possibleExits[MaxExits]; + hhAIPassageway *tmp; + int count = 0; + + bool isRandom; + if (gameLocal.GetTime() - currPassageway.GetEntity()->timeLastEntered > SEC2MS(10)) { + // Try to prevent us from being stuck after 10 seconds + isRandom = true; + } else { + // Choose possibleExits[] randomly if we have no enemy or based on chance. Otherwise choose based on line-of-sight to the player + isRandom = !enemy.IsValid() || gameLocal.random.RandomInt(100) > 50; + } + trace_t tr; + idVec3 toPos; + if (!isRandom) { + toPos = enemy->GetOrigin(); + } + + // We've tried once, now we can use our entrance as an exit + if(gameLocal.GetTime() - currPassageway.GetEntity()->timeLastEntered > CheckPassageFreqInitial + 1000) { + count = 1; + possibleExits[0] = currPassageway.GetEntity(); // We can always go out the same way we came in + } + for(int i=0;itargets.Num();i++) { + if(currPassageway->targets[i] != NULL && currPassageway->targets[i].GetEntity()->IsType(hhAIPassageway::Type)) { + if (isRandom) { + possibleExits[count++] = static_cast(currPassageway->targets[i].GetEntity()); + } else { + tmp = static_cast(currPassageway->targets[i].GetEntity()); + // Choose based on visibility to our enemy + gameLocal.clip.TracePoint( tr, tmp->GetOrigin(), toPos, MASK_SHOT_BOUNDINGBOX, tmp ); + if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == enemy.GetEntity() ) ) { + lastVisibleEnemyPos = toPos; // Can now see enemy + possibleExits[count++] = tmp; + } + } + } + + // We've got enough + if(count >= MaxExits) + break; + } + + if(count == 0) { + exitNode = currPassageway.GetEntity(); + } else if ( enemy.IsValid() && gameLocal.random.RandomInt(100) > 70 ) { + // Find the one closest to the last known enemy pos + if (!exitNode) { + float bestDistance = -1.0f, distSq; + for (int i = 0; i < count; i++) { + distSq = ( possibleExits[i]->GetOrigin() - lastVisibleEnemyPos ).LengthSqr(); + if ( bestDistance == -1.0f || distSq < bestDistance ) { + exitNode = possibleExits[i]; + bestDistance = distSq; + } + } + } + } else { + // Pick a random one + int randExit = gameLocal.random.RandomInt(count); // TODO: Randomness isn't very random! Fix! + HH_ASSERT(randExit >= 0 && randExit < count); + exitNode = possibleExits[randExit]; + } + } + + if(!exitNode) + exitNode = currPassageway.GetEntity(); + + // The one we picked is disabled, so go out where we came in + // TODO: make this time based, try a few more times before going back out + if(!exitNode->IsPassagewayEnabled()) { + exitNode = currPassageway.GetEntity(); + } + + // Special case: The passageway is never meant to be exited from, use it's specified exit point instead + const char *altPassage = exitNode->spawnArgs.GetString("force_passageway", ""); + if (altPassage && altPassage[0]) { + idEntity *node = gameLocal.FindEntity(altPassage); + if (node && node->IsType(hhAIPassageway::Type)) { + exitNode = reinterpret_cast (node); + } + } + + // Make sure no one is blocking where we want to go out + idBounds myBnds = GetPhysics()->GetBounds(); + myBnds.Expand(12.0f); + myBnds.TranslateSelf(exitNode->GetExitPos()); + idEntity *ents[MAX_GENTITIES]; + int count = gameLocal.clip.EntitiesTouchingBounds(myBnds, -1, ents, MAX_GENTITIES); + for(int i=0;iIsHidden() && (ents[i]->IsType(idActor::Type) || ents[i]->IsType(hhMoveable::Type))) { + // Chunk moveables + if ( ents[i]->IsType(idMoveable::Type) ) { + ents[i]->Damage( this, this, vec3_origin, "damage_gib", 1.0f, INVALID_JOINT ); + ents[i]->SquishedByDoor(this); + continue; + } + gameLocal.Warning("Passageway exit blocked.\n"); + PostEventMS(&MA_HandlePassageway, CheckPassageFreq); + if(ai_debugBrain.GetInteger() != 0) { + gameRenderWorld->DebugBounds(colorYellow, myBnds, vec3_origin, 2000); + } + return; + } + } + + // Else we can just exit now - its safe + exitNode->ProcessEvent(&EV_Activate, this); +} + +#define LinkScriptVariable( name ) name.LinkTo(scriptObject, #name) +void hhHarvesterSimple::LinkScriptVariables() { + hhMonsterAI::LinkScriptVariables(); + LinkScriptVariable(AI_CLIMB_RANGE); + LinkScriptVariable(AI_PASSAGEWAY_HEALTH); +} + +void hhHarvesterSimple::Event_UseThisPassageway(hhAIPassageway *pn, bool force) { + nextPassageway.Assign(pn); + + if (force) { // Snap to position + Event_FindReaction("passageway"); + hhReaction *reaction = targetReaction.GetReaction(); + if (!reaction) { + gameLocal.Warning("useThisPassageway could not find a valid reaction!\n"); + } else { + idVec3 tp = GetTouchPos( reaction->causeEntity.GetEntity(), reaction->desc ); + SetOrigin(tp); + } + } +} + +/* +===================== +hhHarvesterSimple::Save +===================== +*/ +void hhHarvesterSimple::Save( idSaveGame *savefile ) const { + savefile->WriteBool( allowPreDeath ); + savefile->WriteInt( lastAntiProjectileAttack ); + savefile->WriteInt( passageCount ); + savefile->WriteBool( bGibOnDeath ); + savefile->WriteInt( lastPassagewayTime ); + + lastPassageway.Save( savefile ); + nextPassageway.Save( savefile ); +} + +/* +===================== +hhHarvesterSimple::Restore +===================== +*/ +void hhHarvesterSimple::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( allowPreDeath ); + savefile->ReadInt( lastAntiProjectileAttack ); + savefile->ReadInt( passageCount ); + savefile->ReadBool( bGibOnDeath ); + savefile->ReadInt( lastPassagewayTime ); + + lastPassageway.Restore( savefile ); + nextPassageway.Restore( savefile ); + + bSmokes = spawnArgs.GetBool("smokes", "0"); +} + +void hhHarvesterSimple::SpawnSmoke(void) { + const char *bones[MAX_HARVESTER_LEGS]; + bones[0] = spawnArgs.GetString( "bone_smoke_front_left" ); + bones[1] = spawnArgs.GetString( "bone_smoke_front_right" ); + bones[2] = spawnArgs.GetString( "bone_smoke_rear_left" ); + bones[3] = spawnArgs.GetString( "bone_smoke_rear_right" ); + + const char *psystem = spawnArgs.GetString( "fx_smoke" ); + if ( psystem && *psystem ) { + idVec3 bonePos; + idMat3 boneAxis; + + for(int i = 0; i < MAX_HARVESTER_LEGS; i++) { + if( fxSmoke[i] != NULL ) { + gameLocal.Warning("Harvester already had smoke particles for leg %i.\n", i); + continue; + } + + hhFxInfo fxInfo; + + this->GetJointWorldTransform( bones[i], bonePos, boneAxis ); + fxInfo.SetEntity( this ); + fxInfo.SetBindBone( bones[i] ); + fxInfo.Toggle(); + fxInfo.RemoveWhenDone( false ); + + BroadcastFxInfo( psystem, bonePos, boneAxis, &fxInfo, &EV_Broadcast_AppendFxToList ); + } + } +} + +void hhHarvesterSimple::ClearSmoke(void) { + for(int i=0; i < MAX_HARVESTER_LEGS; i++) { + if(fxSmoke[i].IsValid()) { + fxSmoke[i]->Nozzle(false); + } + } + + for(int i = 0; i < MAX_HARVESTER_LEGS; i++) { + SAFE_REMOVE(fxSmoke[MAX_HARVESTER_LEGS]); + } +} + +void hhHarvesterSimple::Event_AppendFxToList(hhEntityFx *fx) { + for(int i = 0; i < MAX_HARVESTER_LEGS; ++i) { + if(fxSmoke[i] != NULL) { + continue; + } + + fxSmoke[i] = fx; + return; + } +} + +void hhHarvesterSimple::Event_GibOnDeath(const idList* parmList) { + bGibOnDeath = (atoi((*parmList)[0].c_str()) != 0); +} + +void hhHarvesterSimple::Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location) { + ClearSmoke(); + hhMonsterAI::Killed(inflictor, attacker, damage, dir, location); + + if (bGibOnDeath) { + Gib(vec3_origin, "damage_gib"); + } +} + +void hhHarvesterSimple::Event_Activate(idEntity *activator) { + if (fl.hidden) { + // If we're hidden, we're probably birthing out of passageway. Figure out which one. + hhAIPassageway *nearest = NULL; + float dist, nearDist = idMath::INFINITY; + hhAIPassageway *passage = reinterpret_cast (gameLocal.FindEntityOfType( hhAIPassageway::Type, NULL )); + while (passage) { + dist = (passage->GetOrigin() - GetOrigin()).Length(); + if (dist < nearDist) { + nearDist = dist; + nearest = passage; + } + passage = reinterpret_cast (gameLocal.FindEntityOfType( hhAIPassageway::Type, passage )); + } + + // If we found one near us and it's near enough, mark it as our last passageway + if (nearest && nearDist < 250.0f) { + lastPassageway = nearest; + lastPassagewayTime = gameLocal.time; + } + } + hhMonsterAI::Event_Activate(activator); +} + +void hhHarvesterSimple::AnimMove( void ) { + idVec3 goalPos; + idVec3 delta; + idVec3 goalDelta; + float goalDist; + monsterMoveResult_t moveResult; + idVec3 newDest; + + // Turn on physics to prevent flying harvesters + BecomeActive(TH_PHYSICS); + + idVec3 oldorigin = physicsObj.GetOrigin(); +#ifdef HUMANHEAD //jsh wallwalk + idMat3 oldaxis = GetGravViewAxis(); +#else + idMat3 oldaxis = viewAxis; +#endif + + AI_BLOCKED = false; + + if ( move.moveCommand < NUM_NONMOVING_COMMANDS ){ + move.lastMoveOrigin.Zero(); + move.lastMoveTime = gameLocal.time; + } + + move.obstacle = NULL; + if ( ( move.moveCommand == MOVE_FACE_ENEMY ) && enemy.GetEntity() ) { + TurnToward( lastVisibleEnemyPos ); + goalPos = oldorigin; + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + goalPos = oldorigin; + } else if ( GetMovePos( goalPos ) ) { + if ( move.moveCommand != MOVE_WANDER ) { + CheckObstacleAvoidance( goalPos, newDest ); + TurnToward( newDest ); + } else { + TurnToward( goalPos ); + } + } + + Turn(); + + if ( move.moveCommand == MOVE_SLIDE_TO_POSITION ) { + if ( gameLocal.time < move.startTime + move.duration ) { + goalPos = move.moveDest - move.moveDir * MS2SEC( move.startTime + move.duration - gameLocal.time ); + delta = goalPos - oldorigin; + delta.z = 0.0f; + } else { + delta = move.moveDest - oldorigin; + delta.z = 0.0f; + StopMove( MOVE_STATUS_DONE ); + } + } else if ( allowMove ) { +#ifdef HUMANHEAD //jsh wallwalk + GetMoveDelta( oldaxis, GetGravViewAxis(), delta ); +#else + GetMoveDelta( oldaxis, viewAxis, delta ); +#endif + } else { + delta.Zero(); + } + + if ( move.moveCommand == MOVE_TO_POSITION ) { + goalDelta = move.moveDest - oldorigin; + goalDist = goalDelta.LengthFast(); + if ( goalDist < delta.LengthFast() ) { + delta = goalDelta; + } + } + +#ifdef HUMANHEAD //shrink functionality + float scale = renderEntity.shaderParms[SHADERPARM_ANY_DEFORM_PARM1]; + if ( scale > 0.0f && scale < 2.0f ) { + delta *= scale; + } +#endif + physicsObj.SetDelta( delta ); + physicsObj.ForceDeltaMove( disableGravity ); + + RunPhysics(); + + if ( ai_debugMove.GetBool() ) { + // HUMANHEAD JRM - so we can see if grav is on or off + if(disableGravity) { + gameRenderWorld->DebugLine( colorRed, oldorigin, physicsObj.GetOrigin(), 5000 ); + } else { + gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 5000 ); + } + } + + moveResult = physicsObj.GetMoveResult(); + if ( !af_push_moveables && attack.Length() && TestMelee() ) { + DirectDamage( attack, enemy.GetEntity() ); + } else { + idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); + if ( blockEnt && blockEnt->IsType( hhHarvesterSimple::Type ) ) { + StopMove( MOVE_STATUS_BLOCKED_BY_MONSTER ); + return; + } + if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) { + KickObstacles( viewAxis[ 0 ], kickForce, blockEnt ); + } + } + + BlockedFailSafe(); + + AI_ONGROUND = physicsObj.OnGround(); + + idVec3 org = physicsObj.GetOrigin(); + if ( oldorigin != org ) { + TouchTriggers(); + } + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, gameLocal.msec ); + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec ); + gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true ); + DrawRoute(); + } +} + +void hhHarvesterSimple::Event_CanBecomeSolid( void ) { + int i; + int num; + idEntity * hit; + idClipModel *cm; + idClipModel *clipModels[ MAX_GENTITIES ]; + + num = gameLocal.clip.ClipModelsTouchingBounds( physicsObj.GetAbsBounds(), MASK_MONSTERSOLID, clipModels, MAX_GENTITIES ); + for ( i = 0; i < num; i++ ) { + cm = clipModels[ i ]; + + // don't check render entities + if ( cm->IsRenderModel() ) { + continue; + } + + hit = cm->GetEntity(); + if ( hit == this ) { + continue; + } + + // Special case, crush any moveables blocking our start point + if ( hit->IsType( idMoveable::Type ) ) { + hit->Damage( this, this, vec3_origin, "damage_gib", 1.0f, INVALID_JOINT ); + hit->SquishedByDoor(this); + } + + if ( !hit->fl.takedamage ) { + continue; + } + + if ( physicsObj.ClipContents( cm ) ) { + idThread::ReturnFloat( false ); + return; + } + } + + idThread::ReturnFloat( true ); +} + +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/src/Prey/ai_harvester_simple.h b/src/Prey/ai_harvester_simple.h new file mode 100644 index 0000000..2b06d57 --- /dev/null +++ b/src/Prey/ai_harvester_simple.h @@ -0,0 +1,79 @@ + +#ifndef __PREY_AI_HARVESTERSIMPLE_H__ +#define __PREY_AI_HARVESTERSIMPLE_H__ + +extern const idEventDef AI_OnProjectileLaunch; +extern const idEventDef MA_EnterPassageway; +extern const idEventDef MA_ExitPassageway; + +#define MAX_HARVESTER_LEGS 4 + +class hhHarvesterSimple : public hhMonsterAI { + +public: + CLASS_PROTOTYPE(hhHarvesterSimple); + +#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + virtual void Event_StartPreDeath(void) {}; + void Event_OnProjectileLaunch(hhProjectile *proj) {}; + void Event_AntiProjectileAttack(hhProjectile *proj) {}; + void Event_EnterPassageway(hhAIPassageway *pn) {}; + void Event_ExitPassageway(hhAIPassageway *pn) {}; + void Event_HandlePassageway(void) {}; + void Event_UseThisPassageway(hhAIPassageway *pn, bool force) {}; + void Event_AppendFxToList(hhEntityFx *fx) {}; + void Event_GibOnDeath(const idList* parmList) {}; + virtual void Event_Activate(idEntity *activator) {}; + virtual void Event_CanBecomeSolid(void) {}; +#else + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void LinkScriptVariables(void); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location); + +protected: + + void Spawn(void); + + virtual int EvaluateReaction( const hhReaction *react ); + + void SpawnSmoke(void); + void ClearSmoke(void); + + //predeath torso code + virtual void Event_StartPreDeath(void); + virtual bool Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + bool allowPreDeath; // TRUE if we will allow predeath + + virtual void AnimMove(void); + + //anti-projectile chaff + void Event_OnProjectileLaunch(hhProjectile *proj); + void Event_AntiProjectileAttack(hhProjectile *proj); + void Event_EnterPassageway(hhAIPassageway *pn); + void Event_ExitPassageway(hhAIPassageway *pn); + void Event_HandlePassageway(void); + void Event_UseThisPassageway(hhAIPassageway *pn, bool force); + void Event_AppendFxToList(hhEntityFx *fx); + void Event_GibOnDeath(const idList* parmList); + virtual void Event_Activate(idEntity *activator); + virtual void Event_CanBecomeSolid(void); + + int lastAntiProjectileAttack; + int lastPassagewayTime; + idEntityPtr lastPassageway; + idEntityPtr nextPassageway; + idScriptFloat AI_CLIMB_RANGE; + idScriptFloat AI_PASSAGEWAY_HEALTH; + + idEntityPtr fxSmoke[MAX_HARVESTER_LEGS]; + + int passageCount; + bool bSmokes; + bool bGibOnDeath; +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +}; + +#endif \ No newline at end of file diff --git a/src/Prey/ai_hunter_simple.cpp b/src/Prey/ai_hunter_simple.cpp new file mode 100644 index 0000000..b40e8f1 --- /dev/null +++ b/src/Prey/ai_hunter_simple.cpp @@ -0,0 +1,2139 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef MA_LaserOn("laserOn", NULL); +const idEventDef MA_LaserOff("laserOff", NULL); +const idEventDef MA_EscapePortal( "escapePortal" ); +const idEventDef MA_AssignSniperFx( "", "e" ); +const idEventDef MA_KickAngle( "kickAngle", "v" ); +const idEventDef MA_AssignFlashLightFx( "", "e" ); +const idEventDef MA_FlashLightOn( "flashLightOn" ); +const idEventDef MA_FlashLightOff( "flashLightOff" ); +const idEventDef MA_EnemyCanSee( "enemyCanSee", NULL, 'd' ); +const idEventDef MA_PrintAction( "printAction", "s" ); +const idEventDef MA_GetAlly( "getAlly", NULL, 'e' ); +const idEventDef MA_TriggerDelay( "triggerDelay", "Ef" ); +const idEventDef MA_CallBackup( "callBackup", "f" ); +const idEventDef MA_SaySound( "saySound", "s", 'f' ); +const idEventDef MA_CheckRush( "" ); +const idEventDef MA_CheckRetreat( "" ); +const idEventDef MA_SayEnemyInfo( "sayEnemyInfo", NULL, 'd' ); +const idEventDef MA_SetNextVoiceTime( "setNextVoiceTime", "d" ); +const idEventDef MA_GetCoverNode( "getCoverNode", NULL, 'v' ); +const idEventDef MA_GetCoverPoint( "getCoverPoint", NULL, 'v' ); +const idEventDef MA_OnProjectileHit( "", "e" ); +const idEventDef MA_GetSightNode( "getSightNode", NULL, 'v' ); +const idEventDef MA_GetNearSightPoint( "getNearSightPoint", NULL, 'v' ); +const idEventDef MA_Blocked( "" ); +const idEventDef MA_EnemyPortal( "", "e" ); +const idEventDef MA_GetEnemyPortal( "getEnemyPortal", NULL, 'e' ); +const idEventDef MA_EnemyVehicleDocked( "enemyVehicleDocked", NULL, 'd' ); + +CLASS_DECLARATION(hhMonsterAI, hhHunterSimple) + EVENT( MA_OnProjectileLaunch, hhHunterSimple::Event_OnProjectileLaunch ) + EVENT( MA_OnProjectileHit, hhHunterSimple::Event_OnProjectileHit ) + EVENT( MA_LaserOn, hhHunterSimple::Event_LaserOn) + EVENT( MA_LaserOff, hhHunterSimple::Event_LaserOff) + EVENT( MA_EscapePortal, hhHunterSimple::Event_EscapePortal ) + EVENT( MA_AssignSniperFx, hhHunterSimple::Event_AssignSniperFx ) + EVENT( MA_KickAngle, hhHunterSimple::Event_KickAngle ) + EVENT( MA_AssignFlashLightFx, hhHunterSimple::Event_AssignFlashLightFx ) + EVENT( MA_FlashLightOn, hhHunterSimple::Event_FlashLightOn ) + EVENT( MA_FlashLightOff, hhHunterSimple::Event_FlashLightOff ) + EVENT( MA_EnemyCanSee, hhHunterSimple::Event_EnemyCanSee ) + EVENT( MA_PrintAction, hhHunterSimple::Event_PrintAction ) + EVENT( MA_GetAlly, hhHunterSimple::Event_GetAlly ) + EVENT( MA_TriggerDelay, hhHunterSimple::Event_TriggerDelay ) + EVENT( MA_CallBackup, hhHunterSimple::Event_CallBackup ) + EVENT( MA_SaySound, hhHunterSimple::Event_SaySound ) + EVENT( MA_CheckRush, hhHunterSimple::Event_CheckRush ) + EVENT( MA_CheckRetreat, hhHunterSimple::Event_CheckRetreat ) + EVENT( MA_SayEnemyInfo, hhHunterSimple::Event_SayEnemyInfo ) + EVENT( MA_SetNextVoiceTime, hhHunterSimple::Event_SetNextVoiceTime ) + EVENT( MA_GetCoverNode, hhHunterSimple::Event_GetCoverNode ) + EVENT( MA_GetCoverPoint, hhHunterSimple::Event_GetCoverPoint ) + EVENT( MA_GetSightNode, hhHunterSimple::Event_GetSightNode ) + EVENT( MA_GetNearSightPoint, hhHunterSimple::Event_GetNearSightPoint ) + EVENT( MA_Blocked, hhHunterSimple::Event_Blocked ) + EVENT( MA_EnemyPortal, hhHunterSimple::Event_EnemyPortal ) + EVENT( MA_GetEnemyPortal, hhHunterSimple::Event_GetEnemyPortal ) + EVENT( MA_EnemyVehicleDocked, hhHunterSimple::Event_EnemyVehicleDocked ) +END_CLASS + +hhHunterSimple::hhHunterSimple() { + beamLaser = 0; + kickSpeed = ang_zero; + kickAngles = ang_zero; + alternateAccuracy = false; + bFlashLight = false; + nodeList.Clear(); + ally.Clear(); + nextEnemyCheck = 0; + enemyPortal = 0; + nextBlockCheckTime = 0; + lastMoveOrigin = vec3_zero; + nextSpiritCheck = 0; //HUMANHEAD jsh PCF 5/2/06 hunter combat fixes +} + +void hhHunterSimple::Spawn() { + bFlashLight = false; + lastChargeTime = 0; + endSpeechTime = 0; + enemyRushCount = 0; + enemyRetreatCount = 0; + nextVoiceTime = 0; + currentAction.Clear(); + currentSpeech.Clear(); + flashlightLength = spawnArgs.GetFloat( "flashlightLength", "90" ); + flashlightTime = gameLocal.time + SEC2MS( spawnArgs.GetInt( "flashlight_delay" ) ); + beamLaser = hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamLaser" ) ); + initialOrigin = GetOrigin(); + if( beamLaser.IsValid() ) { + beamLaser->Activate( false ); + idMat3 boneAxis; + idVec3 boneOrigin; + GetJointWorldTransform( spawnArgs.GetString("laser_bone"), boneOrigin, boneAxis ); + beamLaser->SetOrigin( boneOrigin ); + beamLaser->BindToJoint( this, spawnArgs.GetString("laser_bone"), false ); + } + PostEventSec( &MA_CheckRush, 0 ); + PostEventSec( &MA_CheckRetreat, 0 ); +} + +void hhHunterSimple::Event_PostSpawn() { + hhMonsterAI::Event_PostSpawn(); + + //look for ainodes in targets list + for ( int i=0;iIsType( hhAINode::Type ) ) { + nodeList.Append( targets[i] ); + } + } + if ( nodeList.Num() <= 0 ) { + //if targets list had none, find dynamically + idEntity *ent; + float distSq; + aasPath_t path; + int toAreaNum, areaNum; + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( !ent || !ent->spawnArgs.GetBool( "ainode", "0" ) ) { + continue; + } + distSq = (GetOrigin() - ent->GetOrigin()).LengthSqr(); + if ( distSq > 2250000 ) { //less than 1500 + continue; + } + //check reachability + toAreaNum = PointReachableAreaNum( ent->GetOrigin() ); + areaNum = PointReachableAreaNum( physicsObj.GetOrigin() ); + if ( !toAreaNum || !PathToGoal( path, areaNum, physicsObj.GetOrigin(), toAreaNum, ent->GetOrigin() ) ) { + continue; + } + nodeList.Append( ent ); + } + } +} + +void hhHunterSimple::Event_OnProjectileLaunch(hhProjectile *proj) { + if ( !enemy.IsValid() || !proj || AI_VEHICLE ) { + return; + } + if ( GetPhysics() && GetPhysics()->GetGravityNormal() != idVec3( 0,0,-1 ) ) { + return; + } + + float min = spawnArgs.GetFloat( "dda_dodge_min", "0.3" ); + float max = spawnArgs.GetFloat( "dda_dodge_max", "0.8" ); + float dodgeChance = 0.6f; + + dodgeChance = (min + (max-min)*gameLocal.GetDDAValue() ); + + if ( ai_debugBrain.GetBool() ) { + gameLocal.Printf( "%s dodge chance %f\n", GetName(), dodgeChance ); + } + if ( proj->GetOwner() && proj->GetOwner() == enemy.GetEntity() ) { + float reactChance = proj->spawnArgs.GetFloat( "react_chance" ); + if ( gameLocal.random.RandomFloat() < reactChance ) { + if ( ai_debugBrain.GetBool() ) { + gameLocal.Printf( "say %s\n", proj->spawnArgs.GetString( "react_sound" ) ); + } + AI_ENEMY_RETREAT = true; + PostEventSec( &MA_SaySound, gameLocal.random.RandomFloat(), proj->spawnArgs.GetString( "react_sound" ) ); + } + } + if ( gameLocal.random.RandomFloat() > dodgeChance ) { + return; + } + if ( proj->GetOwner() && proj->GetOwner()->IsType( hhHunterSimple::Type ) ) { + return; + } + + //determine which side to dodge to + const function_t *newstate = NULL; + idVec3 povPos, targetPos; + povPos = enemy->GetPhysics()->GetOrigin(); + targetPos = GetPhysics()->GetOrigin(); + idVec3 povToTarget = targetPos - povPos; + povToTarget.z = 0.f; + povToTarget.Normalize(); + idVec3 povLeft, povUp; + povToTarget.OrthogonalBasis(povLeft, povUp); + povLeft.Normalize(); + + idVec3 projVel = proj->GetPhysics()->GetLinearVelocity(); + projVel.Normalize(); + float dot = povLeft * projVel; + + if ( dot < 0 ) { + newstate = GetScriptFunction( "state_DodgeRight" ); + } else { + newstate = GetScriptFunction( "state_DodgeLeft" ); + } + + if ( newstate ) { + SetState( newstate ); + UpdateScript(); + } +} + +void hhHunterSimple::Event_LaserOn() { + if ( beamLaser.IsValid() ) { + beamLaser->Activate( true ); + } +} + +void hhHunterSimple::Event_LaserOff() { + if ( beamLaser.IsValid() ) { + beamLaser->Activate( false ); + } +} + +void hhHunterSimple::Event_KickAngle( const idAngles &ang ) { + kickSpeed = ang; +} + +void hhHunterSimple::Think() { + PROFILE_SCOPE("AI", PROFMASK_NORMAL|PROFMASK_AI); + if (ai_skipThink.GetBool()) { + return; + } + + hhMonsterAI::Think(); + + trace_t trace; + if ( flashlightTime <= 0 || gameLocal.time > flashlightTime ) { + flashlightTime = 0; + if ( !AI_DEAD && !IsHidden() && !InVehicle() ) { + idMat3 boneAxis; + idVec3 boneOrigin; + GetJointWorldTransform( "fx_barrel", boneOrigin, boneAxis ); + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorRed, boneOrigin, boneOrigin + boneAxis[0] * flashlightLength, 10, 1 ); + } + gameLocal.clip.TracePoint( trace, boneOrigin, boneOrigin + boneAxis[0] * flashlightLength, MASK_OPAQUE, this ); + if ( trace.fraction < 1.0f ) { + Event_FlashLightOff(); + } else { + Event_FlashLightOn(); + } + } else if ( bFlashLight ) { + Event_FlashLightOff(); + } + } + if ( enemy.IsValid() && beamLaser.IsValid() && beamLaser->IsActivated() ) { + memset(&trace, 0, sizeof(trace)); + + float frametime = (gameLocal.time - gameLocal.previousTime); + kickSpeed-=4*kickSpeed*frametime/1000; + kickSpeed-=30*kickAngles*frametime/1000; + kickAngles+=kickSpeed*frametime/1000; + + idAngles ang = (lastVisibleEnemyPos + enemy->EyeOffset() - idVec3( 0,0,10 ) - beamLaser->GetOrigin()).ToAngles(); + ang += kickAngles; + idVec3 forward, up, right; + ang.ToVectors( &forward, &right, &up ); + gameLocal.clip.TracePoint( trace, beamLaser->GetOrigin(), beamLaser->GetOrigin() + forward * 4000, MASK_SHOT_BOUNDINGBOX, this ); + if ( trace.fraction < 1.0f ) { + idEntity *hitEnt = gameLocal.GetTraceEntity( trace ); + if ( hitEnt && hitEnt->IsType( idPlayer::Type ) || + ( hitEnt && hitEnt->IsType( hhVehicle::Type ) ) ) { + //if we hit the player, set the endpoint a little farther back + //to prevent the laser from stopping at our camera viewpoint + idVec3 offset = (trace.endpos - beamLaser->GetOrigin()).ToNormal() * 100; + beamLaser->SetTargetLocation( trace.endpos + offset ); + } else { + beamLaser->SetTargetLocation( trace.endpos ); + } + } else { + beamLaser->SetTargetLocation( beamLaser->GetOrigin() + forward * 5000 ); + } + } + + if ( ai_debugBrain.GetBool() && ally.IsValid() && ally->GetHealth() > 0 ) { + gameRenderWorld->DebugArrow( colorGreen, GetOrigin(), ally->GetOrigin(), 10, 1 ); + } + + if ( health > 0 && ai_debugActions.GetBool() && currentAction.Length() ) { + if ( AI_ENEMY_VISIBLE ) { + gameRenderWorld->DrawText("visible", GetEyePosition() + idVec3(0.0f, 0.0f, 30.0f), 0.4f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3()); + } + if ( AI_ENEMY_SHOOTABLE ) { + gameRenderWorld->DrawText("shootable", GetEyePosition() + idVec3(0.0f, 0.0f, 20.0f), 0.4f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3()); + } + gameRenderWorld->DrawText(va( "%s", currentAction.c_str() ), GetEyePosition() + idVec3(0.0f, 0.0f, 0.0f), 0.4f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3()); + } + if ( health > 0 && ai_printSpeech.GetBool() && currentSpeech.Length() ) { + gameRenderWorld->DrawText(va( "%s", currentSpeech.c_str() ), GetEyePosition() + idVec3(0.0f, 0.0f, 10.0f), 0.4f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3()); + if ( endSpeechTime > 0 && gameLocal.time > endSpeechTime ) { + currentSpeech.Clear(); + endSpeechTime = 0; + } + } + + if ( enemy.IsValid() ) { + //HUMANHEAD jsh PCF 5/2/06 hunter combat fixes + if ( nextSpiritCheck > 0 && gameLocal.time > nextSpiritCheck && enemy->IsType( hhSpiritProxy::Type ) ) { + nextSpiritCheck = 0; + SetEnemy( gameLocal.GetLocalPlayer() ); + } + if ( AI_ENEMY_VISIBLE ) { + AI_ENEMY_LAST_SEEN = MS2SEC( gameLocal.realClientTime ); + } + if ( enemy->GetHealth() < spawnArgs.GetInt( "health_warning", "25" ) ) { + AI_ENEMY_HEALTH_LOW = true; + } + } +} + +idProjectile *hhHunterSimple::LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone, const idDict* desiredProjectileDef ) { //HUMANHEAD mdc - added desiredProjectileDef for supporting multiple projs. + //jsh overridden to allow per-projectile accuracy + idVec3 muzzle; + idVec3 dir; + idVec3 start; + trace_t tr; + idBounds projBounds; + float distance; + const idClipModel *projClip; + float attack_accuracy; + float attack_cone; + float projectile_spread; + float diff; + float angle; + float spin; + idAngles ang; + int num_projectiles; + int i; + idMat3 axis; + idVec3 tmp; + idProjectile *lastProjectile; + + //HUMANHEAD mdc - added to support multiple projectiles + if( desiredProjectileDef ) { //try to set our projectile to the desiredProjectile + int projIndex = FindProjectileInfo( desiredProjectileDef ); + if( projIndex >= 0 ) { + SetCurrentProjectile( projIndex ); + } + } + //HUMANHEAD END + + + if ( !projectileDef || AI_DEAD ) { + gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() ); + return NULL; + } + + if ( projectileDef->GetBool( "snipe", "0" ) ) { + attack_accuracy = 0; + } else { + if ( projectileDef->GetFloat( "attack_accuracy" ) ) { + attack_accuracy = projectileDef->GetFloat( "attack_accuracy", "7" ); + } else { + attack_accuracy = spawnArgs.GetFloat( "attack_accuracy", "7" ); + } + } + + attack_cone = spawnArgs.GetFloat( "attack_cone", "70" ); + if ( projectileDef->GetFloat( "projectile_spread" ) ) { + projectile_spread = projectileDef->GetFloat( "projectile_spread", "0" ); + } else { + projectile_spread = spawnArgs.GetFloat( "projectile_spread", "0" ); + } + if ( projectileDef->GetFloat( "num_projectiles" ) ) { + num_projectiles = projectileDef->GetFloat( "num_projectiles", "1" ); + } else { + num_projectiles = spawnArgs.GetFloat( "num_projectiles", "1" ); + } + + + GetMuzzle( jointname, muzzle, axis ); + + if ( !projectile.GetEntity() ) { + CreateProjectile( muzzle, axis[ 0 ] ); + } + + lastProjectile = projectile.GetEntity(); + + if ( target != NULL ) { + tmp = target->GetPhysics()->GetAbsBounds().GetCenter() - muzzle; + tmp.Normalize(); + axis = tmp.ToMat3(); + } else { + axis = viewAxis; + } + + // rotate it because the cone points up by default + tmp = axis[2]; + axis[2] = axis[0]; + axis[0] = -tmp; + + // make sure the projectile starts inside the monster bounding box + const idBounds &ownerBounds = physicsObj.GetAbsBounds(); + projClip = lastProjectile->GetPhysics()->GetClipModel(); + projBounds = projClip->GetBounds().Rotate( axis ); + + // 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( muzzle, viewAxis[ 0 ], distance ) ) { + start = muzzle + 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(); + } + + gameLocal.clip.Translation( tr, start, muzzle, projClip, axis, MASK_SHOT_RENDERMODEL, this ); + muzzle = tr.endpos; + + // set aiming direction + GetAimDir( muzzle, target, this, dir ); + ang = dir.ToAngles(); + + // hunter inaccuracy is calculated instead of random spread based + float hitchance; + if ( !projectileDef->GetFloat( "hit_chance", "0.2", hitchance ) ) { + hitchance = spawnArgs.GetFloat( "hit_chance" ); + } + float hitAccuracy; + if ( !projectileDef->GetFloat( "hit_accuracy", "0", hitAccuracy ) ) { + hitAccuracy = spawnArgs.GetFloat( "hit_accuracy" ); + } + float missAccuracy; + if ( !projectileDef->GetFloat( "miss_accuracy", "0", missAccuracy ) ) { + missAccuracy = spawnArgs.GetFloat( "miss_accuracy" ); + } + float t = MS2SEC( gameLocal.time + entityNumber * 497 ); + alternateAccuracy = !alternateAccuracy; + if ( gameLocal.random.RandomFloat() < hitchance ) { + //rolled a hit so add very little inaccuracy + ang.pitch += idMath::Fabs( idMath::Sin16( t * 5.1 ) * hitAccuracy ); + ang.yaw += idMath::Sin16( t * 6.7 ) * hitAccuracy; + } else { + float rand = gameLocal.random.RandomFloat(); + if ( alternateAccuracy ) { + rand *= -1; + } + ang.pitch += rand * missAccuracy; + if ( !alternateAccuracy ) { + rand *= -1; + } + ang.yaw += rand * missAccuracy; + } + + //quick hack that should be fine for now + bool oriented = GetPhysics()->GetGravityNormal() != idVec3( 0,0,-1 ); + if ( !oriented && clampToAttackCone ) { + // 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, current_yaw ); + if ( diff > attack_cone ) { + ang.yaw = current_yaw + attack_cone; + } else if ( diff < -attack_cone ) { + ang.yaw = current_yaw - attack_cone; + } + } + + axis = ang.ToMat3(); + + float spreadRad = DEG2RAD( projectile_spread ); + for( i = 0; i < num_projectiles; i++ ) { + // spread the projectiles out + angle = idMath::Sin( spreadRad * 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(); + + // launch the projectile + if ( !projectile.GetEntity() ) { + CreateProjectile( muzzle, dir ); + } + lastProjectile = projectile.GetEntity(); + lastProjectile->Launch( muzzle, dir, vec3_origin ); + projectile = NULL; + } + + TriggerWeaponEffects( muzzle, axis ); + + lastAttackTime = gameLocal.time; + +//HUMANHEAD mdc - added to support multiple projectiles + projectile = NULL; + SetCurrentProjectile( projectileDefaultDefIndex ); //set back to our default projectile to be on the safe side +//HUMANHEAD END + + return lastProjectile; +} + +void hhHunterSimple::Event_EscapePortal() { + static const char * passPrefix = "portal_"; + const char * portalDef; + idEntity * portal; + idDict portalArgs; + idList xferKeys; + idList xferValues; + const idKeyValue * buddyKV; + + + // Find out which portal def to spawn - If none specified, then exit; + buddyKV = spawnArgs.FindKey( "portal_buddy" ); + if ( buddyKV && buddyKV->GetValue().Length() && gameLocal.FindEntity(buddyKV->GetValue().c_str()) ) { + // Case of a valid portal_buddy key, make a real portal + portalDef = spawnArgs.GetString( "def_portal" ); + } else { + portalDef = spawnArgs.GetString( "def_fakeportal" ); + } + + if ( !portalDef || !portalDef[0] ) { + return; + } + + // Set the origin of the portal to us. + //portalArgs.SetVector( "origin", GetOrigin() ); + idVec3 escapePortalOffset = spawnArgs.GetVector( "escapePortalOffset", "40 0 0" ); + portalArgs.SetVector( "origin", GetOrigin() + ( escapePortalOffset * GetAxis() ) ); + portalArgs.SetFloat( "angle", GetAxis()[0].ToYaw() - 180 ); + + // Set the portal draw distance to zero (so the player cannot see through the creature portal) + portalArgs.Set( "shaderParm5", "0" ); + + // Pass along all 'portal_' keys to the portal's spawnArgs; + hhUtils::GetKeysAndValues( spawnArgs, passPrefix, xferKeys, xferValues ); + for ( int i = 0; i < xferValues.Num(); ++i ) { + xferKeys[ i ].StripLeadingOnce( passPrefix ); + portalArgs.Set( xferKeys[ i ].c_str(), xferValues[ i ].c_str() ); + } + + // Set the name of the associated game portal so it can be turned on and off + portalArgs.Set( "gamePortalName", GetName() ); + + // Spawn the portal + portal = gameLocal.SpawnObject( portalDef, &portalArgs ); + if ( !portal ) { + return; + } + + // Move the portal up some pre determinted amt, since its origin is in the middle of it + float offset = spawnArgs.GetFloat( "offset_portal", 0 ); + portal->GetPhysics()->SetOrigin( portal->GetPhysics()->GetOrigin() + (portal->GetAxis()[2] * offset) ); + + // Update the camera stuff + portal->ProcessEvent( &EV_UpdateCameraTarget ); + + // Open the portal - Need to delay this, so that PostSpawn gets called/sets up the partner portal + //? Should we always pass in the player? + portal->PostEventSec( &EV_Activate, 0, gameLocal.GetLocalPlayer() ); + + // Maybe wait for it to finish? + + +} + +void hhHunterSimple::Event_FindReaction( const char* effect ) { + idEntity* ent; + hhReaction* react; + hhReactionDesc::Effect react_effect; + idEntity* bestEnt = NULL; + int bestReactIndex = -1; + float bestDistance = -1; + float meToReact, enemyToReact; + int bestRank = -1; + + react_effect = hhReactionDesc::StrToEffect( effect ); + + if( react_effect == hhReactionDesc::Effect_Invalid ) { + gameLocal.Warning( "unknown effect '%s' requested from FindReaction", effect ); + idThread::ReturnEntity( NULL ); + return; + } + + for ( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if( !ent || ent->fl.isDormant || !ent->fl.refreshReactions ) { + continue; + } + + for( int j = 0; j < ent->GetNumReactions(); j++ ) { + react = ent->GetReaction( j ); + if( !react || !react->IsActive() ) { + continue; + } + if( react_effect != react->desc->effect ) { + continue; + } + if( react->desc->flags & hhReactionDesc::flag_Exclusive ) { //check exclusiveness + if ( react->exclusiveOwner.IsValid() && react->exclusiveOwner->health <= 0 ) { + react->exclusiveOwner.Clear(); + } + if( react->exclusiveOwner.GetEntity() && react->exclusiveOwner != this ) { + continue; + } + } + //Skip based on flag requirements + if( (react->desc->flags & hhReactionDesc::flagReq_RangeAttack) && !AI_HAS_RANGE_ATTACK ) { + continue; + } + // Skip monsters without melee attack + if( (react->desc->flags & hhReactionDesc::flagReq_MeleeAttack) && !AI_HAS_MELEE_ATTACK ) { + continue; + } + if( react->desc->flags & hhReactionDesc::flagReq_KeyValue ) { + idStr val; + if( spawnArgs.GetString(react->desc->key, "", val) ) { + if( val != react->desc->keyVal ) { + continue; + } + } + else { + continue; + } + } + // Skip monsters without specific animation? + if( react->desc->flags & hhReactionDesc::flagReq_Anim ) { + if( !GetAnimator()->HasAnim(react->desc->anim) ) { + continue; + } + } + + // Skip monsters in vehicles? + if( (react->desc->flags & hhReactionDesc::flagReq_NoVehicle) && InVehicle() ) { + continue; + } + + float distToEnt, distToEnemy, distEnemyToEnt; + // Check actual specifics for reaction type + switch( react->desc->effect ) { + case hhReactionDesc::Effect_HaveFun: + case hhReactionDesc::Effect_Vehicle: + case hhReactionDesc::Effect_VehicleDock: + case hhReactionDesc::Effect_ProvideCover: + //only use reasonably reachable objects + if ( enemy.IsValid() ) { + distToEnt = TravelDistance( GetOrigin(), ent->GetOrigin() ); + distToEnemy = (GetOrigin() - enemy->GetOrigin()).Length(); + distEnemyToEnt = (enemy->GetOrigin() - ent->GetOrigin()).Length(); + if ( distToEnt > 1.0 ) { + float ratio = distToEnt / distEnemyToEnt; + if ( ratio > 2.0f ) { + //skip if object is farther than enemy or if enemy is closer to object than i am + continue; + } + } + } + case hhReactionDesc::Effect_Heal: + if ( enemy.IsValid() ) { + distToEnt = TravelDistance( GetOrigin(), ent->GetOrigin() ); + distToEnemy = (GetOrigin() - enemy->GetOrigin()).Length(); + distEnemyToEnt = (enemy->GetOrigin() - ent->GetOrigin()).Length(); + if ( distToEnt > 1.0 ) { + float ratio = distToEnt / distEnemyToEnt; + if ( ratio > 2.0f ) { + //skip if object is farther than enemy or if enemy is closer to object than i am + continue; + } + } + } + case hhReactionDesc::Effect_Climb: + case hhReactionDesc::Effect_Passageway: + case hhReactionDesc::Effect_CallBackup: + break; + case hhReactionDesc::Effect_DamageEnemy: + //check if enemy is close enough to matter + if ( enemy.IsValid() && react->desc->effectRadius > 0 ) { + enemyToReact = (enemy->GetOrigin() - react->causeEntity->GetOrigin()).LengthSqr(); + if ( enemyToReact > react->desc->effectRadius * react->desc->effectRadius ) { + continue; + } + } + //check if i'm too close + meToReact = (GetOrigin() - react->causeEntity->GetOrigin()).LengthSqr(); + if ( meToReact < react->desc->effectRadius * react->desc->effectRadius ) { + continue; + } + break; + case hhReactionDesc::Effect_Damage: + default: + continue; + } + // if we have actually gotten this far, do our intense calculations last.. + int rank = EvaluateReaction( react ); + if( rank == 0 ) { + continue; + } + +// We have a valid reaction... + float distSq = ( react->causeEntity->GetOrigin() - GetOrigin() ).LengthSqr(); + if ( bestRank == -1 || bestRank <= rank ) { // Check the reaction rank against our best rank + if (rank == bestRank && distSq > bestDistance) { // If they are the same rank, but the current one is farther then ignore it. + continue; + } + bestEnt = ent; + bestReactIndex = j; + bestDistance = distSq; + bestRank = rank; + } + } + } + + if ( bestEnt ) { + targetReaction.entity = bestEnt; + targetReaction.reactionIndex = bestReactIndex; + hhReaction *reaction = targetReaction.GetReaction(); + if( reaction && reaction->desc->flags & hhReactionDesc::flagReq_RangeAttack ) { + shootTarget = bestEnt; + } else { + shootTarget = NULL; + } + idThread::ReturnEntity( targetReaction.entity.GetEntity() ); + } else { + idThread::ReturnEntity( NULL ); + } +} + +void hhHunterSimple::AnimMove( void ) { + //overridden to set AI_BLOCKED when blockEnt is found + idVec3 goalPos; + idVec3 delta; + idVec3 goalDelta; + float goalDist; + monsterMoveResult_t moveResult; + idVec3 newDest; + + idVec3 oldorigin = physicsObj.GetOrigin(); +#ifdef HUMANHEAD //jsh wallwalk + idMat3 oldaxis = GetGravViewAxis(); +#else + idMat3 oldaxis = viewAxis; +#endif + + AI_BLOCKED = false; + + if ( move.moveCommand < NUM_NONMOVING_COMMANDS ){ + move.lastMoveOrigin.Zero(); + move.lastMoveTime = gameLocal.time; + } + + move.obstacle = NULL; + if ( ( move.moveCommand == MOVE_FACE_ENEMY ) && enemy.GetEntity() ) { + TurnToward( lastVisibleEnemyPos ); + goalPos = oldorigin; + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + goalPos = oldorigin; + } else if ( GetMovePos( goalPos ) ) { + if ( move.moveCommand != MOVE_WANDER ) { + CheckObstacleAvoidance( goalPos, newDest ); + nextMovePos = newDest; + TurnToward( newDest ); + } else { + TurnToward( goalPos ); + } + } + + Turn(); + + if ( move.moveCommand == MOVE_SLIDE_TO_POSITION ) { + if ( gameLocal.time < move.startTime + move.duration ) { + goalPos = move.moveDest - move.moveDir * MS2SEC( move.startTime + move.duration - gameLocal.time ); + delta = goalPos - oldorigin; + delta.z = 0.0f; + } else { + delta = move.moveDest - oldorigin; + delta.z = 0.0f; + StopMove( MOVE_STATUS_DONE ); + } + } else if ( allowMove ) { +#ifdef HUMANHEAD //jsh wallwalk + GetMoveDelta( oldaxis, GetGravViewAxis(), delta ); +#else + GetMoveDelta( oldaxis, viewAxis, delta ); +#endif + } else { + delta.Zero(); + } + + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorRed, physicsObj.GetOrigin(), physicsObj.GetOrigin() + delta.ToNormal() * 100, 10 ); + gameRenderWorld->DebugArrow( colorGreen, physicsObj.GetOrigin(), physicsObj.GetOrigin() + renderEntity.axis[1] * 100, 10 ); + gameRenderWorld->DebugArrow( colorGreen, physicsObj.GetOrigin(), physicsObj.GetOrigin() + GetPhysics()->GetAxis()[2] * 100, 10 ); + } + + if ( move.moveCommand == MOVE_TO_POSITION ) { + goalDelta = move.moveDest - oldorigin; + goalDist = goalDelta.LengthFast(); + if ( goalDist < delta.LengthFast() ) { + delta = goalDelta; + } + } + physicsObj.SetDelta( delta ); + physicsObj.ForceDeltaMove( disableGravity ); + + RunPhysics(); + + moveResult = physicsObj.GetMoveResult(); + if ( !af_push_moveables && attack.Length() && TestMelee() ) { + DirectDamage( attack, enemy.GetEntity() ); + } else { + idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); + if ( blockEnt ) { + AI_BLOCKED = true; + AI_NEXT_DIR_TIME = 0; + + if ( blockEnt->IsType( idAI::Type ) ) { + StopMove( MOVE_STATUS_BLOCKED_BY_MONSTER ); + } + } + if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) { + KickObstacles( viewAxis[ 0 ], kickForce, blockEnt ); + } + } + + BlockedFailSafe(); + + AI_ONGROUND = physicsObj.OnGround(); + + idVec3 org = physicsObj.GetOrigin(); + if ( oldorigin != org ) { + TouchTriggers(); + } + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, gameLocal.msec ); + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec ); + gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true ); + DrawRoute(); + } +} + +void hhHunterSimple::Event_AssignSniperFx( hhEntityFx* fx ) { + sniperFx = fx; +} + +void hhHunterSimple::Event_AssignFlashLightFx( hhEntityFx* fx ) { + flashLightFx = fx; +} + +bool hhHunterSimple::UpdateAnimationControllers( void ) { + idVec3 local; + idVec3 focusPos; + idVec3 left; + idVec3 dir; + idVec3 orientationJointPos; + idVec3 localDir; + idAngles newLookAng; + idAngles diff; + idMat3 mat; + idMat3 axis; + idMat3 orientationJointAxis; + idAFAttachment *headEnt = head.GetEntity(); + idVec3 eyepos; + idVec3 pos; + int i; + idAngles jointAng; + float orientationJointYaw; + + if ( AI_DEAD ) { + return idActor::UpdateAnimationControllers(); + } + + if ( orientationJoint == INVALID_JOINT ) { + orientationJointAxis = viewAxis; + orientationJointPos = physicsObj.GetOrigin(); + orientationJointYaw = current_yaw; + } else { + GetJointWorldTransform( orientationJoint, gameLocal.time, orientationJointPos, orientationJointAxis ); + orientationJointYaw = orientationJointAxis[ 2 ].ToYaw(); + orientationJointAxis = idAngles( 0.0f, orientationJointYaw, 0.0f ).ToMat3(); + } + + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorCyan, orientationJointPos, orientationJointPos + orientationJointAxis[0] * 64.0, 10, 1 ); + } + + if ( focusJoint != INVALID_JOINT ) { + if ( headEnt ) { + headEnt->GetJointWorldTransform( focusJoint, gameLocal.time, eyepos, axis ); + } else { + // JRMMERGE_GRAVAXIS - What about GetGravAxis() are we still using/needing that? + GetJointWorldTransform( focusJoint, gameLocal.time, eyepos, axis ); + } + eyeOffset.z = eyepos.z - physicsObj.GetOrigin().z; + } else { + eyepos = GetEyePosition(); + } + + if ( headEnt ) { + CopyJointsFromBodyToHead(); + } + + // 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(); + + idEntity *focusEnt = focusEntity.GetEntity(); + //HUMANHEAD jsh allow eyefocus independent from allowJointMod + if ( ( !allowJointMod && !allowEyeFocus ) || ( gameLocal.time >= focusTime && focusTime != -1 ) ) { + focusPos = GetEyePosition() + orientationJointAxis[ 0 ] * 512.0f; + } else if ( focusEnt == NULL ) { + // keep looking at last position until focusTime is up + focusPos = currentFocusPos; + } else if ( focusEnt == enemy.GetEntity() ) { + if ( beamLaser.IsValid() && beamLaser->IsActivated() ) { + focusPos = beamLaser->GetTargetLocation(); + } else { + focusPos = lastVisibleEnemyPos + lastVisibleEnemyEyeOffset - eyeVerticalOffset * enemy.GetEntity()->GetPhysics()->GetGravityNormal(); + } + } else if ( focusEnt->IsType( idActor::Type ) ) { + focusPos = static_cast( focusEnt )->GetEyePosition() - eyeVerticalOffset * focusEnt->GetPhysics()->GetGravityNormal(); + } else { + focusPos = focusEnt->GetPhysics()->GetOrigin(); + } + + currentFocusPos = currentFocusPos + ( focusPos - currentFocusPos ) * eyeFocusRate; + + // determine yaw from origin instead of from focus joint since joint may be offset, which can cause us to bounce between two angles + dir = focusPos - orientationJointPos; + newLookAng.yaw = idMath::AngleNormalize180( dir.ToYaw() - orientationJointYaw ); + newLookAng.roll = 0.0f; + newLookAng.pitch = 0.0f; + newLookAng += lookOffset; + + // determine pitch from joint position + dir = focusPos - eyepos; + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorYellow, eyepos, eyepos + dir, 10, 1 ); + } + dir.NormalizeFast(); + physicsObj.GetAxis().ProjectVector( dir, localDir ); + newLookAng.pitch = -idMath::AngleNormalize180( localDir.ToPitch() ) + lookOffset.pitch; + newLookAng.roll = 0.0f; + diff = newLookAng - lookAng; + + if ( eyeAng != diff ) { + eyeAng = diff; + eyeAng.Clamp( eyeMin, eyeMax ); + idAngles angDelta = diff - eyeAng; + if ( !angDelta.Compare( ang_zero, 0.1f ) ) { + alignHeadTime = gameLocal.time; + } else { + alignHeadTime = gameLocal.time + ( 0.5f + 0.5f * gameLocal.random.RandomFloat() ) * focusAlignTime; + } + } + + if ( idMath::Fabs( newLookAng.yaw ) < 0.1f ) { + alignHeadTime = gameLocal.time; + } + + if ( ( gameLocal.time >= alignHeadTime ) || ( gameLocal.time < forceAlignHeadTime ) ) { + alignHeadTime = gameLocal.time + ( 0.5f + 0.5f * gameLocal.random.RandomFloat() ) * focusAlignTime; + destLookAng = newLookAng; + destLookAng.Clamp( lookMin, lookMax ); + } + + diff = destLookAng - lookAng; + if ( ( lookMin.pitch == -180.0f ) && ( lookMax.pitch == 180.0f ) ) { + if ( ( diff.pitch > 180.0f ) || ( diff.pitch <= -180.0f ) ) { + diff.pitch = 360.0f - diff.pitch; + } + } + if ( ( lookMin.yaw == -180.0f ) && ( lookMax.yaw == 180.0f ) ) { + if ( diff.yaw > 180.0f ) { + diff.yaw -= 360.0f; + } else if ( diff.yaw <= -180.0f ) { + diff.yaw += 360.0f; + } + } + lookAng = lookAng + diff * headFocusRate; + lookAng.Normalize180(); + + jointAng.roll = 0.0f; + if ( allowJointMod ) { + //quick hack. dont change yaw if in different gravity + bool oriented = GetPhysics()->GetGravityNormal() != idVec3( 0,0,-1 ); + for( i = 0; i < lookJoints.Num(); i++ ) { + if ( oriented ) { + jointAng.yaw = 0; + } else { + jointAng.yaw = lookAng.yaw * lookJointAngles[ i ].yaw; + } + jointAng.pitch = lookAng.pitch * lookJointAngles[ i ].pitch; + animator.SetJointAxis( lookJoints[ i ], JOINTMOD_WORLD, jointAng.ToMat3() ); + } + } + + if ( move.moveType == MOVETYPE_FLY ) { + // lean into turns + AdjustFlyingAngles(); + } + + if ( headEnt ) { + idAnimator *headAnimator = headEnt->GetAnimator(); + + // HUMANHEAD pdm: Added support for look joints in head entities + if ( allowJointMod ) { + for( i = 0; i < headLookJoints.Num(); i++ ) { + jointAng.pitch = lookAng.pitch * headLookJointAngles[ i ].pitch; + jointAng.yaw = lookAng.yaw * headLookJointAngles[ i ].yaw; + headAnimator->SetJointAxis( headLookJoints[ i ], JOINTMOD_WORLD, jointAng.ToMat3() ); + } + } + // HUMANHEAD END + + if ( allowEyeFocus ) { + idMat3 eyeAxis = ( lookAng + eyeAng ).ToMat3(); idMat3 headTranspose = headEnt->GetPhysics()->GetAxis().Transpose(); + axis = eyeAxis * orientationJointAxis; + left = axis[ 1 ] * eyeHorizontalOffset; + eyepos -= headEnt->GetPhysics()->GetOrigin(); + headAnimator->SetJointPos( leftEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + ( axis[ 0 ] * 64.0f + left ) * headTranspose ); + headAnimator->SetJointPos( rightEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + ( axis[ 0 ] * 64.0f - left ) * headTranspose ); + + //if ( ai_debugMove.GetBool() ) { + // gameRenderWorld->DebugLine( colorRed, orientationJointPos, eyepos + ( axis[ 0 ] * 64.0f + left ) * headTranspose, gameLocal.msec ); + //} + } else { + headAnimator->ClearJoint( leftEyeJoint ); + headAnimator->ClearJoint( rightEyeJoint ); + } + } else { + if ( allowEyeFocus ) { + idMat3 eyeAxis = ( lookAng + eyeAng ).ToMat3(); + axis = eyeAxis * orientationJointAxis; + left = axis[ 1 ] * eyeHorizontalOffset; + eyepos += axis[ 0 ] * 64.0f - physicsObj.GetOrigin(); + animator.SetJointPos( leftEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + left ); + animator.SetJointPos( rightEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos - left ); + } else { + animator.ClearJoint( leftEyeJoint ); + animator.ClearJoint( rightEyeJoint ); + } + } + + //HUMANHEAD pdm jawflap + hhAnimator *theAnimator; + if (head.IsValid()) { + theAnimator = head->GetAnimator(); + } + else { + theAnimator = GetAnimator(); + } + JawFlap(theAnimator); + //END HUMANHEAD + + return true; +} + +void hhHunterSimple::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + //overridden to remove muzzle fx on death + if ( AI_DEAD ) { + AI_DAMAGE = true; + return; + } + if ( enemy.IsValid() ) { + gameLocal.AlertAI( enemy.GetEntity(), spawnArgs.GetInt( "death_alert_radius", "800" ) ); + } + Event_FlashLightOff(); + if ( beamLaser.IsValid() ) { + beamLaser->Unbind(); + SAFE_REMOVE( beamLaser ); + } + hhMonsterAI::Killed( inflictor, attacker, damage, dir, location ); + + //taken from idEntity::RemoveBinds() + //remove any lingering fx on death + idEntity *ent; + idEntity *next; + for( ent = GetTeamChain(); ent != NULL; ent = next ) { + next = ent->GetTeamChain(); + if ( ent->GetBindMaster() == this && ent->IsType( idEntityFx::Type ) ) { + ent->Unbind(); + if( !ent->fl.noRemoveWhenUnbound ) { + ent->PostEventMS( &EV_Remove, 0 ); + if (gameLocal.isClient) { + ent->Hide(); + } + } + next = GetTeamChain(); + } + } + +} + +void hhHunterSimple::Save( idSaveGame *savefile ) const { + beamLaser.Save( savefile ); + savefile->WriteVec3( nextMovePos ); + savefile->WriteAngles( kickAngles ); + savefile->WriteAngles( kickSpeed ); + savefile->WriteFloat( flashlightLength ); + sniperFx.Save( savefile ); + + ally.Save( savefile ); + muzzleFx.Save( savefile ); + flashLightFx.Save( savefile ); + savefile->WriteBool( bFlashLight ); + savefile->WriteInt( endSpeechTime ); + savefile->WriteInt( nextEnemyCheck ); + savefile->WriteFloat( lastEnemyDistance ); + savefile->WriteInt( lastChargeTime ); + savefile->WriteInt( nextVoiceTime ); + savefile->WriteVec3( initialOrigin ); + savefile->WriteInt( flashlightTime ); + enemyPortal.Save( savefile ); + savefile->WriteVec3( lastMoveOrigin ); + savefile->WriteInt( nextBlockCheckTime ); + savefile->WriteInt( nextSpiritCheck ); //HUMANHEAD jsh PCF 5/2/06 hunter combat fixes + + //HUMANHEAD jsh PCF 4/28/06 save nodelist + savefile->WriteInt(nodeList.Num()); + for (int i = 0; i < nodeList.Num(); i++) { + nodeList[i].Save(savefile); + } + //END HUMANHEAD +}; + +void hhHunterSimple::Restore( idRestoreGame *savefile ) { + beamLaser.Restore( savefile ); + savefile->ReadVec3( nextMovePos ); + savefile->ReadAngles( kickAngles ); + savefile->ReadAngles( kickSpeed ); + savefile->ReadFloat( flashlightLength ); + sniperFx.Restore( savefile ); + + ally.Restore( savefile ); + muzzleFx.Restore( savefile ); + flashLightFx.Restore( savefile ); + savefile->ReadBool( bFlashLight ); + savefile->ReadInt( endSpeechTime ); + savefile->ReadInt( nextEnemyCheck ); + savefile->ReadFloat( lastEnemyDistance ); + savefile->ReadInt( lastChargeTime ); + savefile->ReadInt( nextVoiceTime ); + savefile->ReadVec3( initialOrigin ); + savefile->ReadInt( flashlightTime ); + enemyPortal.Restore( savefile ); + savefile->ReadVec3( lastMoveOrigin ); + savefile->ReadInt( nextBlockCheckTime ); + savefile->ReadInt( nextSpiritCheck ); //HUMANHEAD jsh PCF 5/2/06 hunter combat fixes + + //HUMANHEAD jsh PCF 4/28/06 save nodelist + int num; + savefile->ReadInt(num); + nodeList.SetNum(num); + for (int i = 0; i < num; i++) { + nodeList[i].Restore(savefile); + } + //END HUMANHEAD + + alternateAccuracy = false; + enemyRushCount = 0; + enemyRetreatCount = 0; + enemyHiddenCount = 0; + currentAction.Clear(); + currentSpeech.Clear(); +}; + +void hhHunterSimple::Event_FlashLightOn() { + if ( !bFlashLight && gameLocal.time > flashlightTime && spawnArgs.GetBool( "can_flashlight" ) ) { + bFlashLight = true; + BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_flashLight"), "fx_barrel", NULL, &MA_AssignFlashLightFx, false ); + } +} + +void hhHunterSimple::Event_FlashLightOff() { + bFlashLight = false; + SAFE_REMOVE( flashLightFx ); +} + +void hhHunterSimple::PrintDebug() { + hhMonsterAI::PrintDebug(); + gameLocal.Printf( " Ally: %s\n", ally.IsValid() ? ally->GetName() : "none" ); + gameLocal.Printf( " Allow Movement: %s\n", allowMove ? "yes" : "no" ); + gameLocal.Printf( " Turn rate : %f\n", turnRate ); + if ( nodeList.Num() ) { + gameLocal.Printf( " Cover Points:\n" ); + aasPath_t path; + int toAreaNum, areaNum; + for( int i=0;iGetOrigin() ); + areaNum = PointReachableAreaNum( physicsObj.GetOrigin() ); + if ( !toAreaNum || !PathToGoal( path, areaNum, physicsObj.GetOrigin(), toAreaNum, nodeList[i]->GetOrigin() ) ) { + common->Printf( " %s: " S_COLOR_RED "NOT REACHABLE\n", nodeList[i]->GetName() ); + } else { + common->Printf( " %s: " S_COLOR_GREEN " REACHABLE\n", nodeList[i]->GetName() ); + } + } + } + } +} + +bool hhHunterSimple::TurnToward( const idVec3 &pos ) { + idVec3 dir; + idVec3 local_dir; + float lengthSqr; + + if (AI_VEHICLE && InVehicle()) { + GetVehicleInterfaceLocal()->OrientTowards( pos, 0.5 ); + return true; + } + + dir = pos - physicsObj.GetOrigin(); +#ifdef HUMANHEAD //jsh wallwalk + physicsObj.GetAxis().ProjectVector( dir, local_dir ); +#else + physicsObj.GetGravityAxis().ProjectVector( dir, local_dir ); +#endif + local_dir.z = 0.0f; + lengthSqr = local_dir.LengthSqr(); + if ( lengthSqr > Square( 2.0f ) || ( lengthSqr > Square( 0.1f ) && enemy.GetEntity() == NULL ) ) { + //HUMANHEAD jsh PCF 4/29/06 changed to use enemy rather than player for directional movement + if ( !AI_DIR_MOVEMENT || AI_PATHING || !enemy.IsValid() ) { + AI_DIR_MOVEMENT = false; + ideal_yaw = idMath::AngleNormalize180( local_dir.ToYaw() ); + AI_DIR = HUNTER_N; + } else { + float toEnemy = (enemy->GetOrigin() - GetOrigin()).ToAngles().yaw; + float toPos = (pos - GetOrigin()).ToAngles().yaw; + if ( gameLocal.time > AI_NEXT_DIR_TIME && lengthSqr > Square( 2.0f ) ) { + float ang = toEnemy - toPos; + ang = idMath::AngleNormalize360( ang ); + AI_NEXT_DIR_TIME = gameLocal.time + SEC2MS(1); + if ( ang >= 135 && ang < 225 ) { + AI_DIR = HUNTER_S; + ideal_yaw = idMath::AngleNormalize180( local_dir.ToYaw() + 180 ); + } else if ( ang >= 45 && ang < 135 ) { + AI_DIR = HUNTER_W; + ideal_yaw = idMath::AngleNormalize180( local_dir.ToYaw() + 90 ); + } else if ( ang >= 225 && ang < 315 ) { + AI_DIR = HUNTER_E; + ideal_yaw = idMath::AngleNormalize180( local_dir.ToYaw() + 270 ); + } else { + AI_DIR = HUNTER_N; + ideal_yaw = idMath::AngleNormalize180( local_dir.ToYaw() ); + } + } + } + } + + bool result = FacingIdeal(); + return result; +} + +void hhHunterSimple::LinkScriptVariables( void ) { + hhMonsterAI::LinkScriptVariables(); + AI_DIR.LinkTo( scriptObject, "AI_DIR" ); + AI_DIR_MOVEMENT.LinkTo( scriptObject, "AI_DIR_MOVEMENT" ); + AI_LAST_DAMAGE_TIME.LinkTo( scriptObject, "AI_LAST_DAMAGE_TIME" ); + AI_ENEMY_RUSH.LinkTo( scriptObject, "AI_ENEMY_RUSH" ); + AI_ENEMY_RETREAT.LinkTo( scriptObject, "AI_ENEMY_RETREAT" ); + AI_ENEMY_HEALTH_LOW.LinkTo( scriptObject, "AI_ENEMY_HEALTH_LOW" ); + AI_ENEMY_RESURRECTED.LinkTo( scriptObject, "AI_ENEMY_RESURRECTED" ); + AI_ALLOW_ORDERS.LinkTo( scriptObject, "AI_ALLOW_ORDERS" ); + AI_ONGROUND.LinkTo( scriptObject, "AI_ONGROUND" ); + AI_ENEMY_SHOOTABLE.LinkTo( scriptObject, "AI_ENEMY_SHOOTABLE" ); + AI_ENEMY_LAST_SEEN.LinkTo( scriptObject, "AI_ENEMY_LAST_SEEN" ); + AI_SHOTBLOCK.LinkTo( scriptObject, "AI_SHOTBLOCK" ); + AI_NEXT_DIR_TIME.LinkTo( scriptObject, "AI_NEXT_DIR_TIME" ); + AI_BLOCKED_FAILSAFE.LinkTo( scriptObject, "AI_BLOCKED_FAILSAFE" ); + AI_KNOCKBACK.LinkTo( scriptObject, "AI_KNOCKBACK" ); +} + +void hhHunterSimple::Event_GetAlly() { + if ( ally.IsValid() ) { + idEntity *foo = ally.GetEntity(); + if ( ally->GetHealth() > 0 ) { + idThread::ReturnEntity( ally.GetEntity() ); + return; + } else { + ally.Clear(); + } + } + + idBounds bo( GetOrigin() ); + bo.ExpandSelf( 2000 ); + hhHunterSimple *entAI = NULL; + idLocationEntity *myLoc = NULL; + idLocationEntity *entLoc = NULL; + float bestDistSq = 9999999; + float distSq = 0; + entAI = static_cast(gameLocal.FindEntity( spawnArgs.GetString( "ally" ) )); + if ( entAI && entAI->ally.IsValid() ) { + entAI = NULL; + } + if ( !entAI ) { + hhHunterSimple *hunter = NULL; + for ( int i=0;i(hhMonsterAI::allSimpleMonsters[i]); + if ( !hunter || hunter == this || hunter->health <= 0 ) { + continue; + } + if ( !hunter->IsType( hhHunterSimple::Type ) || hunter->IsHidden() || hunter->ally.IsValid() ) { + continue; + } + if ( !hunter->GetEnemy() || hunter->GetEnemy() != GetEnemy() ) { + continue; + } + + //if both locations are null, it likely means this locations haven't been set + //i.e. dev test map so just let the hunters to ally + myLoc = gameLocal.LocationForPoint( GetOrigin() ); + entLoc = gameLocal.LocationForPoint( hunter->GetOrigin() ); + if ( myLoc != entLoc ) { + continue; + } + + distSq = (GetOrigin() - hunter->GetOrigin()).LengthSqr(); + if ( distSq > 2250000 ) { //less than 1500 + continue; + } + if ( distSq < bestDistSq ) { + entAI = hunter; + bestDistSq = distSq; + } + } + } + if ( entAI ) { + ally.Assign( entAI ); + entAI->ally.Assign( this ); + idThread::ReturnEntity( ally.GetEntity() ); + return; + } else { + ally.Clear(); + idThread::ReturnEntity( NULL ); + return; + } +} + +void hhHunterSimple::Event_PrintAction( const char *action ) { + currentAction = action; +} + +void hhHunterSimple::Event_EnemyCanSee() { + trace_t tr; + if ( !enemy.IsValid() ) { + idThread::ReturnInt( false ); + return; + } + + // Check if a trace succeeds from the player to the entity + gameLocal.clip.TracePoint( tr, enemy->GetEyePosition(), GetEyePosition(), MASK_MONSTERSOLID, enemy.GetEntity() ); + if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == this ) ) { + idThread::ReturnInt( true ); + return; + } + idThread::ReturnInt( false ); +} + +void hhHunterSimple::Event_TriggerDelay( idEntity *ent, float delay ) { + if ( !ent ) { + return; + } + if ( delay < 0.0f ) { + delay = 0.0f; + } + ent->PostEventSec( &EV_Activate, delay, this ); +} + +void hhHunterSimple::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); + if ( damageDef && damageDef->GetBool( "pain_knockback", "0" ) ) { + AI_KNOCKBACK = true; + } + hhMonsterAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + AI_LAST_DAMAGE_TIME = MS2SEC(gameLocal.time); +} + +void hhHunterSimple::Event_CallBackup( float delay ) { + idEntity *entAI = gameLocal.FindEntity( spawnArgs.GetString( "backup" ) ); + if ( entAI && entAI->IsType( idAI::Type ) && entAI->spawnArgs.GetBool( "portal", "0" ) ) { + if ( delay < 0.0f ) { + delay = 0.0f; + } + entAI->PostEventSec( &EV_Activate, delay, this ); + } +} + +bool hhHunterSimple::CanSee( idEntity *ent, bool useFov ) { + trace_t tr; + idVec3 eye; + idVec3 toPos; + + if ( ent->IsHidden() ) { + return false; + } + + if ( ent->IsType( idActor::Type ) ) { + idActor *act = static_cast(ent); + + // If this actor is in a vehicle, look at the vehicle, not the actor + if(act->InVehicle()) { + ent = act->GetVehicleInterface()->GetVehicle(); + } + } + + if ( ent->IsType( idActor::Type ) ) { + toPos = ( ( idActor * )ent )->GetEyePosition(); + } else { + toPos = ent->GetPhysics()->GetOrigin(); + } + + if ( useFov && !CheckFOV( toPos ) ) { + return false; + } + + eye = GetEyePosition(); + + if ( InVehicle() ) { + gameLocal.clip.TracePoint( tr, eye, toPos, MASK_SHOT_BOUNDINGBOX, GetVehicleInterface()->GetVehicle() ); // HUMANHEAD JRM + if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == ent ) ) { + return true; + } + } else { + gameLocal.clip.TracePoint( tr, eye, toPos, MASK_SHOT_BOUNDINGBOX, this ); // HUMANHEAD JRM + + //if enemy is close to where the trace hit, he is shootable + if ( tr.fraction < 1.0f && enemy.IsValid() ) { + float hitDist = (GetEyePosition() - tr.endpos).LengthFast(); + float fullDist = (enemy->GetEyePosition() - GetEyePosition()).LengthFast(); + if ( fullDist > 0.0 ) { + if ( hitDist / fullDist > 0.75f ) { + AI_ENEMY_SHOOTABLE = true; + } else { + AI_ENEMY_SHOOTABLE = false; + } + } + } + + if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == ent ) ) { + return true; + } else if ( bSeeThroughPortals && aas ) { + shootTarget = NULL; + int myArea = gameRenderWorld->PointInArea( GetOrigin() ); + int numPortals = gameRenderWorld->NumGamePortalsInArea( myArea ); + if ( numPortals > 0 ) { + int enemyArea = gameRenderWorld->PointInArea( ent->GetOrigin() ); + for ( int i=0;iGetSoundPortal( myArea, i ).areas[0] == enemyArea ) { + //find the portal and set it as this monster's shoottarget + idEntity *spawnedEnt = NULL; + for( spawnedEnt = gameLocal.spawnedEntities.Next(); spawnedEnt != NULL; spawnedEnt = spawnedEnt->spawnNode.Next() ) { + if ( !spawnedEnt->IsType( hhPortal::Type ) ) { + continue; + } + if ( gameRenderWorld->PointInArea( spawnedEnt->GetOrigin() ) == myArea) { + shootTarget = spawnedEnt; + return true; + } + } + } + } + } + } + } + + return false; +} + +void hhHunterSimple::Event_SaySound( const char *soundName ) { + if ( AI_DEAD || ai_skipSpeech.GetBool() || gameLocal.time < nextVoiceTime ) { + return; + } + //if we've got an ally, clear out this sound so that he won't say it + if ( ally.IsValid() ) { + ally->spawnArgs.Set( soundName, "" ); + } + int time; + StartSound( soundName, ( s_channelType )SND_CHANNEL_VOICE2, 0, 0, &time ); + idThread::ReturnFloat( MS2SEC( time ) ); +} + +void hhHunterSimple::Event_OnProjectileLand(hhProjectile *proj) { + const function_t *newstate = NULL; + newstate = GetScriptFunction( "state_AvoidGrenade" ); + shootTarget = proj; + if ( newstate ) { + SetState( newstate ); + UpdateScript(); + } +} + +void hhHunterSimple::Event_CheckRush() { + if ( enemy.IsValid() ) { + float currentDist = (enemy->GetOrigin() - GetOrigin()).Length(); + if ( currentDist < lastEnemyDistance ) { + //enemy is advancing + enemyRushCount++; + if ( enemyRushCount > spawnArgs.GetFloat( "charge_threshold", "3" ) ) { + if ( currentDist < spawnArgs.GetFloat( "rush_distance", "400" ) ) { + AI_ENEMY_RUSH = true; + } + } + } else { + enemyRushCount = 0; + AI_ENEMY_RUSH = false; + } + lastEnemyDistance = currentDist; + } + PostEventSec( &MA_CheckRush, spawnArgs.GetFloat( "rush_freq", "0.5" ) ); +} + +void hhHunterSimple::Event_SetNextVoiceTime( int nextTime ) { + nextVoiceTime = nextTime; +} + +void hhHunterSimple::Event_CheckRetreat() { + if ( enemy.IsValid() ) { + float currentDist = (enemy->GetOrigin() - GetOrigin()).Length(); + if ( currentDist > lastEnemyDistance ) { + //enemy is retreating + enemyRetreatCount++; + if ( enemyRetreatCount > spawnArgs.GetFloat( "retreat_threshold", "3" ) ) { + AI_ENEMY_RETREAT = true; + } + } else { + enemyRetreatCount = 0; + AI_ENEMY_RETREAT = false; + } + } + PostEventSec( &MA_CheckRetreat, spawnArgs.GetFloat( "retreat_freq", "0.5" ) ); +} + +void hhHunterSimple::Event_SayEnemyInfo() { + if ( AI_DEAD || !enemy.IsValid() || !ally.IsValid() || gameLocal.time < nextVoiceTime ) { + idThread::ReturnInt( false ); + return; + } + hhHunterSimple *hunterAlly = static_cast(ally.GetEntity()); + if ( !hunterAlly ) { + idThread::ReturnInt( false ); + return; + } + bool closerToEnemy = false; + idVec3 meToEnemy = enemy->GetOrigin() - GetOrigin(); + idVec3 allyToEnemy = enemy->GetOrigin() - ally->GetOrigin(); + float myDistToEnemy = meToEnemy.LengthFast(); + float allyDistToEnemy = allyToEnemy.LengthFast(); + float distToAlly = (ally->GetOrigin() - GetOrigin()).LengthFast(); + if ( myDistToEnemy < allyDistToEnemy ) { + closerToEnemy = true; + } + if ( !CanSee( enemy.GetEntity(), false ) ) { + hunterAlly->SetNextVoiceTime( gameLocal.time + SEC2MS(1) ); + Event_SaySound( "snd_behind_cover" ); + idThread::ReturnInt( true ); + return; + } + if ( closerToEnemy ) { + //check if enemy is underneath + if ( allyToEnemy.z < -spawnArgs.GetFloat( "z_down_dist", "75" ) ) { + if ( idMath::Fabs(meToEnemy.z) < spawnArgs.GetFloat( "z_equal_dist", "40" ) ) { + Event_SaySound( "snd_down_here" ); + hunterAlly->SetNextVoiceTime( gameLocal.time + SEC2MS(1) ); + idThread::ReturnInt( true ); + return; + } + } + if ( myDistToEnemy < spawnArgs.GetFloat( "over_there_dist", "400" ) ) { + Event_SaySound( "snd_over_here" ); + hunterAlly->SetNextVoiceTime( gameLocal.time + SEC2MS(1) ); + idThread::ReturnInt( true ); + return; + } + } else { + if ( myDistToEnemy > spawnArgs.GetFloat( "over_there_dist", "400" ) ) { + Event_SaySound( "snd_over_there" ); + hunterAlly->SetNextVoiceTime( gameLocal.time + SEC2MS(1) ); + idThread::ReturnInt( true ); + return; + } + } + idThread::ReturnInt( false ); +} + +bool hhHunterSimple::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. + assert( idStr::Icmpn( soundName, "snd_", 4 ) == 0 ); + + if ( !spawnArgs.GetString( soundName, "", &sound ) ) { + return false; + } + + if ( sound[0] == '\0' ) { + return false; + } + + if ( !gameLocal.isNewFrame ) { + // don't play the sound, but don't report an error + return true; + } + + // HUMANHEAD nla - Check if this sound should be played every X seconds + float minInterval; + int lastPlayed; + if ( spawnArgs.GetFloat( va( "min_%s", soundName ), "0", minInterval ) && minInterval > 0 ) { + if ( GetLastTimeSoundPlayed()->GetInt( soundName, "-1", lastPlayed ) ) { + if ( gameLocal.time - minInterval * 1000 < lastPlayed ) { + return( false ); + } + } + GetLastTimeSoundPlayed()->SetInt( soundName, gameLocal.time ); + } else if ( channel == SND_CHANNEL_VOICE2 ) { + //only play voice over sounds once for the hunter + if ( GetLastTimeSoundPlayed()->GetInt( soundName, "-1", lastPlayed ) ) { + if ( lastPlayed > 0 ) { + return( false ); + } + } + GetLastTimeSoundPlayed()->SetInt( soundName, gameLocal.time ); + } + // HUMANHEAD END + + shader = declManager->FindSound( sound ); + bool result; + result = StartSoundShader( shader, channel, soundShaderFlags, broadcast, length ); + if ( result && shader && channel == SND_CHANNEL_VOICE2 ) { + //print debug info for voice overs + currentSpeech = shader->GetName(); + endSpeechTime = gameLocal.time + SEC2MS(3); + } + return result; +} + +void hhHunterSimple::Event_GetCoverNode() { + float dist, bestDist; + idEntity *bestNode = NULL; + idEntity *node = NULL; + if ( !enemy.IsValid() || !EnemyPositionValid() ) { + idThread::ReturnEntity( NULL ); + return; + } + + //find the closest attack node that can see our enemy + bestNode = NULL; + bestDist = 9999999.0f; + idLocationEntity *myLocation = gameLocal.LocationForPoint( physicsObj.GetOrigin() ); + idLocationEntity *entLocation = NULL; + for( int i = 0; i < nodeList.Num(); i++ ) { + if ( !nodeList[i].IsValid() ) { + continue; + } + if ( !nodeList[i]->IsType( hhAINode::Type ) ) { + continue; + } + node = nodeList[ i ].GetEntity(); + if ( !node ) { + continue; + } + + //check if node is used + if ( node->IsType( hhAINode::Type ) ) { + hhAINode *aiNode = static_cast(node); + if ( aiNode && aiNode->user.IsValid() && !aiNode->user.IsEqualTo( this ) ) { + if ( !aiNode->user->IsHidden() && aiNode->user->GetHealth() > 0 ) { + continue; + } else { + aiNode->user.Clear(); + } + } + } + + //skip if node is farther than enemy or enemy is closer to object than i am + float distToNode = TravelDistance( GetOrigin(), node->GetOrigin() ); + float distEnemyToNode = (enemy->GetOrigin() - node->GetOrigin()).Length(); + if ( distEnemyToNode < distToNode || distEnemyToNode < 200 ) { + continue; + } + //skip if object is farther than enemy or if enemy is closer to object than i am + if ( distToNode > 1.0 && distToNode / distEnemyToNode > 2.0f ) { + continue; + } + + //skip if enemy can see the node + trace_t tr; + gameLocal.clip.TracePoint( tr, enemy->GetEyePosition(), node->GetOrigin(), MASK_SHOT_BOUNDINGBOX, this ); + if ( tr.fraction >= 1.0f ) { + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorMagenta, enemy->GetEyePosition(), node->GetOrigin(), 30, 5000 ); + } + continue; + } + + //find closest node + idVec3 org = node->GetPhysics()->GetOrigin(); + dist = ( physicsObj.GetOrigin() - org ).LengthSqr(); + if ( dist > bestDist ) { + continue; + } + + //found a node that passed all tests + bestNode = node; + bestDist = dist; + } + if ( bestNode && bestNode->IsType( hhAINode::Type ) ) { + hhAINode *bestAINode = static_cast(bestNode); + if ( bestAINode ) { + bestAINode->user.Assign( this ); + } + } + if ( bestNode ) { + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorGreen, enemy->GetEyePosition(), bestNode->GetOrigin(), 30, 5000 ); + } + idThread::ReturnVector( bestNode->GetOrigin() ); + return; + } + idThread::ReturnVector( vec3_zero ); +} + +void hhHunterSimple::Event_GetCoverPoint() { + if ( !enemy.IsValid() ) { + idThread::ReturnVector( vec3_zero ); + return; + } + //couldn't find a node. try sampling some nearby points + idVec3 testPoint; + float bestDist = 9999999.0f; + idVec3 finalPoint = vec3_zero; + int finalArea = -1; + bool clipped = false; + float yaw = (GetOrigin() - enemy->GetOrigin()).ToYaw(); + int num; + idClipModel *cm; + idClipModel *clipModels[ MAX_GENTITIES ]; + idBounds bounds; + trace_t tr; + float distance = spawnArgs.GetFloat( "cover_range", "200" ); + float initialYaw = yaw; + for ( int i=0;i<16;i++ ) { + testPoint = GetOrigin() + distance * idAngles( 0, yaw, 0 ).ToForward(); + testPoint.z += 35; + yaw += 22.5; + + if( gameLocal.clip.Contents( testPoint, GetPhysics()->GetClipModel(), viewAxis, CONTENTS_SOLID, this ) != 0 ) { + continue; + } + + //skip if node is farther than enemy or enemy is closer to object than i am + float distToNode = TravelDistance( GetOrigin(), testPoint ); + float distEnemyToNode = (enemy->GetOrigin() - testPoint).Length(); + if ( distEnemyToNode < distToNode || distEnemyToNode < 200 ) { + continue; + } + //skip if object is farther than enemy or if enemy is closer to object than i am + if ( distToNode > 1.0 && distToNode / distEnemyToNode > 2.0f ) { + continue; + } + + //make sure it wont clip into anything at testPoint + clipped = false; + bounds.FromTransformedBounds( GetPhysics()->GetBounds(), testPoint, GetPhysics()->GetAxis() ); + num = gameLocal.clip.ClipModelsTouchingBounds( bounds, MASK_MONSTERSOLID, clipModels, MAX_GENTITIES ); + for ( int j = 0; j < num; j++ ) { + cm = clipModels[ j ]; + // don't check render entities + if ( cm->IsRenderModel() ) { + continue; + } + idEntity *hit = cm->GetEntity(); + if ( ( hit == this ) || !hit->fl.takedamage ) { + continue; + } + if ( physicsObj.ClipContents( cm ) ) { + clipped = true; + break; + } + } + if ( clipped ) { + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugBounds( colorRed, bounds, vec3_origin, 10000 ); + } + continue; + } + + //make sure we can path there + aasPath_t path; + int toAreaNum = PointReachableAreaNum( testPoint ); + int areaNum = PointReachableAreaNum( physicsObj.GetOrigin() ); + if ( !toAreaNum || !PathToGoal( path, areaNum, physicsObj.GetOrigin(), toAreaNum, testPoint ) ) { + continue; + } + + //make sure enemy cant see the point + gameLocal.clip.TracePoint( tr, enemy->GetEyePosition(), testPoint, MASK_SHOT_BOUNDINGBOX, this ); + if ( tr.fraction >= 1.0f ) { + continue; + } + + float distToPoint = (GetOrigin() - testPoint).Length(); + if ( distToPoint < bestDist ) { + finalPoint = testPoint; + bestDist = distToPoint; + finalArea = toAreaNum; + } + } + + if ( finalPoint != vec3_zero ) { + if ( aas ) { + aas->PushPointIntoAreaNum( finalArea, finalPoint ); + } + idThread::ReturnVector( finalPoint ); + return; + } + + idThread::ReturnVector( vec3_zero ); +} + +void hhHunterSimple::Event_GetSightNode() { + if ( !enemy.IsValid() ) { + idThread::ReturnVector( vec3_zero ); + return; + } + + //find a good spot to attack from + trace_t tr; + float distance = spawnArgs.GetFloat( "attack_range", "500" ); + idVec3 testPoint; + idVec3 finalPoint = vec3_zero; + bool clipped = false; + float yaw = (GetOrigin() - enemy->GetOrigin()).ToYaw(); + float initialYaw = yaw; + idEntity *bestNode = NULL; + idEntity *node = NULL; + + //find an attack node that can see our enemy + for( int i = 0; i < nodeList.Num(); i++ ) { + if ( !nodeList[i].IsValid() ) { + continue; + } + if ( !nodeList[i]->IsType( hhAINode::Type ) ) { + continue; + } + node = nodeList[ i ].GetEntity(); + if ( !node ) { + continue; + } + + //check if node is used + if ( node->IsType( hhAINode::Type ) ) { + hhAINode *aiNode = static_cast(node); + if ( aiNode && aiNode->user.IsValid() && !aiNode->user.IsEqualTo( this ) ) { + if ( !aiNode->user->IsHidden() && aiNode->user->GetHealth() > 0 ) { + continue; + } else { + aiNode->user.Clear(); + } + } + } + + //check if we can see enemy from this node + idVec3 eye = testPoint + ( GetPhysics()->GetGravityNormal() * -eyeOffset.z ); + //HUMANHEAD jsh PCF 4/27/06 changed trace to end at enemy origin instead of enemy eye position to fix slowdown + gameLocal.clip.TracePoint( tr, eye, enemy->GetOrigin(), MASK_SHOT_BOUNDINGBOX, enemy.GetEntity() ); + if ( tr.fraction < 1.0f || ( gameLocal.GetTraceEntity( tr ) != enemy.GetEntity() ) ) { + continue; + } + + //found a node that passed all tests + bestNode = node; + } + if ( bestNode && bestNode->IsType( hhAINode::Type ) ) { + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorGreen, GetOrigin(), bestNode->GetOrigin(), 10, 5000 ); + } + idThread::ReturnVector( bestNode->GetOrigin() ); + return; + } + idThread::ReturnVector( vec3_zero ); +} + +void hhHunterSimple::Event_GetNearSightPoint() { + trace_t tr; + float distance = spawnArgs.GetFloat( "attack_range", "500" ); + idVec3 testPoint; + idVec3 finalPoint = vec3_zero; + bool clipped = false; + float yaw = 0; + float initialYaw = 0; + int num, i, j; + idClipModel *cm; + idClipModel *clipModels[ MAX_GENTITIES ]; + idBounds bounds; + int finalArea = -1; + idList listo; + + if ( !enemy.IsValid() ) { + idThread::ReturnVector( vec3_zero ); + return; + } + + yaw = (GetOrigin() - enemy->GetOrigin()).ToYaw(); + initialYaw = yaw; + //test 8 points around the monster, starting with one directly in front of it + for ( i=0;i<32;i++ ) { + testPoint = GetOrigin() + distance * idAngles( 0, yaw, 0 ).ToForward(); + testPoint.z += spawnArgs.GetFloat( "attack_z", "300" ); + yaw += 11.25; + + //make sure it wont clip into anything at testPoint + clipped = false; + bounds.FromTransformedBounds( GetPhysics()->GetBounds(), testPoint, GetPhysics()->GetAxis() ); + num = gameLocal.clip.ClipModelsTouchingBounds( bounds, MASK_MONSTERSOLID, clipModels, MAX_GENTITIES ); + for ( j = 0; j < num; j++ ) { + cm = clipModels[ j ]; + if ( cm->IsRenderModel() ) { + continue; + } + idEntity *hit = cm->GetEntity(); + if ( ( hit == this ) || !hit->fl.takedamage ) { + continue; + } + if ( physicsObj.ClipContents( cm ) ) { + clipped = true; + break; + } + } + if ( clipped ) { + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugBounds( colorRed, bounds, vec3_origin, 10000 ); + } + continue; + } + + //make sure we can path there + int toAreaNum = PointReachableAreaNum( testPoint ); + int areaNum = PointReachableAreaNum( physicsObj.GetOrigin() ); + aasPath_t path; + if ( !toAreaNum || !PathToGoal( path, areaNum, physicsObj.GetOrigin(), toAreaNum, testPoint ) ) { + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorYellow, GetOrigin(), testPoint, 10, 5000 ); + } + continue; + } + + //make sure we can see enemy there + idVec3 eye = testPoint + ( GetPhysics()->GetGravityNormal() * -eyeOffset.z ); + gameLocal.clip.TracePoint( tr, eye, enemy->GetEyePosition(), MASK_SHOT_BOUNDINGBOX, enemy.GetEntity() ); + if ( tr.fraction < 1.0f ) { + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorRed, eye, enemy->GetEyePosition(), 10, 5000 ); + } + continue; + } + + listo.Append( testPoint ); + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorGreen, GetOrigin(), testPoint, 10, 5000 ); + } + finalArea = toAreaNum; + } + if ( listo.Num() ) { + finalPoint = listo[gameLocal.random.RandomInt(listo.Num())]; + } + if ( finalPoint != vec3_zero ) { + if ( aas && finalArea != -1 ) { + aas->PushPointIntoAreaNum( finalArea, finalPoint ); + } + idThread::ReturnVector( finalPoint ); + return; + } + idThread::ReturnVector( vec3_zero ); +} + +void hhHunterSimple::Show() { + flashlightTime = gameLocal.time + SEC2MS( spawnArgs.GetInt( "flashlight_delay" ) ); + hhMonsterAI::Show(); +} + +void hhHunterSimple::Event_EnemyPortal( idEntity *ent ) { + //HUMANHEAD jsh PCF 4/27/06 Added check for enemy and hiddenness + if ( !enemy.IsValid() || AI_PATHING || fl.hidden ) { + return; + } + enemyPortal = ent; + SetState( GetScriptFunction( "state_PortalAttack" ) ); +} + +void hhHunterSimple::Event_GetEnemyPortal() { + if ( enemyPortal.IsValid() ) { + idThread::ReturnEntity( enemyPortal.GetEntity() ); + } else { + idThread::ReturnEntity( NULL ); + } +} + +void hhHunterSimple::Event_OnProjectileHit(hhProjectile *proj) { + if ( !enemy.IsValid() || !proj || AI_VEHICLE ) { + return; + } + if ( ( proj->GetOrigin().ToVec2() - GetOrigin().ToVec2() ).LengthFast() < 150 ) { + AI_SHOTBLOCK = true; + } +} + +void hhHunterSimple::BlockedFailSafe() { + if ( move.moveCommand < NUM_NONMOVING_COMMANDS || !ai_blockedFailSafe.GetBool() || blockedRadius < 0.0f || !enemy.IsValid() || AI_PATHING ) { + return; + } + if ( gameLocal.time > nextBlockCheckTime ) { + if ( ( lastMoveOrigin - physicsObj.GetOrigin() ).Length() < blockedRadius ) { + AI_BLOCKED_FAILSAFE = true; + } + lastMoveOrigin = physicsObj.GetOrigin(); + nextBlockCheckTime = gameLocal.time + blockedMoveTime; + } +} +void hhHunterSimple::UpdateEnemyPosition( void ) { + idActor *enemyEnt = enemy.GetEntity(); + int enemyAreaNum; + int areaNum; + aasPath_t path; + predictedPath_t predictedPath; + idVec3 enemyPos; + bool onGround; + + if ( !enemyEnt ) { + return; + } + + const idVec3 &org = physicsObj.GetOrigin(); + + if ( move.moveType == MOVETYPE_FLY ) { + enemyPos = enemyEnt->GetPhysics()->GetOrigin(); + onGround = true; + } else { + onGround = enemyEnt->GetFloorPos( 64.0f, enemyPos ); + if ( enemyEnt->OnLadder() ) { + onGround = false; + } + } + + if ( onGround ) { + // when we don't have an AAS, we can't tell if an enemy is reachable or not, + // so just assume that he is. + if ( !aas ) { + enemyAreaNum = 0; + lastReachableEnemyPos = enemyPos; + } else { + enemyAreaNum = PointReachableAreaNum( enemyPos, 1.0f ); + if ( enemyAreaNum ) { + areaNum = PointReachableAreaNum( org ); + if ( PathToGoal( path, areaNum, org, enemyAreaNum, enemyPos ) ) { + lastReachableEnemyPos = enemyPos; + } + } + } + } + + AI_ENEMY_IN_FOV = false; + AI_ENEMY_VISIBLE = false; + + if ( CanSee( enemyEnt, false ) ) { + AI_ENEMY_VISIBLE = true; + if ( CheckFOV( enemyEnt->GetPhysics()->GetOrigin() ) ) { + AI_ENEMY_IN_FOV = true; + } + + SetEnemyPosition(); + } + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugBounds( colorLtGrey, enemyEnt->GetPhysics()->GetBounds(), lastReachableEnemyPos, gameLocal.msec ); + gameRenderWorld->DebugBounds( colorWhite, enemyEnt->GetPhysics()->GetBounds(), lastVisibleReachableEnemyPos, gameLocal.msec ); + } +} + +//HUMANHEAD jsh PCF 4/27/06 increased the upper z zalue of the bounds to 90.0f +bool hhHunterSimple::ReachedPos( const idVec3 &pos, const moveCommand_t moveCommand ) const { + if ( move.moveType == MOVETYPE_SLIDE ) { + idBounds bnds( idVec3( -4, -4.0f, -8.0f ), idVec3( 4.0f, 4.0f, 64.0f ) ); + bnds.TranslateSelf( physicsObj.GetOrigin() ); + if ( bnds.ContainsPoint( pos ) ) { + return true; + } + } else { + if ( ( moveCommand == MOVE_TO_ENEMY ) || ( moveCommand == MOVE_TO_ENTITY ) ) { + if ( physicsObj.GetAbsBounds().IntersectsBounds( idBounds( pos ).Expand( 8.0f ) ) ) { + return true; + } + } else { + idBounds bnds( idVec3( -16.0, -16.0f, -8.0f ), idVec3( 16.0, 16.0f, 90.0f ) ); + bnds.TranslateSelf( physicsObj.GetOrigin() ); + if ( bnds.ContainsPoint( pos ) ) { + return true; + } + } + } + return false; +} + +void hhHunterSimple::Event_EnemyIsSpirit( hhPlayer *player, hhSpiritProxy *proxy ) { + HH_ASSERT( player == enemy.GetEntity() ); + //HUMANHEAD jsh PCF 4/29/06 stop looking and make enemy not visible for a frame + Event_LookAtEntity( NULL, 0.0f ); + AI_ENEMY_VISIBLE = false; + enemy = proxy; + nextSpiritCheck = gameLocal.time + SEC2MS( spawnArgs.GetFloat( "spirit_check_delay", "5" ) ); +} + +void hhHunterSimple::Distracted( idActor *newEnemy ) { + if ( newEnemy && newEnemy->IsType( hhTalon::Type ) ) { + SetState( GetScriptFunction( "state_TalonCombat" ) ); + } else { + SetState( GetScriptFunction( "state_Combat" ) ); + } + + SetEnemy( newEnemy ); +} + +void hhHunterSimple::Event_EnemyVehicleDocked() { + if ( enemy.IsValid() && enemy->InVehicle() && enemy->GetVehicleInterface() ) { + hhVehicle *vehicle = enemy->GetVehicleInterface()->GetVehicle(); + if ( vehicle && vehicle->IsDocked() ) { + idThread::ReturnInt( true ); + return; + } + } + idThread::ReturnInt( false ); +} + +void hhHunterSimple::Event_Blocked() { + AI_NEXT_DIR_TIME = 0; + StopMove( MOVE_STATUS_DONE ); +} \ No newline at end of file diff --git a/src/Prey/ai_hunter_simple.h b/src/Prey/ai_hunter_simple.h new file mode 100644 index 0000000..3e1c199 --- /dev/null +++ b/src/Prey/ai_hunter_simple.h @@ -0,0 +1,128 @@ + +#ifndef __PREY_AI_HUNTER_SIMPLE_H__ +#define __PREY_AI_HUNTER_SIMPLE_H__ + +#define HUNTER_N 0 +#define HUNTER_NE 1 +#define HUNTER_E 2 +#define HUNTER_SE 3 +#define HUNTER_S 4 +#define HUNTER_SW 5 +#define HUNTER_W 6 +#define HUNTER_NW 7 + +class hhHunterSimple : public hhMonsterAI { + +public: + + CLASS_PROTOTYPE(hhHunterSimple); + + hhHunterSimple(); + void Event_OnProjectileLaunch(hhProjectile *proj); + void Event_OnProjectileHit(hhProjectile *proj); + void Event_LaserOn(); + void Event_LaserOff(); + void Event_FindReaction( const char* effect ); + + void Spawn(); + void Think(); + idProjectile *LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone, const idDict* desiredProjectileDef ); + void AnimMove(); + bool UpdateAnimationControllers(); + 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 PrintDebug(); + virtual void LinkScriptVariables(); + bool TurnToward( const idVec3 &pos ); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + bool StartSound( const char *soundName, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ); + void SetNextVoiceTime( int newTime ) { nextVoiceTime = newTime; } + void Show(); + void BlockedFailSafe(); + void UpdateEnemyPosition(); + void Distracted( idActor *newEnemy ); + bool ReachedPos( const idVec3 &pos, const moveCommand_t moveCommand ) const; + + void Event_EscapePortal(); + void Event_AssignSniperFx( hhEntityFx* fx ); + void Event_AssignMuzzleFx( hhEntityFx* fx ); + void Event_AssignFlashLightFx( hhEntityFx* fx ); + void Event_KickAngle( const idAngles &ang ); + void Event_FlashLightOn(); + void Event_FlashLightOff(); + void Event_PostSpawn(); + void Event_EnemyCanSee(); + void Event_PrintAction( const char *action ); + void Event_GetAlly(); + void Event_TriggerDelay( idEntity *ent, float delay ); + void Event_CallBackup( float delay ); + virtual bool CanSee( idEntity *ent, bool useFov ); + void Event_GetAdvanceNode(); + void Event_GetRetreatNode(); + void Event_OnProjectileLand(hhProjectile *proj); + void Event_SaySound( const char *soundName ); + void Event_CheckRush(); + void Event_CheckRetreat(); + void Event_SayEnemyInfo(); + void Event_SetNextVoiceTime( int nextTime ); + void Event_GiveOrders( const char *orders ); + void Event_AllowOrders( bool orders ); + void Event_GetCoverNode(); + void Event_GetCoverPoint(); + void Event_GetSightNode(); + void Event_GetNearSightPoint(); + void Event_Blocked(); + void Event_EnemyPortal( idEntity *portal ); + void Event_GetEnemyPortal(); + void Event_EnemyVehicleDocked(); + void Event_EnemyIsSpirit( hhPlayer *player, hhSpiritProxy *proxy ); //HUMANHEAD jsh PCF 5/2/06 hunter combat fixes + + idEntityPtr ally; + idScriptBool AI_ALLOW_ORDERS; + idScriptBool AI_SHOTBLOCK; +protected: + idVec3 nextMovePos; + idEntityPtr beamLaser; + idAngles kickAngles; + idAngles kickSpeed; + idEntityPtr sniperFx; + idEntityPtr muzzleFx; + idEntityPtr flashLightFx; + idList< idEntityPtr< idEntity > > nodeList; + bool alternateAccuracy; + bool bFlashLight; + idScriptInt AI_DIR; + idScriptFloat AI_LAST_DAMAGE_TIME; + idStr currentAction; + idStr currentSpeech; + int endSpeechTime; + idScriptBool AI_DIR_MOVEMENT; + idScriptBool AI_ENEMY_RUSH; + idScriptBool AI_ENEMY_RETREAT; + idScriptBool AI_ENEMY_HEALTH_LOW; + idScriptBool AI_ENEMY_RESURRECTED; + idScriptBool AI_ONGROUND; + idScriptBool AI_ENEMY_SHOOTABLE; + idScriptBool AI_BLOCKED_FAILSAFE; + idScriptInt AI_ENEMY_LAST_SEEN; + idScriptInt AI_NEXT_DIR_TIME; + idScriptBool AI_KNOCKBACK; + int nextEnemyCheck; + float lastEnemyDistance; + int enemyRushCount; + int enemyRetreatCount; + int enemyHiddenCount; + int lastChargeTime; + int nextVoiceTime; + idVec3 initialOrigin; + float flashlightLength; + int flashlightTime; + idEntityPtr enemyPortal; + idVec3 lastMoveOrigin; + int nextBlockCheckTime; + int nextSpiritCheck; //HUMANHEAD jsh PCF 5/2/06 hunter combat fixes +}; + +#endif /* __PREY_AI_HUNTER_SIMPLE_H__ */ + diff --git a/src/Prey/ai_inspector.cpp b/src/Prey/ai_inspector.cpp new file mode 100644 index 0000000..7e1a9dd --- /dev/null +++ b/src/Prey/ai_inspector.cpp @@ -0,0 +1,136 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef MA_CheckReactions( "", "e" ); + +CLASS_DECLARATION( hhMonsterAI, hhAIInspector ) + EVENT( EV_PostSpawn, hhAIInspector::Event_PostSpawn ) + EVENT( MA_CheckReactions, hhAIInspector::Event_CheckReactions ) +END_CLASS + +void hhAIInspector::Spawn() { +} + +void hhAIInspector::Event_PostSpawn() { + const function_t* func = GetScriptFunction( "state_InspectorIdle" ); + if( !func ) { + gameLocal.Warning( "unable to find state_InspectorIdle" ); + return; + } + SetState( func ); +} + +void hhAIInspector::CheckReactions( idEntity* entity ) { + PostEventMS( &MA_CheckReactions, 1000, entity ); +} + +void hhAIInspector::Event_CheckReactions( idEntity* entity ) { + checkReaction = entity; + targetReaction.entity = entity; + targetReaction.reactionIndex = 0; + if( !targetReaction.GetReaction() ) { + targetReaction.reactionIndex = -1; + gameLocal.Warning( "unable to properly use requested reaction... possibly no reactions on this entity" ); + return; + } + + const function_t* func = GetScriptFunction( "state_InspectorReactions" ); + if( !func ) { + gameLocal.Warning( "unable to find checkreaction state on inspector..." ); + return; + } + SetState( func ); +} + +void hhAIInspector::RestartInspector( const char* inspectClass, idEntity* newReaction, idPlayer* starter ) { + float yaw; + idVec3 org; + idDict spawnDict; + idEntity* react_scout; + idEntity* use_reaction = NULL; + +//Find our dictionary to use + const idDict* edict = gameLocal.FindEntityDefDict( inspectClass, false ); + if( !edict ) { + gameLocal.Printf( "Unable to find entitfydef '%s'", inspectClass ); + return; + } + spawnDict = *edict; //copy the values to an editable dict + +//Setup values for position/orientation + yaw = starter->viewAngles.yaw; + org = starter->GetPhysics()->GetOrigin() + idAngles( 0, yaw, 0 ).ToForward() * 180 + idVec3( 0, 0, 1 ); + +// Set dictionary values... + spawnDict.Set( "angle", va("%f", yaw + 180) ); + spawnDict.Set( "origin", org.ToString() ); + spawnDict.Set( "spawnClass", "hhAIInspector" ); + +// Perform actual spawn + react_scout = static_cast(gameLocal.SpawnEntityType(hhAIInspector::Type, &spawnDict)); + if( !react_scout ) { + gameLocal.Printf( "failed to spawn inspector monster..." ); + return; + } + + use_reaction = newReaction; + + if( gameLocal.inspector != NULL ) { +//save our old use_reaction if we haven't specified one + if( !use_reaction && gameLocal.inspector.IsValid() && gameLocal.inspector->checkReaction.IsValid() ) { + use_reaction = gameLocal.inspector->checkReaction.GetEntity(); + } +// Remove our old inspector + gameLocal.inspector->PostEventMS( &EV_Remove, 0 ); + } + gameLocal.inspector = react_scout; + if( use_reaction ) { + gameLocal.inspector->CheckReactions( use_reaction ); + } +} + +void hhAIInspector::RestartInspector( idEntity* newReaction, idPlayer* starter ) { + idEntity* react_scout; + float yaw; + idVec3 org; + + yaw = starter->viewAngles.yaw; + org = starter->GetPhysics()->GetOrigin() + idAngles( 0, yaw, 0 ).ToForward() * 180 + idVec3( 0, 0, 1 ); + + idDict dict = gameLocal.inspector.GetEntity()->spawnArgs; + + dict.Set( "angle", va("%f", yaw + 180) ); + dict.Set( "origin", org.ToString() ); + react_scout = static_cast(gameLocal.SpawnEntityType(hhAIInspector::Type, &dict)); + if( !react_scout ) { + gameLocal.Printf( "failed to spawn inspector monster..." ); + return; + } + + if( gameLocal.inspector != NULL ) { + gameLocal.inspector->PostEventMS( &EV_Remove, 0 ); + } + gameLocal.inspector = react_scout; + gameLocal.inspector->CheckReactions( newReaction ); +} + +/* +===================== +hhAIInspector::Save +===================== +*/ +void hhAIInspector::Save( idSaveGame *savefile ) const { + checkReaction.Save( savefile ); +} + +/* +===================== +hhAIInspector::Restore +===================== +*/ +void hhAIInspector::Restore( idRestoreGame *savefile ) { + checkReaction.Restore( savefile ); +} + diff --git a/src/Prey/ai_inspector.h b/src/Prey/ai_inspector.h new file mode 100644 index 0000000..1675bfe --- /dev/null +++ b/src/Prey/ai_inspector.h @@ -0,0 +1,28 @@ +#ifndef __PREY_AI_INSPECTOR_H__ +#define __PREY_AI_INSPECTOR_H__ + +class hhAIInspector : public hhMonsterAI { + +public: + CLASS_PROTOTYPE(hhAIInspector); + +public: + void Spawn(); + void CheckReactions( idEntity* entity ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + static void RestartInspector( const char* inspectClass, idEntity* newReaction, idPlayer* starter ); + static void RestartInspector( idEntity* newReaction, idPlayer* starter ); + +public: +// Events. + void Event_PostSpawn(); + void Event_CheckReactions( idEntity* entity ); + +protected: + idEntityPtr checkReaction; + +}; + +#endif diff --git a/src/Prey/ai_jetpack_harvester_simple.cpp b/src/Prey/ai_jetpack_harvester_simple.cpp new file mode 100644 index 0000000..9246289 --- /dev/null +++ b/src/Prey/ai_jetpack_harvester_simple.cpp @@ -0,0 +1,662 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef AI_RemoveJetFx("removeJetFx"); +const idEventDef AI_CreateJetFx("createJetFx"); +const idEventDef AI_LaunchMine("launchMine", "d"); +const idEventDef MA_IsEnemySniping("isEnemySniping", NULL, 'd'); +const idEventDef MA_NumMines("numMines", NULL, 'f'); +const idEventDef MA_DodgeProjectile("", "e"); +const idEventDef MA_AppendHoverFx( "", "e" ); + +CLASS_DECLARATION( hhMonsterAI, hhJetpackHarvesterSimple ) + EVENT( AI_RemoveJetFx, hhJetpackHarvesterSimple::Event_RemoveJetFx ) + EVENT( EV_Activate, hhJetpackHarvesterSimple::Event_Activate) + EVENT( AI_CreateJetFx, hhJetpackHarvesterSimple::Event_CreateJetFx ) + EVENT( AI_LaunchMine, hhJetpackHarvesterSimple::Event_LaunchMine ) + EVENT( EV_Broadcast_AppendFxToList, hhJetpackHarvesterSimple::Event_AppendToThrusterList ) + EVENT( MA_OnProjectileLaunch, hhJetpackHarvesterSimple::Event_OnProjectileLaunch ) + EVENT( MA_DodgeProjectile, hhJetpackHarvesterSimple::Event_DodgeProjectile ) + EVENT( MA_IsEnemySniping, hhJetpackHarvesterSimple::Event_IsEnemySniping ) + EVENT( MA_NumMines, hhJetpackHarvesterSimple::Event_NumMines ) + EVENT( MA_AppendHoverFx, hhJetpackHarvesterSimple::Event_AppendHoverFx ) +END_CLASS + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + +hhJetpackHarvesterSimple::~hhJetpackHarvesterSimple() { + for ( int i=0;iGetPhysics()->GetAxis(); + collision.endpos = mineList[i]->GetPhysics()->GetOrigin(); + collision.c.point = mineList[i]->GetPhysics()->GetOrigin(); + collision.c.normal.Set( 0, 0, 1 ); + mineList[i]->Explode( &collision, idVec3(0,0,0), 0 ); + } + } +} + +void hhJetpackHarvesterSimple::Spawn(void) { + lastAntiProjectileAttack = gameLocal.GetTime(); + for(int i=0;iGetJointWorldTransform( bones[i], bonePos, boneAxis ); + fxInfo.SetEntity( this ); + fxInfo.SetBindBone( bones[i] ); + fxInfo.Toggle(); + fxInfo.RemoveWhenDone( false ); + + if( fxThrusters[ThrustType_Idle][i] != NULL ) { + gameLocal.Warning("Jetpack harvester already had flamejet made."); + } + + BroadcastFxInfo( psystem, bonePos, boneAxis, &fxInfo, &EV_Broadcast_AppendFxToList ); + } + } + + psystem = spawnArgs.GetString( "fx_thruster_hover" ); + if ( psystem && *psystem ) { + for(int i=0;i* parmList ) { + if( !parmList || parmList->Num() != 2 ) { + return; + } + + idVec3 jointPos; + idMat3 jointAxis; + const char* projectileDefName = (*parmList)[0].c_str(); + const char* jointName = (*parmList)[1].c_str(); + + if( !projectileDefName || !projectileDefName[0] ) { + return; + } + + if( !GetJointWorldTransform(jointName, jointPos, jointAxis) ) { + return; + } + + hhHarvesterMine* mine = static_cast( gameLocal.SpawnObject(projectileDefName) ); + mine->Create( this, jointPos, jointAxis ); + mine->Launch( jointPos, jointAxis, physicsObj.GetPushedLinearVelocity() ); +} + +void hhJetpackHarvesterSimple::Hide( void ) { + hhMonsterAI::Hide(); + + for(int i=0;iNozzle(FALSE); + } + if(fxThrusters[ThrustType_Hover][i].GetEntity()) { + fxThrusters[ThrustType_Hover][i]->Nozzle(FALSE); + } + } +} + +void hhJetpackHarvesterSimple::Show( void ) { + hhMonsterAI::Show(); + + for(int i=0;iNozzle(TRUE); + } + if(fxThrusters[ThrustType_Hover][i].GetEntity()) { + fxThrusters[ThrustType_Hover][i]->Nozzle(FALSE); + } + } +} + +idProjectile *hhJetpackHarvesterSimple::LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone, const idDict* desiredProjectileDef ) { //HUMANHEAD mdc - added desiredProjectileDef for supporting multiple projs. + idVec3 muzzle; + idVec3 dir; + idVec3 start; + trace_t tr; + idBounds projBounds; + float distance; + const idClipModel *projClip; + float attack_accuracy; + float attack_cone; + float projectile_spread; + float diff; + float angle; + float spin; + idAngles ang; + int num_projectiles; + int i; + idMat3 axis; + idVec3 tmp; + idProjectile *lastProjectile; + + //HUMANHEAD mdc - added to support multiple projectiles + if( desiredProjectileDef ) { //try to set our projectile to the desiredProjectile + int projIndex = FindProjectileInfo( desiredProjectileDef ); + if( projIndex >= 0 ) { + SetCurrentProjectile( projIndex ); + } + } + //HUMANHEAD END + + + if ( !projectileDef ) { + gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() ); + return NULL; + } + + attack_accuracy = spawnArgs.GetFloat( "attack_accuracy", "7" ); + attack_cone = spawnArgs.GetFloat( "attack_cone", "70" ); + projectile_spread = desiredProjectileDef->GetFloat( "projectile_spread", "0" ); + num_projectiles = desiredProjectileDef->GetInt( "num_projectiles", "1" ); + idVec3 launch_velocity = vec3_zero; + if ( desiredProjectileDef->GetFloat( "launch_y", "0" ) > 0.0f ) { + if ( gameLocal.random.RandomFloat() < 0.5 ) { + launch_velocity.y = -desiredProjectileDef->GetFloat( "launch_y", "0" ); + } else { + launch_velocity.y = desiredProjectileDef->GetFloat( "launch_y", "0" ); + } + launch_velocity *= viewAxis; + } + + GetMuzzle( jointname, muzzle, axis ); + + if ( !projectile.GetEntity() ) { + CreateProjectile( muzzle, axis[ 0 ] ); + } + + lastProjectile = projectile.GetEntity(); + + if ( target != NULL ) { + tmp = target->GetPhysics()->GetAbsBounds().GetCenter() - muzzle; + tmp.Normalize(); + axis = tmp.ToMat3(); + } else { + axis = viewAxis; + } + + // rotate it because the cone points up by default + tmp = axis[2]; + axis[2] = axis[0]; + axis[0] = -tmp; + + // make sure the projectile starts inside the monster bounding box + const idBounds &ownerBounds = physicsObj.GetAbsBounds(); + projClip = lastProjectile->GetPhysics()->GetClipModel(); + projBounds = projClip->GetBounds().Rotate( axis ); + + // 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( muzzle, viewAxis[ 0 ], distance ) ) { + start = muzzle + 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(); + } + + gameLocal.clip.Translation( tr, start, muzzle, projClip, axis, MASK_SHOT_RENDERMODEL, this ); + muzzle = tr.endpos; + + // set aiming direction + GetAimDir( muzzle, target, this, dir ); + ang = dir.ToAngles(); + + // adjust his aim so it's not perfect. uses sine based movement so the tracers appear less random in their spread. + float t = MS2SEC( gameLocal.time + entityNumber * 497 ); + ang.yaw += idMath::Sin16( t * 6.7 ) * attack_accuracy; + + if ( clampToAttackCone ) { + // 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, current_yaw ); + if ( diff > attack_cone ) { + ang.yaw = current_yaw + attack_cone; + } else if ( diff < -attack_cone ) { + ang.yaw = current_yaw - attack_cone; + } + } + + axis = ang.ToMat3(); + + float spreadRad = DEG2RAD( projectile_spread ); + for( i = 0; i < num_projectiles; i++ ) { + // spread the projectiles out + angle = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() ); + spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat(); + //dir = axis[ 0 ] + axis[ 2 ] * ( 0 * idMath::Sin( spin ) ) - axis[ 1 ] * ( angle * idMath::Cos( spin ) ); + dir = axis[ 0 ] - axis[ 1 ] * ( angle * idMath::Cos( spin ) ); + dir.Normalize(); + + // launch the projectile + if ( !projectile.GetEntity() ) { + CreateProjectile( muzzle, dir ); + } + lastProjectile = projectile.GetEntity(); + lastProjectile->Launch( muzzle, dir, launch_velocity ); + projectile = NULL; + if ( projectileDef->GetBool( "mine", "0" ) ) { + idEntityPtr &entityPtr = mineList.Alloc(); + entityPtr = lastProjectile; + } + } + + TriggerWeaponEffects( muzzle, axis ); + + lastAttackTime = gameLocal.time; + +//HUMANHEAD mdc - added to support multiple projectiles + projectile = NULL; + SetCurrentProjectile( projectileDefaultDefIndex ); //set back to our default projectile to be on the safe side +//HUMANHEAD END + + return lastProjectile; +} + +void hhJetpackHarvesterSimple::AnimMove( void ) { + idVec3 goalPos; + idVec3 delta; + idVec3 goalDelta; + float goalDist; + monsterMoveResult_t moveResult; + idVec3 newDest; + + idVec3 oldorigin = physicsObj.GetOrigin(); +#ifdef HUMANHEAD //jsh wallwalk + idMat3 oldaxis = GetGravViewAxis(); +#else + idMat3 oldaxis = viewAxis; +#endif + + physicsObj.UseFlyMove( false ); + + AI_BLOCKED = false; + + if ( move.moveCommand < NUM_NONMOVING_COMMANDS ){ + move.lastMoveOrigin.Zero(); + move.lastMoveTime = gameLocal.time; + } + + move.obstacle = NULL; + if ( ( move.moveCommand == MOVE_FACE_ENEMY ) && enemy.GetEntity() ) { + TurnToward( lastVisibleEnemyPos ); + goalPos = oldorigin; + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + goalPos = oldorigin; + } else if ( GetMovePos( goalPos ) ) { + if ( move.moveCommand != MOVE_WANDER ) { + CheckObstacleAvoidance( goalPos, newDest ); + TurnToward( newDest ); + } else { + TurnToward( goalPos ); + } + } + + Turn(); + + if ( move.moveCommand == MOVE_SLIDE_TO_POSITION ) { + if ( gameLocal.time < move.startTime + move.duration ) { + goalPos = move.moveDest - move.moveDir * MS2SEC( move.startTime + move.duration - gameLocal.time ); + delta = goalPos - oldorigin; + delta.z = 0.0f; + } else { + delta = move.moveDest - oldorigin; + delta.z = 0.0f; + StopMove( MOVE_STATUS_DONE ); + } + } else if ( allowMove ) { +#ifdef HUMANHEAD //jsh wallwalk + GetMoveDelta( oldaxis, GetGravViewAxis(), delta ); +#else + GetMoveDelta( oldaxis, viewAxis, delta ); +#endif + } else { + delta.Zero(); + } + + if ( move.moveCommand == MOVE_TO_POSITION ) { + goalDelta = move.moveDest - oldorigin; + goalDist = goalDelta.LengthFast(); + if ( goalDist < delta.LengthFast() ) { + delta = goalDelta; + } + } + + delta *= spawnArgs.GetFloat( "fly_scale", "1.0" ); + + physicsObj.SetDelta( delta ); + physicsObj.ForceDeltaMove( disableGravity ); + + RunPhysics(); + + if ( ai_debugMove.GetBool() ) { + // HUMANHEAD JRM - so we can see if grav is on or off + if(disableGravity) { + gameRenderWorld->DebugLine( colorRed, oldorigin, physicsObj.GetOrigin(), 5000 ); + } else { + gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 5000 ); + } + } + + moveResult = physicsObj.GetMoveResult(); + if ( !af_push_moveables && attack.Length() && TestMelee() ) { + DirectDamage( attack, enemy.GetEntity() ); + } else { + idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); + if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) { + KickObstacles( viewAxis[ 0 ], kickForce, blockEnt ); + } + } + + BlockedFailSafe(); + + AI_ONGROUND = physicsObj.OnGround(); + + idVec3 org = physicsObj.GetOrigin(); + if ( oldorigin != org ) { + TouchTriggers(); + } + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, gameLocal.msec ); + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec ); + gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true ); + DrawRoute(); + } +} + +void hhJetpackHarvesterSimple::Event_NumMines() { + //we only want valid mines, so remove dead ones + for ( int i=0;ihealth <= 0 ) { + mineList.RemoveIndex(i); + } + } + idThread::ReturnFloat( float(mineList.Num()) ); +} + +void hhJetpackHarvesterSimple::Event_DodgeProjectile( hhProjectile *proj ) { + if ( !proj || !enemy.IsValid() ) { + return; + } + float min = spawnArgs.GetFloat( "dda_dodge_min", "0.3" ); + float max = spawnArgs.GetFloat( "dda_dodge_max", "0.8" ); + + float dodgeChance = (min + (max-min)*gameLocal.GetDDAValue() ); + + if ( ai_debugBrain.GetBool() ) { + gameLocal.Printf( "%s dodge chance %f\n", GetName(), dodgeChance ); + } + if ( gameLocal.random.RandomFloat() > dodgeChance ) { + return; + } + + idVec3 fw = viewAxis[0]; + idVec3 projFw = proj->GetAxis()[0]; + if(proj->GetOwner()) + projFw = proj->GetOwner()->GetAxis()[0]; + float dot = fw * projFw; + if(dot > -.7f) + return; + const function_t *newstate = NULL; + + //determine which side to dodge to + idVec3 povPos, targetPos; + povPos = enemy->GetPhysics()->GetOrigin(); + targetPos = GetPhysics()->GetOrigin(); + idVec3 povToTarget = targetPos - povPos; + povToTarget.z = 0.f; + povToTarget.Normalize(); + idVec3 povLeft, povUp; + povToTarget.OrthogonalBasis(povLeft, povUp); + povLeft.Normalize(); + idVec3 projVel = proj->GetPhysics()->GetLinearVelocity(); + projVel.Normalize(); + dot = povLeft * projVel; + if ( dot < 0 ) { + newstate = GetScriptFunction( "state_DodgeRight" ); + } else { + newstate = GetScriptFunction( "state_DodgeLeft" ); + } + if ( newstate ) { + lastAntiProjectileAttack = gameLocal.GetTime(); + SetState( newstate ); + UpdateScript(); + } +} + +void hhJetpackHarvesterSimple::Event_OnProjectileLaunch( hhProjectile *proj ) { + if ( !proj || !spawnArgs.GetBool( "can_dodge", "1" ) || health <= 0 ) { + return; + } + if ( gameLocal.GetTime() - lastAntiProjectileAttack < 1000 * int(spawnArgs.GetFloat( "dodge_freq", "2.0" )) ) { + return; + } + if( proj->GetOwner() && !(ReactionTo(proj->GetOwner()) & (ATTACK_ON_SIGHT | ATTACK_ON_DAMAGE)) ) { + return; // The person who launched this projectile wasn't someone to worry about + } + if ( !enemy.IsValid() ) { + return; + } + PostEventSec( &MA_DodgeProjectile, spawnArgs.GetFloat( "dodge_delay", "0.5" ), proj ); +} + +bool hhJetpackHarvesterSimple::Collide( const trace_t &collision, const idVec3 &velocity ) { + if ( af.IsActive() && !IsHidden() && !freezeDamage && !specialDamage ) { + const char *fx = spawnArgs.GetString("fx_death"); + if ( fx && *fx ) { + hhFxInfo fxInfo; + fxInfo.RemoveWhenDone(true); + BroadcastFxInfo(fx, GetOrigin(), GetAxis(), &fxInfo); + Hide(); + PostEventMS(&EV_Remove, 1000); + } + } + + return false; +} + +void hhJetpackHarvesterSimple::Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if (!frozen) { + // Being tractored can mess up predeath + fl.canBeTractored = false; + } + HandleNoGore(); + if ( AI_DEAD ) { + AI_PAIN = true; + AI_DAMAGE = true; + return; + } + + //stop rocket fx and sounds + StopSound( SND_CHANNEL_BODY3, false ); + for(int i=0;iGetGravityNormal() * 800, MASK_MONSTERSOLID, this); + if( TraceInfo.fraction < 1.0f ) { + if ( HasPathTo( TraceInfo.endpos ) ) { + Event_SetMoveType( MOVETYPE_ANIM ); + health += 30; + state = GetScriptFunction( "state_StartPreDeath" ); + SetState( state ); + SetWaitState( "" ); + return; + } + } + } + + hhMonsterAI::Killed( inflictor, attacker, damage, dir, location ); + + //explode creature's mines upon death + for ( int i=0;iGetPhysics()->GetAxis(); + collision.endpos = mineList[i]->GetPhysics()->GetOrigin(); + collision.c.point = mineList[i]->GetPhysics()->GetOrigin(); + collision.c.normal.Set( 0, 0, 1 ); + mineList[i]->Explode( &collision, idVec3(0,0,0), 0 ); + } + } +} + +void hhJetpackHarvesterSimple::Event_IsEnemySniping(void) { + idThread::ReturnInt( false ); +} + +/* +===================== +hhJetpackHarvesterSimple::Save +===================== +*/ +void hhJetpackHarvesterSimple::Save( idSaveGame *savefile ) const { + int i; + for ( i = 0; i < ThrustSide_Total; i++ ) { + for ( int j = 0; j < ThrustType_Total; j++ ) { + fxThrusters[i][j].Save( savefile ); + } + } + + savefile->WriteInt( lastAntiProjectileAttack ); + savefile->WriteBool( allowPreDeath ); + + int num = mineList.Num(); + savefile->WriteInt( num ); + savefile->WriteBool( specialDamage ); + for ( i = 0; i < num; i++ ) { + mineList[i].Save( savefile ); + } +} + +/* +===================== +hhJetpackHarvesterSimple::Restore +===================== +*/ +void hhJetpackHarvesterSimple::Restore( idRestoreGame *savefile ) { + int i; + for ( i = 0; i < ThrustSide_Total; i++ ) { + for ( int j = 0; j < ThrustType_Total; j++ ) { + fxThrusters[i][j].Restore( savefile ); + } + } + + savefile->ReadInt( lastAntiProjectileAttack ); + savefile->ReadBool( allowPreDeath ); + + int num; + savefile->ReadInt( num ); + savefile->ReadBool( specialDamage ); + mineList.SetNum( num ); + for ( i = 0; i < num; i++ ) { + mineList[i].Restore( savefile ); + } +} + +void hhJetpackHarvesterSimple::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); + if ( !AI_DEAD ) { + if ( damageDef && damageDef->GetBool( "ice" ) && spawnArgs.GetBool( "can_freeze", "0" ) ) { + freezeDamage = true; + allowPreDeath = false; + } else { + freezeDamage = false; + } + if ( damageDef && damageDef->GetBool( "no_special_death" ) ) { + specialDamage = true; + allowPreDeath = false; + } + } + hhMonsterAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/src/Prey/ai_jetpack_harvester_simple.h b/src/Prey/ai_jetpack_harvester_simple.h new file mode 100644 index 0000000..e8d0f7a --- /dev/null +++ b/src/Prey/ai_jetpack_harvester_simple.h @@ -0,0 +1,66 @@ + +#ifndef __PREY_AI_JETPACK_HARVESTER_SIMPLE_H__ +#define __PREY_AI_JETPACK_HARVESTER_SIMPLE_H__ + + +class hhJetpackHarvesterSimple : public hhMonsterAI { + +public: + CLASS_PROTOTYPE(hhJetpackHarvesterSimple); + +#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + void Event_AppendHoverFx( hhEntityFx* fx ) {}; + void Event_AppendToThrusterList( hhEntityFx* fx ) {}; + void Event_RemoveJetFx(void) {}; + void Event_CreateJetFx(void) {}; + void Event_LaunchMine( const idList* parmList ) {}; + void Event_OnProjectileLaunch( hhProjectile *proj ) {}; + void Event_DodgeProjectile( hhProjectile *proj ) {}; + void Event_IsEnemySniping(void) {}; + void Event_NumMines(void) {}; +#else +protected: + enum ThrustSide + { + ThrustSide_Left, + ThrustSide_Right, + ThrustSide_Total + }; + + enum ThrustType + { + ThrustType_Idle = 0, + ThrustType_Hover, + ThrustType_Total + }; + + ~hhJetpackHarvesterSimple(); + void Spawn(void); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void AnimMove( void ); + void Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + idProjectile* LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone, const idDict* desiredProjectileDef ); + void Event_AppendHoverFx( hhEntityFx* fx ); + void Event_AppendToThrusterList( hhEntityFx* fx ); + void Event_RemoveJetFx(void); + void Event_CreateJetFx(void); + void Event_LaunchMine( const idList* parmList ); + void Event_OnProjectileLaunch( hhProjectile *proj ); + void Event_DodgeProjectile( hhProjectile *proj ); + void Event_IsEnemySniping(void); + void Event_NumMines(void); + virtual void Hide( void ); + virtual void Show( void ); + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + idEntityPtr fxThrusters[ThrustType_Total][ThrustSide_Total]; + int lastAntiProjectileAttack; + bool allowPreDeath; + idList< idEntityPtr > mineList; + bool freezeDamage; + bool specialDamage; +#endif +}; + +#endif \ No newline at end of file diff --git a/src/Prey/ai_keeper_simple.cpp b/src/Prey/ai_keeper_simple.cpp new file mode 100644 index 0000000..3244fba --- /dev/null +++ b/src/Prey/ai_keeper_simple.cpp @@ -0,0 +1,694 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef MA_KeeperStartBlast("keeper_StartBlast", "e"); +const idEventDef MA_KeeperEndBlast("keeper_EndBlast", NULL); +const idEventDef MA_KeeperUpdateTelepathicThrow("keeper_UpdateTelepathicThrow", NULL, NULL); +const idEventDef MA_KeeperTelepathicThrow("keeper_TelepathicThrow", NULL, NULL); +const idEventDef MA_KeeperStartTeleport("keeper_StartTeleport", NULL); +const idEventDef MA_KeeperEndTeleport("keeper_EndTeleport", NULL); +const idEventDef MA_KeeperTeleportExit("keeper_TeleportExit", NULL); +const idEventDef MA_KeeperCreatePortal("keeper_createPortal", NULL); +const idEventDef MA_KeeperEnableShield("keeper_enableShield", NULL); +const idEventDef MA_KeeperDisableShield("keeper_disableShield", NULL); +const idEventDef MA_KeeperAssignShieldFx( "", "e" ); +const idEventDef MA_KeeperTrigger("keeper_Trigger", NULL, NULL); +const idEventDef MA_KeeperTeleportEnter("keeper_TeleportEnter", NULL); +const idEventDef MA_KeeperStartHeadFx("keeper_StartHeadFx"); +const idEventDef MA_KeeperEndHeadFx("keeper_EndHeadFx"); +const idEventDef MA_KeeperAssignHeadFx( "", "e" ); +const idEventDef MA_KeeperGetThrowEntity( "keeper_GetThrowEntity", NULL, 'e' ); +const idEventDef MA_KeeperGetTriggerEntity( "keeper_GetTriggerEntity", NULL, 'e' ); +const idEventDef MA_KeeperTriggerEntity( "keeper_TriggerEntity", "e" ); +const idEventDef MA_KeeperThrowEntity( "keeper_ThrowEntity", "e" ); +const idEventDef MA_KeeperForceDisableShield("keeper_forceDisableShield", NULL); + +CLASS_DECLARATION( hhMonsterAI, hhKeeperSimple ) + EVENT( MA_KeeperStartBlast, hhKeeperSimple::Event_StartBlast ) + EVENT( MA_KeeperEndBlast, hhKeeperSimple::Event_EndBlast ) + EVENT( MA_KeeperUpdateTelepathicThrow, hhKeeperSimple::Event_UpdateTelepathicThrow ) + EVENT( MA_KeeperTelepathicThrow, hhKeeperSimple::Event_TelepathicThrow ) + EVENT( MA_KeeperStartTeleport, hhKeeperSimple::Event_StartTeleport ) + EVENT( MA_KeeperEndTeleport, hhKeeperSimple::Event_EndTeleport ) + EVENT( MA_KeeperTeleportExit, hhKeeperSimple::Event_TeleportExit ) + EVENT( MA_KeeperCreatePortal, hhKeeperSimple::Event_CreatePortal ) + EVENT( MA_KeeperEnableShield, hhKeeperSimple::Event_EnableShield ) + EVENT( MA_KeeperDisableShield, hhKeeperSimple::Event_DisableShield ) + EVENT( MA_KeeperAssignShieldFx, hhKeeperSimple::Event_AssignShieldFx ) + EVENT( MA_KeeperTrigger, hhKeeperSimple::Event_KeeperTrigger ) + EVENT( MA_KeeperTeleportEnter, hhKeeperSimple::Event_TeleportEnter ) + EVENT( MA_KeeperStartHeadFx, hhKeeperSimple::Event_StartHeadFx ) + EVENT( MA_KeeperEndHeadFx, hhKeeperSimple::Event_EndHeadFx ) + EVENT( MA_KeeperAssignHeadFx, hhKeeperSimple::Event_AssignHeadFx ) + EVENT( MA_KeeperTriggerEntity, hhKeeperSimple::Event_TriggerEntity ) + EVENT( MA_KeeperThrowEntity, hhKeeperSimple::Event_ThrowEntity ) + EVENT( MA_KeeperGetTriggerEntity, hhKeeperSimple::Event_GetTriggerEntity ) + EVENT( MA_KeeperGetThrowEntity, hhKeeperSimple::Event_GetThrowEntity ) + EVENT( MA_KeeperForceDisableShield, hhKeeperSimple::Event_ForceDisableShield ) +END_CLASS + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + +hhKeeperSimple::hhKeeperSimple() { + shieldAlpha = 0.0f; +} + +hhKeeperSimple::~hhKeeperSimple() { + idEntity *ent = NULL; + if ( throwEntity.IsValid() ) { + ent = throwEntity.GetEntity(); + } else if ( targetReaction.entity.IsValid() ) { + ent = targetReaction.entity.GetEntity(); + } else { + return; + } + + if ( ent && ent->IsType(hhMoveable::Type)) { + hhMoveable *throwMoveable = static_cast(ent); + if ( throwMoveable ) { + throwMoveable->Event_Unhover(); + } + } +} + +void hhKeeperSimple::Spawn( void ) { + beamAttack = idEntityPtr ( hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "beamAttack" ) ) ); + if(beamAttack.IsValid()) { + beamAttack->MoveToJoint( this, spawnArgs.GetString("bone_beamAttack")); + beamAttack->BindToJoint( this, spawnArgs.GetString("bone_beamAttack"), false ); + beamAttack->Activate(FALSE); + } + + beamTelepathic = idEntityPtr (hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "beamTelepathic" ) ) ); + if(beamTelepathic.IsValid()) { + beamTelepathic->MoveToJoint( this, spawnArgs.GetString("bone_beamTelepathic")); + beamTelepathic->BindToJoint( this, spawnArgs.GetString("bone_beamTelepathic"), false ); + beamTelepathic->Activate(FALSE); + beamTelepathic->Hide(); + } + if ( spawnArgs.GetInt( "always_shield", "0" ) && !spawnArgs.GetBool( "hide", "0" ) ) { + Event_EnableShield(); + } + nextShieldImpact = gameLocal.time; + bThrowing = false; +} + +void hhKeeperSimple::Think() { + PROFILE_SCOPE("AI", PROFMASK_NORMAL|PROFMASK_AI); + if (ai_skipThink.GetBool()) { + return; + } + + idEntity *ent = NULL; + if ( throwEntity.IsValid() ) { + ent = throwEntity.GetEntity(); + } else if ( targetReaction.entity.IsValid() ) { + ent = targetReaction.entity.GetEntity(); + } + + // Draw beam from our brain to the object we are controlling + if ( beamTelepathic.IsValid() ) { + if( bThrowing && ent && !beamTelepathic->IsHidden() && beamTelepathic->IsActivated() ) { + beamTelepathic->SetTargetLocation(ent->GetOrigin()); + } else { + beamTelepathic->Activate( false ); + beamTelepathic->Hide(); + } + } + + if ( shieldFx.IsValid() ) { + if ( AI_SHIELD ) { + //fade on shield + shieldAlpha += spawnArgs.GetFloat( "shield_delta", "0.05" ); + if ( shieldAlpha > 1.0 ) { + shieldAlpha = 1.0f; + } + } else { + //fade off shield + shieldAlpha -= spawnArgs.GetFloat( "shield_delta", "0.05" ); + if ( shieldAlpha < 0.0 ) { + shieldAlpha = 0.0f; + } + } + shieldFx->SetParticleShaderParm( SHADERPARM_TIME_OF_DEATH, shieldAlpha ); + } + + hhMonsterAI::Think(); + + if ( physicsObj.HasGroundContacts() && !physicsObj.HadGroundContacts() ) { + AI_LANDED = true; + } +} + +void hhKeeperSimple::Event_StartBlast( idEntity *ent) { + if( beamAttack.IsValid() ) { + hhFxInfo fxInfo; + fxInfo.SetEntity( ent ); + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfo( spawnArgs.GetString("fx_portalstart"), ent->GetOrigin() + (spawnArgs.GetVector( "portal_start_offset" ) * ent->GetAxis()), ent->GetAxis(), &fxInfo ); + beamAttack->SetTargetLocation( ent->GetOrigin() + ent->GetAxis() * spawnArgs.GetVector( "telepathic_beam_offset" ) ); + beamAttack->Activate(TRUE); + } +} + +void hhKeeperSimple::Event_EndBlast() { + if( beamAttack.IsValid() ) { + beamAttack->Activate(FALSE); + } +} + +void hhKeeperSimple::Event_UpdateTelepathicThrow() { + idEntity *ent = NULL; + if ( throwEntity.IsValid() ) { + ent = throwEntity.GetEntity(); + } else if ( targetReaction.entity.IsValid() ) { + ent = targetReaction.entity.GetEntity(); + } else { + return; + } + if ( !ent || !ent->IsType(hhMoveable::Type) ) { + return; + } + bThrowing = true; + if ( beamTelepathic.IsValid() ) { + if ( beamTelepathic->IsHidden() ) { + beamTelepathic->Show(); + } + beamTelepathic->Activate( true ); + } + + hhMoveable *throwMoveable = static_cast(ent); + idVec3 hoverToPos; + + if(GetEnemy()) { + UpdateEnemyPosition(); + idVec3 toEnemy = GetLastEnemyPos() - GetOrigin(); + float dist = toEnemy.Normalize(); + hoverToPos = GetOrigin() + toEnemy * dist * 0.25f; + hoverToPos.z = GetOrigin().z + EyeHeight() + 200.0f; + } + else { + hoverToPos = throwMoveable->GetOrigin() + idVec3(0.0f, 0.0f, 128.0f); + } + throwMoveable->AllowImpact( true ); + throwMoveable->Event_HoverTo(hoverToPos); + throwMoveable->GetPhysics()->SetAngularVelocity(idVec3(10.0f, 10.0f, 10.0f)); + // HUMANHEAD mdl: Beef up the damage on movables thrown by the keeper + throwMoveable->SetDamageDef( spawnArgs.GetString( "moveableDamageDef", "damage_movable20" ) ); + PostEventMS(&MA_KeeperUpdateTelepathicThrow, 100); +} + +void hhKeeperSimple::Event_TelepathicThrow() { + idEntity *ent = NULL; + bThrowing = false; + if ( beamTelepathic.IsValid() ) { + beamTelepathic->Hide(); + } + if ( throwEntity.IsValid() ) { + ent = throwEntity.GetEntity(); + } else if ( targetReaction.entity.IsValid() ) { + ent = targetReaction.entity.GetEntity(); + } else { + return; + } + + // Start the object hovering + if ( !ent || !ent->IsType(hhMoveable::Type) ) { + return; + } + + targetReaction.entity.Clear(); + CancelEvents( &MA_KeeperUpdateTelepathicThrow ); + hhMoveable *throwMoveable = static_cast(ent); + if ( throwMoveable && throwMoveable->IsType( hhMoveable::Type ) ) { + throwMoveable->Event_Unhover(); + } + + // Throw it at an enemy if we have one + if(GetEnemy()) { + idVec3 toEnemy = GetEnemy()->GetAimPosition() - throwMoveable->GetOrigin(); + toEnemy.Normalize(); + throwMoveable->GetPhysics()->SetLinearVelocity(toEnemy * spawnArgs.GetFloat("telepathic_throw_velocity", "2000")); + } +} + +void hhKeeperSimple::Event_TeleportExit() { + const function_t *newstate = NULL; + newstate = GetScriptFunction( "state_TeleportExit" ); + if ( newstate ) { + SetState( newstate ); + UpdateScript(); + } +} + +void hhKeeperSimple::Event_TeleportEnter() { + const function_t *newstate = NULL; + newstate = GetScriptFunction( "state_TeleportEnter" ); + if ( newstate ) { + SetState( newstate ); + UpdateScript(); + } +} + +void hhKeeperSimple::Event_StartTeleport() { + Hide(); +} + +void hhKeeperSimple::Event_EndTeleport() { + idVec3 newOrig; + idEntity *ent; + idList nodeList; + float bestScore = -1; + float bestNodeIndex = -1; + + // Find the best position to teleport to + // Find where we want to teleport to + idStr randomNode = spawnArgs.RandomPrefix( "teleport_info", gameLocal.random ); + ent = gameLocal.FindEntity( randomNode ); + if ( ent ) { + newOrig = ent->GetOrigin(); + } else { + newOrig = GetOrigin(); + gameLocal.Warning( "%s doesn't have any teleport infos", name.c_str() ); + } + + //teleport and face enemy + SetOrigin( newOrig ); + if( GetEnemy() ) { + TurnToward(GetEnemy()->GetOrigin()); + current_yaw = ideal_yaw; + Turn(); + } +} + +float hhKeeperSimple::GetTeleportDestRating(const idVec3 &pos) { + // Make sure this spot doesn't contain something else + idEntity *entList[MAX_GENTITIES]; + idBounds myNewBnds = this->GetPhysics()->GetBounds(); + myNewBnds = myNewBnds.Translate(pos); + myNewBnds.ExpandSelf(24.0f); + int numHit = gameLocal.clip.EntitiesTouchingBounds(myNewBnds, -1, entList, MAX_GENTITIES); + if(numHit > 0) { + for(int i=0;iIsHidden() && (entList[i]->IsType(hhMoveable::Type) || entList[i]->IsType(idActor::Type))) { + return -1; + } + } + } + + float w = gameLocal.random.RandomFloat(); + + return idMath::ClampFloat(-1.0f, 1.0f, w); +} + +#define LinkScriptVariable( name ) name.LinkTo( scriptObject, #name ) +void hhKeeperSimple::LinkScriptVariables() { + hhMonsterAI::LinkScriptVariables(); + LinkScriptVariable( AI_LANDED ); + LinkScriptVariable( AI_SHIELD ); +} + +void hhKeeperSimple::Event_CreatePortal (void) { + static const char * passPrefix = "portal_"; + const char * portalDef; + idEntity * portal; + idDict portalArgs; + idList xferKeys; + idList xferValues; + + const idKeyValue *buddyKV = spawnArgs.FindKey( "portal_buddy" ); + if ( buddyKV && buddyKV->GetValue().Length() && gameLocal.FindEntity(buddyKV->GetValue().c_str()) ) { + // Case of a valid portal_buddy key, make a real portal + portalDef = spawnArgs.GetString( "def_portal" ); + } else if ( spawnArgs.FindKey( "portal_cameraTarget" ) ) { // The monster portal has a camera target, so use a real portal instead of the fake portal + portalDef = spawnArgs.GetString( "def_portal" ); + } else { + portalDef = spawnArgs.GetString( "def_fakeportal" ); + } + + if ( !portalDef || !portalDef[0] ) { + return; + } + + // Set the origin of the portal to us. + portalArgs.SetVector( "origin", GetOrigin() ); + portalArgs.Set( "rotation", spawnArgs.GetString( "rotation" ) ); + portalArgs.Set( "remove_on_close", "1" ); + + // Pass along all 'portal_' keys to the portal's spawnArgs; + hhUtils::GetKeysAndValues( spawnArgs, passPrefix, xferKeys, xferValues ); + for ( int i = 0; i < xferValues.Num(); ++i ) { + xferKeys[ i ].StripLeadingOnce( passPrefix ); + portalArgs.Set( xferKeys[ i ].c_str(), xferValues[ i ].c_str() ); + } + + // Spawn the portal + portal = gameLocal.SpawnObject( portalDef, &portalArgs ); + if ( !portal ) { + return; + } + + // Move the portal up some pre determinted amt, since its origin is in the middle of it + trace_t tr; + gameLocal.clip.TracePoint(tr, GetOrigin(), GetOrigin() + idVec3(0,0,-200), MASK_MONSTERSOLID, this); + if ( tr.fraction < 1.0f ) { + portal->GetPhysics()->SetOrigin( tr.c.point + idVec3( 0,0,2 ) ); + } else { + float offset = spawnArgs.GetFloat( "offset_portal", 0 ); + portal->GetPhysics()->SetOrigin( portal->GetPhysics()->GetOrigin() + (portal->GetAxis()[2] * offset) ); + } + + // Update the camera stuff + portal->ProcessEvent( &EV_UpdateCameraTarget ); + + // Open the portal - Need to delay this, so that PostSpawn gets called/sets up the partner portal + //? Should we always pass in the player? + portal->PostEventSec( &EV_Activate, 0, gameLocal.GetLocalPlayer() ); +} + +void hhKeeperSimple::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + hhMonsterAI::Killed( inflictor, attacker, damage, dir, location ); + if ( beamAttack.IsValid() ) { + SAFE_REMOVE( beamAttack ); + } + if( beamTelepathic.IsValid() ) { + SAFE_REMOVE( beamTelepathic ); + } + hhMoveable *throwMoveable = static_cast(targetReaction.entity.GetEntity()); + if ( throwMoveable && throwMoveable->IsType( hhMoveable::Type ) ) { + targetReaction.entity.Clear(); + throwMoveable->Event_Unhover(); + } + if ( renderEntity.customSkin == NULL ) { + SetSkinByName( "skins/monsters/keeper_nolegs" ); + } +} + +void hhKeeperSimple::Event_EnableShield(void) { + if ( spawnArgs.GetInt( "never_shield", "0" ) ) { + return; + } + fl.applyDamageEffects = false; + AI_SHIELD = true; + if ( shieldFx.IsValid() ) { + SAFE_REMOVE( shieldFx ); + } + hhFxInfo fxInfo; + fxInfo.SetEntity( this ); + fxInfo.SetBindBone( "head" ); + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfo( spawnArgs.GetString("fx_shield"), GetOrigin(), mat3_identity, &fxInfo, &MA_KeeperAssignShieldFx ); +} + +void hhKeeperSimple::Event_ForceDisableShield(void) { + fl.applyDamageEffects = true; + AI_SHIELD = false; +} + +void hhKeeperSimple::Event_DisableShield(void) { + if ( spawnArgs.GetInt( "always_shield", "0" ) ) { + return; + } + fl.applyDamageEffects = true; + AI_SHIELD = false; +} + +void hhKeeperSimple::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int loc ) { + if ( AI_SHIELD ) { + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); + if ( damageDef && damageDef->GetBool( "spirit_damage" ) ) { + hhMonsterAI::Damage(inflictor, attacker, dir, damageDefName, damageScale, loc); + } + if ( inflictor ) { + hhFxInfo fxInfo; + fxInfo.RemoveWhenDone( true ); + if ( gameLocal.time >= nextShieldImpact ) { + nextShieldImpact = gameLocal.time + int(spawnArgs.GetFloat( "shield_impact_freq", "0.4" ) * 1000); + BroadcastFxInfoPrefixed( "fx_shield_impact", inflictor->GetOrigin() + GetAxis()*idVec3(50,0,0), mat3_identity, &fxInfo ); + } + StartSound( "snd_shield_impact", SND_CHANNEL_BODY ); + if ( inflictor->IsType( idProjectile::Type ) ) { + inflictor->PostEventMS( &EV_Remove, 0 ); + } + } + } else { + hhMonsterAI::Damage(inflictor, attacker, dir, damageDefName, damageScale, loc); + } +} + +void hhKeeperSimple::Event_AssignShieldFx( hhEntityFx* fx ) { + shieldFx = fx; +} + +void hhKeeperSimple::Event_StartHeadFx() { + hhFxInfo fxInfo; + fxInfo.SetEntity( this ); + fxInfo.SetBindBone( spawnArgs.GetString( "bone_telepathic_fx", "head" ) ); + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfo( spawnArgs.GetString("fx_telepathic"), GetOrigin(), mat3_identity, &fxInfo, &MA_KeeperAssignHeadFx ); +} + +void hhKeeperSimple::Event_EndHeadFx() { + SAFE_REMOVE( headFx ); +} + +void hhKeeperSimple::Event_AssignHeadFx( hhEntityFx* fx ) { + headFx = fx; +} + +void hhKeeperSimple::Event_KeeperTrigger() { + if ( targetReaction.entity.IsValid() ) { + targetReaction.entity->ProcessEvent( &EV_Activate, this ); + hhReaction *reaction = targetReaction.GetReaction(); + if (reaction) { + reaction->active = false; + } + } +} + +idProjectile *hhKeeperSimple::LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone, const idDict* desiredProjectileDef ) { //HUMANHEAD mdc - added desiredProjectileDef for supporting multiple projs. + idVec3 muzzle; + idVec3 dir; + idVec3 start; + trace_t tr; + idBounds projBounds; + float distance; + const idClipModel *projClip; + float attack_accuracy; + float attack_cone; + float projectile_spread; + float diff; + float angle; + float spin; + idAngles ang; + int num_projectiles; + int i; + idMat3 axis; + idVec3 tmp; + idProjectile *lastProjectile; + + //HUMANHEAD mdc - added to support multiple projectiles + if( desiredProjectileDef ) { //try to set our projectile to the desiredProjectile + int projIndex = FindProjectileInfo( desiredProjectileDef ); + if( projIndex >= 0 ) { + SetCurrentProjectile( projIndex ); + } + } + //HUMANHEAD END + + + if ( !projectileDef ) { + gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() ); + return NULL; + } + + attack_accuracy = spawnArgs.GetFloat( "attack_accuracy", "7" ); + attack_cone = spawnArgs.GetFloat( "attack_cone", "70" ); + projectile_spread = spawnArgs.GetFloat( "projectile_spread", "0" ); + num_projectiles = spawnArgs.GetInt( "num_projectiles", "1" ); + + GetMuzzle( jointname, muzzle, axis ); + + if ( !projectile.GetEntity() ) { + CreateProjectile( muzzle, axis[ 0 ] ); + } + + lastProjectile = projectile.GetEntity(); + + if ( target != NULL ) { + tmp = target->GetPhysics()->GetAbsBounds().GetCenter() - muzzle; + tmp.Normalize(); + axis = tmp.ToMat3(); + } else { + axis = viewAxis; + } + + // rotate it because the cone points up by default + tmp = axis[2]; + axis[2] = axis[0]; + axis[0] = -tmp; + + // make sure the projectile starts inside the monster bounding box + const idBounds &ownerBounds = physicsObj.GetAbsBounds(); + projClip = lastProjectile->GetPhysics()->GetClipModel(); + projBounds = projClip->GetBounds().Rotate( axis ); + + // 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( muzzle, viewAxis[ 0 ], distance ) ) { + start = muzzle + 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(); + } + + gameLocal.clip.Translation( tr, start, muzzle, projClip, axis, MASK_SHOT_RENDERMODEL, this ); + muzzle = tr.endpos; + + // set aiming direction + GetAimDir( muzzle, target, this, dir ); + ang = dir.ToAngles(); + + // adjust his aim so it's not perfect. uses sine based movement so the tracers appear less random in their spread. + float t = MS2SEC( gameLocal.time + entityNumber * 497 ); + ang.pitch += idMath::Sin16( t * 5.1 ) * attack_accuracy; + ang.yaw += idMath::Sin16( t * 6.7 ) * attack_accuracy; + + if ( clampToAttackCone ) { + // 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, current_yaw ); + if ( diff > attack_cone ) { + ang.yaw = current_yaw + attack_cone; + } else if ( diff < -attack_cone ) { + ang.yaw = current_yaw - attack_cone; + } + } + + axis = ang.ToMat3(); + + float spreadRad = DEG2RAD( projectile_spread ); + for( i = 0; i < num_projectiles; i++ ) { + // spread the projectiles out + angle = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() ); + spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat(); + //dir = axis[ 0 ] + axis[ 2 ] * ( 0 * idMath::Sin( spin ) ) - axis[ 1 ] * ( angle * idMath::Cos( spin ) ); + dir = axis[ 0 ] - axis[ 1 ] * ( angle * idMath::Cos( spin ) ); + dir.Normalize(); + + // launch the projectile + if ( !projectile.GetEntity() ) { + CreateProjectile( muzzle, dir ); + } + lastProjectile = projectile.GetEntity(); + lastProjectile->Launch( muzzle, dir, vec3_origin ); + projectile = NULL; + } + + TriggerWeaponEffects( muzzle, axis ); + + lastAttackTime = gameLocal.time; + +//HUMANHEAD mdc - added to support multiple projectiles + projectile = NULL; + SetCurrentProjectile( projectileDefaultDefIndex ); //set back to our default projectile to be on the safe side +//HUMANHEAD END + + return lastProjectile; +} + +void hhKeeperSimple::Event_GetTriggerEntity() { + idThread::ReturnEntity( triggerEntity.GetEntity() ); +} + +void hhKeeperSimple::Event_TriggerEntity( idEntity *ent ) { + if ( !ent ) { + return; + } + triggerEntity = ent; + const function_t *newstate = NULL; + newstate = GetScriptFunction( "state_TriggerEntity" ); + if ( newstate ) { + SetState( newstate ); + UpdateScript(); + } +} + +void hhKeeperSimple::Event_GetThrowEntity() { + idThread::ReturnEntity( throwEntity.GetEntity() ); +} + +void hhKeeperSimple::Event_ThrowEntity( idEntity *ent ) { + if ( !ent || !ent->IsType( idMoveable::Type ) ) { + return; + } + throwEntity = ent; + const function_t *newstate = NULL; + newstate = GetScriptFunction( "state_ThrowEntity" ); + if ( newstate ) { + SetState( newstate ); + UpdateScript(); + } +} + +void hhKeeperSimple::Hide() { + Event_DisableShield(); + hhMonsterAI::Hide(); +} + +void hhKeeperSimple::Show() { + hhMonsterAI::Show(); + if ( spawnArgs.GetInt( "always_shield", "0" ) ) { + Event_EnableShield(); + } +} + +void hhKeeperSimple::HideNoDormant() { + //overridden to hide shield fx + if ( shieldFx.IsValid() ) { + shieldFx->Hide(); + } + idAI::Hide(); +} + +/* +===================== +hhKeeperSimple::Save +===================== +*/ +void hhKeeperSimple::Save( idSaveGame *savefile ) const { + beamTelepathic.Save( savefile ); + beamAttack.Save( savefile ); + shieldFx.Save( savefile ); + headFx.Save( savefile ); + triggerEntity.Save( savefile ); + throwEntity.Save( savefile ); + savefile->WriteInt( nextShieldImpact ); + savefile->WriteBool( bThrowing ); +} + +/* +===================== +hhKeeperSimple::Restore +===================== +*/ +void hhKeeperSimple::Restore( idRestoreGame *savefile ) { + beamTelepathic.Restore( savefile ); + beamAttack.Restore( savefile ); + shieldFx.Restore( savefile ); + headFx.Restore( savefile ); + triggerEntity.Restore( savefile ); + throwEntity.Restore( savefile ); + savefile->ReadInt( nextShieldImpact ); + savefile->ReadBool( bThrowing ); +} + +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/src/Prey/ai_keeper_simple.h b/src/Prey/ai_keeper_simple.h new file mode 100644 index 0000000..94c97bf --- /dev/null +++ b/src/Prey/ai_keeper_simple.h @@ -0,0 +1,98 @@ + +#ifndef __PREY_AI_KEEPER_SIMPLE_H__ +#define __PREY_AI_KEEPER_SIMPLE_H__ + +class hhKeeperSimple : public hhMonsterAI { + +public: + CLASS_PROTOTYPE(hhKeeperSimple); + +#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + void Event_GetTriggerEntity() {}; + void Event_TriggerEntity( idEntity *ent ) {}; + void Event_GetThrowEntity() {}; + void Event_ThrowEntity( idEntity *ent ) {}; + void Event_StartBlast( idEntity *ent ) {}; + void Event_EndBlast() {}; + void Event_BeginTelepathicThrow(idEntity *throwMe) {}; + void Event_UpdateTelepathicThrow() {}; + void Event_TelepathicThrow() {}; + void Event_StartTeleport() {}; + void Event_EndTeleport() {}; + void Event_TeleportExit() {}; + void Event_TeleportEnter() {}; + void Event_CreatePortal() {}; + void Event_EnableShield() {}; + void Event_DisableShield() {}; + void Event_ForceDisableShield() {}; + void Event_AssignShieldFx( hhEntityFx* fx ) {}; + void Event_StartHeadFx() {}; + void Event_EndHeadFx() {}; + void Event_AssignHeadFx( hhEntityFx* fx ) {}; + void Event_KeeperTrigger() {}; +#else + + + hhKeeperSimple(); + ~hhKeeperSimple(); + void Spawn( void ); + virtual void Think(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void LinkScriptVariables(); + virtual 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 ); + idProjectile* LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone, const idDict* desiredProjectileDef ); + void Hide(); + void Show(); + void HideNoDormant(); + idScriptBool AI_LANDED; + idScriptBool AI_SHIELD; +protected: + void Event_GetTriggerEntity(); + void Event_TriggerEntity( idEntity *ent ); + void Event_GetThrowEntity(); + void Event_ThrowEntity( idEntity *ent ); + + // Attack blast + void Event_StartBlast( idEntity *ent ); + void Event_EndBlast(); + + // Telepathic Throwing + void Event_BeginTelepathicThrow(idEntity *throwMe); + void Event_UpdateTelepathicThrow(); + void Event_TelepathicThrow(); + + // Teleporting + void Event_StartTeleport(); + void Event_EndTeleport(); + void Event_TeleportExit(); + void Event_TeleportEnter(); + void Event_CreatePortal(); + + // Shield + void Event_EnableShield(); + void Event_DisableShield(); + void Event_ForceDisableShield(); + void Event_AssignShieldFx( hhEntityFx* fx ); + void Event_StartHeadFx(); + void Event_EndHeadFx(); + void Event_AssignHeadFx( hhEntityFx* fx ); + + // Telepathic Triggering + void Event_KeeperTrigger(); + + float GetTeleportDestRating(const idVec3 &pos); + idEntityPtr beamTelepathic; // Beam used when we are telepathically controlling an object + idEntityPtr beamAttack; + idEntityPtr shieldFx; + idEntityPtr headFx; + idEntityPtr triggerEntity; + idEntityPtr throwEntity; + int nextShieldImpact; + float shieldAlpha; + bool bThrowing; +#endif +}; + +#endif \ No newline at end of file diff --git a/src/Prey/ai_mutate.cpp b/src/Prey/ai_mutate.cpp new file mode 100644 index 0000000..5ac9ceb --- /dev/null +++ b/src/Prey/ai_mutate.cpp @@ -0,0 +1,63 @@ + + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +CLASS_DECLARATION(hhMonsterAI, hhMutate) + EVENT( MA_OnProjectileLaunch, hhMutate::Event_OnProjectileLaunch ) +END_CLASS + +void hhMutate::Event_OnProjectileLaunch(hhProjectile *proj) { + const function_t *newstate = NULL; + if ( !enemy.IsValid() || !AI_COMBAT ) { + return; + } + + float min = spawnArgs.GetFloat( "dda_dodge_min", "0.3" ); + float max = spawnArgs.GetFloat( "dda_dodge_max", "0.8" ); + float dodgeChance = 0.6f; + + dodgeChance = (min + (max-min)*gameLocal.GetDDAValue() ); + + if ( ai_debugBrain.GetBool() ) { + gameLocal.Printf( "%s dodge chance %f\n", GetName(), dodgeChance ); + } + if ( gameLocal.random.RandomFloat() > dodgeChance ) { + return; + } + + //determine which side to dodge to + idVec3 povPos, targetPos; + povPos = enemy->GetPhysics()->GetOrigin(); + targetPos = GetPhysics()->GetOrigin(); + idVec3 povToTarget = targetPos - povPos; + povToTarget.z = 0.f; + povToTarget.Normalize(); + idVec3 povLeft, povUp; + povToTarget.OrthogonalBasis(povLeft, povUp); + povLeft.Normalize(); + + idVec3 projVel = proj->GetPhysics()->GetLinearVelocity(); + projVel.Normalize(); + float dot = povLeft * projVel; + if ( dot < 0 ) { + newstate = GetScriptFunction( "state_DodgeBackRight" ); + } else { + newstate = GetScriptFunction( "state_DodgeBackLeft" ); + } + + if ( newstate ) { + SetState( newstate ); + UpdateScript(); + } +} + +#define LinkScriptVariable( name ) name.LinkTo( scriptObject, #name ) +void hhMutate::LinkScriptVariables(void) { + hhMonsterAI::LinkScriptVariables(); + LinkScriptVariable( AI_COMBAT ); +} +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/src/Prey/ai_mutate.h b/src/Prey/ai_mutate.h new file mode 100644 index 0000000..431bc97 --- /dev/null +++ b/src/Prey/ai_mutate.h @@ -0,0 +1,15 @@ + +#ifndef __PREY_AI_MUTATE_H__ +#define __PREY_AI_MUTATE_H__ + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +class hhMutate : public hhMonsterAI { +public: + CLASS_PROTOTYPE(hhMutate); +protected: + void Event_OnProjectileLaunch(hhProjectile *proj); + void LinkScriptVariables(); + idScriptBool AI_COMBAT; +}; +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +#endif \ No newline at end of file diff --git a/src/Prey/ai_mutilatedhuman.cpp b/src/Prey/ai_mutilatedhuman.cpp new file mode 100644 index 0000000..5a488f6 --- /dev/null +++ b/src/Prey/ai_mutilatedhuman.cpp @@ -0,0 +1,390 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef MH_AlertFriends("alertFriends", NULL, NULL); +const idEventDef MH_DropBinds("dropBinds"); +const idEventDef MH_DropProjectiles("" ); + +CLASS_DECLARATION(hhMonsterAI, hhMutilatedHuman) + EVENT( MH_AlertFriends, hhMutilatedHuman::Event_AlertFriends ) + EVENT( MH_DropBinds, hhMutilatedHuman::Event_DropBinds ) + EVENT( MH_DropProjectiles, hhMutilatedHuman::Event_DropProjectiles ) +END_CLASS + +void hhMutilatedHuman::Spawn() { + damageFlag = 0; +} + +void hhMutilatedHuman::Event_DropBinds( ) { + idEntity *ent; + idEntity *next; + for( ent = teamChain; ent != NULL; ent = next ) { + next = ent->GetTeamChain(); + if ( ent && !ent->IsType( hhProjectile::Type ) && !ent->IsType( idEntityFx::Type ) && ent->GetBindMaster() == this && ent != head.GetEntity() ) { + ent->Unbind(); + next = teamChain; + } + } +} + +void hhMutilatedHuman::Event_DropProjectiles() { + idEntity *ent; + idEntity *next; + for( ent = teamChain; ent != NULL; ent = next ) { + next = ent->GetTeamChain(); + if ( ent && ent->IsType( hhProjectile::Type ) ) { + ent->Unbind(); + ent->Hide(); + ent->PostEventSec( &EV_Remove, 5 ); + next = teamChain; + } + } +} + +void hhMutilatedHuman::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + if (!fl.takedamage) { + return; + } + hhMonsterAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + + if ( AI_LIMB_FELL ) { + PostEventSec( &MH_DropProjectiles, 0.1f ); + } + + if ( AI_DEAD ) { + return; + } + + idDict args; + idVec3 bonePos; + idMat3 boneAxis; + idStr debrisName; + idStr damageGroup = GetDamageGroup( location ); + + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); + if ( damageDef && damageDef->GetBool( "ice" ) ) { + return; + } + + if ( !( damageGroup == "head" && damageDef->GetInt( "damage" ) >= spawnArgs.GetFloat( "head_falloff_damage", "70" ) ) ) { + if ( gameLocal.random.RandomFloat() >= spawnArgs.GetFloat( "chanceLimbsWillFallOff", "0.1" ) ) { // CJR: Build in a chance that the limbs will fall off + return; + } + } + + if ( damageGroup == "left_arm" && !(damageFlag & BIT(0)) ) { + damageFlag |= BIT(0); + debrisName = spawnArgs.GetString( "def_debris_arm" ); + if ( !debrisName.IsEmpty() ) { + idEntity *debris = gameLocal.SpawnObject( debrisName, &args ); + if ( debris ) { + AI_LIMB_FELL = true; + GetJointWorldTransform( spawnArgs.GetString( "bone_arm_left" ), bonePos, boneAxis ); + debris->SetOrigin( bonePos ); + debris->GetPhysics()->SetLinearVelocity( spawnArgs.GetVector( "debris_arm_velocity" ) * GetAxis() ); + debris->GetPhysics()->SetAngularVelocity( 60 * hhUtils::RandomVector() ); + BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_left_arm_blood"), spawnArgs.GetString("bone_arm_left") ); + } + } + + } else if ( damageGroup == "right_arm" && !(damageFlag & BIT(1)) ) { + damageFlag |= BIT(1); + debrisName = spawnArgs.GetString( "def_debris_arm" ); + if ( !debrisName.IsEmpty() ) { + idEntity *debris = gameLocal.SpawnObject( debrisName, &args ); + if ( debris ) { + AI_LIMB_FELL = true; + GetJointWorldTransform( spawnArgs.GetString( "bone_arm_right" ), bonePos, boneAxis ); + debris->SetOrigin( bonePos ); + debris->GetPhysics()->SetLinearVelocity( spawnArgs.GetVector( "debris_arm_velocity" ) * GetAxis() ); + debris->GetPhysics()->SetAngularVelocity( 60 * hhUtils::RandomVector() ); + BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_right_arm_blood"), spawnArgs.GetString("bone_arm_right") ); + } + } + } else if ( damageGroup == "chest" && !(damageFlag & BIT(2)) ) { + damageFlag |= BIT(2); + damageFlag |= BIT(3); + debrisName = spawnArgs.GetString( "def_debris_chest" ); + if ( !debrisName.IsEmpty() ) { + idEntity *debris = gameLocal.SpawnObject( debrisName, &args ); + if ( debris ) { + AI_LIMB_FELL = true; + GetJointWorldTransform( spawnArgs.GetString( "bone_chest" ), bonePos, boneAxis ); + debris->SetOrigin( bonePos ); + debris->GetPhysics()->SetLinearVelocity( spawnArgs.GetVector( "debris_chest_velocity" ) * GetAxis() ); + debris->GetPhysics()->SetAngularVelocity( 60 * hhUtils::RandomVector() ); + BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_chest_blood"), spawnArgs.GetString("bone_chest") ); + } + } + } else if ( damageGroup == "head" ) { + if ( head.IsValid() && !head->IsHidden() ) { + debrisName = spawnArgs.GetString( "def_debris_head" ); + if ( !debrisName.IsEmpty() ) { + idEntity *debris = gameLocal.SpawnObject( debrisName, &args ); + if ( debris ) { + AI_LIMB_FELL = true; + GetJointWorldTransform( spawnArgs.GetString( "bone_head" ), bonePos, boneAxis ); + debris->SetOrigin( bonePos ); + debris->GetPhysics()->SetLinearVelocity( spawnArgs.GetVector( "debris_head_velocity" ) * GetAxis() ); + debris->GetPhysics()->SetAngularVelocity( 60 * hhUtils::RandomVector() ); + BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_head_blood"), spawnArgs.GetString("bone_head") ); + } + } + head->Hide(); + } + } + + idStr skinName = spawnArgs.GetString("damage_skin"); + skinName += damageFlag; + if ( damageFlag > 0 ) { + SetSkin( declManager->FindSkin( skinName ) ); + UpdateVisuals(); + } +} + +void hhMutilatedHuman::Event_AlertFriends() { + hhMonsterAI *check; + if ( !enemy.IsValid() ) { + return; + } + + for( int i = 0; i < targets.Num(); i++ ) { + check = static_cast(targets[ i ].GetEntity()); + if ( !check || check->IsHidden() || !check->IsType( hhMutilatedHuman::Type ) ) { + continue; + } + check->SetEnemy( enemy.GetEntity() ); + } +} + +void hhMutilatedHuman::AnimMove( void ) { + //overridden to set enemy if blockedEnt can be an enemy + idVec3 goalPos; + idVec3 delta; + idVec3 goalDelta; + float goalDist; + monsterMoveResult_t moveResult; + idVec3 newDest; + + idVec3 oldorigin = physicsObj.GetOrigin(); +#ifdef HUMANHEAD //jsh wallwalk + idMat3 oldaxis = GetGravViewAxis(); +#else + idMat3 oldaxis = viewAxis; +#endif + + AI_BLOCKED = false; + + if ( move.moveCommand < NUM_NONMOVING_COMMANDS ){ + move.lastMoveOrigin.Zero(); + move.lastMoveTime = gameLocal.time; + } + + move.obstacle = NULL; + if ( ( move.moveCommand == MOVE_FACE_ENEMY ) && enemy.GetEntity() ) { + TurnToward( lastVisibleEnemyPos ); + goalPos = oldorigin; + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + goalPos = oldorigin; + } else if ( GetMovePos( goalPos ) ) { + if ( move.moveCommand != MOVE_WANDER ) { + CheckObstacleAvoidance( goalPos, newDest ); + TurnToward( newDest ); + } else { + TurnToward( goalPos ); + } + } + + Turn(); + + if ( move.moveCommand == MOVE_SLIDE_TO_POSITION ) { + if ( gameLocal.time < move.startTime + move.duration ) { + goalPos = move.moveDest - move.moveDir * MS2SEC( move.startTime + move.duration - gameLocal.time ); + delta = goalPos - oldorigin; + delta.z = 0.0f; + } else { + delta = move.moveDest - oldorigin; + delta.z = 0.0f; + StopMove( MOVE_STATUS_DONE ); + } + } else if ( allowMove ) { +#ifdef HUMANHEAD //jsh wallwalk + GetMoveDelta( oldaxis, GetGravViewAxis(), delta ); +#else + GetMoveDelta( oldaxis, viewAxis, delta ); +#endif + } else { + delta.Zero(); + } + + if ( move.moveCommand == MOVE_TO_POSITION ) { + goalDelta = move.moveDest - oldorigin; + goalDist = goalDelta.LengthFast(); + if ( goalDist < delta.LengthFast() ) { + delta = goalDelta; + } + } + + physicsObj.SetDelta( delta ); + physicsObj.ForceDeltaMove( disableGravity ); + + RunPhysics(); + + if ( ai_debugMove.GetBool() ) { + // HUMANHEAD JRM - so we can see if grav is on or off + if(disableGravity) { + gameRenderWorld->DebugLine( colorRed, oldorigin, physicsObj.GetOrigin(), 5000 ); + } else { + gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 5000 ); + } + } + + moveResult = physicsObj.GetMoveResult(); + if ( !af_push_moveables && attack.Length() && TestMelee() ) { + DirectDamage( attack, enemy.GetEntity() ); + } else { + idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); + if ( blockEnt && blockEnt->IsType( idActor::Type ) && ReactionTo( blockEnt ) != ATTACK_IGNORE ) { + SetEnemy( static_cast(blockEnt) ); + } + if ( blockEnt && blockEnt->IsType( hhPod::Type ) && blockEnt->GetPhysics()->IsPushable() ) { + KickObstacles( viewAxis[ 0 ], kickForce, blockEnt ); + blockEnt->Damage( this, this, vec3_zero, spawnArgs.GetString( "kick_damage" ), 1.0f, INVALID_JOINT ); + } + if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) { + KickObstacles( viewAxis[ 0 ], kickForce, blockEnt ); + } + } + + BlockedFailSafe(); + + AI_ONGROUND = physicsObj.OnGround(); + + idVec3 org = physicsObj.GetOrigin(); + if ( oldorigin != org ) { + TouchTriggers(); + } + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, gameLocal.msec ); + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec ); + gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true ); + DrawRoute(); + } +} + +int hhMutilatedHuman::ReactionTo( const idEntity *ent ) { + const idActor *actor = static_cast( ent ); + if( actor && actor->IsType(hhDeathProxy::Type) ) { + return ATTACK_IGNORE; + } + + if ( ent->health <= 0 ) { + return ATTACK_IGNORE; + } + + if ( ent->fl.hidden ) { + // ignore hidden entities + return ATTACK_IGNORE; + } + + if ( !ent->IsType( idActor::Type ) ) { + return ATTACK_IGNORE; + } + + actor = static_cast( ent ); + if ( actor->IsType( idPlayer::Type ) && static_cast(actor)->noclip ) { + // ignore players in noclip mode + return ATTACK_IGNORE; + } + + //only attack spiritwalking players if they hurt me + if ( ent->IsType( hhPlayer::Type ) ) { + const hhPlayer *player = static_cast( ent ); + if ( nextSpiritProxyCheck == 0 && player && player->IsSpiritWalking() ) { + nextSpiritProxyCheck = gameLocal.time + SEC2MS(2); + return ATTACK_ON_DAMAGE; + } + } + + if ( ent->IsType( hhSpiritProxy::Type ) ) { + if ( gameLocal.time > nextSpiritProxyCheck ) { + nextSpiritProxyCheck = 0; + //attack spiritproxy on sight if we have no enemy or if its closer than our current enemy + if ( enemy.IsValid() && enemy->IsType( hhPlayer::Type ) ) { + float distToEnemy = (enemy->GetOrigin() - GetOrigin()).LengthSqr(); + float distToProxy = (ent->GetOrigin() - GetOrigin()).LengthSqr(); + if ( distToProxy < distToEnemy ) { + return ATTACK_ON_SIGHT; + } + } else { + return ATTACK_ON_SIGHT; + } + } else { + return ATTACK_IGNORE; + } + } + + if ( ent && ent->spawnArgs.GetBool( "never_target" ) ) { + return ATTACK_IGNORE; + } + + //if we're hurt, its probably because of the player, so keep him as an enemy + if ( actor->IsType( idPlayer::Type ) && health < spawnHealth ) { + return ATTACK_ON_DAMAGE | ATTACK_ON_ACTIVATE | ATTACK_ON_SIGHT; + } + + // 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 + if ( actor->IsType( idPlayer::Type ) ) { + return ATTACK_ON_DAMAGE; + } else { + return ATTACK_ON_DAMAGE | ATTACK_ON_ACTIVATE; + } + } + //force players to only alert through damage + if ( actor->IsType( idPlayer::Type ) ) { + return ATTACK_ON_DAMAGE | ATTACK_ON_ACTIVATE; + } else if ( actor->IsType( idAI::Type ) ) { + //only allow ai actors to otherwise alert them + return ATTACK_ON_SIGHT | ATTACK_ON_DAMAGE | ATTACK_ON_ACTIVATE; + } + } + + // monsters will fight when attacked by lower ranked monsters. rank 0 never fights back. + if ( rank && ( actor->rank < rank ) ) { + return ATTACK_ON_DAMAGE; + } + + // don't fight back + return ATTACK_IGNORE; +} + +/* +===================== +hhMutilatedHuman::Save +===================== +*/ +void hhMutilatedHuman::Save( idSaveGame *savefile ) const { + savefile->WriteInt( damageFlag ); +} + +/* +===================== +hhMutilatedHuman::Restore +===================== +*/ +void hhMutilatedHuman::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( damageFlag ); +} + +#define LinkScriptVariable( name ) name.LinkTo( scriptObject, #name ) +void hhMutilatedHuman::LinkScriptVariables(void) { + hhMonsterAI::LinkScriptVariables(); + LinkScriptVariable( AI_LIMB_FELL ); +} \ No newline at end of file diff --git a/src/Prey/ai_mutilatedhuman.h b/src/Prey/ai_mutilatedhuman.h new file mode 100644 index 0000000..fcde7d1 --- /dev/null +++ b/src/Prey/ai_mutilatedhuman.h @@ -0,0 +1,23 @@ +#ifndef __PREY_AI_MUTILATEDHUMAN_H__ +#define __PREY_AI_MUTILATEDHUMAN_H__ + +class hhMutilatedHuman : public hhMonsterAI { +public: + CLASS_PROTOTYPE(hhMutilatedHuman); + void Event_AlertFriends(); + void Event_DropBinds(); + void Event_DropProjectiles(); + int ReactionTo( const idEntity *ent ); + void Spawn(); + void AnimMove(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + void LinkScriptVariables(); +protected: + idScriptBool AI_LIMB_FELL; + int damageFlag; +}; + + +#endif //__PREY_AI_MUTILATEDHUMAN_H__ \ No newline at end of file diff --git a/src/Prey/ai_passageway.cpp b/src/Prey/ai_passageway.cpp new file mode 100644 index 0000000..b744d4e --- /dev/null +++ b/src/Prey/ai_passageway.cpp @@ -0,0 +1,144 @@ +// +// ai_passageway.cpp +// +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_EnablePassageway("enable", NULL); +const idEventDef EV_DisablePassageway("disable", NULL); + +// hhAIPassageway +CLASS_DECLARATION(hhAnimated, hhAIPassageway) + EVENT(EV_AnimDone, hhAIPassageway::Event_AnimDone) + EVENT(EV_Activate, hhAIPassageway::Event_Trigger) + EVENT(EV_PostSpawn, hhAIPassageway::PostSpawn) + EVENT(EV_EnablePassageway, hhAIPassageway::Event_EnablePassageway) + EVENT(EV_DisablePassageway, hhAIPassageway::Event_DisablePassageway) +END_CLASS + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + +// +// Spawn() +// +void hhAIPassageway::Spawn() { + timeLastEntered = 0; + lastEntered = NULL; + enabled = TRUE; + hasEntered = FALSE; + + GetPhysics()->SetContents(CONTENTS_SOLID); + + PostEventMS( &EV_PostSpawn, 0 ); +} + +// +// PostSpawn() +// +void hhAIPassageway::PostSpawn() { + SetEnablePassageway(spawnArgs.GetBool("enabled", "1")); +} + +// +// SetEnablePassageway() +// +void hhAIPassageway::SetEnablePassageway(bool tf) { + + // Ignore if we're already in that state + if(enabled == tf) + return; + + fl.refreshReactions = tf; + enabled = tf; +} + +// +// Event_AnimDone() +// +void hhAIPassageway::Event_AnimDone( int animIndex ) { + //When we are done we want to pass ourselves as the activator + activator = this; + + hhAnimated::Event_AnimDone( animIndex ); +} + +// +// Event_Trigger() +// +void hhAIPassageway::Event_Trigger( idEntity *activator ) { + + if(!activator) + return; + + // Ignore being activated by another passage node - they link to eachother, + // so they must be ignored otherwise it will continuously trigger + if( activator->IsType(hhAIPassageway::Type) ) { + return; + } + + // Play our anim + hhAnimated::Event_Activate( activator ); + HH_ASSERT(anim != NULL); + + if( !activator->IsType(hhMonsterAI::Type) ) { + return; + } + + hhMonsterAI *ai = static_cast(activator); + + // Can't enter if dead + if(ai->health <= 0) + return; + + if(!ai->IsInsidePassageway()) + { + //HUMANHEAD PCF mdl 04/29/06 - Added lastEntered check + if(hasEntered && lastEntered.GetEntity() == ai) { + ai->ProcessEvent(&MA_EnterPassageway, this); + hasEntered = FALSE; + } else { + lastEntered = ai; + timeLastEntered = gameLocal.GetTime(); + hasEntered = TRUE; + } + + } + else + { + ai->PostEventMS(&MA_ExitPassageway, 32, this); + hasEntered = FALSE; + } +} + +// +// GetExitPos() +// +idVec3 hhAIPassageway::GetExitPos(void) { + idVec3 pos = GetOrigin(); + idVec3 exitOffset; + spawnArgs.GetVector("exit_offset", "0 0 0", exitOffset); + exitOffset *= GetAxis(); + pos += exitOffset; + pos.z += 0.2f; + return pos; +} + +void hhAIPassageway::Save(idSaveGame *savefile) const { + savefile->WriteInt( timeLastEntered - gameLocal.time ); + lastEntered.Save( savefile ); + savefile->WriteBool( hasEntered ); + savefile->WriteBool( enabled ); +} + +void hhAIPassageway::Restore(idRestoreGame *savefile) { + savefile->ReadInt( timeLastEntered ); + timeLastEntered += gameLocal.time; + + lastEntered.Restore( savefile ); + savefile->ReadBool( hasEntered ); + savefile->ReadBool( enabled ); +} + +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/src/Prey/ai_passageway.h b/src/Prey/ai_passageway.h new file mode 100644 index 0000000..2d4f00f --- /dev/null +++ b/src/Prey/ai_passageway.h @@ -0,0 +1,49 @@ +#ifndef __PREY_AI_PASSAGENODE_H__ +#define __PREY_AI_PASSAGENODE_H__ + + + +// +// hhAIPassageway +// +class hhAIPassageway : public hhAnimated +{ +public: + CLASS_PROTOTYPE(hhAIPassageway); + +#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + virtual void PostSpawn() {}; + void SetEnablePassageway(bool tf) {}; + void Event_AnimDone( int animIndex ) {}; + void Event_Trigger( idEntity *activator ) {}; + void Event_EnablePassageway(void) {} + void Event_DisablePassageway(void) {} +#else + + hhAIPassageway() {timeLastEntered = 0; lastEntered = NULL;} + virtual void Spawn(); + virtual void PostSpawn(); + + void Save(idSaveGame *savefile) const; + void Restore(idRestoreGame *savefile); + + idVec3 GetExitPos(void); // The point that a monster should be spawned upon exiting this passageway + + void SetEnablePassageway(bool tf); + bool IsPassagewayEnabled(void) const {return enabled;} + + int timeLastEntered; // The time someone last entered this passage node + idEntityPtr lastEntered; // The ai that last entered this passage node + bool hasEntered; // FALSE if the AI has not fully entered yet - waiting for one more trigger + +protected: + void Event_AnimDone( int animIndex ); + void Event_Trigger( idEntity *activator ); + void Event_EnablePassageway(void) {SetEnablePassageway(TRUE);} + void Event_DisablePassageway(void) {SetEnablePassageway(FALSE);} + + bool enabled; // TRUE if we are a valid passagway +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +}; + +#endif diff --git a/src/Prey/ai_possessedTommy.cpp b/src/Prey/ai_possessedTommy.cpp new file mode 100644 index 0000000..c220c47 --- /dev/null +++ b/src/Prey/ai_possessedTommy.cpp @@ -0,0 +1,120 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION( hhMonsterAI, hhPossessedTommy ) +END_CLASS + +// TODO: Set up a rhythmic damaging of this entity. + +void hhPossessedTommy::Spawn() { + physicsObj.SetContents( CONTENTS_CORPSE | CONTENTS_MONSTERCLIP | CONTENTS_RENDERMODEL ); + nextDrop = 0; +} + +void hhPossessedTommy::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( attacker->IsType( hhPlayer::Type ) ) { // Being attacked by a player, must be a spiritwalking player + if ( possessedProxy.IsValid() ) { + hhSpiritProxy *proxy = possessedProxy.GetEntity(); + + Hide(); + + proxy->SetOrigin( GetOrigin() ); + proxy->SetAxis( GetAxis() ); + proxy->Show(); + proxy->GetPhysics()->SetContents( CONTENTS_CORPSE | CONTENTS_MONSTERCLIP | CONTENTS_RENDERMODEL ); + + if ( proxy->GetPlayer() ) { + proxy->GetPlayer()->Unpossess(); + } + } + + hhMonsterAI::Killed( inflictor, attacker, damage, dir, location ); + PostEventMS( &EV_Remove, 0 ); + + return; + } + + // OTHERWISE, KILL THIS ENTITY AND KILL THE PLAYER -- create a death proxy here, etc + hhMonsterAI::Killed( inflictor, attacker, damage, dir, location ); + + // Kill the player + if (possessedProxy.IsValid() ) { + if ( possessedProxy->GetPlayer() ) { + possessedProxy->GetPlayer()->PossessKilled(); + } + } +} + +void hhPossessedTommy::Event_Remove(void) { + if (!fl.hidden && possessedProxy.IsValid()) { + Hide(); + + hhSpiritProxy *proxy = possessedProxy.GetEntity(); + + proxy->SetOrigin( GetOrigin() ); + proxy->SetAxis( GetAxis() ); + proxy->Show(); + proxy->GetPhysics()->SetContents( CONTENTS_CORPSE | CONTENTS_MONSTERCLIP | CONTENTS_RENDERMODEL ); + + if ( proxy->GetPlayer() ) { + proxy->GetPlayer()->Unpossess(); + } + } + hhMonsterAI::Event_Remove(); +} + +void hhPossessedTommy::Think(void) { + PROFILE_SCOPE("AI", PROFMASK_NORMAL|PROFMASK_AI); + if (ai_skipThink.GetBool()) { + return; + } + + hhMonsterAI::Think(); + if (possessedProxy.IsValid() && gameLocal.time > nextDrop) { + hhPlayer *player = possessedProxy->GetPlayer(); + if (player) { + player->SetHealth(player->GetHealth() - 1); + if (player->GetHealth() <= 0) { + Hide(); + + possessedProxy->SetOrigin( GetOrigin() ); + possessedProxy->SetAxis( GetAxis() ); + possessedProxy->Show(); + possessedProxy->GetPhysics()->SetContents( CONTENTS_CORPSE | CONTENTS_MONSTERCLIP | CONTENTS_RENDERMODEL ); + + player->Unpossess(); + + player->Killed(this, this, 1, vec3_origin, INVALID_JOINT); + PostEventMS(&EV_Remove, 0); + } + } + + float min = spawnArgs.GetFloat( "dda_drain_min" ); + float max = spawnArgs.GetFloat( "dda_drain_max" ); + nextDrop = gameLocal.time + min + (max - min) * (1.0f - gameLocal.GetDDAValue()); + } +} + +void hhPossessedTommy::Save( idSaveGame *savefile ) const { + possessedProxy.Save( savefile ); +} + +void hhPossessedTommy::Restore( idRestoreGame *savefile ) { + possessedProxy.Restore( savefile ); + nextDrop = 0; +} + +void hhPossessedTommy::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + hhMonsterAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + // Turn to a different direction + float adjust = 45.0f + (gameLocal.random.RandomFloat() * 30.0f); + if (gameLocal.random.RandomInt(100) > 50) { + adjust = -adjust; + } + gameLocal.Printf("Adjusting by %f\n", adjust); + TurnToward(current_yaw + adjust); + move.nextWanderTime = 0; +} + diff --git a/src/Prey/ai_possessedTommy.h b/src/Prey/ai_possessedTommy.h new file mode 100644 index 0000000..9d365ab --- /dev/null +++ b/src/Prey/ai_possessedTommy.h @@ -0,0 +1,30 @@ +#ifndef __PREY_AI_POSSESSED_TOMMY_H__ +#define __PREY_AI_POSSESSED_TOMMY_H__ + +/*********************************************************************** + hhPossessedTommy. +***********************************************************************/ +class hhPossessedTommy : public hhMonsterAI { + +public: + CLASS_PROTOTYPE( hhPossessedTommy ); + +public: + void Spawn(); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void Think(void); + + void SetPossessedProxy( hhSpiritProxy *newProxy ) { possessedProxy = newProxy; } + + virtual void Event_Remove(void); + + 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 ); +protected: + idEntityPtr possessedProxy; + int nextDrop; +}; + +#endif //__PREY_AI_POSSESSED_TOMMY_H__ \ No newline at end of file diff --git a/src/Prey/ai_reaction.cpp b/src/Prey/ai_reaction.cpp new file mode 100644 index 0000000..baf5151 --- /dev/null +++ b/src/Prey/ai_reaction.cpp @@ -0,0 +1,639 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +/////////////////////////////////////////// +// +// hhReactionDesc +// +/////////////////////////////////////////// + + +// +// CausedToStr() +// +idStr hhReactionDesc::CauseToStr(Cause c) { + switch(c) { + case Cause_Damage: + return idStr("Damage"); + case Cause_Touch: + return idStr("Touch"); + case Cause_Use: + return idStr("Use"); + case Cause_None: + return idStr("None"); + case Cause_PlayAnim: + return idStr("PlayAnim"); + case Cause_Telepathic_Trigger: + return idStr("TelepathicTrigger"); + case Cause_Telepathic_Throw: + return idStr("TelepathicThrow"); + } + + return idStr("!!UNDEFINED!!"); +} + +// +// EffectToStr() +// +idStr hhReactionDesc::EffectToStr(Effect e) { + switch(e) { + case Effect_Damage: + return idStr("Damage"); + case Effect_DamageEnemy: + return idStr("DamageEnemy"); + case Effect_VehicleDock: + return idStr("VehicleDock"); + case Effect_Vehicle: + return idStr("Vehicle"); + case Effect_Heal: + return idStr("Heal"); + case Effect_HaveFun: + return idStr("HaveFun"); + case Effect_ProvideCover: + return idStr("ProvideCover"); + case Effect_BroadcastTargetMsgs: + return idStr("BroadcastTargetMsgs"); + case Effect_Climb: + return idStr("Climb"); + case Effect_Passageway: + return idStr("Passageway"); + case Effect_CallBackup: + return idStr("CallBackup"); + } + + return idStr("!!UNDEFINED!!"); +} + +// +// StrToCause() +// +hhReactionDesc::Cause hhReactionDesc::StrToCause(const char* str) { + for(int i=0;i 1) { + flags |= flagReq_KeyValue; + int firstSpace = keyString.Find(' '); + if( firstSpace >= 0 ) { + key = keyString.Left( firstSpace); + keyVal = keyString.Right( keyString.Length() - firstSpace -1); + } + } + else + gameLocal.Error("Invalide key for 'reaction_req_ley' value: %s", (const char*)keyString); + } +//MDC begin + keyString.Empty(); + if( keys.GetString( "finish_key", "", keyString)) { + if( keyString.Length() > 1 ) { + int firstSpace = keyString.Find(' '); + if( firstSpace >= 0 ) { + finish_key = keyString.Left( firstSpace ); + finish_val = keyString.Right( keyString.Length() - firstSpace - 1 ); + } + } + } +//MDC end + + idStr animString(""); + if(keys.GetString("req_anim", "", animString)) { + flags |= flagReq_Anim; + anim = animString; + } + + if(keys.GetBool("req_rangeattack")) { + flags |= flagReq_RangeAttack; + } + if(keys.GetBool("req_meleeattack")) { + flags |= flagReq_MeleeAttack; + } + if(keys.GetBool("req_can_see")) { + flags |= flagReq_CanSee; + } + + if(keys.GetBool("req_telepathic") || cause == hhReactionDesc::Cause_Telepathic_Throw || cause == hhReactionDesc::Cause_Telepathic_Trigger) { + flags |= flagReq_Telepathic; + } + + + // Effect volumes + effectVolumes.Clear(); + const idKeyValue *kv = keys.MatchPrefix("effect_volume", NULL); + while(kv) { + idStr effectVolName = kv->GetValue(); + if(effectVolName.Length() > 0) { + idEntity *e = gameLocal.FindEntity(effectVolName.c_str()); + if(!e) { + gameLocal.Error("Failed to find effect_volume named %s", effectVolName.c_str()); + } + if(!e->IsType(hhReactionVolume::Type)) { + gameLocal.Error("effect_volume named %s was of incorrect spawn type. Must be hhReactionVolume", effectVolName.c_str()); + } + + effectVolumes.AddUnique(static_cast(e)); + } + + // Move to next volume + kv = keys.MatchPrefix("effect_volume", kv); + } + + listenerRadius = keys.GetFloat("listener_radius", "-1.0f"); + listenerMinRadius = keys.GetFloat("listener_min_radius", "-1.0f"); + + // Store listener volumes + listenerVolumes.Clear(); + kv = keys.MatchPrefix("listener_volume", NULL); + while(kv) { + idStr listenVolName = kv->GetValue(); + if(listenVolName.Length() > 0) { + idEntity *e = gameLocal.FindEntity(listenVolName.c_str()); + if(!e) { + gameLocal.Error("Failed to find listener_volume named %s", listenVolName.c_str()); + } + if(!e->IsType(hhReactionVolume::Type)) { + gameLocal.Error("listener_volume named %s was of incorrect spawn type. Must be hhReactionVolume", listenVolName.c_str()); + } + + listenerVolumes.AddUnique(static_cast(e)); + } + + // Move to next volume + kv = keys.MatchPrefix("listener_volume", kv); + } + + // Store touchdir + touchDir = keys.GetString("touchdir",""); + + // Extract out touch offsets + kv = keys.MatchPrefix("touchoffset_", NULL); + touchOffsets.SetGranularity(1); + touchOffsets.Clear(); + idStr tmpStr; + idStr keyName; + int usIndex; + while(kv) { + tmpStr = kv->GetKey(); + usIndex = tmpStr.FindChar("touchoffset_", '_'); + keyName = tmpStr.Mid(usIndex+1, strlen(kv->GetKey())-usIndex-1); + touchOffsets.Set(keyName, kv->GetValue()); + + // Move to the next + kv = keys.MatchPrefix("touchoffset_", kv); + } + + // Extract out touch bounds + kv = keys.MatchPrefix("safebound_", NULL ); + while(kv) { + tmpStr = kv->GetKey(); + usIndex = tmpStr.FindChar("safebound_", '_'); + keyName = tmpStr.Mid(usIndex+1, strlen(kv->GetKey())-usIndex-1); + touchOffsets.Set(keyName, kv->GetValue()); + kv = keys.MatchPrefix("safebound_", kv); + } +} + +/* +================ +hhReactionDesc::Save +================ +*/ +void hhReactionDesc::Save(idSaveGame *savefile) const { + savefile->WriteString(name); + savefile->WriteInt(effect); + savefile->WriteInt(cause); + savefile->WriteInt(flags); + savefile->WriteFloat(effectRadius); + savefile->WriteFloat(effectMinRadius); + + int num = effectVolumes.Num(); + savefile->WriteInt(num); + for(int i = 0; i < num; i++) { + savefile->WriteObject( effectVolumes[i] ); + } + + savefile->WriteString(key); + savefile->WriteString(keyVal); + savefile->WriteString(anim); + savefile->WriteFloat(listenerRadius); + savefile->WriteFloat(listenerMinRadius); + + num = listenerVolumes.Num(); + savefile->WriteInt(num); + for(int i = 0; i < num; i++) { + savefile->WriteObject(listenerVolumes[i]); + } + + savefile->WriteDict(&touchOffsets); + savefile->WriteString(touchDir); + savefile->WriteString(finish_key); + savefile->WriteString(finish_val); +} + +/* +================ +hhReactionDesc::Restore +================ +*/ +void hhReactionDesc::Restore( idRestoreGame *savefile ) { + savefile->ReadString(name); + savefile->ReadInt(reinterpret_cast (effect)); + savefile->ReadInt(reinterpret_cast (cause)); + savefile->ReadInt(reinterpret_cast (flags)); + savefile->ReadFloat(effectRadius); + savefile->ReadFloat(effectMinRadius); + + int num; + savefile->ReadInt(num); + effectVolumes.SetNum(num); + for(int i = 0; i < num; i++) { + savefile->ReadObject(reinterpret_cast (effectVolumes[i])); + } + + savefile->ReadString(key); + savefile->ReadString(keyVal); + savefile->ReadString(anim); + savefile->ReadFloat(listenerRadius); + savefile->ReadFloat(listenerMinRadius); + + savefile->ReadInt(num); + listenerVolumes.SetNum(num); + for(int i = 0; i < num; i++) { + savefile->ReadObject(reinterpret_cast (listenerVolumes[i])); + } + + savefile->ReadDict(&touchOffsets); + savefile->ReadString(touchDir); + savefile->ReadString(finish_key); + savefile->ReadString(finish_val); +} + +/////////////////////////////////////////// +// +// hhReactionVolume +// +/////////////////////////////////////////// +CLASS_DECLARATION(idEntity, hhReactionVolume) +END_CLASS + +// +// Spawn() +// +void hhReactionVolume::Spawn() { + + flags = 0; + + if(spawnArgs.GetBool("players", "1")) { + flags |= flagPlayers; + } + + if(spawnArgs.GetBool("monsters", "1")) { + flags |= flagMonsters; + } + + if(flags == 0) { + gameLocal.Error("Reaction volume %s is not targetting players or monsters - Why even have this volume?", (const char*)name); + } + +} + +bool hhReactionVolume::IsValidEntity( idEntity *ent ) { + assert(GetPhysics() != NULL); + assert(flags != 0); // have to be looking for something - else whats the point? + + if ( !ent || ent->IsHidden() ) { + return false; + } + if(flags & flagMonsters && !ent->IsType(idAI::Type)) { + if(flags & flagPlayers && !ent->IsType(hhPlayer::Type)) { + return false; + } + } + idEntity * entityList[ MAX_GENTITIES ]; + int numEnts = gameLocal.clip.EntitiesTouchingBounds(GetPhysics()->GetAbsBounds(), CONTENTS_BODY, entityList, MAX_GENTITIES); + for(int i=0;i &outValidEnts) { + + assert(GetPhysics() != NULL); + assert(flags != 0); // have to be looking for something - else whats the point? + + idEntity * entityList[ MAX_GENTITIES ]; + + int numEnts = gameLocal.clip.EntitiesTouchingBounds(GetPhysics()->GetAbsBounds(), CONTENTS_BODY, entityList, MAX_GENTITIES); + for(int i=0;iIsHidden()) + continue; + if(flags & flagPlayers && entityList[i]->IsType(hhPlayer::Type)) { + outValidEnts.Append(entityList[i]); + if(ai_debugBrain.GetInteger()) { + gameRenderWorld->DebugArrow(colorMagenta, GetOrigin(), entityList[i]->GetOrigin(), 10, 1000); + } + continue; + } + } + + if(ai_debugBrain.GetInteger()) { + gameRenderWorld->DebugBounds(colorWhite, GetPhysics()->GetBounds(), GetOrigin(), 1000); + } +} + +/* +================ +hhReactionVolume::Save +================ +*/ +void hhReactionVolume::Save(idSaveGame *savefile) const { + savefile->WriteInt(flags); +} + +/* +================ +hhReactionVolume::Restore +================ +*/ +void hhReactionVolume::Restore(idRestoreGame *savefile) { + savefile->ReadInt(flags); +} + +/////////////////////////////////////////// +// +// hhReactionHandler +// +/////////////////////////////////////////// + + +// +// construction +// +hhReactionHandler::hhReactionHandler() { +} + + +// +// destruction +// +hhReactionHandler::~hhReactionHandler() { + reactionDescs.DeleteContents(TRUE); +} + +// +// LoadReactionDesc() +// +const hhReactionDesc* hhReactionHandler::LoadReactionDesc(const char* defName, const idDict *uniqueDescKeys) { + + // Ignore null reactions + if ( !defName || !defName[0] ) { + return NULL; + } + + const hhReactionDesc *ret = NULL; + + // If we do not have unqiue keys, we can just use a cached version + if(!uniqueDescKeys) { + ret = FindReactionDesc(defName); + if(ret) { + return ret; + } + } + + // Get the def dict + const idDict *reactDefDict = gameLocal.FindEntityDefDict(defName); + if(!reactDefDict) { + gameLocal.Error("Failed to find reaction dict named %s", defName); + } + + // Build the keys that will be passed onto the creation + idDict finalDict; + finalDict.Copy(*reactDefDict); + StripReactionPrefix(finalDict); + if(uniqueDescKeys) { + idDict uniqueCopy(*uniqueDescKeys); + StripReactionPrefix(uniqueCopy); + finalDict.Copy(uniqueCopy); + } + + hhReactionDesc* react = CreateReactionDesc(defName, finalDict); + return react; +} + +// +// createReactionDesc() +// +hhReactionDesc* hhReactionHandler::CreateReactionDesc(const char *desiredName, const idDict &keys) { + + idStr finalName = GetUniqueReactionDescName(desiredName); + hhReactionDesc *desc = new hhReactionDesc(); + + desc->name = finalName; + desc->Set(keys); + + reactionDescs.Append(desc); + return desc; +} + +// +// getUniqueReactionDescName() +// +idStr hhReactionHandler::GetUniqueReactionDescName(const char *name) { + + int count = 0; + idStr finalName(name); + while(1) { + if(count > 0) { + finalName = idStr(name) + idStr("_") + idStr(count); + } + + // Did we find a name that was not taken? + if(FindReactionDesc(finalName.c_str()) == NULL) { + return finalName; + } +#ifdef _DEBUG + if(count > 128) { + assert(FALSE); // How the hell did we get so many copies? + } +#endif + + count++; + } +} + +// +// findReactionDesc() +// +const hhReactionDesc* hhReactionHandler::FindReactionDesc(const char *name) { + + for(int i=0;iname.c_str(), name) == 0) { + return reactionDescs[i]; + } + } + + return NULL; +} + +// +// stripReactionPrefix() +// +void hhReactionHandler::StripReactionPrefix(idDict &dict) { + const idKeyValue *kv = NULL;//dict.MatchPrefix("reaction"); + + idDict newDict; + + for(int i=0;iGetKey(); + + // Do we have a reaction token to strip out? + if(idStr::FindText(key.c_str(), "reaction") != -1) { + + int endPrefix = idStr::FindChar(key.c_str(), '_'); + if(endPrefix == -1) { + gameLocal.Error("reactionX_ prefix not found."); + } + idStr realKey(key); + realKey = key.Mid(endPrefix+1, key.Length() - endPrefix - 1); + key = realKey; + } + + //dict.Delete(kv->GetKey().c_str()); + newDict.Set(key.c_str(), kv->GetValue()); + + kv = dict.MatchPrefix("reaction", kv); + } + + + dict.Clear(); + dict.Copy(newDict); +} + +void hhReactionHandler::Save(idSaveGame *savefile) const { + savefile->WriteInt(reactionDescs.Num()); + for (int i = 0; i < reactionDescs.Num(); i++) { + reactionDescs[i]->Save(savefile); + } +} + +void hhReactionHandler::Restore( idRestoreGame *savefile ) { + reactionDescs.DeleteContents(true); + int num; + savefile->ReadInt(num); + reactionDescs.SetNum(num); + for (int i = 0; i < num; i++) { + reactionDescs[i] = new hhReactionDesc(); + reactionDescs[i]->Restore(savefile); + } +} + +void hhReaction::Save(idSaveGame *savefile) const { + savefile->WriteBool(active); + exclusiveOwner.Save(savefile); + savefile->WriteInt(exclusiveUntil); + if (desc) { + savefile->WriteString(desc->name); + } else { + savefile->WriteString(""); + } + causeEntity.Save(savefile); + int num = effectEntity.Num(); + savefile->WriteInt(num); + for(int i = 0; i < num; i++) { + effectEntity[i].ent.Save(savefile); + savefile->WriteFloat(effectEntity[i].weight); + } +} + +void hhReaction::Restore(idRestoreGame *savefile) { + savefile->ReadBool(active); + exclusiveOwner.Restore(savefile); + savefile->ReadInt(exclusiveUntil); + + idStr name; + savefile->ReadString(name); + desc = gameLocal.GetReactionHandler()->FindReactionDesc(name); + causeEntity.Restore(savefile); + int num; + savefile->ReadInt(num); + hhReactionTarget ent; + for(int i = 0; i < num; i++) { + ent.ent.Restore(savefile); + savefile->ReadFloat(ent.weight); + effectEntity.Append(ent); + } +} + diff --git a/src/Prey/ai_reaction.h b/src/Prey/ai_reaction.h new file mode 100644 index 0000000..5385afe --- /dev/null +++ b/src/Prey/ai_reaction.h @@ -0,0 +1,261 @@ +#ifndef __HH_REACTION_H +#define __HH_REACTION_H + + +// +// hhReactionTarget +// +class hhReactionTarget +{ +public: + hhReactionTarget() { + weight = 1.0f; + ent = NULL; + } + + hhReactionTarget(idEntity *e, float w = 1.0f) { + ent = e; + weight = w; + } + + idEntityPtr ent; // The entity that is effected + float weight; // The degree ent is effected +}; + +// +// hhReactionVolume +// +class hhReactionVolume : public idEntity { +public: + CLASS_PROTOTYPE( hhReactionVolume ); + + enum { + flagPlayers = (1<<0), + flagMonsters = (1<<1), + flagActors = (flagPlayers|flagMonsters), + }; + + void Spawn(void); + + // Gets all of the entities (filtered by validFlags) that are inside this volume + void GetValidEntitiesInside(idList &outValidEnts); + bool IsValidEntity( idEntity *ent ); + + void Save(idSaveGame *savefile) const; + void Restore(idRestoreGame *savefile); + +protected: + int flags; // Flags for this volume +}; + +// +// hhReactionDesc +// +// Holds the unchanging properties of a reaction +// +class hhReactionDesc +{ +public: + friend class hhReactionHandler; + hhReactionDesc() { + name = idStr(""); + effect = Effect_Invalid; + cause = Cause_Invalid; + flags = 0; + effectRadius = -1.0f; + effectMinRadius = -1.0f; + effectVolumes.SetGranularity(1); + key = idStr(""); + keyVal = idStr(""); + anim = idStr(""); + listenerRadius = -1.0f; + listenerMinRadius = -1.0f; + listenerVolumes.SetGranularity(1); + } + + enum Cause { + Cause_Invalid = -1, + Cause_Damage, // AI can damage this to get reaction + Cause_Touch, // AI can touch this to get reaction + Cause_Use, // AI can 'use' this to get reaction + Cause_None, // AI does nothing, and the effect is always felt + Cause_PlayAnim, // AI must play an anim to get the desired effect + Cause_Telepathic_Trigger, // AI must use their telepathy to trigger this entity + Cause_Telepathic_Throw, // AI must use their telepathy to physically throw this entity + + Cause_Total + }; + + enum Effect { + Effect_Invalid = -1, + Effect_Damage, // Causes damage (reduces health) + Effect_DamageEnemy, // Causes damage (reduces health) + Effect_VehicleDock, // Causes damage (reduces health) + Effect_Vehicle, // Causes damage (reduces health) + Effect_Heal, // Heals entity (increases health) + Effect_HaveFun, // Something 'fun' for this entity to do + Effect_ProvideCover, // Indicates this entity can possibily provide 'cover' for a monster + Effect_BroadcastTargetMsgs, // This entity will have its target's msg's sent + Effect_Climb, // This entity will let the monster climb it. + Effect_Passageway, // This entity is a harvester passageway + Effect_CallBackup, // Calls for help + + Effect_Total + }; + + enum { + flag_EffectAllPlayers = (1<<0), // This entity automatically targets ALL entities of type hhPlayer + flag_EffectAllMonsters = (1<<1), // This entity automatically targets ALL entities of typehhAI + flag_Exclusive = (1<<2), // This msg is exclusive, and once a monster has committed to it - NO one else should listen to it + flag_EffectListener = (1<<3), // The effect of this reaction is applied to the listener + flag_AnimFaceCauseDir = (1<<4), // When playing an anim for this reaction, face the direction of our cause + flag_AnimTriggerCause = (1<<5), // When playing an anim for this reaction, trigger the cause entity + flag_SnapToPoint = (1<<6), // This msg is only for monsters that are telepathic + flagReq_MeleeAttack = (1<<8), // This msg is for monsters that can melee attack + flagReq_RangeAttack = (1<<9), // This msg is for monsters that can range attack + flagReq_Anim = (1<<10), // This msg is for monsters that have a given anim (anim) + flagReq_KeyValue = (1<<11), // This msg is for monsters that have a given key set (key, keyVal) + flagReq_NoVehicle = (1<<12), // This msg is for monsters that are NOT in vehicles + flagReq_CanSee = (1<<13), // This msg is only for monsters that can see the cause entity + flagReq_Telepathic = (1<<14), // This msg is only for monsters that are telepathic + }; + + + static idStr CauseToStr(Cause c); // Converts activate enum into readable form + static idStr EffectToStr(Effect e); // Converts reaction enum to readable form + static Cause StrToCause(const char* str); // Converts string to caused by enum or Caused_Invalid if not found + static Effect StrToEffect(const char* str); // Converts string to effect enum or Effect_Invalid if not found + void Set(const idDict &keys); // Sets this description to the given key values + bool CauseRequiresPathfinding(void) const {return cause == Cause_Touch || cause == Cause_Use || cause == Cause_PlayAnim;} + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + idStr name; // unique name for each reaction desc + Effect effect; // pdm: for reaction system + Cause cause; // pdm: for reaction system + unsigned int flags; // jrm: reaction flags for reaction system + float effectRadius; // jrm: If > 0.0f then ONLY target entities that are at most this far away + float effectMinRadius; // jrm: if > 0.0f then ONLY target entities that are at least this far away + idList effectVolumes; // jrm: if valid, then only entities that are inside one of these volumes are valid 'effect' entities + idStr key; // jrm: the key required for a monster to be eligable for this reaction msg (ReactionFlag_KeyReq must be set) + idStr keyVal; // jrm: the value of the reaction key required + idStr anim; // jrm: the value of the reaction key required + float listenerRadius; // jrm: listeners farther away than this distance will not receive this reaction + float listenerMinRadius; // jrm: listeners closer than this dist will not receive this reaction + idList listenerVolumes; // jrm: only listeners that are inside one of these volumes will receive this reaction + idDict touchOffsets; // Example: Use "monster_hunter" as key to get touchOffset values + idStr touchDir; // Indicates how a monster should approach the cause entity + + idStr finish_key; // mdc: used to set a key on monster after finishing a reaction + idStr finish_val; // mdc: used to set a key on monster after finishing a reaction +}; + +// +// hhReaction +// +// Describes a reaction ready to be processed by the AI. Holds a description of the reaction, as well as the cause/effect entities. +// Do the cause describe by to to get the effect in applied to +// +class hhReaction +{ +public: + hhReaction() + { + desc = NULL; + causeEntity = NULL; + active = TRUE; + exclusiveOwner = NULL; + exclusiveUntil = -1; + effectEntity.Clear(); + }; + hhReaction(const hhReactionDesc *reactDesc, idEntity *cause, const hhReactionTarget &effect) + { + active = TRUE; + desc = reactDesc; + causeEntity = cause; + exclusiveUntil = -1; + exclusiveOwner = NULL; + effectEntity.Append(effect); + } + + bool IsActive() const {return active;} + bool IsActiveForListener(idEntity *listener) const { + if(!active) { + return FALSE; + } + + // If someone has claimed this, and their claim is still valid... Only active for that particular AI + if(exclusiveOwner.GetEntity() && gameLocal.GetTime() < exclusiveUntil) { + return exclusiveOwner.GetEntity() == listener; + } + + // No one has claimed us, or time has expired, so anyone can claim us now + return TRUE; + }; + + bool ClaimExclusivity(idEntity *claimer, int ms=1000) { + + // Cannot claim, someone else already has it + if(exclusiveOwner.GetEntity() && gameLocal.GetTime() < exclusiveUntil) { + return FALSE; + } + + // They claimed it now.... + exclusiveOwner = claimer; + exclusiveUntil = gameLocal.GetTime() + ms; + return TRUE; + } + + void Save(idSaveGame *savefile) const; + void Restore(idRestoreGame *savefile); + + bool active; // TRUE to be considered for sending + idEntityPtr exclusiveOwner; // The AI that has 'claimed' this reaction + int exclusiveUntil; // Until gameLocal.time has reached this value, this reaction does not send msgs (except for exclusive reactions) + const hhReactionDesc* desc; // Info about the kind of reaction this is + idEntityPtr causeEntity; // The entity to apply the cause to + idList effectEntity; // The effect of doing the cause is felt by these entities +}; + + +// +// hhReactionHandler +// +// Manages all of the reaction descs currently loaded +// +class hhReactionHandler +{ +public: + hhReactionHandler(); + virtual ~hhReactionHandler(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // Loads the given reaction def name. If uniqueDescKeys are specified, a unique hhReactionDesc* is guarenteed to be returned. + // Otherwise, if no override keys are specified and a reaction of the given name is found, it is returned without creating a new one + // The returned desc is ONLY a desc, it is not meant to be modified in any way + const hhReactionDesc* LoadReactionDesc(const char* defName, const idDict &uniqueDescKeys) { + if(uniqueDescKeys.GetNumKeyVals() <= 0) { + return LoadReactionDesc(defName, NULL); + } else { + return LoadReactionDesc(defName, &uniqueDescKeys); + } + } + const hhReactionDesc* LoadReactionDesc(const char* defName) {return LoadReactionDesc(defName, NULL);} + +protected: + friend class hhReaction; + + // Creates a new reaction desc with the given name and keys to describe the reaction + hhReactionDesc* CreateReactionDesc(const char *name, const idDict &keys); + idStr GetUniqueReactionDescName(const char *name); + const hhReactionDesc* FindReactionDesc(const char *name); + void StripReactionPrefix(idDict &dict); // Removes any "reaction_" or "reaction1_" prefix from the given dict + const hhReactionDesc* LoadReactionDesc(const char* defName, const idDict *uniqueDescKeys); + + + idList reactionDescs; +}; + +#endif \ No newline at end of file diff --git a/src/Prey/ai_spawncase.cpp b/src/Prey/ai_spawncase.cpp new file mode 100644 index 0000000..105b7be --- /dev/null +++ b/src/Prey/ai_spawncase.cpp @@ -0,0 +1,227 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +// +// hhAISpawnCase +// + +const idEventDef EV_CreateAI( "", NULL); +const idEventDef EV_AutoTrigger( "", NULL); + +CLASS_DECLARATION( hhAnimated, hhAISpawnCase ) + EVENT( EV_Activate, hhAISpawnCase::Event_Trigger ) + EVENT( EV_CreateAI, hhAISpawnCase::Event_CreateAI ) + EVENT( EV_AutoTrigger, hhAISpawnCase::Event_AutoTrigger ) +END_CLASS + +// +// Spawn() +// +void hhAISpawnCase::Spawn( void ) { + + triggerToggle = FALSE; + aiSpawnCount = 0; + waitingForAutoTrigger = FALSE; + triggerQueue = 0; + + CreateEntSpawnArgs(); + + GetPhysics()->SetContents(CONTENTS_SHOOTABLE|CONTENTS_PLAYERCLIP|CONTENTS_MOVEABLECLIP|CONTENTS_IKCLIP|CONTENTS_SHOOTABLEBYARROW); + + // Next frame, create the AI to attach to this case - Wait a frame so that we can copy targets + PostEventMS(&EV_CreateAI, 0); +} + +// +// Event_CreateAI() +// +void hhAISpawnCase::Event_CreateAI( void ) { + + // Spawn the monster + const char *monsterName = spawnArgs.GetString("def_monster", NULL); + int i; + if ( aiSpawnCount < spawnArgs.GetInt("max_monsters", "1") && monsterName && monsterName[0]) { + idDict args = entSpawnArgs; + + idVec3 offset = spawnArgs.GetVector("monster_offset", "0 0 0"); + offset *= GetPhysics()->GetAxis(); + + args.SetVector( "origin", GetPhysics()->GetOrigin() + offset); + args.SetBool("encased", TRUE); + args.Set("encased_idle", spawnArgs.GetString("anim_encased_monster_idle", "encased_idle")); + args.Set("encased_exit", spawnArgs.GetString("anim_encased_monster_exit", "encased_exit")); + args.Set( "encased_exit_offset", spawnArgs.GetString( "encased_exit_offset", "0 0 0" ) ); + args.SetMatrix("rotation", GetAxis()); + encasedAI = static_cast(gameLocal.SpawnObject(monsterName, &args)); + if(!encasedAI.GetEntity()) { + //gameLocal.Warning("No monster specified for case"); + return; + } + + // Copy the targets that our case has to our newly spawned monster + for( i = 0; i < targets.Num(); i++ ) { + encasedAI->targets.AddUnique(targets[i]); + } + if ( spawnArgs.GetBool( "rotated" ) ) { + encasedAI->SetAxis( GetAxis() ); + encasedAI->GetPhysics()->SetAxis( GetAxis() ); + encasedAI->GetRenderEntity()->axis = GetAxis(); + encasedAI->viewAxis = GetAxis(); + encasedAI->Bind(this, true); + } else { + encasedAI->viewAxis = GetAxis(); + encasedAI->Bind(this, FALSE); + } + aiSpawnCount++; + + if(spawnArgs.GetBool("monster_hide", "0")) { + encasedAI->ProcessEvent(&EV_Hide); + } + } +} + +// +// Event_Trigger() +// +void hhAISpawnCase::Event_Trigger( idEntity *activator ) { + bool triggeredAI = false; + bool showedAI = false; + + // We are waiting for an auto-trigger, so don't fire now - but remember that we need to later + if(waitingForAutoTrigger) { + triggerQueue++; + //gameLocal.Printf("\nQUEUED TRIGGERS: %i", triggerQueue); + return; + } + + //gameLocal.Printf("\n * TRIGGERED *"); + if(encasedAI.GetEntity()) { + if(encasedAI->IsHidden()) { + encasedAI->ProcessEvent(&EV_Show); + showedAI = true; + } + + //if triggered_spawn is set, spawn the encasedAI on the first trigger, + //and but don't activate it until a second trigger + if ( !showedAI || !spawnArgs.GetBool( "triggered_spawn", "0" ) ) { + encasedAI->Unbind(); + // removed since monster_offset should take care of this + //encasedAI->SetOrigin(encasedAI->GetOrigin() + encasedAI->viewAxis[0] * 64); + encasedAI.GetEntity()->ProcessEvent(&EV_Activate, activator); + triggeredAI = true; + } + } + + // Start the anim playing + hhAnimated::Event_Activate(activator); + + if ( spawnArgs.GetInt( "no_anim", "0" ) ) { + ProcessEvent(&EV_CreateAI); + //if triggered_spawn is set, dont show the next AI until the next trigger + if ( encasedAI.IsValid() && triggeredAI && spawnArgs.GetBool( "triggered_spawn", "0" ) ) { + encasedAI->ProcessEvent(&EV_Hide); + } + } + triggerToggle = !triggerToggle; +} + +// +// Event_AutoTrigger() +// +void hhAISpawnCase::Event_AutoTrigger( void ) { + waitingForAutoTrigger = FALSE; + ProcessEvent(&EV_Activate, this); +} + +// +// Event_AnimDone() +// +void hhAISpawnCase::Event_AnimDone( int animIndex ) { + + // Call back first + hhAnimated::Event_AnimDone(animIndex); + + const char *n = NULL; + + // Door is OPEN, now we queue up the CLOSE anim + if(triggerToggle) { + n = spawnArgs.GetString("anim_retrigger"); + + // Should we automatically retrigger? (ie. close the door after it was opened?) + if(spawnArgs.GetFloat("auto_retrigger_delay", "-1") >= 0.0f ) { + int delay = SEC2MS(spawnArgs.GetFloat("auto_retrigger_delay", "-1")); + waitingForAutoTrigger = TRUE; + PostEventMS(&EV_AutoTrigger, delay); + } + } + // Door is CLOSED, now we queue up the OPEN anim + else { + + // If we have queued triggers saved up - then lets fire one now since we are now closed + if(triggerQueue > 0) { + triggerQueue--; + PostEventMS(&EV_Activate, 0, this); + //gameLocal.Printf("\nQUEUED TRIGGERS: %i", triggerQueue); + } + n = spawnArgs.GetString("anim"); + int maxMonsters = spawnArgs.GetInt("max_monsters", "1"); + if(maxMonsters < 0 || aiSpawnCount < maxMonsters) { + Event_CreateAI(); + } else { + //gameLocal.Printf("\nMax monsters reached."); + } + } + + anim = GetAnimator()->GetAnim( n ); + HH_ASSERT( anim ); +} + +/* +================ +hhAISpawnCase::Save +================ +*/ +void hhAISpawnCase::Save( idSaveGame *savefile ) const { + encasedAI.Save( savefile ); + savefile->WriteBool( triggerToggle ); + savefile->WriteInt( aiSpawnCount ); + savefile->WriteBool( waitingForAutoTrigger ); + savefile->WriteInt( triggerQueue ); +} + +/* +================ +hhAISpawnCase::Restore +================ +*/ +void hhAISpawnCase::Restore( idRestoreGame *savefile ) { + encasedAI.Restore( savefile ); + savefile->ReadBool( triggerToggle ); + savefile->ReadInt( aiSpawnCount ); + savefile->ReadBool( waitingForAutoTrigger ); + savefile->ReadInt( triggerQueue ); + + CreateEntSpawnArgs(); +} + +/* +================ +hhAISpawnCase::CreateEntSpawnArgs +================ +*/ +void hhAISpawnCase::CreateEntSpawnArgs( void ) { + // Create list of spawn args, copied to encasedAI in Event_CreateAI + entSpawnArgs.Clear(); + idStr tmpStr, realKeyName; + const idKeyValue *kv = spawnArgs.MatchPrefix("ent_", NULL); + while(kv) { + tmpStr = kv->GetKey(); + int usIndex = tmpStr.FindChar("ent_", '_'); + realKeyName = tmpStr.Mid(usIndex+1, strlen( kv->GetKey())-usIndex-1); + entSpawnArgs.Set(realKeyName, kv->GetValue()); + kv = spawnArgs.MatchPrefix("ent_", kv); + } +} diff --git a/src/Prey/ai_spawncase.h b/src/Prey/ai_spawncase.h new file mode 100644 index 0000000..59fb264 --- /dev/null +++ b/src/Prey/ai_spawncase.h @@ -0,0 +1,34 @@ +#ifndef ai_spawncase_H +#define ai_spawncase_H + +// +// hhAISpawnCase +// +// Spawns a specific monster, attaches to model - waits for trigger before releasing monster +// +class hhAISpawnCase : public hhAnimated { + +public: + CLASS_PROTOTYPE(hhAISpawnCase); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + virtual void Event_Trigger( idEntity *activator ); + virtual void Event_AnimDone( int animIndex ); + +protected: + void Event_CreateAI( void ); + void Event_AutoTrigger( void ); + + void CreateEntSpawnArgs( void ); + + idEntityPtr encasedAI; // The AI that is currently encased and waiting to come out + bool triggerToggle; // Toggles back and forth to indicated which anim to play (open or close) + int aiSpawnCount; // Current number of monsters spawned + bool waitingForAutoTrigger; // True if we are waiting for an auto-trigger msg to come in (from auto_retrigger_delay setting) + int triggerQueue; // Number of triggers that have occurred during the delay period that must be re-sent + idDict entSpawnArgs; // Spawn args to copy to spawned entities +}; + +#endif \ No newline at end of file diff --git a/src/Prey/ai_speech.cpp b/src/Prey/ai_speech.cpp new file mode 100644 index 0000000..86c6158 --- /dev/null +++ b/src/Prey/ai_speech.cpp @@ -0,0 +1,403 @@ +// +// ai_speech.cpp +// +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +extern idCVar ai_skipSpeech; + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +////////////////////////////////////////////////////////////////////// +// +// hhAISpeech +// +////////////////////////////////////////////////////////////////////// + +// jrm: none yet + + + +////////////////////////////////////////////////////////////////////// +// +// hhAISpeechHandler +// +////////////////////////////////////////////////////////////////////// + +float hhAISpeechHandler::TokenMatchWeight[] = {3.0f, 2.0f, 1.0f}; +float hhAISpeechHandler::TokenWildcardMatchWeight[] = {1.5f, 1.0f, 0.5f}; +#define TokenWildCard "*" +#define SpeechShaderPrefix "snd_speech_" // IF THIS CHANGES YOU MUST CHANGE THE LENGTH DEFINE! +#define SpeechShaderPrefixLen 11 +#define FreqToken ':' +#define FreqTokenStr ":" + +// +// Construction +// +hhAISpeechHandler::hhAISpeechHandler() +{ + firstSpeech = NULL; + lastSpeech = NULL; + nextSpeechTime = 0; +}; + +// +// Update() +// +void hhAISpeechHandler::Update() +{ + hhAISpeech *currSpeech = firstSpeech; + hhAISpeech *nextSpeech = NULL; + + if(ai_skipSpeech.GetBool()) + return; + + //for( currSpeech = speechQueue.Next(); currSpeech != NULL; currSpeech = nextSpeech ) + while(currSpeech != NULL) + { + // We can't play any more speech yet! + if(gameLocal.GetTime() < nextSpeechTime) + return; + + nextSpeech = currSpeech->next; + + // Has this speech expired? + if(gameLocal.GetTime() > currSpeech->expireTime) + { + RemoveSpeechFromQueue(currSpeech); + currSpeech = nextSpeech; + continue; + } + + // Play the speech! + currSpeech->speaker->StartSound(currSpeech->snd, SND_CHANNEL_VOICE, 0, true, NULL); + + // Debug Start + /* + idStr speechStr; + sprintf(speechStr, "\"%s\"", (const char*)currSpeech->snd); + speechStr.ToLower(); + gameRenderWorld->DrawText(speechStr, currSpeech->speaker->GetOrigin() + idVec3(0.0f, 0.0f, 12.0f), 0.5f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(),1,1000); + gameLocal.Printf("\n%.01f, %s says %s", (float)MS2SEC(gameLocal.GetTime()), (const char*)currSpeech->speaker->name, (const char*)speechStr); + */ + // Debug End + + RemoveSpeechFromQueue(currSpeech); + nextSpeechTime = gameLocal.GetTime() + MinSpeechWaitTime; + + currSpeech = nextSpeech; + } + + + +}; + +// +// PlaySpeech() +// +bool hhAISpeechHandler::PlaySpeech(idAI *source, idStr sndShd, int expireDelay, bool allowQueue) +{ + if(ai_skipSpeech.GetBool()) + return FALSE; + + if(!allowQueue) { + // just play it and return + HH_ASSERT(FALSE); // Code not done yet! + return TRUE; + } + + // Do we have more slots availble? + hhAISpeech *freeSpeech = GetFreeSpeech(); + if(!freeSpeech) + return FALSE; + + // Setup the new speech + freeSpeech->speaker = source; + freeSpeech->expireTime = gameLocal.GetTime() + expireDelay; + freeSpeech->snd = sndShd; + AddSpeechToQueue(freeSpeech); + return TRUE; +}; + +// +// ClearSpeech() +// +void hhAISpeechHandler::ClearSpeech() +{ + firstSpeech = NULL; + lastSpeech = NULL; + + for(int i=0;inext = NULL; + speech->prev = NULL; + firstSpeech = speech; + lastSpeech = speech; + return; + } + + // Add it to the end of the list + HH_ASSERT(lastSpeech && lastSpeech->next == NULL); + lastSpeech->next = speech; + speech->prev = lastSpeech; + speech->next = NULL; + lastSpeech = speech; +} + +// +// RemoveSpeechFromQueue() +// +void hhAISpeechHandler::RemoveSpeechFromQueue(hhAISpeech *speech) +{ + speech->Clear(); + + // Remove from one of the ends? + if(speech == firstSpeech || speech == lastSpeech) { + if(speech == firstSpeech) { + firstSpeech = firstSpeech->next; + if(firstSpeech) + firstSpeech->prev = NULL; + } + + if(speech == lastSpeech) { + lastSpeech = lastSpeech->prev; + if(lastSpeech) + lastSpeech->next = NULL; + } + return; + } + + + // Middle? + if(speech->next) + speech->next->prev = speech->prev; + if(speech->prev) + speech->prev->next = speech->next; +} + +// +// GetSpeechFrequency() +// +float hhAISpeechHandler::GetSpeechFrequency(idStr sndShader) +{ + if(ai_skipSpeech.GetBool()) + return 0.0f; + + int firstBreak = sndShader.Find(FreqToken); + if(firstBreak == -1) + return 1.0f; + + int secondBreak = sndShader.Find(FreqTokenStr, true, firstBreak); + if(secondBreak == -1) + secondBreak = sndShader.Length(); + + idStr freqStr = sndShader.Mid(firstBreak, secondBreak - firstBreak); + + int f = atoi((const char*)freqStr); + return idMath::ClampFloat(0.0f, 1.0f, (float(f) * 0.01f)); +}; + +// +// GetSpeechResponseDelay() +// +int hhAISpeechHandler::GetSpeechResponseDelay(idStr sndShader) +{ + // TODO: Fill this code in when we actually need it. Does 1 second work all the time? + + return 500; +}; + +// +// GetSpeechSoundShader() +// +bool hhAISpeechHandler::GetSpeechSoundShader(idAI *source, idStr speechDesc, idStr &outClosestSndShd) +{ + HH_ASSERT(source != NULL); + + const idKeyValue *kv = NULL; + + if(ai_skipSpeech.GetBool()) + return FALSE; + + // Quick out - do we have this EXACT snd shader? + idStr sndShader = SpeechShaderPrefix + speechDesc; + kv = source->spawnArgs.MatchPrefix(sndShader); + if(kv) { + outClosestSndShd = kv->GetKey(); + return TRUE; + } + + // Extrace the tokens for our 'desired' speech pattern + SpeechTokens desired; + if(!ExtractSpeechTokens(speechDesc, desired)) + return FALSE; + + // Find our best potential speech pattern match + float bestRating = 0.0f; + float currRating = -1.0f; + const idKeyValue *bestKey = NULL; + SpeechTokens currPotential; + + kv = source->spawnArgs.MatchPrefix(SpeechShaderPrefix); + + while(kv) + { + idStr speechStr; + speechStr = kv->GetKey().Mid(SpeechShaderPrefixLen, kv->GetKey().Length() - SpeechShaderPrefixLen); + + if(!ExtractSpeechTokens(speechStr, currPotential)) + currRating = -1.0f; + else + currRating = GetMatchRating(desired, currPotential); + + if(currRating > bestRating) + { + bestRating = currRating; + bestKey = kv; + } + + kv = source->spawnArgs.MatchPrefix(SpeechShaderPrefix,kv); + } + + // We've found a match to some degree + if(bestKey && bestRating > 0.0f) + { + outClosestSndShd = bestKey->GetKey(); + return TRUE; + } + + return FALSE; +}; + +// +// GetMatchRating() +// +float hhAISpeechHandler::GetMatchRating(SpeechTokens &desired, SpeechTokens &potential) +{ + float rating = 0.0f; + + for(int i=0;i= 0) + outTokens.tokens[TT_What] = outTokens.tokens[TT_What].Mid(0, outTokens.tokens[TT_What].Length() - (findFreqBreak+1)); + + return true; +}; + +/* +================ +hhAISpeechHandler::Save +================ +*/ +void hhAISpeechHandler::Save(idSaveGame *savefile) const { + for (int i = 0; i < SpeechPoolSize; i++) { + savefile->WriteObject(speechPool[i].speaker); + savefile->WriteString(speechPool[i].snd); + savefile->WriteInt(speechPool[i].expireTime); + savefile->WriteInt(GetPoolIndex(speechPool[i].next)); + savefile->WriteInt(GetPoolIndex(speechPool[i].prev)); + } + + savefile->WriteInt(GetPoolIndex(firstSpeech)); + savefile->WriteInt(GetPoolIndex(lastSpeech)); + savefile->WriteInt(nextSpeechTime); +} + +/* +================ +hhAISpeechHandler::Restore +================ +*/ +void hhAISpeechHandler::Restore(idRestoreGame *savefile) { + int tmp; + for (int i = 0; i < SpeechPoolSize; i++) { + savefile->ReadObject(reinterpret_cast (speechPool[i].speaker)); + savefile->ReadString(speechPool[i].snd); + savefile->ReadInt(speechPool[i].expireTime); + savefile->ReadInt(tmp); + speechPool[i].next = (tmp == -1 ? NULL : &speechPool[tmp]); + savefile->ReadInt(tmp); + speechPool[i].prev = (tmp == -1 ? NULL : &speechPool[tmp]); + } + + savefile->ReadInt(tmp); + firstSpeech = (tmp == -1 ? NULL : &speechPool[tmp]); + savefile->ReadInt(tmp); + lastSpeech = (tmp == -1 ? NULL : &speechPool[tmp]); + savefile->ReadInt(nextSpeechTime); +} + + +int hhAISpeechHandler::GetPoolIndex(hhAISpeech *obj) const { + if (!obj) { + return -1; + } + + for (int i = 0; i < SpeechPoolSize; i++) { + if (obj == &speechPool[i]) { + return i; + } + } + + HH_ASSERT(!"Couldn't find pool index!"); + return -1; +} + +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/src/Prey/ai_speech.h b/src/Prey/ai_speech.h new file mode 100644 index 0000000..8aa2e63 --- /dev/null +++ b/src/Prey/ai_speech.h @@ -0,0 +1,102 @@ +#ifndef __HH_AISPEECH_H +#define __HH_AISPEECH_H + + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +// +// hhAISpeech +// +class hhAISpeech +{ +public: + hhAISpeech() {Clear(); next = NULL; prev = NULL; } + + void Clear(void) {speaker = NULL; expireTime = -1;} + bool IsCleared(void) {return speaker == NULL;} // Assumes speech will NEVER come from a 'null' speaker, NULL = empty + + idAI* speaker; + idStr snd; + int expireTime; + + hhAISpeech *next; + hhAISpeech *prev; +}; +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + +// +// hhAISpeechHandler +// +class hhAISpeechHandler +{ +public: +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + enum + { + SpeechPoolSize = 8, + MinSpeechWaitTime = 2000, // We have to wait at LEAST this long before we can play ANY more speech + }; + + hhAISpeechHandler(); + virtual ~hhAISpeechHandler() { } + virtual void Update(); // note: not named Think() on purpose, because we are NOT an entity + + // Play speech from a given AI - returns true if speech was queued - DOES NOT mean it is guarenteed to play + bool PlaySpeech(idAI *source, idStr sndShader, int expireDelay = 1000, bool allowQueue = true); + + // Finds the matching snd shader to the speechDesc given, as well as 0->1 of the frequency for this speech + // Returns FALSE if no possible match could be found. + bool GetSpeechSoundShader(idAI *source, idStr speechDesc, idStr &outClosestSndShd); + + // From a sound shader name, return the frequency from 0->1 + float GetSpeechFrequency(idStr sndShader); + + // From a sound shader name, return the delay in MS of how long it takes to 'comprehend' the given shader. + // Example: Someone says "Take the shuttle!" and we want the person taking the shuttle to wait at least 1 second before they do it + // so it looks like they comprehended the command + int GetSpeechResponseDelay(idStr sndShader); + + // Remove all queued speech + void ClearSpeech(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +protected: + hhAISpeech speechPool[SpeechPoolSize]; // Pool of all possible speech slots + hhAISpeech* GetFreeSpeech(); // Returns the first empty speech obj available in the pool + int ClearExpiredSpeech(); // Clears out any expired speech in the pool + + enum TokenType + { + TT_Desire = 0, // The desire we are going to satisfy + TT_How, // How we are going to satisfy it + TT_What, // What entity we will use to satisfy it + + TT_Total, + }; + + struct SpeechTokens + { + idStr tokens[TT_Total]; + }; + + static float TokenMatchWeight[TT_Total]; + static float TokenWildcardMatchWeight[TT_Total]; + + // Returns a 0+ rating of 'how' close the potential speech matches the desired speech + float GetMatchRating(SpeechTokens &desired, SpeechTokens &potential); + bool ExtractSpeechTokens(idStr speechDesc, SpeechTokens &outTokens); + + // because I hate id's linked list + hhAISpeech *firstSpeech; + hhAISpeech *lastSpeech; + void AddSpeechToQueue(hhAISpeech *speech); + void RemoveSpeechFromQueue(hhAISpeech *speech); + int GetPoolIndex(hhAISpeech *obj) const; + + int nextSpeechTime; // The time we are next allowed to play speech +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +}; + + +#endif \ No newline at end of file diff --git a/src/Prey/ai_sphereboss.cpp b/src/Prey/ai_sphereboss.cpp new file mode 100644 index 0000000..6a3b606 --- /dev/null +++ b/src/Prey/ai_sphereboss.cpp @@ -0,0 +1,533 @@ + + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_UpdateTarget( "" ); +const idEventDef EV_DirectMoveToPosition("directMoveToPosition", "v" ); +const idEventDef MA_GetCircleNode("getCircleNode", NULL, 'e' ); +const idEventDef MA_SpinClouds("spinClouds", "f" ); +const idEventDef MA_SetSeekScale("setSeekScale", "f" ); + +CLASS_DECLARATION(hhMonsterAI, hhSphereBoss) + EVENT( EV_UpdateTarget, hhSphereBoss::Event_UpdateTarget ) + EVENT( EV_DirectMoveToPosition, hhSphereBoss::Event_DirectMoveToPosition ) + EVENT( MA_GetCircleNode, hhSphereBoss::Event_GetCircleNode ) + EVENT( MA_SpinClouds, hhSphereBoss::Event_SpinClouds ) + EVENT( MA_SetSeekScale, hhSphereBoss::Event_SetSeekScale ) +END_CLASS + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + +void hhSphereBoss::Spawn() { + for ( int i=0;i<10;i++ ) { + lastTargetPos.Append( vec3_zero ); + } + nextShieldImpact = 0; + lastNodeIndex = -1; + PostEventSec( &EV_UpdateTarget, spawnArgs.GetFloat( "target_period", "0.1" ) ); +} + +void hhSphereBoss::Event_UpdateTarget() { + for ( int i=0;i<10;i++ ) { + if ( i == 9 ) { + if ( enemy.IsValid() ) { + lastTargetPos[i] = enemy->GetOrigin(); + } else { + lastTargetPos[i] = lastTargetPos[i - 1]; + } + } else { + lastTargetPos[i] = lastTargetPos[i + 1]; + } + } + PostEventSec( &EV_UpdateTarget, spawnArgs.GetFloat( "target_period", "0.1" ) ); +} + +//overridden to allow custom accuracy/numbers per projectile def +idProjectile *hhSphereBoss::LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone, const idDict* desiredProjectileDef ) { + idVec3 muzzle; + idVec3 dir; + idVec3 start; + trace_t tr; + idBounds projBounds; + float distance; + const idClipModel *projClip; + float attack_accuracy; + float attack_cone; + float projectile_spread; + float diff; + float angle; + float spin; + idAngles ang; + int num_projectiles; + int i; + idMat3 axis; + idVec3 tmp; + idProjectile *lastProjectile; + + //HUMANHEAD mdc - added to support multiple projectiles + if( desiredProjectileDef ) { //try to set our projectile to the desiredProjectile + int projIndex = FindProjectileInfo( desiredProjectileDef ); + if( projIndex >= 0 ) { + SetCurrentProjectile( projIndex ); + } + } + //HUMANHEAD END + + + if ( !projectileDef ) { + gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() ); + return NULL; + } + + if ( projectileDef->GetFloat( "attack_accuracy" ) ) { + attack_accuracy = projectileDef->GetFloat( "attack_accuracy", "7" ); + } else { + attack_accuracy = spawnArgs.GetFloat( "attack_accuracy", "7" ); + } + attack_cone = spawnArgs.GetFloat( "attack_cone", "70" ); + if ( projectileDef->GetFloat( "projectile_spread" ) ) { + projectile_spread = projectileDef->GetFloat( "projectile_spread", "0" ); + } else { + projectile_spread = spawnArgs.GetFloat( "projectile_spread", "0" ); + } + if ( projectileDef->GetFloat( "num_projectiles" ) ) { + num_projectiles = projectileDef->GetFloat( "num_projectiles", "1" ); + } else { + num_projectiles = spawnArgs.GetFloat( "num_projectiles", "1" ); + } + + GetMuzzle( jointname, muzzle, axis ); + + if ( !projectile.GetEntity() ) { + CreateProjectile( muzzle, axis[ 0 ] ); + } + + lastProjectile = projectile.GetEntity(); + + if ( target != NULL ) { + tmp = target->GetPhysics()->GetAbsBounds().GetCenter() - muzzle; + tmp.Normalize(); + axis = tmp.ToMat3(); + } else { + axis = viewAxis; + } + + // rotate it because the cone points up by default + tmp = axis[2]; + axis[2] = axis[0]; + axis[0] = -tmp; + + // make sure the projectile starts inside the monster bounding box + const idBounds &ownerBounds = physicsObj.GetAbsBounds(); + projClip = lastProjectile->GetPhysics()->GetClipModel(); + projBounds = projClip->GetBounds().Rotate( axis ); + + // 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( muzzle, viewAxis[ 0 ], distance ) ) { + start = muzzle + 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(); + } + + gameLocal.clip.Translation( tr, start, muzzle, projClip, axis, MASK_SHOT_RENDERMODEL, this ); + muzzle = tr.endpos; + + // set aiming direction + if ( spawnArgs.GetBool( "lag_target", "0" ) ) { + dir = (lastTargetPos[0] - muzzle).ToNormal(); + } else { + GetAimDir( muzzle, target, this, dir ); + } + ang = dir.ToAngles(); + + // adjust his aim so it's not perfect. uses sine based movement so the tracers appear less random in their spread. + float t = MS2SEC( gameLocal.time + entityNumber * 497 ); + ang.pitch += idMath::Sin16( t * 5.1 ) * attack_accuracy; + ang.yaw += idMath::Sin16( t * 6.7 ) * attack_accuracy; + + if ( clampToAttackCone ) { + // 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, current_yaw ); + if ( diff > attack_cone ) { + ang.yaw = current_yaw + attack_cone; + } else if ( diff < -attack_cone ) { + ang.yaw = current_yaw - attack_cone; + } + } + + axis = ang.ToMat3(); + + float spreadRad = DEG2RAD( projectile_spread ); + for( i = 0; i < num_projectiles; i++ ) { + // spread the projectiles out + angle = idMath::Sin( spreadRad * 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(); + + // launch the projectile + if ( !projectile.GetEntity() ) { + CreateProjectile( muzzle, dir ); + } + lastProjectile = projectile.GetEntity(); + lastProjectile->Launch( muzzle, dir, vec3_origin ); + projectile = NULL; + } + + TriggerWeaponEffects( muzzle,axis ); + + lastAttackTime = gameLocal.time; + +//HUMANHEAD mdc - added to support multiple projectiles + projectile = NULL; + SetCurrentProjectile( projectileDefaultDefIndex ); //set back to our default projectile to be on the safe side +//HUMANHEAD END + + return lastProjectile; +} + +void hhSphereBoss::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + HandleNoGore(); + if ( !AI_DEAD ) { + AI_DEAD = true; + state = GetScriptFunction( "state_SphereDeath" ); + SetState( state ); + SetWaitState( "" ); + } +} + +void hhSphereBoss::FlyTurn( void ) { + if ( AI_FACE_ENEMY ) { + TurnToward( enemy->GetOrigin() ); + } else { + if ( move.moveCommand == MOVE_FACE_ENEMY ) { + TurnToward( lastVisibleEnemyPos ); + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + } else if ( move.speed > 0.0f ) { + const idVec3 &vel = physicsObj.GetLinearVelocity(); + if ( vel.ToVec2().LengthSqr() > 0.1f ) { + TurnToward( vel.ToYaw() ); + } + } + } + Turn(); +} + +void hhSphereBoss::Event_SetSeekScale( float new_scale ) { + fly_seek_scale = new_scale; +} + +void hhSphereBoss::Event_GetCircleNode() { + if ( !enemy.IsValid() ) { + idThread::ReturnEntity( NULL ); + return; + } + idEntity *ent = NULL; + idEntity *bestEnt = NULL; + lastNodeIndex++; + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( !ent || !ent->IsType( hhAINode::Type ) ) { + continue; + } + if ( !ent->spawnArgs.GetBool( "circle_node" ) ) { + continue; + } + if ( lastNodeIndex == ent->spawnArgs.GetInt( "circle_node_index" ) ) { + bestEnt = ent; + break; + } + } + if ( !bestEnt ) { + lastNodeIndex = 0; + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( !ent || !ent->IsType( hhAINode::Type ) ) { + continue; + } + if ( !ent->spawnArgs.GetBool( "circle_node" ) ) { + continue; + } + if ( lastNodeIndex == ent->spawnArgs.GetInt( "circle_node_index" ) ) { + bestEnt = ent; + break; + } + } + } + if ( bestEnt ) { + idThread::ReturnEntity( bestEnt ); + } else { + idThread::ReturnEntity( NULL ); + } +} + +void hhSphereBoss::Event_GetCombatNode() { + idEntity *ent = NULL; + float bestDist = 0.0f; + float dist; + idEntity *bestEnt = NULL; + idList list; + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( !ent || !ent->spawnArgs.GetBool( "ainode" ) ) { + continue; + } + dist = (ent->GetOrigin() - GetOrigin()).Length(); + if ( dist < spawnArgs.GetFloat( "node_dist", "400" ) ) { + continue; + } + list.Append( ent ); + } + if ( list.Num() > 0 ) { + idThread::ReturnEntity( list[gameLocal.random.RandomInt(list.Num())] ); + } else { + idThread::ReturnEntity( NULL ); + } +} + +void hhSphereBoss::Event_DirectMoveToPosition(const idVec3 &pos) { + StopMove(MOVE_STATUS_DONE); + DirectMoveToPosition(pos); +} + +void hhSphereBoss::FlyMove( void ) { + idVec3 goalPos; + idVec3 oldorigin; + idVec3 newDest; + + AI_BLOCKED = false; + if ( ( move.moveCommand != MOVE_NONE ) && ReachedPos( move.moveDest, move.moveCommand ) ) { + if ( AI_FACE_ENEMY ) { + StopMove( MOVE_STATUS_DONE ); + } else { + AI_MOVE_DONE = true; + } + } + + idVec3 vel = physicsObj.GetLinearVelocity(); + goalPos = move.moveDest; + if ( ReachedPos( move.moveDest, move.moveCommand ) ) { + StopMove( MOVE_STATUS_DONE ); + } + + if ( move.speed ) { + FlySeekGoal( vel, goalPos ); + } + AddFlyBob( vel ); + AdjustFlySpeed( vel ); + 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( disableGravity ); + RunPhysics(); + + monsterMoveResult_t moveResult = physicsObj.GetMoveResult(); + if ( !af_push_moveables && attack.Length() && TestMelee() ) { + DirectDamage( attack, enemy.GetEntity() ); + } else { + idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); + if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) { + KickObstacles( viewAxis[ 0 ], kickForce, blockEnt ); + } else if ( moveResult == MM_BLOCKED ) { + move.blockTime = gameLocal.time + 500; + AI_BLOCKED = true; + } + } + + idVec3 org = physicsObj.GetOrigin(); + if ( oldorigin != org ) { + TouchTriggers(); + } + + if ( ai_debugMove.GetBool() ) { + 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, goalPos, gameLocal.msec, true ); + gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true ); + DrawRoute(); + } +} + +void hhSphereBoss::AdjustFlySpeed( idVec3 &vel ) { + float speed; + + // apply dampening + float damp = spawnArgs.GetFloat( "fly_dampening", "0.01" ); + vel -= vel * damp * MS2SEC( gameLocal.msec ); + + // gradually speed up/slow down to desired speed + speed = vel.Normalize(); + speed += ( move.speed - speed ) * MS2SEC( gameLocal.msec ); + if ( speed < 0.0f ) { + speed = 0.0f; + } else if ( move.speed && ( speed > move.speed ) ) { + speed = move.speed; + } + + vel *= speed; +} + +bool hhSphereBoss::ReachedPos( const idVec3 &pos, const moveCommand_t moveCommand ) const { + if ( move.moveType == MOVETYPE_SLIDE ) { + idBounds bnds( idVec3( -4, -4.0f, -8.0f ), idVec3( 4.0f, 4.0f, 64.0f ) ); + bnds.TranslateSelf( physicsObj.GetOrigin() ); + if ( bnds.ContainsPoint( pos ) ) { + return true; + } + } else { + if ( ( moveCommand == MOVE_TO_ENEMY ) || ( moveCommand == MOVE_TO_ENTITY ) ) { + if ( physicsObj.GetAbsBounds().IntersectsBounds( idBounds( pos ).Expand( 8.0f ) ) ) { + return true; + } + } else { + idBounds bnds( idVec3( -64.0, -64.0f, -64.0f ), idVec3( 64.0, 64.0f, 64.0f ) ); + bnds.TranslateSelf( physicsObj.GetOrigin() ); + if ( bnds.ContainsPoint( pos ) ) { + return true; + } + } + } + return false; +} + +#define LinkScriptVariable( name ) name.LinkTo( scriptObject, #name ) +void hhSphereBoss::LinkScriptVariables() { + hhMonsterAI::LinkScriptVariables(); + LinkScriptVariable( AI_FACE_ENEMY ); + LinkScriptVariable( AI_CAN_DAMAGE ); +} + +void hhSphereBoss::Event_SpinClouds( float shouldSpin ) { + if ( shouldSpin == 0.0f ) { + const idKeyValue *kv = spawnArgs.MatchPrefix( "cloud" ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent ) { + ent->SetShaderParm( spawnArgs.GetInt( "spin_parm", "4" ), ent->spawnArgs.GetFloat( "shaderparm4", "0" ) ); + } + kv = spawnArgs.MatchPrefix( "cloud", kv ); + } + } else { + const idKeyValue *kv = spawnArgs.MatchPrefix( "cloud" ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent ) { + ent->SetShaderParm( spawnArgs.GetInt( "spin_parm", "4" ), spawnArgs.GetFloat( "spin_value", "0.5" ) ); + } + kv = spawnArgs.MatchPrefix( "cloud", kv ); + } + } +} + +void hhSphereBoss::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + //overridden to allow custom wound code + if ( !AI_CAN_DAMAGE && inflictor && spawnArgs.MatchPrefix("mineDamage") ) { + hhFxInfo fxInfo; + fxInfo.RemoveWhenDone( true ); + if ( gameLocal.time >= nextShieldImpact ) { + nextShieldImpact = gameLocal.time + int(spawnArgs.GetFloat( "shield_impact_freq", "0.4" ) * 1000); + idVec3 offset; + if ( attacker && inflictor ) { + offset = (attacker->GetOrigin() - inflictor->GetOrigin()).ToNormal() * spawnArgs.GetFloat( "shield_impact_offset", "150" ); + const char *defName = spawnArgs.GetString( "fx_shield_impact" ); + idEntityFx *impactFx = SpawnFxLocal( defName, inflictor->GetOrigin() + offset, mat3_identity, &fxInfo, gameLocal.isClient ); + if ( impactFx ) { + impactFx->Bind( this, true ); + } + } + } + StartSound( "snd_shield_impact", SND_CHANNEL_BODY ); + if ( inflictor && inflictor->IsType( idProjectile::Type ) ) { + inflictor->PostEventMS( &EV_Remove, 0 ); + } + } else { + hhFxInfo fxInfo; + fxInfo.RemoveWhenDone( true ); + if ( gameLocal.time >= nextShieldImpact ) { + nextShieldImpact = gameLocal.time + int(spawnArgs.GetFloat( "shield_impact_freq", "0.4" ) * 1000); + idVec3 offset; + if ( attacker && inflictor ) { + offset = (attacker->GetOrigin() - inflictor->GetOrigin()).ToNormal() * spawnArgs.GetFloat( "shield_impact_offset", "150" ); + BroadcastFxInfoPrefixed( "fx_pain_impact", inflictor->GetOrigin() + offset, mat3_identity, &fxInfo ); + } + } + } + + bool mine_damage = false; + if ( !AI_CAN_DAMAGE && spawnArgs.MatchPrefix("mineDamage") != NULL ) { + const idKeyValue *kv = spawnArgs.MatchPrefix("mineDamage"); + while( kv && kv->GetValue().Length() ) { + if ( !kv->GetValue().Icmp(damageDefName) ) { + mine_damage = true; + break; + } + kv = spawnArgs.MatchPrefix("mineDamage", kv); + } + if (!mine_damage) { + return; + } + } + + if ( !mine_damage ) { + hhMonsterAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + } + + if ( !AI_CAN_DAMAGE && health > 0 ) { + SetState( GetScriptFunction( "state_Pain" ) ); + SetWaitState( "" ); + } +} + +/* +===================== +hhSphereBoss::Save +===================== +*/ +void hhSphereBoss::Save( idSaveGame *savefile ) const { + savefile->WriteInt( lastTargetPos.Num() ); + savefile->WriteInt( nextShieldImpact ); + for ( int i=0;iWriteVec3( lastTargetPos[i] ); + } +} + +/* +===================== +hhSphereBoss::Restore +===================== +*/ +void hhSphereBoss::Restore( idRestoreGame *savefile ) { + int num = 0; + savefile->ReadInt( num ); + savefile->ReadInt( nextShieldImpact ); + lastTargetPos.SetNum( num ); + for ( int i=0;iReadVec3( lastTargetPos[i] ); + } +} + +void hhSphereBoss::AddLocalMatterWound( jointHandle_t jointNum, const idVec3 &localOrigin, const idVec3 &localNormal, const idVec3 &localDir, int damageDefIndex, const idMaterial *collisionMaterial ) { + //overridden to remove woundmanager hook and use custom wound code is Damage() + if ( AI_CAN_DAMAGE ) { + return hhMonsterAI::AddLocalMatterWound( jointNum, localOrigin, localNormal, localDir, damageDefIndex, collisionMaterial ); + } +} +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/src/Prey/ai_sphereboss.h b/src/Prey/ai_sphereboss.h new file mode 100644 index 0000000..e00422e --- /dev/null +++ b/src/Prey/ai_sphereboss.h @@ -0,0 +1,45 @@ + +#ifndef __PREY_AI_SPHEREBOSS_H__ +#define __PREY_AI_SPHEREBOSS_H__ + +class hhSphereBoss : public hhMonsterAI { + +public: + CLASS_PROTOTYPE(hhSphereBoss); +#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + void Event_UpdateTarget() {}; + void Event_GetCombatNode() {}; + void Event_GetCircleNode() {}; + void Event_DirectMoveToPosition(const idVec3 &pos) {}; + void Event_SpinClouds( float shouldSpin ) {}; + void Event_SetSeekScale( float new_scale ) {}; +#else + void Spawn(); + void FlyTurn(); + void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + idProjectile *LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone, const idDict* desiredProjectileDef ); + void AdjustFlySpeed( idVec3 &vel ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void FlyMove( void ); + bool ReachedPos( const idVec3 &pos, const moveCommand_t moveCommand ) const; + void LinkScriptVariables(); + void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + void AddLocalMatterWound( jointHandle_t jointNum, const idVec3 &localOrigin, const idVec3 &localNormal, const idVec3 &localDir, int damageDefIndex, const idMaterial *collisionMaterial ); + + void Event_UpdateTarget(); + void Event_GetCombatNode(); + void Event_GetCircleNode(); + void Event_DirectMoveToPosition(const idVec3 &pos); + void Event_SpinClouds( float shouldSpin ); + void Event_SetSeekScale( float new_scale ); +protected: + idScriptBool AI_FACE_ENEMY; + idScriptBool AI_CAN_DAMAGE; + idList lastTargetPos; + int lastNodeIndex; + int nextShieldImpact; +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +}; + +#endif \ No newline at end of file diff --git a/src/Prey/anim_baseanim.cpp b/src/Prey/anim_baseanim.cpp new file mode 100644 index 0000000..b2b85fa --- /dev/null +++ b/src/Prey/anim_baseanim.cpp @@ -0,0 +1,104 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" +#include "../renderer/Model_local.h" // Hackish. Needed to access certain classes. Imitates game/anim/Blend.cpp +/* +=============== +hhMD5Anim::hhMD5Anim +=============== +*/ +hhMD5Anim::hhMD5Anim(void) { + + start = 0.0f; + end = 1.0f; +} + + +/* +============================== +hhMD5Anim::setLimits + Set the limits of the animation to play. + start and end should vary from 0 to 1 +============================== +*/ +void hhMD5Anim::SetLimits(float start, float end) const { + + if (start <= end) { + this->start = start; + this->end = end; + } + else { + this->start = end; + this->end = start; + } + +} + + +/* +============================== +hhMD5Anim::ConvertTimeToFrame +============================== +*/ +void hhMD5Anim::ConvertTimeToFrame( int time, int cycleCount, + frameBlend_t &frame ) const { + float newTime; + float timeOffset; + + + timeOffset = animLength * start; + + if (time < 0) { + newTime = timeOffset; + } + else { + newTime = time + timeOffset; + } + + idMD5Anim::ConvertTimeToFrame(newTime, cycleCount, frame); + + +} + + +/* +============================== +hhMD5Anim::Length +============================== +*/ +int hhMD5Anim::Length(void) const { + int intLength; + + + intLength = animLength * (end - start); + if ( ( intLength == 0 ) && ( end != start ) ) { + intLength = 1; + } + + + return( intLength ); + +} + +/* +================ +hhMD5Anim::Save +================ +*/ +void hhMD5Anim::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( start ); + savefile->WriteFloat( end ); +} + +/* +================ +hhMD5Anim::Restore +================ +*/ +void hhMD5Anim::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( start ); + savefile->ReadFloat( end ); +} + diff --git a/src/Prey/anim_baseanim.h b/src/Prey/anim_baseanim.h new file mode 100644 index 0000000..0b123ba --- /dev/null +++ b/src/Prey/anim_baseanim.h @@ -0,0 +1,29 @@ + +#ifndef __PREY_ANIM_MD5ANIM_H__ +#define __PREY_ANIM_MD5ANIM_H__ + +// Forward declar +class idMD5Bone; + +class hhMD5Anim : public idMD5Anim { + +public: + + hhMD5Anim(void); + void SetLimits(float start, float end) const; + virtual void ConvertTimeToFrame( int time, int cycleCount, frameBlend_t &frame ) const; + virtual int Length( void ) const; + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +protected: + mutable float start; + mutable float end; + + +}; + + +#endif /* __PREY_ANIM_MD5ANIM_H__ */ + diff --git a/src/Prey/force_converge.cpp b/src/Prey/force_converge.cpp new file mode 100644 index 0000000..4a81156 --- /dev/null +++ b/src/Prey/force_converge.cpp @@ -0,0 +1,173 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION( idForce, hhForce_Converge ) +END_CLASS + + +hhForce_Converge::hhForce_Converge( void ) { + restoreFactor = 1.0f; + restoreForceSlack = 1.0f; + restoreTime = 0.1f; + ent = NULL; + physics = NULL; + axisEnt = NULL; + bShuttle = false; +} + +hhForce_Converge::~hhForce_Converge( void ) { +} + +void hhForce_Converge::Save(idSaveGame *savefile) const { + ent.Save(savefile); + axisEnt.Save(savefile); + savefile->WriteVec3( target ); + savefile->WriteVec3( offset ); + savefile->WriteInt( bodyID ); + savefile->WriteFloat( restoreTime ); + savefile->WriteFloat( restoreFactor ); + savefile->WriteFloat( restoreForceSlack ); + savefile->WriteBool( bShuttle ); +} + +void hhForce_Converge::Restore( idRestoreGame *savefile ) { + ent.Restore(savefile); + axisEnt.Restore(savefile); + savefile->ReadVec3( target ); + savefile->ReadVec3( offset ); + savefile->ReadInt( bodyID ); + savefile->ReadFloat( restoreTime ); + savefile->ReadFloat( restoreFactor ); + savefile->ReadFloat( restoreForceSlack ); + savefile->ReadBool( bShuttle ); + + physics = NULL; // HUMANHEAD mdl: Updated each frame +} + +void hhForce_Converge::Evaluate( int time ) { + + // Get the physics of ent each frame, in case their physics object was changed. + physics = ent.IsValid() ? ent->GetPhysics() : NULL; + + // Apply convergent force + if (physics) { + idVec3 p = physics->GetOrigin( bodyID ) + offset * physics->GetAxis( bodyID ); + idVec3 x = p - target; + + if (axisEnt.IsValid()) { + idBounds bounds = ent->GetPhysics()->GetBounds(); + idVec3 rightPt = ent->GetOrigin() + (ent->GetAxis()[0] * bounds[0].x); + idVec3 axisRightPt = axisEnt->GetOrigin() + (axisEnt->GetAxis()[1] * bounds[1].x); + + idVec3 leftPt = ent->GetOrigin() + (ent->GetAxis()[0] * bounds[1].x); + idVec3 axisLeftPt = axisEnt->GetOrigin() + (axisEnt->GetAxis()[1] * bounds[0].x); + + //gameRenderWorld->DebugLine(idVec4(1, 0, 0, 1), rightPt, axisRightPt, 100); + + p = rightPt; + x = p - axisRightPt; + + x *= restoreForceSlack; //allow a linear buildup based on the distance of the target point + + idVec3 v = physics->GetLinearVelocity(); + float m = physics->GetMass(); + float b = idMath::Sqrt(4*restoreFactor*m); + idVec3 force = -restoreFactor*x - b*v; // use with addforce, k=200000 + physics->AddForce(bodyID, p, force * 0.5f ); // Great to show mass variations + + //gameRenderWorld->DebugLine(idVec4(1, 0, 0, 1), leftPt, axisLeftPt, 100); + + p = leftPt; + x = p - axisLeftPt; + + x *= restoreForceSlack; //allow a linear buildup based on the distance of the target point + + force = axisLeftPt - leftPt; + force *= m * 4; + physics->AddForce(bodyID, p, force ); // Great to show mass variations + return; + } + + x *= restoreForceSlack; //allow a linear buildup based on the distance of the target point + + if (ent->IsType(idAFEntity_Base::Type)) { // Actors use velocity method since their masses are unrealistic, ragdolls need it too + if (!ent->fl.tooHeavyForTractor) { + // Velocity method: cover distance in fixed time + idVec3 velocity = -x / restoreTime; // Use with SetLinearVelocity() + if (ent->IsType(idActor::Type)) { //if a player is affected by this force.. + //..do not allow him to build up velocity into his ground plane + if (ent->GetPhysics()->IsType(idPhysics_Player::Type) && ent->GetPhysics()->HasGroundContacts()) { + velocity.x *= -ent->GetPhysics()->GetGravityNormal().x; + velocity.y *= -ent->GetPhysics()->GetGravityNormal().y; + velocity.z *= -ent->GetPhysics()->GetGravityNormal().z; + } + } + physics->SetLinearVelocity(velocity, bodyID); + } + else { + // Entities that are too heavy get no force, just give feedback force + } + } else { + float m = physics->GetMass(); + if (bShuttle && m < 1500.0f) { // Different equation for picking up low mass objects with the shuttle + idVec3 v = physics->GetLinearVelocity(); + float springFactor = restoreFactor*0.0002f; + float dampFactor = idMath::Sqrt(springFactor); + idVec3 force = (-x * springFactor * m) - (v * dampFactor * m); + physics->AddForce(bodyID, p, force ); + } else { // Critically Damped spring: m*a = -k*x - b*v ; b = 2 * sqrt(m*k) + idVec3 v = physics->GetLinearVelocity(); + float m = physics->GetMass(); + float b = idMath::Sqrt(4.0f*restoreFactor*m); + idVec3 force = -restoreFactor*x - b*v; // use with addforce, k=200000 + physics->AddForce(bodyID, p, force ); // Great to show mass variations + } + } +/* else { // Impulse method + idVec3 v = physics->GetLinearVelocity(); + float b = idMath::Sqrt(4*restoreFactor); + impulse = -restoreFactor*x - b*v; // Use with applyimpulse, k=50000 + physics->ApplyImpulse(bodyID, p, impulse ); // Worked for all but some moveables (k=50000) + } + else { // Mass scaled force method, applies varying force to keep constant velocity + idVec3 v = physics->GetLinearVelocity(); + float m = physics->GetMass(); + float b = idMath::Sqrt(4*restoreFactor); + impulse = (-restoreFactor*x - b*v) * m; // use with addforce, k=500 + physics->AddForce(bodyID, p, impulse ); // Works for all but ragdolls (k=500) + } + }*/ + } +} + +void hhForce_Converge::RemovePhysics( const idPhysics *phys ) { + // physics of ent is stored each evaluate so we can compare pointers directly, rather than querying + // ent about it's physics. This is done because typically, physics objects are reported as removed + // during deconstruction of the entities. + if (ent.IsValid() && phys == physics) { + SetEntity(NULL); + } +} + +void hhForce_Converge::SetRestoreTime( float time ) { + restoreTime = idMath::ClampFloat(0.02f, time, time); +} + +void hhForce_Converge::SetTarget(idVec3 &newTarget) { + target = newTarget; +} + +void hhForce_Converge::SetEntity(idEntity *entity, int id, const idVec3 &point) { + ent = entity; + bodyID = id; + offset = point; + physics = NULL; +} + +void hhForce_Converge::SetAxisEntity(idEntity *entity) { + axisEnt = entity; +} + diff --git a/src/Prey/force_converge.h b/src/Prey/force_converge.h new file mode 100644 index 0000000..e35ecda --- /dev/null +++ b/src/Prey/force_converge.h @@ -0,0 +1,44 @@ +#ifndef __FORCE_CONVERGE_H__ +#define __FORCE_CONVERGE_H__ + +//=============================================================================== +// +// Convergent force +// +//=============================================================================== + +class hhForce_Converge : public idForce { + CLASS_PROTOTYPE( hhForce_Converge ); + +public: + hhForce_Converge( void ); + virtual ~hhForce_Converge( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void SetTarget(idVec3 &newTarget); + void SetEntity(idEntity *entity, int id=0, const idVec3 &point=vec3_origin); + idEntity * GetEntity() const { return ent.GetEntity(); } + void SetRestoreTime( float time ); + void SetRestoreFactor( float factor ){ restoreFactor = factor; } + void SetRestoreSlack(float slack) { restoreForceSlack = slack; } + +public: // common force interface + virtual void Evaluate( int time ); + virtual void RemovePhysics( const idPhysics *phys ); + void SetAxisEntity(idEntity *entity); + void SetShuttle(bool shuttle) { bShuttle = shuttle; } + +private: + idEntityPtr ent; // Entity to apply forces to + idEntityPtr axisEnt; + idPhysics * physics; // physics object of entity during last evaluate + idVec3 target; // Point of convergence + idVec3 offset; // Entity local offset for force application + int bodyID; // Body ID for force application + float restoreTime; // Ideal convergence time + float restoreFactor; // Spring constant for restorative force + float restoreForceSlack; //rww - allow a linear buildup based on the distance of the target point + bool bShuttle; //mdl: Use an alternate spring equation for shuttle tractor beam +}; + +#endif diff --git a/src/Prey/game_afs.cpp b/src/Prey/game_afs.cpp new file mode 100644 index 0000000..df8ce09 --- /dev/null +++ b/src/Prey/game_afs.cpp @@ -0,0 +1,100 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +/*********************************************************************** + + hhAFEntity + +***********************************************************************/ + +// nla - Added to allow the mappers to do huge translations w/out too much bouncing +const idEventDef EV_EnableRagdoll( "enableRagdoll", "" ); +const idEventDef EV_DisableRagdoll( "disableRagdoll", "" ); + +CLASS_DECLARATION( idAFEntity_Generic, hhAFEntity ) + EVENT( EV_EnableRagdoll, hhAFEntity::Event_EnableRagdoll ) + EVENT( EV_DisableRagdoll, hhAFEntity::Event_DisableRagdoll ) +END_CLASS + +void hhAFEntity::Spawn( void ) { + fl.takedamage = !spawnArgs.GetBool("noDamage"); + if (spawnArgs.FindKey("gravity")) { + PostEventMS(&EV_ResetGravity, 100); // Post after first think, when gravity is reset by UpdateGravity() + } +} + +void hhAFEntity::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + if ( CheckRagdollDamage( inflictor, attacker, dir, damageDefName, location ) ) { + return; + } + idAFEntity_Generic::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} + +//This is so ragdolls shutdown when attached to movers that are outside of the pvs. +void hhAFEntity::DormantBegin() { + idEntity::DormantBegin(); + Event_DisableRagdoll(); +} + +void hhAFEntity::DormantEnd() { + idEntity::DormantEnd(); + Event_EnableRagdoll(); +} + +void hhAFEntity::Event_EnableRagdoll() { + af.GetPhysics()->Thaw(); +} + +void hhAFEntity::Event_DisableRagdoll() { + af.GetPhysics()->Freeze(); +} + + + + +/*********************************************************************** + + hhAFEntity_WithAttachedHead + +***********************************************************************/ + +CLASS_DECLARATION( idAFEntity_WithAttachedHead, hhAFEntity_WithAttachedHead ) + EVENT( EV_EnableRagdoll, hhAFEntity_WithAttachedHead::Event_EnableRagdoll ) + EVENT( EV_DisableRagdoll, hhAFEntity_WithAttachedHead::Event_DisableRagdoll ) +END_CLASS + +void hhAFEntity_WithAttachedHead::Spawn( void ) { + fl.takedamage = !spawnArgs.GetBool("noDamage"); + if (spawnArgs.FindKey("gravity")) { + PostEventMS(&EV_ResetGravity, 100); // Post after first think, when gravity is reset by UpdateGravity() + } +} + +void hhAFEntity_WithAttachedHead::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + if ( CheckRagdollDamage( inflictor, attacker, dir, damageDefName, location ) ) { + return; + } + idAFEntity_WithAttachedHead::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} + +//This is so ragdolls shutdown when attached to movers that are outside of the pvs. +void hhAFEntity_WithAttachedHead::DormantBegin() { + idEntity::DormantBegin(); + Event_DisableRagdoll(); +} + +void hhAFEntity_WithAttachedHead::DormantEnd() { + idEntity::DormantEnd(); + Event_EnableRagdoll(); +} + +void hhAFEntity_WithAttachedHead::Event_EnableRagdoll() { + af.GetPhysics()->Thaw(); +} + +void hhAFEntity_WithAttachedHead::Event_DisableRagdoll() { + af.GetPhysics()->Freeze(); +} diff --git a/src/Prey/game_afs.h b/src/Prey/game_afs.h new file mode 100644 index 0000000..113d499 --- /dev/null +++ b/src/Prey/game_afs.h @@ -0,0 +1,49 @@ +#ifndef __GAME_AFS_H__ +#define __GAME_AFS_H__ + +extern const idEventDef EV_CursorDrop; +extern const idEventDef EV_CursorDone; + +/*********************************************************************** + +hhAFEntity + +***********************************************************************/ + +class hhAFEntity : public idAFEntity_Generic { + +public: + CLASS_PROTOTYPE( hhAFEntity ); + + void Spawn(); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void DormantBegin(); + virtual void DormantEnd(); + + void Event_EnableRagdoll( void ); + void Event_DisableRagdoll( void ); +}; + + +/*********************************************************************** + +hhAFEntity_WithAttachedHead + +***********************************************************************/ + +class hhAFEntity_WithAttachedHead : public idAFEntity_WithAttachedHead { + +public: + CLASS_PROTOTYPE( hhAFEntity_WithAttachedHead ); + + void Spawn(); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void DormantBegin(); + virtual void DormantEnd(); + + void Event_EnableRagdoll(); + void Event_DisableRagdoll(); +}; + + +#endif // __GAME_AFS_H__ diff --git a/src/Prey/game_alarm.cpp b/src/Prey/game_alarm.cpp new file mode 100644 index 0000000..bf0d609 --- /dev/null +++ b/src/Prey/game_alarm.cpp @@ -0,0 +1,76 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +//========================================================================== +// +// hhAlarmLight +// +//========================================================================== +CLASS_DECLARATION(idLight, hhAlarmLight) + EVENT(EV_Activate, hhAlarmLight::Event_Activate) +END_CLASS + +void hhAlarmLight::Spawn() { + bAlarmOn = false; + fl.takedamage = false; // Never take damage + + /* Shader parms set up like this (so the editor shows the light being on): + parm6 parm7 + editor(on): 0 0 + spawn(off): 1 0 + trigger(on): 1 1 + broken(off): 1 0 + */ + SetParmState(1.0f, 0.0f); + + // setup the clipModel + GetPhysics()->SetContents( CONTENTS_SOLID ); +} + +void hhAlarmLight::Save(idSaveGame *savefile) const { + savefile->WriteBool( bAlarmOn ); +} + +void hhAlarmLight::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( bAlarmOn ); +} + +void hhAlarmLight::TurnOn() { + if (!bAlarmOn) { + bAlarmOn = true; + + StartSound("snd_alarm", SND_CHANNEL_BODY2, 0, true, NULL); + + SetParmState(1.0f, 1.0f); + } +} + +void hhAlarmLight::TurnOff() { + if (bAlarmOn) { + bAlarmOn = false; + StopSound(SND_CHANNEL_BODY2, true); + + SetParmState(0.0f, 1.0f); + } +} + +void hhAlarmLight::SetParmState(float value6, float value7) { + SetShaderParm(6, value6); + SetShaderParm(7, value7); + SetLightParm(6, value6); + SetLightParm(7, value7); +} + +void hhAlarmLight::Event_Activate(idEntity *activator) { + if (bAlarmOn) { + if (activator && activator->IsType(hhPlayer::Type)) { + TurnOff(); + } + } + else { + TurnOn(); + } +} diff --git a/src/Prey/game_alarm.h b/src/Prey/game_alarm.h new file mode 100644 index 0000000..84ef812 --- /dev/null +++ b/src/Prey/game_alarm.h @@ -0,0 +1,21 @@ +#ifndef __GAME_ALARMLIGHT_H__ +#define __GAME_ALARMLIGHT_H__ + +class hhAlarmLight : public idLight { + CLASS_PROTOTYPE( hhAlarmLight ); + +public: + void Spawn(); + void TurnOn(); + void TurnOff(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +protected: + void SetParmState(float value6, float value7); + void Event_Activate(idEntity *activator); + + bool bAlarmOn; +}; + +#endif diff --git a/src/Prey/game_anim.cpp b/src/Prey/game_anim.cpp new file mode 100644 index 0000000..4986df0 --- /dev/null +++ b/src/Prey/game_anim.cpp @@ -0,0 +1,244 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +/* +============ +hhAnim::AddFrameCommandExtra +============ +*/ +bool hhAnim::AddFrameCommandExtra( idToken &token, frameCommand_t &fc, idLexer &src, idStr &errorText ) { + errorText = ""; + + + if ( token == "stopSnd" ) { + fc.type = FC_STOPSND; + return( true ); + } else if ( token == "stopSnd_voice" ) { + fc.type = FC_STOPSND_VOICE; + return( true ); + } else if ( token == "stopSnd_voice2" ) { + fc.type = FC_STOPSND_VOICE2; + return( true ); + } else if ( token == "stopSnd_body" ) { + fc.type = FC_STOPSND_BODY; + return( true ); + } else if ( token == "stopSnd_body2" ) { + fc.type = FC_STOPSND_BODY2; + return( true ); + } else if ( token == "stopSnd_body3" ) { + fc.type = FC_STOPSND_BODY3; + return( true ); + } else if ( token == "stopSnd_weapon" ) { + fc.type = FC_STOPSND_WEAPON; + return( true ); + } else if ( token == "stopSnd_item" ) { + fc.type = FC_STOPSND_ITEM; + return( true ); + } else if ( token == "event_args" ) { + fc.type = FC_EVENT_ARGS; + src.ParseRestOfLine( token ); + idStr error = InitFrameCommandEvent( fc, token ); + if( error.Length() ) { + errorText = error.c_str(); + return( false ); + } + + return( true ); + } else if ( token == "launch_missile" ) { + /* HUMANHEAD JRM - Because we parse out the joint when launched + if ( !modelDef->FindJoint( cmd ) ) { + return va( "Joint '%s' not found", cmd.c_str() ); + } + */ + src.ParseRestOfLine( token ); + /* HUMANHEAD JRM - need the rest of the line not just one token + if( !src.ReadTokenOnLine( &token ) ) { + errorText = va( "Unexpected end of line" ); + return( true ); + } + */ + fc.type = FC_LAUNCHMISSILE; + fc.string = new idStr( token ); + + return( true ); + } else if( token == "launch_altMissile" ) { + if( !src.ReadTokenOnLine( &token ) ) { + errorText = va( "Unexpected end of line" ); + return( true ); + } + fc.type = FC_LAUNCHALTMISSILE; + fc.string = new idStr( token ); + + return( true ); + } else if ( token == "launch_missile_bonedir" ) { + /* HUMANHEAD JRM - we want the whole token + if( !src.ReadTokenOnLine( &token ) ) { + errorText = va( "Unexpected end of line" ); + return( true ); + } + */ + src.ParseRestOfLine( token ); + fc.type = FC_LAUNCHMISSILE_BONEDIR; + fc.string = new idStr( token ); + + return( true ); + } else if ( token == "leftfootprint" ) { + fc.type = FC_LEFTFOOTPRINT; + + return( true ); + } else if ( token == "rightfootprint" ) { + fc.type = FC_RIGHTFOOTPRINT; + + return( true ); + } else if ( token == "mood" ) { + fc.type = FC_MOOD; + if( !src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( "" ); + return( true ); + } + fc.string = new idStr( token ); + + return( true ); + } else if ( token == "kick_obstacle" ) { + if( !src.ReadTokenOnLine( &token ) ) { + errorText = va( "Unexpected end of line" ); + return( true ); + } + fc.type = FC_KICK_OBSTACLE; + fc.string = new idStr( token ); + + return( true ); + } else if ( token == "trigger_anim_ent" ) { + /* + if( !src.ReadTokenOnLine( &token ) ) { + errorText = va( "Unexpected end of line" ); + return( true ); + } + */ + fc.type = FC_TRIGGER_ANIM_ENT; + return( true ); + } else if ( token == "set_key" ) { + fc.type = FC_SETKEY; + if( !src.ParseRestOfLine( token ) ) { + errorText = va( "Unexpected end of line" ); + return ( true ); + } + if( !fc.parmList ) { + fc.parmList = new idList; + } + hhUtils::SplitString( token, *fc.parmList, ' ' ); + if( fc.parmList->Num() != 2 ) { + errorText = va( "Invalid number of arguments for setkey frame-command" ); + return ( true ); + } + return ( true ); + } + + + return( false ); +} + +/* +============ +hhAnim::CallFrameCommandsExtra +============ +*/ +bool hhAnim::CallFrameCommandsExtra( const frameCommand_t &command, idEntity *ent ) const { + switch( command.type ) { + case FC_EVENT_ARGS: { + if ( command.function && command.parmList && command.function->eventdef ) { + ent->ProcessEvent( command.function->eventdef, (int)command.parmList ); + } + return( true ); + } + case FC_LAUNCHALTMISSILE: { + //ent->ProcessEvent( &AI_AttackAltMissile, command.string->c_str(), NULL ); + return( true ); + } + case FC_STOPSND: + case FC_STOPSND_VOICE: + case FC_STOPSND_VOICE2: + case FC_STOPSND_BODY: + case FC_STOPSND_BODY2: + case FC_STOPSND_BODY3: + case FC_STOPSND_WEAPON: + case FC_STOPSND_ITEM: { + ent->StopSound( s_channelType(command.type - FC_STOPSND), false ); //rww - do not broadcast + return( true ); + } + case FC_HIDE: { + ent->ProcessEvent( &EV_Hide ); + return( true ); + } + case FC_MOOD: { + if(command.string) { + ent->ProcessEvent( &AI_SetAnimPrefix, command.string->c_str()); + } else { + ent->ProcessEvent( &AI_SetAnimPrefix, " "); + } + return( true ); + } + case FC_LEFTFOOTPRINT: { + ent->ProcessEvent( &EV_FootprintLeft ); + return( true ); + } + case FC_RIGHTFOOTPRINT: { + ent->ProcessEvent( &EV_FootprintRight ); + return( true ); + } + case FC_LAUNCHMISSILE: { // JRM: changed to AttackMissileEx (so we can pass in projectile) + ent->ProcessEvent( &MA_AttackMissileEx, command.string->c_str(),0 ); + return( true ); + } + case FC_LAUNCHMISSILE_BONEDIR: { // JRM: need so we can launch missiles + ent->ProcessEvent( &MA_AttackMissileEx, command.string->c_str(),1 ); + return( true ); + } + case FC_SETKEY: {//mdc: added to be able to set keys from frame commands + if( command.parmList && command.parmList->Num() == 2 ) { + ent->spawnArgs.Set( (*command.parmList)[0].c_str(), (*command.parmList)[1].c_str() ); + } + return ( true ); + } + } + + return( false ); + +} + + +/* +===================== +hhAnim::InitFrameCommandEvent +===================== +*/ +idStr hhAnim::InitFrameCommandEvent( frameCommand_t &command, const idStr& cmdStr ) const { + idStr error; + + if( !command.parmList ) { + command.parmList = new idList; + } + + hhUtils::SplitString( cmdStr, *command.parmList, ' ' ); + + // Find the function + command.function = gameLocal.program.FindFunction( (*command.parmList)[0].c_str(), gameLocal.program.FindType((*command.parmList)[0].c_str()) ); + if( !command.function || !command.function->eventdef || command.function->eventdef->GetNumArgs() != 1 ) { + error = va("Invalid event('%s') for frameCommands event or event_args\n", (*command.parmList)[0].c_str() ); + } + + //Remove function name from the list + command.parmList->RemoveIndex( 0 ); + + //If no parms are needed, no need to waste memory + if( command.parmList->Num() <= 0 ) { + SAFE_DELETE_PTR( command.parmList ); + } + + return error; +} + diff --git a/src/Prey/game_anim.h b/src/Prey/game_anim.h new file mode 100644 index 0000000..b7d27af --- /dev/null +++ b/src/Prey/game_anim.h @@ -0,0 +1,23 @@ + +#ifndef __PREY_GAME_ANIM_H__ +#define __PREY_GAME_ANIM_H__ + +class hhAnim : public idAnim { + +public: + + hhAnim() : idAnim() { exactMatch = false; } + hhAnim( const idDeclModelDef *modelDef, const idAnim *anim ) : idAnim( modelDef, anim ) { } + + virtual bool AddFrameCommandExtra( idToken &token, frameCommand_t &fc, idLexer &src, idStr &errorText ); + virtual bool CallFrameCommandsExtra( const frameCommand_t &command, idEntity *ent ) const; + + bool exactMatch; + +protected: + virtual idStr InitFrameCommandEvent( frameCommand_t &command, const idStr& cmdStr ) const; + +}; + + +#endif /* __PREY_GAME_ANIM_H__ */ diff --git a/src/Prey/game_animBlend.cpp b/src/Prey/game_animBlend.cpp new file mode 100644 index 0000000..28eacce --- /dev/null +++ b/src/Prey/game_animBlend.cpp @@ -0,0 +1,276 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +/* +============ +hhAnimBlend::hhAnimBlend +============ +*/ +hhAnimBlend::hhAnimBlend() { + + frozen = false; + freezeStart = -1; + freezeEnd = -1; + freezeCurrent = -1; + rotateTime = -1; + rotateEvent = NULL; + +} + + +/* +===================== +hhAnimBlend::FrameHasChanged +===================== +*/ +bool hhAnimBlend::FrameHasChanged( int currentTime ) const { + + + UpdateFreezeTime( currentTime ); + + if ( frozen ) { + return false; + } + + return( idAnimBlend::FrameHasChanged( currentTime ) ); + +} + + +/* +===================== +hhAnimBlend::AnimTime +===================== +*/ +int hhAnimBlend::AnimTime( int currentTime ) const { + + + if ( animNum ) { + UpdateFreezeTime( currentTime ); + } + + return( idAnimBlend::AnimTime( currentTime ) ); + +} + + +/* +===================== +hhAnimBlend::GetFrameNumber +===================== +*/ +int hhAnimBlend::GetFrameNumber( int currentTime ) const { + + //gameLocal.Printf( "In FRAME NUM\n" ); + + UpdateFreezeTime( currentTime ); + + return( idAnimBlend::GetFrameNumber( currentTime ) ); + +} + + +/* +===================== +hhAnimBlend::CallFrameCommands +===================== +*/ +void hhAnimBlend::CallFrameCommands( idEntity *ent, int fromtime, int totime ) const { + + + if ( frozen ) { + return; + } + + idAnimBlend::CallFrameCommands( ent, fromtime, totime ); + +} + +/* +===================== +hhAnimBlend::BlendAnim +===================== +*/ +bool hhAnimBlend::BlendAnim( int currentTime, int channel, int numJoints, idJointQuat *blendFrame, float &blendWeight, bool removeOriginOffset, bool overrideBlend, bool printInfo ) const { + + + UpdateFreezeTime( currentTime ); + + return( idAnimBlend::BlendAnim( currentTime, channel, numJoints, blendFrame, blendWeight, removeOriginOffset, overrideBlend, printInfo ) ); + +} + + +/* +===================== +hhAnimBlend::BlendOrigin +===================== +*/ +void hhAnimBlend::BlendOrigin( int currentTime, idVec3 &blendPos, float &blendWeight, bool removeOriginOffset ) const { + + //gameLocal.Printf( "BLEND ORIGIN\n" ); + + UpdateFreezeTime( currentTime ); + + if ( frozen ) { + return; + } + + idAnimBlend::BlendOrigin( currentTime, blendPos, blendWeight, removeOriginOffset ); + +} + + +/* +===================== +hhAnimBlend::BlendDelta +===================== +*/ +void hhAnimBlend::BlendDelta( int fromtime, int totime, idVec3 &blendDelta, float &blendWeight ) const { + + + if ( frozen ) { + return; + } + + idAnimBlend::BlendDelta( fromtime, totime, blendDelta, blendWeight ); + +} + + +/* +===================== +hhAnimBlend::AddBounds +===================== +*/ +bool hhAnimBlend::AddBounds( int currentTime, idBounds &bounds, bool removeOriginOffset ) const { + + + UpdateFreezeTime( currentTime ); + + if ( frozen ) { + return false; + } + + return( idAnimBlend::AddBounds( currentTime, bounds, removeOriginOffset ) ); + +} + + +/* +=============== +hhAnimBlend::UpdateFreezeTime +=============== +*/ +void hhAnimBlend::UpdateFreezeTime( int currentTime ) const { + + if ( !frozen ) { + return; + } + + if ( currentTime > freezeCurrent ) { + starttime += currentTime - freezeCurrent; + endtime += currentTime - freezeCurrent; + freezeCurrent = currentTime; + } + +} + +extern const idEventDef EV_CheckThaw; + +/* +================ +hhAnimBlend::Freeze + Freeze the animation + Inputs: currentTime - The current time of freeze + freezeEnd - Delta time to end the freeze. Set to -1 if will thaw manually + Outputs: true if frozen successfully, false otherwise +HUMANHEAD nla +================ +*/ +bool hhAnimBlend::Freeze( int currentTime, idEntity *owner, int aFreezeEnd ) { + + if ( frozen ) { + return( false ); + } + + frozen = true; + freezeStart = currentTime; + freezeCurrent = currentTime; + freezeEnd = currentTime + aFreezeEnd; + + // Post the thaw event if time is known + if ( owner ) { + owner->PostEventMS( &EV_CheckThaw, aFreezeEnd ); + } + else { + gameLocal.Warning( "Freeze called on an animator with no owner!" ); + } + + + return( true ); +} + + +/* +=============== +hhAnimBlend::Freeze +HUMANHEAD nla +=============== +*/ +bool hhAnimBlend::Freeze( int currentTime, idEntity *owner ) { + + return( Freeze( currentTime, owner, -1 ) ); +} + + +/* +=============== +hhAnimBlend::Thaw +HUMANHEAD nla +=============== +*/ +bool hhAnimBlend::Thaw( int currentTime ) { + + if ( !frozen ) { + return( false ); + } + + UpdateFreezeTime( currentTime ); + frozen = false; + + + return( true ); +} + + +/* +=============== +hhAnimBlend::ThawIfTime +HUMANHEAD nla +=============== +*/ +bool hhAnimBlend::ThawIfTime( int currentTime ) { + + if ( !frozen ) { + return( false ); + } + + if ( freezeEnd < 0 ) { + return( false ); + } + + if ( currentTime >= freezeEnd ) { + return( Thaw( currentTime ) ); + } + + return( false ); +} + + + + diff --git a/src/Prey/game_animBlend.h b/src/Prey/game_animBlend.h new file mode 100644 index 0000000..caf33b4 --- /dev/null +++ b/src/Prey/game_animBlend.h @@ -0,0 +1,6 @@ + +#ifndef __PREY_GAME_ANIMBLEND_H__ +#define __PREY_GAME_ANIMBLEND_H__ + + +#endif /* __PREY_GAME_ANIMBLEND_H__ */ diff --git a/src/Prey/game_animDriven.cpp b/src/Prey/game_animDriven.cpp new file mode 100644 index 0000000..3864f0b --- /dev/null +++ b/src/Prey/game_animDriven.cpp @@ -0,0 +1,287 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_PlayCycle( "", "ds" ); + +CLASS_DECLARATION( hhAnimatedEntity, hhAnimDriven ) + EVENT( EV_PlayCycle, hhAnimDriven::Event_PlayCycle ) +END_CLASS + + +//============== +// hhAnimDriven::Spawn +//============== +void hhAnimDriven::Spawn() { + + passenger = NULL; + hadPassenger = false; + spawnTime = gameLocal.time; + + // We are gonna move the guy manually, turn off having the anim move him + GetAnimator()->RemoveOriginOffset( true ); + + // How do we deal with our owner? + physicsAnim.SetSelf( this ); + + if( spawnArgs.GetBool( "solid", "0" ) ) { + fl.takedamage = true; + physicsAnim.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + GetPhysics()->SetContents( CONTENTS_SOLID ); + } else { + fl.takedamage = false; + GetPhysics()->SetContents( 0 ); + physicsAnim.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + GetPhysics()->UnlinkClip(); + } + + + // move up to make sure the monster is at least an epsilon above the floor + physicsAnim.SetOrigin( GetOrigin() + idVec3( 0, 0, CM_CLIP_EPSILON ) ); + + SetPhysics( &physicsAnim ); + + //? Have this start at a set time afterwards? + int animLength = PlayAnim( ANIMCHANNEL_ALL, "initial" ); + PostEventMS( &EV_PlayCycle, animLength, ANIMCHANNEL_ALL, "move" ); + + float delta_min = spawnArgs.GetFloat( "delta_scale_min", "1.0" ); + float delta_max = spawnArgs.GetFloat( "delta_scale_max", "1.0" ); + if ( delta_min != 1.0f || delta_max != 1.0f ) { + deltaScale.x = idMath::ClampFloat( delta_min, delta_max, gameLocal.random.RandomFloat() ); + deltaScale.y = idMath::ClampFloat( delta_min, delta_max, gameLocal.random.RandomFloat() ); + deltaScale.z = idMath::ClampFloat( delta_min, delta_max, gameLocal.random.RandomFloat() ); + } else { + deltaScale = idVec3( 1.0f, 1.0f, 1.0f ); + } + + BecomeActive( TH_TICKER ); +} + +void hhAnimDriven::Save(idSaveGame *savefile) const { + savefile->WriteStaticObject( physicsAnim ); + passenger.Save(savefile); + savefile->WriteInt( spawnTime ); + savefile->WriteBool( hadPassenger ); +} + +void hhAnimDriven::Restore( idRestoreGame *savefile ) { + savefile->ReadStaticObject( physicsAnim ); + passenger.Restore(savefile); + savefile->ReadInt( spawnTime ); + savefile->ReadBool( hadPassenger ); +} + + +//============= +// hhAnimDriven::SetPassenger +//============= +void hhAnimDriven::SetPassenger( idEntity *newPassenger, bool orientFromPassenger ) { + idVec3 origin; + idMat3 axis; + + //?! Right now handle only 1 passenger. If we have one, warn and exit. + if ( passenger.IsValid() ) { + const char *oldName = passenger->GetName(); + const char *newName = "NULL"; + + if ( newPassenger ) { + newName = newPassenger->GetName(); + } + gameLocal.Warning( "Error: hhAnimDriven::SetPassenger tried to add passenger %s but already had %s\n", + newName, oldName ); + return; + } + + passenger = newPassenger; + hadPassenger = true; + fl.takedamage = true; + + if ( orientFromPassenger ) { + origin = passenger->GetOrigin(); + axis = passenger->GetAxis(); + } + + GetPhysics()->SetClipModel( new idClipModel( passenger->GetPhysics()->GetClipModel() ), 1.0f ); + GetPhysics()->SetContents( passenger->GetPhysics()->GetContents() ); + GetPhysics()->SetClipMask( passenger->GetPhysics()->GetClipMask() ); + + if ( orientFromPassenger ) { + SetOrigin( origin ); + SetAxis( axis ); + } + + passenger->Bind( this, true ); + +} + + +//============== +// hhAnimDriven::Think +//============== +void hhAnimDriven::Think() { + idVec3 delta; + + // Done move unless we have a passenger. (Give us like 10 secs to get one) + if ( !passenger.IsValid() ) { + // If we had a passenger, they removed themselves, and so should we :) + if ( hadPassenger ) { + PostEventMS( &EV_Remove, 0 ); + } + // If never had a passenger for a second, remove ourselves + else if ( gameLocal.time - spawnTime > 1000 ) { + PostEventMS( &EV_Remove, 0 ); + gameLocal.Warning( "hhAnimDriven %s existed for a second w/out a passenger. Removing.", GetName() ); + } + return; + } + + // Move them based on their anim + GetAnimator()->GetDelta( gameLocal.time - gameLocal.msec, gameLocal.time, delta ); + delta *= GetAxis(); + delta.x *= deltaScale.x; + delta.y *= deltaScale.y; + delta.z *= deltaScale.z; + + physicsAnim.SetDelta( delta ); + + // gameLocal.Printf( "%d Doing %s\n", gameLocal.GetTime(), delta.ToString() ); + + hhAnimatedEntity::Think(); + +} + + +//============ +// +//============ +int hhAnimDriven::PlayAnim( int channel, const char *animName ) { + int animIndex; + int length; + + + animIndex = GetAnimator()->GetAnim( animName ); + if ( !animIndex ) { + return( 0 ); + } + + //gameLocal.Printf( "Playing Anim %s\n", animName ); + + GetAnimator()->PlayAnim( channel, animIndex, gameLocal.GetTime(), 0 ); + + length = GetAnimator()->CurrentAnim( channel )->GetEndTime() - gameLocal.GetTime(); + + return( length ); + +} + + +//============ +// hhAnimDrive::Event_PlayCycle() +//============ +void hhAnimDriven::Event_PlayCycle( int channel, const char *animName ) { + + PlayCycle( channel, animName ); + +} + + +//============ +// +//============ +bool hhAnimDriven::PlayCycle( int channel, const char *animName ) { + int animIndex; + + animIndex = GetAnimator()->GetAnim( animName ); + if ( !animIndex ) { + return( false ); + } + + //gameLocal.Printf( "Cycling Anim %s\n", animName ); + + GetAnimator()->CycleAnim( channel, animIndex, gameLocal.GetTime(), 0 ); + + return( true ); +} + + +/* +================ +hhAnimDriven::ClearAllAnims +================ +*/ +void hhAnimDriven::ClearAllAnims() { + + GetAnimator()->ClearAllAnims( gameLocal.GetTime(), 0 ); + +} + + +//=========== +// +//============ +bool hhAnimDriven::Collide( const trace_t &collision, const idVec3 &velocity ) { + // If we hit something, and have a passenger, pass along the message + if( passenger.IsValid() ) { + if( !passenger->Collide(collision, velocity) ) { + return( false ); + } + + passenger->Unbind(); + } //. We have a valid passenger + + + // If we made it this far, assume the collision is for real, and + // remove ourselves from interacting with the world + fl.takedamage = false; + GetPhysics()->SetContents( 0 ); + GetPhysics()->UnlinkClip(); + ClearAllAnims(); + PostEventMS( &EV_Remove, 0 ); + + return( true ); +} + +bool hhAnimDriven::AllowCollision( const trace_t& collision ) { + if ( !spawnArgs.GetBool( "projectile_collision", "1" ) ) { + idEntity* ent = gameLocal.entities[ collision.c.entityNum ]; + if ( !ent || ent->IsType( idProjectile::Type ) ) { + return false; + } + } + + return true; +} + +void hhAnimDriven::UpdateAnimation( void ) { + PROFILE_SCOPE("Animation", PROFMASK_NORMAL); // HUMANHEAD pdm + + // 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; + } + + // call any frame commands that have happened in the past frame + if ( !fl.hidden ) { + 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; + } + + // update the renderEntity + UpdateVisuals(); + + // the animation is updated + animator.ClearForceUpdate(); +} diff --git a/src/Prey/game_animDriven.h b/src/Prey/game_animDriven.h new file mode 100644 index 0000000..cf47d32 --- /dev/null +++ b/src/Prey/game_animDriven.h @@ -0,0 +1,40 @@ +#ifndef __PREY_GAME_ANIMDRIVEN_H__ +#define __PREY_GAME_ANIMDRIVEN_H__ + +// Class name by Jimmy! :) +class hhAnimDriven : public hhAnimatedEntity { + public: + CLASS_PROTOTYPE( hhAnimDriven ); + + + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think(); + + void SetPassenger( idEntity *newPassenger, bool orientFromPassenger = true ); + + + // Events + void Event_PlayCycle( int channel, const char *animName ); + + + bool Collide( const trace_t &collision, const idVec3 &velocity ); + int PlayAnim( int channel, const char *animName ); + bool PlayCycle( int channel, const char *animName ); + void ClearAllAnims(); + void UpdateAnimation(); + bool AllowCollision( const trace_t& collision ); + + protected: + hhPhysics_Delta physicsAnim; + + idEntityPtr passenger; + + int spawnTime; + bool hadPassenger; + idVec3 deltaScale; //scale anim delta movement by this +}; + +#endif /* __PREY_GAME_ANIMDRIVEN_H__ */ diff --git a/src/Prey/game_animatedentity.cpp b/src/Prey/game_animatedentity.cpp new file mode 100644 index 0000000..6825f3e --- /dev/null +++ b/src/Prey/game_animatedentity.cpp @@ -0,0 +1,424 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_CheckCycleRotate( "" ); +const idEventDef EV_CheckThaw( "" ); +const idEventDef EV_SpawnFxAlongBone( "spawnFXAlongBone", "d" ); +const idEventDef EV_Silent( "", NULL ); + +CLASS_DECLARATION( idAnimatedEntity, hhAnimatedEntity ) + EVENT( EV_CheckCycleRotate, hhAnimatedEntity::Event_CheckAnimatorCycleRotate ) + EVENT( EV_CheckThaw, hhAnimatedEntity::Event_CheckAnimatorThaw ) + EVENT( EV_SpawnFxAlongBone, hhAnimatedEntity::Event_SpawnFXAlongBone ) + EVENT( EV_Thread_SetSilenceCallback,hhAnimatedEntity::Event_SetSilenceCallback ) + EVENT( EV_Silent, hhAnimatedEntity::Event_Silent ) +END_CLASS + +/* +============== +hhAnimatedEntity::Spawn +============== +*/ +void hhAnimatedEntity::Spawn( void ) { + hasFlapInfo = false; + + // nla - Added to allow for 1 frame anims to play/proper bone initialization + if ( GetAnimator() ) { + int anim = GetAnimator()->GetAnim("init"); + if ( anim ) { + GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, 0); + GetAnimator()->ForceUpdate(); + UpdateModel(); + } + } +} + +void hhAnimatedEntity::Save(idSaveGame *savefile) const { + savefile->WriteInt( waitingThread ); + savefile->WriteInt( silentTimeOffset ); + savefile->WriteInt( nextSilentTime ); + savefile->WriteFloat( lastAmplitude ); +} + +void hhAnimatedEntity::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( waitingThread ); + savefile->ReadInt( silentTimeOffset ); + savefile->ReadInt( nextSilentTime ); + savefile->ReadFloat( lastAmplitude ); + hasFlapInfo = false; +} + +/* +============== +hhAnimatedEntity::hhAnimatedEntity +============== +*/ +hhAnimatedEntity::hhAnimatedEntity() { + // WaitForSilence support + waitingThread = 0; + silentTimeOffset = 0; + nextSilentTime = 0; + lastAmplitude = 0.0f; + hasFlapInfo = false; +} + +/* +============== +hhAnimatedEntity::~hhAnimatedEntity +============== +*/ +hhAnimatedEntity::~hhAnimatedEntity() { + Event_Silent(); +} + +/* +============== +hhAnimatedEntity::FillDebugVars +============== +*/ +void hhAnimatedEntity::FillDebugVars(idDict *args, int page) { + switch(page) { + case 1: + args->SetInt("anims", GetAnimator()->NumAnims()); + break; + } + idAnimatedEntity::FillDebugVars(args, page); +} + +/* +============== +hhAnimatedEntity::Think +============== +*/ +void hhAnimatedEntity::Think() { + idAnimatedEntity::Think(); + + //HUMANHEAD: aob - moved from idActor + UpdateWounds(); + //HUMANHEAD END +} + +/* +===================== +hhAnimatedEntity::GetAnimator +===================== +*/ +hhAnimator *hhAnimatedEntity::GetAnimator( void ) { + return &animator; +} + +/* +===================== +hhAnimatedEntity::GetAnimator +===================== +*/ +const hhAnimator *hhAnimatedEntity::GetAnimator( void ) const { + return &animator; +} + +/* +===================== +hhAnimatedEntity::GetJointWorldTransform +===================== +*/ +bool hhAnimatedEntity::GetJointWorldTransform( jointHandle_t jointHandle, int currentTime, idVec3 &offset, idMat3 &axis ) { + return idAnimatedEntity::GetJointWorldTransform( jointHandle, currentTime, offset, axis ); +} + +/* +===================== +hhAnimatedEntity::GetJointWorldTransform +===================== +*/ +bool hhAnimatedEntity::GetJointWorldTransform( const char* jointName, idVec3 &offset, idMat3 &axis ) { + return GetJointWorldTransform( GetAnimator()->GetJointHandle(jointName), gameLocal.GetTime(), offset, axis ); +} + +/* +===================== +hhAnimatedEntity::GetJointWorldTransform +===================== +*/ +bool hhAnimatedEntity::GetJointWorldTransform( jointHandle_t jointHandle, idVec3 &offset, idMat3 &axis ) { + return GetJointWorldTransform( jointHandle, gameLocal.GetTime(), offset, axis ); +} + +/* +============== +hhAnimatedEntity::UpdateWounds +============== +*/ +void hhAnimatedEntity::UpdateWounds( void ) { +} + +/* +================== +idEntity::SpawnFXAlongBonePrefixLocal +================== +*/ +void hhAnimatedEntity::SpawnFxAlongBonePrefixLocal( const idDict* dict, const char* fxKeyPrefix, const char* bonePrefix, const hhFxInfo* const fxInfo, const idEventDef* eventDef ) { + idVec3 bonePos; + idMat3 boneAxis; + + for( const idKeyValue* kv = dict->MatchPrefix(bonePrefix); kv; kv = dict->MatchPrefix(bonePrefix, kv) ) { + if( !kv->GetValue().Length() ) { + continue; + } + + GetJointWorldTransform( kv->GetValue().c_str(), bonePos, boneAxis ); + SpawnFXPrefixLocal( dict, fxKeyPrefix, bonePos, boneAxis, fxInfo, eventDef ); + } +} + +/* +================= +hhAnimatedEntity::SpawnFXAlongBone +================= +*/ +void hhAnimatedEntity::BroadcastFxInfoAlongBonePrefix( const idDict* args, const char* fxKey, const char* bonePrefix, const hhFxInfo* const fxInfo, const idEventDef* eventDef, bool broadcast ) { + if( !fxKey || !fxKey[0] || !bonePrefix || !bonePrefix[0] ) { + return; + } + + idVec3 bonePos; + idMat3 boneAxis; + hhFxInfo localFxInfo( fxInfo ); + + localFxInfo.SetEntity( this ); + localFxInfo.RemoveWhenDone( true ); + + const idKeyValue* keyValue = args->MatchPrefix( bonePrefix ); + while( keyValue && keyValue->GetValue().Length() ) { + + if( GetJointWorldTransform( keyValue->GetValue().c_str(), bonePos, boneAxis ) ) { + //AOB: HACK - crashed in idBitMsg::DirToBits because we didn't appear to be normalized + localFxInfo.SetNormal( boneAxis[0].ToNormal() ); + localFxInfo.SetBindBone( keyValue->GetValue().c_str() ); + + BroadcastFxInfoPrefixed( fxKey, bonePos, boneAxis, &localFxInfo, eventDef, broadcast ); + } + + keyValue = args->MatchPrefix( bonePrefix, keyValue ); + } +} + +/* +================= +hhAnimatedEntity::SpawnFXAlongBone +mdl +================= +*/ +void hhAnimatedEntity::BroadcastFxInfoAlongBonePrefixUnique( const idDict* args, const char* fxKey, const char* bonePrefix, const hhFxInfo* const fxInfo, const idEventDef* eventDef, bool broadcast ) { + if( !fxKey || !fxKey[0] || !bonePrefix || !bonePrefix[0] ) { + return; + } + + idVec3 bonePos; + idMat3 boneAxis; + hhFxInfo localFxInfo( fxInfo ); + + localFxInfo.SetEntity( this ); + localFxInfo.RemoveWhenDone( true ); + + const idKeyValue* keyValue = args->MatchPrefix( bonePrefix ); + idStr postfix; + int prefixLen = strlen( bonePrefix ); + while( keyValue && keyValue->GetValue().Length() ) { + postfix = keyValue->GetKey().Right( keyValue->GetKey().Length() - prefixLen ); + + if( GetJointWorldTransform( keyValue->GetValue().c_str(), bonePos, boneAxis ) ) { + //AOB: HACK - crashed in idBitMsg::DirToBits because we didn't appear to be normalized + localFxInfo.SetNormal( boneAxis[0].ToNormal() ); + localFxInfo.SetBindBone( keyValue->GetValue().c_str() ); + + // Changed this because it was causing every fx listed to be bound to every bone listed. -mdl + BroadcastFxInfo( spawnArgs.GetString( fxKey + postfix ), bonePos, boneAxis, &localFxInfo, eventDef, broadcast ); + //BroadcastFxInfoPrefixed( fxKey, bonePos, boneAxis, &localFxInfo, eventDef ); + } + + keyValue = args->MatchPrefix( bonePrefix, keyValue ); + } +} + +/* +================ +hhAnimatedEntity::SpawnFxAlongBone +================ +*/ +void hhAnimatedEntity::BroadcastFxInfoAlongBone( const char* fxName, const char* boneName, const hhFxInfo* const fxInfo, const idEventDef* eventDef, bool broadcast ) { + //mdc: pass through to our newer version of the function (this is mainly done for backwards compatibility) + BroadcastFxInfoAlongBone( false, fxName, boneName, fxInfo, eventDef, broadcast ); +} + +void hhAnimatedEntity::BroadcastFxInfoAlongBone( bool bNoRemoveWhenUnbound, const char* fxName, const char* boneName, const hhFxInfo* const fxInfo, const idEventDef* eventDef, bool broadcast ) { + if( !fxName || !fxName[0] || !boneName || !boneName[0] ) { + return; + } + + idVec3 bonePos; + idMat3 boneAxis; + hhFxInfo localFxInfo( fxInfo ); + + GetJointWorldTransform( boneName, bonePos, boneAxis ); + localFxInfo.SetNormal( boneAxis[0] ); + localFxInfo.SetEntity( this ); + localFxInfo.NoRemoveWhenUnbound( bNoRemoveWhenUnbound ); //mdc: added + localFxInfo.SetBindBone( boneName ); + + BroadcastFxInfo( fxName, bonePos, boneAxis, &localFxInfo, eventDef, broadcast ); +} + +/* +================ +hhAnimatedEntity::Event_SpawnFXAlongBone +================ +*/ +void hhAnimatedEntity::Event_SpawnFXAlongBone( idList* fxParms ) { + if ( !fxParms ) { + return; + } + + bool bNoRemoveWhenUnbound = false; //default to false (we normally want to delete fx when the bound-entity is deleted) + HH_ASSERT( fxParms->Num() >= 2 && fxParms->Num() <= 3 ); //allow 2-3 parameters + if( fxParms->Num() == 3 ) { + //make sure the 3rd paramater is our special noRemoveWhenUnbound flag + if( !idStr::Icmp((*fxParms)[2].c_str(), "noremovewhenunbound") ) { + bNoRemoveWhenUnbound = true; + } + else { + gameLocal.Warning( "unrecognized 3rd paramater on frame command for SpawnFXAlongBone on entity '%s'", this->GetName() ); + } + } + BroadcastFxInfoAlongBone(bNoRemoveWhenUnbound, spawnArgs.GetString((*fxParms)[0].c_str()), (*fxParms)[1].c_str() ); +} + + +// HUMANHEAD pdm: Jaw flapping support +// This provides generalized support for any animated entity to translate voice channel sound to bone movements +void hhAnimatedEntity::JawFlap(hhAnimator *theAnimator) { + PROFILE_SCOPE("Jaw Flap", PROFMASK_NORMAL); + float amplitude = 0.0f; + + if (fl.noJawFlap || !g_jawflap.GetBool()) { + return; + } + + // Get amplitude from head or body + idEntity *headEntity = NULL; + if (IsType(idActor::Type)) { + headEntity = static_cast(this)->GetHead(); + if (headEntity && headEntity->GetSoundEmitter()) { + amplitude = headEntity->GetSoundEmitter()->CurrentVoiceAmplitude( SND_CHANNEL_VOICE ); + } + } + + if( amplitude == 0.0f && GetSoundEmitter() ) { + amplitude = GetSoundEmitter()->CurrentVoiceAmplitude( SND_CHANNEL_VOICE ); + } + + if (amplitude != 0.0f || lastAmplitude != 0.0f) { + //HUMANHEAD rww - only get the joint handles and info for jaw flapping once + //we have to do this here, since the head is not valid at hhAnimatedEntity::Spawn + if (!hasFlapInfo) { + jawFlapList.Clear(); + const char *prefix = "jawbone_"; + const idKeyValue *kv = spawnArgs.MatchPrefix(prefix); + while( kv && kv->GetValue().Length() ) { + jointHandle_t bone = theAnimator->GetJointHandle( kv->GetValue() ); + if (bone != INVALID_JOINT) { + jawFlapInfo_t flapInfo; + + idStr xformName = kv->GetKey(); + xformName.Strip(prefix); + + flapInfo.bone = bone; + flapInfo.rMagnitude = spawnArgs.GetVector( va("jawflapR_%s", xformName.c_str()) ); + flapInfo.tMagnitude = spawnArgs.GetVector( va("jawflapT_%s", xformName.c_str()) ); + flapInfo.rMinThreshold = spawnArgs.GetFloat( va("jawflapRMin_%s", xformName.c_str()) ); + flapInfo.tMinThreshold = spawnArgs.GetFloat( va("jawflapTMin_%s", xformName.c_str()) ); + jawFlapList.Append(flapInfo); + } + kv = spawnArgs.MatchPrefix(prefix, kv); + } + hasFlapInfo = true; + } + + lastAmplitude = amplitude; + //HUMANHEAD rww - changed to use a list and not do constant runtime joint lookups + for (int i = 0; i < jawFlapList.Num(); i++) { + jawFlapInfo_t &flapInfo = jawFlapList[i]; + + // Handle Rotation + idAngles angles = ang_zero; + if (amplitude > flapInfo.rMinThreshold) { + float factor = amplitude - flapInfo.rMinThreshold; + angles = idAngles(factor*flapInfo.rMagnitude[0], factor*flapInfo.rMagnitude[1], factor*flapInfo.rMagnitude[2]); + } + theAnimator->SetJointAxis( flapInfo.bone, JOINTMOD_WORLD, angles.ToMat3() ); + + // Handle translation + idVec3 translate = vec3_origin; + if (amplitude > flapInfo.tMinThreshold) { + float factor = amplitude - flapInfo.tMinThreshold; + translate = idVec3(factor*flapInfo.tMagnitude[0], factor*flapInfo.tMagnitude[1], factor*flapInfo.tMagnitude[2]); + } + theAnimator->SetJointPos( flapInfo.bone, JOINTMOD_WORLD, translate ); + } + } +} + +// WaitForSilence support: Tracks the next time the voice channel will be silent, and if any threads are doing a waitForSilence() block, calls them back. +// If a waitForSilence() call is issued while no sound is playing on the voice channel, it will wait until there is something and then wait for silence. +bool hhAnimatedEntity::StartSoundShader( const idSoundShader *shader, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ) { + int localLength = 0; + bool retVal = idAnimatedEntity::StartSoundShader(shader, channel, soundShaderFlags, broadcast, &localLength); + + // need to track 'nextSilentTime' locally also, so if a tiny sound is played after a longer sound, it doesn't think silence will follow short sound + if (channel == SND_CHANNEL_VOICE && (localLength > 0) && (gameLocal.time + localLength > nextSilentTime)) { + nextSilentTime = gameLocal.time + localLength; + + // If there's already a thread waiting for silence and this sound pushes silence out further, repost an event for it + if (waitingThread) { + CancelEvents(&EV_Silent); + int time = localLength+silentTimeOffset; + time = idMath::ClampInt(1, time, time); // Keep positive and wait at least 1ms + PostEventMS(&EV_Silent, time); + } + } + if (length) { + *length = localLength; + } + return retVal; +} + +void hhAnimatedEntity::Event_Silent() { + if (waitingThread) { + idThread::ObjectMoveDone( waitingThread, this ); + waitingThread = 0; + } +} + +void hhAnimatedEntity::Event_SetSilenceCallback(float plusOrMinusSeconds) { + + if (!waitingThread) { + waitingThread = idThread::CurrentThreadNum(); + silentTimeOffset = SEC2MS(plusOrMinusSeconds); + + if (nextSilentTime > gameLocal.time) { + // If sound already playing + int time = (nextSilentTime - gameLocal.time) + silentTimeOffset; + time = idMath::ClampInt(1, time, time); // Keep positive and wait at least 1ms + CancelEvents(&EV_Silent); + PostEventMS(&EV_Silent, time); + } + else { + // No sound playing, wait for sound to play first + } + idThread::ReturnInt( true ); + } + else { + idThread::ReturnInt( false ); + } +} diff --git a/src/Prey/game_animatedentity.h b/src/Prey/game_animatedentity.h new file mode 100644 index 0000000..7ac5a59 --- /dev/null +++ b/src/Prey/game_animatedentity.h @@ -0,0 +1,74 @@ +#ifndef __GAME_ANIMATEDENTITY_H +#define __GAME_ANIMATEDENTITY_H + +extern const idEventDef EV_CheckCycleRotate; +extern const idEventDef EV_CheckThaw; +extern const idEventDef EV_SpawnFxAlongBone; + +//HUMANHEAD rww +typedef struct jawFlapInfo_s { + jointHandle_t bone; + idVec3 rMagnitude; + idVec3 tMagnitude; + float rMinThreshold; + float tMinThreshold; +} jawFlapInfo_t; +//HUMANHEAD END + +class hhAnimatedEntity : public idAnimatedEntity { +public: + CLASS_PROTOTYPE( hhAnimatedEntity ); + + void Spawn( void ); + hhAnimatedEntity(); + virtual ~hhAnimatedEntity(); + + void Think(); + + virtual hhAnimator * GetAnimator( void ); + virtual const hhAnimator * GetAnimator( void ) const; + virtual void FillDebugVars( idDict *args, int page ); + + virtual idClipModel* GetCombatModel() const { return combatModel; } + + virtual bool GetJointWorldTransform( jointHandle_t jointHandle, int currentTime, idVec3 &offset, idMat3 &axis ); + virtual bool GetJointWorldTransform( const char* jointName, idVec3 &offset, idMat3 &axis ); + virtual bool GetJointWorldTransform( jointHandle_t jointHandle, idVec3 &offset, idMat3 &axis ); + + void JawFlap(hhAnimator *theAnimator); + + void SpawnFxAlongBonePrefixLocal( const idDict* dict, const char* fxKeyPrefix, const char* bonePrefix, const hhFxInfo* const fxInfo = NULL, const idEventDef* eventDef = NULL ); + virtual void BroadcastFxInfoAlongBonePrefix( const idDict* args, const char* fxKey, const char* bonePrefix, const hhFxInfo* const fxInfo = NULL, const idEventDef* eventDef = NULL, bool broadcast = true ); //HUMANHEAD rww - added broadcast + virtual void BroadcastFxInfoAlongBonePrefixUnique( const idDict* args, const char* fxKey, const char* bonePrefix, const hhFxInfo* const fxInfo = NULL, const idEventDef* eventDef = NULL, bool broadcast = true ); //HUMANHEAD rww - added broadcast + virtual void BroadcastFxInfoAlongBone( const char* fxName, const char* boneName, const hhFxInfo* const fxInfo = NULL, const idEventDef* eventDef = NULL, bool broadcast = true ); //HUMANHEAD rww - added broadcast + virtual void BroadcastFxInfoAlongBone( bool bNoRemoveWhenUnbound, const char* fxName, const char* boneName, const hhFxInfo* const fxInfo = NULL, const idEventDef* eventDef = NULL, bool broadcast = true ); //HUMANHEAD rww - added broadcast + + // WaitForSilence support + virtual bool StartSoundShader( const idSoundShader *shader, const s_channelType channel, int soundShaderFlags = 0, bool broadcast = false, int *length = NULL ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +protected: + // WaitForSilence support + int waitingThread; // Thread that's blocking until we tell it we're silent + int silentTimeOffset; // +/- this amount + int nextSilentTime; // Time at which all sounds will be ended + void Event_Silent(); + void Event_SetSilenceCallback(float plusOrMinusSeconds); + + float lastAmplitude; + + bool hasFlapInfo; //HUMANHEAD rww + idList jawFlapList; //HUMANHEAD rww + + //HUMANHEAD: aob - moved from idActor + virtual void UpdateWounds( void ); + virtual hhWoundManagerRenderEntity* CreateWoundManager() const { if (!animator.IsAnimatedModel()) { return idEntity::CreateWoundManager(); } return new hhWoundManagerAnimatedEntity(this); } + +protected: + void Event_CheckAnimatorCycleRotate( void ) { GetAnimator()->CheckCycleRotate(); } + void Event_CheckAnimatorThaw( void ) { GetAnimator()->CheckThaw(); } + void Event_SpawnFXAlongBone( idList* fxParms ); +}; + +#endif diff --git a/src/Prey/game_animatedgui.cpp b/src/Prey/game_animatedgui.cpp new file mode 100644 index 0000000..b6b566e --- /dev/null +++ b/src/Prey/game_animatedgui.cpp @@ -0,0 +1,208 @@ +//************************************************************************** +//** +//** hhAnimatedGui +//** +//************************************************************************** + +//TODO: Could impliment a queue for open/close events, so we can wait until +// fully open before closing +// -or- +// could make animations blend out of current and into the new +// requires dynamic control of animation weights + + +// HEADER FILES ------------------------------------------------------------ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const float AG_SMALL_SCALE = 0.01f; +const float AG_LARGE_SCALE = 1.0f; + +CLASS_DECLARATION( hhAnimatedEntity, hhAnimatedGui ) + EVENT(EV_PlayIdle, hhAnimatedGui::Event_PlayIdle) + EVENT(EV_Activate, hhAnimatedGui::Event_Trigger) +END_CLASS + + +//========================================================================== +// +// hhAnimatedGui::Spawn +// +//========================================================================== + +void hhAnimatedGui::Spawn(void) { + idDict args; + + GetPhysics()->SetContents( CONTENTS_BODY ); + + idleOpenAnim = GetAnimator()->GetAnim("idleopen"); + idleCloseAnim = GetAnimator()->GetAnim("idleclose"); + openAnim = GetAnimator()->GetAnim("open"); + closeAnim = GetAnimator()->GetAnim("close"); + + bOpen = false; + guiScale.Init(gameLocal.time, 0, AG_SMALL_SCALE, AG_SMALL_SCALE); + + // Spawn and bind the console on + const char *consoleName = spawnArgs.GetString("def_gui"); + if (consoleName && *consoleName) { + args.Clear(); + args.Set("gui", spawnArgs.GetString("gui_topass")); + args.Set("origin", GetOrigin().ToString()); // need the joint position + args.Set("rotation", GetAxis().ToString()); + attachedConsole = gameLocal.SpawnObject(consoleName, &args); + assert(attachedConsole); + attachedConsole->SetOrigin(GetOrigin() + GetAxis()[0]*10); + attachedConsole->Bind(this, true); + attachedConsole->Hide(); + } + + // Spawn the trigger + const char *triggerName = spawnArgs.GetString("def_trigger"); + if (triggerName && *triggerName) { + args.Clear(); + args.Set( "target", name.c_str() ); + args.Set( "mins", spawnArgs.GetString("triggerMins") ); + args.Set( "maxs", spawnArgs.GetString("triggerMaxs") ); + args.Set( "bind", name.c_str() ); + args.SetVector( "origin", GetOrigin() ); + args.SetMatrix( "rotation", GetAxis() ); + idEntity *trigger = gameLocal.SpawnObject( triggerName, &args ); + } + + if (idleOpenAnim && idleCloseAnim) { + PostEventMS(&EV_PlayIdle, 0); + } +} + +hhAnimatedGui::hhAnimatedGui() { + attachedConsole = NULL; +} + +hhAnimatedGui::~hhAnimatedGui() { +} + +void hhAnimatedGui::Save(idSaveGame *savefile) const { + savefile->WriteBool( bOpen ); + savefile->WriteInt( idleOpenAnim ); + savefile->WriteInt( idleCloseAnim ); + savefile->WriteInt( openAnim ); + savefile->WriteInt( closeAnim ); + + savefile->WriteFloat( guiScale.GetStartTime() ); // idInterpolate + savefile->WriteFloat( guiScale.GetDuration() ); + savefile->WriteFloat( guiScale.GetStartValue() ); + savefile->WriteFloat( guiScale.GetEndValue() ); + + savefile->WriteObject( attachedConsole ); +} + +void hhAnimatedGui::Restore( idRestoreGame *savefile ) { + float set; + + savefile->ReadBool( bOpen ); + savefile->ReadInt( idleOpenAnim ); + savefile->ReadInt( idleCloseAnim ); + savefile->ReadInt( openAnim ); + savefile->ReadInt( closeAnim ); + + savefile->ReadFloat( set ); // idInterpolate + guiScale.SetStartTime( set ); + savefile->ReadFloat( set ); + guiScale.SetDuration( set ); + savefile->ReadFloat( set ); + guiScale.SetStartValue(set); + savefile->ReadFloat( set ); + guiScale.SetEndValue( set ); + + savefile->ReadObject( reinterpret_cast( attachedConsole ) ); +} + +//========================================================================== +// +// hhAnimatedGui::Think +// +//========================================================================== +void hhAnimatedGui::Think( void ) { + hhAnimatedEntity::Think(); + + if (thinkFlags & TH_THINK) { + float curScale = guiScale.GetCurrentValue(gameLocal.time); + attachedConsole->SetDeformation(DEFORMTYPE_SCALE, curScale); + if (guiScale.IsDone(gameLocal.time)) { + if (curScale == AG_SMALL_SCALE) { // Just finished scaling down + attachedConsole->Hide(); + } + BecomeInactive(TH_THINK); + } + } +} + +//========================================================================== +// +// hhAnimatedGui::Event_PlayIdle +// +//========================================================================== +void hhAnimatedGui::Event_PlayIdle() { + + GetAnimator()->ClearAllAnims(gameLocal.time, 0); + + if (bOpen) { + GetAnimator()->CycleAnim(ANIMCHANNEL_ALL, idleOpenAnim, gameLocal.time, 0); + + FadeInGui(); + } + else { + GetAnimator()->CycleAnim(ANIMCHANNEL_ALL, idleCloseAnim, gameLocal.time, 0); + } +} + + +//========================================================================== +// +// hhAnimatedGui::Event_Trigger +// +//========================================================================== + +void hhAnimatedGui::Event_Trigger( idEntity *activator ) { + + if (!openAnim || !closeAnim) { + return; + } + + GetAnimator()->ClearAllAnims(gameLocal.time, 0); + + int ms; + if(bOpen) { + GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, closeAnim, gameLocal.time, 0); + ms = GetAnimator()->GetAnim( closeAnim )->Length(); + bOpen = false; + + FadeOutGui(); + } + else { + GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, openAnim, gameLocal.time, 0); + ms = GetAnimator()->GetAnim( openAnim )->Length(); + bOpen = true; + } + + PostEventMS(&EV_PlayIdle, ms); +} + +void hhAnimatedGui::FadeInGui() { + float curScale = guiScale.GetCurrentValue(gameLocal.time); + guiScale.Init(gameLocal.time, 500, curScale, AG_LARGE_SCALE); + BecomeActive(TH_THINK); + attachedConsole->SetDeformation(DEFORMTYPE_SCALE, curScale); + attachedConsole->Show(); +} + +void hhAnimatedGui::FadeOutGui() { + float curScale = guiScale.GetCurrentValue(gameLocal.time); + guiScale.Init(gameLocal.time, 100, curScale, AG_SMALL_SCALE); + BecomeActive(TH_THINK); + attachedConsole->Show(); +} diff --git a/src/Prey/game_animatedgui.h b/src/Prey/game_animatedgui.h new file mode 100644 index 0000000..4cf3842 --- /dev/null +++ b/src/Prey/game_animatedgui.h @@ -0,0 +1,34 @@ + +#ifndef __GAME_ANIMATEDGUI_H__ +#define __GAME_ANIMATEDGUI_H__ + + +class hhAnimatedGui : public hhAnimatedEntity { +public: + CLASS_PROTOTYPE(hhAnimatedGui); + + void Spawn(void); + hhAnimatedGui(); + virtual ~hhAnimatedGui(); + virtual void Think(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +protected: + void FadeInGui(); + void FadeOutGui(); + + void Event_Trigger( idEntity *activator ); + void Event_PlayIdle(); + +protected: + bool bOpen; + int idleOpenAnim; + int idleCloseAnim; + int openAnim; + int closeAnim; + idInterpolate guiScale; + idEntity * attachedConsole; +}; + +#endif /* __GAME_ANIMATEDGUI_H__ */ diff --git a/src/Prey/game_animator.cpp b/src/Prey/game_animator.cpp new file mode 100644 index 0000000..76905ce --- /dev/null +++ b/src/Prey/game_animator.cpp @@ -0,0 +1,251 @@ +//************************************************************************** +//** +//** hhAnimated +//** +//************************************************************************** + +// HEADER FILES ------------------------------------------------------------ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_PositionDefaultPose(""); +const idEventDef EV_StartDefaultAnim(""); +const idEventDef EV_SetAnim( "setAnim", "s" ); // nla +const idEventDef EV_IsAnimDone( "isAnimDone", "d", 'd' ); + +CLASS_DECLARATION( idAnimated, hhAnimated ) + EVENT( EV_Activate, hhAnimated::Event_Activate ) + EVENT( EV_SetAnim, hhAnimated::Event_SetAnim ) + EVENT( EV_IsAnimDone, hhAnimated::Event_IsAnimDone ) + EVENT( EV_AnimDone, hhAnimated::Event_AnimDone ) + EVENT( EV_Animated_Start, hhAnimated::Event_Start ) + EVENT( EV_PositionDefaultPose, hhAnimated::Event_PositionDefaultPose ) + EVENT( EV_StartDefaultAnim, hhAnimated::Event_StartDefaultAnim ) + EVENT( EV_Footstep, hhAnimated::Event_Footstep ) + EVENT( EV_FootstepLeft, hhAnimated::Event_Footstep ) + EVENT( EV_FootstepRight, hhAnimated::Event_Footstep ) +END_CLASS + +/* +=============== +hhAnimated::Spawn +================ +*/ +void hhAnimated::Spawn() { + isAnimDone = true; + + fl.takedamage = health > 0; + + const char* startAnimName = spawnArgs.GetString( "start_anim" ); + if( !startAnimName || !startAnimName[0] ) { + PostEventMS( &EV_PositionDefaultPose, 0 ); + } +} + +void hhAnimated::Save(idSaveGame *savefile) const { + savefile->WriteBool( isAnimDone ); +} + +void hhAnimated::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( isAnimDone ); +} + +/* +=============== +hhAnimated::Damage +================ +*/ +void hhAnimated::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + if ( CheckRagdollDamage( inflictor, attacker, dir, damageDefName, location ) ) { + return; + } + + idAnimated::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} + +/* +=============== +hhAnimated::Killed +================ +*/ +void hhAnimated::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + GetAnimator()->ClearAllAnims( gameLocal.time, 0 ); + idAnimBlend* pAnim = NULL;//GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, "death", gameLocal.time, 0, false ); + + if( !af.IsActive() ) { + PostEventMS( &EV_StartRagdoll, (pAnim) ? pAnim->Length() : 0 ); + } +} + +/* +=============== +hhAnimated::StartRagdoll +================ +*/ +bool hhAnimated::StartRagdoll( void ) { + bool parentAns = false; + float slomoStart, slomoEnd; + + // NOTE: These first two calls are ripped from idAnimated::StartRagdoll() + // if no AF loaded + if ( !af.IsLoaded() ) { + return false; + } + + // if the AF is already active + if ( af.IsActive() ) { + return true; + } + + // HUMANHEAD nla - Added to fix the issue with inproper offsets for fixed constraints. Taken from idAFEntity::LoadAF (Caused ragdolls to be 'fixed' to the center of the world/0,0,0) + af.GetPhysics()->Rotate( GetPhysics()->GetAxis().ToRotation() ); + af.GetPhysics()->Translate( GetPhysics()->GetOrigin() ); + // HUMANHEAD END + + parentAns = idAnimated::StartRagdoll(); + + // HUMANHEAD nla - Allow the ragdolls to slow into the ragdoll + 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 ); + + // Allow ragdolls to be active when first started + // This logic ripped from idAFEntity::LoadAF + af.GetPhysics()->PutToRest(); + af.GetPhysics()->Activate(); + + af.UpdateAnimation(); + GetAnimator()->CreateFrame( gameLocal.time, true ); + UpdateVisuals(); + // HUMANHEAD END + + return( parentAns ); +} + +/* +================ +hhAnimated::UpdateAnimationControllers +================ +*/ +bool hhAnimated::UpdateAnimationControllers( void ) { + bool retValue = idAnimated::UpdateAnimationControllers(); + + JawFlap(GetAnimator()); + + return retValue; +} + +/* +================ +hhAnimated::Event_SetAnim +================ +*/ +void hhAnimated::Event_SetAnim( const char *animname ) { + assert( animname ); + + anim = GetAnimator()->GetAnim( animname ); + HH_ASSERT( anim ); + + ProcessEvent( &EV_Activate, this ); +} + +/* +================ +hhAnimated::Event_Start +================ +*/ +void hhAnimated::Event_Start( void ) { + idAnimated::Event_Start(); + + isAnimDone = false; +} + +/* +=============== +hhAnimated::Event_AnimDone +================ +*/ +void hhAnimated::Event_AnimDone( int animIndex ) { + idAnimated::Event_AnimDone( animIndex ); + + if( !spawnArgs.GetBool("resetDefaultAnim") ) { + isAnimDone = true; + } else { + PostEventMS( &EV_StartDefaultAnim, 0 ); + } +} + +/* +================ +hhAnimated::Event_IsAnimDone +================ +*/ +void hhAnimated::Event_IsAnimDone( int timeMS ) { + idThread::ReturnInt( (int) isAnimDone ); +} + +/* +=============== +hhAnimated::Event_PositionDefaultPose +================ +*/ +void hhAnimated::Event_PositionDefaultPose() { + const char* animName = spawnArgs.GetString( "defaultPose" ); + + if( !animName || !animName[0] ) { + return; + } + + int pAnim = GetAnimator()->GetAnim( animName ); + GetAnimator()->SetFrame( ANIMCHANNEL_ALL, pAnim, spawnArgs.GetInt("pose_frame", "1"), gameLocal.GetTime(), 0 ); + + GetAnimator()->ForceUpdate(); + UpdateModel(); + UpdateAnimation(); +} + +/* +=============== +hhAnimated::Event_StartDefaultAnim +================ +*/ +void hhAnimated::Event_StartDefaultAnim() { + const char* animName = spawnArgs.GetString( "start_anim" ); + + if( !animName || animName[0] ) { + return; + } + + GetAnimator()->ClearAllAnims( gameLocal.GetTime(), 0 ); + int pAnim = GetAnimator()->GetAnim( animName ); + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, pAnim, gameLocal.time, FRAME2MS(blendFrames) ); +} + + +/* +=============== +hhAnimated::Event_Activate +================ +*/ +void hhAnimated::Event_Activate( idEntity *_activator ) { + if (spawnArgs.GetBool("ragdollOnTrigger")) { + StartRagdoll(); + return; + } + + idAnimated::Event_Activate(_activator); +} + +/* +=============== +hhAnimated::Event_Footstep +================ +*/ +void hhAnimated::Event_Footstep() { + StartSound( "snd_footstep", SND_CHANNEL_BODY3, 0, false, NULL ); +} diff --git a/src/Prey/game_animator.h b/src/Prey/game_animator.h new file mode 100644 index 0000000..746c560 --- /dev/null +++ b/src/Prey/game_animator.h @@ -0,0 +1,32 @@ + +#ifndef __GAME_ANIMATED_H__ +#define __GAME_ANIMATED_H__ + +class hhAnimated : public idAnimated { + CLASS_PROTOTYPE( hhAnimated ); + + public: + void Spawn(); + 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 bool UpdateAnimationControllers( void ); + + bool StartRagdoll( void ); + + protected: + void Event_SetAnim( const char* animname ); + void Event_IsAnimDone( int timeMS ); + virtual void Event_AnimDone( int animIndex ); + void Event_Start( void ); + void Event_PositionDefaultPose(); + void Event_StartDefaultAnim(); + virtual void Event_Activate( idEntity *_activator ); + void Event_Footstep(); + + protected: + bool isAnimDone; +}; + +#endif /* __GAME_ANIMATED_H__ */ diff --git a/src/Prey/game_arcadegame.cpp b/src/Prey/game_arcadegame.cpp new file mode 100644 index 0000000..74eb789 --- /dev/null +++ b/src/Prey/game_arcadegame.cpp @@ -0,0 +1,766 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +#define DEFAULT_HIGH_SCORE 76535 +#define DEFAULT_HIGH_SCORE_NAME "Teacher" +#define FRUIT_DELAY 15000 + +const idEventDef EV_GameStart("", NULL); +const idEventDef EV_PlayerRespawn("", NULL); +const idEventDef EV_MonsterRespawn("", "d"); +const idEventDef EV_ToggleFruit("", NULL); +const idEventDef EV_GameOver("", NULL); +const idEventDef EV_NextMap("", NULL); + +CLASS_DECLARATION(hhConsole, hhArcadeGame) + EVENT( EV_GameStart, hhArcadeGame::Event_GameStart) + EVENT( EV_PlayerRespawn, hhArcadeGame::Event_PlayerRespawn) + EVENT( EV_MonsterRespawn, hhArcadeGame::Event_MonsterRespawn) + EVENT( EV_ToggleFruit, hhArcadeGame::Event_ToggleFruit) + EVENT( EV_GameOver, hhArcadeGame::Event_GameOver) + EVENT( EV_NextMap, hhArcadeGame::Event_NextMap) + EVENT( EV_CallGuiEvent, hhArcadeGame::Event_CallGuiEvent) +END_CLASS + + +hhArcadeGame::hhArcadeGame() { +} + +hhArcadeGame::~hhArcadeGame() { +} + +void hhArcadeGame::Spawn() { + highscore = DEFAULT_HIGH_SCORE; + highscoreName = DEFAULT_HIGH_SCORE_NAME; + + GetMazeForLevel(1); + victoryAmount = spawnArgs.GetInt("victory"); + bPlayerMoving = false; + playerMove.Set(1,0); + bGameOver = true; + ResetMap(); + ResetPlayerAndMonsters(); + ResetGame(); + UpdateView(); +} + +void hhArcadeGame::GetMazeForLevel(int lev) { + numMazes = idMath::ClampInt(1, 999, spawnArgs.GetInt("numMazes")); + const char *maze = spawnArgs.GetString(va("maze%d", (lev-1)%numMazes+1)); + assert(idStr::Length(maze) == (ARCADE_GRID_WIDTH*ARCADE_GRID_HEIGHT)); + memset(startingGrid, 0, ARCADE_GRID_WIDTH*ARCADE_GRID_HEIGHT); + for (int y=0; yWrite(startingGrid, ARCADE_GRID_WIDTH*ARCADE_GRID_HEIGHT); + for (i=0; iWriteVec2(playerMove); + savefile->WriteBool(bPlayerMoving); + savefile->WriteBool(bSimulating); + savefile->WriteBool(bGameOver); + savefile->WriteBool(bPoweredUp); + savefile->WriteInt(score); + savefile->WriteInt(lives); + savefile->WriteInt(level); + savefile->WriteInt(nextScoreBoost); + savefile->WriteInt(nextPelletTime); + savefile->WriteInt(victoryAmount); + savefile->WriteInt(numMazes); + savefile->WriteInt(highscore); + savefile->WriteString(highscoreName); +} + +void hhArcadeGame::Restore( idRestoreGame *savefile ) { + int i,j; + savefile->Read(startingGrid, ARCADE_GRID_WIDTH*ARCADE_GRID_HEIGHT); + for (i=0; iReadVec2(playerMove); + savefile->ReadBool(bPlayerMoving); + savefile->ReadBool(bSimulating); + savefile->ReadBool(bGameOver); + savefile->ReadBool(bPoweredUp); + savefile->ReadInt(score); + savefile->ReadInt(lives); + savefile->ReadInt(level); + savefile->ReadInt(nextScoreBoost); + savefile->ReadInt(nextPelletTime); + savefile->ReadInt(victoryAmount); + savefile->ReadInt(numMazes); + savefile->ReadInt(highscore); + savefile->ReadString(highscoreName); +} + +void hhArcadeGame::ConsoleActivated() { + BecomeActive(TH_MISC3); +} + +void hhArcadeGame::StartGame() { + if (bGameOver) { + ResetMap(); + ResetPlayerAndMonsters(); + StartSound("snd_intro", SND_CHANNEL_ANY, 0, true, NULL); + ResetGame(); + StartSound("snd_coin", SND_CHANNEL_ANY, 0, true, NULL); + PostEventMS(&EV_GameStart, 2000); + bGameOver = false; + UpdateView(); + } +} + +void hhArcadeGame::FindType(int type, int &xOut, int &yOut, int skipX, int skipY) { + xOut = 0; + yOut = 0; + for (int x=0; xSetStateInt("player_x", guiPos.x); + gui->SetStateInt("player_y", guiPos.y); + gui->SetStateBool("player_vis", player.IsVisible()); + gui->SetStateBool("player_moving", bPlayerMoving); + gui->SetStateFloat("player_angle", player.angle); + gui->SetStateBool("player_dying", player.dying); + } + + for (ix=0; ix<4; ix++) { + if (monsters[ix].Changed()) { + guiPos = SmoothMovement(monsters[ix]); + gui->SetStateInt(va("monster%d_x", ix+1), guiPos.x); + gui->SetStateInt(va("monster%d_y", ix+1), guiPos.y); + gui->SetStateBool(va("monster%d_vis", ix+1), monsters[ix].IsVisible()); + gui->SetStateInt(va("monster%d_pu", ix+1), monsters[ix].vulnerableTimeRemaining); + gui->SetStateFloat(va("monster%d_angle", ix+1), monsters[ix].angle); + } + } + + // Traverse grid, telling gui about state of the pellets and powerups + int curPowerup = 1; + int curPellet = 1; + for (int x=0; xSetStateInt(va("pellet%d_x", curPellet), x * ARCADE_CELL_WIDTH); + gui->SetStateInt(va("pellet%d_y", curPellet), y * ARCADE_CELL_HEIGHT); + gui->SetStateBool(va("pellet%d_vis", curPellet), grid[x][y].IsVisible()); + curPellet++; + } + else if (grid[x][y].GetType() == ARCADE_POWERUP) { + gui->SetStateInt(va("powerup%d_x", curPowerup), x * ARCADE_CELL_WIDTH); + gui->SetStateInt(va("powerup%d_y", curPowerup), y * ARCADE_CELL_HEIGHT); + gui->SetStateBool(va("powerup%d_vis", curPowerup), grid[x][y].IsVisible()); + curPowerup++; + } + } + } + } + + if (fruit.Changed()) { + gui->SetStateInt("fruit_x", fruit.x * ARCADE_CELL_WIDTH); + gui->SetStateInt("fruit_y", fruit.y * ARCADE_CELL_HEIGHT); + gui->SetStateBool("fruit_vis", fruit.IsVisible()); + } + + if (score > highscore) { + highscore = score; + highscoreName = cvarSystem->GetCVarString("ui_name"); + } + + gui->SetStateInt("maze", (level-1)%numMazes+1); + gui->SetStateInt("level", level); + gui->SetStateInt("score", score); + gui->SetStateInt("lives", lives); + gui->SetStateInt("highscore", highscore); + gui->SetStateString("highscorename", highscoreName.c_str()); + gui->SetStateBool("gameover", bGameOver); + gui->StateChanged(gameLocal.time, true); + } +} + +bool hhArcadeGame::HandleSingleGuiCommand(idEntity *entityGui, idLexer *src) { + + idToken token; + + if (!src->ReadToken(&token)) { + return false; + } + + if (token == ";") { + return false; + } + + if (token.Icmp("startgame") == 0) { + // Trigger this entity, used for tip system + idEntity *agTrig = gameLocal.FindEntity( "arcadegame_used" ); + if( agTrig ) { + agTrig->PostEventMS( &EV_Activate, 0, this ); + } + + BecomeActive(TH_MISC3); + StartGame(); + } + else if (token.Icmp("reset") == 0) { + BecomeInactive(TH_MISC3); + bSimulating = false; + bGameOver = true; + bPlayerMoving = false; + playerMove.Set(1,0); + ResetMap(); + ResetPlayerAndMonsters(); + ResetGame(); + highscore = 1000; + highscoreName = DEFAULT_HIGH_SCORE_NAME; + + UpdateView(); + } + else { + src->UnreadToken(&token); + return false; + } + + return true; +} + +void hhArcadeGame::PlayerControls(usercmd_t *cmd) { + if (bGameOver && (cmd->buttons & BUTTON_ATTACK)) { + StartGame(); + } + + // These come from the player every tick + if (cmd->rightmove != 0 || cmd->forwardmove != 0) { + playerMove.x = cmd->rightmove > 0 ? 1 : cmd->rightmove < 0 ? -1 : 0; + playerMove.y = cmd->forwardmove < 0 ? 1 : cmd->forwardmove > 0 ? -1 : 0; + } +} + +idVec2 MoveForDirection(int dir) { + switch(dir) { + case PAC_MOVE_LEFT: return idVec2(-1, 0); + case PAC_MOVE_RIGHT: return idVec2(1, 0); + case PAC_MOVE_UP: return idVec2(0, -1); + case PAC_MOVE_DOWN: return idVec2(0, 1); + } + return idVec2(0,0); +} + +bool hhArcadeGame::MoveIsValid(idVec2 &move, MovingGamePiece &piece) { + int newX = piece.x + move.x; + int newY = piece.y + move.y; + int cell = grid[newX][newY].GetType(); + + if (cell == ARCADE_CLIPPLAYER) { + // Hack: disallow reentry into ghost cage. Assumes the monster start is adjacent to the playerclip + int sourceCell = grid[piece.x][piece.y].GetType(); + if (sourceCell != ARCADE_MONSTERSTART) { + return false; + } + } + return (cell != ARCADE_SOLID && (cell != ARCADE_CLIPPLAYER || !piece.isPlayer ) ); +} + +void hhArcadeGame::GrowPellets() { + if (!bSimulating) { + return; + } + + int growth = 0; + int i, j; + int x, y; + int startX = gameLocal.random.RandomInt(ARCADE_GRID_WIDTH); + int startY = gameLocal.random.RandomInt(ARCADE_GRID_HEIGHT); + + StartSound("snd_grow", SND_CHANNEL_ANY, 0, true, NULL); + + for (i=0; i 4) { + return; + } + } + } + } +} + +void hhArcadeGame::CheckForCollisions(MovingGamePiece &piece) { + int x = piece.x; + int y = piece.y; + int ix; + int type; + int destX, destY; + + switch(grid[x][y].GetType()) { + case ARCADE_TELEPORT: + FindType(ARCADE_TELEPORT, destX, destY, x, y); + piece.Set(destX, destY); + break; + + case ARCADE_PELLET: + if (piece.isPlayer && grid[x][y].IsVisible()) { + grid[x][y].SetVisible(false); + AddScore(1); + StartSound("snd_pellet", SND_CHANNEL_ANY, 0, true, NULL); + } + break; + + case ARCADE_POWERUP: + if (piece.isPlayer && grid[x][y].IsVisible()) { + AddScore(50); + grid[x][y].SetVisible(false); + monsters[0].vulnerableTimeRemaining += 5000 / level; + monsters[1].vulnerableTimeRemaining += 5000 / level; + monsters[2].vulnerableTimeRemaining += 5000 / level; + monsters[3].vulnerableTimeRemaining += 5000 / level; + bPoweredUp = true; + StopSound(SND_CHANNEL_ITEM, true); + StartSound("snd_powerup", SND_CHANNEL_ANY, 0, true, NULL); + StartSound("snd_poweruploop", SND_CHANNEL_ITEM, 0, true, NULL); + } + break; + } + + if (piece.isPlayer) { + if (fruit.IsVisible() && piece.x==fruit.x && piece.y==fruit.y) { + fruit.SetVisible(false); + AddScore(1000*level); + StartSound("snd_fruit", SND_CHANNEL_ANY, 0, true, NULL); + CancelEvents(&EV_ToggleFruit); + PostEventMS(&EV_ToggleFruit, FRUIT_DELAY); + } + } + + // player / monster collisions + for (ix=0; ix<4; ix++) { + if (monsters[ix].IsVisible() && player.x==monsters[ix].x && player.y==monsters[ix].y) { + if (monsters[ix].vulnerableTimeRemaining > 0) { + monsters[ix].SetVisible(false); + StartSound("snd_eatmonster", SND_CHANNEL_ANY, 0, true, NULL); + AddScore(500); + PostEventMS(&EV_MonsterRespawn, 3000, ix); + } + else if (!gameLocal.GetLocalPlayer()->godmode) { + bSimulating = false; + player.SetVisible(false); + player.dying = true; + StartSound("snd_die", SND_CHANNEL_ANY, 0, true, NULL); + if (lives <= 0) { + PostEventMS(&EV_GameOver, 0); + } + else { + lives--; + PostEventMS(&EV_PlayerRespawn, 2000); + } + return; + } + } + } + + // Check for life bonus + if (score >= nextScoreBoost) { + lives++; + nextScoreBoost *= 2; + StartSound("snd_lifeboost", SND_CHANNEL_ANY, 0, true, NULL); + } + + // Check for level completion + if (piece.isPlayer) { + bool levelCompleted = true; + for (x=0; levelCompleted && x 0 && (level+1 >= victoryAmount)) { + ActivateTargets( gameLocal.GetLocalPlayer() ); + StartSound( "snd_victory", SND_CHANNEL_ANY ); + victoryAmount = 0; + } + StartSound("snd_leveldone", SND_CHANNEL_ANY, 0, true, NULL); + PostEventMS(&EV_NextMap, 2000); + } + } +} + +bool hhArcadeGame::DoMove(idVec2 &move, MovingGamePiece &piece) { + + bool bMoved = move.x || move.y; + piece.Move(move); + + if (piece.isPlayer) { + // Acount for player movement direction + bPlayerMoving = bMoved; + } + + CheckForCollisions(piece); + + return bMoved; +} + +void hhArcadeGame::DoPlayerMove() { + bPlayerMoving = false; + if (!bSimulating) { + return; + } + + bool bValidXMove = playerMove.x && MoveIsValid(idVec2(playerMove.x, 0), player); + bool bValidYMove = playerMove.y && MoveIsValid(idVec2(0, playerMove.y), player); + + // Allow move direction to alternate between X and Y + if (player.lastMove.x) { + if (bValidYMove) { + DoMove(idVec2(0, playerMove.y), player); + } + else if (bValidXMove) { + DoMove(idVec2(playerMove.x, 0), player); + } + else if (MoveIsValid(player.lastMove, player)) { + DoMove(player.lastMove, player); + } + } + else { + if (bValidXMove) { + DoMove(idVec2(playerMove.x, 0), player); + } + else if (bValidYMove) { + DoMove(idVec2(0, playerMove.y), player); + } + else if (MoveIsValid(player.lastMove, player)) { + DoMove(player.lastMove, player); + } + } +} + +void hhArcadeGame::DoMonsterAI(MovingGamePiece &monster, int index) { + if (!bSimulating) { + return; + } + + if (monster.vulnerableTimeRemaining > 0) { + monster.vulnerableTimeRemaining -= monster.moveDelay; + monster.vulnerableTimeRemaining = idMath::ClampInt(0, 100000, monster.vulnerableTimeRemaining); + } + + idVec2 move; + idVec2 toPlayer; + idVec2 toPlayerMove; + int playerDirX, playerDirY; + toPlayer.Set(player.x - monster.x, player.y - monster.y); + playerDirX = toPlayer.x < 0 ? PAC_MOVE_LEFT : PAC_MOVE_RIGHT; + playerDirY = toPlayer.y < 0 ? PAC_MOVE_UP : PAC_MOVE_DOWN; + toPlayerMove.Set( + toPlayer.x > 0 ? 1 : toPlayer.x < 0 ? -1 : 0, + toPlayer.y > 0 ? 1 : toPlayer.y < 0 ? -1 : 0 + ); + idVec2 illegalMove = -monster.lastMove; + bool bGoTowardPlayer = gameLocal.random.RandomInt(4) >= index; + int choice; + int startingChoice = index; // Makes each monster unique + int j; + + // Keep moving in direction of last move until there is an intersection + // At intersection, choose between one of the directions, favoring one going towards + // the player. Never go back in the direction you came from unless at a dead end. + + // Go towards player if possible + if (bGoTowardPlayer) { + move = MoveForDirection(playerDirX); + if (MoveIsValid(move, monster) && move != illegalMove) { + DoMove(move, monster); + return; + } + move = MoveForDirection(playerDirY); + if (MoveIsValid(move, monster) && move != illegalMove) { + DoMove(move, monster); + return; + } + } + + // Otherwise move in one of the other valid directions + for (j=0; j<4; j++) { + choice = (startingChoice + j) % 4; + move = MoveForDirection(choice); + if (MoveIsValid(move, monster) && move != illegalMove) { + DoMove(move, monster); + return; + } + } + + // If still haven't found a valid move, must be at a dead end, now allow illegalMove + for (j=0; j<4; j++) { + choice = (startingChoice + j) % 4; + move = MoveForDirection(choice); + if (MoveIsValid(move, monster)) { + DoMove(move, monster); + return; + } + } +} + +void hhArcadeGame::Think() { + hhConsole::Think(); + + if (thinkFlags & TH_MISC3) { + if (bSimulating) { + // Player + if (gameLocal.time > player.nextMoveTime) { + player.nextMoveTime = gameLocal.time + player.moveDelay; + if (playerMove.LengthSqr() > 0.0f) { + DoPlayerMove(); + } + } + + // AI + for (int ix=0; ix<4; ix++) { + if (gameLocal.time > monsters[ix].nextMoveTime) { + monsters[ix].nextMoveTime = gameLocal.time + monsters[ix].moveDelay; + DoMonsterAI(monsters[ix], ix); + } + } + + // See if power up sound should end + if (bPoweredUp) { + bPoweredUp = false; + for (int ix=0; ix<4; ix++) { + if (monsters[ix].vulnerableTimeRemaining > 0) { + bPoweredUp = true; + break; + } + } + if (!bPoweredUp) { + StopSound(SND_CHANNEL_ITEM, true); + } + } + + if (gameLocal.time > nextPelletTime) { + nextPelletTime = gameLocal.time + 3500; + //GrowPellets(); + } + + UpdateView(); + } + } +} + +void hhArcadeGame::AddScore(int amount) { + if (!gameLocal.GetLocalPlayer()->godmode) { + score += amount; + } +} + +void hhArcadeGame::Event_GameStart() { + bSimulating = true; + PostEventMS(&EV_ToggleFruit, FRUIT_DELAY); +} + +void hhArcadeGame::Event_GameOver() { // Player ran out of lives + CancelEvents(&EV_ToggleFruit); + StopSound(SND_CHANNEL_ITEM, true); + bSimulating = false; + bGameOver = true; + CallNamedEvent("gameover"); + UpdateView(); +} + +void hhArcadeGame::Event_NextMap() { // Transition to next map + CancelEvents(&EV_ToggleFruit); + StopSound(SND_CHANNEL_ITEM, true); + GetMazeForLevel(++level); + ResetMap(); + ResetPlayerAndMonsters(); + StartSound("snd_intro", SND_CHANNEL_ANY, 0, true, NULL); + PostEventMS(&EV_GameStart, 2000); + UpdateView(); +} + +void hhArcadeGame::Event_PlayerRespawn() { // Player died, respawn + CancelEvents(&EV_ToggleFruit); + ResetPlayerAndMonsters(); + StartSound("snd_intro", SND_CHANNEL_ANY, 0, true, NULL); + player.SetVisible(true); + player.dying = false; + PostEventMS(&EV_GameStart, 3000); + UpdateView(); +} + +void hhArcadeGame::Event_MonsterRespawn(int index) { // Monster died, respawn + int x,y; + FindType(ARCADE_MONSTERSTART, x, y); + monsters[index].SetVisible(true); + monsters[index].vulnerableTimeRemaining = 0; + monsters[index].Set(x,y); + monsters[index].nextMoveTime = gameLocal.time + 3000; +} + +void hhArcadeGame::Event_ToggleFruit() { + if (fruit.IsVisible()) { + fruit.SetVisible(false); + PostEventMS(&EV_ToggleFruit, FRUIT_DELAY); + } + else { + fruit.SetVisible(true); + PostEventMS(&EV_ToggleFruit, 5000); + } +} + +void hhArcadeGame::Event_CallGuiEvent(const char *eventName) { + + if (!idStr::Icmp(eventName, "turnoff")) { + // Release any players routing commands to us + for( int i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[i] && gameLocal.entities[i]->IsType(hhPlayer::Type) ) { + hhPlayer *player = static_cast(gameLocal.entities[i]); + if (player->guiWantsControls.IsValid() && player->guiWantsControls.GetEntity() == this) { + player->guiWantsControls = NULL; + } + } + } + } + + CallNamedEvent(eventName); +} + +void hhArcadeGame::LockedGuiReleased(hhPlayer *player) { + idEntity *agTrig = gameLocal.FindEntity( "arcadegame_released" ); + if( agTrig ) { + agTrig->PostEventMS( &EV_Activate, 0, this ); + } +} + diff --git a/src/Prey/game_arcadegame.h b/src/Prey/game_arcadegame.h new file mode 100644 index 0000000..4844d94 --- /dev/null +++ b/src/Prey/game_arcadegame.h @@ -0,0 +1,188 @@ + +#ifndef __GAME_ARCADEGAME_H__ +#define __GAME_ARCADEGAME_H__ + +#define ARCADE_GRID_WIDTH 19 +#define ARCADE_GRID_HEIGHT 21 +#define ARCADE_CELL_WIDTH 20 +#define ARCADE_CELL_HEIGHT 20 + +#define ARCADE_EMPTY 0 +#define ARCADE_SOLID 1 +#define ARCADE_PELLET 2 +#define ARCADE_POWERUP 3 +#define ARCADE_CLIPPLAYER 4 +#define ARCADE_TELEPORT 5 +#define ARCADE_FRUIT 6 +#define ARCADE_PLAYERSTART 7 +#define ARCADE_MONSTERSTART 8 + +enum { + PAC_MOVE_LEFT=0, + PAC_MOVE_RIGHT=1, + PAC_MOVE_UP=2, + PAC_MOVE_DOWN=3 +}; + +class GamePiece { +public: + GamePiece() { type = ARCADE_EMPTY; visible = true; changed = true; } + virtual ~GamePiece() { } + void SetType(char t) { type = t; changed = true; } + void SetVisible(bool v) { visible = v; changed = true; } + char GetType() { return type; } + bool IsVisible() { return visible; } + bool Changed() { return changed; } + virtual void Save( idSaveGame *savefile ) const { + savefile->WriteByte(type); + savefile->WriteBool(visible); + savefile->WriteBool(changed); + } + virtual void Restore( idRestoreGame *savefile ) { + savefile->ReadByte(type); + savefile->ReadBool(visible); + savefile->ReadBool(changed); + } + +protected: + byte type; + bool visible; + bool changed; +}; + +class MovingGamePiece : public GamePiece { +public: + MovingGamePiece() { + x = y = 0; isPlayer = false; dying = false; + lastMove.Zero(); + lastMoveTime = 0; + angle = 0.0f; + moveDelay = 250; + nextMoveTime = 0; + vulnerableTimeRemaining = 0; + } + void Set(int nx, int ny, bool nvis=true) { + x=nx; y=ny; visible = nvis; + lastMove.Zero(); + lastMoveTime = gameLocal.time; + changed = true; + } + void Move(idVec2 move) { + x += move.x; + y += move.y; + lastMove = move; + lastMoveTime = gameLocal.time; + angle = + lastMove.x > 0 ? 0.0f : + lastMove.x < 0 ? 180.0f : + lastMove.y < 0 ? 90.0f : + lastMove.y > 0 ? 270.0f : angle; + + changed = true; + } + virtual void Save( idSaveGame *savefile ) const { + GamePiece::Save(savefile); + savefile->WriteInt(x); + savefile->WriteInt(y); + savefile->WriteBool(isPlayer); + savefile->WriteFloat(angle); + savefile->WriteBool(dying); + savefile->WriteVec2(lastMove); + savefile->WriteInt(lastMoveTime); + savefile->WriteInt(moveDelay); + savefile->WriteInt(nextMoveTime); + savefile->WriteInt(vulnerableTimeRemaining); + } + virtual void Restore( idRestoreGame *savefile ) { + GamePiece::Restore(savefile); + savefile->ReadInt(x); + savefile->ReadInt(y); + savefile->ReadBool(isPlayer); + savefile->ReadFloat(angle); + savefile->ReadBool(dying); + savefile->ReadVec2(lastMove); + savefile->ReadInt(lastMoveTime); + savefile->ReadInt(moveDelay); + savefile->ReadInt(nextMoveTime); + savefile->ReadInt(vulnerableTimeRemaining); + } + + int x,y; + bool isPlayer; + bool dying; // used for player only + idVec2 lastMove; + float angle; + int lastMoveTime; + int moveDelay; // Delay in MS between moves + int nextMoveTime; // Next time piece is allowed to move + int vulnerableTimeRemaining; // monsters only, time before powerup wears off +}; + + +class hhArcadeGame : public hhConsole { +public: + CLASS_PROTOTYPE( hhArcadeGame ); + + hhArcadeGame(); + virtual ~hhArcadeGame(); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + virtual void Think(); + virtual bool HandleSingleGuiCommand(idEntity *entityGui, idLexer *src); + virtual void PlayerControls(usercmd_t *cmd); + virtual void ConsoleActivated(); + virtual void LockedGuiReleased(hhPlayer *player); + + void StartGame(); + bool MoveIsValid(idVec2 &move, MovingGamePiece &piece); + bool DoMove(idVec2 &move, MovingGamePiece &piece); + void CheckForCollisions(MovingGamePiece &piece); + void ResetMap(); + void ResetPlayerAndMonsters(); + void ResetGame(); + void GrowPellets(); + void DoPlayerMove(); + void DoMonsterAI(MovingGamePiece &monster, int index); + idVec2 SmoothMovement(MovingGamePiece &piece); + void UpdateView(); + void FindType(int type, int &x, int &y, int skipX=-1, int skipY=-1); + void GetMazeForLevel(int lev); + void AddScore(int amount); + +protected: + void Event_GameStart(); + void Event_GameOver(); + void Event_PlayerRespawn(); + void Event_MonsterRespawn(int index); + void Event_ToggleFruit(); + void Event_NextMap(); + void Event_CallGuiEvent(const char *eventName); + +protected: + char startingGrid[ARCADE_GRID_WIDTH][ARCADE_GRID_HEIGHT]; + GamePiece grid[ARCADE_GRID_WIDTH][ARCADE_GRID_HEIGHT]; + MovingGamePiece monsters[4]; + MovingGamePiece player; + MovingGamePiece fruit; + idVec2 playerMove; + idStr highscoreName; + bool bPlayerMoving; + bool bSimulating; + bool bGameOver; + bool bPoweredUp; + int score; + int highscore; + int lives; + int level; + int numMazes; + int nextScoreBoost; + int nextPelletTime; + +private: + int victoryAmount; +}; + + +#endif diff --git a/src/Prey/game_barrel.cpp b/src/Prey/game_barrel.cpp new file mode 100644 index 0000000..e610f5f --- /dev/null +++ b/src/Prey/game_barrel.cpp @@ -0,0 +1,181 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +CLASS_DECLARATION( hhMoveable, hhBarrel ) +END_CLASS + +/* +================ +hhBarrel::hhBarrel +================ +*/ +hhBarrel::hhBarrel() { + radius = 1.0f; + barrelAxis = 0; + lastOrigin.Zero(); + lastAxis.Identity(); + additionalRotation = 0.0f; + additionalAxis.Identity(); + fl.networkSync = true; +} + +/* +================ +hhBarrel::Save +================ +*/ +void hhBarrel::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( radius ); + savefile->WriteInt( barrelAxis ); + savefile->WriteVec3( lastOrigin ); + savefile->WriteMat3( lastAxis ); + savefile->WriteFloat( additionalRotation ); + savefile->WriteMat3( additionalAxis ); +} + +/* +================ +hhBarrel::Restore +================ +*/ +void hhBarrel::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( radius ); + savefile->ReadInt( barrelAxis ); + savefile->ReadVec3( lastOrigin ); + savefile->ReadMat3( lastAxis ); + savefile->ReadFloat( additionalRotation ); + savefile->ReadMat3( additionalAxis ); +} + +/* +================ +hhBarrel::BarrelThink +================ +*/ +void hhBarrel::BarrelThink( void ) { + bool wasAtRest, onGround; + float movedDistance, rotatedDistance, angle; + idVec3 curOrigin, gravityNormal, dir; + idMat3 curAxis, axis; + + wasAtRest = IsAtRest(); + + // 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(); +} + +/* +================ +hhBarrel::Think +================ +*/ +void hhBarrel::Think( void ) { + if ( thinkFlags & TH_THINK ) { + if ( !FollowInitialSplinePath() ) { + BecomeInactive( TH_THINK ); + } + } + + BarrelThink(); +} + +/* +================ +hhBarrel::GetPhysicsToVisualTransform +================ +*/ +bool hhBarrel::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + origin = vec3_origin; + axis = additionalAxis; + return true; +} + +/* +================ +hhBarrel::Spawn +================ +*/ +void hhBarrel::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(); +} + +/* +================ +hhBarrel::ClientPredictionThink +================ +*/ +void hhBarrel::ClientPredictionThink( void ) { + Think(); +} + + diff --git a/src/Prey/game_barrel.h b/src/Prey/game_barrel.h new file mode 100644 index 0000000..db21d80 --- /dev/null +++ b/src/Prey/game_barrel.h @@ -0,0 +1,30 @@ +#ifndef __HH_BARREL_H +#define __HH_BARREL_H + +class hhBarrel : public hhMoveable { + +public: + CLASS_PROTOTYPE( hhBarrel ); + hhBarrel(); + + 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 +}; + + +#endif \ No newline at end of file diff --git a/src/Prey/game_bindController.cpp b/src/Prey/game_bindController.cpp new file mode 100644 index 0000000..5541d1b --- /dev/null +++ b/src/Prey/game_bindController.cpp @@ -0,0 +1,290 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +//========================================================================== +// +// hhBindController +// +// These can be bound to anything. They can be used to attach a player, monster, almost any entity +// and take control of their movement through space. Players are optionally allowed to look around +// while bound. This class was necessary to abstract all the physics differences away from the +// objects doing the binding. +//========================================================================== + +const idEventDef EV_BindAttach("bindattach", "ed"); +const idEventDef EV_BindDetach("binddrop", NULL); +const idEventDef EV_BindAttachBody("bindattachbody", "edd"); + +CLASS_DECLARATION(idEntity, hhBindController) + EVENT( EV_BindAttach, hhBindController::Event_Attach ) + EVENT( EV_BindDetach, hhBindController::Event_Detach ) + EVENT( EV_BindAttachBody, hhBindController::Event_AttachBody ) +END_CLASS + + +void hhBindController::Spawn() { + boundRider = NULL; + hand = NULL; + yawLimit = 180.0f; // no limit + float tension = spawnArgs.GetFloat("tension", "0.01"); + const char *anim = spawnArgs.GetString("boundanim"); + const char *hand = spawnArgs.GetString("def_hand"); + float yaw = spawnArgs.GetFloat("yawlimit", "180"); + float pitch = spawnArgs.GetFloat("pitchlimit", "75"); + bOrientPlayer = spawnArgs.GetBool("orientplayer"); + + bLooseBind = false; + + SetRiderParameters(anim, hand, yaw, pitch); + SetTension(tension); + hangID = 0; +} + +hhBindController::~hhBindController() { + Detach(); +} + +void hhBindController::Save(idSaveGame *savefile) const { + boundRider.Save(savefile); + savefile->WriteObject( hand ); + savefile->WriteBool( bLooseBind ); + savefile->WriteBool( bOrientPlayer ); + savefile->WriteStaticObject( force ); + savefile->WriteString( animationName ); + savefile->WriteString( handName ); + savefile->WriteFloat( yawLimit ); + savefile->WriteFloat(pitchLimit); + savefile->WriteInt( hangID ); +} + +void hhBindController::Restore( idRestoreGame *savefile ) { + boundRider.Restore(savefile); + savefile->ReadObject( reinterpret_cast(hand) ); + savefile->ReadBool( bLooseBind ); + savefile->ReadBool( bOrientPlayer ); + savefile->ReadStaticObject( force ); + savefile->ReadString( animationName ); + savefile->ReadString( handName ); + savefile->ReadFloat( yawLimit ); + savefile->ReadFloat(pitchLimit); + savefile->ReadInt( hangID ); +} + +idEntity *hhBindController::GetRider() const { + // Since the entity could be deleted at any time, we get the rider from the force (if used) + if (bLooseBind) { + //TODO: Make this force use a safe entityptr too + return force.GetEntity(); + } + return boundRider.GetEntity(); +} + +void hhBindController::SetTension(float tension) { + force.SetRestoreTime((1.0f - tension)*5.0f); + force.SetRestoreFactor(tension * g_springConstant.GetFloat()); +} + +void hhBindController::SetSlack(float slack) { + force.SetRestoreSlack(slack); +} + +void hhBindController::Think() { + idEntity::Think(); //TODO: This needed? + + if (bLooseBind && GetRider()) { + idVec3 loc = GetOrigin(); + force.SetTarget(loc); + + //TODO: Cull hangID out of this class if not needed. + + + // FIXME: Why is this needed? (Took out --pdm, test crane/rail before removing) +/* + idEntity *rider = GetRider(); + if (rider->IsType(idAFEntity_Base::Type)) { // Update the offset depending on if actor is ragdoll + idAFEntity_Base *af = static_cast(rider); + if (af->IsActiveAF()) { + force.SetEntity(rider, hangID, vec3_origin); + } + else { // non-ragdolled rider + if (rider->IsType(idActor::Type)) { + force.SetEntity(rider, 0, af->EyeOffset()); + } + else { + force.SetEntity(rider, 0, rider->GetOrigin()); + } + } + } +*/ + force.Evaluate(gameLocal.time); + } + else if (GetRider() && GetRider()->IsType(hhPlayer::Type) && OrientPlayer()) { + // This to insure that oriented players don't get out of sync + hhPlayer *player = static_cast(GetRider()); + idAngles angles = player->GetUntransformedViewAngles(); + player->SetOrientation(GetOrigin(), GetAxis(), angles.ToMat3()[0], angles); + } +} + +// Create our invisible hand to get rid of the weapon +void hhBindController::CreateHand(hhPlayer *player) { + if (gameLocal.isClient) { //rww + return; + } + + if (handName.Length()) { + ReleaseHand(); + + hand = hhHand::AddHand( player, handName.c_str() ); + } +} + +void hhBindController::ReleaseHand() { + if (hand) { + hand->RemoveHand(); + hand = NULL; + } +} + +void hhBindController::SetRiderParameters(const char *animname, const char *handname, float yaw, float pitch) { + animationName = animname; + handName = handname; + yawLimit = yaw; + pitchLimit = pitch; +} + +bool hhBindController::ValidRider(idEntity *ent) const { + return (ent != NULL); +} + +void hhBindController::Attach(idEntity *ent, bool loose, int bodyID, idVec3 &point) { + hhPlayer *player = ent->IsType(hhPlayer::Type) ? static_cast(ent) : NULL; + + if (GetRider()==NULL && ValidRider(ent)) { + + bLooseBind = loose; + + if (bLooseBind) { + idVec3 loc = GetOrigin(); + force.SetEntity(ent, bodyID, point); + force.SetTarget(loc); + if (ent->spawnArgs.GetBool("stable_tractor", "0")) { + force.SetAxisEntity(this); + } else { + force.SetAxisEntity(NULL); + } + BecomeActive(TH_THINK); + } + else { + boundRider = ent; + boundRider->SetOrigin(GetOrigin()); + + if (player && bOrientPlayer) { + idAngles angles = player->GetUntransformedViewAngles(); + player->SetOrientation(GetOrigin(), GetAxis(), angles.ToMat3()[0], angles); + } + else { + boundRider->SetAxis(GetAxis()); + } + boundRider->Bind(this, false); + } + + // Apply player parameters + if (player && !gameLocal.isMultiplayer) { //rww - in mp, don't want to restrict players when bound + player->maxRelativeYaw = yawLimit; + player->maxRelativePitch = pitchLimit; + player->bClampYaw = yawLimit < 180.0f; + CreateHand(player); +// player->hhweapon->Hide(); + } + // Play animation on actors (but not players in mp -rww) + if (ent->IsType(idActor::Type) && !ent->fl.tooHeavyForTractor && (!gameLocal.isMultiplayer || !ent->IsType(hhPlayer::Type))) { + idActor *actor = static_cast(ent); + actor->AI_BOUND = true; + if ( actor->GetHealth() > 0 ) { + actor->ProcessEvent(&AI_SetAnimPrefix, animationName.c_str()); + actor->SetAnimState( ANIMCHANNEL_LEGS, "Legs_Bound", 4 ); + actor->SetAnimState( ANIMCHANNEL_TORSO, "Torso_Bound", 4 ); + } + actor->BecameBound(this); + } + + hangID = 0; + if (ent->IsType(idAFEntity_Base::Type)) { + hangID = static_cast(ent)->spawnArgs.GetInt("hangID"); + } + } +} + +void hhBindController::Detach() { + idEntity *rider = GetRider(); + hhPlayer *player = rider && rider->IsType(hhPlayer::Type) ? static_cast(rider) : NULL; + if (rider) { + if (player) { + player->bClampYaw = false; + + if (gameLocal.isMultiplayer) { //do not fiddle with the player's anims in MP when he is grabbed, let him act normally + // Release player animation + player->AI_BOUND = false; + player->SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + player->SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); + } + + ReleaseHand(); + } + if (rider->IsType(idActor::Type)) { + idActor *actor = static_cast(rider); + actor->AI_BOUND = false; + actor->BecameUnbound(this); + } + + if (bLooseBind) { + force.SetEntity(NULL); + } + else { + rider->Unbind(); + rider->SetAxis(mat3_identity); + if (player && bOrientPlayer) { + // Set everything back to normal at current view angles + idAngles angles = player->GetUntransformedViewAngles(); + player->SetOrientation(GetOrigin(), mat3_identity, angles.ToMat3()[0], angles); + } + boundRider = NULL; + } + } +} + +idVec3 hhBindController::GetRiderBindPoint() const { + idEntity* rider = GetRider(); + + if( !rider ) { + return vec3_origin; + } + + if( rider->IsType(idActor::Type) ) { + idActor *actor = static_cast( rider ); + if( actor->IsActiveAF() ) { + return actor->GetOrigin( GetHangID() ); + } else { + return actor->GetEyePosition(); + } + } + + return rider->GetOrigin(); +} + +void hhBindController::Event_Attach(idEntity *rider, bool loose) { + Attach(rider, loose); +} + +void hhBindController::Event_AttachBody(idEntity *rider, bool loose, int bodyID) { + Attach(rider, loose, bodyID); +} + +void hhBindController::Event_Detach() { + Detach(); +} + diff --git a/src/Prey/game_bindController.h b/src/Prey/game_bindController.h new file mode 100644 index 0000000..aa6c137 --- /dev/null +++ b/src/Prey/game_bindController.h @@ -0,0 +1,68 @@ + +#ifndef __HH_GAME_BINDCONTROLLER_H__ +#define __HH_GAME_BINDCONTROLLER_H__ + + +/* +=================================================================================== + + hhBindController + + Entity used to control an entity's position in space. + Used for rail rides, tractor beams, crane, etc. + +=================================================================================== +*/ + +extern const idEventDef EV_BindAttach; +extern const idEventDef EV_BindDetach; + +class hhBindController : public idEntity { + +public: + CLASS_PROTOTYPE( hhBindController ); + + void Spawn(); + virtual ~hhBindController(); + virtual void Think(); + virtual void Attach(idEntity *ent, bool loose=false, int bodyID=0, idVec3 &point=vec3_origin); + virtual void Detach(); + void SetTension(float tension); + void SetSlack(float slack); + void SetRiderParameters(const char *animname, const char *handname, float yaw, float pitch); + idVec3 GetRiderBindPoint() const; + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + bool OrientPlayer() const { return bOrientPlayer; } + idEntity * GetRider() const; + ID_INLINE bool IsLoose() const { return bLooseBind; } + ID_INLINE int GetHangID() const { return hangID; } + ID_INLINE void SetShuttle(bool shuttle) { force.SetShuttle(shuttle); } + +protected: + bool ValidRider(idEntity *ent) const; + void CreateHand(hhPlayer *player); + void ReleaseHand(); + + void Event_Attach(idEntity *rider, bool loose); + void Event_AttachBody(idEntity *rider, bool loose, int bodyID); + void Event_Detach(); + +protected: + idEntityPtr boundRider; // Pointer to rider for true bind-based riders + // Loose bindings store rider in the force + hhHand * hand; + bool bLooseBind; // True if the bind is through a force + bool bOrientPlayer; // Orient the player to my orientation + hhForce_Converge force; // Force used for "loose" binding + + // Apply these to any entities bound + idStr animationName; // Animation to play on riders + idStr handName; // Hand to apply to player riders + float yawLimit; // Yaw restriction for player riders + float pitchLimit; //rww - used for slabs and other things to restrict pitch of rider + int hangID; // Body id to attach to for hanging +}; + +#endif \ No newline at end of file diff --git a/src/Prey/game_blackjack.cpp b/src/Prey/game_blackjack.cpp new file mode 100644 index 0000000..6574f74 --- /dev/null +++ b/src/Prey/game_blackjack.cpp @@ -0,0 +1,508 @@ +// game_blackjack +// + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +enum { + BJRESULT_BUST=0, + BJRESULT_PUSH=1, + BJRESULT_WIN=2, + BJRESULT_LOSE=3, + BJRESULT_BLACKJACK=4, + BJRESULT_5CARD=5 +}; + +const idEventDef EV_Deal("Deal", NULL); +const idEventDef EV_Hit("Hit", NULL); +const idEventDef EV_Stay("Stay", NULL); +const idEventDef EV_Double("Double", NULL); + +CLASS_DECLARATION(hhConsole, hhBlackJack) + EVENT( EV_Deal, hhBlackJack::Event_Deal) + EVENT( EV_Hit, hhBlackJack::Event_Hit) + EVENT( EV_Stay, hhBlackJack::Event_Stay) + EVENT( EV_Double, hhBlackJack::Event_Double) + EVENT( EV_UpdateView, hhBlackJack::Event_UpdateView) +END_CLASS + + +void hhBlackJack::Spawn() { + + Reset(); +} + +void hhBlackJack::Reset() { + bCanDeal = 1; + bCanIncBet = 1; + bCanDecBet = 1; + bCanHit = 0; + bCanStay = 0; + bCanDouble = 0; + bCanSplit = 0; + PlayerBet = Bet = 1; + DealerScore = PlayerScore = 0; + DealerAces = PlayerAces = 0; + PlayerCredits = spawnArgs.GetInt("credits"); + victoryAmount = spawnArgs.GetInt("victory"); + resultIndex = -1; + creditsWon = 0; + PlayerHand.Clear(); + DealerHand.Clear(); + + UpdateView(); +} + +void hhBlackJack::Save(idSaveGame *savefile) const { + int i; + + savefile->WriteInt( PlayerHand.Num() ); // Saving of idList + for( i = 0; i < PlayerHand.Num(); i++ ) { + savefile->Write(&PlayerHand[i], sizeof(card_t)); + } + savefile->WriteInt( DealerHand.Num() ); // Saving of idList + for( i = 0; i < DealerHand.Num(); i++ ) { + savefile->Write(&DealerHand[i], sizeof(card_t)); + } + + savefile->WriteInt(Bet); + savefile->WriteInt(PlayerBet); + savefile->WriteInt(DealerScore); + savefile->WriteInt(PlayerScore); + savefile->WriteInt(DealerAces); + savefile->WriteInt(PlayerAces); + savefile->WriteInt(PlayerCredits); + savefile->WriteInt(victoryAmount); + savefile->WriteInt(resultIndex); + savefile->WriteBool(bCanDeal); + savefile->WriteBool(bCanIncBet); + savefile->WriteBool(bCanDecBet); + savefile->WriteBool(bCanHit); + savefile->WriteBool(bCanStay); + savefile->WriteBool(bCanDouble); + savefile->WriteBool(bCanSplit); + savefile->WriteInt(creditsWon); +} + +void hhBlackJack::Restore( idRestoreGame *savefile ) { + int i, num; + + PlayerHand.Clear(); + savefile->ReadInt( num ); + PlayerHand.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->Read(&PlayerHand[i], sizeof(card_t)); + } + + DealerHand.Clear(); + savefile->ReadInt( num ); + DealerHand.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->Read(&DealerHand[i], sizeof(card_t)); + } + + savefile->ReadInt(Bet); + savefile->ReadInt(PlayerBet); + savefile->ReadInt(DealerScore); + savefile->ReadInt(PlayerScore); + savefile->ReadInt(DealerAces); + savefile->ReadInt(PlayerAces); + savefile->ReadInt(PlayerCredits); + savefile->ReadInt(victoryAmount); + savefile->ReadInt(resultIndex); + savefile->ReadBool(bCanDeal); + savefile->ReadBool(bCanIncBet); + savefile->ReadBool(bCanDecBet); + savefile->ReadBool(bCanHit); + savefile->ReadBool(bCanStay); + savefile->ReadBool(bCanDouble); + savefile->ReadBool(bCanSplit); + savefile->ReadInt(creditsWon); +} + +card_t hhBlackJack::GetCard(bool visible) +{ + card_t card; + char names[] = { '2','3','4','5','6','7','8','9','T','J','Q','K','A'}; + int values[] = { 2 , 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10, 11}; + + int r = gameLocal.random.RandomInt(13); + card.Name = names[r]; + card.Value = values[r]; + card.Suit = gameLocal.random.RandomInt(4); + card.Visible = visible; + return card; +} + +void hhBlackJack::Deal() +{ + // Empty hands + PlayerHand.Clear(); + DealerHand.Clear(); + + Bet = PlayerBet; + creditsWon = 0; + + // Deal initial hands + PlayerHand.Append(GetCard(1)); + DealerHand.Append(GetCard(0)); + PlayerHand.Append(GetCard(1)); + DealerHand.Append(GetCard(1)); + + RetallyScores(); + + bCanIncBet = false; + bCanDecBet = false; + bCanDeal = false; + DeterminePlayCommands(); + resultIndex = -1; + + if (PlayerScore == 21) { + AssessScores(); + EndGame(); + } + + UpdateView(); +} + +void hhBlackJack::Hit() +{ + PlayerHand.Append(GetCard(1)); + RetallyScores(); + DeterminePlayCommands(); + + if (PlayerScore >= 21 || PlayerHand.Num() == 5) { + FollowDealerRules(); + AssessScores(); + EndGame(); + } + + UpdateView(); +} + +void hhBlackJack::Stay() +{ + FollowDealerRules(); + AssessScores(); + EndGame(); + UpdateView(); +} + +void hhBlackJack::Double() +{ + Bet *= 2; + PlayerHand.Append(GetCard(1)); + RetallyScores(); + DeterminePlayCommands(); + + // Force player to stay + FollowDealerRules(); + AssessScores(); + EndGame(); + + UpdateView(); +} + +void hhBlackJack::IncBet() { + + int amount = 1; + idUserInterface *gui = renderEntity.gui[0]; + if (gui) { + amount = gui->GetStateInt("increment"); + } + + int oldBet = PlayerBet; + PlayerBet = idMath::ClampInt(PlayerBet, PlayerCredits, PlayerBet+amount); + PlayerBet = idMath::ClampInt(0, 999999, PlayerBet); + Bet = PlayerBet; + if (PlayerBet != oldBet) { + StartSound( "snd_betchange", SND_CHANNEL_ANY ); + } + + UpdateView(); +} + +void hhBlackJack::DecBet() { + + int amount = 1; + idUserInterface *gui = renderEntity.gui[0]; + if (gui) { + amount = gui->GetStateInt("increment"); + } + + int oldBet = PlayerBet; + if (PlayerBet > amount) { + PlayerBet -= amount; + } + else if (PlayerBet > 1) { + PlayerBet = 1; + } + Bet = PlayerBet; + if (PlayerBet != oldBet) { + StartSound( "snd_betchange", SND_CHANNEL_ANY ); + } + + UpdateView(); +} + + +/* + Blackjack utility functions +*/ + +void hhBlackJack::RetallyScores() +{ + int ix; + PlayerScore = DealerScore = PlayerAces = DealerAces = 0; + + for (ix=0; ix 21 && PlayerAces) { + PlayerScore -= 10; + PlayerAces--; + } + for (ix=0; ix 21 && DealerAces) { + DealerScore -= 10; + DealerAces--; + } +} + +void hhBlackJack::AssessScores() +{ + if (PlayerScore > 21) { + creditsWon = -Bet; + resultIndex = BJRESULT_BUST; + } + else if (DealerScore > 21) { + creditsWon = Bet; + resultIndex = BJRESULT_WIN; + } + else if (PlayerScore == 21 && PlayerHand.Num() == 2) { + // BlackJack + creditsWon = Bet * 2; + resultIndex = BJRESULT_BLACKJACK; + } + else if (PlayerScore <= 21 && PlayerHand.Num() == 5) { + creditsWon = Bet * 5; + resultIndex = BJRESULT_5CARD; + } + else if (DealerScore > PlayerScore) { + creditsWon = -Bet; + resultIndex = BJRESULT_LOSE; + } + else if (PlayerScore > DealerScore) { + creditsWon = Bet; + resultIndex = BJRESULT_WIN; + } + else { + // Push + creditsWon = 0; + resultIndex = BJRESULT_PUSH; + } + + PlayerCredits += creditsWon; + PlayerCredits = idMath::ClampInt(0, 999999999, PlayerCredits); + Bet = PlayerBet; + + // Play victory/failure sound + if (victoryAmount && PlayerCredits >= victoryAmount) { + StartSound( "snd_victory", SND_CHANNEL_ANY, 0, true, NULL ); + ActivateTargets( gameLocal.GetLocalPlayer() ); + victoryAmount = 0; + } + else if (creditsWon > 0) { + StartSound( "snd_win", SND_CHANNEL_ANY, 0, true, NULL ); + } + else if (creditsWon < 0) { + StartSound( "snd_lose", SND_CHANNEL_ANY, 0, true, NULL ); + } +} + +void hhBlackJack::UpdateBetMechanics() { + bCanIncBet = PlayerCredits > PlayerBet; + bCanDecBet = PlayerBet > 0; + if (PlayerCredits < PlayerBet) { + PlayerBet = PlayerCredits; + } +} + +void hhBlackJack::EndGame() { + bCanDeal = 1; + UpdateBetMechanics(); + bCanHit = 0; + bCanStay = 0; + bCanDouble = 0; + bCanSplit = 0; +} + +void hhBlackJack::UpdateView() { + // UpdateView() is posted as an event because sometimes we're already nested down in the gui handling code when it is called + // and it in turn re-enters the gui handling code with HandleNamedEvent() + CancelEvents(&EV_UpdateView); + PostEventMS(&EV_UpdateView, 0); +} + +void hhBlackJack::Event_UpdateView() { + int ix; + bool bGameOver = false; + idUserInterface *gui = renderEntity.gui[0]; + + if (gui) { + if (PlayerCredits <= 0) { + bCanIncBet = bCanDecBet = bCanHit = bCanStay = bCanDouble = bCanSplit = bCanDeal = false; + bGameOver = true; + } + + gui->SetStateBool("bgameover", bGameOver); + gui->SetStateBool("bcanincbet", bCanIncBet); + gui->SetStateBool("bcandecbet", bCanDecBet); + gui->SetStateBool("bcanhit", bCanHit); + gui->SetStateBool("bcanstay", bCanStay); + gui->SetStateBool("bcandouble", bCanDouble); + gui->SetStateBool("bcansplit", bCanSplit); + gui->SetStateBool("bcandeal", bCanDeal); + gui->SetStateInt("credits", PlayerCredits); + gui->SetStateInt("currentbet", Bet); + gui->SetStateInt("result", resultIndex); + gui->SetStateInt("creditswon", creditsWon); + + // Clear + for (ix=0; ix<6; ix++) { + gui->SetStateInt(va("Dealer%d_Visible", ix+1), 0); + gui->SetStateInt(va("Player%d_Visible", ix+1), 0); + gui->SetStateBool(va("Dealer%d_Flipped", ix+1), 0); + gui->SetStateBool(va("Player%d_Flipped", ix+1), 0); + } + + // Show dealer hand + for (ix=0; ixSetStateInt(va("Dealer%d_Visible", ix+1), 1); + if (DealerHand[ix].Visible) { + gui->SetStateBool(va("Dealer%d_Flipped", ix+1), 1); + gui->SetStateInt(va("Dealer%d_Suit", ix+1), DealerHand[ix].Suit); + char cardChar = DealerHand[ix].Name; + if (cardChar == 'T') { + gui->SetStateString(va("Dealer%d_Card", ix+1), "10"); + } + else { + gui->SetStateString(va("Dealer%d_Card", ix+1), va("%c", cardChar)); + } + gui->SetStateInt(va("Dealer%d_Red", ix+1), DealerHand[ix].Suit==SUIT_HEARTS || DealerHand[ix].Suit==SUIT_DIAMONDS ? 1 : 0); + } + } + + // Show player hand + for (ix=0; ixSetStateInt(va("Player%d_Visible", ix+1), 1); + if (PlayerHand[ix].Visible) { + gui->SetStateBool(va("Player%d_Flipped", ix+1), 1); + gui->SetStateInt(va("Player%d_Suit", ix+1), PlayerHand[ix].Suit); + char cardChar = PlayerHand[ix].Name; + if (cardChar == 'T') { + gui->SetStateString(va("Player%d_Card", ix+1), "10"); + } + else { + gui->SetStateString(va("Player%d_Card", ix+1), va("%c", cardChar)); + } + gui->SetStateInt(va("Player%d_Red", ix+1), PlayerHand[ix].Suit==SUIT_HEARTS || PlayerHand[ix].Suit==SUIT_DIAMONDS ? 1 : 0); + } + } + + gui->StateChanged(gameLocal.time, true); + CallNamedEvent("Update"); + } +} + +void hhBlackJack::FollowDealerRules() { + // Flip up cards + for (int i=0; iReadToken(&token)) { + return false; + } + + if (token == ";") { + return false; + } + + if (token.Icmp("deal") == 0) { + Deal(); + } + else if (token.Icmp("hit") == 0) { + Hit(); + } + else if (token.Icmp("stay") == 0) { + Stay(); + } + else if (token.Icmp("double") == 0) { + Double(); + } + else if (token.Icmp("incbet") == 0) { + IncBet(); + } + else if (token.Icmp("decbet") == 0) { + DecBet(); + } + else if (token.Icmp("reset") == 0) { + Reset(); + } + else if (token.Icmp("restart") == 0) { + PlayerCredits = spawnArgs.GetInt("credits"); + Bet = PlayerBet = 1; + EndGame(); + UpdateView(); + } + else { + src->UnreadToken(&token); + return false; + } + + return true; +} + +void hhBlackJack::Event_Deal() { + Deal(); +} + +void hhBlackJack::Event_Hit() { + Hit(); +} + +void hhBlackJack::Event_Stay() { + Stay(); +} + +void hhBlackJack::Event_Double() { + Double(); +} diff --git a/src/Prey/game_blackjack.h b/src/Prey/game_blackjack.h new file mode 100644 index 0000000..7e36374 --- /dev/null +++ b/src/Prey/game_blackjack.h @@ -0,0 +1,70 @@ + +#ifndef __GAME_BLACKJACK_H__ +#define __GAME_BLACKJACK_H__ + +extern const idEventDef EV_Deal; + +typedef struct card_s { + int Suit; + int Value; + char Name; + unsigned char Visible; +}card_t; + +class hhBlackJack : public hhConsole { +public: + CLASS_PROTOTYPE( hhBlackJack ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Deal(); + void Hit(); + void Stay(); + void Double(); + void IncBet(); + void DecBet(); + + void Reset(); + card_t GetCard(bool visible); + void RetallyScores(); + void AssessScores(); + void EndGame(); + void UpdateView(); + void FollowDealerRules(); + void DeterminePlayCommands(); + void UpdateBetMechanics(); + bool HandleSingleGuiCommand(idEntity *entityGui, idLexer *src); + +protected: + void Event_Deal(); + void Event_Hit(); + void Event_Stay(); + void Event_Double(); + void Event_UpdateView(); + +private: + idList PlayerHand; + idList DealerHand; + + int Bet; + int PlayerBet; + int DealerScore, PlayerScore; + int DealerAces, PlayerAces; + int PlayerCredits; + int victoryAmount; + int resultIndex; + int creditsWon; + + bool bCanDeal; + bool bCanIncBet; + bool bCanDecBet; + bool bCanHit; + bool bCanStay; + bool bCanDouble; + bool bCanSplit; +}; + + +#endif /* __GAME_BLACKJACK_H__ */ diff --git a/src/Prey/game_cards.cpp b/src/Prey/game_cards.cpp new file mode 100644 index 0000000..3537ec2 --- /dev/null +++ b/src/Prey/game_cards.cpp @@ -0,0 +1,130 @@ +// game_cards.cpp +// + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +static char cardnames[] = { '2','3','4','5','6','7','8','9','T','J','Q','K','A' }; +static char suitnames[] = { 'D','H','S','C' }; + + +//============================================================== +// hhCard utility class +//============================================================== +hhCard::hhCard() { + suit = SUIT_SPADES; + value = CARD_ACE; +} + +hhCard::hhCard(int value, int suit) { + this->suit = suit; + this->value = value; +} + +int hhCard::operator==(const hhCard &other) const { + return value==other.Value() && suit==other.Suit(); +} + +int hhCard::Value() const { + return value; +} + +int hhCard::Suit() const { + return suit; +} + +char hhCard::ValueName() { + return cardnames[value]; +} + +char hhCard::SuitName() { + return suitnames[suit]; +} + + +//============================================================== +// hhDeck utility class +//============================================================== + +CLASS_DECLARATION(idClass, hhDeck) +END_CLASS + +hhDeck::hhDeck() { + Generate(); + Shuffle(); +} + +void hhDeck::Save(idSaveGame *savefile) const { + int i; + savefile->WriteInt( cards.Num() ); // hhStack + for (i=0; iWrite(&cards[i], sizeof(hhCard)); + } +} + +void hhDeck::Restore( idRestoreGame *savefile ) { + int i, num; + hhCard card; + + cards.Clear(); // hhStack + savefile->ReadInt( num ); + cards.SetNum( num ); + for (i=0; iRead(&card, sizeof(hhCard)); + cards[i] = card; + } +} + +void hhDeck::Generate() { + int value, suit; + cards.Clear(); + for (value=0; value cards; + }; + +#endif /* __GAME_CARDS_H__ */ diff --git a/src/Prey/game_cilia.cpp b/src/Prey/game_cilia.cpp new file mode 100644 index 0000000..5137437 --- /dev/null +++ b/src/Prey/game_cilia.cpp @@ -0,0 +1,300 @@ +//************************************************************************** +//** +//** GAME_CILIA.CPP +//** +//** Specific type of SpherePart. When shot, they retract for a while, +//** and trigger nearby cilia to retract as well. +//************************************************************************** + +// HEADER FILES ------------------------------------------------------------ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +// MACROS ------------------------------------------------------------------ + +// TYPES ------------------------------------------------------------------- + +// CLASS DECLARATIONS ------------------------------------------------------ + +const idEventDef EV_StickOut( "stickout", NULL ); +const idEventDef EV_TriggerNearby( "triggernearby", NULL ); +const idEventDef EV_Idle( "idle", NULL ); + +CLASS_DECLARATION( hhSpherePart, hhSphereCilia ) + EVENT( EV_Activate, hhSphereCilia::Event_Trigger ) + EVENT( EV_TriggerNearby, hhSphereCilia::Event_TriggerNearby ) + EVENT( EV_StickOut, hhSphereCilia::Event_StickOut ) + EVENT( EV_Idle, hhSphereCilia::Event_Idle ) // JRM + EVENT( EV_Touch, hhSphereCilia::Event_Touch ) +END_CLASS + +// STATE DECLARATIONS ------------------------------------------------------- + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// hhSphereCilia::Spawn +// +//========================================================================== + +void hhSphereCilia::Spawn(void) { + fl.takedamage = true; // Allow the spherepart to be damaged + fl.allowSpiritWalkTouch = true; + bRetracted = false; // Start sticking out + bAlreadyActivated = false; // Hasn't been touched yet + + //HUMANHEAD jsh PCF 4/26/06 allow creatures to trigger cilia + GetPhysics()->SetContents( CONTENTS_TRIGGER | CONTENTS_RENDERMODEL ); + + idleAnim = GetAnimator()->GetAnim("idle"); + pullInAnim = GetAnimator()->GetAnim("pullin"); + stickOutAnim = GetAnimator()->GetAnim("stickout"); + + spawnArgs.GetFloat( "nearbySize", "128", nearbySize ); + spawnArgs.GetFloat( "idleDelay", "-1", idleDelay ); + + if( idleDelay < 0 ) { // negative number denotes a random start delay + idleDelay = gameLocal.random.RandomFloat(); + } + + GetAnimator()->ClearAllAnims( gameLocal.time, 0 ); + GetAnimator()->CycleAnim(ANIMCHANNEL_ALL, idleAnim, gameLocal.time + idleDelay * 1000, 100 ); +} + +void hhSphereCilia::Save(idSaveGame *savefile) const { + + savefile->WriteInt( pullInAnim ); + savefile->WriteInt( stickOutAnim ); + savefile->WriteFloat( nearbySize ); + savefile->WriteFloat( idleDelay ); + savefile->WriteBool( bRetracted ); + savefile->WriteBool( bAlreadyActivated ); +} + +void hhSphereCilia::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( pullInAnim ); + savefile->ReadInt( stickOutAnim ); + savefile->ReadFloat( nearbySize ); + savefile->ReadFloat( idleDelay ); + savefile->ReadBool( bRetracted ); + savefile->ReadBool( bAlreadyActivated ); +} + +//========================================================================== +// +// hhSphereCilia::Damage +// +// Currently similar to +//========================================================================== + +void hhSphereCilia::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + + hhFxInfo fxInfo; + + if( bRetracted ) { // Don't trigger if already retracted + return; + } + + bRetracted = true; + + // Remove collision on the model + GetPhysics()->SetContents(0); + + // Switch to the broken model + SetModel( spawnArgs.GetString( "model_broken", "" ) ); + + // Play an explode sound + StartSound( "snd_explode", SND_CHANNEL_ANY, 0, true, NULL ); + + fl.takedamage = false; + fl.applyDamageEffects = false; // Cilia don't accept damage wounds, since they swap out models + + PostEventSec( &EV_TriggerNearby, 0.2f + gameLocal.random.RandomFloat() * 0.1 ); + + ApplyEffect(); +} + +//========================================================================== +// +// hhSphereCilia::Event_Touch +// +//========================================================================== +void hhSphereCilia::Event_Touch( idEntity *other, trace_t *trace ) { + Trigger( other ); +} + +//========================================================================== +// +// hhSphereCilia::Trigger +// +//========================================================================== + +void hhSphereCilia::Trigger( idEntity *activator ) { + if( bRetracted || !activator ) { // Don't trigger if already retracted + return; + } + + bRetracted = true; + + // Remove collision on the model + GetPhysics()->SetContents(0); + + GetAnimator()->ClearAllAnims( gameLocal.time, 0 ); + GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, pullInAnim, gameLocal.time, 100); + + StartSound( "snd_in", SND_CHANNEL_ANY, 0, true, NULL ); + + PostEventSec( &EV_TriggerNearby, 0.2f + gameLocal.random.RandomFloat() * 0.1 ); + PostEventSec( &EV_StickOut, 5.0f + idleDelay ); + + // Trigger this cilia's targets the first time it is touched + if ( !bAlreadyActivated ) { + ActivateTargets( activator ); + bAlreadyActivated = true; + } + + ApplyEffect(); +} + +//========================================================================== +// +// hhSphereCilia::Event_Trigger +// +//========================================================================== + +void hhSphereCilia::Event_Trigger( idEntity *activator ) { + Trigger( activator ); +} + +//========================================================================== +// +// hhSphereCilia::Event_TriggerNearby +// +// Trigger nearby cilia (to create a cascading retract effect) +//========================================================================== + +void hhSphereCilia::Event_TriggerNearby( void ) { + int i; + int e; + hhSphereCilia *ent; + idEntity *entityList[ MAX_GENTITIES ]; + int numListedEntities; + idBounds bounds; + idVec3 org; + + org = GetPhysics()->GetOrigin(); + for ( i = 0 ; i < 3 ; i++ ) { + bounds[0][i] = org[i] - nearbySize; + bounds[1][i] = org[i] + nearbySize; + } + + // Find the first closest neighbor cilia to trigger + numListedEntities = gameLocal.clip.EntitiesTouchingBounds( bounds, -1, entityList, MAX_GENTITIES ); + + for ( e = 0 ; e < numListedEntities ; e++ ) { + if( !entityList[e]->IsType( hhSphereCilia::Type ) ) { + continue; + } + + ent = static_cast< hhSphereCilia * >( entityList[e] ); + + if( ent->bRetracted ) { + continue; + } + + ent->Trigger( this ); + } +} + +//========================================================================== +// +// hhSphereCilia::Event_StickOut +// +// Return the cilia to its original state +//========================================================================== + +#define MAX_NEARBY_CILIA 8 + +void hhSphereCilia::Event_StickOut( void ) { + int i; + int num; + idEntity *touch[MAX_NEARBY_CILIA]; + idEntity *hit; + bool canFit; + + // Check if the cilia can fit when sticking out + num = gameLocal.clip.EntitiesTouchingBounds( GetPhysics()->GetAbsBounds(), -1, touch, MAX_NEARBY_CILIA ); + + canFit = true; + for( i = 0; i < num; i++ ) { + hit = touch[ i ]; + assert( hit ); + + if(( hit == this )) { + continue; + } + + // Hit an entity, so reset the cilia to attempt to emerge in a bit + PostEventSec( &EV_StickOut, 5.0f ); + return; + } + + //HUMANHEAD jsh PCF 4/26/06 allow creatures to trigger cilia + // Restore the proper collision + GetPhysics()->SetContents( CONTENTS_TRIGGER | CONTENTS_RENDERMODEL ); + + // Play the stick out anims and blend the idle back in + GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, stickOutAnim, gameLocal.time, 100); + PostEventMS(&EV_Idle, 100); // JRM + + StartSound( "snd_out", SND_CHANNEL_ANY, 0, true, NULL); + + bRetracted = false; +} + +// +// Event_Idle +// +// JRM +void hhSphereCilia::Event_Idle(void) { + + assert(idleAnim); + GetAnimator()->CycleAnim(ANIMCHANNEL_ALL, idleAnim, gameLocal.time, 200); +}; + +void hhSphereCilia::ApplyEffect( void ) { + hhFxInfo fxInfo; + fxInfo.SetNormal( GetAxis()[0] ); + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfoPrefixed( "fx_explode", GetOrigin() + (GetAxis()[0] * 16.0f), GetAxis(), &fxInfo ); + + int num; + int i; + idEntity* ents[MAX_GENTITIES]; + + num = gameLocal.clip.EntitiesTouchingBounds( GetPhysics()->GetAbsBounds(), MASK_SHOT_BOUNDINGBOX|CONTENTS_RENDERMODEL, ents, MAX_GENTITIES ); + + const char *damageType = spawnArgs.GetString("damageType", "damage_cilia"); + for( i = 0; i < num; i++ ) { + if( ents[ i ] != this && !ents[ i ]->IsType(hhSphereCilia::Type) && !ents[ i ]->IsType(idAFAttachment::Type) ) { + ents[ i ]->Damage( this, this, vec3_origin, damageType, 1.0f, INVALID_JOINT ); + } + } + +} + diff --git a/src/Prey/game_cilia.h b/src/Prey/game_cilia.h new file mode 100644 index 0000000..1afadf2 --- /dev/null +++ b/src/Prey/game_cilia.h @@ -0,0 +1,36 @@ + +#ifndef __GAME_CILIA_H +#define __GAME_CILIA_H + +extern const idEventDef EV_TriggerNearby; +extern const idEventDef EV_StickOut; + +class hhSphereCilia : public hhSpherePart { +public: + CLASS_PROTOTYPE( hhSphereCilia ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void Trigger( idEntity *activator ); + void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + +protected: + void Event_Trigger( idEntity *activator ); + void Event_TriggerNearby( void ); + void Event_StickOut( void ); + void Event_Idle( void ); // JRM + void Event_Touch( idEntity *other, trace_t *trace ); + + void ApplyEffect( void ); + +protected: + int pullInAnim; + int stickOutAnim; + float nearbySize; + float idleDelay; + bool bRetracted; + bool bAlreadyActivated; +}; + +#endif /* __GAME_CILIA_H */ diff --git a/src/Prey/game_console.cpp b/src/Prey/game_console.cpp new file mode 100644 index 0000000..44e7cec --- /dev/null +++ b/src/Prey/game_console.cpp @@ -0,0 +1,822 @@ +// Game_console.cpp +// + +// Container class for all world objects that have GUIs on them. (except monsters & weapons) +// Provides some keys to these guis not provided by default GUI code like "rand" + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +#define TRANSLATION_TIME 1000 // Time over which translation takes place + +const idEventDef EV_CallGuiEvent("guiEvent", "s"); + +//========================================================================== +// +// hhConsole +// +//========================================================================== + +CLASS_DECLARATION(idStaticEntity, hhConsole) + EVENT( EV_Activate, hhConsole::Event_Activate ) + EVENT( EV_TalonAction, hhConsole::Event_TalonAction ) + EVENT( EV_CallGuiEvent, hhConsole::Event_CallGuiEvent ) + EVENT( EV_BecomeNonSolid, hhConsole::Event_BecomeNonSolid ) + EVENT( EV_PostSpawn, hhConsole::Event_PostSpawn ) +END_CLASS + +void hhConsole::Spawn() { +// BecomeActive(TH_MISC2); // For ai + + bTimeEventsAlways = spawnArgs.GetBool("timeEventsAlways"); + bUsesRand = spawnArgs.GetBool("usesRand"); + if (bUsesRand || bTimeEventsAlways) { + BecomeActive(TH_THINK); + } + + bool bDamageable = !spawnArgs.GetBool("noDamage"); + if (bDamageable) { + fl.takedamage = true; + } + + // Stuff color into gui_parms 20,21,22 + idVec3 color; + GetColor(color); + SetOnAllGuis("gui_parm20", color[0]); + SetOnAllGuis("gui_parm21", color[1]); + SetOnAllGuis("gui_parm22", color[2]); + + // Translation variables + translationAlpha.Init(gameLocal.time, 0, 0.0f, 0.0f); // Start at 0 (untranslated) + transState = TS_UNTRANSLATED; + + // Start idle sound + StartSound("snd_idle", SND_CHANNEL_ANY, 0, true, NULL); + + UpdateVisuals(); + + // Init AI data + aiCanUse = spawnArgs.GetBool("ai_can_use"); + aiUseCount = 0; + aiReuseWaitTime = (int)(spawnArgs.GetFloat("ai_reuse_wait")*1000.0f); + aiUseTime = (int)(spawnArgs.GetFloat("ai_use_time")*1000.0f); + aiTriggerWaitTime = (int)(spawnArgs.GetFloat("ai_use_trigger_wait")*1000.0f); + aiMaxUses = spawnArgs.GetInt("ai_max_uses", "-1"); + aiRetriggerWait = (int)(spawnArgs.GetFloat("ai_use_retrigger_wait")*1000.0f); + + // Clamp the trigger wait to be less than the total use time + if(aiTriggerWaitTime > aiUseTime) { + aiTriggerWaitTime = aiUseTime - 32; + if(aiTriggerWaitTime < 0) { + aiTriggerWaitTime = 0; + } + } + + aiCurrUsedStartTime = gameLocal.GetTime(); + aiCurrUsedBy = NULL; + aiLastUsedBy = NULL; + aiLastUsedTime = -aiReuseWaitTime; + aiWaitingToTrigger = true; + aiLastTriggerTime = gameLocal.GetTime() - 1000; + + perchSpot = NULL; + PostEventMS( &EV_PostSpawn, 0 ); +} + +void hhConsole::Event_PostSpawn() { + // Automatically cause all consoles to spawn Talon perch spots + if(!spawnArgs.GetBool("noTalonTarget")) { + idDict args; + idVec3 offset; + bool bGuiInteractive = false; + + // cjr - Iterate through guis and determine if any are interactive. If all are not, then Talon should not squawk + for ( int ix = 0; ix < MAX_RENDERENTITY_GUI; ix++ ) { + if ( renderEntity.gui[ix] && renderEntity.gui[ix]->IsInteractive() ) { + bGuiInteractive = true; + } + } + + const char *perchDef; + if ( spawnArgs.GetBool( "noTalonSquawk" ) || !bGuiInteractive) { // CJR: Check if talon should squawk at this spot + perchDef = spawnArgs.GetString("def_perch"); + } else { + perchDef = spawnArgs.GetString("def_perchSquawk"); // Talon should squawk at this spot + } + + if (!gameLocal.isClient) { + if (perchDef && perchDef[0]) { + // Set the offset for the perch location and priority + offset = GetPhysics()->GetAxis() * spawnArgs.GetVector("offset_perch", "0 0 24"); + float offsetYaw = spawnArgs.GetFloat("offset_perchyaw"); + args.SetVector("origin", GetPhysics()->GetOrigin() + offset); + + idAngles angles = GetAxis().ToAngles(); + angles.yaw += offsetYaw; + args.SetMatrix("rotation", angles.ToMat3()); + args.Set("target", this->name.c_str()); + perchSpot = (hhTalonTarget *)gameLocal.SpawnObject( perchDef, &args ); // Consoles are automatically high priority spots + if (perchSpot && ( spawnArgs.GetBool("bindPerchSpot") || IsBound() ) ) { + perchSpot->Bind(this, true); + } + } + else { + gameLocal.Warning("Need def_perch key on %s", name.c_str()); + perchSpot = NULL; + } + } + } +} + +void hhConsole::Save(idSaveGame *savefile) const { + savefile->WriteFloat( translationAlpha.GetStartTime() ); // idInterpolate + savefile->WriteFloat( translationAlpha.GetDuration() ); + savefile->WriteFloat( translationAlpha.GetStartValue() ); + savefile->WriteFloat( translationAlpha.GetEndValue() ); + + savefile->Write( &transState, sizeof(transState) ); + savefile->WriteBool( bTimeEventsAlways ); + savefile->WriteBool( bUsesRand ); + savefile->WriteObject( perchSpot ); + savefile->WriteBool( aiCanUse ); + savefile->WriteInt( aiUseCount ); + savefile->WriteInt( aiMaxUses ); + savefile->WriteInt( aiReuseWaitTime ); + savefile->WriteInt( aiUseTime ); + savefile->WriteInt( aiTriggerWaitTime ); + savefile->WriteBool( aiWaitingToTrigger ); + savefile->WriteInt( aiLastTriggerTime ); + savefile->WriteInt( aiRetriggerWait ); + aiCurrUsedBy.Save(savefile); + savefile->WriteInt( aiCurrUsedStartTime ); + aiLastUsedBy.Save(savefile); + savefile->WriteInt( aiLastUsedTime ); +} + +void hhConsole::Restore( idRestoreGame *savefile ) { + float set; + + savefile->ReadFloat( set ); // idInterpolate + translationAlpha.SetStartTime( set ); + savefile->ReadFloat( set ); + translationAlpha.SetDuration( set ); + savefile->ReadFloat( set ); + translationAlpha.SetStartValue(set); + savefile->ReadFloat( set ); + translationAlpha.SetEndValue( set ); + + savefile->Read( &transState, sizeof(transState) ); + savefile->ReadBool( bTimeEventsAlways ); + savefile->ReadBool( bUsesRand ); + savefile->ReadObject( reinterpret_cast(perchSpot) ); + savefile->ReadBool( aiCanUse ); + savefile->ReadInt( aiUseCount ); + savefile->ReadInt( aiMaxUses ); + savefile->ReadInt( aiReuseWaitTime ); + savefile->ReadInt( aiUseTime ); + savefile->ReadInt( aiTriggerWaitTime ); + savefile->ReadBool( aiWaitingToTrigger ); + savefile->ReadInt( aiLastTriggerTime ); + savefile->ReadInt( aiRetriggerWait ); + + aiCurrUsedBy.Restore(savefile); + savefile->ReadInt( aiCurrUsedStartTime ); + aiLastUsedBy.Restore(savefile); + savefile->ReadInt( aiLastUsedTime ); +} + +void hhConsole::Event_CallGuiEvent(const char *eventName) { + CallNamedEvent(eventName); +} + +void hhConsole::Event_BecomeNonSolid( void ) { + GetPhysics()->GetClipModel()->SetContents( 0 ); + GetPhysics()->SetClipMask( 0 ); +} + +void hhConsole::SetOnAllGuis(const char *key, float value) { + for (int ix=0; ixSetStateFloat(key, value); + renderEntity.gui[ix]->StateChanged(gameLocal.time); + } + } +} + +void hhConsole::SetOnAllGuis(const char *key, int value) { + for (int ix=0; ixSetStateInt(key, value); + renderEntity.gui[ix]->StateChanged(gameLocal.time); + } + } +} + +void hhConsole::SetOnAllGuis(const char *key, bool value) { + for (int ix=0; ixSetStateBool(key, value); + renderEntity.gui[ix]->StateChanged(gameLocal.time); + } + } +} + +void hhConsole::SetOnAllGuis(const char *key, const char *value) { + for (int ix=0; ixSetStateString(key, value); + renderEntity.gui[ix]->StateChanged(gameLocal.time); + } + } +} + +void hhConsole::Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location) { + + fl.takedamage = false; + + // JRM - disable msgs on kill? + if(spawnArgs.GetBool("ai_damage_disable_msgs","0")) { + fl.refreshReactions = false; + } + + // Inform the gui that we are damaged (not used, since gui materials can't see entity parms) +// SetShaderParm(SHADERPARM_DIVERSITY, gameLocal.random.RandomFloat()); + SetOnAllGuis("damaged", true); + + // Tell the gui that we died, so the gui can be in sync + CallNamedEvent("Death"); +} + +bool hhConsole::HandleSingleGuiCommand(idEntity *entityGui, idLexer *src) { + idToken token; + if (!src->ReadToken(&token) || token == ";") { + return false; + } + if (token.Icmp("setfont") == 0) { + if (src->ReadToken(&token)) { + idStr fontname = "fonts\\"; + fontname += token; + if (renderEntity.gui[0]) { + renderEntity.gui[0]->Translate(fontname.c_str()); + } + } + return true; + } + + src->UnreadToken( &token ); + return false; +} + +// This is overridden so we can update camera targets reguardless of PVS +void hhConsole::Present( void ) { + PROFILE_SCOPE("Present", PROFMASK_NORMAL); + + if ( !gameLocal.isNewFrame ) { + 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 + if ( cameraTarget ) { // && gameLocal.InPlayerPVS( this ) ) { // HUMANHEAD pdm: removed PVS check + 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 ); + } +} + +void hhConsole::Think() { + + if (thinkFlags & TH_THINK) { + if (bUsesRand) { + float rand = gameLocal.random.RandomFloat(); + SetOnAllGuis("rand", rand); + } + + if (bTimeEventsAlways) { + // Call event handler so time events get run + const char *cmd; + sysEvent_t ev; + memset( &ev, 0, sizeof( ev ) ); + ev.evType = SE_NONE; + + // This should work after the merge is done, since in the new code, HandleEvent() calls RunTimeEvents() + for (int ix=0; ixHandleEvent(&ev, gameLocal.time); + HandleGuiCommands(this, cmd); + } + } + } + } + + if (thinkFlags & TH_MISC1) { + // Handle any translation + if (transState == TS_TRANSLATING || transState == TS_UNTRANSLATING) { + float alpha = translationAlpha.GetCurrentValue(gameLocal.time); + if (renderEntity.gui[0] || renderEntity.gui[1] || renderEntity.gui[2]) { + + SetOnAllGuis("translationAlpha", alpha); + + // Determine if the transition is done + if (translationAlpha.IsDone(gameLocal.time)) { + if (transState == TS_TRANSLATING) { + transState = TS_TRANSLATED; + BecomeInactive(TH_MISC1); + } + else if (transState == TS_UNTRANSLATING) { + transState = TS_UNTRANSLATED; + BecomeInactive(TH_MISC1); + } + } + } + } + } + + // JRM - ai using update + if (thinkFlags & TH_MISC2) { + UpdateUse(); + } + + idStaticEntity::Think(); +} + +// Toggles the translation state of the gui +void hhConsole::Translate(bool bLanded) { + + if (!spawnArgs.GetBool("translate")) { + return; + } + + float curalpha = translationAlpha.GetCurrentValue(gameLocal.time); + switch(transState) { + case TS_UNTRANSLATED: + case TS_UNTRANSLATING: + translationAlpha.Init(gameLocal.time, TRANSLATION_TIME, curalpha, 1.0f); + transState = TS_TRANSLATING; + BecomeActive(TH_MISC1); + break; + case TS_TRANSLATED: + case TS_TRANSLATING: + translationAlpha.Init(gameLocal.time, TRANSLATION_TIME, curalpha, 0.0f); + transState = TS_UNTRANSLATING; + BecomeActive(TH_MISC1); + break; + } + + if (bLanded) { + StartSound( "snd_translate", SND_CHANNEL_ANY, 0, true, NULL); + } +} + +void hhConsole::ConsoleActivated() { + // Called when someone "uses" the gui (gui calls activate cmd) + // JRM - turn off msgs after the player uses this + if(!aiCurrUsedBy.IsValid() && spawnArgs.GetBool("ai_player_use_disable_msgs", "0")) { + fl.refreshReactions = false; + } +} + +void hhConsole::ClearTalonTargetType() { // CJR - Called when any command is sent to a gui. This disables talon's squawking + if ( perchSpot ) { + perchSpot->SetSquawk( false ); // Set the console to stop Talon from squawking + } +} + +void hhConsole::Event_Activate( idEntity *activator ) { + // Don't toggle on/off like in idStaticEntity + // any GUIs still get the onTrigger() event +} + +void hhConsole::Event_TalonAction(idEntity *talon, bool landed) { + Translate(landed); +} + +void hhConsole::Use(idAI *ai) { + HH_ASSERT(ai); + + // Just started using console? + if(!aiCurrUsedBy.IsValid()) { + aiCurrUsedBy = ai; + aiCurrUsedStartTime = gameLocal.GetTime(); + BecomeActive(TH_MISC2); + } +} + +void hhConsole::UpdateUse(void) { + + // Time to deactivate? + if(aiCurrUsedBy.IsValid() && gameLocal.GetTime() > aiCurrUsedStartTime + aiUseTime) { + aiLastUsedBy = aiCurrUsedBy; + aiLastUsedTime = gameLocal.GetTime(); + aiCurrUsedBy = NULL; + aiWaitingToTrigger = true; + aiUseCount++; + if(aiMaxUses > 0 && aiUseCount >= aiMaxUses) { + fl.refreshReactions = false; + } + BecomeInactive(TH_MISC2); + } + + // Time to fire triggers? + if (aiCurrUsedBy.IsValid()) { + + // Waiting to trigger... + if(aiWaitingToTrigger && gameLocal.GetTime() > aiCurrUsedStartTime + aiTriggerWaitTime) { + aiWaitingToTrigger = false; + + OnTriggeredByAI(aiCurrUsedBy.GetEntity()); + } + // Time to re-trigger? + else if (!aiWaitingToTrigger && aiRetriggerWait > 0 && gameLocal.GetTime() - aiLastTriggerTime > aiRetriggerWait) { + OnTriggeredByAI(aiCurrUsedBy.GetEntity()); + } + } +} + +void hhConsole::OnTriggeredByAI(idAI *ai) { + + aiLastTriggerTime = gameLocal.GetTime(); + + // Tell the gui that we were used, so the gui can be in sync + CallNamedEvent("AIUse"); +} + +bool hhConsole::CanUse(idAI *ai) { + HH_ASSERT(ai); + + if (!aiCanUse) { + return false; + } + + if (aiMaxUses > 0 && aiUseCount >= aiMaxUses) { + return false; + } + + // Can't use it if someone else is using it + if (aiCurrUsedBy.IsValid() && aiCurrUsedBy.GetEntity() != ai) { + return false; + } + + // Have to wait before we can use it again? + if (gameLocal.GetTime() < aiLastUsedTime + aiReuseWaitTime) { + return false; + } + + return ai->spawnArgs.GetBool("CanUseConsole", "0"); +} + +//========================================================================== +// +// hhConsoleCountdown +// +//========================================================================== + +const idEventDef EV_CountdownStart("startcountdown", NULL); +const idEventDef EV_CountdownStop("stopcountdown", NULL); +const idEventDef EV_CountdownSet("setcountdown", "d"); + +CLASS_DECLARATION(hhConsole, hhConsoleCountdown) + EVENT( EV_Activate, hhConsoleCountdown::Event_Activate ) + EVENT( EV_CountdownStart, hhConsoleCountdown::Event_StartCountdown ) + EVENT( EV_CountdownStop, hhConsoleCountdown::Event_StopCountdown ) + EVENT( EV_CountdownSet, hhConsoleCountdown::Event_SetCountdown ) +END_CLASS + +void hhConsoleCountdown::Spawn() { + countingDown = false; + countStart = spawnArgs.GetFloat("countStart"); + countEnd = spawnArgs.GetFloat("countEnd"); + countdown.Init(gameLocal.time, 0, countStart, countStart); + SetGuiOctal((int)countStart); + + if (spawnArgs.GetBool("enabled")) { + StartCountdown(); + } +} + +void hhConsoleCountdown::Save(idSaveGame *savefile) const { + savefile->WriteBool(countingDown); + savefile->WriteFloat(countStart); + savefile->WriteFloat(countEnd); + + savefile->WriteFloat( countdown.GetStartTime() ); // idInterpolate + savefile->WriteFloat( countdown.GetDuration() ); + savefile->WriteFloat( countdown.GetStartValue() ); + savefile->WriteFloat( countdown.GetEndValue() ); +} + +void hhConsoleCountdown::Restore( idRestoreGame *savefile ) { + float set; + + savefile->ReadBool(countingDown); + savefile->ReadFloat(countStart); + savefile->ReadFloat(countEnd); + + savefile->ReadFloat( set ); // idInterpolate + countdown.SetStartTime( set ); + savefile->ReadFloat( set ); + countdown.SetDuration( set ); + savefile->ReadFloat( set ); + countdown.SetStartValue(set); + savefile->ReadFloat( set ); + countdown.SetEndValue( set ); +} + +void hhConsoleCountdown::SetGuiOctal(int value) { + // Convert to base-8 numbers and set on gui + idStr octalValue; + sprintf(octalValue, "%o", value); + SetOnAllGuis("countdown", octalValue.c_str()); +} + +void hhConsoleCountdown::UpdateGUI(float curValue) { + SetGuiOctal((int)curValue); + SetOnAllGuis("fraction", curValue / countStart); + SetOnAllGuis("counting", countingDown); +} + +void hhConsoleCountdown::Think() { + hhConsole::Think(); + + if (thinkFlags & TH_MISC3) { + if (countingDown) { + UpdateGUI(countdown.GetCurrentValue(gameLocal.time)); + if (countdown.IsDone(gameLocal.time)) { + BecomeInactive(TH_MISC3); + ActivateTargets(this); + StopCountdown(); + } + } + } +} + +void hhConsoleCountdown::SetCountdown(int count) { + if (countingDown) { + gameLocal.Warning("You must stop countdown before set"); + return; + } + countStart = count; + countdown.Init(gameLocal.time, 0, countStart, countStart); + UpdateGUI(count); +} + +void hhConsoleCountdown::StartCountdown() { + if (!countingDown) { + // Start counting down from current value + float curValue = countdown.GetCurrentValue(gameLocal.time); + // resume from previously stopped count + countdown.Init(gameLocal.time, SEC2MS(countStart), curValue, countEnd); + BecomeActive(TH_MISC3); + countingDown = true; + } +} + +void hhConsoleCountdown::StopCountdown() { + if (countingDown) { + // Stop countdown at current value + float curValue = countdown.GetCurrentValue(gameLocal.time); + countdown.Init(gameLocal.time, 0, curValue, curValue); + countingDown = false; + UpdateGUI(countdown.GetCurrentValue(gameLocal.time)); + } +} + +void hhConsoleCountdown::Reset() { + // Reset to initial start time (or set start time) + countdown.Init(gameLocal.time, 0, countStart, countStart); + UpdateGUI(countdown.GetCurrentValue(gameLocal.time)); +} + +void hhConsoleCountdown::Event_Activate(idEntity *activator) { + StartCountdown(); +} +void hhConsoleCountdown::Event_SetCountdown(int count) { + SetCountdown(count); +} +void hhConsoleCountdown::Event_StartCountdown() { + StartCountdown(); +} +void hhConsoleCountdown::Event_StopCountdown() { + StopCountdown(); +} + + +//========================================================================== +// +// hhConsoleKeypad +// +// Generic console for entering text on a keypad and displaying it +//========================================================================== + +CLASS_DECLARATION(hhConsole, hhConsoleKeypad) +END_CLASS + +void hhConsoleKeypad::Spawn() { + maxLength = spawnArgs.GetInt("maxlength"); + + SetOnAllGuis("atmax", false); +} + +void hhConsoleKeypad::Save(idSaveGame *savefile) const { + savefile->WriteString(keyBuffer); + savefile->WriteInt(maxLength); +} + +void hhConsoleKeypad::Restore( idRestoreGame *savefile ) { + savefile->ReadString(keyBuffer); + savefile->ReadInt(maxLength); +} + +void hhConsoleKeypad::UpdateGui() { + int length = keyBuffer.Length(); + + // Enforce maxlength + if (length > maxLength) { + keyBuffer = keyBuffer.Left(maxLength); + } + + SetOnAllGuis("atmax", (length >= maxLength)); + SetOnAllGuis("keypad", keyBuffer.c_str()); +} + +bool hhConsoleKeypad::HandleSingleGuiCommand(idEntity *entityGui, idLexer *src) { + idToken token; + if (!src->ReadToken(&token) || token == ";") { + return false; + } + if (token.Icmp("backspace") == 0) { + if (keyBuffer.Length() > 0) { + keyBuffer = keyBuffer.Left(keyBuffer.Length()-1); + } + } + else if (token.Icmp("clear") == 0) { + keyBuffer.Empty(); + } + else if (token.Icmp("enter") == 0) { + keyBuffer += '\n'; + } + else if (token.IcmpPrefix("keypad_") == 0) { // KEYPAD_X + token.ToLower(); + token.Strip("keypad_"); + keyBuffer += token.c_str(); + } + else { + return false; + } + + UpdateGui(); + return true; +} + + +//========================================================================== +// +// hhConsoleAlarm +// +// alarm console +//========================================================================== + +const idEventDef EV_SpawnMonster(""); + +CLASS_DECLARATION(hhConsole, hhConsoleAlarm) + EVENT( EV_SpawnMonster, hhConsoleAlarm::Event_SpawnMonster ) +END_CLASS + +void hhConsoleAlarm::Spawn() { + bAlarmActive = false; + numMonsters = 0; + bSpawning = false; + maxMonsters = spawnArgs.GetInt( "max_monsters", "0" ); + BecomeActive(TH_TICKER); +} + +void hhConsoleAlarm::Save(idSaveGame *savefile) const { + savefile->WriteBool(bAlarmActive); + savefile->WriteInt( numMonsters ); + savefile->WriteInt( maxMonsters ); + currentMonster.Save( savefile ); + savefile->WriteBool( bSpawning ); +} + +void hhConsoleAlarm::Restore( idRestoreGame *savefile ) { + savefile->ReadBool(bAlarmActive); + savefile->ReadInt( numMonsters ); + savefile->ReadInt( maxMonsters ); + currentMonster.Restore( savefile ); + savefile->ReadBool( bSpawning ); +} + +void hhConsoleAlarm::ConsoleActivated() { + bAlarmActive = !bAlarmActive; + if ( !bAlarmActive ) { + CancelEvents( &EV_SpawnMonster ); + for ( int i=0;iIsType( hhMountedGun::Type ) ) { + targets[i]->PostEventMS( &EV_Deactivate, 0 ); + } + targets[i]->DeactivateTargetsType( hhMountedGun::Type ); + } + } + } +} + +void hhConsoleAlarm::Event_SpawnMonster() { + idDict args; + // Copy keys for monster + idStr tmpStr, realKeyName; + const idKeyValue *kv = spawnArgs.MatchPrefix("ent_", NULL); + while(kv) { + tmpStr = kv->GetKey(); + int usIndex = tmpStr.FindChar("ent_", '_'); + realKeyName = tmpStr.Mid(usIndex+1, strlen(kv->GetKey())-usIndex-1); + args.Set(realKeyName, kv->GetValue()); + kv = spawnArgs.MatchPrefix("ent_", kv); + } + idEntity *ent = gameLocal.FindEntity( spawnArgs.GetString( "spawn_location" ) ); + if ( ent ) { + args.SetVector( "origin", ent->GetOrigin() ); + } else { + gameLocal.Warning("No spawn_location specified for %s\n", GetName()); + return; + } + + // entity collision checks for seeing if we are going to collide with another entity on spawn + const idDict *entDef = gameLocal.FindEntityDefDict( spawnArgs.GetString( "def_monster" ), false ); + if ( entDef ) { + bool clipped = false; + idVec3 entSize; + idClipModel *cm; + idClipModel *clipModels[ MAX_GENTITIES ]; + entDef->GetVector( "size", "0", entSize ); + idBounds bounds = idBounds( ent->GetOrigin() ).Expand( max( entSize.x, entSize.y ) ); + int num = gameLocal.clip.ClipModelsTouchingBounds( bounds, MASK_MONSTERSOLID, clipModels, MAX_GENTITIES ); + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugBounds( colorRed, bounds, vec3_origin, 5000 ); + } + for ( int i=0;iIsRenderModel() ) { + continue; + } + idEntity *hit = cm->GetEntity(); + if ( ( hit == this ) || !hit->fl.takedamage || !hit->IsType( idActor::Type ) ) { + continue; + } + clipped = true; + break; + } + if ( !clipped ) { + currentMonster = gameLocal.SpawnObject(spawnArgs.GetString( "def_monster" ), &args); + if ( currentMonster.IsValid() ) { + currentMonster->PostEventMS( &EV_Activate, 0, this ); + bSpawning = false; + numMonsters++; + } else { + gameLocal.Warning( "%s could not spawn monster\n", GetName() ); + } + } + } +} + +void hhConsoleAlarm::Ticker() { + + if (bAlarmActive) { + if ( currentMonster.IsValid() && currentMonster->GetHealth() <= 0 ) { + currentMonster.Clear(); + } + + //check on our monster + if ( !bSpawning && !currentMonster.IsValid() && spawnArgs.GetString( "def_monster" )[0] ) { + if ( !maxMonsters || numMonsters < maxMonsters ) { + float min = spawnArgs.GetFloat( "mindelay", "2.0" ); + float max = spawnArgs.GetFloat( "maxdelay", "2.0" ); + if ( max < min ) { + max = min; + } + bSpawning = true; + PostEventSec( &EV_SpawnMonster, min + (max-min)*gameLocal.random.RandomFloat() ); + } + } + } + + hhConsole::Ticker(); +} \ No newline at end of file diff --git a/src/Prey/game_console.h b/src/Prey/game_console.h new file mode 100644 index 0000000..4ca9442 --- /dev/null +++ b/src/Prey/game_console.h @@ -0,0 +1,148 @@ + +#ifndef __GAME_CONSOLE_H__ +#define __GAME_CONSOLE_H__ + +extern const idEventDef EV_CallGuiEvent; + +enum ETranslationState { + TS_TRANSLATING, + TS_TRANSLATED, + TS_UNTRANSLATING, + TS_UNTRANSLATED +}; + +class hhTalonTarget; // CJR + +class hhConsole : public idStaticEntity { +public: + CLASS_PROTOTYPE( hhConsole ); + + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + virtual void Think(); + virtual void ConsoleActivated(); + virtual void ClearTalonTargetType(); // CJR - Called when any command is sent to a gui. This disables talon's squawking + + virtual void Present( void ); + virtual bool HandleSingleGuiCommand(idEntity *entityGui, idLexer *src); + virtual void Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location); + void Translate(bool bLanded); + void SetOnAllGuis(const char *key, float value); + void SetOnAllGuis(const char *key, int value); + void SetOnAllGuis(const char *key, bool value); + void SetOnAllGuis(const char *key, const char *value); + virtual void PlayerControls(usercmd_t *cmd) {} + + virtual void Use(idAI *ai); // JRM - An AI is starting to use this console + bool CanUse(idAI *ai); // JRM - Returns TRUE if the given AI is allowed to use this console right now + int GetLastUsedTime(void) {return aiLastUsedTime;} + idAI* GetLastUsedAI(void) {return aiLastUsedBy.GetEntity();} + virtual void OnTriggeredByAI(idAI *ai); // JRM - Called when the AI actually "presses" the console + void UpdateUse(void); // Called every tick + +protected: + void Event_Activate(idEntity *activator); + void Event_TalonAction(idEntity *talon, bool landed); + void Event_CallGuiEvent(const char *eventName); + void Event_BecomeNonSolid( void ); + void Event_PostSpawn(); + +protected: + idInterpolate translationAlpha; + ETranslationState transState; + bool bTimeEventsAlways; + bool bUsesRand; + + hhTalonTarget *perchSpot; // Associated Talon perch spot with this console + + // JRM - AI data + bool aiCanUse; // True if the AI is allowed to use this console + int aiUseCount; // Number of times AI has used this console + int aiMaxUses; // Max number of times the ai can use this console + int aiReuseWaitTime; // Time the AI has to wait before they can use this console again (ms) + int aiUseTime; // How long once AI starts using this console, do they stay here? (total ms) + int aiTriggerWaitTime; // How long once AI starts using this console until we fire this console's trigger? (ms) + bool aiWaitingToTrigger; // TRUE if the ai is waiting to fire the triggers for this console + int aiLastTriggerTime; // The time we last triggered this console + int aiRetriggerWait; // -1 = Never retrigger, otherwise time (ms) that we should retrigger while using + + idEntityPtr aiCurrUsedBy; // The AI is currently being using this console + int aiCurrUsedStartTime; // The time the current user STARTED using this console + + idEntityPtr aiLastUsedBy; // The AI that last used this console + int aiLastUsedTime; // The time that aiLastUsedBy used finished using this console + +}; + +class hhConsoleCountdown : public hhConsole { + CLASS_PROTOTYPE( hhConsoleCountdown ); + +public: + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think(); + + void Reset(); + void UpdateGUI(float curValue); + void SetGuiOctal(int value); + void SetCountdown(int count); + void StartCountdown(); + void StopCountdown(); + +protected: + void Event_Activate(idEntity *activator); + void Event_SetCountdown(int count); + void Event_StartCountdown(); + void Event_StopCountdown(); + +protected: + bool countingDown; // Whether we are running or not + float countStart; // Starting value of countdown + float countEnd; // Ending value of countdown + idInterpolate countdown; // Interpolator that always carries the current count +}; + + +class hhConsoleKeypad : public hhConsole { + CLASS_PROTOTYPE( hhConsoleKeypad ); + +public: + + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + bool HandleSingleGuiCommand(idEntity *entityGui, idLexer *src); + +protected: + void UpdateGui(); + +protected: + idStr keyBuffer; + int maxLength; +}; + + +class hhConsoleAlarm : public hhConsole { + CLASS_PROTOTYPE( hhConsoleAlarm ); + +public: + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + virtual void ConsoleActivated(); + virtual void Ticker(); +protected: + void Event_SpawnMonster(); + idEntityPtr currentMonster; + int maxMonsters; + int numMonsters; + bool bSpawning; +private: + bool bAlarmActive; +}; + + +#endif /* __GAME_CONSOLE_H__ */ diff --git a/src/Prey/game_damagetester.cpp b/src/Prey/game_damagetester.cpp new file mode 100644 index 0000000..38f2f9d --- /dev/null +++ b/src/Prey/game_damagetester.cpp @@ -0,0 +1,180 @@ +//************************************************************************** +//** +//** GAME_DAMAGETESTER.CPP +//** +//** +//** This object is used to calculate the damage done to it over time +//** When initially damaged, it calculates the amount of damage done for a +//** given amount of time +//************************************************************************** + +// HEADER FILES ------------------------------------------------------------ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" +#include "game_damagetester.h" + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +// MACROS ------------------------------------------------------------------ + +// TYPES ------------------------------------------------------------------- + +// CLASS DECLARATIONS ------------------------------------------------------ + +const idEventDef EV_ResetTarget( "", NULL ); +const idEventDef EV_CheckRemove( "", NULL ); + +CLASS_DECLARATION( hhAnimatedEntity, hhDamageTester ) + EVENT( EV_ResetTarget, hhDamageTester::Event_ResetTarget ) + EVENT( EV_CheckRemove, hhDamageTester::Event_CheckRemove ) +END_CLASS + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// hhDamageTester::Spawn +// +//========================================================================== + +void hhDamageTester::Spawn(void) { + fl.takedamage = true; + gameLocal.Printf("\"Name\", \"Damage (Melee)\", \"Damage (Close)\", \"Damage (Medium)\", \"Damage (Far)\", \ + \"Hits (Melee)\", \"Hits (Close)\", \"Hits (Medium)\", \"Hits (Far)\"\n"); + + testTime = spawnArgs.GetFloat( "testTime", "5" ); + if( testTime < 1 ) { + testTime = 1; + } + + // Get the distances and reset values + for( int i = 0; i < DD_MAX; i++ ) { + distance[i] = spawnArgs.GetVector( va("distance%d", i), "0 0 0" ); + totalDamage[i] = 0; + hitCount[i] = 0; + } + + // Get the name of the weapon (useful for outputting to an Excel-friendly format) + weaponName = spawnArgs.GetString( "weaponName", "None" ); + + // Store original location and axis + originalLocation = GetPhysics()->GetOrigin(); + originalAxis = GetPhysics()->GetAxis(); + + // Initialize the target + targetIndex = -1; + PostEventMS( &EV_ResetTarget, 0 ); + + GetPhysics()->SetContents( CONTENTS_SOLID ); +} + +//========================================================================== +// +// hhDamageTester::~hhDamageTester +// +//========================================================================== + +hhDamageTester::~hhDamageTester() { + int i; + + gameLocal.Printf( "\nDAMAGE TEST RESULTS:\n" ); + + // Weapon Name + gameLocal.Printf( "\"%s\"", weaponName); + + // Damage/Sec for each distance + for( i = 0; i < DD_MAX; i++ ) { + gameLocal.Printf(", %.1f", totalDamage[i] / testTime ); + } + + // Hits/Sec for each distance + for( i = 0; i < DD_MAX; i++ ) { + gameLocal.Printf(", %.1f", hitCount[i] / testTime ); + } + + gameLocal.Printf("\n\n"); +} + +//========================================================================== +// +// hhDamageTester::Damage +// +//========================================================================== + +void hhDamageTester::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + if( targetIndex < 0 || targetIndex >= DD_MAX ) { + return; + } + + if( bUndamaged ) { // First time this object has been hit + bUndamaged = false; + PostEventSec( &EV_ResetTarget, testTime ); + } + + // Obtain damage + const idDeclEntityDef *damageDef = gameLocal.FindEntityDef( damageDefName, false ); + if ( !damageDef ) { + gameLocal.Warning( "hhDamageTester::Damage: Unknown damageDef '%s'", damageDefName ); + return; + } + + int damage = damageDef->dict.GetInt( "damage", "0" ); + + totalDamage[targetIndex] += damage * damageScale; + hitCount[targetIndex]++; + + return; +} + +//========================================================================== +// +// hhDamageTester::Event_ResetTarget +// +//========================================================================== + +void hhDamageTester::Event_ResetTarget( void ) { + // Reset the target to undamaged + bUndamaged = true; + health = 999999; // Ensure that it will never die + + targetIndex++; + if( targetIndex >= DD_MAX ) { + PostEventMS( &EV_Remove, 0 ); + return; + } + + // Set the location of the target + GetPhysics()->SetOrigin( originalLocation + distance[targetIndex] ); + UpdateVisuals(); + + PostEventSec( &EV_CheckRemove, testTime ); // Checks to autoremove the entity after a certain time +} + +//========================================================================== +// +// hhDamageTester::Event_CheckRemove +// +// Resets the target if a certain period of inactivity has occured (only if +// the weapon was unable to hit the target, for instance the wrench) +// Only happens if the target has not been hit +//========================================================================== + +void hhDamageTester::Event_CheckRemove( void ) { + if( bUndamaged && targetIndex > 0 ) { + PostEventMS( &EV_ResetTarget, 0 ); + } +} + +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/src/Prey/game_damagetester.h b/src/Prey/game_damagetester.h new file mode 100644 index 0000000..16df836 --- /dev/null +++ b/src/Prey/game_damagetester.h @@ -0,0 +1,39 @@ +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +#ifndef __GAME_DAMAGETESTER_H__ +#define __GAME_DAMAGETESTER_H__ + +typedef enum hhDamageDist_s { + DD_MELEE, + DD_CLOSE, + DD_MEDIUM, + DD_FAR, + DD_MAX +} hhDamageDist_t; + +class hhDamageTester : public hhAnimatedEntity { +public: + CLASS_PROTOTYPE( hhDamageTester ); + + ~hhDamageTester(); + void Spawn(void); + void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + void Event_ResetTarget( void ); + void Event_CheckRemove( void ); + +protected: + + bool bUndamaged; + int targetIndex; + float testTime; + + idVec3 originalLocation; + idMat3 originalAxis; + + int totalDamage[DD_MAX]; + int hitCount[DD_MAX]; + idVec3 distance[DD_MAX]; // Relatives distances for each target location (relative to axis) + const char *weaponName; +}; + +#endif /* __GAME_DAMAGETESTER_H__ */ +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/src/Prey/game_dda.cpp b/src/Prey/game_dda.cpp new file mode 100644 index 0000000..feaabcb --- /dev/null +++ b/src/Prey/game_dda.cpp @@ -0,0 +1,681 @@ +/* +=============================================================================== +game_dda.cpp + + This contains the generic functionality for the dynamic difficulty adjustment system, + as well as a statistic tracking system. +=============================================================================== +*/ + +// HEADER FILES --------------------------------------------------------------- + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +//============================================================================= +// +// hhDDAManager::hhDDAManager +// +//============================================================================= + +hhDDAManager::hhDDAManager() { + bForcedDifficulty = false; + difficulty = 0.5f; + + ClearTracking(); +} + +//============================================================================= +// +// hhDDAManager::~hhDDAManager +// +//============================================================================= + +hhDDAManager::~hhDDAManager() { + ClearTracking(); +} + +//============================================================================= +// +// hhDDAManager::Save +// +//============================================================================= + +void hhDDAManager::Save(idSaveGame *savefile) const { + int i; + int num; + + savefile->WriteBool( bForcedDifficulty ); + savefile->WriteFloat( difficulty ); + + for ( i = 0; i < DDA_INDEX_MAX; i++ ) { + ddaProbability[i].Save( savefile ); + } + + savefile->WriteStringList( locationNameData ); + savefile->WriteStringList( locationData ); + + num = healthData.Num(); + savefile->WriteInt( num ); + for( i = 0; i < num; i++ ) { + savefile->WriteInt( healthData[i] ); + } + + savefile->WriteStringList( healthSpiritData ); + savefile->WriteStringList( ammoData ); + savefile->WriteStringList( miscData ); + + num = deathData.Num(); + savefile->WriteInt( num ); + for( i = 0; i < num; i++ ) { + savefile->WriteString( deathData[i].location ); + savefile->WriteInt( deathData[i].time ); + } +} + +//============================================================================= +// +// hhDDAManager::Restore +// +//============================================================================= + +void hhDDAManager::Restore( idRestoreGame *savefile ) { + int i; + int num; + + savefile->ReadBool( bForcedDifficulty ); + savefile->ReadFloat( difficulty ); + + for ( i = 0; i < DDA_INDEX_MAX; i++ ) { + ddaProbability[i].Restore( savefile ); + } + + savefile->ReadStringList( locationNameData ); + savefile->ReadStringList( locationData ); + + savefile->ReadInt( num ); + healthData.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadInt( healthData[i] ); + } + + savefile->ReadStringList( healthSpiritData ); + savefile->ReadStringList( ammoData ); + savefile->ReadStringList( miscData ); + + savefile->ReadInt( num ); + deathData.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadString( deathData[i].location ); + savefile->ReadInt( deathData[i].time ); + } +} + +//============================================================================= +// +// hhDDAManager::ClearTracking +// +//============================================================================= + +void hhDDAManager::ClearTracking() { + locationNameData.Clear(); + locationData.Clear(); + healthData.Clear(); + healthSpiritData.Clear(); + ammoData.Clear(); + miscData.Clear(); +} + +//============================================================================= +// +// hhDDAManager::GetDifficulty +// +//============================================================================= + +float hhDDAManager::GetDifficulty() { + return difficulty; +} + +//============================================================================= +// +// hhDDAManager::RecalculateDifficulty +// +// Recalculate overall difficulty from the individual creature difficulties +// +// Must be done after any damaged are added or deaths are recorded +//============================================================================= + +void hhDDAManager::RecalculateDifficulty( int updateFlags ) { + int count = 0; + float accum = 0.0f; + + float oldDiff = difficulty; // TEMP + + if ( g_wicked.GetBool() ) { // Wicked mode, force the DDA to 1.0 + difficulty = 1.0f; + return; + } + + if ( !g_useDDA.GetBool() ) { // Skip the DDA calculations. After g_wicked so that is still a valid mode + difficulty = 0.5f; + return; + } + + if ( bForcedDifficulty ) { // Don't recalculate if the difficulty is forced + return; + } + + for( int i = 0; i < DDA_INDEX_MAX; i++ ) { + if ( !ddaProbability[i].IsUsed() ) { // Ignore any difficulties that shouldn't count yet + continue; + } + + if ( (1 << i ) & updateFlags ) { // Only update the difficulty based upon the creatures currently attacking + accum += ddaProbability[i].GetIndividualDifficulty(); + count++; + } + } + + if ( count == 0 ) { // No individual difficulties, so return the default + difficulty = 0.5f; + } else { + difficulty = accum / count; + } + + if ( count && g_printDDA.GetBool() ) { + common->Printf( "difficulty: [%.2f]\n", difficulty ); + } +} + +//============================================================================= +// +// hhDDAManager::ForceDifficulty +// +// Force the difficulty to a given value -- used for debugging and Wicked mode +//============================================================================= + +void hhDDAManager::ForceDifficulty( float newDifficulty ) { + if ( newDifficulty > 1.0f ) { // Clamp the max + newDifficulty = 1.0f; + } else if ( newDifficulty < 0.0f ) { // negative difficulty values resets the difficulty + common->Printf( "Difficulty defaulted back to 0.5\n" ); + difficulty = 0.5f; + bForcedDifficulty = false; + return; + } + + bForcedDifficulty = true; + difficulty = newDifficulty; +} + +//============================================================================= +// +// hhDDAManager::DDA_Heartbeat +// +//============================================================================= + +void hhDDAManager::DDA_Heartbeat( hhPlayer* player ) { + idStr locText; + assert( player ); + + if ( !g_trackDDA.GetBool() || gameLocal.isMultiplayer ) { + return; + } + + ammo_t ammoType; + ammoType = idWeapon::GetAmmoNumForName( "ammo_rifle" ); + int ammo_rifle_count = player->inventory.ammo[ ammoType ]; + float ammo_rifle = 100 * player->inventory.AmmoPercentage( player, ammoType ); + + ammoType = idWeapon::GetAmmoNumForName( "ammo_sniper" ); + int ammo_sniper_count = player->inventory.ammo[ ammoType ]; + float ammo_sniper = 100 * player->inventory.AmmoPercentage( player, ammoType ); + + ammoType = idWeapon::GetAmmoNumForName( "ammo_crawler" ); + int ammo_crawler_count = player->inventory.ammo[ ammoType ]; + float ammo_crawler = 100 * player->inventory.AmmoPercentage( player, ammoType ); + + ammoType = idWeapon::GetAmmoNumForName( "ammo_autocannon" ); + int ammo_autocannon_count = player->inventory.ammo[ ammoType ]; + float ammo_autocannon = 100 * player->inventory.AmmoPercentage( player, ammoType ); + + ammoType = idWeapon::GetAmmoNumForName( "ammo_autocannon_grenade" ); + int ammo_autocannon_grenade_count = player->inventory.ammo[ ammoType ]; + float ammo_autocannon_grenade = 100 * player->inventory.AmmoPercentage( player, ammoType ); + + ammoType = idWeapon::GetAmmoNumForName( "ammo_acid" ); + int ammo_acid_count = player->inventory.ammo[ ammoType ]; + float ammo_acid = 100 * player->inventory.AmmoPercentage( player, ammoType ); + + ammoType = idWeapon::GetAmmoNumForName( "ammo_crawler_red" ); + int ammo_crawler_red_count = player->inventory.ammo[ ammoType ]; + float ammo_crawler_red = 100 * player->inventory.AmmoPercentage( player, ammoType ); + + ammoType = idWeapon::GetAmmoNumForName( "ammo_energy" ); + int ammo_energy_count = player->inventory.ammo[ ammoType ]; + float ammo_energy = 100 * player->inventory.AmmoPercentage( player, ammoType ); + + + idStr location; + player->GetLocationText( location ); + + idVec3 playerOrigin = player->GetOrigin(); + + bool bTalonAttack = false; + if ( player->talon.IsValid() && player->talon->IsAttacking() ) { + bTalonAttack = true; + } + + locationNameData.Append( location ); + + locationData.Append( idStr( va( "%.2f %.2f %.2f", playerOrigin.x, playerOrigin.y, playerOrigin.z ) ) ); + + healthData.Append( player->GetHealth() ); + + healthSpiritData.Append( idStr( va( "%d, %d, %.2f, %.2f", player->GetHealth(), player->GetSpiritPower(), player->ddaProbabilityAccum, gameLocal.GetDDA()->GetDifficulty() ) ) ); + + ammoData.Append( idStr( va( "%d, %d, %d, %d, %d, %d, %d, %d, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f", + ammo_rifle_count, ammo_sniper_count, ammo_crawler_count, ammo_autocannon_count, ammo_autocannon_grenade_count, ammo_acid_count, ammo_crawler_red_count, ammo_energy_count, // Ammo totals + ammo_rifle, ammo_sniper, ammo_crawler, ammo_autocannon, ammo_autocannon_grenade, ammo_acid, ammo_crawler_red, ammo_energy ) ) ); // Ammo percentages + + miscData.Append( idStr( va( "%d, %d, %d, %d, %d", player->GetCurrentWeapon(), player->IsLighterOn(), bTalonAttack, player->IsSpiritOrDeathwalking(), player->ddaNumEnemies ) ) ); // currentWeapon, lighter, talon attacking, IsSpiritOrDeathwalking, numEnemies +} + +//============================================================================= +// +// hhDDAManager::DDA_AddDamage +// +//============================================================================= + +void hhDDAManager::DDA_AddDamage( int ddaIndex, int damage ) { + if ( ddaIndex < 0 || ddaIndex >= DDA_INDEX_MAX ) { + common->Warning( "hhDDAManager::DDA_AddDamage: ddaIndex out of range %d [%d]\n", ddaIndex, DDA_INDEX_MAX ); + ddaIndex = 0; + } + + ddaProbability[ddaIndex].AddDamage( damage * 2 ); +} + +//============================================================================= +// +// hhDDAManager::DDA_AddSurvivalHealth +// +//============================================================================= + +void hhDDAManager::DDA_AddSurvivalHealth( int ddaIndex, int health ) { + if ( ddaIndex < 0 || ddaIndex >= DDA_INDEX_MAX ) { + common->Warning( "hhDDAManager::DDA_AddSurvivalHealth: ddaIndex out of range %d [%d]\n", ddaIndex, DDA_INDEX_MAX ); + ddaIndex = 0; + } + + ddaProbability[ddaIndex].AddSurvivalValue( health ); + + // Adjust DDA test + float prob = DDA_GetProbability( ddaIndex, health ); + float probAdjust = ( prob - 0.5f ) * 0.05f; // Difficulty adjustment raises and lowers based upon how far the probability is from the dda level + + ddaProbability[ddaIndex].AdjustDifficulty( probAdjust ); +} + +//============================================================================= +// +// hhDDAManager::DDA_AddDeath +// +//============================================================================= + +void hhDDAManager::DDA_AddDeath( hhPlayer *player, idEntity *attacker ) { + hhMonsterAI *monster; + int ddaIndex; + + if ( g_trackDDA.GetBool() && !gameLocal.isMultiplayer ) { + ddaDeath_t death; + death.time = gameLocal.GetTime(); + player->GetLocationText( death.location ); + deathData.Append( death ); + } + + if ( !attacker->IsType( hhMonsterAI::Type ) ) { + return; + } + + monster = static_cast(attacker); + + if ( monster ) { + ddaIndex = monster->spawnArgs.GetInt( "ddaIndex", "0" ); + + if ( ddaIndex < 0 ) { // Player could be killed by monsters that shouldn't be included in the DDA + return; + } + + if ( ddaIndex >= DDA_INDEX_MAX ) { + ddaIndex = 0; + } + } + + ddaProbability[ddaIndex].AdjustDifficulty( -0.075f ); +} + +//============================================================================= +// +// hhDDAManager::DDA_GetProbability +// +// Return the probability that this index of creature will do enough damage to reduce the player's health to zero +//============================================================================= + +float hhDDAManager::DDA_GetProbability( int ddaIndex, int value ) { + if ( ddaIndex < 0 || ddaIndex >= DDA_INDEX_MAX ) { + common->Warning( "hhDDAManager::DDA_GetProbability: ddaIndex out of range %d [%d]\n", ddaIndex, DDA_INDEX_MAX ); + ddaIndex = 0; + } + + return ddaProbability[ddaIndex].GetProbability( value ); +} + +//============================================================================= +// +// hhDDAManager::Export +// +//============================================================================= + +void hhDDAManager::Export( const char* filename ) { + idStr ExportFile, ExportBase; + + if( !filename ) { + ExportBase = gameLocal.GetMapName(); + ExportBase.StripPath(); + ExportBase.StripFileExtension(); + ExportBase = va("%s_%s", cvarSystem->GetCVarString("win_username"), ExportBase.c_str() ); + ExportBase.DefaultPath( "statfiles/" ); + } + else { + ExportBase = va( "%s_", filename ); + ExportBase.StripFileExtension(); + ExportBase.DefaultPath( "statfiles/" ); + } + + gameLocal.Printf( "Exporting stats to csv files\n" ); + + // Export to .cvs files and to a .lin file + ExportDDAData( ExportBase, "Location Name, Health, Spirit Power, Survival Chance, DDA", "_HEALTH", healthSpiritData ); + + ExportDDAData( ExportBase, + "Location Name, Ammo Rifle, Ammo Sniper, Ammo Crawler, Ammo Autocannon, Ammo Grenade, Ammo Acid, Ammo Launcher, Ammo Energy, Ammo Rifle %, Ammo Sniper %, Ammo Crawler %, Ammo Autocannon %, Ammo Grenade %, Ammo Acid %, Ammo Launcher %, Ammo Energy %", + "_AMMO", ammoData ); + + ExportDDAData( ExportBase, "Location Name, Current Weapon, Lighter On, Talon Attacking, IsSpiritOrDeathwalking, Num Enemies", "_MISC", miscData ); + + ExportLINData( ExportBase ); +} + +//============================================================================= +// +// hhDDAManager::ExportDDAData +// +//============================================================================= + +void hhDDAManager::ExportDDAData( idStr fileName, const char* header, const char *fileAddition, idList data ) { + idFile* f = NULL; + + //Build the filename + fileName.Append( fileAddition ); + + fileName.DefaultFileExtension("csv"); + f = fileSystem->OpenFileWrite( fileName.c_str() ); + if( !f ) { + common->Warning( "Failed to open stat tracking file '%s'", fileName.c_str() ); + return; + } + + //Export the header for this file... + f->Printf( "%s\n", header ); + + for( int i = 0; i < data.Num(); i++ ) { + f->Printf( "%s, %s\n", locationNameData[i].c_str(), data[i].c_str() ); + } + + //Close the output file + fileSystem->CloseFile( f ); +} + +//============================================================================= +// +// hhDDAManager::ExportLINData +// +//============================================================================= + +void hhDDAManager::ExportLINData( idStr fileName ) { + idFile* f = NULL; + + //Build the filename + fileName.DefaultFileExtension("lin"); + f = fileSystem->OpenFileWrite( fileName.c_str() ); + if( !f ) { + common->Warning( "Failed to open stat tracking file '%s'", fileName.c_str() ); + return; + } + + for( int i = 0; i < locationData.Num(); i++ ) { + if ( healthData[i] > 0 ) { // Only store living health values in the .lin file + f->Printf( "%s\n", locationData[i].c_str() ); + } + } + + //Close the output file + fileSystem->CloseFile( f ); +} + +//============================================================================= +// +// hhDDAManager::PrintDDA +// +//============================================================================= + +void hhDDAManager::PrintDDA( void ) { + int i; + hhDDAProbability *prob; + + for( i = 0; i < DDA_INDEX_MAX; i++ ) { + prob = &ddaProbability[i]; + if ( !prob->IsUsed() ) { + continue; + } + + common->Printf( "DDA Index: %d\n", i ); + common->Printf( "Ind. Difficulty: %.2f\n", prob->GetIndividualDifficulty() ); +/* + common->Printf( "Last Damages: [%.1f] [%.1f] [%.1f] [%.1f] [%.1f] [%.1f] [%.1f] [%.1f]\n", + prob->damages[0], prob->damages[1], prob->damages[2], prob->damages[3], + prob->damages[4], prob->damages[5], prob->damages[6], prob->damages[7] ); +*/ + } +} + +//============================================================================= +// NEW DDA +//============================================================================= + +//============================================================================= +// +// hhDDAProbability::hhDDAProbability +// +//============================================================================= + +hhDDAProbability::hhDDAProbability() { + damageRover = 0; + survivalRover = 0; + + mean = 25.0f; + stdDeviation = 1.0f; + bUsed = false; + individualDifficulty = 0.5f; + + for( int i = 0; i < NUM_DAMAGES; i++ ) { + damages[i] = mean; // Initialize to a low, but reasonable value + survivalValues[i] = 0.5f; // Initialize to a middle value + } +} + +//============================================================================= +// +// hhDDAProbability::Save +// +//============================================================================= + +void hhDDAProbability::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteBool( bUsed ); + + for ( i = 0; i < NUM_DAMAGES; i++ ) { + savefile->WriteInt( damages[i] ); + } + + savefile->WriteInt( damageRover ); + + for ( i = 0; i < NUM_DAMAGES; i++ ) { + savefile->WriteFloat( survivalValues[i] ); + } + + savefile->WriteInt( survivalRover ); + + savefile->WriteFloat( mean ); + savefile->WriteFloat( stdDeviation ); + savefile->WriteFloat( individualDifficulty ); +} + +//============================================================================= +// +// hhDDAProbability::Restore +// +//============================================================================= + +void hhDDAProbability::Restore( idRestoreGame *savefile ) { + int i; + + savefile->ReadBool( bUsed ); + + for ( i = 0; i < NUM_DAMAGES; i++ ) { + savefile->ReadInt( damages[i] ); + } + + savefile->ReadInt( damageRover ); + + for ( i = 0; i < NUM_DAMAGES; i++ ) { + savefile->ReadFloat( survivalValues[i] ); + } + + savefile->ReadInt( survivalRover ); + + savefile->ReadFloat( mean ); + savefile->ReadFloat( stdDeviation ); + savefile->ReadFloat( individualDifficulty ); +} + +//============================================================================= +// +// hhDDAProbability::AddDamage +// +//============================================================================= + +void hhDDAProbability::AddDamage( int damage ) { + // Add the damage to the list + damages[ damageRover ] = damage; + damageRover = ( damageRover + 1 ) % NUM_DAMAGES; + + bUsed = true; + + CalculateProbabilityCurve(); +} + +//============================================================================= +// +// hhDDAProbability::AddSurvivalValue +// +//============================================================================= + +void hhDDAProbability::AddSurvivalValue( int playerHealth ) { + // Get the survival probability against this creature and store it + survivalValues[ survivalRover ] = GetProbability( playerHealth ); + survivalRover = ( survivalRover + 1 ) % NUM_DAMAGES; +} + +//============================================================================= +// +// hhDDAProbability::GetProbability +// +//============================================================================= + +float hhDDAProbability::GetProbability( int value ) { + float z; + float x; + float probability = 0; + + if ( stdDeviation <= 0.0f ) { + stdDeviation = 1.0f; + } + + z = idMath::Fabs( value - mean ) / stdDeviation; + x = 1 + z * ( 0.049867347 + z * ( 0.0211410061 + z * ( 0.0032776263 ))); + probability = ( idMath::Exp( idMath::Log( x ) * -16 ) / 2 ); + if ( value >= mean ) { + probability = 1.0f - probability; + } + + return probability; +} + +//============================================================================= +// +// hhDDAProbability::CalculateProbabilityCurve +// +//============================================================================= + +void hhDDAProbability::CalculateProbabilityCurve() { + int i; + float diff; + float sumDiffSqr = 0; + + // Recompute mean and std deviation when new damage information is obtained + mean = 0; + for ( i = 0; i < NUM_DAMAGES; i++ ) { + mean += damages[i]; + } + mean /= NUM_DAMAGES; + + // calculate standard deviation + for ( i = 0; i < NUM_DAMAGES; i++ ) { + diff = mean - damages[i]; + sumDiffSqr += diff * diff; + } + + stdDeviation = idMath::Sqrt( sumDiffSqr / NUM_DAMAGES ); +} + +//============================================================================= +// +// hhDDAProbability::GetSurvivalMean +// +//============================================================================= + +float hhDDAProbability::GetSurvivalMean( void ) { + float sum = 0; + + for ( int i = 0; i < NUM_DAMAGES; i++ ) { + sum += survivalValues[i]; + } + + return sum / NUM_DAMAGES; +} + +//============================================================================= +// +// hhDDAProbability::AdjustDifficulty +// +//============================================================================= + +void hhDDAProbability::AdjustDifficulty( float diff ) { + individualDifficulty = idMath::ClampFloat( 0.0f, 1.0f, individualDifficulty + diff ); +} diff --git a/src/Prey/game_dda.h b/src/Prey/game_dda.h new file mode 100644 index 0000000..5c7abb7 --- /dev/null +++ b/src/Prey/game_dda.h @@ -0,0 +1,128 @@ +/* +=============================================================================== +game_dda.h + This contains the functionality for a dynamic difficulty adjustment system, + as well as a statistic tracking system. +=============================================================================== +*/ +#ifndef __GAME_DDA_H__ +#define __GAME_DDA_H__ + +// DDA_Export Methods. +enum hhDDAExport { + DDA_ExportTEXT, + DDA_ExportCSV, +}; + +typedef struct ddaDeath_s { + int time; + idStr location; +} ddaDeath_t; + +const int DDA_INDEX_MAX = 16; + +//====================================================================================== +//====================================================================================== +// +// NEW DDA SYSTEM +// +//====================================================================================== +//====================================================================================== + +#define NUM_DAMAGES 8 + +class hhDDAProbability { +public: + hhDDAProbability(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void AddDamage( int damage ); + void AddSurvivalValue( int playerHealth ); + + float GetProbability( int value ); + float GetSurvivalMean( void ); + + void AdjustDifficulty( float diff ); + float GetIndividualDifficulty( void ) { return individualDifficulty; } + + bool IsUsed( void ) { return bUsed; } + +protected: + + void CalculateProbabilityCurve(); + + bool bUsed; // True if this probability instance is used (so the damage probability of later creatures doesn't influence earlier levels) + + int damages[NUM_DAMAGES]; + int damageRover; + + float survivalValues[NUM_DAMAGES]; + int survivalRover; + + float mean; + float stdDeviation; + + float individualDifficulty; +}; + +/*********************************************************************** + hhDDAManager. + Class that controls the DDA tracking and difficulty of the game. +***********************************************************************/ +class hhDDAManager { +public: + hhDDAManager(); + ~hhDDAManager(); +public: + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //Public difficulty functions/accessors.. + float GetDifficulty(); //Called to get current difficulty rating (0.0 - 1.0) + void DifficultySummary(); + void ForceDifficulty( float newDifficulty ); // Force the difficulty to this value + + void RecalculateDifficulty( int updateFlags ); + + //Public DDA Notifications. (these are called to update the DDA system about activities happening in the game) + void DDA_Heartbeat( hhPlayer* player ); + + void DDA_AddDamage( int ddaIndex, int damage ); + void DDA_AddSurvivalHealth( int ddaIndex, int health ); + void DDA_AddDeath( hhPlayer *player, idEntity *attacker ); + + float DDA_GetProbability( int ddaIndex, int value ); + + //Public DDA Exporting functions. These are called to export DDA stats to either the console or a file. + void Export( const char* filename ); + + void PrintDDA( void ); + + //Public DDA Controls. These are used to clear/reset the DDA system (on map-switching). + void ClearTracking(); + +protected: + //Protected Internal functions for exporting purposes. + void ExportDDAData( idStr fileName, const char* header, const char *fileAddition, idList data ); + void ExportLINData( idStr FileName ); + + //Protected Interal Difficulty 'Ticker'. Difficulty system will re-evaluate whether to change diff here. + //o void DifficultyUpdate(); + + //Protected Internal Difficulty data. + bool bForcedDifficulty; // Used for testing, and Wicked mode + float difficulty; // Our current difficulty rating. + hhDDAProbability ddaProbability[DDA_INDEX_MAX]; // CJR: array of probabilites used in the DDA system + + idList locationNameData; + idList locationData; + idList healthData; + idList healthSpiritData; + idList ammoData; + idList miscData; + idList deathData; +}; + +#endif // __GAME_DDA_H__ \ No newline at end of file diff --git a/src/Prey/game_deathwraith.cpp b/src/Prey/game_deathwraith.cpp new file mode 100644 index 0000000..de82b70 --- /dev/null +++ b/src/Prey/game_deathwraith.cpp @@ -0,0 +1,559 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +#define DEBUGGING_WRAITHS 0 + +//============================================================================= +// hhDeathWraith +//============================================================================= + +CLASS_DECLARATION( hhWraith, hhDeathWraith ) + EVENT( AI_FindEnemy, hhWraith::Event_FindEnemy ) +END_CLASS + + +//============================================================================= +// +// hhDeatWraith::Spawn +// +//============================================================================= + +void hhDeathWraith::Spawn() { + // Each wraith is assigned a random distance, rotation speed, and height around the enemy + idVec3 minCircleInfo = spawnArgs.GetVector( "min_circleInfo", "100 10 100" ); // dist, speed, height + idVec3 maxCircleInfo = spawnArgs.GetVector( "max_circleInfo", "100 10 100" ); // dist, speed, height + + circleDist = minCircleInfo[0] + gameLocal.random.RandomFloat() * ( maxCircleInfo[0] - minCircleInfo[0] ); + circleSpeed = minCircleInfo[1] + gameLocal.random.RandomFloat() * ( maxCircleInfo[1] - minCircleInfo[1] ); + circleHeight = minCircleInfo[2] + gameLocal.random.RandomFloat() * ( maxCircleInfo[2] - minCircleInfo[2] ); + + circleClockwise = (gameLocal.random.RandomFloat() > 0.5f ? true : false); + + // Compute the random attack time (in frames) + attackTimeMin = spawnArgs.GetFloat( "min_attackTime", "4" ) * 60; + attackTimeDelta = ( spawnArgs.GetFloat( "max_attackTime", "8" ) * 60 ) - attackTimeMin; + + if ( state == WS_FLY ) { + // The wraith is now circling the enemy - set the attack time + countDownTimer = attackTimeMin + gameLocal.random.RandomInt( attackTimeDelta ); + } + + // Determine the wraith type + healthWraith = (gameLocal.random.RandomFloat() < spawnArgs.GetFloat( "healthTypeChance", "0.35" ) ? true : false); +} + +//============================================================================= +// +// hhDeathWraith::FlyUp +// +//============================================================================= +void hhDeathWraith::FlyUp() { + hhWraith::FlyUp(); + + if ( state == WS_FLY ) { + // The wraith is now circling the enemy - set the attack time + countDownTimer = attackTimeMin + gameLocal.random.RandomInt( attackTimeDelta ); + } +} + +//============================================================================= +// +// hhDeathWraith::FlyToEnemy +// +// Circle an enemy +//============================================================================= +void hhDeathWraith::FlyToEnemy() { + float distAdjust; + idVec3 origin; + float delta; + bool dir; + +#if DEBUGGING_WRAITHS +gameRenderWorld->DebugLine(colorWhite, GetOrigin(), GetOrigin()+idVec3(0,0,5), 5000); +#endif + + // Look for an enemy for the Wraith + // FIXME: This needs to be done often, because the player could spiritwalk after the Wraith has him as the enemy + // and we want the wraiths to try to target the player's proxy. + // We could either have this check via a heartbeat, or have some type of broadcast message when the player spiritwalks (?) + Event_FindEnemy( false ); + + // Ensure that the wraith is on the correct path, and if not, then set the wraith's velocity and angle to return to the path + origin = GetOrigin(); + idVec3 toEnemy = enemy->GetOrigin() - origin; + toEnemy.z = 0; + float distance = toEnemy.Normalize(); + + idVec3 tangent( -toEnemy.y, toEnemy.x, 0 ); // Quick tangent, swap x and y + + // If counter-clockwise, then reverse the tangent vector + if ( !circleClockwise ) { + tangent *= -1; + } + + // Determine if the radius is too large and gradually pull the Wraith in closer or if the wraith is too close, push it out + if ( distance > circleDist + circleSpeed ) { // Move closer + distAdjust = circleSpeed * 0.1f; + } else if ( distance < circleDist - circleSpeed ) { // Move farther away + distAdjust = -circleSpeed * 0.1f; + } else { // No adjustment + distAdjust = 0; + } + + // Decide if the wraith should change z-height + idVec3 zVelocity( 0, 0, 0 ); + + if ( origin.z < enemy->GetOrigin().z + circleHeight ) { + zVelocity.z = 2.0f; + } else if ( origin.z > enemy->GetOrigin().z + circleHeight + 30.0f ) { + zVelocity.z = -2.0f; + } + + // Move the wraith through the world + idVec3 velocity = tangent * circleSpeed + toEnemy * distAdjust + zVelocity; + + // Rotate towards velocity direction + dir = GetFacePosAngle( GetOrigin() + velocity, delta ); + + if ( delta > this->turn_threshold ) { // Wraith should turn + if ( dir ) { // Turn to the left + deltaViewAngles.yaw = this->turn_radius_max * (60.0f * USERCMD_ONE_OVER_HZ); + } else { + deltaViewAngles.yaw = -this->turn_radius_max * (60.0f * USERCMD_ONE_OVER_HZ); + } + } + + SetOrigin( GetOrigin() + (velocity * (60.0f * USERCMD_ONE_OVER_HZ)) ); + + // Decide if the wraith has waited long enough and then attack + if ( --countDownTimer <= 0 ) { + if ( health > 0 && enemy.IsValid() ) { // Only attack if the wraith has an enemy (which it should at all times) and the wraith hasn't been killed + EnterAttackState(); + } else { // Reset the attack time + countDownTimer = attackTimeMin + gameLocal.random.RandomInt( attackTimeDelta ); + } + } +} + +//============================================================================= +// +// hhDeathWraith::Damage +// +//============================================================================= +void hhDeathWraith::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + // Skip wraiths brighten when damaged logic + hhMonsterAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} + +//============================================================================= +// +// hhDeathWraith::Killed +// +// When a deathwraith is killed, it immediately is removed +// The queue is reset and a new wraith will be spawned shortly after +//============================================================================= +void hhDeathWraith::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + hhFxInfo fxInfo; + + // Stop flyloop and chatter sounds, if any + StopSound( SND_CHANNEL_BODY ); + StopSound( SND_CHANNEL_VOICE ); + + SetShaderParm( SHADERPARM_TIMEOFFSET, MS2SEC( gameLocal.time ) ); + SetShaderParm( SHADERPARM_DIVERSITY, 1.0f ); + + const char *deathEffect = spawnArgs.GetString("fx_deatheffect1"); + if (attacker && attacker->IsType(hhPlayer::Type)) { + hhPlayer *player = static_cast( attacker ); + if ( healthWraith ) { + deathEffect = spawnArgs.GetString("fx_deatheffect2"); + } + } + + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfoAlongBone( deathEffect, spawnArgs.GetString( "joint_collision" ), &fxInfo ); + + physicsObj.SetContents(0); + fl.takedamage = false; + UnlinkCombat(); + + StartSound( "snd_death", SND_CHANNEL_VOICE ); + + NotifyDeath( inflictor, attacker ); + + PostEventSec( &EV_Remove, spawnArgs.GetFloat( "burnOutTime" ) ); +} + +//============================================================================= +// +// hhDeathWraith::NotifyDeath +// +// Handles notification of player on death +//============================================================================= +void hhDeathWraith::NotifyDeath( idEntity *inflictor, idEntity *attacker ) { + + if ( attacker && attacker->IsType( hhPlayer::Type ) ) { + hhPlayer *player = static_cast( attacker ); + + SpawnEnergy(player); + + if ( player->IsDeathWalking() ) { + player->KilledDeathWraith(); + } + } +} + +//============================================================================= +// +// hhDeathWraith::SpawnEnergy +// +// Energy travels from wraith to deathWalkProxy +//============================================================================= +void hhDeathWraith::SpawnEnergy(hhPlayer *player) { + idEntity *destEntity = NULL; + const char *deathEnergyName = NULL; + idVec3 origin; + idMat3 axis; + idDict args; + + if ( healthWraith ) { + // Spawn health energy + deathEnergyName = spawnArgs.GetString("def_deathEnergyHealth", NULL); + } + else { + // Spawn spirit power energy + deathEnergyName = spawnArgs.GetString("def_deathEnergySpirit", NULL); + } + destEntity = player->GetDeathwalkEnergyDestination(); + + // Spawn the energy system that will deliver the spiritpower to the player + if (deathEnergyName && destEntity) { + idVec3 center = GetPhysics()->GetAbsBounds().GetCenter(); + args.Clear(); + args.Set("origin", center.ToString()); + idEntity *ent = gameLocal.SpawnObject(deathEnergyName, &args); + if (ent && ent->IsType(hhDeathWraithEnergy::Type)) { + hhDeathWraithEnergy *energy = static_cast(ent); + energy->SetPlayer(player); + energy->SetDestination( destEntity->GetOrigin() ); + } + } +} + +//============================================================================= +// +// hhDeathWraith::Think +// +//============================================================================= +void hhDeathWraith::Think( void ) { + + if ( enemy.IsValid() ) { + assert( enemy->IsType( hhPlayer::Type ) ); + hhPlayer *player = reinterpret_cast ( enemy.GetEntity() ); + + if( !player->IsDeathWalking() ) { + BecomeInactive( TH_THINK ); + PostEventMS( &EV_Remove, 0 ); + return; + } + else { + if ( healthWraith ) { + SetShaderParm(SHADERPARM_MODE, 1.0f); + } + else { + SetShaderParm(SHADERPARM_MODE, 0.0f); + } + } + } + + // Don't call hhWraith::Think(), since we don't want our health to drop over time + hhMonsterAI::Think(); + + // Since idAI is only capable of yawing, not piching, we post apply our pitch + if (state == WS_DEATH_CHARGE && enemy.IsValid()) { + idVec3 toEnemy = enemy->GetOrigin() - GetOrigin(); + toEnemy.Normalize(); + viewAxis = toEnemy.ToMat3(); + } + +#if DEBUGGING_WRAITHS + gameRenderWorld->DebugLine(colorRed, GetOrigin(), GetOrigin()+viewAxis[0]*20, 1000); + gameRenderWorld->DebugLine(colorGreen, GetOrigin(), GetOrigin()+viewAxis[1]*20, 1000); + gameRenderWorld->DebugLine(colorBlue, GetOrigin(), GetOrigin()+viewAxis[2]*20, 1000); +#endif +} + +//============================================================================= +// +// hhDeathWraith::FlyMove +// +//============================================================================= +void hhDeathWraith::FlyMove( void ) { + idMat3 axis; + idVec3 delta; + idVec3 toEnemy; + + // The state of the creature determines how it will move + switch ( state ) { + case WS_SPAWN: + FlyUp(); // Flying up right after spawn + break; + case WS_FLY: + FlyToEnemy(); + break; + case WS_FLEE: // Flying away + FlyAway(); + break; + case WS_DEATH_CHARGE: // Charging an enemy + +#if 1 + // Tranform animation movement by actual 'pitched' axis + if (enemy.IsValid()) { + toEnemy = enemy->GetOrigin() - GetOrigin(); + toEnemy.Normalize(); + axis = toEnemy.ToMat3(); + } else { // HUMANHEAD mdl: Enemy might not be valid if player is just leaving deathwalk + axis = viewAxis; + } +#else + axis = viewAxis; +#endif + + if (ChargeEnemy()) { + // Using animation delta to generate movement + physicsObj.UseFlyMove( false ); + physicsObj.UseVelocityMove( false ); + GetMoveDelta( axis, axis, delta ); + physicsObj.SetDelta( delta ); + physicsObj.ForceDeltaMove( true ); + RunPhysics(); + } + return; + case WS_STILL: // Wraith is not moving at all (playing a specific anim, etc) + return; + } + + // run the physics for this frame + physicsObj.UseFlyMove( true ); + physicsObj.UseVelocityMove( false ); + physicsObj.SetDelta( vec3_zero ); + physicsObj.ForceDeltaMove( disableGravity ); + RunPhysics(); +} + + +//============================================================================= +// +// hhDeathWraith::EnterAttackState +// +//============================================================================= +void hhDeathWraith::EnterAttackState() { + state = WS_DEATH_CHARGE; + + // Set the wraith's rotation target towards the enemy + TurnTowardEnemy(); + + SetShaderParm( SHADERPARM_MISC, 1 ); // Charging + StartSound( "snd_charge", SND_CHANNEL_VOICE2 ); + + countDownTimer = PlayAnim( "alert", 2 ) / USERCMD_MSEC; +} + +//============================================================================= +// +// hhDeathWraith::ChargeEnemy +// +//============================================================================= +bool hhDeathWraith::ChargeEnemy( void ) { + + if ( !enemy.IsValid() ) { + ExitAttackState(); + return false; + } + + assert(enemy.IsValid() && enemy->IsType(hhPlayer::Type)); + + // Set the wraith's rotation target towards the enemy + TurnTowardEnemy(); + + // Attempt to attack the enemy + // If the wraith did not damage the enemy, then continue the attack flight + if ( !CheckEnemy() ) { + // Once the animation is finished (and the wraith wasn't killed or didn't hit the player), + // then transition to circle mode which will handle getting the wraith back to the path + if ( --countDownTimer <= 0 ) { + ExitAttackState(); + return false; + } + } + return true; +} + +//============================================================================= +// +// hhDeathWraith::ExitAttackState +// +//============================================================================= +void hhDeathWraith::ExitAttackState() { + state = WS_FLY; + PlayCycle( "fly", 2 ); + countDownTimer = attackTimeMin + gameLocal.random.RandomInt( attackTimeDelta ); + SetShaderParm( SHADERPARM_MISC, 0 ); // No longer charging + + GetPhysics()->SetLinearVelocity(vec3_origin); +} + +//============================================================================= +// +// hhDeathWraith::CheckEnemy +// +//============================================================================= +bool hhDeathWraith::CheckEnemy( void ) { + + // Don't try to damage if the wraith doesn't have a target, or if the wraith is dead or harvested + if ( health <= 0 || !enemy.IsValid() || fl.takedamage == false ) { + return false; + } + + idVec3 center = GetPhysics()->GetAbsBounds().GetCenter(); + idVec3 dir = enemy->GetPhysics()->GetAbsBounds().GetCenter() - center; + + if ( dir.LengthSqr() < spawnArgs.GetFloat( "minDamageDistSqr", "2500" ) ) { + HitEnemy(); + return true; + } + + return false; +} + +//============================================================================= +// +// hhDeathWraith::HitEnemy +// +//============================================================================= +void hhDeathWraith::HitEnemy( void ) { + hhFxInfo fxInfo; + + if (IsHidden()) { + return; + } + + // Activate the death trigger + idEntityPtr deathTrigger = gameLocal.FindEntity( spawnArgs.GetString( "triggerOnHit" ) ); + if ( deathTrigger.IsValid() ) { + if ( deathTrigger->RespondsTo( EV_Activate ) || deathTrigger->HasSignal( SIG_TRIGGER ) ) { + deathTrigger->Signal( SIG_TRIGGER ); + deathTrigger->ProcessEvent( &EV_Activate, this ); + } + } + + Hide(); + fl.takedamage = false; + + // Hit enemy effects -- for now, using the death effects + idVec3 center = GetPhysics()->GetAbsBounds().GetCenter(); + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfoPrefixed( "fx_hitenemy", center, mat3_identity, &fxInfo ); + StartSound( "snd_attack", SND_CHANNEL_VOICE ); + + ApplyImpulseToEnemy(); + + if (enemy->IsType(hhPlayer::Type)) { + hhPlayer *player = static_cast(enemy.GetEntity()); + + if ( healthWraith ) { + int playerHealth = player->GetHealth(); + int minHealth = player->spawnArgs.GetInt( "minResurrectHealth", "50" ); + + playerHealth -= spawnArgs.GetInt( "playerHealthDamage", "10" ); + if ( playerHealth < minHealth ) { + playerHealth = minHealth; + } + player->SetHealth( playerHealth ); + } else { + player->DeathWalkDamagedByWraith( this, spawnArgs.GetString( "def_damagetype" ) ); + } + } + + PostEventMS( &EV_Remove, 2000 ); +} + +//============================================================================= +// +// hhDeathWraith::ApplyImpulseToEnemy +// +//============================================================================= +void hhDeathWraith::ApplyImpulseToEnemy() { + if( enemy.IsValid() ) { + idMat3 axis = GetAxis(); + idVec3 impulseDir = axis[0] - axis[2]; + impulseDir.Normalize(); + enemy->ApplyImpulse( this, 0, enemy->GetOrigin(), impulseDir * spawnArgs.GetFloat("impulseMagnitude") * enemy->GetPhysics()->GetMass() ); + } +} + +//============================================================================= +// +// hhDeathWraith::EnemyDead +// +//============================================================================= +void hhDeathWraith::EnemyDead() { + return; // Death wraiths expect their enemies to be dead. +} + +//============================================================================= +// +// hhDeathWraith::PlayCycle +// +//============================================================================= +void hhDeathWraith::PlayCycle( const char *name, int blendFrame ) { + GetAnimator()->ClearAllAnims( gameLocal.GetTime(), FRAME2MS(blendFrame) ); + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, GetAnimator()->GetAnim(name), gameLocal.GetTime(), FRAME2MS(blendFrame) ); +} + +//============================================================================= +// +// hhDeathWraith::PlayAnim +// +//============================================================================= +int hhDeathWraith::PlayAnim( const char *name, int blendFrame ) { + int fadeTime = FRAME2MS( blendFrame ); + int animLength = 0; + + GetAnimator()->ClearAllAnims( gameLocal.GetTime(), FRAME2MS(blendFrame) ); + + int anim = GetAnimator()->GetAnim( name ); + if ( anim ) { + GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, anim, gameLocal.GetTime(), fadeTime ); + animLength = GetAnimator()->CurrentAnim( ANIMCHANNEL_ALL )->Length(); + animLength = (animLength > fadeTime) ? animLength - fadeTime : animLength; + } + + return animLength; +} + +//============================================================================= +// +// hhDeathWraith::Event_FindEnemy +// +//============================================================================= +void hhDeathWraith::Event_FindEnemy( int useFOV ) { + // These are meant for single player deathwalk only + idPlayer *player = gameLocal.GetLocalPlayer(); + SetEnemy( player ); + idThread::ReturnEntity( player ); +} + +//============================================================================= +// +// hhDeathWraith::TeleportIn +// +//============================================================================= + +void hhDeathWraith::TeleportIn( idEntity *activator ) { + Show(); + PostEventMS( &EV_Activate, 0, activator ); +} diff --git a/src/Prey/game_deathwraith.h b/src/Prey/game_deathwraith.h new file mode 100644 index 0000000..51763c9 --- /dev/null +++ b/src/Prey/game_deathwraith.h @@ -0,0 +1,48 @@ +#ifndef __GAME_DEATHWRAITH_H__ +#define __GAME_DEATHWRAITH_H__ + +class hhDeathWraith : public hhWraith { + public: + CLASS_PROTOTYPE( hhDeathWraith ); + + void Spawn(); + virtual void FlyToEnemy(); + virtual void SetEnemy( idActor *newEnemy ) { enemy = newEnemy; } + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void Think( void ); + + protected: + virtual int PlayAnim( const char *name, int blendFrame ); + virtual void PlayCycle( const char *name, int blendFrame ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void NotifyDeath( idEntity *inflictor, idEntity *attacker ); + void SpawnEnergy(hhPlayer *player); + void EnterAttackState(); + void ExitAttackState(); + virtual bool ChargeEnemy(); + bool CheckEnemy(); + void HitEnemy(); + void ApplyImpulseToEnemy(); + virtual void FlyUp(); + virtual void FlyMove( void ); + virtual void EnemyDead(); + void Event_FindEnemy( int useFOV ); + void TeleportIn( idEntity *activator ); + + protected: + // Variables for controlling the movement of the wraith around the anchor point + // These variables are unique for each wraith for variety + float circleDist; // Distance to stay from the anchor point + float circleSpeed; // Orbit speed + float circleHeight; // Height above the point + bool circleClockwise; // if true, orbit clockwise + + int attackTimeMin; // Range for the attack time + int attackTimeDelta; // Range for the attack time + + idMat3 chargeAxis; + + bool healthWraith; // true if a health type +}; + +#endif \ No newline at end of file diff --git a/src/Prey/game_debrisspawner.cpp b/src/Prey/game_debrisspawner.cpp new file mode 100644 index 0000000..eb0d9ba --- /dev/null +++ b/src/Prey/game_debrisspawner.cpp @@ -0,0 +1,439 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_RemoveAll( "removeAll", NULL ); + +CLASS_DECLARATION( idEntity, hhDebrisSpawner ) + EVENT( EV_Activate, hhDebrisSpawner::Activate ) +END_CLASS + +/* +============ +hhDebrisSpawner::hhDebrisSpawner() +============ +*/ +hhDebrisSpawner::hhDebrisSpawner() { +} + + +/* +============ +hhDebrisSpawner::~hhDebrisSpawner() +============ +*/ +hhDebrisSpawner::~hhDebrisSpawner() { +} + + +/* +============ +hhDebrisSpawner::Spawn() + Spawns the entity, and all other entities associated with the mass +============ +*/ +void hhDebrisSpawner::Spawn() { + + // Set the defaults + activated = false; + sourceEntity = NULL; + hasBounds = false; + + // Get the passed in + spawnArgs.GetVector( "origin", "0 0 0", origin ); + spawnArgs.GetVector( "orientation", "1 0 0", orientation ); + spawnArgs.GetVector( "velocity", "0 0 0", velocity ); + spawnArgs.GetVector( "power", "50 50 50", power ); + spawnArgs.GetFloat( "duration", "0", duration ); + spawnArgs.GetBool( "multi_trigger", "0", multiActivate ); + spawnArgs.GetBool( "spawnUsingEntity", "0", useEntity ); + spawnArgs.GetBool( "fillBounds", "1", fillBounds ); + spawnArgs.GetBool( "testBounds", "1", testBounds ); + spawnArgs.GetBool( "nonsolid", "0", nonSolid ); + spawnArgs.GetBool( "useAFBounds", "0", useAFBounds ); //rww + + // Hide the model + GetPhysics()->SetContents( 0 ); + Hide(); + bounds = GetPhysics()->GetClipModel()->GetBounds(); + GetPhysics()->DisableClip(); + + // Do stuff + power *= 20; + + // spawnWhenActivated means that + if ( !useEntity && !spawnArgs.GetInt( "trigger", "0" ) ) { + Activate( NULL ); + } +} + +void hhDebrisSpawner::Save(idSaveGame *savefile) const { + savefile->WriteVec3(origin); + savefile->WriteVec3(orientation); + savefile->WriteVec3(velocity); + savefile->WriteVec3(power); + savefile->WriteBool(activated); + savefile->WriteBool(multiActivate); + savefile->WriteBool(hasBounds); + savefile->WriteBounds(bounds); + savefile->WriteFloat(duration); + savefile->WriteBool(fillBounds); + savefile->WriteBool(testBounds); + savefile->WriteObject(sourceEntity); +} + +void hhDebrisSpawner::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3(origin); + savefile->ReadVec3(orientation); + savefile->ReadVec3(velocity); + savefile->ReadVec3(power); + savefile->ReadBool(activated); + savefile->ReadBool(multiActivate); + savefile->ReadBool(hasBounds); + savefile->ReadBounds(bounds); + savefile->ReadFloat(duration); + savefile->ReadBool(fillBounds); + savefile->ReadBool(testBounds); + savefile->ReadObject( reinterpret_cast(sourceEntity) ); + + spawnArgs.GetBool( "spawnUsingEntity", "0", useEntity ); + spawnArgs.GetBool( "nonsolid", "0", nonSolid ); + spawnArgs.GetBool( "useAFBounds", "0", useAFBounds ); +} + + +/* +================ +hhDebrisSpawner::Activate + Do the actual spawning of debris, FX, etc.. +================ +*/ +void hhDebrisSpawner::Activate( idEntity *aSourceEntity ) { + + if ( useEntity ) { + sourceEntity = aSourceEntity; + } + else { // If we don't have an entity, use our bounds instead + hasBounds = true; + } + + if ( !activated || multiActivate ) { + SpawnDebris(); + + SpawnFX(); + SpawnDecals(); + + // If we have a fixed duration, remove ourselves after they are removed + if ( ( duration > 0.0f ) && !multiActivate ) { + PostEventSec( &EV_Remove, duration + 1.0f ); + } + + activated = true; + } +} + + +/* +============ +hhDebrisSpawner::SpawnDebris +============ +*/ +void hhDebrisSpawner::SpawnDebris() { + const idKeyValue * kv = NULL; + idEntity * debris; + idDict args; + idStr debrisEntity; + idList debrisEntities; + int numDebris; + // For entity sources + idBounds sourceBounds; + idVec3 sourceBoundCenter; + idVec3 debrisOrigin; + idBounds searchBounds; + idVec3 debrisVelocity; + bool fit; + idBounds afBounds; //rww + bool gotAFBounds = false; //rww + idVec3 defOrigin = vec3_zero; + idAngles defAngles = ang_zero; + + // Optimization: if player is far away, don't spawn the chunks + if (!gameLocal.isMultiplayer) { + float maxDistance = spawnArgs.GetFloat("max_debris_distance"); + if (maxDistance > 0.0f) { + float distSquared = (gameLocal.GetLocalPlayer()->GetOrigin() - origin).LengthSqr(); + if (distSquared > maxDistance*maxDistance) { + return; + } + } + } + args.SetInt( "nodrop", 1 ); + args.SetVector( "origin", origin ); + args.SetFloat( "duration", duration ); + + // Pass along variabes requested + hhUtils::PassArgs( spawnArgs, args, "pass_" ); + + if (useAFBounds && sourceEntity && sourceEntity->IsType(idActor::Type)) { //rww - try for AF bounds + idActor *actor = static_cast(sourceEntity); + if (actor->IsActiveAF()) { //they are ragdolling, so we have a chance. + idPhysics_AF *afPhys = actor->GetAFPhysics(); + + if (afPhys) { //got the af physics object, now loop through the bodies and collect an appropriate bounds. + afBounds.Zero(); + + for (int bodyCount = 0; bodyCount < afPhys->GetNumBodies(); bodyCount++) { + idAFBody *b = afPhys->GetBody(bodyCount); + if (b) { + idClipModel *bodyClip = b->GetClipModel(); + if (bodyClip) { //now add the bounds of the clipModel into afBounds + idBounds bodyBounds = bodyClip->GetBounds(); + idVec3 point = (b->GetWorldOrigin()-origin); + + bodyBounds.AddPoint(point); + afBounds.AddBounds(bodyBounds); + gotAFBounds = true; + } + } + } + } + } + } + + // Find all Debris + while ( GetNextDebrisData( debrisEntities, numDebris, kv, defOrigin, defAngles ) ) { + int numEntities = debrisEntities.Num(); + for ( int count = 0; count < numDebris; ++count ) { + // Select a debris to use from the list + debrisEntity = debrisEntities[ gameLocal.random.RandomInt( numEntities ) ]; + + // If we have a bounding box to fill, use that + if ( fillBounds && ( sourceEntity || hasBounds ) ) { + if (gotAFBounds) { //rww + sourceBounds = afBounds; + //gameRenderWorld->DebugBounds(colorGreen, afBounds, origin, 1000); + } + else if ( sourceEntity ) { + sourceBounds = sourceEntity->GetPhysics()->GetBounds(); + } + else { + sourceBounds = bounds; + } + + fit = false; + for ( int i = 0; i < 4; i++ ) { + if ( i == 0 && defOrigin != vec3_zero ) { + //first try bone origin without random offset + debrisOrigin = defOrigin; + } else { + debrisOrigin = origin + hhUtils::RandomPointInBounds( sourceBounds ); + } + + if ( !testBounds || hhUtils::EntityDefWillFit( debrisEntity, debrisOrigin, defAngles.ToMat3(), CONTENTS_SOLID, NULL ) ) { + fit = true; + break; // Found a spot for the gib + } + } + + if ( !fit ) { // Gib didn't fit after 4 attempts, so don't spawn the gib + //common->Warning( "SpawnDebris: gib didn't fit when spawned (%s)\n", debrisEntity.c_str() ); + defAngles = ang_zero; + continue; + } + + args.SetVector( "origin", debrisOrigin ); + if ( sourceEntity ) { + idMat3 sourceAxis = idAngles( 0, sourceEntity->GetAxis().ToAngles().yaw, 0 ).ToMat3(); + args.SetMatrix( "rotation", defAngles.ToMat3() * sourceAxis ); + } else { + args.SetMatrix( "rotation", defAngles.ToMat3() ); + } + } + + + if ( duration ) { + args.SetFloat( "removeTime", duration + duration * gameLocal.random.CRandomFloat() ); + } + + // Spawn the object + debris = gameLocal.SpawnObject( debrisEntity, &args ); + HH_ASSERT(debris != NULL); // JRM - Nick, I added this because of a crash I got. Hopefully this will catch it sooner + + // mdl: Added this check to make sure no AFs go through the debris spawner + if ( debris->IsType( idAFEntity_Base::Type ) ) { + gameLocal.Warning( "hhDebrisSpawner spawned an articulated entity: '%s'.", debrisEntity.c_str() ); + } + + if ( nonSolid ) { + + idVec3 origin; + if ( debris->GetFloorPos( 64.0f, origin ) ) { + debris->SetOrigin( origin ); // Start on floor, since we're not going to be moving at all + debris->RunPhysics(); // Make sure any associated effects get updated before we turn collision off + } + + // Turn off collision + debris->GetPhysics()->SetContents( 0 ); + debris->GetPhysics()->SetClipMask( 0 ); + debris->GetPhysics()->UnlinkClip(); + debris->GetPhysics()->PutToRest(); + + } else { + // Add in random velocity + idVec3 randVel( gameLocal.random.RandomInt( power.x ) - power.x / 2, + gameLocal.random.RandomInt( power.y ) - power.y / 2, + gameLocal.random.RandomInt( power.z ) - power.z / 2 ); + + // Set linear velocity + debrisVelocity.x = velocity.x * power.x * 0.25; + debrisVelocity.y = velocity.y * power.y * 0.25; + debrisVelocity.z = 0.0f; + debris->GetPhysics()->SetLinearVelocity( debrisVelocity + randVel ); + + // Add random angular velocity + idVec3 aVel; + aVel.x = spawnArgs.GetFloat( "ang_vel", "90.0" ) * gameLocal.random.CRandomFloat(); + aVel.y = spawnArgs.GetFloat( "ang_vel", "90.0" ) * gameLocal.random.CRandomFloat(); + aVel.z = spawnArgs.GetFloat( "ang_vel", "90.0" ) * gameLocal.random.CRandomFloat(); + + if ( defAngles == ang_zero ) { + debris->GetPhysics()->SetAxis( idVec3( 1, 0, 0 ).ToMat3() ); + } + debris->GetPhysics()->SetAngularVelocity( aVel ); + } + } + defAngles = ang_zero; + + // Zero out the list + debrisEntities.Clear(); + } + + return; +} + + +/* +============ +hhDebrisSpawner::SpawnFX +============ +*/ +void hhDebrisSpawner::SpawnFX() { + hhFxInfo fxInfo; + + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfoPrefixed( "fx", origin, orientation.ToMat3(), &fxInfo ); +} + + +//============================================================================= +// +// hhDebrisSpawner::SpawnDecals +// +//============================================================================= + +void hhDebrisSpawner::SpawnDecals( void ) { + const int DIR_COUNT = 5; + idVec3 dir[DIR_COUNT] = { ( 1, 0, 0 ), ( -1, 0, 0 ), ( 0, 0, -1 ), ( 0, 1, 0 ), ( 0, -1, 0) }; + + // do blood splats + float size = spawnArgs.GetFloat( "decal_size", "96" ); + + const idKeyValue* kv = spawnArgs.MatchPrefix( "mtr_decal" ); + while( kv ) { + if( kv->GetValue().Length() ) { + gameLocal.ProjectDecal( origin, dir[ gameLocal.random.RandomInt( DIR_COUNT ) ], 64.0f, true, size, kv->GetValue().c_str() ); + } + kv = spawnArgs.MatchPrefix( "mtr_decal", kv ); + } +} + +/* +============ +hhDebrisSpawner::GetNextDebrisData +============ +*/ +bool hhDebrisSpawner::GetNextDebrisData( idList &entityDefs, int &count, const idKeyValue * &kv, idVec3 &origin, idAngles &angle ) { + static char debrisDef[] = "def_debris"; + static char debrisKey[] = "debris"; + idStr indexStr; + idStr entityDefBase; + int numDebris, minDebris, maxDebris; + origin = vec3_zero; + + // Loop through looking for the next valid debris key + for ( kv = spawnArgs.MatchPrefix( debrisDef, kv ); + kv && kv->GetValue().Length(); + kv = spawnArgs.MatchPrefix( debrisDef, kv ) ) { + indexStr = kv->GetKey(); + indexStr.Strip( debrisDef ); + + // Is a valid debris base And it isn't a variation. (ie, contains a .X postfix) + if ( idStr::IsNumeric( indexStr ) && indexStr.Find( '.' ) < 0 ) { + + // Get Number of Debris + numDebris = -1; + + if ( sourceEntity && sourceEntity->IsType( idAI::Type ) ) { + idVec3 bonePos; + idMat3 boneAxis; + idStr bone = spawnArgs.GetString( va( "%s%s%s", debrisKey, "_bone", ( const char * ) indexStr ) ); + if( !bone.IsEmpty() ) { + static_cast(sourceEntity)->GetJointWorldTransform( bone.c_str(), bonePos, boneAxis ); + origin = bonePos; + angle = spawnArgs.GetAngles( va( "%s%s%s", debrisKey, "_angle", ( const char * ) indexStr ) ); + } + } + if ( !spawnArgs.GetInt( va( "%s%s%s", debrisKey, "_num", + ( const char * ) indexStr ), + "-1", numDebris ) || numDebris < 0 ) { + // No num found, look for min and max + if ( spawnArgs.GetInt( va( "%s%s%s", debrisKey, "_min", + ( const char * ) indexStr ), + "-1", minDebris ) && minDebris >= 0 && + spawnArgs.GetInt( va( "%s%s%s", debrisKey, "_max", + ( const char * ) indexStr ), + "-1", maxDebris ) && maxDebris >= 0 ) { + numDebris = + gameLocal.random.RandomInt( ( maxDebris - minDebris ) ) + minDebris; + } //. No min/max found + + } //. No num found + + // No valid num found + if ( numDebris < 0 ) { + gameLocal.Warning( "ERROR: No debris num could be be found for %s%s", + ( const char * ) debrisDef, + ( const char * ) indexStr ); + } + else { // Valid num found + const char * entityDefPrefix; + const idKeyValue * otherDefKey = NULL; + + entityDefBase = kv->GetValue(); + count = numDebris; + + entityDefs.Append( entityDefBase ); + + // Find the other defs that may be there. using .X scheme + entityDefPrefix = va( "%s.", (const char *) kv->GetKey() ); + + // gameLocal.Printf( "Looking for %s\n", entityDefPrefix ); + + for ( otherDefKey = spawnArgs.MatchPrefix( entityDefPrefix, otherDefKey ); + otherDefKey && otherDefKey->GetValue().Length(); + otherDefKey = spawnArgs.MatchPrefix( entityDefPrefix, otherDefKey ) ) { + // gameLocal.Printf( "Would have added %s\n", (const char *) otherDefKey->GetValue() ); + entityDefs.Append( otherDefKey->GetValue() ); + } + + return( true ); + } + + } //. Valid debris base + } //. debris loop + + + return( false ); +} + diff --git a/src/Prey/game_debrisspawner.h b/src/Prey/game_debrisspawner.h new file mode 100644 index 0000000..0aaec89 --- /dev/null +++ b/src/Prey/game_debrisspawner.h @@ -0,0 +1,47 @@ +#ifndef __PREY_GAME_DEBRISMASS_H__ +#define __PREY_GAME_DEBRISMASS_H__ + +class hhDebrisSpawner : public idEntity { + CLASS_PROTOTYPE( hhDebrisSpawner ); + + public: + hhDebrisSpawner(); + virtual ~hhDebrisSpawner(); + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void Activate( idEntity *sourceEntity ); + + ID_INLINE float GetDuration() const { return duration; } + + protected: + void Event_RemoveAll(); + + void SpawnDebris(); + void SpawnFX(); + void SpawnDecals( void ); + + bool GetNextDebrisData( idList &entityDefs, int &count, + const idKeyValue * &kv, idVec3 &origin, idAngles &angle ); +protected: + idVec3 origin; + idVec3 orientation; + idVec3 velocity; + idVec3 power; + bool activated; + bool multiActivate; + bool useEntity; // Use an entity to spawn in + bool useAFBounds; //rww - use collective AF bodies to produce an appropriate bounds for spawning. + bool hasBounds; + idBounds bounds; + // Vars for removing everything after a fixed period + float duration; + + bool fillBounds; // Try to fill in the bounds with the gibs. + bool testBounds; // test to see if debris fits in bounds + bool nonSolid; + + idEntity * sourceEntity; +}; + +#endif __PREY_GAME_DEBRISMASS_H__ diff --git a/src/Prey/game_dock.cpp b/src/Prey/game_dock.cpp new file mode 100644 index 0000000..4a959a9 --- /dev/null +++ b/src/Prey/game_dock.cpp @@ -0,0 +1,70 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +//----------------------------------------------------------------------- +// +// hhDock +// +//----------------------------------------------------------------------- +const idEventDef EV_DockLock("lockVehicleInDock", NULL); +const idEventDef EV_DockUnlock("unlockDock", NULL); + +ABSTRACT_DECLARATION(idEntity, hhDock) + EVENT( EV_DockLock, hhDock::Event_Lock) + EVENT( EV_DockUnlock, hhDock::Event_Unlock) + EVENT( EV_Activate, hhDock::Event_Activate) + EVENT( EV_PostSpawn, hhDock::Event_PostSpawn ) +END_CLASS + +void hhDock::Spawn() { + if ( !gameLocal.isClient ) { + PostEventMS(&EV_PostSpawn, 0); + } +} + +void hhDock::Event_PostSpawn() { + dockingZone = SpawnDockingZone(); +} + +void hhDock::Save(idSaveGame *savefile) const { + dockingZone.Save(savefile); +} + +void hhDock::Restore( idRestoreGame *savefile ) { + dockingZone.Restore(savefile); +} + +hhDockingZone *hhDock::SpawnDockingZone() { + hhDockingZone *zone; + idBounds localBounds; + + // Define our bounds + localBounds[0] = spawnArgs.GetVector("dockingzonemins"); + localBounds[1] = spawnArgs.GetVector("dockingzonemaxs"); + + // create a docking zone with this size + idDict args; + args.SetVector( "origin", GetOrigin() ); + args.SetVector( "mins", localBounds[0] ); + args.SetVector( "maxs", localBounds[1] ); + args.SetMatrix( "rotation", GetAxis() ); + zone = (hhDockingZone *)gameLocal.SpawnEntityType(hhDockingZone::Type, &args); + assert(zone); + zone->Bind(this, true); + zone->RegisterDock(this); + return zone; +} + +void hhDock::Event_Lock() { + Lock(); +} + +void hhDock::Event_Unlock() { + Unlock(); +} + +void hhDock::Event_Activate( idEntity *activator ) { + Unlock(); +} \ No newline at end of file diff --git a/src/Prey/game_dock.h b/src/Prey/game_dock.h new file mode 100644 index 0000000..197e739 --- /dev/null +++ b/src/Prey/game_dock.h @@ -0,0 +1,49 @@ +#ifndef __GAME_DOCK_H__ +#define __GAME_DOCK_H__ + +extern const idEventDef EV_DockLock; +extern const idEventDef EV_DockUnlock; + +class hhShuttle; +class hhDockingZone; + +class hhDock : public idEntity { +public: + ABSTRACT_PROTOTYPE( hhDock ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool ValidEntity(idEntity *ent) = 0; + virtual void EntityEntered( idEntity *ent ) = 0; + virtual void EntityEncroaching( idEntity *ent ) = 0; + virtual void EntityLeaving( idEntity *ent ) = 0; + virtual void ShuttleExit( hhShuttle *shuttle ) = 0; + virtual bool IsLocked() = 0; + virtual bool CanExitLocked() = 0; + virtual bool AllowsBoost() = 0; + virtual bool AllowsFiring() = 0; + virtual bool AllowsExit() = 0; + virtual bool IsTeleportDest() = 0; + virtual void UpdateAxis( const idMat3 &newAxis ) = 0; + + virtual bool Recharges() const { return false; } + +protected: + //rww - needs a delay for mp + virtual void Event_PostSpawn(); + + hhDockingZone* SpawnDockingZone(); + virtual void Lock()=0; + virtual void Unlock()=0; + + void Event_Lock(); + void Event_Unlock(); + void Event_Activate(idEntity *activator); + +protected: + idEntityPtr dockingZone; +}; + +#endif \ No newline at end of file diff --git a/src/Prey/game_dockedgun.cpp b/src/Prey/game_dockedgun.cpp new file mode 100644 index 0000000..925d7cc --- /dev/null +++ b/src/Prey/game_dockedgun.cpp @@ -0,0 +1,16 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +//========================================================================== +// +// hhDockedGun +// +//========================================================================== + +CLASS_DECLARATION(hhShuttle, hhDockedGun) +END_CLASS + +void hhDockedGun::Spawn() { +} diff --git a/src/Prey/game_dockedgun.h b/src/Prey/game_dockedgun.h new file mode 100644 index 0000000..7679826 --- /dev/null +++ b/src/Prey/game_dockedgun.h @@ -0,0 +1,19 @@ +#ifndef __GAME_DOCKEDGUN_H__ +#define __GAME_DOCKEDGUN_H__ + +//========================================================================== +// +// hhDockedGun +// +//========================================================================== + +class hhDockedGun : public hhShuttle { + CLASS_PROTOTYPE( hhDockedGun ); + +public: + void Spawn(); + +protected: +}; + +#endif diff --git a/src/Prey/game_door.cpp b/src/Prey/game_door.cpp new file mode 100644 index 0000000..a255211 --- /dev/null +++ b/src/Prey/game_door.cpp @@ -0,0 +1,586 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION( idDoor, hhDoor ) + EVENT( EV_Mover_ReturnToPos1, hhDoor::Event_ReturnToPos1 ) + EVENT( EV_ReachedPos, hhDoor::Event_Reached_BinaryMover ) + EVENT( EV_SetBuddiesShaderParm, hhDoor::Event_SetBuddiesShaderParm ) + EVENT( EV_Touch, hhDoor::Event_Touch ) +END_CLASS + +//-------------------------------- +// hhDoor::Spawn +//-------------------------------- +void hhDoor::Spawn() { + airlockMaster = NULL; + + airlockTeam.SetOwner( this ); + airlockTeamName = spawnArgs.GetString( "airlockTeam" ); + spawnArgs.GetFloat( "airlockwait", "3", airLockSndWait ); + airLockSndWait *= 1000.0f; // Convert from seconds to ms + + idEntity* master = DetermineTeamMaster( GetAirLockTeamName() ); + if( master ) { + airlockMaster = static_cast( master ); + if( airlockMaster != this ) { + JoinAirLockTeam( airlockMaster ); + } + } + + if( spawnArgs.GetBool("start_open") ) { + VerifyAirlockTeamStatus(); + } + + if( moveMaster != this ) { + CopyTeamInfoToMoveMaster( static_cast(moveMaster) ); + } + + // We only open when dead if we have health to start with + openWhenDead = health > 0; + forcedOpen = false; + nextAirLockSnd = 0; + + bShuttleDoors = spawnArgs.GetBool( "shuttle_doors" ); +} + +void hhDoor::Save(idSaveGame *savefile) const { + savefile->WriteString(airlockTeamName); + savefile->WriteObject(airlockMaster); + savefile->WriteBool(openWhenDead); + savefile->WriteBool(forcedOpen); + savefile->WriteFloat(airLockSndWait); +} + +void hhDoor::Restore( idRestoreGame *savefile ) { + savefile->ReadString(airlockTeamName); + savefile->ReadObject( reinterpret_cast(airlockMaster) ); + savefile->ReadBool(openWhenDead); + savefile->ReadBool(forcedOpen); + savefile->ReadFloat(airLockSndWait); + + airlockTeam.SetOwner( this ); + if( airlockMaster && airlockMaster != this ) { + JoinAirLockTeam( airlockMaster ); + } + + nextAirLockSnd = 0; + + bShuttleDoors = spawnArgs.GetBool( "shuttle_doors" ); +} + +//-------------------------------- +// hhDoor::~hhDoor +//-------------------------------- +hhDoor::~hhDoor() { + airlockTeam.Remove(); +} + +//-------------------------------- +// hhDoor::Killed +//-------------------------------- +void hhDoor::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + + // Only can really be killed if we can be damaged + if ( openWhenDead ) { + Open(); + + fl.takedamage = false; + forcedOpen = true; + } +} // Killed( idEntity *, idEntity *, int, const idVec3 &, int ) + +//-------------------------------- +// hhDoor::Use_BinaryMover +//-------------------------------- +void hhDoor::Use_BinaryMover( idEntity *activator ) { + if ( airlockMaster && activator && !activator->IsType( idPlayer::Type ) ) { + return; // Don't allow anyone but the player to affect airlock doors + } + + // only the master should be used + if ( moveMaster != this ) { + moveMaster->Use_BinaryMover( activator ); + return; + } + + if ( !enabled ) { + return; + } + + if ( moverState == MOVER_POS1 || moverState == MOVER_2TO1 ) { + GotoPosition2(); + } + else if ( moverState == MOVER_POS2 || moverState == MOVER_1TO2 ) { + GotoPosition1(); + } +} + +//-------------------------------- +// hhDoor::GotoPosition1 +// +// CloseDoor +//-------------------------------- +void hhDoor::GotoPosition1() { + GotoPosition1( spawnArgs.GetBool("toggle") ? 0.0f : wait ); +} + +//-------------------------------- +// hhDoor::GotoPosition1 +// +// CloseDoor +//-------------------------------- +void hhDoor::GotoPosition1( float wait ) { + idMover_Binary* slave = NULL; + int partial = 0; + + //HUMANHEAD: aob - airlock stuff + if( !CanClose() ) { + return; + } + //HUMNAHEAD END + + if ( moverState == MOVER_POS2 ) { + SetGuiStates( guiBinaryMoverStates[MOVER_2TO1] ); + + CancelReturnToPos1(); + + if( wait > 0 ) { + //HUMANHEAD: aob + PostEventSec( &EV_Mover_ReturnToPos1, wait ); + //HUMANHEAD END + } else { + ProcessEvent( &EV_Mover_ReturnToPos1 ); + } + } + + else + + // 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(); + MatchActivateTeam( MOVER_2TO1, physicsObj.GetTime() - partial ); + } +} + +//-------------------------------- +// hhDoor::GotoPosition2 +// +// OpenDoor +//-------------------------------- +void hhDoor::GotoPosition2() { + GotoPosition2( 0.0f ); +} + +//-------------------------------- +// hhDoor::GotoPosition2 +// +// OpenDoor +//-------------------------------- +void hhDoor::GotoPosition2( float wait ) { + int partial = 0; + hhDoor *airlockMaster = GetAirLockMaster(); + + //HUMANHEAD: aob - airlock stuff + if( !CanOpen() && airlockMaster ) { + ForceAirLockTeamClosed(); + if( gameLocal.time > nextAirLockSnd ) { + int length; + StartSound( "snd_airlock", SND_CHANNEL_BODY, 0, false, &length ); + nextAirLockSnd = gameLocal.time + length + airLockSndWait; + } + return; + } + //HUMNAHEAD END + + if ( moverState == MOVER_POS1 ) { + ActivatePrefixed("triggerStartOpen", GetActivator()); + + SetGuiStates( guiBinaryMoverStates[MOVER_1TO2] ); + + MatchActivateTeam( MOVER_1TO2, gameLocal.time ); + + // open areaportal + ProcessEvent( &EV_Mover_OpenPortal ); + } + else if ( moverState == MOVER_2TO1 ) { // only partway up before reversing + // use the physics times because this might be executed during the physics simulation + partial = physicsObj.GetLinearEndTime() - physicsObj.GetTime(); + MatchActivateTeam( MOVER_1TO2, physicsObj.GetTime() - partial ); + } + + if( airlockMaster ) { + // Mark the other team members as locked + for( hhDoor* node = airlockMaster->airlockTeam.ListHead()->Owner(); node != NULL; node = node->airlockTeam.Next() ) { + + // Skip this door and it's buddies + if( node == this || buddyNames.Find( node->name ) ) { + continue; + } + + // Don't change doors that are really locked + if( node->IsLocked() ) { + continue; + } + + // Mark as locked + node->SetBuddiesShaderParm( SHADERPARM_MISC, 1.0f ); + } + + // This prevents the our shader from being set as locked from the player moving too quickly between airlock doors + SetBuddiesShaderParm( SHADERPARM_MISC, 0.0f ); + } +} + +//-------------------------------- +// hhDoor::Open +//-------------------------------- +void hhDoor::Open( void ) { + GotoPosition2( wait ); +} + +//-------------------------------- +// hhDoor::Close +//-------------------------------- +void hhDoor::Close( void ) { + // HUMANHEAD nla + if ( ForcedOpen() ) { return; } + // HUMANHEAD + + GotoPosition1( 0.0f ); +} + +//-------------------------------- +// hhDoor::CanOpen +//-------------------------------- +bool hhDoor::CanOpen() const { + if( !GetAirLockMaster() ) { + return true; + } + + for( hhDoor* node = GetAirLockMaster()->airlockTeam.ListHead()->Owner(); node != NULL; node = node->airlockTeam.Next() ) { + if( node == this ) { + continue; + } + + if( OnMyMoveTeam(node) ) { + continue; + } + + if( node->IsOpen() ) { + return false; + } + } + + return true; +} + +//-------------------------------- +// hhDoor::CanClose +//-------------------------------- +bool hhDoor::CanClose() const { + return true; +} + +//-------------------------------- +// hhDoor::OnMyMoveTeam +//-------------------------------- +bool hhDoor::OnMyMoveTeam( hhDoor* doorPiece ) const { + for( const idMover_Binary* localDoorPiece = this; localDoorPiece != NULL; localDoorPiece = localDoorPiece->GetActivateChain() ) { + if( localDoorPiece == doorPiece ) { + return true; + } + } + + return false; +} + +//-------------------------------- +// hhDoor::CopyTeamInfoToMoveMaster +//-------------------------------- +void hhDoor::CopyTeamInfoToMoveMaster( hhDoor* master ) { + if( !master ) { + return; + } + + for( int ix = buddies.Num() - 1; ix >= 0; --ix ) { + master->buddies.AddUnique( buddies[ix] ); + } + + for( int ix = buddyNames.Num() - 1; ix >= 0; --ix ) { + master->buddyNames.AddUnique( buddyNames[ix] ); + } +} + +//-------------------------------- +// hhDoor::DetermineTeamMaster +//-------------------------------- +idEntity* hhDoor::DetermineTeamMaster( const char* teamName ) { + idEntity* ent = NULL; + + if ( teamName && teamName[0] ) { + // 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(hhModelDoor::Type) && !idStr::Icmp( static_cast(ent)->GetAirLockTeamName(), teamName )) { + return ent; + } + if (ent->IsType(hhDoor::Type) && !idStr::Icmp( static_cast(ent)->GetAirLockTeamName(), teamName )) { + return ent; + } + } + } + + return NULL; +} + +//-------------------------------- +// hhDoor::JoinAirLockTeam +//-------------------------------- +void hhDoor::JoinAirLockTeam( hhDoor *master ) { + assert( master ); + + airlockTeam.AddToEnd( master->airlockTeam ); +} + +//-------------------------------- +// hhDoor::VerifyAirlockTeamStatus +//-------------------------------- +void hhDoor::VerifyAirlockTeamStatus() { + if( !GetAirLockMaster() ) { + return; + } + + for( hhDoor* node = GetAirLockMaster()->airlockTeam.ListHead()->Owner(); node != NULL; node = node->airlockTeam.Next() ) { + if( node == this ) { + continue; + } + + if( OnMyMoveTeam(node) ) { + continue; + } + + if( !node->IsClosed() ) { + gameLocal.Warning( "Airlock team '%s' has more than one member starting open", GetAirLockTeamName() ); + } + } +} + +//-------------------------------- +// hhDoor::IsClosed +//-------------------------------- +bool hhDoor::IsClosed() const { + return ( moverState == MOVER_POS1 ); +} + +//-------------------------------- +// hhDoor::ForceAirLockTeamClosed +//-------------------------------- +void hhDoor::ForceAirLockTeamClosed() { + hhDoor* localMoveMaster = NULL; + + if( !GetAirLockMaster() ) { + return; + } + + for( hhDoor* node = GetAirLockMaster()->airlockTeam.ListHead()->Owner(); node != NULL; node = node->airlockTeam.Next() ) { + if( node == this ) { + continue; + } + + if( !node->moveMaster->IsType(hhDoor::Type) ) { + continue; + } + + localMoveMaster = static_cast( node->moveMaster ); + //if( node != localMoveMaster ) { + // continue; + //} + + localMoveMaster->CancelReturnToPos1(); + if( !localMoveMaster->IsClosed() ) { + localMoveMaster->Close(); + } + } +} + +//-------------------------------- +// hhDoor::ForceAirLockTeamOpen +//-------------------------------- +void hhDoor::ForceAirLockTeamOpen() { + hhDoor* localMoveMaster = NULL; + + if( !GetAirLockMaster() ) { + return; + } + + for( hhDoor* node = GetAirLockMaster()->airlockTeam.ListHead()->Owner(); node != NULL; node = node->airlockTeam.Next() ) { + if( node == this ) { + continue; + } + + if( !node->moveMaster->IsType(hhDoor::Type) ) { + continue; + } + + localMoveMaster = static_cast( node->moveMaster ); + //if( node != localMoveMaster ) { + // continue; + //} + + localMoveMaster->CancelReturnToPos1(); + if( !localMoveMaster->IsOpen() ) { + localMoveMaster->Open(); + } + } +} + +//-------------------------------- +// hhDoor::CancelReturnToPos1 +//-------------------------------- +void hhDoor::CancelReturnToPos1() { + for( idMover_Binary* slave = this; slave != NULL; slave = slave->GetActivateChain() ) { + slave->CancelEvents( &EV_Mover_ReturnToPos1 ); + } +} + +//-------------------------------- +// hhDoor::SetBuddiesShaderParm +//-------------------------------- +void hhDoor::SetBuddiesShaderParm( int parm, float value ) { + idEntity* buddy = NULL; + + for( int ix = buddyNames.Num() - 1; ix >= 0; --ix ) { + if( !buddyNames[ix].Length() ) { + continue; + } + + buddy = gameLocal.FindEntity( buddyNames[ix].c_str() ); + if( !buddy ) { + continue; + } + + buddy->SetShaderParm( parm, value ); + } +} + +//-------------------------------- +// hhDoor::ToggleBuddiesShaderParm +//-------------------------------- +void hhDoor::ToggleBuddiesShaderParm( int parm, float firstValue, float secondValue, float toggleDelay) { + SetBuddiesShaderParm( parm, firstValue ); + PostEventMS( &EV_SetBuddiesShaderParm, toggleDelay, parm, secondValue ); +} + +//-------------------------------- +// hhDoor::Event_SetBuddiesShaderParm +//-------------------------------- +void hhDoor::Event_SetBuddiesShaderParm( int parm, float value ) { + SetBuddiesShaderParm( parm, value ); +} + +//-------------------------------- +// hhDoor::Event_ReturnToPos1 +//-------------------------------- +void hhDoor::Event_ReturnToPos1( void ) { + + if ( !ForcedOpen() ) { + idDoor::Event_ReturnToPos1(); + } + +} // Event_ReturnToPos1() + +//-------------------------------- +// hhDoor::Event_Reached_BinaryMover +//-------------------------------- +void hhDoor::Event_Reached_BinaryMover( void ) { + if ( moverState == MOVER_2TO1 ) { + SetBlocked(false); + ActivatePrefixed("triggerClosed", this); + } else if (moverState == MOVER_1TO2) { + ActivatePrefixed("triggerOpened", this); + } + + if ( moverState == MOVER_1TO2 ) { + // reached pos2 + idThread::ObjectMoveDone( move_thread, this ); + move_thread = 0; + + // HUMANHEAD aob - removed + //if ( moveMaster == this ) { + //StartSound( "snd_opened", SND_CHANNEL_ANY ); + //} + //HUMANHEAD END + + SetMoverState( MOVER_POS2, gameLocal.time ); + + SetGuiStates( guiBinaryMoverStates[MOVER_POS2] ); + + UpdateBuddies(1); + + if( enabled && wait >= 0 && !spawnArgs.GetBool("toggle") ) { + // return to pos1 after a delay + //HUMANHEAD: aob + CancelReturnToPos1(); + //HUMANHEAD END + PostEventSec( &EV_Mover_ReturnToPos1, wait ); + } + + // fire targets + ActivateTargets( moveMaster->GetActivator() ); + } else if ( moverState == MOVER_2TO1 ) { + // reached pos1 + idThread::ObjectMoveDone( move_thread, this ); + move_thread = 0; + + SetMoverState( MOVER_POS1, gameLocal.time ); + + SetGuiStates( guiBinaryMoverStates[MOVER_POS1] ); + + UpdateBuddies(0); + + // close areaportals + if ( moveMaster == this ) { + ProcessEvent( &EV_Mover_ClosePortal ); + } + + if( GetAirLockMaster() ) { + // Mark the other team members as unlocked + for( hhDoor* node = GetAirLockMaster()->airlockTeam.ListHead()->Owner(); node != NULL; node = node->airlockTeam.Next() ) { + + // Skip this door and it's buddies + if( node == this || buddyNames.Find( node->name ) ) { + continue; + } + + // Don't change doors that are really locked + if( node->IsLocked() ) { + continue; + } + + // Mark as unlocked when door closes + node->SetBuddiesShaderParm( SHADERPARM_MISC, 0.0f ); + } + } + } else { + gameLocal.Error( "Event_Reached_BinaryMover: bad moverState" ); + } +} + +/* +================ +hhDoor::Event_Touch +================ +*/ +void hhDoor::Event_Touch( idEntity *other, trace_t *trace ) { + if ( bShuttleDoors && ( ! other->IsType( idActor::Type ) || ! reinterpret_cast ( other )->InVehicle() ) ) { + if ( gameLocal.time > nextSndTriggerTime ) { + StartSound( "snd_locked", SND_CHANNEL_ANY, 0, false, NULL ); + nextSndTriggerTime = gameLocal.time + 10000; + } + return; + } + + idDoor::Event_Touch( other, trace ); +} diff --git a/src/Prey/game_door.h b/src/Prey/game_door.h new file mode 100644 index 0000000..80ce90d --- /dev/null +++ b/src/Prey/game_door.h @@ -0,0 +1,69 @@ +#ifndef __PREY_DOOR_H__ +#define __PREY_DOOR_H__ + +class hhDoor : public idDoor { + CLASS_PROTOTYPE( hhDoor ); + +public: + void Spawn(); + virtual ~hhDoor(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Use_BinaryMover( idEntity *activator ); + virtual void GotoPosition1(); + virtual void GotoPosition2(); + + //HUMANHEAD: aob + virtual void GotoPosition1( float wait ); + virtual void GotoPosition2( float wait ); + //HUMANHEAD END + + ID_INLINE const char* GetAirLockTeamName() const { return airlockTeamName.c_str(); } + ID_INLINE hhDoor* GetAirLockMaster() const { return airlockMaster; } + void Open(); + void Close(); + bool CanOpen() const; + bool CanClose() const; + bool IsClosed() const; + + bool OnMyMoveTeam( hhDoor* doorPiece ) const; + void CopyTeamInfoToMoveMaster( hhDoor* master ); + + void CancelReturnToPos1(); + + void SetBuddiesShaderParm( int parm, float value ); + void ToggleBuddiesShaderParm( int parm, float firstValue, float secondValue, float toggleDelay ); + + bool ForcedOpen() const { return( forcedOpen ); }; + void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + +protected: + idEntity* DetermineTeamMaster( const char* teamName ); + void JoinAirLockTeam( hhDoor *master ); + void VerifyAirlockTeamStatus(); + + void ForceAirLockTeamClosed(); + void ForceAirLockTeamOpen(); + +protected: + void Event_ReturnToPos1( void ); // overridden to stop it moving when closed + void Event_SetBuddiesShaderParm( int parm, float value ); + void Event_Reached_BinaryMover( void ); + virtual void Event_Touch( idEntity *other, trace_t *trace ); + +public: + idLinkList airlockTeam; + +protected://More airLock stuff + idStr airlockTeamName; + hhDoor* airlockMaster; + + bool openWhenDead; // Do we open when dead? + bool forcedOpen; // Is the door forced open + float airLockSndWait; + float nextAirLockSnd; + bool bShuttleDoors; +}; + +#endif diff --git a/src/Prey/game_eggspawner.cpp b/src/Prey/game_eggspawner.cpp new file mode 100644 index 0000000..065f5d2 --- /dev/null +++ b/src/Prey/game_eggspawner.cpp @@ -0,0 +1,281 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +//========================================================================== +// +// hhEggSpawner +// +// When activated, shoots out an egg along it's X axis +//========================================================================== + +#define MAX_HATCH_TIME (SEC2MS(10)) + +CLASS_DECLARATION(hhAnimatedEntity, hhEggSpawner) + EVENT( EV_PlayIdle, hhEggSpawner::Event_PlayIdle) + EVENT( EV_Activate, hhEggSpawner::Event_Activate) +END_CLASS + +void hhEggSpawner::Spawn(void) { + GetPhysics()->SetContents( CONTENTS_SOLID ); + fl.takedamage = true; + + idleAnim = GetAnimator()->GetAnim("idle"); + hatchAnim = GetAnimator()->GetAnim("launch"); + painAnim = GetAnimator()->GetAnim("pain"); + + PostEventMS(&EV_PlayIdle, 0); +} + +void hhEggSpawner::Save(idSaveGame *savefile) const { + savefile->WriteInt( idleAnim ); + savefile->WriteInt( hatchAnim ); + savefile->WriteInt( painAnim ); +} + +void hhEggSpawner::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( idleAnim ); + savefile->ReadInt( hatchAnim ); + savefile->ReadInt( painAnim ); +} + +void hhEggSpawner::SpawnEgg(idEntity *activator) { + idVec3 dir = GetPhysics()->GetAxis()[0]; + idVec3 offset = spawnArgs.GetVector("offset_spawn"); + float power = spawnArgs.GetFloat("power"); + + // Play anim + if (hatchAnim) { + GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, hatchAnim, gameLocal.time, 0); + int ms = GetAnimator()->GetAnim( hatchAnim )->Length(); + PostEventMS(&EV_PlayIdle, ms); + } + + idDict args; + args.Clear(); + args.SetVector( "origin", GetPhysics()->GetOrigin() + offset * GetPhysics()->GetAxis() ); + hhEgg *egg = static_cast(gameLocal.SpawnObject(spawnArgs.GetString("def_egg"), &args)); + if (egg) { + egg->GetPhysics()->SetLinearVelocity(dir * power); + egg->SetActivator(activator); + + // Copy our targets + for ( int i = 0; i < targets.Num(); i++ ) { + egg->targets.AddUnique(targets[i]); + } + } + + StartSound( "snd_spawn", SND_CHANNEL_ANY, 0, true, NULL ); +} + +void hhEggSpawner::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + // Don't actually take damage, but give feedback + if (painAnim) { + GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, painAnim, gameLocal.time, 0); + int ms = GetAnimator()->GetAnim( painAnim )->Length(); + PostEventMS(&EV_PlayIdle, ms); + } +} + +void hhEggSpawner::Event_PlayIdle() { + if (idleAnim) { + GetAnimator()->ClearAllAnims(gameLocal.time, 200); + GetAnimator()->CycleAnim(ANIMCHANNEL_ALL, idleAnim, gameLocal.time, 200); + } +} + +void hhEggSpawner::Event_Activate(idEntity *activator) { + SpawnEgg(activator); +} + + +//========================================================================== +// +// hhEgg +// +// Moveable that spawns a creature on impact +//========================================================================== + +const idEventDef EV_Hatch(""); + +CLASS_DECLARATION(hhMoveable, hhEgg) + EVENT( EV_Activate, hhEgg::Event_Activate) + EVENT( EV_Hatch, hhEgg::Event_Hatch) +END_CLASS + +void hhEgg::Spawn(void) { + enemy = NULL; + bHatched = false; + bHatching = false; + const char *tableName = spawnArgs.GetString("table_hatch"); + table = static_cast(declManager->FindType( DECL_TABLE, tableName, true )); + hatchTime = -1.0f; + + PostEventSec( &EV_Activate, spawnArgs.GetFloat("secondsBeforeHatch"), this ); +} + +void hhEgg::Save(idSaveGame *savefile) const { + + savefile->WriteBool( bHatching ); + savefile->WriteBool( bHatched ); + + savefile->WriteFloat( deformAlpha.GetStartTime() ); // idInterpolate + savefile->WriteFloat( deformAlpha.GetDuration() ); + savefile->WriteFloat( deformAlpha.GetStartValue() ); + savefile->WriteFloat( deformAlpha.GetEndValue() ); + savefile->WriteFloat( hatchTime ); + + enemy.Save(savefile); +} + +void hhEgg::Restore( idRestoreGame *savefile ) { + float set; + + savefile->ReadBool( bHatching ); + savefile->ReadBool( bHatched ); + + savefile->ReadFloat( set ); // idInterpolate + deformAlpha.SetStartTime( set ); + savefile->ReadFloat( set ); + deformAlpha.SetDuration( set ); + savefile->ReadFloat( set ); + deformAlpha.SetStartValue(set); + savefile->ReadFloat( set ); + deformAlpha.SetEndValue( set ); + + savefile->ReadFloat( hatchTime ); + + const char *tableName = spawnArgs.GetString("table_hatch"); + table = static_cast(declManager->FindType( DECL_TABLE, tableName, true )); + + enemy.Restore(savefile); +} + + +void hhEgg::SetActivator(idEntity *activator) { + if (activator && activator->IsType(idActor::Type)) { + enemy = static_cast(activator); + } +} + +void hhEgg::Ticker() { + if (bHatching) { + // over time, send different parms into deformation + if (deformAlpha.IsDone(gameLocal.time)) { + Hatch(); + } + else { + float alpha = deformAlpha.GetCurrentValue(gameLocal.time); + float value = table->TableLookup(alpha); + SetShaderParm(SHADERPARM_ANY_DEFORM, DEFORMTYPE_VIBRATE); + SetShaderParm(SHADERPARM_ANY_DEFORM_PARM1, value); + } + } +} + +bool hhEgg::Collide( const trace_t &collision, const idVec3 &velocity ) { + AttemptToPlayBounceSound( collision, velocity ); + + if (spawnArgs.GetBool("spawnOnImpact")) { //obs + Hatch(); + return true; + } + return false; +} + +void hhEgg::Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location) { + Hatch(); +} + +bool hhEgg::SpawnHatchling(const char *monsterName, const idVec3 &spawnLocation) { + idDict args; + args.Clear(); + args.SetVector( "origin", spawnLocation ); + args.SetFloat( "angle", GetAxis().ToAngles().yaw ); + args.Set( "trigger_anim", "birth" ); + hhMonsterAI *monster = static_cast(gameLocal.SpawnObject(monsterName, &args)); + + if (monster) { + // Set enemy + if (enemy.IsValid()) { + monster->SetEnemy(enemy.GetEntity()); + } + + // Copy our targets + for ( int i = 0; i < targets.Num(); i++ ) { + monster->targets.AddUnique(targets[i]); + } + + monster->PostEventSec( &EV_Activate, 0, this ); + } + + return monster != NULL; +} + +void hhEgg::Hatch() { + if (!bHatched) { + if (hatchTime == -1.0f) { + hatchTime = gameLocal.time; + } + // Spawn hatchling + const char *monsterName = spawnArgs.GetString("def_hatchling", NULL); + if (monsterName) { + idVec3 spawnLocation = GetPhysics()->GetOrigin(); + + const float hatchOffsetDistance = 50.0f; + bool spawned = false; + idVec3 offset; + idVec3 offsetLocation; + trace_t trace; + memset(&trace, 0, sizeof(trace_t)); + + if (hhUtils::EntityDefWillFit(monsterName, spawnLocation, mat3_identity, MASK_MONSTERSOLID, this)) { + //gameRenderWorld->DebugArrow(colorGreen, spawnLocation, offsetLocation, 10, 10000); + spawned = SpawnHatchling(monsterName, spawnLocation); + } + + if (!spawned) { // Creature wouldn't fit, try again later + CancelEvents(&EV_Hatch); + if (gameLocal.time - hatchTime > MAX_HATCH_TIME) { + // Give up after 15 seconds of struggling + ActivateTargets(this); + // Spawn gibs from monster + const idDict *dict = gameLocal.FindEntityDefDict(monsterName, false); + if (dict && dict->FindKey("def_gibdebrisspawner")) { + GetPhysics()->SetLinearVelocity( vec3_zero ); //HUMANHEAD jsh PCF zero out velocity to prevent debris huge translation + hhUtils::SpawnDebrisMass(dict->GetString("def_gibdebrisspawner"), this); + } + } else { + //idVec3 dir = hhUtils::RandomVector(); + //gameRenderWorld->DebugArrow(colorGreen, spawnLocation, spawnLocation + dir * 50.0f, 10, 250); + GetPhysics()->AddForce(0, GetPhysics()->GetOrigin(), hhUtils::RandomVector() * idMath::ClampFloat(0x1000000, 0x100000000, (0x1000000 * ((gameLocal.time - hatchTime) / 25.0f)))); + PostEventMS(&EV_Hatch, 250); + return; + } + } + } + + hatchTime = -1.0f; + bHatched = true; + GetPhysics()->SetContents(0); + PostEventMS(&EV_Remove, 0); + + StartSound( "snd_hatch", SND_CHANNEL_ANY, 0, true, NULL ); + GetPhysics()->SetLinearVelocity( vec3_zero ); //HUMANHEAD jsh PCF zero out velocity to prevent debris huge translation + hhUtils::SpawnDebrisMass(spawnArgs.GetString("def_afterbirth"), this); + } +} + +void hhEgg::Event_Activate(idEntity *activator) { + if (!bHatching) { + bHatching = true; + int msForTable = SEC2MS(spawnArgs.GetFloat("secondsToHatch")); + deformAlpha.Init(gameLocal.time, msForTable, 0.0f, 1.0f); + BecomeActive(TH_TICKER); + } +} + +void hhEgg::Event_Hatch(void) { + Hatch(); +} diff --git a/src/Prey/game_eggspawner.h b/src/Prey/game_eggspawner.h new file mode 100644 index 0000000..e2a350c --- /dev/null +++ b/src/Prey/game_eggspawner.h @@ -0,0 +1,53 @@ + +#ifndef __GAME_EGGSPAWNER_H__ +#define __GAME_EGGSPAWNER_H__ + +class hhEggSpawner : public hhAnimatedEntity { + CLASS_PROTOTYPE( hhEggSpawner ); +public: + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void SpawnEgg(idEntity *activator); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + +protected: + void Event_Activate(idEntity *activator); + void Event_PlayIdle(); + +protected: + int idleAnim; + int hatchAnim; + int painAnim; +}; + + +class hhEgg : public hhMoveable { + CLASS_PROTOTYPE( hhEgg ); +public: + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual void Ticker(); + virtual void Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void Hatch(); + void SetActivator(idEntity *activator); + +protected: + bool SpawnHatchling(const char *monsterName, const idVec3 &spawnLocation); + void Event_Activate(idEntity *activator); + void Event_Hatch(); + +protected: + bool bHatching; + bool bHatched; + idInterpolate deformAlpha; + const idDeclTable * table; + idEntityPtr enemy; + float hatchTime; +}; + +#endif diff --git a/src/Prey/game_energynode.cpp b/src/Prey/game_energynode.cpp new file mode 100644 index 0000000..89a2aa8 --- /dev/null +++ b/src/Prey/game_energynode.cpp @@ -0,0 +1,126 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_EnableNode( "enableNode", NULL ); +const idEventDef EV_DisableNode( "disableNode", NULL ); + +CLASS_DECLARATION( idStaticEntity, hhEnergyNode ) + EVENT( EV_EnableNode, hhEnergyNode::Event_Enable ) + EVENT( EV_DisableNode, hhEnergyNode::Event_Disable ) +END_CLASS + +hhEnergyNode::hhEnergyNode(void) +:disabled(false) +{} + +hhEnergyNode::~hhEnergyNode(void) { + Event_Disable(); +} + + +void hhEnergyNode::Save(idSaveGame *savefile) const { + savefile->WriteBool( disabled ); + savefile->WriteVec3( leechPoint ); + + energyFx.Save( savefile ); +} + +void hhEnergyNode::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( disabled ); + savefile->ReadVec3( leechPoint ); + + energyFx.Restore( savefile ); +} + +void hhEnergyNode::Spawn( void ) { + fl.networkSync = true; + GetPhysics()->SetContents( CONTENTS_SOLID ); + + leechPoint = GetAxis()*spawnArgs.GetVector("leechPoint")+GetOrigin(); + + fl.clientEvents = true; + PostEventMS(&EV_EnableNode, 1); +} + +void hhEnergyNode::LeechTrigger(idEntity *activator, const char* type) { + idStr name = spawnArgs.GetString(type); + if ( name != "" ) { + idEntity* ent = gameLocal.FindEntity(name); + if ( ent ) { + if ( ent->RespondsTo( EV_Activate ) || ent->HasSignal( SIG_TRIGGER ) ) { + ent->Signal( SIG_TRIGGER ); + ent->ProcessEvent( &EV_Activate, activator ); + } + ent->TriggerGuis(); + } + } +} + +void hhEnergyNode::Finish() { + Event_Disable(); + + if ( spawnArgs.GetInt("infinite","0") || gameLocal.isMultiplayer ) { // delay before reenabling -cjr + PostEventSec( &EV_EnableNode, spawnArgs.GetFloat( "reenableDelay", "20" ) ); + } +} + +void hhEnergyNode::Event_Enable() { + disabled = false; + + const idDict *energyDef = NULL; + const char* str = spawnArgs.GetString( "def_energy" ); + if ( str && str[0] ) { + energyDef = gameLocal.FindEntityDefDict( str ); + } + + if ( energyDef ) { + hhFxInfo fxInfo; + if (IsBound() || spawnArgs.GetBool("force_bind")) { + fxInfo.SetEntity( this ); // Only bind if we will be moving + } + energyFx = SpawnFxLocal( energyDef->GetString("fx_node"), leechPoint, GetAxis(), &fxInfo, true ); + + idVec3 color = energyDef->GetVector("nodeColor"); + SetShaderParm( SHADERPARM_RED, color.x ); + SetShaderParm( SHADERPARM_GREEN, color.y ); + SetShaderParm( SHADERPARM_BLUE, color.z ); + + StartSound( "snd_activate", SND_CHANNEL_ANY ); + StartSound( "snd_idle", SND_CHANNEL_BODY ); + } +} + +void hhEnergyNode::Event_Disable() { + disabled = true; + if( energyFx.IsValid() ) { + SAFE_REMOVE(energyFx); + } + + SetShaderParm( SHADERPARM_RED, 0 ); + SetShaderParm( SHADERPARM_GREEN, 0 ); + SetShaderParm( SHADERPARM_BLUE, 0 ); + + StopSound( SND_CHANNEL_BODY ); +} + +void hhEnergyNode::WriteToSnapshot( idBitMsgDelta &msg ) const { + idStaticEntity::WriteToSnapshot(msg); + + msg.WriteBits(disabled, 1); +} + +void hhEnergyNode::ReadFromSnapshot( const idBitMsgDelta &msg ) { + idStaticEntity::ReadFromSnapshot(msg); + + bool snapDisabled = !!msg.ReadBits(1); + if (snapDisabled != disabled) { + if (snapDisabled) { + Event_Disable(); + } + else { + Event_Enable(); + } + } +} diff --git a/src/Prey/game_energynode.h b/src/Prey/game_energynode.h new file mode 100644 index 0000000..f8055e3 --- /dev/null +++ b/src/Prey/game_energynode.h @@ -0,0 +1,35 @@ +#ifndef __GAME_ENERGYNODE_H__ +#define __GAME_ENERGYNODE_H__ + +class hhEnergyNode : public idStaticEntity { +public: + CLASS_PROTOTYPE( hhEnergyNode ); + + hhEnergyNode(void); + ~hhEnergyNode(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + inline bool CanLeech() { return !disabled; } + void LeechTrigger(idEntity *activator, const char* type); + void Finish(); + + virtual void Event_Enable(); + virtual void Event_Disable(); + + //rww - network code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + idVec3 leechPoint; + +protected: + bool disabled; + + idEntityPtr energyFx; +}; + +#endif // __GAME_ENERGYNODE_H__ diff --git a/src/Prey/game_entityfx.cpp b/src/Prey/game_entityfx.cpp new file mode 100644 index 0000000..54f5f36 --- /dev/null +++ b/src/Prey/game_entityfx.cpp @@ -0,0 +1,637 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION( idEntityFx, hhEntityFx ) + EVENT( EV_Activate, hhEntityFx::Event_Trigger ) + EVENT( EV_Fx_KillFx, hhEntityFx::Event_ClearFx ) +END_CLASS + +/* +================ +hhEntityFx::hhEntityFx +================ +*/ +hhEntityFx::hhEntityFx() { + // HUMANHEAD nla + setFxInfo = false; + // HUMANHEAD + +// HUMANHEAD bg + restartActive = false; +// HUMANHEAD END +} + +/* +================ +hhEntityFx::~hhEntityFx +================ +*/ +hhEntityFx::~hhEntityFx() { + //HUMANHEAD: aob - need to shut everything down + Event_ClearFx(); + //HUMANHEAD END +} + +void hhEntityFx::Save(idSaveGame *savefile) const { + savefile->WriteStaticObject( fxInfo ); + savefile->WriteBool( setFxInfo ); + savefile->WriteBool( removeWhenDone ); +// HUMANHEAD bg + savefile->WriteBool( restartActive ); +// HUMANHEAD END +} + +void hhEntityFx::Restore( idRestoreGame *savefile ) { + savefile->ReadStaticObject( fxInfo ); + savefile->ReadBool( setFxInfo ); + savefile->ReadBool( removeWhenDone ); +// HUMANHEAD bg + savefile->ReadBool( restartActive ); +// HUMANHEAD END +} + +void hhEntityFx::WriteToSnapshot( idBitMsgDelta &msg ) const { + idEntityFx::WriteToSnapshot(msg); + + fxInfo.WriteToSnapshot(msg); +} + +void hhEntityFx::ReadFromSnapshot( const idBitMsgDelta &msg ) { + idEntityFx::ReadFromSnapshot(msg); + + fxInfo.ReadFromSnapshot(msg); +} + +/* +================ +hhEntityFx::CleanUpSingleAction +================ +*/ +void hhEntityFx::CleanUpSingleAction( const idFXSingleAction& fxaction, idFXLocalAction& laction ) { + idEntityFx::CleanUpSingleAction( fxaction, laction ); + + // HUMANHEAD pdm: sound "duration" support + if ( fxaction.type == FX_SOUND && fxaction.sibling == -1 && laction.soundStarted ) { + const idSoundShader *def = declManager->FindSound( fxaction.data ); + refSound.referenceSound->StopSound( SND_CHANNEL_ANY ); + } + + if ( fxaction.type == FX_PARTICLE && fxaction.sibling == -1 ) { + laction.particleSystem = NULL; + laction.particleStartTime = -1; + } + // HUMANHEAD END +} + +/* +================ +hhEntityFx::Hide +================ +*/ +void hhEntityFx::Hide() { + CleanUp(); +} + +/* +================ +hhEntityFx::Run + PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +================ +*/ +void hhEntityFx::Run( int time ) { + int ieff, j; + idEntity *ent = NULL; + const idDict *projectileDef = NULL; + idProjectile *projectile = NULL; + + if ( !fxEffect || IsHidden() ) { + return; + } + + for( ieff = 0; ieff < fxEffect->events.Num(); ieff++ ) { + const idFXSingleAction& fxaction = fxEffect->events[ieff]; + idFXLocalAction& laction = actions[ieff]; + + // + // if we're currently done with this one + // + if ( laction.start == -1 ) { + continue; + } + + // + // see if it's delayed + // + if ( laction.delay ) { + if ( laction.start + (time - laction.start) < laction.start + (laction.delay * 1000) ) { + continue; + } + } + + // + // each event can have it's own delay and restart + // + int actualStart = laction.delay ? laction.start + (int)( laction.delay * 1000 ) : laction.start; + float pct = (float)( time - actualStart ) / (1000 * fxaction.duration ); + if ( pct >= 1.0f ) { + laction.start = -1; + float totalDelay = 0.0f; + if ( fxaction.restart ) { + if ( fxaction.random1 || fxaction.random2 ) { + totalDelay = fxaction.random1 + gameLocal.random.RandomFloat() * (fxaction.random2 - fxaction.random1); + } else { + totalDelay = fxaction.delay; + } + laction.delay = totalDelay; + laction.start = time; + } + continue; + } + + if ( fxaction.fire.Length() ) { + for( j = 0; j < fxEffect->events.Num(); j++ ) { + if ( fxEffect->events[j].name.Icmp( fxaction.fire ) == 0 ) { + actions[j].delay = 0; + } + } + } + + idFXLocalAction *useAction = NULL; + if ( fxaction.sibling == -1 ) { + useAction = &laction; + } else { + useAction = &actions[fxaction.sibling]; + } + assert( useAction ); + + switch( fxaction.type ) { + case FX_ATTACHLIGHT: + case FX_LIGHT: { + if ( useAction->lightDefHandle == -1 ) { + if ( fxaction.type == FX_LIGHT ) { + memset( &useAction->renderLight, 0, sizeof( renderLight_t ) ); + //HUMANHEAD: bjk once again fixing aob code + useAction->renderLight.axis = DetermineAxis( fxaction ); + useAction->renderLight.origin = GetOrigin() + fxaction.offset * useAction->renderLight.axis; + useAction->renderLight.axis = hhUtils::SwapXZ( useAction->renderLight.axis ); + //HUMANHEAD END + useAction->renderLight.lightRadius[0] = fxaction.lightRadius; + useAction->renderLight.lightRadius[1] = fxaction.lightRadius; + useAction->renderLight.lightRadius[2] = fxaction.lightRadius; + useAction->renderLight.shader = declManager->FindMaterial( fxaction.data, false ); + useAction->renderLight.shaderParms[ SHADERPARM_RED ] = fxaction.lightColor.x; + useAction->renderLight.shaderParms[ SHADERPARM_GREEN ] = fxaction.lightColor.y; + useAction->renderLight.shaderParms[ SHADERPARM_BLUE ] = fxaction.lightColor.z; + useAction->renderLight.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; + useAction->renderLight.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( time ); + useAction->renderLight.referenceSound = refSound.referenceSound; + useAction->renderLight.pointLight = true; + if ( fxaction.noshadows ) { + useAction->renderLight.noShadows = true; + } + useAction->lightDefHandle = gameRenderWorld->AddLightDef( &useAction->renderLight ); + } + if ( fxaction.noshadows ) { + for( j = 0; j < fxEffect->events.Num(); j++ ) { + idFXLocalAction& laction2 = actions[j]; + if ( laction2.modelDefHandle != -1 ) { + laction2.renderEntity.noShadow = true; + } + } + } + } else if ( fxaction.trackOrigin ) { + //HUMANHEAD: bjk + useAction->renderLight.axis = DetermineAxis( fxaction ); + useAction->renderLight.origin = GetOrigin() + fxaction.offset * useAction->renderLight.axis; + useAction->renderLight.axis = hhUtils::SwapXZ( useAction->renderLight.axis ); + gameRenderWorld->UpdateLightDef( useAction->lightDefHandle, &useAction->renderLight ); + //HUMANHEAD END + } + ApplyFade( fxaction, *useAction, time, actualStart ); + break; + } + case FX_SOUND: { + if ( !useAction->soundStarted ) { + useAction->soundStarted = true; + const idSoundShader *shader = declManager->FindSound(fxaction.data); + StartSoundShader( shader, SND_CHANNEL_ANY, 0, false, NULL ); + for( j = 0; j < fxEffect->events.Num(); j++ ) { + idFXLocalAction& laction2 = actions[j]; + if ( laction2.lightDefHandle != -1 ) { + laction2.renderLight.referenceSound = refSound.referenceSound; + gameRenderWorld->UpdateLightDef( laction2.lightDefHandle, &laction2.renderLight ); + } + } + } + break; + } + case FX_DECAL: { + if ( !useAction->decalDropped ) { + useAction->decalDropped = true; + // HUMANHEAD pdm: Increased depth to from 8 to 25 + //TODO: Expose depth and parrallel to the FX files + gameLocal.ProjectDecal( GetPhysics()->GetOrigin(), GetPhysics()->GetGravity(), 25.0f, true, fxaction.size, fxaction.data ); + } + break; + } + case FX_SHAKE: { + if ( !useAction->shakeStarted ) { + idDict args; + args.Clear(); + args.SetFloat( "kick_time", fxaction.shakeTime ); + args.SetFloat( "kick_amplitude", fxaction.shakeAmplitude ); + for ( j = 0; j < gameLocal.numClients; j++ ) { + idPlayer *player = gameLocal.GetClientByNum( j ); + if ( player && ( player->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ).LengthSqr() < Square( fxaction.shakeDistance ) ) { + if ( !gameLocal.isMultiplayer || !fxaction.shakeIgnoreMaster || GetBindMaster() != player ) { + player->playerView.DamageImpulse( fxaction.offset, &args ); + } + } + } + if ( fxaction.shakeImpulse != 0.0f && fxaction.shakeDistance != 0.0f ) { + idEntity *ignore_ent = NULL; + if ( gameLocal.isMultiplayer ) { + ignore_ent = this; + if ( fxaction.shakeIgnoreMaster ) { + ignore_ent = GetBindMaster(); + } + } + // lookup the ent we are bound to? + gameLocal.RadiusPush( GetPhysics()->GetOrigin(), fxaction.shakeDistance, fxaction.shakeImpulse, this, ignore_ent, 1.0f, true ); + } + useAction->shakeStarted = true; + } + break; + } + case FX_ATTACHENTITY: + case FX_MODEL: { + if ( useAction->modelDefHandle == -1 ) { + memset( &useAction->renderEntity, 0, sizeof( renderEntity_t ) ); + //HUMANHEAD: aob - rotated offset by axis + useAction->renderEntity.axis = DetermineAxis( fxaction ); + useAction->renderEntity.origin = GetOrigin() + fxaction.offset * useAction->renderEntity.axis; + useAction->renderEntity.axis = hhUtils::SwapXZ( useAction->renderEntity.axis ); + useAction->renderEntity.weaponDepthHack = renderEntity.weaponDepthHack; + useAction->renderEntity.onlyVisibleInSpirit = renderEntity.onlyVisibleInSpirit; + useAction->renderEntity.onlyInvisibleInSpirit = renderEntity.onlyInvisibleInSpirit; // tmj + useAction->renderEntity.allowSurfaceInViewID = renderEntity.allowSurfaceInViewID; // bjk + //HUMANHEAD END + useAction->renderEntity.hModel = renderModelManager->FindModel( fxaction.data ); + // HUMANHEAD pdm: allow color on fx, but they'll be the same for all particles + if ( renderEntity.shaderParms[SHADERPARM_RED] != 0.0f || + renderEntity.shaderParms[SHADERPARM_GREEN] != 0.0f || + renderEntity.shaderParms[SHADERPARM_BLUE] != 0.0f) { + useAction->renderEntity.shaderParms[ SHADERPARM_RED ] = renderEntity.shaderParms[SHADERPARM_RED]; + useAction->renderEntity.shaderParms[ SHADERPARM_GREEN ] = renderEntity.shaderParms[SHADERPARM_GREEN]; + useAction->renderEntity.shaderParms[ SHADERPARM_BLUE ] = renderEntity.shaderParms[SHADERPARM_BLUE]; + } + else { + useAction->renderEntity.shaderParms[ SHADERPARM_RED ] = 1.0f; + useAction->renderEntity.shaderParms[ SHADERPARM_GREEN ] = 1.0f; + useAction->renderEntity.shaderParms[ SHADERPARM_BLUE ] = 1.0f; + } + useAction->renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( time ); + useAction->renderEntity.shaderParms[3] = 1.0f; + useAction->renderEntity.shaderParms[ SHADERPARM_DIVERSITY ] = gameLocal.random.RandomFloat(); //HUMANHEAD bjk + if ( useAction->renderEntity.hModel ) { + useAction->renderEntity.bounds = useAction->renderEntity.hModel->Bounds( &useAction->renderEntity ); + } + useAction->modelDefHandle = gameRenderWorld->AddEntityDef( &useAction->renderEntity ); + } else if ( fxaction.trackOrigin ) { + //HUMANHEAD: aob - rotated offset by axis + useAction->renderEntity.axis = DetermineAxis( fxaction ); + if (fxaction.offset.x || fxaction.offset.y || fxaction.offset.z) { + useAction->renderEntity.origin = GetOrigin() + fxaction.offset * useAction->renderEntity.axis; + } + else { + useAction->renderEntity.origin = GetOrigin(); + } + useAction->renderEntity.axis = hhUtils::SwapXZ( useAction->renderEntity.axis ); + gameRenderWorld->UpdateEntityDef( useAction->modelDefHandle, &useAction->renderEntity ); + //HUMANHEAD END + } + ApplyFade( fxaction, *useAction, time, actualStart ); + break; + } + case FX_PARTICLE: { + //HUMANHEAD: aob + //rww - kind of a hack, but otherwise the timing is off, which causes issues in mp. + if (useAction->particleStartTime == -2) { + useAction->particleStartTime = gameLocal.time; + } + + if( !useAction->particleSystem && fxaction.data.Length() ) { + useAction->particleSystem = static_cast( declManager->FindType( DECL_PARTICLE, fxaction.data, false ) ); + } else if( useAction->particleStartTime >= 0 && useAction->particleSystem ) { + idMat3 axis = DetermineAxis( fxaction ); + if( !gameLocal.smokeParticles->EmitSmoke(useAction->particleSystem, useAction->particleStartTime, gameLocal.random.RandomFloat(), GetOrigin() + fxaction.offset * axis, hhUtils::SwapXZ(axis)) ) { + useAction->particleStartTime = -1; + } + } + // HUMANHEAD END + break; + } + case FX_LAUNCH: { + //HUMANHEAD rww - if this is a client ent then assert. + assert(!fl.clientEntity); + + if ( gameLocal.isClient ) { + // client never spawns entities outside of ClientReadSnapshot + useAction->launched = true; + break; + } + if ( !useAction->launched ) { + useAction->launched = true; + projectile = NULL; + // FIXME: may need to cache this if it is slow + projectileDef = gameLocal.FindEntityDefDict( fxaction.data, false ); + if ( !projectileDef ) { + gameLocal.Warning( "projectile \'%s\' not found", fxaction.data.c_str() ); + } else { + gameLocal.SpawnEntityDef( *projectileDef, &ent, false ); + if ( ent && ent->IsType( idProjectile::Type ) ) { + projectile = ( idProjectile * )ent; + projectile->Create( this, GetPhysics()->GetOrigin(), GetPhysics()->GetAxis()[0] ); + projectile->Launch( GetPhysics()->GetOrigin(), GetPhysics()->GetAxis()[0], vec3_origin ); + } + } + } + break; + } + } + } +} + +/* +================ +hhEntityFx::DetermineAxis + +HUMANHEAD: aob +================ +*/ +idMat3 hhEntityFx::DetermineAxis( const idFXSingleAction& fxaction ) { + idVec3 fxDir; + + if( fxaction.explicitAxis ) { + if (fxaction.useAxis == AXIS_CUSTOMLOCAL) { + // When customlocal is set, axis is the specifed axis, untransformed by the entities axis + return fxaction.dir.ToMat3(); + } + return (fxaction.dir[0] * GetAxis()[0] + fxaction.dir[1] * GetAxis()[1] + fxaction.dir[2] * GetAxis()[2]).ToMat3(); + } else if( fxInfo.GetAxisFor(fxaction.useAxis, fxDir) ) { + return fxDir.ToMat3(); + } + + return GetAxis(); +} + +/* +================ +hhEntityFx::Nozzle + +HUMANHEAD: aob - used to toggle particle systems +================ +*/ +void hhEntityFx::Nozzle( bool bOn ) { + if ( !fxEffect ) { + return; + } + + if( bOn && !IsActive( TH_THINK ) ) { + Event_Trigger( NULL ); + } + + if( !bOn ) { + //Canceling events incase we are toggled before our actvate event has fired. + CancelEvents( &EV_Activate ); + + if( IsActive( TH_THINK ) ) { + Event_ClearFx(); + } + } +} + +/* +================ +hhEntityFx::DormantBegin + +//HUMANHEAD: aob +================ +*/ +void hhEntityFx::DormantBegin() { + idEntityFx::DormantBegin(); + + //HUMANHEAD: aob - used CleanUp directly so 'started' doesn't get reset + //Stop(); + CleanUp(); + //HUMANHEAD END + + //want to make sure we can trigger these back on + nextTriggerTime = -1; +} + +/* +================ +hhEntityFx::DormantEnd + +//HUMANHEAD: aob +================ +*/ +void hhEntityFx::DormantEnd() { + idEntityFx::DormantEnd(); + + if ( started >= 0 ) { + CreateFx( this ); + } +} + +/* +================ +hhEntityFx::Event_Trigger +================ +*/ +void hhEntityFx::Event_Trigger( idEntity *activator ) { + if ( gameLocal.time < nextTriggerTime ) { + return; + } + +// HUMANHEAD bg: Special handling for "restart" fx enable/disable. + if ( restartActive && (activator != this) ) { + CancelEvents( &EV_Fx_Action ); + CancelEvents( &EV_Activate ); + CancelEvents( &EV_Fx_KillFx ); + Stop(); + CleanUp(); + BecomeInactive( TH_THINK ); + restartActive = false; + return; + } + if ( !restartActive && spawnArgs.GetFloat( "restart" ) ) { + restartActive = true; + } +// HUMANHEAD END + + //HUMANHEAD: aob + if( spawnArgs.GetBool("toggle") && IsActive(TH_THINK) ) { + ProcessEvent( &EV_Fx_KillFx ); + return; + } + //HUMANHEAD END + + //HUMANHEAD: aob - moved logic to helper function so I can call code specifically + CreateFx( activator ); + //HUMANHEAD END +} + +/* +================ +hhEntityFx::CreateFx + +HUMANHEAD: aob +================ +*/ +void hhEntityFx::CreateFx( idEntity *activator ) { + if ( g_skipFX.GetBool() ) { + return; + } + + float fxActionDelay; + const char *fx; + + if ( spawnArgs.GetString( "fx", "", &fx) ) { + Setup( fx ); + Start( gameLocal.time ); + //HUMANHEAD: aob - added so fx can be deleted at end of duration + if( RemoveWhenDone() ) { + PostEventMS( &EV_Remove, Duration() ); + } else { + PostEventMS( &EV_Fx_KillFx, Duration() ); + } + //HUMANHEAD END + BecomeActive( TH_THINK ); + } + + fxActionDelay = spawnArgs.GetFloat( "fxActionDelay" ); + if ( fxActionDelay != 0.0f ) { + nextTriggerTime = gameLocal.time + SEC2MS( fxActionDelay ); + } else { + // prevent multiple triggers on same frame + nextTriggerTime = gameLocal.time + 1; + } + PostEventSec( &EV_Fx_Action, fxActionDelay, activator ); +} + +/* +================ +hhEntityFx::StartFx + +creates an fx entity, ONLY CALL THIS ON THE SERVER. -rww +================ +*/ +hhEntityFx *hhEntityFx::StartFx( const char *fx, const idVec3 *useOrigin, const idMat3 *useAxis, idEntity *ent, bool bind ) +{ + + if ( g_skipFX.GetBool() || !fx || !*fx ) { + return NULL; + } + + idDict args; + args.SetBool( "start", true ); + args.Set( "fx", fx ); + hhEntityFx *nfx = static_cast( gameLocal.SpawnEntityType( hhEntityFx::Type, &args ) ); + if ( nfx->Joint() && *nfx->Joint() ) { + nfx->BindToJoint( ent, nfx->Joint(), true ); + nfx->SetOrigin( vec3_origin ); + } else { + nfx->SetOrigin( (useOrigin) ? *useOrigin : ent->GetPhysics()->GetOrigin() ); + nfx->SetAxis( (useAxis) ? *useAxis : ent->GetPhysics()->GetAxis() ); + } + + if ( bind ) { + // never bind to world spawn + if ( ent != gameLocal.world ) { + nfx->Bind( ent, true ); + } + } + nfx->Show(); + return nfx; +} + +/* +================ +hhEntityFx::SetParticleShaderParm + Allow setting shader parms directly to particle renderentities + Note that this functionality isn't set to the default SetShaderParm() + to avoid breaking any existing code +================ +*/ +void hhEntityFx::SetParticleShaderParm( int parmnum, float value ) { + + if ( ( parmnum < 0 ) || ( parmnum >= MAX_ENTITY_SHADER_PARMS ) ) { + gameLocal.Warning( "shader parm index (%d) out of range", parmnum ); + return; + } + + for( int ieff = 0; ieff < fxEffect->events.Num(); ieff++ ) { + const idFXSingleAction& fxaction = fxEffect->events[ieff]; + idFXLocalAction *useAction = NULL; + idFXLocalAction& laction = actions[ieff]; + if ( fxaction.sibling == -1 ) { + useAction = &laction; + } else { + useAction = &actions[fxaction.sibling]; + } + if ( !useAction ) { + continue; + } + switch( fxaction.type ) { + case FX_MODEL: { + useAction->renderEntity.shaderParms[parmnum] = value; + break; + } + } + } +} + +/* +================ +hhEntityFx::Event_ClearFx + + Clears any visual fx started when item(mob) was spawned +================ +*/ +void hhEntityFx::Event_ClearFx( void ) { + + if ( g_skipFX.GetBool() ) { + return; + } + + Stop(); + CleanUp(); + BecomeInactive( TH_THINK ); + + if ( spawnArgs.GetBool("test") ) { + PostEventMS( &EV_Activate, 0, this ); + } else { + if (!spawnArgs.GetBool("triggered")) { + float rest = spawnArgs.GetInt( "restart", "0" ); + if ( rest == 0.0f ) { + //HUMANHEAD: aob - added RemoveWhenDone and CancelEvents call + if( RemoveWhenDone() ) { + PostEventSec( &EV_Remove, 0.1f ); + } else { + CancelEvents( &EV_Fx_Action ); + } + //HUMANHEAD END + } else { + rest *= gameLocal.random.RandomFloat(); +// HUMANHEAD bg: Give ability for minimum restart time by adding offset. + rest += spawnArgs.GetFloat( "restartDelay", "0" ); +// HUMANHEAD END + PostEventSec( &EV_Activate, rest, this ); + } + } + } +} diff --git a/src/Prey/game_entityfx.h b/src/Prey/game_entityfx.h new file mode 100644 index 0000000..bae8d61 --- /dev/null +++ b/src/Prey/game_entityfx.h @@ -0,0 +1,62 @@ +#ifndef __HH_ENTITY_FX_H +#define __HH_ENTITY_FX_H + +class hhEntityFx : public idEntityFx { + CLASS_PROTOTYPE( hhEntityFx ); + +public: + hhEntityFx(); + virtual ~hhEntityFx(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //HUMANHEAD rww + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + //HUMANHEAD END + + virtual void Run( int time ); + + // HUMANHEAD nla + void SetUseAxis( fxAxis theAxis ) { }; + void SetFxInfo( const hhFxInfo &i ) { fxInfo = i; setFxInfo = true; removeWhenDone = fxInfo.RemoveWhenDone(); GetRenderEntity()->weaponDepthHack = fxInfo.UseWeaponDepthHack(); } + bool RemoveWhenDone() { return( removeWhenDone ); } + void RemoveWhenDone( bool remove ) { removeWhenDone = remove; } + void Toggle() { Nozzle( !IsActive(TH_THINK) ); } + void Nozzle( bool bOn ); + idMat3 DetermineAxis( const idFXSingleAction& fxaction ); + void CreateFx( idEntity *activator ); + + virtual void Hide(); + + static hhEntityFx *StartFx( const char *fx, const idVec3 *useOrigin, const idMat3 *useAxis, idEntity *ent, bool bind ); + void SetParticleShaderParm( int parmnum, float value ); + // HUMANHEAD END + +protected: + virtual void CleanUpSingleAction( const idFXSingleAction& fxaction, idFXLocalAction& laction ); + virtual void DormantBegin(); + virtual void DormantEnd(); + +protected: + void Event_Trigger( idEntity *activator ); + void Event_ClearFx( void ); + +protected: + // HUMANHEAD nla + enum fxAxis { AXIS_CURRENT, AXIS_NORMAL, AXIS_BOUNCE, AXIS_INCOMING, AXIS_CUSTOMLOCAL }; + // HUMANHEAD END + + // HUMANHEAD + hhFxInfo fxInfo; + bool setFxInfo; + bool removeWhenDone; + // HUMANHEAD END + + // HUMANHEAD bg + bool restartActive; + // HUMANHEAD END +}; + +#endif \ No newline at end of file diff --git a/src/Prey/game_entityspawner.cpp b/src/Prey/game_entityspawner.cpp new file mode 100644 index 0000000..cd91c9b --- /dev/null +++ b/src/Prey/game_entityspawner.cpp @@ -0,0 +1,117 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_SpawnEntity("spawnEntity", NULL, 'd'); + +CLASS_DECLARATION( idEntity, hhEntitySpawner ) + EVENT(EV_Activate, hhEntitySpawner::Event_Activate) + EVENT(EV_SpawnEntity, hhEntitySpawner::Event_SpawnEntity) +END_CLASS + +// +// Spawn() +// +void hhEntitySpawner::Spawn( void ) { + + maxSpawnCount = spawnArgs.GetInt("max_spawn", "-1"); // Default: Infinite + currSpawnCount = 0; + if(!spawnArgs.GetString("def_entity", "", entDefName)) { + gameLocal.Error("def_entity not specified for hhEntitySpawner"); + } + entSpawnArgs.Clear(); + idStr tmpStr; + idStr realKeyName; + + // Copy keys for monster + const idKeyValue *kv = spawnArgs.MatchPrefix("ent_", NULL); + while(kv) { + tmpStr = kv->GetKey(); + int usIndex = tmpStr.FindChar("ent_", '_'); + realKeyName = tmpStr.Mid(usIndex+1, strlen(kv->GetKey())-usIndex-1); + entSpawnArgs.Set(realKeyName, kv->GetValue()); + + kv = spawnArgs.MatchPrefix("ent_", kv); + } +} + +void hhEntitySpawner::Save(idSaveGame *savefile) const { + savefile->WriteString( entDefName ); + savefile->WriteDict( &entSpawnArgs ); + savefile->WriteInt( maxSpawnCount ); + savefile->WriteInt( currSpawnCount ); +} + +void hhEntitySpawner::Restore( idRestoreGame *savefile ) { + savefile->ReadString( entDefName ); + savefile->ReadDict( &entSpawnArgs ); + savefile->ReadInt( maxSpawnCount ); + savefile->ReadInt( currSpawnCount ); +} + +// +// Event_Activate() +// +void hhEntitySpawner::Event_Activate(idEntity *activator) { + Event_SpawnEntity(); +} + +// +// Event_Activate() +// +void hhEntitySpawner::Event_SpawnEntity( void ) { + idVec3 entSize; + + // Can't spawn anymore + if(maxSpawnCount >= 0 && currSpawnCount >= maxSpawnCount) { + return; + } + + idDict args = entSpawnArgs;; + + args.SetVector( "origin", GetPhysics()->GetOrigin()); + args.SetMatrix("rotation", GetAxis()); + + // entity collision checks for seeing if we are going to collide with another entity on spawn + const idDict *entDef = gameLocal.FindEntityDefDict( entDefName, false ); + if ( !entDef ) { + if (!entDefName) { //HUMANHEAD rww + gameLocal.Error("NULL entDefName in hhEntitySpawner::Event_SpawnEntity\n"); + } + else { + gameLocal.Warning( "Unknown Def '%s'", entDefName ); + } + return; + } + entDef->GetVector( "size", "0", entSize ); + idBounds bounds = idBounds( GetPhysics()->GetOrigin() ).Expand( max( entSize.x, entSize.y ) ); + idEntity* ents[MAX_GENTITIES]; + int numModels = gameLocal.clip.EntitiesTouchingBounds( bounds, -1, ents, MAX_GENTITIES ); + for ( int i = 0; i < numModels ; i++ ) { + if( ents[i] && ents[i]->IsType(idActor::Type) ) { + idThread::ReturnInt( false ); + return; + } + } + + idEntity *e = gameLocal.SpawnObject(entDefName, &args); + + if(!e) { + gameLocal.Error("hhEntitySpawner: Failed to spawn entity def named: %s", entDefName); + return; + } + + // Copy the targets that our case has to our newly spawned entity + for( int i = 0; i < targets.Num(); i++ ) { + e->targets.AddUnique(targets[i]); + } + + if(e->IsType(idAI::Type)) { + static_cast(e)->viewAxis = GetAxis(); + } + + currSpawnCount++; + idThread::ReturnInt( true ); +} diff --git a/src/Prey/game_entityspawner.h b/src/Prey/game_entityspawner.h new file mode 100644 index 0000000..701ab2a --- /dev/null +++ b/src/Prey/game_entityspawner.h @@ -0,0 +1,25 @@ +#ifndef GAME_ENTITYSPAWNER_H +#define GAME_ENTITYSPAWNER_H + +// +// hhEntitySpawner +// +class hhEntitySpawner : public idEntity { +public: + CLASS_PROTOTYPE( hhEntitySpawner ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +protected: + void Event_Activate(idEntity *activator); + void Event_SpawnEntity( void ); + + idStr entDefName; + idDict entSpawnArgs; + int maxSpawnCount; + int currSpawnCount; +}; + +#endif \ No newline at end of file diff --git a/src/Prey/game_events.cpp b/src/Prey/game_events.cpp new file mode 100644 index 0000000..79dd453 --- /dev/null +++ b/src/Prey/game_events.cpp @@ -0,0 +1,12 @@ +// game_events.cpp +// +// All of our events that have use to multiple classes are kept together so everyone can get at them + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + + + diff --git a/src/Prey/game_events.h b/src/Prey/game_events.h new file mode 100644 index 0000000..60cfa48 --- /dev/null +++ b/src/Prey/game_events.h @@ -0,0 +1,18 @@ +#ifndef __PREY_EVENTS_H__ +#define __PREY_EVENTS_H__ + + +/* +extern const idEventDef EV_Kill; + +extern const idEventDef EV_Trigger; +extern const idEventDef EV_Retrigger; +extern const idEventDef EV_UnTrigger; +extern const idEventDef EV_PollForUntouch; + +extern const idEventDef EV_Hit; +extern const idEventDef EV_Stay; +extern const idEventDef EV_Double; +*/ + +#endif // __PREY_EVENTS_H__ diff --git a/src/Prey/game_fixedpod.cpp b/src/Prey/game_fixedpod.cpp new file mode 100644 index 0000000..e7b27f8 --- /dev/null +++ b/src/Prey/game_fixedpod.cpp @@ -0,0 +1,209 @@ +// Game_FixedPod.cpp +// +// non-moving exploding pod + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_ExplodedBy("", "e"); +const idEventDef EV_SpawnShrapnel(""); + +CLASS_DECLARATION(idEntity, hhFixedPod) + EVENT( EV_Activate, hhFixedPod::Event_Trigger ) + EVENT( EV_ExplodeDamage, hhFixedPod::Event_ExplodeDamage ) + EVENT( EV_ExplodedBy, hhFixedPod::Event_ExplodedBy ) + EVENT( EV_SpawnShrapnel, hhFixedPod::Event_SpawnDebris ) + EVENT( EV_Broadcast_AssignFx, hhFixedPod::Event_AssignFx ) +END_CLASS + + +hhFixedPod::hhFixedPod() { + fx = NULL; +} + +//========================================================================== +// +// hhFixedPod::Spawn +// +//========================================================================== +void hhFixedPod::Spawn(void) { + + fl.takedamage = true; + + // setup the clipModel + GetPhysics()->SetContents( CONTENTS_SOLID ); + + // Spawn the energy beam + hhBeamSystem *beam = hhBeamSystem::SpawnBeam( GetOrigin(), spawnArgs.GetString("beam") ); + if( beam ) { + beam->SetOrigin(GetPhysics()->GetOrigin() - GetPhysics()->GetAxis()[2]*28); + beam->SetAxis(GetPhysics()->GetAxis()); + if (IsBound() || spawnArgs.GetBool("force_bind")) { + beam->Bind(this, false); // Only bind if we will be moving + } + beam->SetTargetLocation(beam->GetPhysics()->GetOrigin() + GetPhysics()->GetAxis()[2] * 56); + // Bound entities automatically removed upon destruction + } + + hhFxInfo fxInfo; + if (IsBound() || spawnArgs.GetBool("force_bind")) { + fxInfo.SetEntity( this ); // Only bind if we will be moving + } + fxInfo.RemoveWhenDone( false ); + fxInfo.NoRemoveWhenUnbound( true ); + BroadcastFxInfo( spawnArgs.GetString("fx_energybeam"), GetOrigin(), GetAxis(), &fxInfo, &EV_Broadcast_AssignFx ); + + StartSound( "snd_idle", SND_CHANNEL_IDLE, 0, true, NULL ); +} + +//========================================================================== +// +// hhFixedPod::Event_AssignFxSmoke +// +//========================================================================== +void hhFixedPod::Event_AssignFx( hhEntityFx* fx ) { + this->fx = fx; +} + +void hhFixedPod::Save(idSaveGame *savefile) const { + fx.Save(savefile); +} + +void hhFixedPod::Restore( idRestoreGame *savefile ) { + fx.Restore(savefile); +} + +//========================================================================== +// +// hhFixedPod::Killed +// +//========================================================================== +void hhFixedPod::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + fl.takedamage = false; + + // Activate targets + ActivateTargets( attacker ); + + GetPhysics()->SetContents( 0 ); + + // Need to post an event because already in physics code now, can't nest a projectile spawn from within physics code + // currently, because rigid body physics ::Evaluate is not reentrant friendly (has a static timer) + PostEventMS( &EV_ExplodedBy, 0, attacker ); +} + + +//========================================================================== +/// +// hhFixedPod::Explode +// +//========================================================================== +void hhFixedPod::Explode( idEntity *attacker ) { + hhFxInfo fxInfo; + + if (fx.IsValid()) { + fx->Hide(); + fx->PostEventMS(&EV_Remove, 0); + } + + // Spawn explosion + StopSound( SND_CHANNEL_IDLE, true ); + StartSound( "snd_explode", SND_CHANNEL_ANY, 0, true, NULL ); + + fxInfo.SetNormal( idVec3(0.0f, 0.0f, 1.0f) ); + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfoPrefixed( "fx_detonate", GetOrigin(), GetAxis(), &fxInfo ); + + Hide(); + + PostEventMS( &EV_SpawnShrapnel, 10 ); + PostEventMS( &EV_ExplodeDamage, 250, attacker ); // NOTE: This even MUST occur before the remove event + PostEventMS( &EV_Remove, 500 ); // Remove after a small delay to allow sound commands to execute +} + +//========================================================================== +// +// hhFixedPod::SpawnDebris +// +//========================================================================== +void hhFixedPod::SpawnDebris() { + idEntity* ent = NULL; + idVec3 launchDir; + int amount = 0; + idDebris* debris = NULL; + const idDict *dict = NULL; + + int numShrapnel = spawnArgs.GetInt( "debris_count" ); + if( !numShrapnel ) { + return; + } + + for( const idKeyValue* kv = spawnArgs.MatchPrefix("def_debris", NULL); kv; kv = spawnArgs.MatchPrefix("def_debris", kv) ) { + if( !kv->GetValue().Length() ) { + continue; + } + + dict = gameLocal.FindEntityDefDict( kv->GetValue().c_str(), false ); + if( !dict ) { + continue; + } + + amount = hhMath::hhMax( 1, gameLocal.random.RandomInt(numShrapnel) ); + for ( int i = 0; i < amount; i++ ) { + launchDir = hhUtils::RandomVector(); + launchDir.Normalize(); + + gameLocal.SpawnEntityDef( *dict, &ent ); + if ( !ent || !ent->IsType( idDebris::Type ) ) { + gameLocal.Error( "'%s' is not an idDebris", kv->GetValue().c_str() ); + } + + debris = static_cast(ent); + debris->Create( this, GetOrigin(), launchDir.ToMat3() ); + debris->Launch(); + } + } +} + +//========================================================================== +// +// hhFixedPod::Event_ExplodeDamage +// +// Applies the radius damage slightly after the actual explosion, so that +// when one explodes it will cascade and explode other fixed pods. +//========================================================================== + +void hhFixedPod::Event_ExplodeDamage( idEntity *attacker ) { + gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), this, attacker, this, this, spawnArgs.GetString("def_explodedamage") ); +} + +//========================================================================== +// +// hhFixedPod::Event_ExplodedBy +// +//========================================================================== +void hhFixedPod::Event_ExplodedBy( idEntity *activator ) { + Explode(activator); +} + +//========================================================================== +// +// hhFixedPod::Event_Trigger +// +//========================================================================== + +void hhFixedPod::Event_Trigger( idEntity *activator ) { + Explode(activator); +} + +//========================================================================== +// +// hhFixedPod::Event_SpawnDebris +// +//========================================================================== + +void hhFixedPod::Event_SpawnDebris() { + SpawnDebris(); +} + diff --git a/src/Prey/game_fixedpod.h b/src/Prey/game_fixedpod.h new file mode 100644 index 0000000..bc312cd --- /dev/null +++ b/src/Prey/game_fixedpod.h @@ -0,0 +1,31 @@ + +#ifndef __GAME_FIXEDPOD_H__ +#define __GAME_FIXEDPOD_H__ + +extern const idEventDef EV_ExplodedBy; + +class hhFixedPod : public idEntity { +public: + CLASS_PROTOTYPE( hhFixedPod ); + + hhFixedPod(); + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + void Explode( idEntity *attacker ); + void SpawnDebris(); + +protected: + void Event_Trigger( idEntity *activator ); + void Event_ExplodeDamage( idEntity *attacker ); + void Event_ExplodedBy( idEntity *activator ); + void Event_SpawnDebris(); + void Event_AssignFx( hhEntityFx* fx ); + + idEntityPtr fx; +}; + + +#endif /* __GAME_FIXEDPOD_H__ */ diff --git a/src/Prey/game_forcefield.cpp b/src/Prey/game_forcefield.cpp new file mode 100644 index 0000000..8942db4 --- /dev/null +++ b/src/Prey/game_forcefield.cpp @@ -0,0 +1,625 @@ +//************************************************************************** +//** +//** GAME_FORCEFIELD.CPP +//** +//** Game code for the forcefield +//** +//************************************************************************** + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION( idEntity, hhForceField ) + EVENT( EV_Activate, hhForceField::Event_Activate ) +END_CLASS + +/* +=========== +hhForceField::Spawn +=========== +*/ +void hhForceField::Spawn(void) { + fl.takedamage = true; + SetShaderParm( SHADERPARM_TIMEOFFSET, 1.0f ); + SetShaderParm( SHADERPARM_MODE, 0.0f ); + + fade = 0.0f; + + activationRate = spawnArgs.GetFloat( "activationRate" ); + deactivationRate = spawnArgs.GetFloat( "deactivationRate" ); + undamageFadeRate = spawnArgs.GetFloat( "undamageFadeRate" ); + +// BecomeActive( TH_THINK|TH_TICKER ); + + cachedContents = CONTENTS_FORCEFIELD | CONTENTS_BLOCK_RADIUSDAMAGE | CONTENTS_SHOOTABLE; + + physicsObj.SetSelf( this ); + + if (spawnArgs.GetBool("isSimpleBox")) { + // Simple boxes are cheaper and can be bound to other objects + physicsObj.SetClipModel( new idClipModel(idTraceModel(GetPhysics()->GetBounds())), 1.0f ); + physicsObj.SetContents( cachedContents ); + physicsObj.SetOrigin( GetOrigin() ); + physicsObj.SetAxis( GetAxis() ); + SetPhysics( &physicsObj ); + } + else { + // Non-simple has real per-poly collision with it's model, uses default static physics because we don't have a tracemodel. + // This loses the activation of contacts on the object (so some things may not fall through when disabled), but in my tests, + // the spirit proxy falls through fine. NOTE: To fix the movables, etc. not falling when these are turned off, I'm manually + // Activating appropriate physics that overlap the bounds when Turned off. +// physicsObj.SetClipModel( new idClipModel(GetPhysics()->GetClipModel()), 1.0f ); +// physicsObj.SetContents( cachedContents ); +// physicsObj.SetOrigin( GetOrigin() ); +// physicsObj.SetAxis( GetAxis() ); +// SetPhysics( &physicsObj ); + + // Apparently the flags need to be on the material to make projectiles hit these. + GetPhysics()->SetContents(cachedContents); + } + + EnterOnState(); + damagedState = false; + + nextCollideFxTime = gameLocal.time; + + + fl.networkSync = true; //always want to sync over the net +} + +void hhForceField::Save( idSaveGame *savefile ) const { + savefile->WriteInt( fieldState ); + savefile->WriteBool( damagedState ); + savefile->WriteFloat( activationRate ); + savefile->WriteFloat( deactivationRate ); + savefile->WriteFloat( undamageFadeRate ); + savefile->WriteInt( applyImpulseAttempts ); + savefile->WriteInt( cachedContents ); + savefile->WriteFloat( fade ); + savefile->WriteInt( nextCollideFxTime ); + savefile->WriteStaticObject( physicsObj ); +} + +void hhForceField::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( reinterpret_cast ( fieldState ) ); + savefile->ReadBool( damagedState ); + savefile->ReadFloat( activationRate ); + savefile->ReadFloat( deactivationRate ); + savefile->ReadFloat( undamageFadeRate ); + savefile->ReadInt( applyImpulseAttempts ); + savefile->ReadInt( cachedContents ); + savefile->ReadFloat( fade ); + savefile->ReadInt( nextCollideFxTime ); + savefile->ReadStaticObject( physicsObj ); + + // Only restore physics if we were using it before + if (spawnArgs.GetBool("isSimpleBox")) { + RestorePhysics( &physicsObj ); + } +} + +void hhForceField::WriteToSnapshot( idBitMsgDelta &msg ) const { + physicsObj.WriteToSnapshot(msg); + msg.WriteBits(damagedState, 1); + msg.WriteFloat(activationRate); + msg.WriteFloat(deactivationRate); + msg.WriteFloat(undamageFadeRate); + msg.WriteBits(applyImpulseAttempts, 32); + msg.WriteBits(cachedContents, 32); + msg.WriteFloat(fade); + msg.WriteBits(nextCollideFxTime, 32); + msg.WriteBits(fieldState, 4); + msg.WriteBits(IsHidden(), 1); + + msg.WriteFloat(renderEntity.shaderParms[SHADERPARM_TIMEOFFSET]); + msg.WriteFloat(renderEntity.shaderParms[SHADERPARM_MODE]); +} + +void hhForceField::ReadFromSnapshot( const idBitMsgDelta &msg ) { + physicsObj.ReadFromSnapshot(msg); + damagedState = !!msg.ReadBits(1); + activationRate = msg.ReadFloat(); + deactivationRate = msg.ReadFloat(); + undamageFadeRate = msg.ReadFloat(); + applyImpulseAttempts = msg.ReadBits(32); + cachedContents = msg.ReadBits(32); + fade = msg.ReadFloat(); + nextCollideFxTime = msg.ReadBits(32); + fieldState = (States)msg.ReadBits(4); + bool hidden = !!msg.ReadBits(1); + if (IsHidden() != hidden) { + if (hidden) { + Hide(); + SetSkinByName( spawnArgs.GetString("skin_off" ) ); + GetPhysics()->SetContents( 0 ); + } + else { + Show(); + SetSkinByName( NULL ); + GetPhysics()->SetContents( cachedContents ); + } + } + + float f; + bool changed = false; + + f = msg.ReadFloat(); + changed = (changed || (renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] != f)); + renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = f; + + f = msg.ReadFloat(); + changed = (changed || (renderEntity.shaderParms[SHADERPARM_MODE] != f)); + renderEntity.shaderParms[SHADERPARM_MODE] = f; + + if (changed) { + UpdateModel(); + UpdateVisuals(); + } +} + +void hhForceField::ClientPredictionThink( void ) { + idEntity::ClientPredictionThink(); +} + +/* +=========== +hhForceField::ApplyImpulse +=========== +*/ +void hhForceField::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + + if (!ent->IsType(idActor::Type)) { + // Don't play sound for actor footsteps, landings. They play their own localized sounds for + // the sake of very large forcefields. + StartSound( "snd_impulse", SND_CHANNEL_ANY ); + } + EnterDamagedState(); + + // Apply the hit effect + trace_t trace; + idStr fxCollide = spawnArgs.GetString( "fx_collide" ); + + if ( fxCollide.Length() && gameLocal.time > nextCollideFxTime && point != GetOrigin()) { + // Trace to find normal + idVec3 dir = force; + dir.NormalizeFast(); + dir *= 200; + idVec3 start = point-dir; + idVec3 end = point+dir; + memset(&trace, 0, sizeof(trace)); + gameLocal.clip.TracePoint(trace, start, end, CONTENTS_FORCEFIELD, ent); + + if (trace.fraction < 1.0f) { + // Spawn fx oriented to normal of collision + hhFxInfo fxInfo; + fxInfo.SetNormal( trace.c.normal ); + BroadcastFxInfo( fxCollide, trace.c.point, mat3_identity, &fxInfo, 0, false ); //rww - changed to not broadcast + nextCollideFxTime = gameLocal.time + 200; + } + } +} + +/* +=========== +hhForceField::Ticker +=========== +*/ +void hhForceField::Ticker( void ) { + + if( fieldState == StatePreTurningOn ) { + idEntity* entityList[MAX_GENTITIES]; + idEntity* entity = NULL; + idPhysics* physics = NULL; + int numEntities = 0; + int numEntitiesImpulseAppliedTo = 0; + idVec3 force = DetermineForce() * GetAxis(); + + numEntities = gameLocal.clip.EntitiesTouchingBounds( GetPhysics()->GetAbsBounds(), MASK_SOLID, entityList, MAX_GENTITIES ); + for( int ix = 0; ix < numEntities; ++ix ) { + entity = entityList[ix]; + + if( !entity ) { + continue; + } + + if( entity == this ) { + continue; + } + + //Removing mass from any calculations regarding impulse + entity->ApplyImpulse( this, 0, entity->GetOrigin(), force * entity->GetPhysics()->GetMass() ); + numEntitiesImpulseAppliedTo++; + } + + --applyImpulseAttempts; + if( !numEntitiesImpulseAppliedTo || !applyImpulseAttempts ) { + fieldState = StateTurningOn; + GetPhysics()->SetContents( cachedContents ); + SetSkin( NULL ); + StartSound( "snd_start", SND_CHANNEL_ANY ); + + Show(); + + EnterTurningOnState(); + } + } else if( fieldState == StateTurningOn ) { + float deltaTime = MS2SEC( gameLocal.msec ); + + renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] += activationRate * deltaTime; + if( renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] >= 1.0f ) { + EnterOnState(); + } + + UpdateVisuals(); + } else if( fieldState == StateTurningOff ) { + float deltaTime = MS2SEC( gameLocal.msec ); + + renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] -= deactivationRate * deltaTime; + if( renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] <= 0.0f ) { + EnterOffState(); + } + + UpdateVisuals(); + } + + if( damagedState ) { + float deltaTime = MS2SEC( gameLocal.msec ); + + // Fade parm back to normal + fade -= undamageFadeRate * deltaTime; + if ( fade <= 0.0f ) { // Finished fading + fade = 0; + damagedState = false; + } + + SetShaderParm( SHADERPARM_MODE, fade ); + } + + if (!damagedState && (fieldState==StateOn || fieldState==StateOff)) { + BecomeInactive( TH_TICKER ); + } +} + +/* +=========== +hhForceField::DetermineThinnestAxis +=========== +*/ +int hhForceField::DetermineThinnestAxis() { + int best = 0; + idBounds bounds = GetPhysics()->GetBounds(); + for( int i = 1; i < 3; ++i ) { + if ( bounds[1][ i ] - bounds[0][ i ] < bounds[1][ best ] - bounds[0][ best ] ) { + best = i; + } + } + + return best; +} + +/* +=========== +hhForceField::DetermineForce +=========== +*/ +idVec3 hhForceField::DetermineForce() { + idVec3 force( vec3_origin ); + float forceMagnitude = spawnArgs.GetFloat( "forceMagnitude" ); + int dir = spawnArgs.GetBool( "positiveDir", "1" ) ? 1.0f : -1.0f; + + force[DetermineThinnestAxis()] = forceMagnitude * dir; + + return force; +} + +/* +=========== +hhForceField::IsAtRest + +Used to activate entites at rest +=========== +*/ +bool hhForceField::IsAtRest( int id ) const { + return ( fieldState != StateOff ); +} + +/* +=========== +hhForceField::Event_Activate +=========== +*/ +void hhForceField::Event_Activate( idEntity *activator ) { + switch( fieldState ) { + case StatePreTurningOn: + EnterTurningOffState(); + break; + case StateTurningOff: + EnterPreTurningOnState(); + break; + case StateTurningOn: + EnterTurningOffState(); + break; + case StateOn: + EnterTurningOffState(); + break; + case StateOff: + EnterPreTurningOnState(); + break; + } +} + +/* +=========== +hhForceField::EnterDamagedState +=========== +*/ +void hhForceField::EnterDamagedState() { + damagedState = true; + fade = 1.0f; + SetShaderParm( SHADERPARM_MODE, fade ); // Instantly change to the new shader + BecomeActive( TH_TICKER ); +} + +/* +=========== +hhForceField::EnterPreTurningOnState +=========== +*/ +void hhForceField::EnterPreTurningOnState() { + fieldState = StatePreTurningOn; + applyImpulseAttempts = spawnArgs.GetInt( "applyImpulseAttempts" ); + BecomeActive( TH_TICKER ); +} + +/* +=========== +hhForceField::EnterOnState +=========== +*/ +void hhForceField::EnterOnState() { + fieldState = StateOn; + StartSound( "snd_loop", SND_CHANNEL_IDLE ); + //HUMANHEAD PCF rww 05/15/06 - prevent stuck-in-forcefield by killing everything within + if (gameLocal.isMultiplayer) { + gameLocal.KillBoxMasked( this, CONTENTS_BODY ); + } + //HUMANHEAD END +} + +/* +=========== +hhForceField::EnterTurningOnState +=========== +*/ +void hhForceField::EnterTurningOnState() { + GetPhysics()->SetContents( cachedContents ); + SetSkin( NULL ); + StartSound( "snd_start", SND_CHANNEL_ANY ); + + Show(); + BecomeActive( TH_TICKER ); +} + +/* +=========== +hhForceField::EnterTurningOffState +=========== +*/ +void hhForceField::EnterTurningOffState() { + fieldState = StateTurningOff; + StopSound( SND_CHANNEL_IDLE ); + StartSound( "snd_stop", SND_CHANNEL_ANY ); + BecomeActive( TH_TICKER ); +} + +/* +=========== +hhForceField::EnterOffState +=========== +*/ +void hhForceField::EnterOffState() { + fieldState = StateOff; + if (spawnArgs.GetBool("isSimpleBox")) { + GetPhysics()->ActivateContactEntities(); + } + else { + // To allow complex shaped forcefields, we need to manually activate the physics of any contacts here + idClipModel *clipModels[ MAX_GENTITIES ]; + idClipModel *cm; + idBounds bounds = GetPhysics()->GetAbsBounds(); + bounds.ExpandSelf(bounds.GetRadius()*0.1f); // Expand the bounds by 10% to catch things on the surface + int num = gameLocal.clip.ClipModelsTouchingBounds( bounds, MASK_ALL, clipModels, MAX_GENTITIES ); + for ( int i=0;iGetEntity(); + if (hit && hit->GetPhysics()->IsAtRest() && + (hit->GetPhysics()->IsType(idPhysics_RigidBody::Type) || hit->GetPhysics()->IsType(idPhysics_AF::Type)) ) { + if ( !hit->IsType( hhVehicle::Type ) ) { //HUMANHEAD jsh PCF 5/26/06: fix 30hz vehicle console jittering + hit->GetPhysics()->Activate(); + } + } + } + } + + // HUMANHEAD PCF pdm 04/27/06: Unbind any bound projectiles + idEntity *ent; + idEntity *next; + for( ent = teamChain; ent != NULL; ent = next ) { + next = ent->GetTeamChain(); + if ( ent && ent->IsType( hhProjectile::Type ) ) { + ent->Unbind(); // bjk drops all bound projectiles such as arrows and mines + next = teamChain; + + //HUMANHEAD bjk PCF (4-28-06) - explode crawlers + if (ent->IsType(hhProjectileStickyCrawlerGrenade::Type)) { + static_cast(ent)->PostEventSec( &EV_Explode, 0.2f ); + } + } + } + + GetPhysics()->SetContents( 0 ); + Hide(); + SetSkinByName( spawnArgs.GetString("skin_off" ) ); // Set the force field to completely not draw +} + +//-------------------------------------------------------------------------- +// hhShuttleForcefield +//-------------------------------------------------------------------------- +CLASS_DECLARATION( idEntity, hhShuttleForceField ) + EVENT( EV_Activate, hhShuttleForceField::Event_Activate ) +END_CLASS + +void hhShuttleForceField::Spawn() { + nextCollideFxTime = gameLocal.time; + GetPhysics()->SetContents(CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + + // Start in the on state + fieldState = StateOn; + fade.Init(gameLocal.time, 0, 1.0f, 1.0f); + SetShaderParm(SHADERPARM_TIMEOFFSET, fade.GetCurrentValue(gameLocal.time)); + + fl.networkSync = true; //rww +} + +void hhShuttleForceField::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + trace_t trace; + idStr fxCollide = spawnArgs.GetString( "fx_collide" ); + + if ( fxCollide.Length() && gameLocal.time > nextCollideFxTime && point != GetOrigin()) { + + // Trace to find normal + idVec3 dir = force; + dir.NormalizeFast(); + dir *= 200; + idVec3 start = point-dir; + idVec3 end = point+dir; + memset(&trace, 0, sizeof(trace)); + gameLocal.clip.TracePoint(trace, start, end, CONTENTS_SOLID, ent); + + if (trace.fraction < 1.0f) { + // Spawn fx oriented to normal of collision + hhFxInfo fxInfo; + fxInfo.SetNormal( trace.c.normal ); + BroadcastFxInfo( fxCollide, trace.c.point, mat3_identity, &fxInfo ); + nextCollideFxTime = gameLocal.time + 200; + + StartSound( "snd_collide", SND_CHANNEL_ANY, 0, true, NULL ); + ActivatePrefixed( "triggerCollide", this ); // bg: Feedback hook. + } + } +} + +void hhShuttleForceField::Ticker() { + SetShaderParm(SHADERPARM_TIMEOFFSET, fade.GetCurrentValue(gameLocal.time)); + if (fade.IsDone(gameLocal.time)) { + if (fieldState == StateTurningOn) { + // Entering On state + fieldState = StateOn; + GetPhysics()->SetContents(CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + } + else if (fieldState == StateTurningOff) { + // Entering Off state + fieldState = StateOff; + GetPhysics()->SetContents(0); + } + + BecomeInactive(TH_TICKER); + } +} + +void hhShuttleForceField::Event_Activate(idEntity *activator) { + int duration = SEC2MS(spawnArgs.GetFloat("fade_duration")); + float currentFade = fade.GetCurrentValue(gameLocal.time); + if (fieldState == StateOn || fieldState == StateTurningOn) { + // Entering TurningOff state + fade.Init(gameLocal.time, duration, currentFade, 0.0f); + fieldState = StateTurningOff; + } + else if (fieldState == StateOff || fieldState == StateTurningOff) { + // Entering TurningOn state + fade.Init(gameLocal.time, duration, currentFade, 1.0f); + fieldState = StateTurningOn; + //HUMANHEAD PCF rww 05/15/06 - prevent stuck-in-forcefield by killing everything within + if (gameLocal.isMultiplayer) { + gameLocal.KillBoxMasked( this, CONTENTS_BODY ); + } + //HUMANHEAD END + } + BecomeActive( TH_TICKER ); +} + +void hhShuttleForceField::Save( idSaveGame *savefile ) const { + savefile->WriteInt( nextCollideFxTime ); + savefile->WriteInt( fieldState ); + savefile->WriteFloat( fade.GetStartTime() ); // idInterpolate + savefile->WriteFloat( fade.GetDuration() ); + savefile->WriteFloat( fade.GetStartValue() ); + savefile->WriteFloat( fade.GetEndValue() ); +} + +void hhShuttleForceField::Restore( idRestoreGame *savefile ) { + float set; + + savefile->ReadInt( nextCollideFxTime ); + savefile->ReadInt( reinterpret_cast ( fieldState ) ); + + savefile->ReadFloat( set ); // idInterpolate + fade.SetStartTime( set ); + savefile->ReadFloat( set ); + fade.SetDuration( set ); + savefile->ReadFloat( set ); + fade.SetStartValue(set); + savefile->ReadFloat( set ); + fade.SetEndValue( set ); +} + +void hhShuttleForceField::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteBits(GetPhysics()->GetContents(), 32); + + msg.WriteBits(nextCollideFxTime, 32); + msg.WriteBits(fieldState, 8); + + msg.WriteFloat(fade.GetStartTime()); + msg.WriteFloat(fade.GetDuration()); + msg.WriteFloat(fade.GetStartValue()); + msg.WriteFloat(fade.GetEndValue()); + + msg.WriteFloat(renderEntity.shaderParms[SHADERPARM_TIMEOFFSET]); + msg.WriteFloat(renderEntity.shaderParms[SHADERPARM_MODE]); +} + +void hhShuttleForceField::ReadFromSnapshot( const idBitMsgDelta &msg ) { + int contents = msg.ReadBits(32); + if (contents != GetPhysics()->GetContents()) { + GetPhysics()->SetContents(contents); + } + + nextCollideFxTime = msg.ReadBits(32); + fieldState = (States)msg.ReadBits(8); + + fade.SetStartTime(msg.ReadFloat()); + fade.SetDuration(msg.ReadFloat()); + fade.SetStartValue(msg.ReadFloat()); + fade.SetEndValue(msg.ReadFloat()); + + float f; + bool changed = false; + + f = msg.ReadFloat(); + changed = (changed || (renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] != f)); + renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = f; + + f = msg.ReadFloat(); + changed = (changed || (renderEntity.shaderParms[SHADERPARM_MODE] != f)); + renderEntity.shaderParms[SHADERPARM_MODE] = f; + + if (changed) { + UpdateVisuals(); + } +} + +void hhShuttleForceField::ClientPredictionThink( void ) { + idEntity::ClientPredictionThink(); +} diff --git a/src/Prey/game_forcefield.h b/src/Prey/game_forcefield.h new file mode 100644 index 0000000..72f65f6 --- /dev/null +++ b/src/Prey/game_forcefield.h @@ -0,0 +1,94 @@ +#ifndef __GAME_FORCEFIELD_H__ +#define __GAME_FORCEFIELD_H__ + +class hhForceField : public idEntity { + CLASS_PROTOTYPE( hhForceField ); + +public: + void Spawn(); + void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - network code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + +protected: + void Ticker(); + + int DetermineThinnestAxis(); + idVec3 DetermineForce(); + + virtual bool IsAtRest( int id ) const; + + void EnterDamagedState(); + void EnterPreTurningOnState(); + void EnterTurningOnState(); + void EnterOnState(); + void EnterTurningOffState(); + void EnterOffState(); + +protected: + void Event_Activate( idEntity *activator ); + +protected: + enum States { + StatePreTurningOn = 0, + StateTurningOn, + StateOn, + StateTurningOff, + StateOff + } fieldState; + bool damagedState; + + float activationRate; + float deactivationRate; + float undamageFadeRate; + + int applyImpulseAttempts; + + int cachedContents; + + float fade; + + int nextCollideFxTime; + + hhPhysics_StaticForceField physicsObj; +}; + + +class hhShuttleForceField : public idEntity { + CLASS_PROTOTYPE( hhShuttleForceField ); + +public: + void Spawn(); + void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - network code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + + void Ticker(); + +protected: + void Event_Activate(idEntity *activator); + + enum States { + StatePreTurningOn = 0, + StateTurningOn, + StateOn, + StateTurningOff, + StateOff + } fieldState; + +private: + int nextCollideFxTime; + idInterpolate fade; +}; + +#endif /* __GAME_FORCEFIELD_H__ */ diff --git a/src/Prey/game_fxinfo.cpp b/src/Prey/game_fxinfo.cpp new file mode 100644 index 0000000..5609647 --- /dev/null +++ b/src/Prey/game_fxinfo.cpp @@ -0,0 +1,343 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +CLASS_DECLARATION( idClass, hhFxInfo ) +END_CLASS + + +hhFxInfo::hhFxInfo() { + Reset(); +} + +hhFxInfo::hhFxInfo( const hhFxInfo* fxInfo ) { + Assign( fxInfo ); +} + +hhFxInfo::hhFxInfo( const hhFxInfo& fxInfo ) { + Assign( &fxInfo ); +} + +void hhFxInfo::Save(idSaveGame *savefile) const { + fxInfoFlags_s infoFlags = flags; + LittleBitField( &infoFlags, sizeof( infoFlags ) ); + savefile->Write( &infoFlags, sizeof( infoFlags ) ); + + savefile->WriteVec3( normal ); + savefile->WriteVec3( incomingVector ); + savefile->WriteVec3( bounceVector ); + savefile->WriteString( bindBone ); + entity.Save(savefile); +} + +void hhFxInfo::Restore( idRestoreGame *savefile ) { + savefile->Read( &flags, sizeof( flags ) ); + LittleBitField( &flags, sizeof( flags ) ); + + savefile->ReadVec3( normal ); + savefile->ReadVec3( incomingVector ); + savefile->ReadVec3( bounceVector ); + savefile->ReadString( bindBone ); + entity.Restore(savefile); +} + +void hhFxInfo::WriteToSnapshot( idBitMsgDelta &msg ) const { + /* + msg.WriteBits(flags.normalIsSet, 1); + msg.WriteBits(flags.incomingIsSet, 1); + msg.WriteBits(flags.bounceIsSet, 1); + msg.WriteBits(flags.start, 1); + msg.WriteBits(flags.removeWhenDone, 1); + msg.WriteBits(flags.toggle, 1); + msg.WriteBits(flags.onlyVisibleInSpirit, 1); + msg.WriteBits(flags.onlyInvisibleInSpirit, 1); + msg.WriteBits(flags.useWeaponDepthHack, 1); + msg.WriteBits(flags.bNoRemoveWhenUnbound, 1); + + msg.WriteFloat(normal.x); + msg.WriteFloat(normal.y); + msg.WriteFloat(normal.z); + msg.WriteFloat(incomingVector.x); + msg.WriteFloat(incomingVector.y); + msg.WriteFloat(incomingVector.z); + msg.WriteFloat(bounceVector.x); + msg.WriteFloat(bounceVector.y); + msg.WriteFloat(bounceVector.z); + msg.WriteString(bindBone); + msg.WriteBits(entity.GetSpawnId(), 32); + */ + + msg.WriteBits(flags.normalIsSet, 1); + msg.WriteBits(flags.removeWhenDone, 1); + msg.WriteFloat(normal.x); + msg.WriteFloat(normal.y); + msg.WriteFloat(normal.z); +} + +void hhFxInfo::ReadFromSnapshot( const idBitMsgDelta &msg ) { + /* + flags.normalIsSet = !!msg.ReadBits(1); + flags.incomingIsSet = !!msg.ReadBits(1); + flags.bounceIsSet = !!msg.ReadBits(1); + flags.start = !!msg.ReadBits(1); + flags.removeWhenDone = !!msg.ReadBits(1); + flags.toggle = !!msg.ReadBits(1); + flags.onlyVisibleInSpirit = !!msg.ReadBits(1); + flags.onlyInvisibleInSpirit = !!msg.ReadBits(1); + flags.useWeaponDepthHack = !!msg.ReadBits(1); + flags.bNoRemoveWhenUnbound = !!msg.ReadBits(1); + + normal.x = msg.ReadFloat(); + normal.y = msg.ReadFloat(); + normal.z = msg.ReadFloat(); + incomingVector.x = msg.ReadFloat(); + incomingVector.y = msg.ReadFloat(); + incomingVector.z = msg.ReadFloat(); + bounceVector.x = msg.ReadFloat(); + bounceVector.y = msg.ReadFloat(); + bounceVector.z = msg.ReadFloat(); + + char buf[128]; + msg.ReadString(buf, 128); + bindBone = buf; + entity.SetSpawnId(msg.ReadBits(32)); + */ + + flags.normalIsSet = !!msg.ReadBits(1); + flags.removeWhenDone = !!msg.ReadBits(1); + normal.x = msg.ReadFloat(); + normal.y = msg.ReadFloat(); + normal.z = msg.ReadFloat(); +} + +hhFxInfo& hhFxInfo::Assign( const hhFxInfo* fxInfo ) { + Reset(); + + if( !fxInfo ) { + return *this; + } + + if( fxInfo->NormalIsSet() ) { + SetNormal( fxInfo->GetNormal() ); + } + + if( fxInfo->IncomingVectorIsSet() ) { + SetIncomingVector( fxInfo->GetIncomingVector() ); + } + + if( fxInfo->BounceVectorIsSet() ) { + SetBounceVector( fxInfo->GetBounceVector() ); + } + + if( fxInfo->EntityIsSet() ) { + SetEntity( fxInfo->GetEntity() ); + } + + if( fxInfo->BindBoneIsSet() ) { + SetBindBone( fxInfo->GetBindBone() ); + } + + SetStart( fxInfo->StartIsSet() ); + + RemoveWhenDone( fxInfo->RemoveWhenDone() ); + + Toggle( fxInfo->Toggle() ); + + OnlyVisibleInSpirit( fxInfo->OnlyVisibleInSpirit() ); + OnlyInvisibleInSpirit( fxInfo->OnlyInvisibleInSpirit() ); + UseWeaponDepthHack( fxInfo->UseWeaponDepthHack() ); + NoRemoveWhenUnbound( fxInfo->NoRemoveWhenUnbound() ); + + return *this; +} + +hhFxInfo& hhFxInfo::operator=( const hhFxInfo* fxInfo ) { + return Assign( fxInfo ); +} + +hhFxInfo& hhFxInfo::operator=( const hhFxInfo& fxInfo ) { + return Assign( &fxInfo ); +} + +void hhFxInfo::SetNormal( const idVec3 &v ) { + assert( v.Length() ); + normal = v; + flags.normalIsSet = true; +} + +void hhFxInfo::SetIncomingVector( const idVec3 &v ) { + assert( v.Length() ); + incomingVector = v; + flags.incomingIsSet = true; +} + +void hhFxInfo::SetBounceVector( const idVec3 &v ) { + assert( v.Length() ); + bounceVector = v; + flags.bounceIsSet = true; +} + +void hhFxInfo::SetEntity( idEntity *e ) { + entity = e; +} + +void hhFxInfo::SetBindBone( const char* bindBone ) { + this->bindBone = bindBone; +} + +void hhFxInfo::SetStart(const bool start) { + flags.start = start; +} + +void hhFxInfo::RemoveWhenDone( const bool removeWhenDone ) { + flags.removeWhenDone = removeWhenDone; +} + +void hhFxInfo::Toggle( const bool tf ) { + flags.toggle = tf; +} + +void hhFxInfo::OnlyVisibleInSpirit( const bool spirit ) { + flags.onlyVisibleInSpirit = spirit; +} + +void hhFxInfo::OnlyInvisibleInSpirit( const bool spirit ) { + flags.onlyInvisibleInSpirit = spirit; +} + +void hhFxInfo::UseWeaponDepthHack( const bool weaponDepthHack ) { + flags.useWeaponDepthHack = weaponDepthHack; +} + +void hhFxInfo::Triggered( const bool tf ) { + flags.triggered = tf; +} + +const idVec3& hhFxInfo::GetNormal( ) const { + assert( flags.normalIsSet ); + return( normal ); +} + +const idVec3& hhFxInfo::GetIncomingVector( ) const { + assert( flags.incomingIsSet ); + return( incomingVector ); +} + +const idVec3& hhFxInfo::GetBounceVector( ) const { + assert( flags.bounceIsSet ); + return( bounceVector ); +} + +const char* hhFxInfo::GetBindBone() const { + return bindBone.c_str(); +} + +idEntity* const hhFxInfo::GetEntity( ) const { + return( entity.GetEntity() ); +} + +bool hhFxInfo::RemoveWhenDone() const { + return flags.removeWhenDone; +} + +bool hhFxInfo::StartIsSet() const { + return flags.start; +} + +bool hhFxInfo::NormalIsSet( ) const { + return flags.normalIsSet; +} + +bool hhFxInfo::IncomingVectorIsSet( ) const { + return flags.incomingIsSet; +} + +bool hhFxInfo::BounceVectorIsSet( ) const { + return flags.bounceIsSet; +} + +bool hhFxInfo::BindBoneIsSet() const { + return bindBone.Length() > 0; +} + +bool hhFxInfo::EntityIsSet() const { + return entity.IsValid(); +} + +bool hhFxInfo::Toggle() const { + return flags.toggle; +} + +bool hhFxInfo::OnlyVisibleInSpirit( void ) const { + return flags.onlyVisibleInSpirit; +} + +bool hhFxInfo::OnlyInvisibleInSpirit( void ) const { + return flags.onlyInvisibleInSpirit; +} + +bool hhFxInfo::UseWeaponDepthHack() const { + return flags.useWeaponDepthHack; +} + +bool hhFxInfo::Triggered() const { + return flags.triggered; +} + +void hhFxInfo::NoRemoveWhenUnbound( const bool noRemove ) { + flags.bNoRemoveWhenUnbound = noRemove; +} + +bool hhFxInfo::NoRemoveWhenUnbound() const { + return flags.bNoRemoveWhenUnbound; +} + +void hhFxInfo::Reset() { + entity = NULL; + normal.Zero(); bounceVector.Zero(); + incomingVector.Zero(); + bindBone.Empty(); + flags.normalIsSet = false; + flags.bounceIsSet = false; + flags.incomingIsSet = false; + flags.start = true; + flags.removeWhenDone = true; + flags.toggle = false; // jrm + flags.onlyVisibleInSpirit = false; + flags.onlyInvisibleInSpirit = false; // tmj + flags.useWeaponDepthHack = false; + flags.triggered = false; // mdl + flags.bNoRemoveWhenUnbound = false; // mdc +} + +bool hhFxInfo::GetAxisFor( int which, idVec3& dir ) const { + switch ( which ) { + case AXIS_NORMAL: + if ( NormalIsSet() ) { dir = GetNormal(); return true; } + else { + if ( g_debugFX.GetBool() ) gameLocal.Warning("Tried to get fxInfo Normal when it isn't set"); + } + break; + case AXIS_BOUNCE: + if ( BounceVectorIsSet() ) { dir = GetBounceVector(); return true; } + else { + if ( g_debugFX.GetBool() ) gameLocal.Warning("Tried to get fxInfo Bounce Vector when it isn't set"); + } + break; + case AXIS_CUSTOMLOCAL: + // handled in alternate path + if ( g_debugFX.GetBool() ) { + gameLocal.Warning("Using customlocal without axis"); + } + break; + case AXIS_INCOMING: + if ( IncomingVectorIsSet() ) { dir = GetIncomingVector(); return true; } + else { + if ( g_debugFX.GetBool() ) gameLocal.Warning("Tried to get fxInfo Incoming Vector when it isn't set"); + } + break; + } + return false; +} \ No newline at end of file diff --git a/src/Prey/game_fxinfo.h b/src/Prey/game_fxinfo.h new file mode 100644 index 0000000..31d2f52 --- /dev/null +++ b/src/Prey/game_fxinfo.h @@ -0,0 +1,175 @@ +#ifndef __HH_FX_INFO_H +#define __HH_FX_INFO_H + +class hhFxInfo : public idClass { + CLASS_PROTOTYPE( hhFxInfo ); + +public: + hhFxInfo(); + hhFxInfo( const hhFxInfo* fxInfo ); + hhFxInfo( const hhFxInfo& fxInfo ); + hhFxInfo& Assign( const hhFxInfo* fxInfo ); + hhFxInfo& operator=( const hhFxInfo* fxInfo ); + hhFxInfo& operator=( const hhFxInfo& fxInfo ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //HUMANHEAD rww + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + //HUMANHEAD END + + void SetNormal( const idVec3 &v ); + void SetIncomingVector( const idVec3 &v ); + void SetBounceVector( const idVec3 &v ); + void SetEntity( idEntity *e ); + void SetBindBone( const char* bindBone ); + void SetStart( const bool start ); + void RemoveWhenDone( const bool removeWhenDone ); + void Toggle( const bool tf ); + void OnlyVisibleInSpirit( const bool spirit ); + void OnlyInvisibleInSpirit( const bool spirit ); + void UseWeaponDepthHack( const bool weaponDepthHack ); + void Triggered( const bool tf ); + + void NoRemoveWhenUnbound( const bool noRemove ); + + const idVec3& GetNormal( ) const; + const idVec3& GetIncomingVector( ) const; + const idVec3& GetBounceVector( ) const; + const char* GetBindBone() const; + idEntity* const GetEntity( ) const; + + bool RemoveWhenDone() const; + bool StartIsSet() const; + bool NormalIsSet( ) const; + bool IncomingVectorIsSet( ) const; + bool BounceVectorIsSet( ) const; + bool BindBoneIsSet() const; + bool EntityIsSet() const; + bool Toggle() const; + bool OnlyVisibleInSpirit( void ) const; + bool OnlyInvisibleInSpirit( void ) const; + bool UseWeaponDepthHack() const; + bool Triggered() const; + + bool NoRemoveWhenUnbound() const; + + void Reset(); + + bool GetAxisFor( int which, idVec3& dir ) const; + + void WriteToBitMsg( idBitMsg* msg ) const; + void ReadFromBitMsg( const idBitMsg* bitMsg ); + +protected: + struct fxInfoFlags_s { + bool normalIsSet; + bool incomingIsSet; + bool bounceIsSet; + bool start; + bool removeWhenDone; + bool toggle; + bool onlyVisibleInSpirit; // CJR + bool onlyInvisibleInSpirit; // tmj + bool useWeaponDepthHack; + bool bNoRemoveWhenUnbound; //mdc + bool triggered; // mdl + } flags; + + idVec3 normal; + idVec3 incomingVector; + idVec3 bounceVector; + idStr bindBone; + idEntityPtr entity; +}; + +ID_INLINE void hhFxInfo::WriteToBitMsg( idBitMsg* msg ) const { + bool varIsSet = false; + + varIsSet = NormalIsSet(); + msg->WriteBool( varIsSet ); + if( varIsSet ) { + msg->WriteDir( GetNormal(), 24 ); + } + + varIsSet = IncomingVectorIsSet(); + msg->WriteBool( varIsSet ); + if( varIsSet ) { + msg->WriteDir( GetIncomingVector(), 24 ); + } + + varIsSet = BounceVectorIsSet(); + msg->WriteBool( varIsSet ); + if( varIsSet ) { + msg->WriteVec3( GetBounceVector() ); + } + + varIsSet = EntityIsSet(); + msg->WriteBool( varIsSet ); + if( varIsSet ) { + msg->WriteBits( GetEntity()->entityNumber, GENTITYNUM_BITS ); + } + + varIsSet = BindBoneIsSet(); + msg->WriteBool( varIsSet ); + if( varIsSet ) { + msg->WriteString( GetBindBone() ); + } + + msg->WriteBool( StartIsSet() ); + + msg->WriteBool( RemoveWhenDone() ); + + msg->WriteBool( Toggle() ); + + msg->WriteBool( OnlyVisibleInSpirit() ); + msg->WriteBool( OnlyInvisibleInSpirit() ); + msg->WriteBool( UseWeaponDepthHack() ); + msg->WriteBool( NoRemoveWhenUnbound() ); +} + +ID_INLINE void hhFxInfo::ReadFromBitMsg( const idBitMsg* msg ) { + bool varIsSet = false; + + varIsSet = msg->ReadBool(); + if( varIsSet ) { + SetNormal( msg->ReadDir(24) ); + } + + varIsSet = msg->ReadBool(); + if( varIsSet ) { + SetIncomingVector( msg->ReadDir(24) ); + } + + varIsSet = msg->ReadBool(); + if( varIsSet ) { + SetBounceVector( msg->ReadVec3() ); + } + + varIsSet = msg->ReadBool(); + if( varIsSet ) { + SetEntity( gameLocal.entities[ msg->ReadBits(GENTITYNUM_BITS) ] ); + } + + varIsSet = msg->ReadBool(); + if( varIsSet ) { + char boneName[256]; + msg->ReadString( boneName, 256 ); + SetBindBone( boneName ); + } + + SetStart( msg->ReadBool() ); + + RemoveWhenDone( msg->ReadBool() ); + + Toggle( msg->ReadBool() ); + + OnlyVisibleInSpirit( msg->ReadBool() ); + OnlyInvisibleInSpirit( msg->ReadBool() ); + UseWeaponDepthHack( msg->ReadBool() ); + NoRemoveWhenUnbound( msg->ReadBool() ); +} + +#endif \ No newline at end of file diff --git a/src/Prey/game_gibbable.cpp b/src/Prey/game_gibbable.cpp new file mode 100644 index 0000000..a228b51 --- /dev/null +++ b/src/Prey/game_gibbable.cpp @@ -0,0 +1,146 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/********************************************************************** + +hhGibbable + +**********************************************************************/ + +const idEventDef EV_Respawn(""); + +CLASS_DECLARATION( hhAnimatedEntity, hhGibbable ) + EVENT( EV_Activate, hhGibbable::Event_Activate) + EVENT( EV_PlayIdle, hhGibbable::Event_PlayIdle) + EVENT( EV_Respawn, hhGibbable::Event_Respawn) +END_CLASS + +// Called during idEntity::Spawn +void hhGibbable::SetModel( const char *modelname ) { + // NLATODO - Is this called still? + hhAnimatedEntity::SetModel( modelname ); + + bool bAnimates = spawnArgs.FindKey("anim idle") != NULL; +} + +void hhGibbable::Spawn(void) { + + bVertexColorFade = spawnArgs.GetBool("materialFade"); + if (bVertexColorFade) { + SetDeformation(DEFORMTYPE_VERTEXCOLOR, 1.0f); + } + + //HUMANHEAD: aob - Flynn wanted some gibbables to be triggered only + fl.takedamage = !spawnArgs.GetBool("noDamage", "0"); + + // setup the clipModel +// GetPhysics()->SetContents( CONTENTS_SOLID ); + GetPhysics()->SetContents( CONTENTS_BODY | CONTENTS_RENDERMODEL ); + + idleAnim = GetAnimator()->GetAnim("idle"); + painAnim = GetAnimator()->GetAnim("pain"); + + idleChannel = GetChannelForAnim( "idle" ); + painChannel = GetChannelForAnim( "pain" ); + + PostEventMS(&EV_PlayIdle, 1000); +} + +void hhGibbable::Save(idSaveGame *savefile) const { + savefile->WriteInt( idleAnim ); + savefile->WriteInt( painAnim ); + savefile->WriteInt( idleChannel ); + savefile->WriteInt( painChannel ); + savefile->WriteBool( bVertexColorFade ); +} + +void hhGibbable::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( idleAnim ); + savefile->ReadInt( painAnim ); + savefile->ReadInt( idleChannel ); + savefile->ReadInt( painChannel ); + savefile->ReadBool( bVertexColorFade ); +} + +bool hhGibbable::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + + // Adjust vertex color + if (bVertexColorFade) { + float fadeAlpha = idMath::ClampFloat(0.0f, 1.0f, ((float)health / spawnArgs.GetFloat("health"))); + SetDeformation(DEFORMTYPE_VERTEXCOLOR, fadeAlpha); + } + + if (painAnim) { + GetAnimator()->PlayAnim( painChannel, painAnim, gameLocal.time, 0); + } + + return( hhAnimatedEntity::Pain(inflictor, attacker, damage, dir, location) ); +} + +void hhGibbable::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + Explode(attacker); +} + +int hhGibbable::DetermineThinnestAxis() { + int best = 0; + idBounds bounds = GetPhysics()->GetBounds(); + for( int i = 1; i < 3; ++i ) { + if ( bounds[1][ i ] - bounds[0][ i ] < bounds[1][ best ] - bounds[0][ best ] ) { + best = i; + } + } + + return best; +} + +void hhGibbable::Explode(idEntity *activator) { + hhFxInfo fxInfo; + + Hide(); + fl.takedamage = false; + GetPhysics()->SetContents( 0 ); + ActivateTargets( activator ); + SetSkinByName(NULL); + if ( spawnArgs.GetFloat( "respawn", "0" ) ) { + PostEventSec( &EV_Respawn, spawnArgs.GetFloat( "respawn", "0" ) ); + } else { + PostEventMS( &EV_Remove, 200 ); // Remove after a small delay to allow sound commands to execute + } + StartSound( "snd_gib", SND_CHANNEL_ANY ); + + // Find thinnest axis in the bounds and use for fx normal + idVec3 thinnest = vec3_origin; + int axisIndex = DetermineThinnestAxis(); + thinnest[axisIndex] = 1.0f; + thinnest *= GetAxis(); + + fxInfo.RemoveWhenDone( true ); + fxInfo.SetNormal(thinnest); + // Spawn FX system for gib + BroadcastFxInfo( spawnArgs.GetString("fx_gib"), GetOrigin(), GetAxis(), &fxInfo ); + + // Spawn gibs + if (spawnArgs.FindKey("def_debrisspawner")) { + hhUtils::SpawnDebrisMass(spawnArgs.GetString("def_debrisspawner"), this ); + } +} + +void hhGibbable::Event_Respawn() { + GetPhysics()->SetContents( CONTENTS_BODY | CONTENTS_RENDERMODEL ); + fl.takedamage = true; + Show(); +} + +void hhGibbable::Event_Activate( idEntity *activator ) { + Explode(activator); +} + +void hhGibbable::Event_PlayIdle( void ) { + if (idleAnim) { + GetAnimator()->ClearAllAnims(gameLocal.time, 0); + GetAnimator()->CycleAnim( idleChannel, idleAnim, gameLocal.time, 0); + } +} + diff --git a/src/Prey/game_gibbable.h b/src/Prey/game_gibbable.h new file mode 100644 index 0000000..600793a --- /dev/null +++ b/src/Prey/game_gibbable.h @@ -0,0 +1,31 @@ +#ifndef __PREY_GIBBABLE_H +#define __PREY_GIBBABLE_H + +class hhGibbable : public hhAnimatedEntity { +public: + CLASS_PROTOTYPE( hhGibbable ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void Explode(idEntity *activator); + 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 ); + virtual void SetModel( const char *modelname ); + +protected: + void Event_Activate( idEntity *activator ); + void Event_PlayIdle( void ); + void Event_Respawn( void ); + int DetermineThinnestAxis(); + +protected: + int idleAnim; + int painAnim; + + int idleChannel; + int painChannel; + bool bVertexColorFade; +}; + +#endif diff --git a/src/Prey/game_gravityswitch.cpp b/src/Prey/game_gravityswitch.cpp new file mode 100644 index 0000000..41cc127 --- /dev/null +++ b/src/Prey/game_gravityswitch.cpp @@ -0,0 +1,204 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_CheckAgain( "", NULL ); + +CLASS_DECLARATION( hhDamageTrigger, hhGravitySwitch ) + EVENT( EV_PostSpawn, hhGravitySwitch::Event_PostSpawn ) + EVENT( EV_CheckAgain, hhGravitySwitch::Event_CheckAgain ) +END_CLASS + + +void hhGravitySwitch::Spawn( void ) { + fl.networkSync = true; + + fl.clientEvents = true; + + CancelEvents(&EV_PostSpawn); // Parent actually already posted one + PostEventMS(&EV_PostSpawn, 0); +} + +void hhGravitySwitch::Event_PostSpawn(void) { + idVec3 origin = GetOrigin() - GetAxis()[0]*10.0f; + + // Determine axis of emitter + // NOTE: axis of gravityswitch is reversed, axis of emitters is: identity==up + idVec3 up(0.0f, 0.0f, 1.0f); + idVec3 back(-1.0f, 0.0f, 0.0f); + idMat3 axis = up.ToMat3().Inverse()*back.ToMat3().Inverse()*GetAxis(); //a mess to * axis to get the emitter to point the same way as the model + + idDict args; + args.Clear(); + args.SetVector("origin", origin); + args.SetMatrix("rotation", axis); + + const char *effectName = spawnArgs.GetString("def_effect", NULL); + if (effectName && *effectName) { + effect = static_cast(gameLocal.SpawnClientObject(effectName, &args) ); + if (effect.IsValid()) { + effect->Hide(); + } + } +} + +hhGravitySwitch::~hhGravitySwitch() { + SAFE_REMOVE( effect ); +} + +void hhGravitySwitch::Save(idSaveGame *savefile) const { + effect.Save(savefile); +} + +void hhGravitySwitch::Restore( idRestoreGame *savefile ) { + effect.Restore(savefile); +} + +void hhGravitySwitch::Damage(idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location) { + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); + if ( damageDef && damageDef->GetBool("radius")) { + return; // Gravity switches are immune to all splash damage + } + hhDamageTrigger::Damage(inflictor, attacker, dir, damageDefName, damageScale, location); +} + +idVec3 hhGravitySwitch::GetGravityVector() { + idVec3 vector; + + if (!spawnArgs.GetVector("vector", NULL, vector)) { + float strength = spawnArgs.GetFloat("strength", "1"); + idVec3 direction = GetPhysics()->GetAxis()[0]; + vector = direction * strength * DEFAULT_GRAVITY; + } + + return vector; +} + +void hhGravitySwitch::SetGravityVector(idEntity *activator) { + idVec3 newGravity = GetGravityVector(); + bool bSwitchedGravity = false; + + int numTargets = targets.Num(); + for ( int ix = 0; ix < numTargets; ix++) { + idEntity *ent = targets[ix].GetEntity(); + if (ent && ent->IsType(hhGravityZone::Type)) { + hhGravityZone *zone = static_cast(ent); + + idVec3 zoneGravity = zone->GetDestinationGravity(); + if (!zoneGravity.Compare(newGravity, VECTOR_EPSILON)) { + zone->SetGravityOnZone( newGravity ); + bSwitchedGravity = true; + } + } + } + + if (bSwitchedGravity) { + if (gameLocal.isMultiplayer && !gameLocal.isClient) { //rww - play sound when shot in MP + StartSound("snd_gravity_mpshot", SND_CHANNEL_ANY, 0, true); + } + ActivateTargets(activator); + BecomeActive(TH_TICKER); + } +} + +void hhGravitySwitch::Ticker() { + // See if our gravity zone(s) are following our gravity + int numTargets = 1; // only need to check one, they are all assumed to be the same. If not, we have problems anyway. + for ( int ix = 0; ix < numTargets; ix++) { + idEntity *ent = targets[ix].GetEntity(); + if (ent && ent->IsType(hhGravityZone::Type)) { + hhGravityZone *zone = static_cast(ent); + if (zone->GetDestinationGravity().Compare( GetGravityVector(), VECTOR_EPSILON )) { + // Zone is still following my gravity, emit smoke + if (effect.IsValid() && effect->IsHidden()) { + effect->Show(); + } + return; + } + } + } + + // If not, stop thinking about it. + BecomeInactive(TH_TICKER); + if (effect.IsValid() && !effect->IsHidden()) { + effect->Hide(); + } + + // Check again in a while in case another switch turns the zone onto my gravity + CancelEvents(&EV_CheckAgain); + PostEventMS(&EV_CheckAgain, 1000); +} + +void hhGravitySwitch::Event_CheckAgain() { + BecomeActive(TH_TICKER); // Check again +} + +void hhGravitySwitch::TriggerAction(idEntity *activator) { + + SetGravityVector(activator); + + // Handle default trigger behavior + // Hack, make targetlist appear to be empty so ActivateTargets() does nothing, we handle this ourself + int num = targets.Num(); + targets.SetNum(0, false); + hhDamageTrigger::TriggerAction(activator); + targets.SetNum(num, false); +} + +void hhGravitySwitch::Event_Enable() { + if (!bEnabled) { + StartSound("snd_gravity_enable", SND_CHANNEL_ANY, 0, true); + } + SetShaderParm(SHADERPARM_TIMEOFFSET, -MS2SEC(gameLocal.time)); + SetShaderParm(SHADERPARM_MISC, 1); + bEnabled = true; + fl.takedamage = true; + GetPhysics()->SetContents( CONTENTS_SHOOTABLE|CONTENTS_IKCLIP|CONTENTS_SHOOTABLEBYARROW ); + BecomeActive(TH_TICKER); +} + +void hhGravitySwitch::Event_Disable() { + if (bEnabled) { + StartSound("snd_gravity_disable", SND_CHANNEL_ANY, 0, true); + } + SetShaderParm(SHADERPARM_MISC, 0); + GetPhysics()->SetContents( CONTENTS_IKCLIP ); + fl.takedamage = false; + bEnabled = false; + BecomeInactive(TH_TICKER); + if (effect.IsValid()) { + effect->Hide(); + } +} + +void hhGravitySwitch::WriteToSnapshot( idBitMsgDelta &msg ) const { + GetPhysics()->WriteToSnapshot(msg); + msg.WriteBits(bEnabled, 1); + msg.WriteFloat(renderEntity.shaderParms[SHADERPARM_TIMEOFFSET]); + msg.WriteFloat(renderEntity.shaderParms[SHADERPARM_MISC]); + + if (effect.IsValid()) { + msg.WriteBits(effect->IsHidden(), 1); + } + else { + msg.WriteBits(0, 1); + } +} + +void hhGravitySwitch::ReadFromSnapshot( const idBitMsgDelta &msg ) { + GetPhysics()->ReadFromSnapshot(msg); + bEnabled = !!msg.ReadBits(1); + renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = msg.ReadFloat(); + renderEntity.shaderParms[SHADERPARM_MISC] = msg.ReadFloat(); + + bool fxHidden = !!msg.ReadBits(1); + if (effect.IsValid() && fxHidden != effect->IsHidden()) { + if (fxHidden) { + effect->Hide(); + } + else { + effect->Show(); + } + } +} diff --git a/src/Prey/game_gravityswitch.h b/src/Prey/game_gravityswitch.h new file mode 100644 index 0000000..e480b39 --- /dev/null +++ b/src/Prey/game_gravityswitch.h @@ -0,0 +1,35 @@ +#ifndef __GAME_GRAVITYSWITCH_H__ +#define __GAME_GRAVITYSWITCH_H__ + +class hhGravitySwitch : public hhDamageTrigger { +public: + CLASS_PROTOTYPE( hhGravitySwitch ); + + virtual ~hhGravitySwitch(); + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // Overridden methods + virtual void Ticker(); + virtual void Damage(idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location); + virtual void TriggerAction(idEntity *activator); + virtual void Event_Enable(); + virtual void Event_Disable(); + virtual void Event_PostSpawn(); + void Event_CheckAgain(); + + //rww - network code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + +protected: + void SetGravityVector(idEntity *activator); + idVec3 GetGravityVector(); + +private: + idEntityPtr effect; + +}; + +#endif // __GAME_GRAVITYSWITCH_H__ diff --git a/src/Prey/game_guihand.cpp b/src/Prey/game_guihand.cpp new file mode 100644 index 0000000..7193ea6 --- /dev/null +++ b/src/Prey/game_guihand.cpp @@ -0,0 +1,52 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +CLASS_DECLARATION( hhHand, hhGuiHand ) +END_CLASS + + +void hhGuiHand::Spawn(void) { + actionAnimDoneTime = 0; + fl.networkSync = true; +} + +void hhGuiHand::Save(idSaveGame *savefile) const { + savefile->WriteInt( actionAnimDoneTime ); +} + +void hhGuiHand::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( actionAnimDoneTime ); +} + +void hhGuiHand::WriteToSnapshot( idBitMsgDelta &msg ) const +{ + hhHand::WriteToSnapshot(msg); + + msg.WriteBits(actionAnimDoneTime, 32); +} + +void hhGuiHand::ReadFromSnapshot( const idBitMsgDelta &msg ) { + hhHand::ReadFromSnapshot(msg); + + actionAnimDoneTime = msg.ReadBits(32); +} + +void hhGuiHand::ClientPredictionThink( void ) { + hhHand::ClientPredictionThink(); +} + +void hhGuiHand::Action(void) { + PlayAnim( -1, action, &EV_Hand_Ready ); +} + +void hhGuiHand::SetAction(const char* str) { + action = str; +} + +bool hhGuiHand::IsValidFor( hhPlayer *who ) { + return( who->InGUIMode() ); +} diff --git a/src/Prey/game_guihand.h b/src/Prey/game_guihand.h new file mode 100644 index 0000000..02b5cfb --- /dev/null +++ b/src/Prey/game_guihand.h @@ -0,0 +1,33 @@ + +#ifndef __PREY_GAME_GUIHAND_H__ +#define __PREY_GAME_GUIHAND_H__ + +// Dumb forward decl +class hhPlayer; + +class hhGuiHand : public hhHand { + +public: + + CLASS_PROTOTYPE(hhGuiHand); + + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + + virtual void Action(void); // Player clicked + virtual void SetAction(const char* str); //HUMANHEAD bjk + virtual bool IsValidFor( hhPlayer *who ); + +protected: + int actionAnimDoneTime; + const char* action; +}; + + +#endif /* __PREY_GAME_GUIHAND_H__ */ + diff --git a/src/Prey/game_gun.cpp b/src/Prey/game_gun.cpp new file mode 100644 index 0000000..cc2e43e --- /dev/null +++ b/src/Prey/game_gun.cpp @@ -0,0 +1,295 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +//----------------------------------------------------------------------- +// +// hhGun +// +//----------------------------------------------------------------------- +const idEventDef EV_SetEnemy("setenemy", "e"); +const idEventDef EV_FireGunAt("fireat", "e"); + +CLASS_DECLARATION(idEntity, hhGun) + EVENT( EV_Activate, hhGun::Event_Activate ) + EVENT( EV_SetEnemy, hhGun::Event_SetEnemy ) + EVENT( EV_FireGunAt, hhGun::Event_FireAt ) +END_CLASS + + +float AngleBetweenVectors(const idVec3 &v1, const idVec3 &v2) { + float dot = (v1 * v2) / ( v1.Length() * v2.Length() ); + return RAD2DEG(idMath::ACos(dot)); +} + +void hhGun::Spawn(void) { + coneAngle = spawnArgs.GetFloat("coneAngle"); + burstCount = spawnArgs.GetInt("bursts"); + burstRate = ( int )( spawnArgs.GetFloat( "burstRate" ) * 1000.0f ); + fireRate = ( int )( spawnArgs.GetFloat( "fireRate" ) * 1000.0f ); + fireDeviation = ( int )( spawnArgs.GetFloat( "fireDeviation" ) * 1000.0f ); + targetOffset = spawnArgs.GetVector("targetOffset"); + targetRadius = spawnArgs.GetFloat("targetRadius"); + nextFireTime = 0; + nextBurstTime = 0; + nextEnemyTime = 0; + firing = false; + enemyRate = 200; + + GetPhysics()->SetContents(CONTENTS_SOLID); + fl.takedamage = true; + + SetEnemy(NULL); + if (spawnArgs.GetBool("enabled")) { + BecomeActive(TH_THINK); + } +} + +void hhGun::Save(idSaveGame *savefile) const { + enemy.Save(savefile); + savefile->WriteInt( enemyRate ); + savefile->WriteInt( nextEnemyTime ); + savefile->WriteVec3( targetOffset ); + savefile->WriteFloat( targetRadius ); + savefile->WriteInt( fireRate ); + savefile->WriteInt( fireDeviation ); + savefile->WriteInt( nextFireTime ); + savefile->WriteInt( burstRate ); + savefile->WriteInt( burstCount ); + savefile->WriteInt( nextBurstTime ); + savefile->WriteInt( curBurst ); + savefile->WriteBool( firing ); + savefile->WriteFloat( coneAngle ); +} + +void hhGun::Restore( idRestoreGame *savefile ) { + enemy.Restore(savefile); + savefile->ReadInt( enemyRate ); + savefile->ReadInt( nextEnemyTime ); + savefile->ReadVec3( targetOffset ); + savefile->ReadFloat( targetRadius ); + savefile->ReadInt( fireRate ); + savefile->ReadInt( fireDeviation ); + savefile->ReadInt( nextFireTime ); + savefile->ReadInt( burstRate ); + savefile->ReadInt( burstCount ); + savefile->ReadInt( nextBurstTime ); + savefile->ReadInt( curBurst ); + savefile->ReadBool( firing ); + savefile->ReadFloat( coneAngle ); +} + +void hhGun::Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location) { + SetEnemy(NULL); + fl.takedamage = false; + BecomeInactive(TH_THINK); + + const char *killedModel = spawnArgs.GetString("model_killed", NULL); + if (killedModel) { + SetModel(killedModel); + UpdateVisuals(); + } + else { + GetPhysics()->SetContents(0); + } + + // Spawn gibs + if (spawnArgs.FindKey("def_debrisspawner")) { + hhUtils::SpawnDebrisMass(spawnArgs.GetString("def_debrisspawner"), this ); + } + + StartSound( "snd_explode", SND_CHANNEL_ANY ); + + ActivateTargets( attacker ); +} + +void hhGun::SetEnemy(idEntity *ent) { + if (ent && ent->IsType(hhPlayer::Type)) { + hhPlayer *player = static_cast(ent); + enemy = player->InVehicle() ? player->GetVehicleInterface()->GetVehicle() : ent; + } + else { + enemy = ent; + } +} + +void hhGun::FindEnemy() { + if (enemy.IsValid() && enemy->GetHealth() > 0) { + return; + } + if ( !gameLocal.InPlayerPVS( this ) ) { + return; + } + + idEntity *bestEnt = NULL; + float bestDistSqr = idMath::INFINITY; + float radiusSqr = targetRadius*targetRadius; + idVec3 origin = GetPhysics()->GetOrigin(); + idMat3 axis = GetPhysics()->GetAxis(); + for ( int i = 0; i < MAX_CLIENTS ; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + + if (ent) { + idVec3 toEnt = ent->GetPhysics()->GetOrigin() + targetOffset - origin; + float distSqr = toEnt.LengthSqr(); + if (distSqr < bestDistSqr && distSqr < radiusSqr && + AngleBetweenVectors(toEnt, axis[0]) < coneAngle) { + bestDistSqr = distSqr; + bestEnt = ent; + } + } + } + + SetEnemy(bestEnt); +} + +idMat3 hhGun::GetAimAxis() { +#if 0 + // Fast approximation + const idDict *projectileDef = declManager->FindEntityDef( spawnArgs.GetString("def_projectile") ); + float projSpeed = idProjectile::GetVelocity( projectileDef ).Length(); + idVec3 firePos = GetPhysics()->GetOrigin(); + idVec3 enemyPos = enemy->GetPhysics()->GetOrigin() + targetOffset; + idVec3 enemyVel = enemy->GetPhysics()->GetLinearVelocity(); + idVec3 toEnemy = enemyPos - firePos; + float enemyDist = toEnemy.Length(); + float projTime = enemyDist / projSpeed; + idVec3 predictedEnemyPos = enemyPos + enemyVel * projTime; + idVec3 aim = predictedEnemyPos - firePos; + aim.Normalize(); + return aim.hhToMat3(); +#else +/* Projectile prediction: + +Let: + Pe(t) = position of enemy at time t Ve = velocity of enemy (known) + Pm(t) = position of missile at time t Vm = velocity of missile (magnitude known) + Pf = position firing from + +(1) Pe(t) = Pe(0) + Ve * t Enemy position function + +(2) Aim(t) = Pe(t) - Pf Aim function is vector from firing point to Pe(t) + + |Aim(t)| distance / distance/sec -> sec +(3) -------- = t + |Vm| + +(4) d = Pe(0) - Pf delta vector from firing point to initial enemy position + +(5) s = |Vm| + + Substituting and simplifying yields a quadratic function in terms of t, Ve, d, & s: + At² + Bt + C = 0 +*/ + const idDict *projectileDef = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_projectile") ); + if ( !projectileDef ) { + gameLocal.Error( "Unknown def_projectile: %s\n", spawnArgs.GetString("def_projectile") ); + } + float projSpeed = idProjectile::GetVelocity( projectileDef ).Length(); + idVec3 firePos = GetPhysics()->GetOrigin(); + idVec3 enemyPos = enemy->GetOrigin() + idVec3(0,0,32); + idVec3 enemyVel = enemy->GetPhysics()->GetLinearVelocity(); + idVec3 d = enemyPos - firePos; + idVec3 v = enemyVel; + float s = projSpeed; + + float a = v.x*v.x + v.y*v.y + v.z*v.x - s*s; // t2 term + float b = 2 * (d.x*v.x + d.y*v.y + d.z*v.z); // t term + float c = d.x*d.x + d.y*d.y + d.z*d.z; + + // Use quadratic formula to solve for t + float t1 = (-b + sqrt(b*b - 4*a*c) ) / (2*a); + float t2 = (-b - sqrt(b*b - 4*a*c) ) / (2*a); + float projTime = t1 > 0 ? t1 : t2; + + idVec3 predictedEnemyPos = enemyPos + enemyVel * projTime; + idVec3 aim = predictedEnemyPos - firePos; + aim.Normalize(); + return aim.hhToMat3(); +#endif +} + +bool hhGun::ValidEnemy() { + return (enemy.IsValid()) ? enemy->GetHealth() > 0 : false; +} + +void hhGun::Fire(idMat3 &axis) { + if (health > 0) { + hhUtils::LaunchProjectile(this, spawnArgs.GetString("def_projectile"), axis, GetOrigin()); + StartSound( "snd_fire", SND_CHANNEL_ANY ); + } +} + +void hhGun::Think(void) { + if (thinkFlags & TH_THINK) { + + // Temp: for placement + if (spawnArgs.GetBool("showCone")) { + float radius = targetRadius * tan(DEG2RAD(coneAngle)); + gameRenderWorld->DebugCone(colorGreen, + GetPhysics()->GetOrigin(), + GetPhysics()->GetAxis()[0] * targetRadius, + 0, radius); + } + + if (!ValidEnemy() && gameLocal.time >= nextEnemyTime ) { + FindEnemy(); + nextEnemyTime = gameLocal.time + enemyRate; + } + + if (ValidEnemy()) { + if (!firing && gameLocal.time >= nextFireTime ) { + firing = true; + curBurst = burstCount; + nextFireTime = gameLocal.time + fireRate + gameLocal.random.CRandomFloat()*fireDeviation; + nextBurstTime = gameLocal.time; + } + + if (firing && gameLocal.time >= nextBurstTime ) { + // Aim + idMat3 aimAxis = GetAimAxis(); + + // Burst if enemy is in cone + if ( AngleBetweenVectors(aimAxis[0], GetPhysics()->GetAxis()[0]) < coneAngle ) { + Fire(aimAxis); + if (--curBurst <= 0) { + firing = false; + } + } + else { + firing = false; + SetEnemy(NULL); + } + + nextBurstTime = gameLocal.time + burstRate; + } + } + } + idEntity::Think(); +} + +void hhGun::Event_Activate(idEntity *activator) { + if (targets.Num() && targets[0].IsValid()) { + Event_FireAt(targets[0].GetEntity()); + return; + } + + if (thinkFlags & TH_THINK) { + BecomeInactive(TH_THINK); + } + else { + BecomeActive(TH_THINK); + } +} + +void hhGun::Event_SetEnemy(idEntity *newEnemy) { + SetEnemy(newEnemy); +} + +void hhGun::Event_FireAt(idEntity *victim) { + idVec3 dir = victim->GetOrigin() - GetOrigin(); + dir.Normalize(); + Fire(dir.ToMat3()); +} diff --git a/src/Prey/game_gun.h b/src/Prey/game_gun.h new file mode 100644 index 0000000..270cc67 --- /dev/null +++ b/src/Prey/game_gun.h @@ -0,0 +1,46 @@ + +#ifndef __GAME_GUN_H__ +#define __GAME_GUN_H__ + +class hhGun : public idEntity { +public: + CLASS_PROTOTYPE( hhGun ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + virtual void Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location); + void SetEnemy(idEntity *ent); + void FindEnemy(); + idMat3 GetAimAxis(); + bool ValidEnemy(); + +protected: + void Fire(idMat3 &axis); + + void Event_Activate(idEntity *activator); + void Event_SetEnemy(idEntity *newEnemy); + void Event_FireAt(idEntity *victim); + +protected: + idEntityPtr enemy; + int enemyRate; + int nextEnemyTime; + idVec3 targetOffset; + float targetRadius; + + int fireRate; + int fireDeviation; + int nextFireTime; + + int burstRate; + int burstCount; + int nextBurstTime; + int curBurst; + bool firing; + float coneAngle; +}; + +#endif // __GAME_GUN_H__ diff --git a/src/Prey/game_hand.cpp b/src/Prey/game_hand.cpp new file mode 100644 index 0000000..502fd26 --- /dev/null +++ b/src/Prey/game_hand.cpp @@ -0,0 +1,1155 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +const idEventDef EV_Hand_DoRemove( "DoHandRemove", NULL ); +const idEventDef EV_Hand_DoAttach( "DoHandAttach", "e" ); +const idEventDef EV_Hand_Remove( "RemoveHand", NULL ); + +const idEventDef EV_Hand_Ready( "" ); +const idEventDef EV_Hand_Lowered( "" ); +const idEventDef EV_Hand_Raise( "" ); + +CLASS_DECLARATION( hhAnimatedEntity, hhHand ) + EVENT ( EV_Hand_DoRemove, hhHand::Event_DoHandRemove ) + EVENT ( EV_Hand_DoAttach, hhHand::Event_DoHandAttach ) + EVENT ( EV_Hand_Remove, hhHand::Event_RemoveHand ) + EVENT ( EV_Hand_Ready, hhHand::Event_Ready ) + EVENT ( EV_Hand_Lowered, hhHand::Event_Lowered ) + EVENT ( EV_Hand_Raise, hhHand::Event_Raise ) +END_CLASS + +/* +============ +hhHand::~hhHand +============ +*/ +hhHand::~hhHand() { + bool shouldWarn = (gameLocal.GameState() != GAMESTATE_SHUTDOWN && !gameLocal.isClient); + //rww - don't warn about this on client, since snapshot entities are free to be removed + //at any time. + + // Check if the hand deleted is still in the hands list + if ( gameLocal.hands.Find( this ) ) { + if ( shouldWarn ) { + gameLocal.Warning( "The hand (%p/%s) was deleted before being removed", + this, spawnArgs.GetString( "classname" ) ); + } + gameLocal.hands.Remove( this ); + } + + if ( owner != NULL ) { + if ( shouldWarn ) { + gameLocal.Warning( "A hand (%p) was deleted while having an owner", this ); + } + } + + StopSound( SND_CHANNEL_ANY ); +} + + +/* +============ +hhHand::Spawn +============ +*/ +void hhHand::Spawn( void ) { + gameLocal.hands.Append( idEntityPtr (this) ); + + owner = NULL; + previousHand = NULL; + + renderEntity.weaponDepthHack = true; + + animEvent = NULL; + + priority = spawnArgs.GetInt("priority"); + + status = HS_UNKNOWN; + idealState = HS_READY; + + animBlendFrames = 4; // needs blending + animDoneTime = 0; + + attached = false; + attachTime = -1; + + spawnArgs.GetBool( "replace_previous", "0", replacePrevious ); + + spawnArgs.GetBool( "lower_weapon", "1", lowerWeapon ); + spawnArgs.GetBool( "aside_weapon", "1", asideWeapon ); + + handedness = spawnArgs.GetInt( "handedness", "2" ); + + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( NULL, 1.0f ); + physicsObj.SetOrigin( GetOrigin() ); + physicsObj.SetAxis( GetAxis() ); + SetPhysics( &physicsObj ); + + // Lower overrides aside + if ( lowerWeapon && asideWeapon ) { + asideWeapon = false; + } + + Hide(); +} + +void hhHand::Save(idSaveGame *savefile) const { + //savefile->WriteObject( owner ); + owner.Save(savefile); + savefile->WriteInt( priority ); + savefile->WriteObject( previousHand ); + savefile->WriteBool( lowerWeapon ); + savefile->WriteBool( asideWeapon ); + savefile->WriteBool( attached ); + savefile->WriteBool( replacePrevious ); + savefile->WriteInt( attachTime ); + savefile->WriteInt( handedness ); + savefile->WriteInt( animDoneTime ); + savefile->WriteInt( animBlendFrames ); + savefile->WriteInt( status ); + savefile->WriteInt( idealState ); + savefile->WriteEventDef(animEvent); + savefile->WriteStaticObject( physicsObj ); +} + +void hhHand::Restore( idRestoreGame *savefile ) { + //savefile->ReadObject( reinterpret_cast(owner) ); + owner.Restore(savefile); + savefile->ReadInt( priority ); + savefile->ReadObject( reinterpret_cast(previousHand) ); + savefile->ReadBool( lowerWeapon ); + savefile->ReadBool( asideWeapon ); + savefile->ReadBool( attached ); + savefile->ReadBool( replacePrevious ); + savefile->ReadInt( attachTime ); + savefile->ReadInt( handedness ); + savefile->ReadInt( animDoneTime ); + savefile->ReadInt( animBlendFrames ); + savefile->ReadInt( status ); + savefile->ReadInt( idealState ); + savefile->ReadEventDef(animEvent); + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); +} + +//rww - network code +void hhHand::WriteToSnapshot( idBitMsgDelta &msg ) const { + //physicsObj.WriteToSnapshot(msg); + //rww - trying to keep this all local + + WriteBindToSnapshot(msg); + + msg.WriteBits(owner.GetSpawnId(), 32); + + if (previousHand) { + msg.WriteBits(gameLocal.GetSpawnId(previousHand), 32); + } + else { + msg.WriteBits(0, 32); + } + + msg.WriteBits(attached, 1); + msg.WriteBits(replacePrevious, 1); + //msg.WriteBits(attachTime, 32); + //msg.WriteBits(animDoneTime, 32); + //msg.WriteBits(animBlendFrames, 32); + + assert(idealState < (1<<4)); + msg.WriteBits(idealState, 4); + + assert(status < (1<<4)); + msg.WriteBits(status, 4); + + msg.WriteBits(IsHidden(), 1); + + assert(gameLocal.hands.Num() < (1<<4)); + msg.WriteBits(gameLocal.hands.Num(), 4); + int i = 0; + while (i < gameLocal.hands.Num()) { + hhHand *hand = gameLocal.hands[i].GetEntity(); + + if (hand) { + msg.WriteBits(gameLocal.GetSpawnId(hand), 32); + } + else { + msg.WriteBits(0, 32); + } + + i++; + } + + msg.WriteBits(renderEntity.allowSurfaceInViewID, GENTITYNUM_BITS); + + idAnimatedEntity::WriteToSnapshot(msg); +} + +void hhHand::ReadFromSnapshot( const idBitMsgDelta &msg ) { + //physicsObj.ReadFromSnapshot(msg); + //rww - trying to keep this all local + + ReadBindFromSnapshot(msg); + + if (owner.SetSpawnId(msg.ReadBits(32))) { + physicsObj.SetSelfOwner(owner.GetEntity()); + if (owner.IsValid() && owner.GetEntity()) { + DoHandAttach(owner.GetEntity()); + } else { + owner = NULL; + } + } + + //Show(); + + idEntityPtr newPrevHand; + int prevHandSpawnId = msg.ReadBits(32); + if (newPrevHand.SetSpawnId(prevHandSpawnId)) { + previousHand = newPrevHand.GetEntity(); + } + else { + previousHand = NULL; + } + + attached = !!msg.ReadBits(1); + replacePrevious = !!msg.ReadBits(1); + //attachTime = msg.ReadBits(32); + //animDoneTime = msg.ReadBits(32); + //animBlendFrames = msg.ReadBits(32); + + idealState = msg.ReadBits(4); + + int nextStatus = msg.ReadBits(4); + if (status != nextStatus) { + if (nextStatus == HS_LOWERING || nextStatus == HS_LOWERED) { + PutAway(); + } + else if (nextStatus == HS_RAISING) { + Raise(); + } + else if (nextStatus == HS_READY) { + //PlayAnim( -1, "idle" ); + Ready(); + } + status = nextStatus; + } + + bool hidden = !!msg.ReadBits(1); + if (hidden != IsHidden()) { + if (hidden) { + Hide(); + } else { + Show(); + } + } + + int numHands = msg.ReadBits(4); + gameLocal.hands.SetNum(numHands); + int i = 0; + while (i < numHands) { + int handSpawnId = msg.ReadBits(32); + gameLocal.hands[i].SetSpawnId( handSpawnId ); + + i++; + } + + renderEntity.allowSurfaceInViewID = msg.ReadBits(GENTITYNUM_BITS); + + idAnimatedEntity::ReadFromSnapshot(msg); + + /* + if (msg.HasChanged()) { + Present(); + } + */ +} + +void hhHand::ClientPredictionThink( void ) { + BecomeActive(TH_THINK|TH_ANIMATE); + UpdateVisuals(); + idAnimatedEntity::ClientPredictionThink(); +} + +bool hhHand::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + switch (event) { + case EVENT_REMOVEHAND: { + RemoveHand(); + return true; + } + default: { + return hhAnimatedEntity::ClientReceiveEvent(event, time, msg); + } + } +} + +/* +============ +hhHand::Event_Ready +============ +*/ +void hhHand::Event_Ready() { + Show(); + Ready(); +} + + +/* +=========== +hhHand::Event_Lowered +=========== +*/ +void hhHand::Event_Lowered() { + status = HS_LOWERED; + Hide(); +} + + +/* +============ +hhHand::Event_Raise +============ +*/ +void hhHand::Event_Raise() { + Raise(); +} + + +/* +============ +hhHand::Ready +============ +*/ +void hhHand::Ready() { + + switch ( idealState ) { + + case HS_READY: + CycleAnim(-1, "idle", 500); + status = HS_READY; + break; + + case HS_LOWERED: + PutAway(); + break; + + default: + gameLocal.Warning( "hhHand::Ready: Unsupported ideal state: %d", idealState ); + break; + + } +} + + +/* +============ +hhHand::Raise +============ +*/ +void hhHand::Raise() { + + Show(); + + if ( IsRaising() ) { // If already raising, return + return; + } + + status = HS_RAISING; + PlayAnim( -1, "raise", &EV_Hand_Ready ); +} + + +/* +============ +hhHand::PutAway +============ +*/ +void hhHand::PutAway() { + + if ( IsLowering() ) { + // already being put away, so don't play more than one put away animations + return; + } + + status = HS_LOWERING; + PlayAnim( -1, "putaway", &EV_Hand_Lowered ); +} + + +/* +=============== +hhHand::CycleAnim +=============== +*/ +void hhHand::CycleAnim( int channel, const char *animname, int blendTime ) { + if ( channel < 0 ) { + channel = GetChannelForAnim( animname ); + } + + int anim = GetAnimator()->GetAnim( animname ); + if ( anim ) { + GetAnimator()->CycleAnim( channel, anim, gameLocal.time, blendTime ); + } +} + +/* +=============== +hhHand::PlayAnim +=============== +*/ +void hhHand::PlayAnim( int channel, const char *animname, const idEventDef *event ) { + int anim; + + // HUMANHEAD nla + if ( animEvent ) { + CancelEvents( animEvent ); + } + animEvent = event; + + if ( channel < 0 ) { + channel = GetChannelForAnim( animname ); + } + // HUMANHEAD END + + anim = GetAnimator()->GetAnim( animname ); + if ( anim ) { + GetAnimator()->PlayAnim( channel, anim, gameLocal.time, FRAME2MS( animBlendFrames ) ); + animDoneTime = GetAnimator()->CurrentAnim( channel )->GetEndTime(); + } else { + // This is a valid case, some hands (mounted gun) don't animate + GetAnimator()->Clear( channel, gameLocal.time, FRAME2MS( animBlendFrames ) ); + animDoneTime = 0; + } + animBlendFrames = 4; + + // HUMANHEAD nla + if ( animEvent ) { + PostEventMS( animEvent, animDoneTime - gameLocal.time ); + } + // HUMANHEAD END +} + + +/* +============ +hhHand::AttachHand +Returns false if the hand can not be attached +============ +*/ +bool hhHand::AttachHand( hhPlayer *player, bool attachNow ) { + assert(player); + + if ( !CheckHandAttach( player ) ) { + return( false ); + } + + int loweringFinished = LowerHandOrWeapon( player ); + SetOwner( player ); + Bind( player, true ); + + if ( loweringFinished >= 0 ) { + player->handNext = this; + // If the attachment should wait, and we don't want to force it to happen now + if ( loweringFinished - gameLocal.time > 0 && !attachNow ) { + PostEventMS( &EV_Hand_DoAttach, loweringFinished - gameLocal.time, player ); + } + else { + Event_DoHandAttach( player ); + } + attachTime = loweringFinished; + } + + return( true ); +} + +/* +============ +hhHand::LowerHandOrWeapon + Used when the hand adds itself. + returns the time when the lowering will be done + will be -1 if the hand has already been attached. +============ +*/ +int hhHand::LowerHandOrWeapon( hhPlayer *player ) { + int animDone = gameLocal.time; + int animDone2; + + // If there is a 'next' hand, we can just replace it, as it is already playing the down anim for the hand + if ( player->handNext.IsValid() ) { + animDone = player->handNext->GetAttachTime(); + + gameLocal.hands.Remove( player->handNext.GetEntity() ); + + // Clear the old hand of any pending events, and then remove it + player->handNext->SetOwner( NULL ); + player->handNext->CancelEvents( NULL ); + player->handNext->ProcessEvent( &EV_Remove ); + + player->handNext = NULL; + } + // Else, If there is just a hand, play the down anim on it or replace it + else if ( player->hand.IsValid() ) { + //! Work out this logic more fully + if ( replacePrevious ) { + ReplaceHand( player ); + animDone = -1; + } + // If it isn't on it's way down, move it down + if ( !player->hand->IsLowered() ) { + player->hand->PutAway(); + animDone = player->hand->GetAnimDoneTime(); + } + } + + animDone2 = HandleWeapon( player, this, animDone - gameLocal.time ); + if ( ( animDone >= 0 ) && ( animDone2 > animDone ) ) { + animDone = animDone2; + } + // If there is a weapon play the down anim on it + /* + if ( player->hhweapon ) { + if ( lowerWeapon && !player->hhweapon->IsLowered() ) { + player->hhweapon->PutAway(); + animDone2 = player->hhweapon->GetAnimDoneTime(); + if ( animDone2 > animDone ) { + animDone = animDone2; + } + } + else if ( asideWeapon && !player->hhweapon->IsAside() ) { + player->hhweapon->PutAside(); + // When aside, we want to have it play right away, so dont' change animDone + } + // gameLocal.Printf("Scheduled down for %.2f\n", animDone); + } + */ + + return( animDone ); +} + +/* +============== +RaiseHandOrWeapon + + Assumes the hand is all the way down +============== +*/ +void hhHand::RaiseHandOrWeapon( hhPlayer *player ) { + hhHand *theHand = NULL; + int raiseDelay = 0; + + // Get the next hand + theHand = this->GetPreviousHand( player ); + + raiseDelay = HandleWeapon( player, theHand ); + + if ( theHand && ( !theHand->IsRaising() || !theHand->IsReady() ) ) { + theHand->PostEventMS( &EV_Hand_Raise, raiseDelay - gameLocal.time ); + } +} + + +/* +================ +hhHand::Reraise + Raises the hand again. +================ +*/ +void hhHand::Reraise( ) { + hhPlayer *thePlayer = NULL; + int raiseDelay = 0; + + CancelEvents( &EV_Remove ); + CancelEvents( &EV_Hand_Raise ); + + if ( owner.IsValid() && owner.GetEntity() && owner->IsType( hhPlayer::Type ) ) { + thePlayer = ( hhPlayer * ) owner.GetEntity(); + } + else { + gameLocal.Printf( "hhHand::Reraise: Warning tried to reraise when not on an hhPlayer (%p)\n", owner ); + return; + } + + raiseDelay = HandleWeapon( thePlayer, this ); + + if ( !IsRaising() || !IsReady() ) { + PostEventMS( &EV_Hand_Raise, raiseDelay - gameLocal.time ); + } +} + + +/* +=========== +hhHand::GetPreviousHand +=========== +*/ +hhHand *hhHand::GetPreviousHand( hhPlayer *player ) { + hhHand *prevHand = NULL; + + // If there is no player, just give them our previous hand + if ( player == NULL ) { + return( previousHand ); + } + + // If we are the next hand, up the current hand + if ( player->handNext == this ) { + prevHand = player->hand.GetEntity(); + } + // Else If we are the current hand + else if ( player->hand == this ) { + // If we have a previous hand, raise it if it isn't already up or being raised + if ( previousHand ) { + prevHand = previousHand; + } + } + // Else we are neither, throw a warning. How are we here? + else { + gameLocal.Warning( "ERROR GetPreviousHand: ERROR: Tried to get a previous hand when we (%p) are not on the player (%p/%p)", + this, player->handNext, player->hand ); + } + + return( prevHand ); +} + + +/* +============ +hhHand::HandleWeapon + Set the weapon in the proper place for the hand + Assumes the hand in question will be raised. + Returns time (MS) to wait until the hand can be raised. Only in the + case of the weapon being lowered does it really matter/ > 0 +============ +*/ +int hhHand::HandleWeapon( hhPlayer *player, hhHand *hand, int weaponDelay, bool doNow ) { + int raiseHandDelay = gameLocal.time; + bool weaponReady = ( hand == NULL ) && !player->ChangingWeapons(); + + // If we don't have a weapon, then just return + if ( !player->weapon.IsValid() || player->GetIdealWeapon() == 0 ) { + return( 0 ); + } + + // If we have a hand to compare to + if ( hand ) { + // If the hand wants the weapon lowered + if ( hand->lowerWeapon || ( hand->handedness & player->weapon->GetHandedness() ) ) { + player->weapon->PutAway(); + if ( doNow ) { + player->weapon->SetState( "Down", 0 ); + } + else { + // Foce the gun to go, so we can know when it'll be done playing the anim. + player->weapon->UpdateScript(); + raiseHandDelay = player->weapon->GetAnimDoneTime(); + } + /* + // If it isn't lowered, lower it + if ( !player->weapon->IsLowered() ) { + player->weapon->PutAway(); + raiseHandDelay = player->weapon->GetAnimDoneTime(); + } + */ + } //. lower the weapon + // If the hand wants the weapon to the side + else if ( hand->asideWeapon ) { + player->weapon->PutAside(); + if ( doNow ) { + player->weapon->SetState( "Aside", 0 ); + } + // We only want a delay if the weapon should pop up? + else if ( player->weapon->IsLowered() ) { + // Foce the gun to go, so we can know when it'll be done playing the anim. + player->weapon->UpdateScript(); + raiseHandDelay = player->weapon->GetAnimDoneTime(); + player->weapon->SetState( "PutAside", 4 ); + } + /* + // If it is lowered, + if ( player->weapon->IsLowered() ) { + // First raise it, then set it aside + int done = weaponDelay; + idAnim *anim = player->weapon->GetAnimator()->GetAnim( "raise" ); // UGLY Hack, as hardcoded to the name + + player->weapon->PostEventMS( &EV_Weapon_WeaponRising, weaponDelay ); + if ( anim ) { done = anim->Length(); } + player->weapon->PostEventMS( &EV_Weapon_Aside, done ); + } + // Else if it is ready/upright and not aside + else if ( !player->weapon->IsAside() ) { + // Just set it aside + player->weapon->PutAside(); + } + */ + } + // Weapon should be in ready mode + else { + weaponReady = true; + } + } // Valid hand + + // If we don't have a hand, so just make sure the weapon is raised + if ( weaponReady ) { + player->weapon->Raise(); + if ( doNow ) { + player->weapon->SetState( "Idle", 0 ); + } + + /* + + // Clear any Aside events that may be posted. Happens when it was lowered, and the previous hand wanted aside + player->weapon->CancelEvents( &EV_Weapon_Aside ); + + // If the weapon is aside, put it upright + if ( player->weapon->IsAside() ) { + player->weapon->PutUpright(); + } + // Else if it is lowering, raise it + else if ( !player->weapon->IsRising() && player->weapon->IsLowered() ) { + player->weapon->Raise(); + } + // Else if we aren't ready, how can we not be??!? + else if ( player->weapon->IsLowered() || player->weapon->IsLowered() ) { + gameLocal.Warning( "hhHand::HandleWeapon ERROR: Have a weapon that is lowered/lowering when shouldn't be!" ); + } + */ + } + + return( raiseHandDelay ); +} + + +/* +============ +hhHand::RemoveHand + Plays the animations and schedules pointer changes +============ +*/ +bool hhHand::RemoveHand( void ) { + hhPlayer *player; + int animDone = gameLocal.time; + + if ( owner.IsValid() && owner.GetEntity() ) { + if ( owner->IsType( hhPlayer::Type ) ) { + player = static_cast( owner.GetEntity() ); + } + else { + gameLocal.Warning( "ERROR: RemoveHand: Tried to remove from a non player" ); + return( false ); + } + } + else { + gameLocal.Warning( "ERROR: RemoveHand: Tried to remove with no owner!!" ); + return( false ); + } + + if ( !CheckHandRemove( player ) ) { + return( false ); + } + + // Unaside is asap if we asided it, and the prev hand isn't gonna want it asided + // NLA - If we want to upright the GUI + hhHand *prevHand = GetPreviousHand( player ); + if ( ( !prevHand || !prevHand->asideWeapon ) && + ( asideWeapon && player->weapon.IsValid() && player->weapon->IsAside() ) ){ + player->weapon->PutUpright(); + } + + CancelEvents( &EV_Hand_DoRemove ); + + // Play down anim + if( !IsLowering() ) { + PutAway(); + animDone = GetAnimDoneTime(); + PostEventMS( &EV_Hand_DoRemove, animDone - gameLocal.time ); + } + else if ( IsLowered() ) { // If already down, just remove it now + Event_DoHandRemove(); + } + + return( true ); +} + + +/* +============ +hhHand::SetOwner +============ +*/ +void hhHand::SetOwner( idActor *owner ) { + + this->owner = owner; + + if (owner) { + if( GetPhysics() && GetPhysics()->IsType(hhPhysics_StaticWeapon::Type) ) { + static_cast(GetPhysics())->SetSelfOwner( owner ); + } + + // only show the surface in player view + renderEntity.allowSurfaceInViewID = owner->entityNumber+1; + } +} + + +/* +============ +hhHand::ReplaceHand +============ +*/ +void hhHand::ReplaceHand( hhPlayer *player) { + //int animDone; + hhHand *hand = NULL; + + //! What to do if hand is null? + if( !player || !player->hand.IsValid() ) { + return; + } + hand = player->hand.GetEntity(); + + // Copy over any key info needed + this->previousHand = hand->previousHand; + + HandleWeapon( player, this ); + + player->hand = this; + hand->attached = false; + hand->owner = NULL; + hand->Hide(); + hand->PostEventMS( &EV_Remove, 0 ); + + gameLocal.hands.Remove( hand ); + + attached = true; + Show(); + Raise(); +} + + +/* +============ +hhHand::CheckHandAttach +============ +*/ +bool hhHand::CheckHandAttach( hhPlayer *owner ) { + bool debug = 0; + + if ( attached ) { + gameLocal.Warning( "Tried to attach an already attached hand." ); + return( false ); + } + + if( !owner ) { + if ( debug ) { gameLocal.Printf( "Out because of owner\n" ); } + return( false ); + } + + // More important hand there. Abort + if ( owner->hand.IsValid() && ( GetPriority() < owner->hand->GetPriority() ) ) { + if ( debug ) { gameLocal.Printf( "Out because of hand priority\n" ); } + return( false ); + } + + // More important hand about to be there. Abort + if ( owner->handNext.IsValid() && ( GetPriority() < owner->handNext->GetPriority() ) ) { + if ( debug ) { gameLocal.Printf( "Out because of next hand priority\n" ); } + return( false ); + } + + //? What do to if both equal priority? + if ( owner->hand.IsValid() && ( GetPriority() == owner->hand->GetPriority() ) ) { + // Same hand, abort! + if ( ( owner->hand->spawnArgs.GetString( "classname" ) == spawnArgs.GetString( "classname" ) ) && ( owner->hand != this ) ) { + if ( debug ) { gameLocal.Printf( "Out because of same hand\n" ); } + return( false ); + } + } + + //? What do to if both equal priority? + if ( owner->handNext.IsValid() && ( GetPriority() == owner->handNext->GetPriority() ) ) { + // Same hand, abort! + hhHand* currentHand = owner->handNext.GetEntity(); + if ( ( owner->handNext->spawnArgs.GetString( "classname" ) == spawnArgs.GetString( "classname" ) ) && ( owner->handNext != this ) ) { + if ( debug ) { gameLocal.Printf( "Out because of same next hand\n" ); } + return( false ); + } + } + + return( true ); +} + + +/* +============ +hhHand::CheckHandRemove +============ +*/ +bool hhHand::CheckHandRemove( hhPlayer *player ) { + + // Check have a valid player + if ( player == NULL ) { + gameLocal.Warning( "ERROR: DoHandRemove: We are not attached to a player!"); + return( false ); + } + + // We aren't here! + if ( IsAttached() && ( player->hand != this ) ) { + gameLocal.Warning( "ERROR: DoHandRemove: Tried to detach from an player we are not assigned to %d %p %p", + (int) IsAttached(), player->hand, this ); + return( false ); + } + + return( true ); +} + +/* +============ +hhHand::SetModel +============ +*/ +void hhHand::SetModel( const char *modelname ) { + hhAnimatedEntity::SetModel( modelname ); +} + +/* +============ +hhHand::Present +============ +*/ +void hhHand::Present() { + hhAnimatedEntity::Present(); +} + +/* +============ +hhHand::Event_DoHandAttach +============ +*/ +void hhHand::Event_DoHandAttach( idEntity *owner ) { + DoHandAttach( owner ); +} + +/* +============ +hhHand::DoHandAttach +============ +*/ +bool hhHand::DoHandAttach( idEntity *owner ) { + hhPlayer *player; + + if ( owner && owner->IsType( hhPlayer::Type ) ) { + player = static_cast( owner ); + } + else { + gameLocal.Warning("ERROR: Tried to attach to a non-player"); + return( false ); + } + + if ( !CheckHandAttach( player ) ) { + PostEventMS( &EV_Remove, 0 ); + return( false ); + } + + // Sanity check, we should be the top dogh + if ( player->handNext != this && !gameLocal.isClient ) { //rww - don't care on client, because we cannot rely on handNext + gameLocal.Warning( "ERROR: We (%p) should be the next hand (%p) but aren't", + this, player->handNext ); + } + + // Make sure nothing has changed + HandleWeapon( player, this ); + + // We are top dog, replace! :) + if ( !replacePrevious ) { + previousHand = player->hand.GetEntity(); + } + else { + previousHand = NULL; + } + player->hand = this; + player->handNext = NULL; + + attached = true; + attachTime = -1; + + Raise(); + Show(); + + return( true ); +} + +/* +============ +hhHand::Event_DoHandRemove +============ +*/ +void hhHand::Event_DoHandRemove( void ) { + DoHandRemove( ); +} + + +/* +============ +hhHand::ForceRemove + - Should only be called when you don't need other hands/weaspons adjusted +============ +*/ +void hhHand::ForceRemove( void ) { + + gameLocal.hands.Remove( this ); + + owner = NULL; + + Hide(); +} + +/* +============ +hhHand::DoHandRemove +============ +*/ +bool hhHand::DoHandRemove( void ) { + hhPlayer *player; + + gameLocal.hands.Remove( this ); + + if ( owner.IsValid() && owner.GetEntity() && owner->IsType( hhPlayer::Type ) ) { + player = static_cast( owner.GetEntity() ); + } + else { + gameLocal.Warning( "ERROR: DoHandRemove: Tried to remove from a non player" ); + return( false ); + } + + // Play up anim + RaiseHandOrWeapon( player ); + + if ( !CheckHandRemove( player ) ) { + return( false ); + } + + // Do the actual removal logic + Hide(); + PostEventMS( &EV_Remove, 0 ); + + if ( IsAttached() ) { + player->hand = previousHand; + previousHand = NULL; + + attached = false; + } + + // If we were the next hand, clear it + if ( player->handNext == this ) { + player->handNext = NULL; + } + + owner = NULL; + + return( true ); +} + +/* +============ +hhHand::Event_RemoveHand + Plays the animations and schedules pointer changes +============ +*/ +void hhHand::Event_RemoveHand( void ) { + RemoveHand(); +} + +/* +=========== +hhHand::AddHand +=========== +*/ +hhHand *hhHand::AddHand( hhPlayer *player, const char *handclass, bool attachNow ) { + idDict args; + hhHand *hand = NULL; + + args.SetVector( "origin", player->GetEyePosition() ); + args.SetMatrix( "rotation", player->viewAngles.ToMat3() ); + + hand = static_cast< hhHand * >( gameLocal.SpawnObject( handclass, &args) ); + if ( hand ) { + hand->SetOwner( player ); + // We have a prob if you can't attach. Should return NULL + if ( !hand->AttachHand( player, attachNow ) ) { + hand->SetOwner( NULL ); + + gameLocal.hands.Remove( hand ); + + hand->PostEventMS( &EV_Remove, 0 ); + + return( NULL ); + } + } + + return( hand ); +} + + +/* +============ +hhHand::PrintHandInfo +============ +*/ +void hhHand::PrintHandInfo( idPlayer *player ) { + hhPlayer *hhplayer = NULL; + hhHand *hand = NULL; + int count = 0; + idList< idEntityPtr< hhHand > > orphaned; + + if ( player->IsType( hhPlayer::Type ) ) { + hhplayer = (hhPlayer *) player; + } + else { + return; + } + + orphaned = gameLocal.hands; + + gameLocal.Printf( "Weapon: %p Class: %s State: %d\n", player->weapon, + (const char *) player->weapon->GetDict()->GetString("classname"), + (int) player->weapon->GetStatus() ); + + hand = hhplayer->hand.GetEntity(); + while ( hand != NULL ) { + count++; + gameLocal.Printf( "Hand %d: %p Class: %s State: %d\n", count, hand, + hand->spawnArgs.GetString("classname"), (int) hand->GetStatus() ); + orphaned.Remove( hand ); + hand = hand->previousHand; + } + + count = 0; + for ( int i = 0; i < orphaned.Num(); ++i ){ + count++; + hand = orphaned[ i ].GetEntity(); + gameLocal.Printf( "Orphaned Hand %d: %p Class: %s State: %d\n", count, hand, + hand->spawnArgs.GetString("classname"), (int) hand->GetStatus() ); + } +} + + +/* +================ +hhHand::GetMasterDefaultPosition +================ +*/ +void hhHand::GetMasterDefaultPosition( idVec3 &masterOrigin, idMat3 &masterAxis ) const { + idActor* actor = NULL; + idEntity* master = GetBindMaster(); + + if( master ) { + if( master->IsType(idActor::Type) ) { + actor = static_cast( master ); + actor->DetermineOwnerPosition( masterOrigin, masterAxis ); + + masterOrigin = actor->ApplyLandDeflect( masterOrigin, 1.1f ); + } else { + hhAnimatedEntity::GetMasterDefaultPosition( masterOrigin, masterAxis ); + } + } +} diff --git a/src/Prey/game_hand.h b/src/Prey/game_hand.h new file mode 100644 index 0000000..e013912 --- /dev/null +++ b/src/Prey/game_hand.h @@ -0,0 +1,171 @@ + +#ifndef __PREY_GAME_HAND_H__ +#define __PREY_GAME_HAND_H__ + +// Forward declar +class hhPlayer; + +extern const idEventDef EV_Hand_DoRemove; +extern const idEventDef EV_Hand_DoAttach; +extern const idEventDef EV_Hand_Remove; +extern const idEventDef EV_Hand_Ready; +extern const idEventDef EV_Hand_Lowered; +extern const idEventDef EV_Hand_Raise; + +typedef enum { + HS_UNKNOWN, + HS_READY, + HS_LOWERING, + HS_LOWERED, + HS_RAISING +} handStatus_t; + + +class hhHand : public hhAnimatedEntity { + +public: + + CLASS_PROTOTYPE(hhHand); + + virtual ~hhHand(); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - network code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + + enum { + EVENT_REMOVEHAND = hhAnimatedEntity::EVENT_MAXEVENTS, + EVENT_MAXEVENTS + }; + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + + static hhHand * AddHand( hhPlayer *player, const char *classname, bool attachNow = false ); + + static void PrintHandInfo( idPlayer *player ); + + virtual void SetModel( const char *modelname ); + + virtual void Present(); + + virtual void Action( void ) { }; + virtual void SetAction( const char* str ) {}; //HUMANHEAD bjk + + int GetPriority( void ) { return( priority ); }; + + int GetStatus( ) { return( status ); }; + + void Reraise( ); + + bool IsReady() { return( status == HS_READY ); } + bool IsLowering() { return( status == HS_LOWERING || status == HS_LOWERED ); } + bool IsLowered() { return( status == HS_LOWERED ); } + bool IsRaising() { return( status == HS_RAISING ); } + + virtual void Raise(); + virtual void PutAway(); + virtual void Ready(); + + void Event_Ready(); + void Event_Lowered(); + void Event_Raise(); + + + void PlayAnim( int channel, const char *animName, const idEventDef *animEvent = NULL ); + void CycleAnim( int channel, const char *animname, int blendTime ); + + bool AttachHand( hhPlayer *player, bool attachNow = false ); + bool RemoveHand( void ); + + bool IsAttached() { return attached; } + + int LowerHandOrWeapon( hhPlayer *player ); + void RaiseHandOrWeapon( hhPlayer *player ); + int HandleWeapon( hhPlayer *player, hhHand *topHand, int weaponDelay = 0, bool doNow = false ); + + // Replace the player hand with this hand. Does a quick swap + void ReplaceHand( hhPlayer *player ); + + int GetAttachTime( ) { return( attachTime ); }; + + // Functions that could be put in common base class + int GetAnimDoneTime() { return( animDoneTime ); }; + + void SetOwner( idActor *owner ); + void GetMasterDefaultPosition( idVec3 &masterOrigin, idMat3 &masterAxis ) const; + + virtual bool IsValidFor( hhPlayer *who ) { return( true ); } + + // These used to be protected. Call only if you know what you are doing! + hhHand * GetPreviousHand( hhPlayer *player = NULL ); + + void Event_DoHandAttach( idEntity *player ); + + void ForceRemove( void ); + +protected: + void Event_RemoveHand( void ); + + // Event only methods. Should NOT be called directly + void Event_DoHandRemove( void ); + + bool DoHandAttach( idEntity *player ); + bool DoHandRemove( void ); + + +protected: + bool CheckHandAttach( hhPlayer *player ); + bool CheckHandRemove( hhPlayer *player ); + +protected: + // Who we are associated with + idEntityPtr owner; + + // What is the priority of this hand? + int priority; + + // Previous hand. Will restore on closing down! + hhHand *previousHand; + + // Should we lower the weapon for this hand? + bool lowerWeapon; + + // If we don't lower the weapon, do we put it aside? + bool asideWeapon; + + // DEBUG - We should never delete w/out being attached + bool attached; + + // Should we replace the previous hand instead of putting it on the hand stack? + bool replacePrevious; + + // What time do we attach? + int attachTime; + + // What hands do we represent? (1 = right, 2 = left, 3 = both) + int handedness; + + // When will we be done animating? + int animDoneTime; + + // How many frames should we blend? + int animBlendFrames; + + // Status of the hand; + int status; + + // What state we would like to be + int idealState; + + // Last event posted with an anim + const idEventDef * animEvent; + + hhPhysics_StaticWeapon physicsObj; +}; + +#endif /* __PREY_GAME_HAND_H__ */ + diff --git a/src/Prey/game_handcontrol.cpp b/src/Prey/game_handcontrol.cpp new file mode 100644 index 0000000..05ed0cb --- /dev/null +++ b/src/Prey/game_handcontrol.cpp @@ -0,0 +1,110 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +CLASS_DECLARATION( hhHand, hhControlHand ) +END_CLASS + + +void hhControlHand::Spawn() { + bProcessControls = false; + oldStatus = 0; + + anims[ 0 ][ 0 ] = GetAnimator()->GetAnim("bottom_backward"); + anims[ 0 ][ 1 ] = GetAnimator()->GetAnim("bottom_center"); + anims[ 0 ][ 2 ] = GetAnimator()->GetAnim("bottom_forward"); + anims[ 1 ][ 0 ] = GetAnimator()->GetAnim("center_backward"); + anims[ 1 ][ 1 ] = GetAnimator()->GetAnim("center_center"); + anims[ 1 ][ 2 ] = GetAnimator()->GetAnim("center_forward"); + anims[ 2 ][ 0 ] = GetAnimator()->GetAnim("top_backward"); + anims[ 2 ][ 1 ] = GetAnimator()->GetAnim("top_center"); + anims[ 2 ][ 2 ] = GetAnimator()->GetAnim("top_forward"); + + fl.networkSync = true; +} + +void hhControlHand::Save(idSaveGame *savefile) const { + savefile->Write( anims, sizeof(int)*HAND_MATRIX_WIDTH*HAND_MATRIX_HEIGHT ); + savefile->WriteBool( bProcessControls ); + savefile->WriteInt( oldStatus ); +} + +void hhControlHand::Restore( idRestoreGame *savefile ) { + savefile->Read( anims, sizeof(int)*HAND_MATRIX_WIDTH*HAND_MATRIX_HEIGHT ); + savefile->ReadBool( bProcessControls ); + savefile->ReadInt( oldStatus ); +} + +void hhControlHand::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteBits(bProcessControls, 1); + msg.WriteBits(oldStatus, 32); + + hhHand::WriteToSnapshot(msg); +} + +void hhControlHand::ReadFromSnapshot( const idBitMsgDelta &msg ) { + bProcessControls = !!msg.ReadBits(1); + oldStatus = msg.ReadBits(32); + + hhHand::ReadFromSnapshot(msg); +} + +void hhControlHand::ClientPredictionThink( void ) { + RunPhysics(); + + // HUMANHEAD pdm + if (thinkFlags & TH_TICKER) { + Ticker(); + } + + UpdateAnimation(); + UpdateVisuals(); + Present(); +} + + +void hhControlHand::Raise( void ) { + hhHand::Raise(); + + SetShaderParm(4, -MS2SEC(gameLocal.time)); // time + SetShaderParm(5, 1.0f); // Dir +} + +void hhControlHand::Ready() { + hhHand::Ready(); + bProcessControls = true; +} + +void hhControlHand::PutAway( void ) { + hhHand::PutAway(); + + SetShaderParm(4, -MS2SEC(gameLocal.time)); // time + SetShaderParm(5, -1.0f); // Dir +} + +void hhControlHand::UpdateControlDirection(idVec3 &dir) { + int anim; + + int curStatus = dir.DirectionMask(); + + if (bProcessControls && oldStatus != curStatus) { + // Determine which anim group to play. (Up/Down/Normal & Forward/Center/Back) + int z_index = dir.z < 0 ? 0 : dir.z > 0 ? 2 : 1; + int x_index = dir.x < 0 ? 0 : dir.x > 0 ? 2 : 1; + + anim = anims[ z_index ][ x_index ]; + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, 250); + + // Now determine which left and right to play + float left = dir.y > 0 ? 1.0f : 0.0f; + float right = dir.y < 0 ? 1.0f : 0.0f; + + GetAnimator()->CurrentAnim( ANIMCHANNEL_ALL )->SetSyncedAnimWeight( 0, left ); + GetAnimator()->CurrentAnim( ANIMCHANNEL_ALL )->SetSyncedAnimWeight( 1, 1.0f ); + GetAnimator()->CurrentAnim( ANIMCHANNEL_ALL )->SetSyncedAnimWeight( 2, right ); + + oldStatus = curStatus; + } +} diff --git a/src/Prey/game_handcontrol.h b/src/Prey/game_handcontrol.h new file mode 100644 index 0000000..1cb3243 --- /dev/null +++ b/src/Prey/game_handcontrol.h @@ -0,0 +1,34 @@ + +#ifndef __PREY_GAME_HAND_CONTROL_H__ +#define __PREY_GAME_HAND_CONTROL_H__ + +#define HAND_MATRIX_WIDTH 3 +#define HAND_MATRIX_HEIGHT 3 + +class hhControlHand : public hhHand { +public: + CLASS_PROTOTYPE(hhControlHand); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - network code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + + void UpdateControlDirection(idVec3 &dir); + virtual void Ready(); + virtual void Raise(); + virtual void PutAway(); + +protected: + // Matrix of anims for up/down/normal & left/ceneter/right + int anims[ HAND_MATRIX_WIDTH ][ HAND_MATRIX_HEIGHT ]; + + bool bProcessControls; + int oldStatus; +}; + +#endif diff --git a/src/Prey/game_healthbasin.cpp b/src/Prey/game_healthbasin.cpp new file mode 100644 index 0000000..aa7b360 --- /dev/null +++ b/src/Prey/game_healthbasin.cpp @@ -0,0 +1,246 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_GiveHealth( "giveHealth" ); + +const idEventDef EV_IdleMode( "" ); +const idEventDef EV_FinishedPuke( "" ); +const idEventDef EV_Expended( "" ); + + +CLASS_DECLARATION( hhAnimatedEntity, hhHealthBasin ) + EVENT( EV_Activate, hhHealthBasin::Event_Activate ) + EVENT( EV_GiveHealth, hhHealthBasin::Event_GiveHealth ) + EVENT( EV_Broadcast_AppendFxToList, hhHealthBasin::Event_AppendFxToIdleList ) + + EVENT( EV_IdleMode, hhHealthBasin::Event_IdleMode ) + EVENT( EV_FinishedPuke, hhHealthBasin::Event_FinishedPuking ) + EVENT( EV_Expended, hhHealthBasin::Event_Expended ) +END_CLASS + + +void hhHealthBasin::Event_IdleMode() { + PlayCycle( "idle" ); + StartSound( "snd_idle", SND_CHANNEL_IDLE ); + BasinMode = BASIN_Idle; +} + +void hhHealthBasin::Event_FinishedPuking() { + if( currentHealth <= 0.f ) { + Event_Expended(); + } + else { + PostEventMS( &EV_IdleMode, PlayAnim("transidle", 4) ); + } +} + +void hhHealthBasin::Event_Expended() { + PlayAnim( "transdeath" ); + StartSound( "snd_die", SND_CHANNEL_ANY ); + hhUtils::RemoveContents( idleFxList, true ); //MDC: This doesn't seem to be working, possibly something with the broadcast fx + BasinMode = BASIN_Expended; +} +/* +================ +hhHealthBasin::hhHealthBasin +================ +*/ +hhHealthBasin::hhHealthBasin() { +} + +/* +================ +hhHealthBasin::Spawn +================ +*/ +void hhHealthBasin::Spawn() { + if ( g_wicked.GetBool() ) { // CJR: Don't spawn health basins in wicked mode + PostEventMS( &EV_Remove, 0 ); + } + + currentHealth = spawnArgs.GetFloat( "maxHealth" ); + activator = NULL; + fl.takedamage = true; + + verificationAbsBounds.Clear(); + GetPhysics()->SetContents( CONTENTS_BODY ); + + SpawnTrigger(); + SpawnFx(); + + PostEventMS( &EV_IdleMode, 0 ); +} + +void hhHealthBasin::Save( idSaveGame *savefile ) const { + savefile->WriteInt( BasinMode ); + activator.Save( savefile ); + savefile->WriteFloat( currentHealth ); + savefile->WriteBounds( verificationAbsBounds ); + + int num = idleFxList.Num(); + savefile->WriteInt( num ); + for( int i = 0; i < num; i++ ) { + idleFxList[i].Save( savefile ); + } +} + +void hhHealthBasin::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( reinterpret_cast ( BasinMode ) ); + activator.Restore( savefile ); + savefile->ReadFloat( currentHealth ); + savefile->ReadBounds( verificationAbsBounds ); + + int num; + savefile->ReadInt( num ); + idleFxList.Clear(); + idleFxList.SetNum( num ); + for( int i = 0; i < num; i++ ) { + idleFxList[i].Restore( savefile ); + } +} + +/* +================ +hhHealthBasin::~hhHealthBasin +================ +*/ +hhHealthBasin::~hhHealthBasin() { + hhUtils::RemoveContents( idleFxList, true ); +} + +/* +=============== +hhHealthBasin::SpawnTrigger +=============== +*/ +void hhHealthBasin::SpawnTrigger() { + idDict args; + + args.Set( "target", name.c_str() ); + args.Set( "mins", spawnArgs.GetString("triggerMins") ); + args.Set( "maxs", spawnArgs.GetString("triggerMaxs") ); + args.SetVector( "origin", GetOrigin() ); + args.SetMatrix( "rotation", GetAxis() ); + gameLocal.SpawnObject( spawnArgs.GetString("def_trigger"), &args ); + + verificationAbsBounds.FromTransformedBounds( idBounds(spawnArgs.GetVector("triggerMins"), spawnArgs.GetVector("triggerMaxs") ) + idBounds( vec3_zero, idVec3(75.0f, 0.0f, 0.0f)), GetOrigin(), GetAxis() ); +} + +/* +=============== +hhHealthBasin::SpawnFx +=============== +*/ +void hhHealthBasin::SpawnFx() { + BroadcastFxInfoAlongBonePrefixUnique( &spawnArgs, "fx_idle", "joint_idleFx", NULL, &EV_Broadcast_AppendFxToList ); +} + +/* +=============== +hhHealthBasin::PlayAnim +=============== +*/ +int hhHealthBasin::PlayAnim( const char* pName, int iBlendTime ) { + int pAnim = 0; + + ClearAnims( iBlendTime ); + + if( !pName && !pName[0] ) { + return 0; + } + + pAnim = GetAnimator()->GetAnim( pName ); + GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, pAnim, gameLocal.time, FRAME2MS( iBlendTime ) ); + + return (pAnim != NULL) ? GetAnimator()->GetAnim( pAnim )->Length() : 0; +} + +/* +=============== +hhHealthBasin::PlayCycle +=============== +*/ +void hhHealthBasin::PlayCycle( const char* pName, int iBlendTime ) { + ClearAnims( iBlendTime ); + + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, GetAnimator()->GetAnim(pName), gameLocal.time, FRAME2MS( iBlendTime ) ); +} + +/* +=============== +hhHealthBasin::ClearAnims +=============== +*/ +void hhHealthBasin::ClearAnims( int iBlendTime ) { + GetAnimator()->ClearAllAnims( gameLocal.time, FRAME2MS( iBlendTime ) ); +} + +/* +=============== +hhHealthBasin::ActivatorVerified +=============== +*/ +bool hhHealthBasin::ActivatorVerified( const idEntityPtr& Activator ) { + if( !Activator.IsValid() || !Activator->GetPhysics()->GetAbsBounds().IntersectsBounds(verificationAbsBounds) ) { + return false; + } + //mdc - also check the health to be a valid activator + if( Activator->health < Activator->GetMaxHealth() ) { + return true; + } + return false; +} + +/* +=============== +hhHealthBasin::Event_AppendFxToIdleList +=============== +*/ +void hhHealthBasin::Event_AppendFxToIdleList( hhEntityFx* fx ) { + idleFxList.Append( fx ); +} + +/* +=============== +hhHealthBasin::Event_Activate +=============== +*/ +void hhHealthBasin::Event_Activate( idEntity *activatedby ) { + if( BasinMode == BASIN_Idle ) { //only allow activation when idle + activator = static_cast(activatedby); + if( ActivatorVerified(activator) ) { //mdc: only begin puking if valid activator (including less than nominal health) + BasinMode = BASIN_Puking; + StopSound( SND_CHANNEL_IDLE ); + StartSound( "snd_puke", SND_CHANNEL_ANY ); + PostEventMS( &EV_FinishedPuke, PlayAnim("puke") ); + } + } +} + +/* +=============== +hhHealthBasin::Event_GiveHealth + +Called from frame command +=============== +*/ +void hhHealthBasin::Event_GiveHealth() { + float amountApplied = 0.0f; + + assert( currentHealth > 0.0f ); + + //Make sure activator is still in the trigger volume, and that they still need health.. + if( !ActivatorVerified(activator) ) { + return; + } + + int oldHealth = activator->health; + if( activator->Give("health", va("%.2f", currentHealth)) ) { + amountApplied = activator->health - oldHealth; + currentHealth -= amountApplied; + + ActivateTargets( activator.GetEntity() ); + } +} \ No newline at end of file diff --git a/src/Prey/game_healthbasin.h b/src/Prey/game_healthbasin.h new file mode 100644 index 0000000..a11e124 --- /dev/null +++ b/src/Prey/game_healthbasin.h @@ -0,0 +1,54 @@ +#ifndef __HH_HEALTH_BASIN_H +#define __HH_HEALTH_BASIN_H + +/* +TODO: + + effects do not delete when using hhUtils::RemoveContents + + Need to have player face basin to allow use +*/ + +class hhHealthBasin : public hhAnimatedEntity { + CLASS_PROTOTYPE( hhHealthBasin ); + +public: + hhHealthBasin(); + virtual ~hhHealthBasin(); + + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +protected: + void SpawnTrigger(); + void SpawnFx(); + + bool ActivatorVerified( const idEntityPtr& Activator ); + + int PlayAnim( const char* pName, int iBlendTime = 0 ); + void PlayCycle( const char* pName, int iBlendTime = 0 ); + void ClearAnims( int iBlendTime = 0 ); + +protected: + void Event_AppendFxToIdleList( hhEntityFx* fx ); + void Event_Activate( idEntity *pActivator ); + void Event_IdleMode(); + void Event_FinishedPuking(); + void Event_Expended(); + void Event_GiveHealth(); + +protected: + enum EBasinMode { + BASIN_Idle, + BASIN_Puking, + BASIN_Expended, + } BasinMode; + + idEntityPtr activator; + float currentHealth; + + idBounds verificationAbsBounds; + + idList< idEntityPtr > idleFxList; +}; + +#endif diff --git a/src/Prey/game_healthspore.cpp b/src/Prey/game_healthspore.cpp new file mode 100644 index 0000000..02a9886 --- /dev/null +++ b/src/Prey/game_healthspore.cpp @@ -0,0 +1,121 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +#define DEFAULT_SPORE_RESPAWN 15000 + +const idEventDef EV_RespawnSpore( "" ); + +CLASS_DECLARATION( idEntity, hhHealthSpore ) + EVENT( EV_Touch, hhHealthSpore::Event_Touch ) + EVENT( EV_RespawnSpore, hhHealthSpore::Event_RespawnSpore ) +END_CLASS + +/* +================ +hhHealthSpore::Spawn +================ +*/ +void hhHealthSpore::Spawn() { + if ( g_wicked.GetBool() ) { // CJR: Don't spawn health spores in wicked mode + PostEventMS( &EV_Remove, 0 ); + } + + GetPhysics()->SetContents( CONTENTS_TRIGGER ); + SetShaderParm( SHADERPARM_MODE, 1.0f ); // Enable the additive glow on the spore + + fl.networkSync = true; +} + +/* +================ +hhHealthSpore::Event_RespawnSpore +================ +*/ +void hhHealthSpore::Event_RespawnSpore() { + if (gameLocal.isClient) { + return; + } + + GetPhysics()->SetContents( CONTENTS_TRIGGER ); //restore contents + SetShaderParm( SHADERPARM_MODE, 1.0f ); //restore additive pass +} + +void hhHealthSpore::Save(idSaveGame *savefile) const { +} + +void hhHealthSpore::Restore( idRestoreGame *savefile ) { +} + +void hhHealthSpore::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteFloat(renderEntity.shaderParms[SHADERPARM_MODE]); +} + +void hhHealthSpore::ReadFromSnapshot( const idBitMsgDelta &msg ) { + renderEntity.shaderParms[SHADERPARM_MODE] = msg.ReadFloat(); + UpdateVisuals(); +} + +/* +================ +hhHealthSpore::ApplyEffect +================ +*/ +void hhHealthSpore::ApplyEffect( idActor* pActor ) { + if( pActor ) { + int oldHealth = pActor->health; + const char *itemHealthKey = "health"; + if (gameLocal.isMultiplayer) { //rww + itemHealthKey = "health_mp"; + } + pActor->Give( "health", spawnArgs.GetString(itemHealthKey) ); + } +} + +/* +================ +hhHealthSpore::Event_Touch +================ +*/ +void hhHealthSpore::Event_Touch( idEntity* pOther, trace_t* pTraceInfo ) { + hhFxInfo fxInfo; + idActor* pActor = NULL; + + if( pOther && pOther->IsType(idActor::Type) ) { + pActor = static_cast( pOther ); + + //rww - do not go above 100 in mp, even when maxhealth has been raised + if (gameLocal.isMultiplayer && pActor->IsType(hhPlayer::Type) && pActor->health >= MAX_HEALTH_NORMAL_MP) { + return; + } + + if( !pActor->IsDamaged() || pActor->health <= 0 ) { + return; + } + } + + fxInfo.SetNormal( GetAxis()[2] ); + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfoPrefixed( "fx_detonate", GetOrigin(), GetAxis(), &fxInfo ); + + SetShaderParm( SHADERPARM_MODE, 0.0f ); // Disable the additive glow on the spore + + ApplyEffect( pActor ); + + ActivateTargets( pActor ); + + //rww - broadcast + StartSound( "snd_explode", SND_CHANNEL_ANY, 0, true ); + + GetPhysics()->SetContents( 0 ); //MDC - clear our contents, so we can not get re-touched. + fl.refreshReactions = false; // JRM - no since telling AI anymore + + if (gameLocal.isMultiplayer) { //rww - respawn functionality for mp + int respawnTime; + if (!spawnArgs.GetInt("respawn", "0", respawnTime)) { + respawnTime = DEFAULT_SPORE_RESPAWN; + } + PostEventMS(&EV_RespawnSpore, respawnTime); + } +} \ No newline at end of file diff --git a/src/Prey/game_healthspore.h b/src/Prey/game_healthspore.h new file mode 100644 index 0000000..c6f9067 --- /dev/null +++ b/src/Prey/game_healthspore.h @@ -0,0 +1,23 @@ +#ifndef __HH_HEALTH_SPORE_H +#define __HH_HEALTH_SPORE_H + +class hhHealthSpore : public idEntity { + CLASS_PROTOTYPE( hhHealthSpore ); + +public: + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - netcode + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +protected: + void ApplyEffect( idActor* pActor ); + + void Event_Touch( idEntity* pOther, trace_t* pTraceInfo ); + virtual void Event_RespawnSpore(); +}; + +#endif \ No newline at end of file diff --git a/src/Prey/game_inventory.cpp b/src/Prey/game_inventory.cpp new file mode 100644 index 0000000..8acd43c --- /dev/null +++ b/src/Prey/game_inventory.cpp @@ -0,0 +1,579 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +void hhInventory::Clear() { + memset( &requirements, 0, sizeof( requirements ) ); + maxSpirit = 0; + bHasDeathwalked = false; + storedHealth = 0; + energyType = "energy_plasma"; + memset( altMode, 0, sizeof( altMode ) ); + memset( weaponRaised, 0, sizeof( weaponRaised ) ); + memset( lastShot, 0, sizeof( lastShot ) ); //HUMANHEAD bjk PATCH 7-27-06 + zoomFov = 0; + + idInventory::Clear(); +} + +void hhInventory::Save(idSaveGame *savefile) const { + idInventory::Save( savefile ); + + savefile->WriteBool(bHasDeathwalked); + savefile->Write(&maxSpirit, sizeof(maxSpirit)); + savefile->Write(&requirements, sizeof(requirements)); + savefile->WriteInt( storedHealth ); + savefile->WriteString( energyType.c_str() ); + savefile->Write(&altMode, sizeof(altMode)); + savefile->Write(&weaponRaised, sizeof(weaponRaised)); + savefile->WriteInt( zoomFov ); +} + +void hhInventory::Restore( idRestoreGame *savefile ) { + idInventory::Restore( savefile ); + + savefile->ReadBool(bHasDeathwalked); + savefile->Read(&maxSpirit, sizeof(maxSpirit)); + savefile->Read(&requirements, sizeof(requirements)); + savefile->ReadInt( storedHealth ); + savefile->ReadString( energyType ); + savefile->Read(&altMode, sizeof(altMode)); + savefile->Read(&weaponRaised, sizeof(weaponRaised)); + memset( lastShot, 0, sizeof( lastShot ) ); //HUMANHEAD bjk PATCH 7-27-06 + savefile->ReadInt( zoomFov ); +} + +/* +============== +hhInventory::GetPersistantData + PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +============== +*/ +void hhInventory::GetPersistantData( idDict &dict ) { + int i; + int num; + idDict *item; + idStr key; + const idKeyValue *kv; + const char *name; + + // don't bother with powerups or the clip + + // maxhealth, maxspirit + dict.SetInt( "maxhealth", maxHealth); + dict.SetInt( "max_ammo_spiritpower", maxSpirit); + dict.SetBool( "bHasDeathwalked", bHasDeathwalked ); + dict.SetInt( "storedHealth", storedHealth ); + dict.Set( "energyType", energyType.c_str() ); //HUMANHEAD bjk + dict.SetInt( "zoomFov", zoomFov ); //HUMANHEAD bjk + + // ammo + for( i = 0; i < AMMO_NUMTYPES; i++ ) { + name = idWeapon::GetAmmoNameForNum( ( ammo_t )i ); + if ( name ) { + dict.SetInt( name, ammo[ i ] ); + } + } + + //HUMANHEAD bjk: weapons + for( i = 0; i < MAX_WEAPONS; i++ ) { + sprintf( key, "altMode_%i", i ); + dict.SetBool( key, altMode[i] ); + sprintf( key, "weaponRaised_%i", i ); + dict.SetBool( key, weaponRaised[i] ); + } + //HUMANHEAD END + + // 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 ); + } + + // HUMANHEAD CJR: copy all keys with "def_" + // Needed for Hunter Hand GUI + kv = item->MatchPrefix( "def_" ); + if ( kv ) { + while( kv ) { + sprintf( key, "item_%i %s", num, kv->GetKey().c_str() ); + dict.Set( key, kv->GetValue() ); + kv = item->MatchPrefix( "def_", kv ); + } + } // HUMANHEAD END + + // HUMANHEAD CJR: copy all keys with "passtogui_" + // Needed for Hunter Hand GUI + kv = item->MatchPrefix( "passtogui_" ); + if ( kv ) { + while( kv ) { + sprintf( key, "item_%i %s", num, kv->GetKey().c_str() ); + dict.Set( key, kv->GetValue() ); + kv = item->MatchPrefix( "passtogui_", kv ); + } + } // HUMANHEAD END + + num++; + } + } + dict.SetInt( "items", num ); + + // weapons + dict.SetInt( "weapon_bits", weapons ); + + 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 ); + } +} + + +/* +============== +hhInventory::RestoreInventory + PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +============== +*/ +void hhInventory::RestoreInventory( idPlayer *owner, const idDict &dict ) { + int i; + int num; + idDict *item; + idStr key; + idStr itemname; + const idKeyValue *kv; + const char *name; + + Clear(); + + // health + maxHealth = dict.GetInt( "maxhealth", "100" ); + bHasDeathwalked = dict.GetBool( "bHasDeathwalked" ); + storedHealth = dict.GetInt( "storedHealth", "0" ); + energyType = dict.GetString( "energyType", "energy_plasma" ); + zoomFov = dict.GetInt( "zoomFov" ); + + // the clip and powerups aren't restored + + // max spirit + maxSpirit = dict.GetInt( "max_ammo_spiritpower" ); + + // ammo + for( i = 0; i < AMMO_NUMTYPES; i++ ) { + name = idWeapon::GetAmmoNameForNum( ( ammo_t )i ); + if ( name ) { + ammo[ i ] = dict.GetInt( name ); + } + } + + //HUMANHEAD bjk: weapons + for( i = 0; i < MAX_WEAPONS; i++ ) { + sprintf( key, "altMode_%i", i ); + altMode[i] = dict.GetBool( key ); + sprintf( key, "weaponRaised_%i", i ); + weaponRaised[i] = dict.GetBool( key ); + } + //HUMANHEAD END + + // 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 ); + } + } + + //HUMANHEAD aob: in addition to the persistent items, give hardcoded items from players.def + Give( owner, dict, "item", dict.GetString( "item" ), NULL, true ); + //HUMANHEAD END + + // weapons are stored as a number for persistant data, but as strings in the entityDef + weapons = dict.GetInt( "weapon_bits", "0" ); + Give( owner, dict, "weapon", dict.GetString( "weapon" ), NULL, false ); + + 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 ); + } + + // Keep weapon switch HUD element from showing up at level load + weaponPulse = false; +} + +int hhInventory::MaxAmmoForAmmoClass( idPlayer *owner, const char *ammo_classname ) const { + int max = 0; + if (ammo_classname != NULL) { + if (!idStr::Icmp(ammo_classname, "ammo_spiritpower")) { + max = maxSpirit; + } + else { + max = idInventory::MaxAmmoForAmmoClass(owner, ammo_classname); + } + } + return max; +} + +// PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +void hhInventory::AddPickupName( const char *name, const char *icon, bool bIsWeapon) { + if ( idStr::Length(icon) > 0 ) { + idItemInfo &info = pickupItemNames.Alloc(); + + if ( !idStr::Icmpn( name, STRTABLE_ID, strlen( STRTABLE_ID ) ) ) { + info.name = common->GetLanguageDict()->GetString( name ); + } else { + info.name = name; + } + info.icon = icon; + info.time = 0; + info.slotZeroTime = 0; + info.matcolorAlpha = 0.0f; + info.bDoubleWide = bIsWeapon; + } +} + +/* +============== +hhInventory::Give + PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +============== +*/ +bool hhInventory::Give( idPlayer *owner, const idDict &spawnArgs, const char *statname, const char *value, int *idealWeapon, bool updateHud ) { + int i; + const char *pos; + const char *end; + int len; + idStr weaponString; + int max; + const idDeclEntityDef *weaponDecl; + bool tookWeapon; + int amount; + idItemInfo info; + hhPlayer* playerOwner; + + + if( owner && owner->IsType( hhPlayer::Type ) ) { + playerOwner = static_cast(owner); + } + + if ( !idStr::Icmp( statname, "health" ) || !idStr::Icmp( statname, "healthspecial" ) ) { //healthspecial is for mp and indicates that this item can push a player's health up to the "real" maxhealth + int localMaxHealth = maxHealth; + if (gameLocal.isMultiplayer) { //rww - only pipes can put us above 100 in mp + if (idStr::Icmp( statname, "healthspecial" )) { + localMaxHealth = MAX_HEALTH_NORMAL_MP; + } + } + if ( playerOwner->health >= localMaxHealth ) { + return false; + } + int oldHealth = playerOwner->health; + playerOwner->health += atoi( value ); + if ( playerOwner->health > localMaxHealth ) { + playerOwner->health = localMaxHealth; + } + if (playerOwner) { + playerOwner->healthPulse = true; + } + } else if ( !idStr::Icmpn( statname, "ammo_", 5 ) ) { + i = AmmoIndexForAmmoClass( statname ); + max = MaxAmmoForAmmoClass( owner, statname ); + if ( ammo[ i ] >= max ) { + return false; + } + amount = atoi( value ); + if ( amount ) { + ammo[ i ] += amount; + if ( ( max > 0 ) && ( ammo[ i ] > max ) ) { + ammo[ i ] = max; + } + ammoPulse = true; + } + if (playerOwner && !idStr::Icmp(statname, "ammo_spiritpower")) { + playerOwner->spiritPulse = true; + } + } else if ( !idStr::Icmp( statname, "item" ) ) { + pos = value; + while( pos != NULL ) { + end = strchr( pos, ',' ); + if ( end ) { + len = end - pos; + end++; + } else { + len = strlen( pos ); + } + + idStr itemName( pos, 0, len ); + + GiveItem( spawnArgs, gameLocal.FindEntityDefDict(itemName.c_str(), false) ); + + pos = end; + } + } else if ( !idStr::Icmp( statname, "weapon" ) ) { + 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 name + for( i = 1; i < MAX_WEAPONS; i++ ) { + if ( weaponName == playerOwner->GetWeaponName(i) ) { + break; + } + } + + if ( i >= MAX_WEAPONS ) { + gameLocal.Error( "Unknown weapon '%s'", weaponName.c_str() ); + } + + // cache the media for this weapon + weaponDecl = gameLocal.FindEntityDef( weaponName, false ); + + // don't pickup "no ammo" weapon types twice + // not for D3 SP .. 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 conistent to pick it up + if ( gameLocal.isMultiplayer && weaponDecl && ( weapons & ( 1 << i ) ) && !weaponDecl->dict.GetInt( "ammoRequired" ) ) { + continue; + } + + if ( !gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || ( weaponName == "weaponobj_fists" ) ) { + playerOwner->UnlockWeapon( i ); //TODO add key for disabling this + if ( ( weapons & ( 1 << i ) ) == 0 || gameLocal.isMultiplayer ) { + if ( (owner->GetUserInfo()->GetBool( "ui_autoSwitch" ) || !gameLocal.isMultiplayer) && idealWeapon ) { + // HUMANHEAD pdm: added spirit check, so we don't autoswitch to weapons when picking them up in spriitwalk + // HUMANHEAD pdm: also added check for spiritweapon, don't autoswitch to it. + if (!static_cast(owner)->IsSpiritOrDeathwalking() && weaponName.Icmp("weaponobj_bow")) { + assert( !gameLocal.isClient ); + *idealWeapon = i; + } + } + + // Pulse if not picking up the spirit bow + if (weaponName.Icmp("weaponobj_bow") != 0) { + weaponPulse = true; + } + weapons |= ( 1 << i ); + tookWeapon = true; + } + } + } + return tookWeapon; + } + else if ( !idStr::Icmp( statname, "maxhealth" ) ) { + if (owner) { + if (gameLocal.GetLocalPlayer() == owner) { //sp, listen server + if (owner->hud) { + owner->hud->HandleNamedEvent( "maxHealthPulse" ); + } + } + else if (gameLocal.isMultiplayer && !gameLocal.isClient) { //otherwise, broadcast event to owner + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init(msgBuf, sizeof(msgBuf)); + msg.WriteBits((1<ServerSendEvent(idPlayer::EVENT_MENUEVENT, &msg, false, -1, owner->entityNumber); + } + } + if (gameLocal.isMultiplayer) { //rww - different behaviour for mp + maxHealth = atoi(value); + } + else { + maxHealth += atoi(value); + } + } + else if ( !idStr::Icmp( statname, "maxspirit" ) ) { + owner->hud->HandleNamedEvent( "maxSpiritPulse" ); + maxSpirit += atoi(value); + } + else { + // unknown item + return false; + } + + return true; +} + +/* +============== +hhInventory::GiveItem +============== +*/ +bool hhInventory::GiveItem( const idDict& spawnArgs, const idDict* item ) { + idStr itemName; + + if( !item ) { + return false; + } + + idDict* dict = new idDict( *item ); + + if( !FindItem(item) ) { + items.Append( dict ); + return true; + } + + SAFE_DELETE_PTR( dict ); + return false; +} + +/* +=============== +hhInventory::HasAmmo +=============== +*/ +int hhInventory::HasAmmo( ammo_t type, int amount ) { + assert(type >= 0 && type < AMMO_NUMTYPES); + + if ( ( type == idWeapon::GetAmmoNumForName("ammo_none") ) || !amount ) { + // always allow weapons that don't use ammo to fire + return -1; + } + + // check if we have infinite ammo + if ( ammo[ type ] < 0 ) { + return -1; + } + + // return how many shots we can fire + return ammo[ type ] / amount; +} + +/* +=============== +hhInventory::HasAmmo +=============== +*/ +int hhInventory::HasAmmo( const char *weapon_classname ) { + int ammoRequired; + ammo_t ammo_i = AmmoIndexForWeaponClass( weapon_classname, &ammoRequired ); + return HasAmmo( ammo_i, ammoRequired ); +} + +/* +=============== +hhInventory::HasAltAmmo +HUMANHEAD bjk +=============== +*/ +int hhInventory::HasAltAmmo( const char *weapon_classname ) { + int ammoRequired; + ammo_t ammo_i = AltAmmoIndexForWeaponClass( weapon_classname, &ammoRequired ); + return HasAmmo( ammo_i, ammoRequired ); +} + +/* +=============== +hhInventory::UseAmmo +=============== +*/ +bool hhInventory::UseAmmo( ammo_t type, int amount ) { + if ( !HasAmmo( type, amount ) ) { + return false; + } + + // take an ammo away if not infinite + if ( ammo[ type ] >= 0 ) { + ammo[ type ] -= amount; + //rww - don't forget this, it's important! + ammoPredictTime = gameLocal.time; // mp client: we predict this. mark time so we're not confused by snapshots + } + + return true; +} + +float hhInventory::AmmoPercentage(idPlayer *player, ammo_t type) { + float amount = ammo[type]; + const char *ammoName = idWeapon::GetAmmoNameForNum( type ); + float max = MaxAmmoForAmmoClass( player, ammoName ); + max = max(1, max); + return amount / max; +} + +/* +=============== +hhInventory::FindInventoryItem +=============== +*/ +idDict* hhInventory::FindItem( const idDict* dict ) { + if( !dict ) { + return NULL; + } + + return FindItem( dict->GetString("inv_name") ); +} + +/* +=============== +hhInventory::FindInventoryItem +=============== +*/ +idDict* hhInventory::FindItem( const char *name ) { + const char* lname = NULL; + + for( int ix = 0; ix < items.Num(); ++ix ) { + if( !items[ix] ) { + continue; + } + + lname = items[ix]->GetString( "inv_name" ); + if ( lname && *lname ) { + if ( idStr::Icmp( name, lname ) == 0 ) { + return items[ix]; + } + } + } + return NULL; +} + +/* +=============== +hhInventory::EvaluateRequirements +=============== +*/ +void hhInventory::EvaluateRequirements(idPlayer *p) { + if (p) { + requirements.bCanDeathWalk = gameLocal.RequirementMet(p, p->spawnArgs.GetString("requirement_deathwalk"), 0); + requirements.bCanSpiritWalk = gameLocal.RequirementMet(p, p->spawnArgs.GetString("requirement_spiritwalk"), 0); + requirements.bCanSummonTalon = gameLocal.RequirementMet(p, p->spawnArgs.GetString("requirement_talon"), 0); + requirements.bCanUseBowVision = gameLocal.RequirementMet(p, p->spawnArgs.GetString("requirement_bowvision"), 0); + requirements.bCanUseLighter = gameLocal.RequirementMet(p, p->spawnArgs.GetString("requirement_lighter"), 0); + requirements.bCanWallwalk = gameLocal.RequirementMet(p, p->spawnArgs.GetString("requirement_wallwalk"), 0); + requirements.bHunterHand = gameLocal.RequirementMet(p, p->spawnArgs.GetString("requirement_hunterhand"), 0); + + // See if talon should be spawned + if (p->IsType(hhPlayer::Type)) { + static_cast(p)->TrySpawnTalon(); + } + } +} + diff --git a/src/Prey/game_inventory.h b/src/Prey/game_inventory.h new file mode 100644 index 0000000..18a0bcb --- /dev/null +++ b/src/Prey/game_inventory.h @@ -0,0 +1,54 @@ + +#ifndef __PREY_GAME_INVENTORY_H__ +#define __PREY_GAME_INVENTORY_H__ + + +class hhInventory : public idInventory { + +public: + virtual void GetPersistantData( idDict &dict ); + virtual void RestoreInventory( idPlayer *owner, const idDict &dict ); + virtual bool Give( idPlayer *owner, const idDict &spawnArgs, const char *statname, const char *value, int *idealWeapon, bool updateHud ); + virtual void Clear(); + virtual int MaxAmmoForAmmoClass( idPlayer *owner, const char *ammo_classname ) const; + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + bool GiveItem( const idDict& spawnArgs, const idDict* item ); + int HasAmmo( ammo_t type, int amount ); + bool UseAmmo( ammo_t type, int amount ); + int HasAmmo( const char *weapon_classname ); + int HasAltAmmo( const char *weapon_classname ); + float AmmoPercentage(idPlayer *player, ammo_t ammoType); + + idDict* FindItem( const idDict* dict ); + idDict* FindItem( const char *name ); + void EvaluateRequirements( idPlayer *p ); + void AddPickupName( const char *name, const char *icon, bool bIsWeapon ); + + // Requirements are precomputed after any inventory item change, for faster access + struct { + bool bCanWallwalk : 1; + bool bCanSpiritWalk : 1; + bool bCanSummonTalon : 1; + bool bCanUseBowVision : 1; + bool bCanUseLighter : 1; + bool bHunterHand : 1; + bool bCanDeathWalk : 1; + } requirements; + int maxSpirit; + bool bHasDeathwalked; + int storedHealth; + + //bjk: persistent weapons + idStr energyType; + bool altMode[ MAX_WEAPONS ]; + bool weaponRaised[ MAX_WEAPONS ]; + int lastShot[ MAX_WEAPONS ]; //HUMANHEAD bjk PATCH 7-27-06 + int zoomFov; +}; + + +#endif /* __PREY_INVENTORY_H__ */ + diff --git a/src/Prey/game_itemautomatic.cpp b/src/Prey/game_itemautomatic.cpp new file mode 100644 index 0000000..e665b82 --- /dev/null +++ b/src/Prey/game_itemautomatic.cpp @@ -0,0 +1,227 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/*********************************************************************** + +hhItemAutomatic + +***********************************************************************/ + +CLASS_DECLARATION( idEntity, hhItemAutomatic ) +END_CLASS + + +//============================================================================= +// +// hhItemAutomatic::Spawn +// +//============================================================================= +void hhItemAutomatic::Spawn() { + GetPhysics()->SetContents( CONTENTS_TRIGGER ); + BecomeActive( TH_THINK ); +} + +//============================================================================= +// +// hhItemAutomatic::Think +// +//============================================================================= + +void hhItemAutomatic::Think() { + if ( thinkFlags & TH_THINK ) { // Guard against the first level-load think + // We only think when we are in the PVS. We think once and remove ourselves. + hhPlayer *player; + player = static_cast< hhPlayer *>( gameLocal.GetLocalPlayer() ); + if ( player && player->CheckFOV( this->GetOrigin() ) ) { + SpawnItem(); + } + } +} + +//============================================================================= +// +// hhItemAutomatic::SpawnItem +// +//============================================================================= + +void hhItemAutomatic::SpawnItem() { + hhPlayer *player; + idDict Args; + idStr itemName = NULL; + idEntity *spawned = NULL; + + // Remove this object and spawn in a new item in it's place + PostEventMS( &EV_Remove, 0 ); + + Args.SetBool( "spin", 0 ); + Args.SetFloat( "triggersize", 48.0f ); + Args.SetFloat( "respawn", 0.0f ); + Args.SetBool( "enablePickup", false ); + Args.SetFloat( "wander_radius", 12.0f ); // in case the item spawned is a crawler, don't let it wander very far + Args.Set( "target", spawnArgs.GetString( "target" ) ); // Pass the target on to the spawned item + + player = static_cast(gameLocal.GetLocalPlayer()); + + itemName = GetNewItem(); + + if ( itemName.IsEmpty() ) { + return; + } + + spawned = gameLocal.SpawnObject( itemName.c_str(), &Args ); + if ( spawned ) { + spawned->SetOrigin( this->GetOrigin() ); + spawned->SetAxis( this->GetAxis() ); + + if ( spawned->IsType( hhItem::Type ) ) { + hhItem *item = static_cast( spawned ); + item->EnablePickup(); + } + } +} + +//============================================================================= +// +// hhItemAutomatic::GetNewItem +// +//============================================================================= + +idStr hhItemAutomatic::GetNewItem() { + int i; + idStr defaultName; + float skipPercent; + int numAmmo; + + idList weaponNames; + idList weaponIndexes; + idList validWeapon; + idList ammoPercent; + idList ammoTypes; + idList itemNames; + idList skipPercentString; + + float total; + + bool bDontSkip = spawnArgs.GetBool( "bDontSkip", "0" ); + + // This is the heart of the DDA system: + // list all available weapons that can have ammo in the cabinet and how much ammo the player has + + hhPlayer *player = static_cast(gameLocal.GetLocalPlayer()); + + // Build lists of all valid weapon names, ammo types and ammo names + // Note that all these must line up -- so the first weapon corresponds with the first ammo type and the first item name + hhUtils::SplitString( idStr(spawnArgs.GetString( "weaponNames" )), weaponNames ); + hhUtils::SplitString( idStr(spawnArgs.GetString( "ammoTypes" )), ammoTypes ); + hhUtils::SplitString( idStr(spawnArgs.GetString( "itemNames" )), itemNames ); + hhUtils::SplitString( idStr(spawnArgs.GetString( "skipPercents" )), skipPercentString ); + + numAmmo = weaponNames.Num(); + + if ( skipPercentString.Num() != numAmmo ) { + gameLocal.Error( "hhItemAutomatic::GetNewItem: skipPercent.Num() != weaponNames.Num()\n" ); + } + + weaponIndexes.SetNum( numAmmo ); + validWeapon.SetNum( numAmmo ); + ammoPercent.SetNum( numAmmo ); + + for( i = 0; i < weaponNames.Num(); i++ ) { + weaponIndexes[i] = player->GetWeaponNum( weaponNames[i].c_str() ); + } + + // compute percentages of each ammo compared to the max allowed + for( i = 0; i < numAmmo; i++ ) { + validWeapon[i] = false; + if ( player->inventory.weapons & (1 << weaponIndexes[i] ) ) { + int ammoIndex = player->inventory.AmmoIndexForAmmoClass( ammoTypes[i].c_str() ); + ammoPercent[i] = player->inventory.AmmoPercentage( player, ammoIndex ); + + if ( !bDontSkip ) { // Facility to guarantee that ammo won't be skipped + float adjustFactor = FindAmmoNearby( itemNames[i].c_str() ); // If similar ammo is nearby, then reduce the chance of this spawning that ammo + ammoPercent[i] *= adjustFactor; + + skipPercent = idMath::ClampFloat( 0.0f, 1.0f, (float)atof( skipPercentString[i].c_str() ) ); + skipPercent *= (1.0f - (gameLocal.GetDDAValue() * 0.5f + 0.25f)); // CJR TEST: Scale based upon DDA + + if ( ammoPercent[i] > skipPercent ) { // Skip this weapon if the player weapon percentage is high enough + validWeapon[i] = false; + continue; + } + } + + ammoPercent[i] = 1.0f - ammoPercent[i]; // Reverse it to make the math below a bit simpler + + if ( ammoPercent[i] == 0.0f ) { + ammoPercent[i] = 0.1f; // Give full ammo at least a slight chance + } + + validWeapon[i] = true; + } + } + + // re-compute the total of all percentages + total = 0; + for( i = 0; i < numAmmo; i++ ) { + if ( validWeapon[i] ) { + total += ammoPercent[i]; + } + } + + // random number from 0 - total + float random = gameLocal.random.RandomFloat() * total; + + // calculate which ammo that number is associated with and add that item to the cabinet + for( i = 0; i < numAmmo; i++ ) { + if ( validWeapon[i] ) { + if ( random <= ammoPercent[i] ) { // This is the ammo we want + return itemNames[i]; + } + + random -= ammoPercent[i]; // Not the item, so remove this percent and check the next value + } + } + + return NULL; +} + +//============================================================================= +// +// hhItemAutomatic::FindAmmoNearby +// +//============================================================================= + +float hhItemAutomatic::FindAmmoNearby( const char *ammoName ) { + int i; + int e; + hhItem *ent; + idEntity *entityList[ MAX_GENTITIES ]; + int numListedEntities; + idBounds bounds; + idVec3 org; + float adjustFactor = 1.0f; + + float nearbySize = spawnArgs.GetFloat( "nearbySize", "512" ); + + org = GetPhysics()->GetOrigin(); + for ( i = 0 ; i < 3 ; i++ ) { + bounds[0][i] = org[i] - nearbySize; + bounds[1][i] = org[i] + nearbySize; + } + + // Find the closest ammo types that are the same class + numListedEntities = gameLocal.clip.EntitiesTouchingBounds( bounds, -1, entityList, MAX_GENTITIES ); + + for ( e = 0 ; e < numListedEntities ; e++ ) { + ent = static_cast< hhItem * >( entityList[e] ); + + const char *name = ent->spawnArgs.GetString( "classname" ); + if ( !idStr::Icmp( name, ammoName ) ) { + adjustFactor += spawnArgs.GetFloat( "nearbyReduction", "0.25" ); + } + } + + return adjustFactor; +} \ No newline at end of file diff --git a/src/Prey/game_itemautomatic.h b/src/Prey/game_itemautomatic.h new file mode 100644 index 0000000..5dab05b --- /dev/null +++ b/src/Prey/game_itemautomatic.h @@ -0,0 +1,24 @@ +#ifndef __HH_ITEM_AUTOMATIC_H +#define __HH_ITEM_AUTOMATIC_H + +/*********************************************************************** + +hhItemAutomatic + +***********************************************************************/ + +class hhItemAutomatic : public idEntity { + CLASS_PROTOTYPE( hhItemAutomatic ); + +public: + void Spawn(); + virtual void Think(); + + +protected: + void SpawnItem(); + idStr GetNewItem(); + float FindAmmoNearby( const char *ammoName ); +}; + +#endif \ No newline at end of file diff --git a/src/Prey/game_itemcabinet.cpp b/src/Prey/game_itemcabinet.cpp new file mode 100644 index 0000000..da1fba6 --- /dev/null +++ b/src/Prey/game_itemcabinet.cpp @@ -0,0 +1,417 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_GiveItems("", "e"); + +/*********************************************************************** + +hhItemCabinet + +***********************************************************************/ + +const idEventDef EV_EnableItemClip( "" ); + +CLASS_DECLARATION( hhAnimatedEntity, hhItemCabinet ) + EVENT( EV_Activate, hhItemCabinet::Event_Activate ) + EVENT( EV_EnableItemClip, hhItemCabinet::Event_EnableItemClip ) + EVENT( EV_Broadcast_AppendFxToList, hhItemCabinet::Event_AppendFxToIdleList ) + EVENT( EV_PostSpawn, hhItemCabinet::Event_PostSpawn ) //rww +END_CLASS + +/* +================ +hhItemCabinet::hhItemCabinet +================ +*/ +hhItemCabinet::hhItemCabinet() { +} + +/* +================ +hhItemCabinet::Spawn +================ +*/ +void hhItemCabinet::Spawn() { + animDoneTime = 0; + + GetPhysics()->SetContents( CONTENTS_BODY ); + + ResetItemList(); + + InitBoneInfo(); + + if (!gameLocal.isClient) { + PostEventMS(&EV_PostSpawn, 0); + } + + StartSound( "snd_idle", SND_CHANNEL_IDLE ); +} + +/* +================ +hhItemCabinet::Event_PostSpawn +================ +*/ +void hhItemCabinet::Event_PostSpawn(void) { + SpawnIdleFX(); +} + +/* +================ +hhItemCabinet::~hhItemCabinet +================ +*/ +hhItemCabinet::~hhItemCabinet() { + + for( int i = 0; i < CABINET_MAX_ITEMS; i++) { + SAFE_REMOVE( itemList[i] ); + } + + hhUtils::RemoveContents< idEntityPtr >( idleFxList, true ); + + StopSound( SND_CHANNEL_IDLE ); +} + +/* +================ +hhItemCabinet::InitBoneInfo +================ +*/ +void hhItemCabinet::InitBoneInfo() { + boneList[0] = idStr( spawnArgs.GetString("bone_shelfTop") ); + boneList[1] = idStr(spawnArgs.GetString("bone_shelfMiddle") ); + boneList[2] = idStr(spawnArgs.GetString("bone_shelfBottom") ); +} + +void hhItemCabinet::Save(idSaveGame *savefile) const { + savefile->WriteInt( animDoneTime ); + + int num = idleFxList.Num(); + savefile->WriteInt( num ); + for( int i = 0; i < num; i++ ) { + idleFxList[i].Save( savefile ); + } + + for( int i = 0; i < CABINET_MAX_ITEMS; i++ ) { + itemList[i].Save( savefile ); + } + + for( int i = 0; i < CABINET_MAX_ITEMS; i++ ) { + savefile->WriteString( boneList[i] ); + } +} + +void hhItemCabinet::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( animDoneTime ); + + int num; + savefile->ReadInt( num ); + idleFxList.Clear(); + idleFxList.SetNum( num ); + for( int i = 0; i < num; i++ ) { + idleFxList[i].Restore( savefile ); + } + + for( int i = 0; i < CABINET_MAX_ITEMS; i++ ) { + itemList[i].Restore( savefile ); + } + + for( int i = 0; i < CABINET_MAX_ITEMS; i++ ) { + savefile->ReadString( boneList[i] ); + } +} + +/* +================ +hhItemCabinet::PlayAnim +================ +*/ +void hhItemCabinet::PlayAnim( const char* pAnimName, int iBlendTime ) { + PlayAnim( GetAnimator()->GetAnim(pAnimName), iBlendTime ); +} + +/* +================ +hhItemCabinet::PlayAnim +================ +*/ +void hhItemCabinet::PlayAnim( int pAnim, int iBlendTime ) { + int iAnimLength = 0; + + if( pAnim ) { + animator.ClearAllAnims( gameLocal.GetTime(), 0 ); + animator.PlayAnim( ANIMCHANNEL_ALL, pAnim, gameLocal.GetTime(), iBlendTime ); + iAnimLength = GetAnimator()->GetAnim( pAnim )->Length(); + } + + animDoneTime = gameLocal.GetTime() + iAnimLength; +} + +/* +================ +hhItemCabinet::SpawnIdleFX +================ +*/ +void hhItemCabinet::SpawnIdleFX() { + hhUtils::RemoveContents< idEntityPtr >( idleFxList, true ); + + BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_idle"), spawnArgs.GetString("bone_idleRight"), NULL, &EV_Broadcast_AppendFxToList ); + BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_idle"), spawnArgs.GetString("bone_idleLeft"), NULL, &EV_Broadcast_AppendFxToList ); +} + +//============================================================================= +// +// hhItemCabinet::SpawnDefaultItems +// +//============================================================================= + +bool hhItemCabinet::SpawnDefaultItems() { + idList items; + + if ( spawnArgs.GetString( "items", NULL ) ) { + hhUtils::SplitString( idStr(spawnArgs.GetString( "items" )), items ); + + int slot = 0; + for( int i = 0; i < items.Num(); i++ ) { + AddItem( items[i], slot ); + if ( ++slot >= CABINET_MAX_ITEMS ) { + return true; + } + } + + return true; + } + + return false; // No default items +} + +/* +================ +hhItemCabinet::SpawnItems +================ +*/ + +void hhItemCabinet::SpawnItems() { + int i; + idStr defaultName; + + int numAmmo; + + idList weaponNames; + idList weaponIndexes; + idList validWeapon; + idList ammoPercent; + idList ammoTypes; + idList itemNames; + + float total; + + bool bAutomatic = true; + + if ( !bAutomatic ) { // Items were placed by a designer, so don't automatically add any items + return; + } + + // This is the heart of the DDA system: + // list all available weapons that can have ammo in the cabinet and how much ammo the player has + + hhPlayer *player = static_cast(gameLocal.GetLocalPlayer()); + + // Build lists of all valid weapon names, ammo types and ammo names + // Note that all these must line up -- so the first weapon corresponds with the first ammo type and the first item name + hhUtils::SplitString( idStr(spawnArgs.GetString( "weaponNames" )), weaponNames ); + hhUtils::SplitString( idStr(spawnArgs.GetString( "ammoTypes" )), ammoTypes ); + hhUtils::SplitString( idStr(spawnArgs.GetString( "itemNames" )), itemNames ); + + numAmmo = weaponNames.Num(); + + weaponIndexes.SetNum( numAmmo ); + validWeapon.SetNum( numAmmo ); + ammoPercent.SetNum( numAmmo ); + + for( i = 0; i < weaponNames.Num(); i++ ) { + weaponIndexes[i] = player->GetWeaponNum( weaponNames[i].c_str() ); + } + + // compute percentages of each ammo compared to the max allowed + for( i = 0; i < numAmmo; i++ ) { + validWeapon[i] = false; + if ( player->inventory.weapons & (1 << weaponIndexes[i] ) ) { + int ammoIndex = player->inventory.AmmoIndexForAmmoClass( ammoTypes[i].c_str() ); + ammoPercent[i] = 1.0f - player->inventory.AmmoPercentage( player, ammoIndex ); + if ( ammoPercent[i] == 0.0f ) { // Give full ammo weapons a slight chance + ammoPercent[i] = 0.01f; + } + validWeapon[i] = true; + } + } + + // for each slot in the cabinet: + for( int slot = 0; slot < numAmmo; slot++ ) { + if ( gameLocal.random.RandomFloat() < spawnArgs.GetFloat( "emptyChance", "0.15" ) ) { // Chance the slot is empty + continue; + } + + // re-compute the total of all percentages + total = 0; + for( i = 0; i < numAmmo; i++ ) { + if ( validWeapon[i] ) { + total += ammoPercent[i]; + } + } + + // random number from 0 - total + float random = gameLocal.random.RandomFloat() * total; + + // calculate which ammo that number is associated with and add that item to the cabinet + for( i = 0; i < numAmmo; i++ ) { + if ( validWeapon[i] ) { + if ( random <= ammoPercent[i] ) { // This is the ammo we want + AddItem( itemNames[i], slot ); // Add the item + ammoPercent[i] *= spawnArgs.GetFloat( "repeatReduce", "0.5" ); // reduce this item's chance of being chosen for the next slot + break; // No need to check further, go to the next slot + } + + random -= ammoPercent[i]; // Not the item, so remove this percent and check the next value + } + } + } +} + +/* +================ +hhItemCabinet::ResetItemList +================ +*/ +void hhItemCabinet::ResetItemList() { + for( int i = 0; i < CABINET_MAX_ITEMS; i++ ) { + SAFE_REMOVE( itemList[i] ); + } +} + +/* +================ +hhItemCabinet::AddItem +================ +*/ +void hhItemCabinet::AddItem( idStr itemName, int slot ) { + idDict Args; + hhItem* item = NULL; + + if ( slot < 0 || slot >= CABINET_MAX_ITEMS ) { + return; + } + + Args.SetBool( "spin", 0 ); + Args.SetFloat( "triggersize", 48.0f ); + Args.SetFloat( "respawn", 0.0f ); + Args.SetBool( "enablePickup", false ); + + item = static_cast( gameLocal.SpawnObject( itemName.c_str(), &Args ) ); + + BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_shelf"), boneList[slot] ); + + // Check if the item should be rotated + bool bRotated = false; + for ( int i = 0; i < spawnArgs.GetInt( "numRotated" ); i++ ) { + if ( !idStr::Icmp( spawnArgs.GetString( va("rotate%d", i) ), itemName.c_str() ) ) { + bRotated = true; + } + } + + if( item ) { + const char *boneName = boneList[ slot ]; + + idVec3 boneOffset; + idMat3 boneAxis; + this->GetJointWorldTransform( boneName, boneOffset, boneAxis ); + + if ( bRotated ) { + item->SetOrigin( boneOffset + GetAxis()[1] * -14 + GetAxis()[2] * 8 ); + item->SetAxis( idMat3( idVec3( 1, 0, 0 ), idVec3( 0, 0, -1), idVec3( 0, 1, 0 ) ) * GetAxis() ); + } else { + item->MoveToJoint( this, boneName ); + item->SetAxis( GetAxis() ); + } + item->BindToJoint( this, boneName, false ); + + itemList[ slot ] = item; + return; + } +} + +/* +=============== +hhItemCabinet::HandleSingleGuiCommand +=============== +*/ +bool hhItemCabinet::HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ) { + + idToken token; + + if( !src->ReadToken(&token) ) { + return false; + } + + if( token == ";" ) { + return false; + } + + if( token.Icmp("openCabinet") == 0 ) { + Event_Activate( NULL ); + return true; + } + + src->UnreadToken( &token ); + return false; +} + +/* +================ +hhItemCabinet::Event_AppendFxToIdleList +================ +*/ +void hhItemCabinet::Event_AppendFxToIdleList( hhEntityFx* fx ) { + idleFxList.Append( fx ); +} + +/* +================ +hhItemCabinet::Event_Activate +================ +*/ +void hhItemCabinet::Event_Activate( idEntity* pActivator ) { + if( gameLocal.GetTime() < animDoneTime ) { + return; + } + + if ( !SpawnDefaultItems() ) { + SpawnItems(); + } + + PlayAnim( "open", 0 ); + StartSound( "snd_open", SND_CHANNEL_ANY ); + PostEventMS( &EV_EnableItemClip, animDoneTime - gameLocal.GetTime() ); + + ActivateTargets(pActivator); + + StartSound( "snd_idle_open", SND_CHANNEL_IDLE ); +} + +/* +================ +hhItemCabinet::Event_EnableItemClip +================ +*/ +void hhItemCabinet::Event_EnableItemClip() { + for( int i = 0; i < CABINET_MAX_ITEMS; i++ ) { + if( !itemList[i].IsValid() ) { + continue; + } + + itemList[i]->EnablePickup(); + itemList[i] = NULL; + } +} diff --git a/src/Prey/game_itemcabinet.h b/src/Prey/game_itemcabinet.h new file mode 100644 index 0000000..1591eb2 --- /dev/null +++ b/src/Prey/game_itemcabinet.h @@ -0,0 +1,51 @@ +#ifndef __HH_ITEM_CABINET_H +#define __HH_ITEM_CABINET_H + +/*********************************************************************** + +hhItemCabinet + +***********************************************************************/ +#define CABINET_MAX_ITEMS 3 + +class hhItemCabinet: public hhAnimatedEntity { + CLASS_PROTOTYPE( hhItemCabinet ); + +public: + hhItemCabinet(); + virtual ~hhItemCabinet(); + + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +protected: + void InitBoneInfo(); + + void PlayAnim( const char* pAnimName, int iBlendTime ); + void PlayAnim( int pAnim, int iBlendTime ); + + void ResetItemList(); + void AddItem( idStr itemName, int slot ); + bool SpawnDefaultItems(); + void SpawnItems(); + void SpawnIdleFX(); + + bool HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ); + +protected: + void Event_AppendFxToIdleList( hhEntityFx* fx ); + void Event_Activate( idEntity* pActivator ); + void Event_EnableItemClip(); + virtual void Event_PostSpawn(void); + +protected: + int animDoneTime; + + idList< idEntityPtr > idleFxList; + + idEntityPtr itemList[ CABINET_MAX_ITEMS ]; + idStr boneList[ CABINET_MAX_ITEMS ]; +}; + +#endif \ No newline at end of file diff --git a/src/Prey/game_jukebox.cpp b/src/Prey/game_jukebox.cpp new file mode 100644 index 0000000..22a5e36 --- /dev/null +++ b/src/Prey/game_jukebox.cpp @@ -0,0 +1,265 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +CLASS_DECLARATION(hhSound, hhJukeBoxSpeaker) +END_CLASS + + +const idEventDef EV_SetNumTracks("setNumTracks", "d"); +const idEventDef EV_SetTrack("setTrack", "d"); +const idEventDef EV_PlaySelected("playSelected", NULL); +const idEventDef EV_TrackOver("", NULL); +const idEventDef EV_SetJukeboxVolume("setvolume", "f"); + +CLASS_DECLARATION(hhConsole, hhJukeBox) + EVENT( EV_SetNumTracks, hhJukeBox::Event_SetNumTracks) + EVENT( EV_SetTrack, hhJukeBox::Event_SetTrack) + EVENT( EV_PlaySelected, hhJukeBox::Event_PlaySelected) + EVENT( EV_TrackOver, hhJukeBox::Event_TrackOver) + EVENT( EV_SetJukeboxVolume, hhJukeBox::Event_SetVolume) +END_CLASS + + +void hhJukeBox::Spawn() { + track = 1; + currentHistorySample = 0; + numTracks = spawnArgs.GetInt("numtracks", "1"); + volume = spawnArgs.GetFloat("volume"); + UpdateView(); +} + +void hhJukeBox::Save(idSaveGame *savefile) const { + int i; + + savefile->WriteFloat( volume ); + savefile->WriteInt( track ); + savefile->WriteInt( numTracks ); + savefile->WriteInt( currentHistorySample ); + + savefile->WriteInt( speakers.Num() ); // Saving of idList + for( i = 0; i < speakers.Num(); i++ ) { + savefile->WriteObject(speakers[i]); + } +} + +void hhJukeBox::Restore( idRestoreGame *savefile ) { + int i, num; + + savefile->ReadFloat( volume ); + savefile->ReadInt( track ); + savefile->ReadInt( numTracks ); + savefile->ReadInt( currentHistorySample ); + + speakers.Clear(); + savefile->ReadInt( num ); + speakers.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadObject( reinterpret_cast(speakers[i]) ); + } + + UpdateVolume(); +} + +void hhJukeBox::ConsoleActivated() { + BecomeActive(TH_MISC3); +} + +void hhJukeBox::UpdateView() { + idUserInterface *gui = renderEntity.gui[0]; + + if (gui) { + gui->SetStateInt("track", track); + gui->SetStateFloat("volume", volume); + } +} + +void hhJukeBox::ClearSpectrum() { + idUserInterface *gui = renderEntity.gui[0]; + if (gui) { + gui->SetStateFloat("amplitude", 0.0f); + + for (int ix=0; ix<10; ix++) { + gui->SetStateFloat(va("amplitude%d", ix), 0.0f); + } + gui->StateChanged(gameLocal.time); + } +} + +void hhJukeBox::SetTrack(int newTrack) { + track = idMath::ClampInt(1, numTracks, newTrack); +} + +void hhJukeBox::PlayCurrentTrack() { + const char *shaderName = spawnArgs.GetString(va("snd_song%d", track), NULL); + const idSoundShader *shader = declManager->FindSound(shaderName); + int time = 0; + + // Clear up any previous state, even if using another mixer at the time + StopCurrentTrack(); + + // In OpenAL, samples that are out of range pause instead of mute so the targetted speakers can get out of sync. + if (shader && targets.Num() && !cvarSystem->GetCVarBool("s_useOpenAL")) { + for (int ix=0; ixStartSoundShader(shader, SND_CHANNEL_VOICE, 0, true, &time); + } + } + } + else { + StartSound(va("snd_song%d", track), SND_CHANNEL_VOICE, 0, true, &time); + } + UpdateVolume(); + CancelEvents(&EV_TrackOver); + PostEventMS(&EV_TrackOver, time + 500); +} + +void hhJukeBox::StopCurrentTrack() { + CancelEvents(&EV_TrackOver); + ClearSpectrum(); + + // Stop speakers and jukebox so no mixer switches can screw us up + StopSound(SND_CHANNEL_VOICE, true); + for (int ix=0; ixStopSound(SND_CHANNEL_VOICE, true); + } + } +} + +bool hhJukeBox::HandleSingleGuiCommand(idEntity *entityGui, idLexer *src) { + + idToken token; + + if (!src->ReadToken(&token)) { + return false; + } + + if (token == ";") { + return false; + } + + if (token.Icmp("prevtrack") == 0) { + SetTrack(track-1); + UpdateView(); + } + else if (token.Icmp("nexttrack") == 0) { + SetTrack(track+1); + UpdateView(); + } + else if (token.Icmp("selecttrack") == 0) { + BecomeActive(TH_MISC3); + PlayCurrentTrack(); + UpdateView(); + } + else if (token.Icmp("turnoff") == 0) { + StopCurrentTrack(); + BecomeInactive(TH_MISC3); + UpdateView(); + } + else if (token.Icmp("turnon") == 0) { + BecomeActive(TH_MISC3); + UpdateView(); + } + else if (token.Icmp("volumeup") == 0) { + volume = idMath::ClampFloat(0.0f, 1.0f, volume + 0.02f); + UpdateVolume(); + UpdateView(); + } + else if (token.Icmp("volumedown") == 0) { + volume = idMath::ClampFloat(0.0f, 1.0f, volume - 0.02f); + UpdateVolume(); + UpdateView(); + } + else { + src->UnreadToken(&token); + return false; + } + + return true; +} + +void hhJukeBox::UpdateEntityVolume(idEntity *ent) { + ent->HH_SetSoundVolume(volume, SND_CHANNEL_VOICE); +} + +void hhJukeBox::UpdateVolume() { + if (targets.Num() && !cvarSystem->GetCVarBool("s_useOpenAL")) { + for (int ix=0; ixCurrentlyPlaying()) { + amplitude = refSound.referenceSound->CurrentAmplitude(); + } + else if (targets.Num() && targets[0].IsValid() && targets[0].GetEntity()->IsType(hhSound::Type) ) { + amplitude = static_cast(targets[0].GetEntity())->GetCurrentAmplitude(SND_CHANNEL_VOICE); + } + + amplitude = idMath::ClampFloat(0.0f, 1.0f, amplitude); + idUserInterface *gui = renderEntity.gui[0]; // Interface area + if (gui) { + gui->SetStateFloat("amplitude", amplitude); + + currentHistorySample = (currentHistorySample+1)%10; + gui->SetStateFloat(va("amplitude%d", currentHistorySample), amplitude); + gui->StateChanged(gameLocal.time); + } + gui = renderEntity.gui[1]; // Outer jukebox + if (gui) { + gui->SetStateFloat("amplitude", amplitude); + } + } +} + +void hhJukeBox::Event_SetNumTracks(int newNumTracks) { + numTracks = newNumTracks; +} + +void hhJukeBox::Event_SetTrack(int newTrack) { + SetTrack(newTrack); + UpdateView(); +} + +void hhJukeBox::Event_PlaySelected() { + PlayCurrentTrack(); + BecomeActive(TH_MISC3); + UpdateView(); +} + +void hhJukeBox::Event_TrackOver() { + ClearSpectrum(); + //loop to beginning + if ( track+1 > numTracks ) { + SetTrack(1); + } else { + SetTrack(track+1); + } + PlayCurrentTrack(); + UpdateView(); +} + +void hhJukeBox::Event_SetVolume(float vol) { + volume = idMath::ClampFloat(0.0f, 1.0f, vol); + UpdateVolume(); + UpdateView(); +} + + diff --git a/src/Prey/game_jukebox.h b/src/Prey/game_jukebox.h new file mode 100644 index 0000000..1eb210c --- /dev/null +++ b/src/Prey/game_jukebox.h @@ -0,0 +1,47 @@ + +#ifndef __GAME_JUKEBOX_H__ +#define __GAME_JUKEBOX_H__ + + +class hhJukeBox : public hhConsole { +public: + CLASS_PROTOTYPE( hhJukeBox ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool HandleSingleGuiCommand(idEntity *entityGui, idLexer *src); + virtual void ConsoleActivated(); + virtual void Think(); + + void ClearSpectrum(); + void SetTrack(int track); + void PlayCurrentTrack(); + void StopCurrentTrack(); + void UpdateView(); + void UpdateVolume(); + void UpdateEntityVolume(idEntity *ent); + + void Event_SetNumTracks(int newNumTracks); + void Event_SetTrack(int newTrack); + void Event_PlaySelected(); + void Event_TrackOver(); + void Event_SetVolume(float vol); + +protected: + float volume; + int track; + int numTracks; + int currentHistorySample; + idList speakers; +}; + + +class hhJukeBoxSpeaker : public hhSound { +public: + CLASS_PROTOTYPE( hhJukeBoxSpeaker ); +}; + + +#endif diff --git a/src/Prey/game_jumpzone.cpp b/src/Prey/game_jumpzone.cpp new file mode 100644 index 0000000..3897b0a --- /dev/null +++ b/src/Prey/game_jumpzone.cpp @@ -0,0 +1,129 @@ +// Game_JumpZone.cpp +// + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +const idEventDef EV_ResetSlopeCheck("", "d"); + +CLASS_DECLARATION(hhTrigger, hhJumpZone) + EVENT( EV_Touch, hhJumpZone::Event_Touch ) + EVENT( EV_Enable, hhJumpZone::Event_Enable ) + EVENT( EV_Disable, hhJumpZone::Event_Disable ) + EVENT( EV_ResetSlopeCheck, hhJumpZone::Event_ResetSlopeCheck ) +END_CLASS + +void hhJumpZone::Spawn(void) { + + velocity = spawnArgs.GetVector("velocity"); + pitchDegrees = spawnArgs.GetFloat("jumpPitch"); + GetPhysics()->SetContents( CONTENTS_TRIGGER ); +} + +void hhJumpZone::Save(idSaveGame *savefile) const { + savefile->WriteVec3( velocity ); + savefile->WriteFloat( pitchDegrees ); +} + +void hhJumpZone::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( velocity ); + savefile->ReadFloat( pitchDegrees ); +} + + +// Given a pitch angle, calculate a speed to get us to destination +static float JumpBallistics( const idVec3 &start, const idVec3 &end, float pitch, float gravity ) { +/* + speed = sqrt( + -0.5f * gravity * ( x / cos(pitch) )^2 + ----------------------------------------- + y - x * tan(pitch) + ); +*/ + float pitchRadians = DEG2RAD(pitch); + float speed = 0.0f; + idVec3 toTarget = end - start; + float dist = toTarget.Length(); + float a = dist / idMath::Cos(pitchRadians); + float num = -0.5f * gravity * a*a; + float den = toTarget.z - dist * idMath::Tan(pitchRadians); + if (den != 0.0f) { + speed = idMath::Sqrt( num / den ); + } + return speed; +} + +idVec3 hhJumpZone::CalculateJumpVelocity() { + idVec3 destination = GetOrigin() + idVec3(0,0,200); + if (targets.Num() == 0 || !targets[0].IsValid()) { + // Use explicit velocity + return velocity; + } + + destination = targets[0]->GetOrigin(); + + idVec3 toTarget = destination - GetOrigin(); + + // Given the angle, calculate a speed to get us to destination + float speed = JumpBallistics(GetOrigin(), destination, pitchDegrees, DEFAULT_GRAVITY); + + idAngles ang; + ang.Set(-pitchDegrees, toTarget.ToYaw(), 0.0f); + return ang.ToForward() * speed; +} + +/* +================ +hhJumpZone::Event_Enable +================ +*/ +void hhJumpZone::Event_Enable( void ) { + GetPhysics()->SetContents( CONTENTS_TRIGGER ); +} + +/* +================ +hhJumpZone::Event_Disable +================ +*/ +void hhJumpZone::Event_Disable( void ) { + GetPhysics()->SetContents( 0 ); +} + + +/* +================ +hhJumpZone::Event_Touch +================ +*/ +void hhJumpZone::Event_Touch( idEntity *other, trace_t *trace ) { + if (other) { + // Enable slope checks on player in case it was turned off by gravity zone. Need it on to + // recognize getting thrown off the ground + if (other->IsType( hhPlayer::Type ) && other->GetPhysics()->IsType(hhPhysics_Player::Type) ) { + static_cast(other->GetPhysics())->SetSlopeCheck(true); + // Post an event to turn it back off? + other->PostEventMS(&EV_ResetSlopeCheck, 200, other->entityNumber); + } + other->GetPhysics()->SetLinearVelocity( CalculateJumpVelocity() ); + } +} + +void hhJumpZone::Event_ResetSlopeCheck(int entNum) { + idEntity *entityList[100]; + + // If the player is still encroaching on an inward gravity zone, reset slope check (off) + idEntity *player = gameLocal.entities[entNum]; + if (player && player->IsType(hhPlayer::Type) && player->GetPhysics()->IsType(hhPhysics_Player::Type)) { + int num = gameLocal.clip.EntitiesTouchingBounds(player->GetPhysics()->GetAbsBounds(), CONTENTS_TRIGGER, entityList, 100); + for (int ix=0; ixIsType(hhGravityZoneInward::Type)) { + static_cast(player->GetPhysics())->SetSlopeCheck(false); + break; + } + } + } +} \ No newline at end of file diff --git a/src/Prey/game_jumpzone.h b/src/Prey/game_jumpzone.h new file mode 100644 index 0000000..d593dc5 --- /dev/null +++ b/src/Prey/game_jumpzone.h @@ -0,0 +1,28 @@ +// Game_JumpZone.h +// + +#ifndef __GAME_JUMPZONE_H__ +#define __GAME_JUMPZONE_H__ + +class hhJumpZone : public hhTrigger { +public: + CLASS_PROTOTYPE( hhJumpZone ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +protected: + idVec3 CalculateJumpVelocity(); + void Event_Touch( idEntity *other, trace_t *trace ); + void Event_Enable( void ); + void Event_Disable( void ); + void Event_ResetSlopeCheck(int entNum); + +public: + idVec3 velocity; + float pitchDegrees; +}; + + +#endif /* __GAME_JUMPZONE_H__ */ diff --git a/src/Prey/game_light.cpp b/src/Prey/game_light.cpp new file mode 100644 index 0000000..4c6c68b --- /dev/null +++ b/src/Prey/game_light.cpp @@ -0,0 +1,103 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/*********************************************************************** + + hhLight + +***********************************************************************/ + +//HUMANHEAD: aob +const idEventDef EV_Light_StartAltMode( "startAltMode" ); +//HUMANHEAD END + +CLASS_DECLARATION( idLight, hhLight ) + EVENT( EV_PostSpawn, hhLight::Event_SetTargetHandles ) + EVENT( EV_ResetTargetHandles, hhLight::Event_SetTargetHandles ) + EVENT( EV_Light_StartAltMode, hhLight::Event_StartAltMode ) +END_CLASS + +/* +================ +hhLight::SetLightCenter + +HUMANHEAD cjr +================ +*/ +void hhLight::SetLightCenter( idVec3 center ) { + renderLight.lightCenter = center; + PresentLightDefChange(); +} + +/* +================ +hhLight::StartAltSound +================ +*/ +void hhLight::StartAltSound() { + if ( refSound.shader ) { + StopSound( SND_CHANNEL_ANY ); + const idSoundShader *alternate = refSound.shader->GetAltSound(); + if ( alternate ) { + StartSoundShader( alternate, SND_CHANNEL_ANY ); + } + } +} + +/* +================ +hhLight::Event_SetTargetHandles + + set the same sound def handle on all targeted entities + +HUMANHEAD: aob +================ +*/ +void hhLight::Event_SetTargetHandles( void ) { + int i; + idEntity *targetEnt = NULL; + + if ( !refSound.referenceSound ) { + return; + } + + for( i = 0; i < targets.Num(); i++ ) { + targetEnt = targets[ i ].GetEntity(); + if ( targetEnt ) { + if( targetEnt->IsType(idLight::Type) ) { + static_cast(targetEnt)->SetLightParent( this ); + } + + targetEnt->FreeSoundEmitter( true ); + + // manually set the refSound to this light's refSound + targetEnt->GetRenderEntity()->referenceSound = renderEntity.referenceSound; + + // update the renderEntity to the renderer + targetEnt->UpdateVisuals(); + } + } +} + +/* +================ +hhLight::Event_StartAltMode +================ +*/ +void hhLight::Event_StartAltMode() { + //Copied fron idLight::BecomeBroken + + // 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; + + StartAltSound(); + + UpdateVisuals(); +} \ No newline at end of file diff --git a/src/Prey/game_light.h b/src/Prey/game_light.h new file mode 100644 index 0000000..fde0488 --- /dev/null +++ b/src/Prey/game_light.h @@ -0,0 +1,25 @@ +#ifndef __HH_GAME_LIGHT_H +#define __HH_GAME_LIGHT_H + +/* +=============================================================================== + +hhLight + +=============================================================================== +*/ + +class hhLight : public idLight { + CLASS_PROTOTYPE( hhLight ); + +public: + void SetLightCenter( idVec3 center ); + + void StartAltSound(); + +protected: + void Event_SetTargetHandles( void ); + void Event_StartAltMode(); +}; + +#endif \ No newline at end of file diff --git a/src/Prey/game_lightfixture.cpp b/src/Prey/game_lightfixture.cpp new file mode 100644 index 0000000..3449e86 --- /dev/null +++ b/src/Prey/game_lightfixture.cpp @@ -0,0 +1,210 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION( hhAFEntity, hhLightFixture ) + EVENT( EV_PostSpawn, hhLightFixture::Event_PostSpawn ) + EVENT( EV_Hide, hhLightFixture::Event_Hide ) + EVENT( EV_Show, hhLightFixture::Event_Show ) +END_CLASS + +/* +=============== +hhLightFixture::Spawn +=============== +*/ +void hhLightFixture::Spawn() { + collisionBone = INVALID_JOINT; + boundLight = NULL; + + PostEventMS( &EV_PostSpawn, 10 ); +} + +void hhLightFixture::Save(idSaveGame *savefile) const { + savefile->WriteInt( collisionBone ); + boundLight.Save(savefile); +} + +void hhLightFixture::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( (int &)collisionBone ); + boundLight.Restore(savefile); +} + +/* +=============== +hhLightFixture::~hhLightFixture +=============== +*/ +hhLightFixture::~hhLightFixture() { + RemoveLight(); +} + +/* +=============== +hhLightFixture::Damage +=============== +*/ +void hhLightFixture::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + if( collisionBone != INVALID_JOINT && (location == INVALID_JOINT || collisionBone == location) ) { + hhAFEntity::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + } +} + +/* +=============== +hhLightFixture::Killed +=============== +*/ +void hhLightFixture::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + hhAFEntity::Killed( inflictor, attacker, damage, dir, location ); + + if( StillBound(boundLight.GetEntity()) ) { + //boundLight->SetShader( spawnArgs.GetString("mtr_lightDestroyed") ); + boundLight->BecomeBroken( attacker ); + } + + const char* skinName = spawnArgs.GetString( "skin_destroyed" ); + if( skinName && skinName[0] ) { + SetSkinByName( skinName ); + } + + // offset the start time of the shader to sync it to the game time + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + + // set the state parm + renderEntity.shaderParms[ SHADERPARM_MODE ] = 1; + + UpdateVisuals(); +} + +/* +=============== +hhLightFixture::GetBoundLight +=============== +*/ +void hhLightFixture::GetBoundLight() { + idVec3 color; + + boundLight = SearchForBoundLight(); + if( boundLight.IsValid() ) { + boundLight->fl.takedamage = false; + + collisionBone = GetAnimator()->GetJointHandle( boundLight->spawnArgs.GetString("bindToJoint") ); + + SetColor( boundLight->spawnArgs.GetVector("_color", "1 1 1") ); + } +} + + +/* +=============== +hhLightFixture::SearchForBoundLight +=============== +*/ +idLight* hhLightFixture::SearchForBoundLight() { + for( idEntity* entity = GetTeamChain(); entity; entity = entity->GetTeamChain() ) { + if( entity && entity->IsType(idLight::Type) ) { + return static_cast( entity ); + } + } + + return NULL; +} + +/* +=============== +hhLightFixture::StillBound +=============== +*/ +bool hhLightFixture::StillBound( const idLight* light ) { + return (light) ? light->IsBoundTo(this) : false; +} + +/* +=============== +hhLightFixture::RemoveLight +=============== +*/ +void hhLightFixture::RemoveLight() { + SAFE_REMOVE( boundLight ); +} + +/* +================ +hhLightFixture::Present +================ +*/ +void hhLightFixture::Present( void ) { + // don't present to the renderer if the entity hasn't changed + if ( !( thinkFlags & TH_UPDATEVISUALS ) ) { + return; + } + + // add the model + hhAFEntity::Present(); + + // reference the sound for shader synced effects + if ( boundLight.IsValid() && StillBound(boundLight.GetEntity()) ) { + renderEntity.referenceSound = boundLight->GetSoundEmitter(); + } + else { + renderEntity.referenceSound = refSound.referenceSound; + } + + PresentModelDefChange(); +} + +/* +================ +hhLightFixture::PresentModelDefChange +================ +*/ +void hhLightFixture::PresentModelDefChange( void ) { + + if ( !renderEntity.hModel || IsHidden() ) { + return; + } + + // add to refresh list + if ( modelDefHandle == -1 ) { + modelDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } else { + gameRenderWorld->UpdateEntityDef( modelDefHandle, &renderEntity ); + } +} + +/* +=============== +hhLightFixture::Event_PostSpawn +=============== +*/ +void hhLightFixture::Event_PostSpawn() { + GetBoundLight(); +} + +/* +=============== +hhLightFixture::Event_Hide +=============== +*/ +void hhLightFixture::Event_Hide() { + idEntity::Event_Hide(); + + if( boundLight.IsValid() ) { + boundLight->Off(); + } +} + +/* +=============== +hhLightFixture::Event_Show +=============== +*/ +void hhLightFixture::Event_Show() { + idEntity::Event_Show(); + + if( boundLight.IsValid() ) { + boundLight->On(); + } +} \ No newline at end of file diff --git a/src/Prey/game_lightfixture.h b/src/Prey/game_lightfixture.h new file mode 100644 index 0000000..5db3229 --- /dev/null +++ b/src/Prey/game_lightfixture.h @@ -0,0 +1,37 @@ +#ifndef __HH_LIGHT_FIXTURE_H +#define __HH_LIGHT_FIXTURE_H + +class hhLightFixture : public hhAFEntity { + CLASS_PROTOTYPE( hhLightFixture ); + +public: + ~hhLightFixture(); + + void Spawn(); + 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 Present( void ); + +protected: + void GetBoundLight(); + idLight* SearchForBoundLight(); + bool StillBound( const idLight* light ); + void RemoveLight(); + + void PresentModelDefChange( void ); + +protected: + void Event_PostSpawn(); + void Event_Hide(); + void Event_Show(); + +protected: + jointHandle_t collisionBone; + + idEntityPtr boundLight; +}; + +#endif \ No newline at end of file diff --git a/src/Prey/game_mine.cpp b/src/Prey/game_mine.cpp new file mode 100644 index 0000000..93d8874 --- /dev/null +++ b/src/Prey/game_mine.cpp @@ -0,0 +1,432 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +//========================================================================== +// +// hhMine +// +//========================================================================== + +const idEventDef EV_ExplodeDamage( "explodeDamage", "e" ); +const idEventDef EV_MineHover( "" ); + +CLASS_DECLARATION(hhMoveable, hhMine) + EVENT( EV_Activate, hhMine::Event_Trigger) + EVENT( EV_ExplodeDamage, hhMine::Event_ExplodeDamage) + EVENT( EV_Remove, hhMine::Event_Remove) + EVENT( EV_ExplodedBy, hhMine::Event_ExplodedBy ) + EVENT( EV_MineHover, hhMine::Event_MineHover ) +END_CLASS + +void hhMine::Spawn(void) { + spawner = NULL; + fl.takedamage = spawnArgs.GetBool("takedamage", "1"); + + idVec3 worldGravityDir( gameLocal.GetGravity() ); + float gravityMagnitude = spawnArgs.GetFloat( "gravity", va("%.2f", worldGravityDir.Normalize()) ); + GetPhysics()->SetGravity( gravityMagnitude * worldGravityDir ); + + bScaleIn = spawnArgs.GetBool("scalein"); + if (bScaleIn) { + fadeAlpha.Init(gameLocal.time, 2000, 0.01f, 1.0f); + BecomeActive(TH_MISC1); + } + + if (spawnArgs.FindKey("snd_spawn")) { + StartSound( "snd_spawn", SND_CHANNEL_ANY ); + } + + if (spawnArgs.FindKey("snd_idle")) { + StartSound( "snd_idle", SND_CHANNEL_IDLE ); + } + + bDetonateOnCollision = spawnArgs.GetBool("DetonateOnCollision"); + bDamageOnCollision = spawnArgs.GetBool("DamageOnCollision"); + bExploded = false; + + if (!spawnArgs.GetBool("nodrop") && GetPhysics()->GetGravity() == vec3_origin) { + gameLocal.Warning("zero grav object without nodrop will not move: %s", name.c_str()); + } + + if (bDetonateOnCollision) { + SpawnTrigger(); + } +} + +void hhMine::Save(idSaveGame *savefile) const { + savefile->WriteObject( spawner ); + savefile->WriteBool( bDetonateOnCollision ); + savefile->WriteBool( bDamageOnCollision ); + + savefile->WriteFloat( fadeAlpha.GetStartTime() ); // idInterpolate + savefile->WriteFloat( fadeAlpha.GetDuration() ); + savefile->WriteFloat( fadeAlpha.GetStartValue() ); + savefile->WriteFloat( fadeAlpha.GetEndValue() ); + + savefile->WriteBool( bScaleIn ); + savefile->WriteBool( bExploded ); +} + +void hhMine::Restore( idRestoreGame *savefile ) { + float set; + + savefile->ReadObject( reinterpret_cast(spawner) ); + savefile->ReadBool( bDetonateOnCollision ); + savefile->ReadBool( bDamageOnCollision ); + + savefile->ReadFloat( set ); // idInterpolate + fadeAlpha.SetStartTime( set ); + savefile->ReadFloat( set ); + fadeAlpha.SetDuration( set ); + savefile->ReadFloat( set ); + fadeAlpha.SetStartValue(set); + savefile->ReadFloat( set ); + fadeAlpha.SetEndValue( set ); + + savefile->ReadBool( bScaleIn ); + savefile->ReadBool( bExploded ); +} + +void hhMine::SpawnTrigger() { + idEntity *trigger; + idDict Args; + + Args.Set( "target", name.c_str() ); + Args.Set( "mins", spawnArgs.GetString("triggerMins") ); + Args.Set( "maxs", spawnArgs.GetString("triggerMaxs") ); + Args.Set( "bind", name.c_str() ); + Args.SetVector( "origin", GetOrigin() ); + Args.SetMatrix( "rotation", GetAxis() ); + trigger = gameLocal.SpawnObject( spawnArgs.GetString("def_trigger"), &Args ); +} + +void hhMine::Think() { + if (thinkFlags & TH_MISC1) { + if (bScaleIn) { + if (fadeAlpha.IsDone(gameLocal.time)) { + SetDeformation(DEFORMTYPE_SCALE, 0.0f); // Turn scaling off + bScaleIn = false; + BecomeInactive(TH_MISC1); + } + else { + SetDeformation(DEFORMTYPE_SCALE, fadeAlpha.GetCurrentValue(gameLocal.time)); + } + } + } + //TODO: Could make these come to rest when gravity is zero and not moving + RunPhysics(); + Present(); +} + +void hhMine::Launch(idVec3 &velocity, idVec3 &avelocity) { + GetPhysics()->SetLinearVelocity(velocity); + GetPhysics()->SetAngularVelocity(avelocity); + BecomeActive(TH_MISC1); +} + +void hhMine::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + // skip idMoveable::Damage which handles damage differently + idEntity::Damage(inflictor, attacker, dir, damageDefName, damageScale, location); +} + +void hhMine::ApplyImpulse(idEntity * ent, int id, const idVec3 &point, const idVec3 &impulse) { + if (impulse == vec3_origin && ent->IsType(idActor::Type)) { // Just pushed + // Apply player's velocity to the pod so it moves at same rate as player and keeps going + idVec3 newimpulse = ent->GetPhysics()->GetLinearVelocity() * GetPhysics()->GetMass(); + hhMoveable::ApplyImpulse(ent, id, point, newimpulse); +// GetPhysics()->SetLinearVelocity(ent->GetPhysics()->GetLinearVelocity()*4); + } + if (bDetonateOnCollision) { + // Need to post an event because already in physics code now, can't nest a projectile spawn from within physics code + // currently, because rigid body physics ::Evaluate is not reentrant friendly (has a static timer) + PostEventMS(&EV_ExplodedBy, 0, this); + } + hhMoveable::ApplyImpulse(ent, id, point, impulse); +} + +bool hhMine::AllowCollision( const trace_t &collision ) { + idEntity *ent = gameLocal.entities[collision.c.entityNum]; + if ( ent && ent->IsType(hhShuttleForceField::Type) ) { + return false; // Allow asteroids to go through shuttle forcefields + } + return true; +} + +bool hhMine::Collide( const trace_t &collision, const idVec3 &velocity ) { + const char *decal; + idEntity *ent = gameLocal.entities[collision.c.entityNum]; + + // project decal + decal = spawnArgs.RandomPrefix( "mtr_decal", gameLocal.random ); + if ( decal && *decal ) { + gameLocal.ProjectDecal( collision.c.point, -collision.c.normal, spawnArgs.GetFloat( "decal_trace", "128.0" ), true, spawnArgs.GetFloat( "decal_size", "6.0" ), decal ); + } + + if (bDamageOnCollision) { + const idKeyValue *kv = spawnArgs.FindKey("def_damage"); + if (ent && kv != NULL) { + ent->Damage(this, gameLocal.world, velocity, kv->GetValue().c_str(), 1.0f, 0); + } + } + if (bDetonateOnCollision) { + // Need to post an event because already in physics code now, can't nest a projectile spawn from within physics code + // currently, because rigid body physics ::Evaluate is not reentrant friendly (has a static timer) + PostEventMS(&EV_ExplodedBy, 0, this); + + return true; + } + return false; +} + +void hhMine::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + fl.takedamage = false; // nla - Prevent killed from being called too many times. + + // Need to post an event because already in physics code now, can't nest a projectile spawn from within physics code + // currently, because rigid body physics ::Evaluate is not reentrant friendly (has a static timer) + PostEventMS(&EV_ExplodedBy, 0, attacker); +} + +void hhMine::Explode( idEntity *attacker ) { + hhFxInfo fxInfo; + int splash_damage_delay = SEC2MS( spawnArgs.GetFloat( "splash_damage_delay", "0.5" ) ); + + if (bExploded || IsHidden() ) { + return; + } + + bExploded = true; + + // Activate targets + ActivateTargets( attacker ); + + // Set for removal + if ( spawnArgs.GetBool( "respawn" ) ) { + fl.takedamage = true; + PostEventSec( &EV_Show, spawnArgs.GetFloat( "respawn_delay", "2" ) ); + bExploded = false; + Hide(); + } else { + PostEventMS( &EV_Remove, 1500 + splash_damage_delay ); + GetPhysics()->SetContents( 0 ); + fl.takedamage = false; + Hide(); + } + + RemoveBinds(); + + // Spawn explosion + StopSound( SND_CHANNEL_IDLE ); + StartSound( "snd_explode", SND_CHANNEL_ANY ); + + //fixme: if we stay with moveables, this can be replaced by key "gib" + if ( spawnArgs.GetString("def_debrisspawner")[0] ) { + hhUtils::SpawnDebrisMass(spawnArgs.GetString("def_debrisspawner"), GetOrigin()); + } + + //fixme: fx are spawned by debris system? + fxInfo.SetNormal( GetAxis()[2] ); + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfoPrefixed( "fx_detonate", GetOrigin(), GetAxis(), &fxInfo ); + + if (spawnArgs.FindKey("def_splash_damage") != NULL) { + PostEventMS(&EV_ExplodeDamage, splash_damage_delay, attacker); + } +} + +bool hhMine::WasSpawnedBy(idEntity *theSpawner) { + return (spawner == theSpawner); +} + +void hhMine::Event_Trigger( idEntity *activator ) { + Explode(activator); +} + +void hhMine::Event_ExplodedBy( idEntity *activator) { + Explode(activator); +} + +void hhMine::Event_MineHover() { + GetPhysics()->SetLinearVelocity( vec3_zero ); +} + +void hhMine::Event_Remove() { + if (spawner) { // notify spawner + spawner->MineRemoved(this); + spawner = NULL; + } + hhMoveable::Event_Remove(); +} + +void hhMine::Event_ExplodeDamage( idEntity *attacker ) { + gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), this, attacker, this, this, spawnArgs.GetString("def_splash_damage") ); +} + + +//========================================================================== +// +// hhMineSpawner +// +//========================================================================== +const idEventDef EV_SpawnMine("SpawnMine", NULL); + +CLASS_DECLARATION(hhAnimatedEntity, hhMineSpawner) + EVENT( EV_Activate, hhMineSpawner::Event_Activate) + EVENT( EV_SpawnMine, hhMineSpawner::Event_SpawnMine) + EVENT( EV_Remove, hhMineSpawner::Event_Remove ) +END_CLASS + +void hhMineSpawner::Spawn(void) { + population = 0; + targetPopulation = spawnArgs.GetInt("population"); + mineVelocity = spawnArgs.GetVector("velocity"); + mineAVelocity = spawnArgs.GetVector("avelocity"); + bRandomDirection = spawnArgs.GetBool("randdir"); + bRandomRotation = spawnArgs.GetBool("randrot"); + spawnDelay = SEC2MS(spawnArgs.GetFloat("spawndelay")); + speed = mineVelocity.Length(); + aspeed = mineAVelocity.Length(); + active = spawnArgs.GetBool("start_on"); + + GetPhysics()->SetContents(0); + if (targetPopulation > 0 && active) { + PostEventMS(&EV_SpawnMine, 2000); + } + BecomeActive(TH_THINK); // Need to be active in order to get dormant messages +} + +void hhMineSpawner::Save(idSaveGame *savefile) const { + savefile->WriteInt( population ); + savefile->WriteInt( targetPopulation ); + savefile->WriteVec3( mineVelocity ); + savefile->WriteVec3( mineAVelocity ); + savefile->WriteBool( bRandomDirection ); + savefile->WriteBool( bRandomRotation ); + savefile->WriteFloat( speed ); + savefile->WriteFloat( aspeed ); + savefile->WriteInt( spawnDelay ); + savefile->WriteBool( active ); +} + +void hhMineSpawner::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( population ); + savefile->ReadInt( targetPopulation ); + savefile->ReadVec3( mineVelocity ); + savefile->ReadVec3( mineAVelocity ); + savefile->ReadBool( bRandomDirection ); + savefile->ReadBool( bRandomRotation ); + savefile->ReadFloat( speed ); + savefile->ReadFloat( aspeed ); + savefile->ReadInt( spawnDelay ); + savefile->ReadBool( active ); +} + +void hhMineSpawner::DormantBegin() { + // Remove all pending spawn events + CancelEvents(&EV_SpawnMine); +} + +void hhMineSpawner::DormantEnd() { + // restart if active + CheckPopulation(); +} + +void hhMineSpawner::CheckPopulation() { + if (active && population < targetPopulation) { + CancelEvents(&EV_SpawnMine); + PostEventMS(&EV_SpawnMine, spawnDelay); + } +} + +void hhMineSpawner::MineRemoved(hhMine *mine) { + --population; + CheckPopulation(); +} + +void hhMineSpawner::SpawnMine() { + if (fl.isDormant) { + return; + } + + const char *mineDefName = spawnArgs.GetString("def_mine"); + idBounds bounds = GetPhysics()->GetAbsBounds(); + idVec3 location = hhUtils::RandomPointInBounds(bounds.Expand(-33)); + + // If won't fit, wait and spawn later + if ( spawnArgs.GetBool( "force_spawn", "0" ) || hhUtils::EntityDefWillFit(mineDefName, location, mat3_identity, CONTENTS_SOLID, NULL)) { + if (bRandomDirection) { + mineVelocity = hhUtils::RandomVector() * speed; + } + if (bRandomRotation) { + mineAVelocity = hhUtils::RandomVector() * aspeed; + } + idDict args; + args.SetVector("origin", location); + + hhMine *mine = static_cast(gameLocal.SpawnObject(mineDefName, &args)); + if (mine) { + ActivateTargets( this ); + mine->SetSpawner(this); + mine->Launch(mineVelocity, mineAVelocity); + float stopDelay = spawnArgs.GetFloat( "stop_delay", "0" ); + if ( stopDelay > 0.0f ) { + mine->PostEventSec( &EV_MineHover, stopDelay ); + } + } + ++population; + CheckPopulation(); + } + else { + // Check population with low delay, since it's already time to spawn + if (active && population < targetPopulation) { + CancelEvents(&EV_SpawnMine); + PostEventMS(&EV_SpawnMine, 500); + } + } +} + +void hhMineSpawner::Event_Activate(idEntity *activator) { + if ( spawnArgs.GetBool( "limit_triggers", "0" ) ) { + active = false; + CancelEvents(&EV_SpawnMine); // Turn off + if ( population < targetPopulation ) { + SpawnMine(); // This also will continue to check population + } + return; + } + + // Use population=0 to spawn purely based on triggers + if (active && targetPopulation != 0) { + active = false; + CancelEvents(&EV_SpawnMine); // Turn off + } + else { + active = true; + SpawnMine(); // This also will continue to check population + } +} + +void hhMineSpawner::Event_SpawnMine() { + SpawnMine(); +} + +void hhMineSpawner::Event_Remove() { + // Handle removal gracefully + idEntity *ent = NULL; + hhMine *mine = NULL; + for (int ix=0; ixIsType(hhMine::Type)) { + mine = static_cast(ent); + if (mine->WasSpawnedBy(this)) { + mine->SetSpawner(NULL); + if ( spawnArgs.GetBool( "explode_on_remove" ) ) { + mine->Explode( NULL ); + } + } + } + } + idEntity::Event_Remove(); +} diff --git a/src/Prey/game_mine.h b/src/Prey/game_mine.h new file mode 100644 index 0000000..2a23b8d --- /dev/null +++ b/src/Prey/game_mine.h @@ -0,0 +1,82 @@ + +#ifndef __GAME_MINE_H__ +#define __GAME_MINE_H__ + +extern const idEventDef EV_ExplodeDamage; + +class hhMineSpawner; + +class hhMine : public hhMoveable { +public: + CLASS_PROTOTYPE( hhMine ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Launch(idVec3 &velocity, idVec3 &avelocity); + virtual bool AllowCollision( const trace_t &collision ); + virtual void ApplyImpulse(idEntity * ent, int id, const idVec3 &point, const idVec3 &impulse); + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + void Explode( idEntity *attacker ); + void SetSpawner(hhMineSpawner *s) { spawner = s; } + bool WasSpawnedBy(idEntity *theSpawner); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void Think(); + +protected: + void SpawnTrigger(); + void Event_ExplodeDamage( idEntity *attacker ); + void Event_Trigger( idEntity *activator ); + virtual void Event_Remove(); + void Event_ExplodedBy( idEntity *activator); + void Event_MineHover(); + +protected: + hhMineSpawner * spawner; // Entity that spawned this object + bool bDetonateOnCollision; + bool bDamageOnCollision; + idInterpolate fadeAlpha; + bool bScaleIn; + bool bExploded; +}; + + +extern const idEventDef EV_SpawnMine; + +class hhMineSpawner : public hhAnimatedEntity { +public: + CLASS_PROTOTYPE( hhMineSpawner ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void MineRemoved(hhMine *mine); + void DormantBegin(); + void DormantEnd(); + +protected: + virtual void SpawnMine(); + void CheckPopulation(); + + void Event_Activate(idEntity *activator); + void Event_SpawnMine(); + virtual void Event_Remove(); + +protected: + int population; + int targetPopulation; + idVec3 mineVelocity; + idVec3 mineAVelocity; + bool bRandomDirection; + bool bRandomRotation; + float speed; // Precomputed speed based on mineVelocity + float aspeed; // Precomputed speed based on mineAVelocity + int spawnDelay; + bool active; +}; + + +#endif diff --git a/src/Prey/game_misc.cpp b/src/Prey/game_misc.cpp new file mode 100644 index 0000000..6a43544 --- /dev/null +++ b/src/Prey/game_misc.cpp @@ -0,0 +1,404 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +//========================================================================== +// +// hhWallWalkable +// +//========================================================================== + +CLASS_DECLARATION( idStaticEntity, hhWallWalkable ) + EVENT(EV_Activate, hhWallWalkable::Event_Activate) +END_CLASS + +void hhWallWalkable::Spawn( void ) { + wallwalkOn = spawnArgs.GetBool("active"); + flicker = spawnArgs.GetBool("flicker"); + + // Get skin references (already precached) + onSkin = declManager->FindSkin( spawnArgs.GetString("skinOn") ); + offSkin = declManager->FindSkin( spawnArgs.GetString("skinOff") ); + + if (wallwalkOn) { + SetSkin( onSkin ); + alphaOn.Init(gameLocal.time, 0, 1.0f, 1.0f); + } + else { + SetSkin( offSkin ); + alphaOn.Init(gameLocal.time, 0, 0.0f, 0.0f); + } + alphaOn.SetHermiteParms(WALLWALK_HERM_S1, WALLWALK_HERM_S2); + SetShaderParm(4, alphaOn.GetCurrentValue(gameLocal.time)); + UpdateVisuals(); + + fl.networkSync = true; +} + +void hhWallWalkable::Save(idSaveGame *savefile) const { + savefile->WriteFloat( alphaOn.GetStartTime() ); // hhHermiteInterpolate + savefile->WriteFloat( alphaOn.GetDuration() ); + savefile->WriteFloat( alphaOn.GetStartValue() ); + savefile->WriteFloat( alphaOn.GetEndValue() ); + savefile->WriteFloat( alphaOn.GetS1() ); + savefile->WriteFloat( alphaOn.GetS2() ); + + savefile->WriteBool( wallwalkOn ); + savefile->WriteBool( flicker ); + savefile->WriteSkin( onSkin ); + savefile->WriteSkin( offSkin ); +} + +void hhWallWalkable::Restore( idRestoreGame *savefile ) { + float set, set2; + + savefile->ReadFloat( set ); // hhHermiteInterpolate + alphaOn.SetStartTime( set ); + savefile->ReadFloat( set ); + alphaOn.SetDuration( set ); + savefile->ReadFloat( set ); + alphaOn.SetStartValue(set); + savefile->ReadFloat( set ); + alphaOn.SetEndValue( set ); + savefile->ReadFloat( set ); + savefile->ReadFloat( set2 ); + alphaOn.SetHermiteParms(set, set2); + + savefile->ReadBool( wallwalkOn ); + savefile->ReadBool( flicker ); + savefile->ReadSkin( onSkin ); + savefile->ReadSkin( offSkin ); +} + +void hhWallWalkable::Think() { + idEntity::Think(); + if (thinkFlags & TH_THINK) { + SetShaderParm(4, alphaOn.GetCurrentValue(gameLocal.time)); + if (alphaOn.IsDone(gameLocal.time)) { + BecomeInactive(TH_THINK); + if (wallwalkOn) { + } + else { + SetSkin( offSkin ); + } + } + } +} + +void hhWallWalkable::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteBits(wallwalkOn, 1); + msg.WriteBits(IsActive(TH_THINK), 1); +} + +void hhWallWalkable::ReadFromSnapshot( const idBitMsgDelta &msg ) { + bool enabled = !!msg.ReadBits(1); + if (wallwalkOn != enabled) { + SetWallWalkable(enabled); + } + + bool thinking = !!msg.ReadBits(1); + if (thinking != IsActive(TH_THINK)) { + if (thinking) { + BecomeActive(TH_THINK); + } + else { + BecomeInactive(TH_THINK); + } + } +} + +void hhWallWalkable::ClientPredictionThink( void ) { + Think(); +} + +void hhWallWalkable::SetWallWalkable(bool on) { + wallwalkOn = on; + + float curAlpha = alphaOn.GetCurrentValue(gameLocal.time); + + if (wallwalkOn) { // Turning on + BecomeActive(TH_THINK); + SetSkin( onSkin ); + StartSound( "snd_powerup", SND_CHANNEL_ANY ); + alphaOn.Init(gameLocal.time, WALLWALK_TRANSITION_TIME, curAlpha, 1.0f ); + alphaOn.SetHermiteParms(WALLWALK_HERM_S1, WALLWALK_HERM_S2); + } + else { // Turning off + BecomeActive(TH_THINK); + StartSound( "snd_powerdown", SND_CHANNEL_ANY ); + alphaOn.Init(gameLocal.time, WALLWALK_TRANSITION_TIME, curAlpha, 0.0f); + alphaOn.SetHermiteParms(WALLWALK_HERM_S1, 1.0f); // no overshoot + } +} + +void hhWallWalkable::Event_Activate(idEntity *activator) { + SetWallWalkable(!wallwalkOn); +} + +//========================================================================== +// +// hhFuncEmitter +// +//========================================================================== +CLASS_DECLARATION( idStaticEntity, hhFuncEmitter ) + EVENT( EV_Activate, hhFuncEmitter::Event_Activate ) +END_CLASS + +void hhFuncEmitter::Spawn( void ) { + particle = static_cast( declManager->FindType(DECL_PARTICLE, spawnArgs.GetString("smoke_particle"), false) ); + particleStartTime = -1; + + (spawnArgs.GetBool("start_off")) ? Hide() : Show(); +} + +void hhFuncEmitter::Hide() { + idStaticEntity::Hide(); + + renderEntity.shaderParms[SHADERPARM_PARTICLE_STOPTIME] = MS2SEC( gameLocal.time ); + + particleStartTime = -1; + + BecomeInactive( TH_TICKER ); +} + +void hhFuncEmitter::Show() { + idStaticEntity::Show(); + + renderEntity.shaderParms[SHADERPARM_PARTICLE_STOPTIME] = 0; + renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.GetTime() ); + + particleStartTime = gameLocal.GetTime(); + + BecomeActive( TH_TICKER ); +} + +void hhFuncEmitter::Save( idSaveGame *savefile ) const { + savefile->WriteParticle( particle ); + savefile->WriteInt( particleStartTime ); +} + +void hhFuncEmitter::Restore( idRestoreGame *savefile ) { + savefile->ReadParticle( particle ); + savefile->ReadInt( particleStartTime ); +} + +void hhFuncEmitter::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteFloat( renderEntity.shaderParms[ SHADERPARM_PARTICLE_STOPTIME ] ); + msg.WriteFloat( renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] ); +} + +void hhFuncEmitter::ReadFromSnapshot( const idBitMsgDelta &msg ) { + renderEntity.shaderParms[ SHADERPARM_PARTICLE_STOPTIME ] = msg.ReadFloat(); + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = msg.ReadFloat(); + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + +void hhFuncEmitter::Ticker() { + if( IsHidden() ) { + return; + } + + if( particle && particleStartTime != -1 ) { + if( !gameLocal.smokeParticles->EmitSmoke(particle, particleStartTime, gameLocal.random.RandomFloat(), GetOrigin(), GetAxis()) ) { + particleStartTime = -1; + } + } + + if( modelDefHandle != -1 ) { + renderEntity.origin = GetOrigin(); + renderEntity.axis = GetAxis(); + UpdateVisuals(); + } +} + +void hhFuncEmitter::Event_Activate( idEntity *activator ) { + (IsHidden() || spawnArgs.GetBool("cycleTrigger")) ? Show() : Hide(); + + UpdateVisuals(); +} + + +//========================================================================== +// +// hhPathEmitter +// +//========================================================================== + +CLASS_DECLARATION( hhFuncEmitter, hhPathEmitter ) +END_CLASS + +void hhPathEmitter::Spawn() { +} + +//========================================================================== +// +// hhDeathWraithEnergy +// +//========================================================================== + +CLASS_DECLARATION( hhPathEmitter, hhDeathWraithEnergy ) +END_CLASS + +void hhDeathWraithEnergy::Spawn() { + startTime = MS2SEC(gameLocal.time); + duration = spawnArgs.GetFloat("duration"); + + startRadius = spawnArgs.GetFloat("startRadius"); + endRadius = spawnArgs.GetFloat("endRadius"); + startTheta = DEG2RAD(spawnArgs.GetFloat("startTheta")); + endTheta = DEG2RAD(spawnArgs.GetFloat("endTheta")); + startZ = spawnArgs.GetFloat("startZ"); + endZ = spawnArgs.GetFloat("endZ"); + + // For testing + SetDestination(vec3_origin); + SetPlayer(static_cast(gameLocal.GetLocalPlayer())); + + StartSound("snd_idle", SND_CHANNEL_BODY); +} + +void hhDeathWraithEnergy::Save(idSaveGame *savefile) const { + savefile->WriteFloat( startTime ); + savefile->WriteFloat( duration ); + savefile->WriteFloat( startRadius ); + savefile->WriteFloat( endRadius ); + savefile->WriteFloat( startTheta ); + savefile->WriteFloat( endTheta ); + savefile->WriteFloat( startZ ); + savefile->WriteFloat( endZ ); + savefile->WriteVec3( centerPosition ); + thePlayer.Save(savefile); + + savefile->WriteFloat(spline.tension); + savefile->WriteFloat(spline.continuity); + savefile->WriteFloat(spline.bias); + savefile->WriteInt(spline.nodes.Num()); // idList + for (int i=0; iWriteVec3(spline.nodes[i]); + } +} + +void hhDeathWraithEnergy::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( startTime ); + savefile->ReadFloat( duration ); + savefile->ReadFloat( startRadius ); + savefile->ReadFloat( endRadius ); + savefile->ReadFloat( startTheta ); + savefile->ReadFloat( endTheta ); + savefile->ReadFloat( startZ ); + savefile->ReadFloat( endZ ); + savefile->ReadVec3( centerPosition ); + thePlayer.Restore(savefile); + + savefile->ReadFloat(spline.tension); + savefile->ReadFloat(spline.continuity); + savefile->ReadFloat(spline.bias); + + int num; + spline.nodes.Clear(); // idList + savefile->ReadInt(num); + spline.nodes.SetNum(num); + for (int i=0; iReadVec3(spline.nodes[i]); + } +} + +void hhDeathWraithEnergy::SetPlayer(hhPlayer *player) { + thePlayer = player; +} + +void hhDeathWraithEnergy::SetDestination(const idVec3 &destination) { + // Cylindrical support + this->centerPosition = destination; + idVec3 toWraith = GetOrigin() - centerPosition; + CartesianToCylindrical(toWraith, startRadius, startTheta, startZ); + endTheta += startTheta; + + // Spline support + spline.Clear(); + spline.SetControls(spawnArgs.GetFloat("tension"), spawnArgs.GetFloat("continuity"), spawnArgs.GetFloat("bias")); + spline.AddPoint(GetOrigin()); + + if (thePlayer.IsValid() && thePlayer->DeathWalkStage2()) { + idEntity *holeMarker = gameLocal.FindEntity( "dw_floatingBodyMarker" ); + if (holeMarker) { + spline.AddPoint(holeMarker->GetOrigin()); + } + } + + spline.AddPoint(destination); +} + +void hhDeathWraithEnergy::CartesianToCylindrical(idVec3 &cartesian, float &radius, float &theta, float &z) { + radius = cartesian.ToVec2().Length(); + theta = idMath::ATan(cartesian.y, cartesian.x); + z = cartesian.z; +} + +idVec3 hhDeathWraithEnergy::CylindricalToCartesian(float radius, float theta, float z) { + idVec3 cartesian; + cartesian.x = radius * idMath::Cos(theta); + cartesian.y = radius * idMath::Sin(theta); + cartesian.z = z; + return cartesian; +} + +void hhDeathWraithEnergy::Ticker() { + float theta; + float radius; + float z; + + if (!thePlayer.IsValid()) { + return; + } + + float alpha = (MS2SEC(gameLocal.time) - startTime) / duration; + + if (alpha < 1.0f) { + + if (thePlayer->DeathWalkStage2()) { + SetOrigin( spline.GetValue(alpha) ); + } + else { + radius = startRadius + alpha*(endRadius-startRadius); + theta = startTheta + alpha*(endTheta-startTheta); + z = startZ + alpha * (endZ - startZ); + + idVec3 locationRelativeToCenter = CylindricalToCartesian(radius, theta, z); + idEntity *destEntity = thePlayer->GetDeathwalkEnergyDestination(); + if (destEntity) { + centerPosition = destEntity->GetOrigin(); + } + + SetOrigin(centerPosition + locationRelativeToCenter); + } + } + else if (!IsHidden()) { + Hide(); + StopSound(SND_CHANNEL_BODY); + + bool energyHealth = spawnArgs.GetBool("healthEnergy"); + + idEntity *dwProxy = thePlayer->GetDeathwalkEnergyDestination(); + if (dwProxy) { + // Spawn arrival effect + StartSound("snd_arrival", SND_CHANNEL_ANY); + + dwProxy->SetShaderParm(SHADERPARM_TIMEOFFSET, -MS2SEC(gameLocal.time) ); + dwProxy->SetShaderParm(SHADERPARM_MODE, energyHealth ? 2 : 1 ); + } + + // Notify the player + thePlayer->DeathWraithEnergyArived(energyHealth); + + PostEventMS(&EV_Remove, 5000); + } + + hhPathEmitter::Ticker(); +} + + diff --git a/src/Prey/game_misc.h b/src/Prey/game_misc.h new file mode 100644 index 0000000..3123f0f --- /dev/null +++ b/src/Prey/game_misc.h @@ -0,0 +1,100 @@ +#ifndef __HH_MISC_H +#define __HH_MISC_H + +#define WALLWALK_TRANSITION_TIME 2510 // Time over which to fade +#define WALLWALK_HERM_S1 1.0f // Slope at start +#define WALLWALK_HERM_S2 -2.0f // Slope at end + +class hhWallWalkable : public idStaticEntity { + CLASS_PROTOTYPE(hhWallWalkable); + +public: + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - netcode + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + + void SetWallWalkable(bool on); + virtual void Think(); + +protected: + void Event_Activate(idEntity *activator); + +protected: + hhHermiteInterpolate alphaOn; // Degree to which wallwalk is on + bool wallwalkOn; + bool flicker; + const idDeclSkin *onSkin; + const idDeclSkin *offSkin; +}; + +class hhFuncEmitter : public idStaticEntity { + CLASS_PROTOTYPE( hhFuncEmitter ); + +public: + void Spawn( void ); + + virtual void Hide(); + virtual void Show(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + +protected: + virtual void Ticker(); + +protected: + void Event_Activate( idEntity *activator ); + +protected: + const idDeclParticle* particle; + int particleStartTime; +}; + + +class hhPathEmitter : public hhFuncEmitter { + CLASS_PROTOTYPE( hhPathEmitter ); + +public: + void Spawn( void ); +}; + + +class hhDeathWraithEnergy : public hhPathEmitter { + CLASS_PROTOTYPE( hhDeathWraithEnergy ); + +public: + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void SetPlayer(hhPlayer *player); + void SetDestination(const idVec3 &destination); + +protected: + virtual void Ticker(); + idVec3 CylindricalToCartesian(float radius, float theta, float z); + void CartesianToCylindrical(idVec3 &cartesian, float &radius, float &theta, float &z); + +protected: + hhTCBSpline spline; + float startTime; + float duration; + float startRadius; + float endRadius; + float startTheta; + float endTheta; + float startZ; + float endZ; + idVec3 centerPosition; + idEntityPtr thePlayer; +}; + +#endif diff --git a/src/Prey/game_modeldoor.cpp b/src/Prey/game_modeldoor.cpp new file mode 100644 index 0000000..e9b1a16 --- /dev/null +++ b/src/Prey/game_modeldoor.cpp @@ -0,0 +1,1076 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +// Events +const idEventDef EV_ModelDoorSpawnTrigger( "" ); +const idEventDef EV_ModelDoorOpen( "open" ); +const idEventDef EV_ModelDoorClose( "close" ); + +const idEventDef EV_ModelDoorClosedBegin( "" ); +const idEventDef EV_ModelDoorOpeningBegin( "" ); +const idEventDef EV_ModelDoorOpenBegin( "" ); +const idEventDef EV_ModelDoorClosingBegin( "" ); + +const idEventDef EV_SetBuddiesShaderParm( "setBuddiesShaderParm", "df" ); + +//-------------------------------- +// hhDoorTrigger +//-------------------------------- +CLASS_DECLARATION( idEntity, hhDoorTrigger ) + EVENT( EV_Touch, hhDoorTrigger::Event_TriggerDoor ) +END_CLASS + +//-------------------------------- +// hhDoorTrigger::hhDoorTrigger() +//-------------------------------- +hhDoorTrigger::hhDoorTrigger() { + enabled = true; // Need to have the ability to disable a trigger +} + +//-------------------------------- +// hhDoorTrigger::Event_TriggerDoor +//-------------------------------- +void hhDoorTrigger::Event_TriggerDoor( idEntity *other, trace_t *trace ) { + if( door && IsEnabled() ) { + door->ProcessEvent( &EV_Touch, other, trace ); + } +} + +//--------------------- +// hhDoorTrigger::GetEntitiesWithin +// ents should be an array of idEntity of length entLength +// +// If not enabled, always returns 0 +//--------------------- +int hhDoorTrigger::GetEntitiesWithin( idEntity **ents, int entsLength ) { + int num; + + if ( !enabled ) { + return( 0 ); + } + + num = gameLocal.clip.EntitiesTouchingBounds( GetPhysics()->GetAbsBounds(), MASK_SHOT_BOUNDINGBOX, ents, entsLength ); + + return( num ); + +} + +void hhDoorTrigger::Save( idSaveGame *savefile ) const { + savefile->WriteObject( door ); + savefile->WriteBool( enabled ); +} + +void hhDoorTrigger::Restore( idRestoreGame *savefile ) { + savefile->ReadObject( reinterpret_cast ( door ) ); + savefile->ReadBool( enabled ); +} + +//-------------------------------- +// hhModelDoor +//-------------------------------- +CLASS_DECLARATION( hhAnimatedEntity, hhModelDoor ) + EVENT( EV_TeamBlocked, hhModelDoor::Event_TeamBlocked ) + EVENT( EV_PartBlocked, hhModelDoor::Event_PartBlocked ) + EVENT( EV_Activate, hhModelDoor::Event_Activate ) + EVENT( EV_Touch, hhModelDoor::Event_Touch ) + EVENT( EV_ModelDoorOpen, hhModelDoor::Event_OpenDoor ) + EVENT( EV_ModelDoorClose, hhModelDoor::Event_CloseDoor ) + EVENT( EV_Thread_SetCallback, hhModelDoor::Event_SetCallback ) + + EVENT( EV_SetBuddiesShaderParm, hhModelDoor::Event_SetBuddiesShaderParm ) + + // Internal events + EVENT( EV_ModelDoorSpawnTrigger, hhModelDoor::Event_SpawnNewDoorTrigger ) + EVENT( EV_ModelDoorClosedBegin, hhModelDoor::Event_STATE_ClosedBegin ) + EVENT( EV_ModelDoorOpeningBegin, hhModelDoor::Event_STATE_OpeningBegin ) + EVENT( EV_ModelDoorOpenBegin, hhModelDoor::Event_STATE_OpenBegin ) + EVENT( EV_ModelDoorClosingBegin, hhModelDoor::Event_STATE_ClosingBegin ) +END_CLASS + +//-------------------------------- +// hhModelDoor::Spawn +//-------------------------------- +void hhModelDoor::Spawn( void ) { + idEntity* master = NULL; + + doorTrigger = NULL; + fl.takedamage = true; + SetBlocking(true); + +// spawnArgs.GetFloat( "dmg", "2", &damage ); + spawnArgs.GetFloat( "triggersize", "50", triggersize ); + spawnArgs.GetBool( "no_touch", "0", noTouch ); + spawnArgs.GetInt( "locked", "0", locked ); + spawnArgs.GetFloat( "wait", "1.5", wait ); + if ( wait <= 0.1f ) { + //sanity check for waittime + wait = 0.1f; + } + spawnArgs.GetFloat( "damage", "1", damage ); + spawnArgs.GetFloat( "airlockwait", "3.0", airLockSndWait ); + airLockSndWait *= 1000.0f; // Convert from seconds to ms + + hhUtils::GetValues( spawnArgs, "buddy", buddyNames, true ); + + // If "health" is supplied, door will open when killed + // So this key determines if we should really take damage or not + fl.takedamage = (health > 0); + forcedOpen = false; + bOpenForMonsters = spawnArgs.GetBool("OpenForMonsters", "1"); + + SetShaderParm( SHADERPARM_MODE, GetDoorShaderParm( locked != 0, true ) ); // 2=locked, 1=unlocked, 0=never locked + + //HUMANHEAD: aob - airlock stuff + airlockMaster = NULL; + + airlockTeam.SetOwner( this ); + airlockTeamName = spawnArgs.GetString( "airlockTeam" ); + master = DetermineTeamMaster( GetAirLockTeamName() ); + if( master ) { + airlockMaster = static_cast( master ); + if( airlockMaster != this ) { + JoinAirLockTeam( airlockMaster ); + } + } + //HUMANHEAD END + + openAnim = GetAnimator()->GetAnim( "open" ); + closeAnim = GetAnimator()->GetAnim( "close" ); + idleAnim = GetAnimator()->GetAnim( "idle" ); + painAnim = GetAnimator()->GetAnim( "pain" ); + + // Spawn the trigger + if (!gameLocal.isClient) { + PostEventMS( &EV_ModelDoorSpawnTrigger, 0 ); + } + + // see if we are on an areaportal + areaPortal = gameRenderWorld->FindPortal( GetPhysics()->GetAbsBounds() ); + + StartSound( "snd_idle", SND_CHANNEL_ANY ); + + if( spawnArgs.GetBool("start_open") ) { + StartOpen(); + } else { + StartClosed(); + } + + threadNum = 0; + nextAirLockSnd = 0; + + finishedSpawn = true; + + fl.networkSync = true; + + bShuttleDoors = spawnArgs.GetBool( "shuttle_doors" ); +} + +//-------------------------------- +// hhModelDoor::hhModelDoor +//-------------------------------- +hhModelDoor::hhModelDoor() { + bOpen = false; + bTransition = false; + finishedSpawn = false; + sndTrigger = NULL; + nextSndTriggerTime = 0; +} + +//-------------------------------- +// hhModelDoor::~hhModelDoor +//-------------------------------- +hhModelDoor::~hhModelDoor() { + airlockTeam.Remove(); + if ( sndTrigger ) { + delete sndTrigger; + sndTrigger = NULL; + } +} + +void hhModelDoor::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( damage ); + savefile->WriteFloat( wait ); + savefile->WriteFloat( triggersize ); + savefile->WriteInt( areaPortal ); + if ( areaPortal ) { + savefile->WriteInt( gameRenderWorld->GetPortalState( areaPortal ) ); + } + savefile->WriteStringList( buddyNames ); + savefile->WriteInt( locked ); + savefile->WriteInt( openAnim ); + savefile->WriteInt( closeAnim ); + savefile->WriteInt( idleAnim ); + savefile->WriteInt( painAnim ); + savefile->WriteBool( forcedOpen ); + savefile->WriteBool( bOpenForMonsters ); + savefile->WriteBool( noTouch ); + savefile->WriteInt( threadNum ); + savefile->WriteObject( doorTrigger ); + savefile->WriteString( airlockTeamName ); + savefile->WriteObject( airlockMaster ); + savefile->WriteBounds( crusherBounds ); + + activatedBy.Save( savefile ); + + savefile->WriteBool( bOpen ); + savefile->WriteBool( bTransition ); + savefile->WriteFloat( airLockSndWait ); + + savefile->WriteClipModel( sndTrigger ); + savefile->WriteInt( nextSndTriggerTime ); +} + +void hhModelDoor::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( damage ); + savefile->ReadFloat( wait ); + savefile->ReadFloat( triggersize ); + savefile->ReadInt( areaPortal ); + if ( areaPortal ) { + int portalState; + savefile->ReadInt( portalState ); + gameLocal.SetPortalState( areaPortal, portalState ); + } + savefile->ReadStringList( buddyNames ); + savefile->ReadInt( locked ); + savefile->ReadInt( openAnim ); + savefile->ReadInt( closeAnim ); + savefile->ReadInt( idleAnim ); + savefile->ReadInt( painAnim ); + savefile->ReadBool( forcedOpen ); + savefile->ReadBool( bOpenForMonsters ); + savefile->ReadBool( noTouch ); + savefile->ReadInt( threadNum ); + savefile->ReadObject( reinterpret_cast ( doorTrigger ) ); + savefile->ReadString( airlockTeamName ); + savefile->ReadObject( reinterpret_cast ( airlockMaster ) ); + savefile->ReadBounds( crusherBounds ); + + activatedBy.Restore( savefile ); + + savefile->ReadBool( bOpen ); + savefile->ReadBool( bTransition ); + savefile->ReadFloat( airLockSndWait ); + + airlockTeam.SetOwner( this ); + if( airlockMaster && airlockMaster != this ) { + JoinAirLockTeam( airlockMaster ); + } + + nextAirLockSnd = 0; + finishedSpawn = true; + bShuttleDoors = spawnArgs.GetBool( "shuttle_doors" ); + + savefile->ReadClipModel( sndTrigger ); + savefile->ReadInt( nextSndTriggerTime ); +} + +//-------------------------------- +// hhModelDoor::Lock +//-------------------------------- +void hhModelDoor::Lock( int f ) { + locked = f; + float parmValue = GetDoorShaderParm( locked != 0, false ); + SetShaderParm( SHADERPARM_MODE, parmValue ); + SetBuddiesShaderParm( SHADERPARM_MODE, parmValue ); + + if (locked) { + CloseDoor(); + } +} + +//-------------------------------- +// hhModelDoor::CanOpen +//-------------------------------- +bool hhModelDoor::CanOpen() const { + if( IsLocked() ) { + return false; + } + + if( !GetAirLockMaster() ) { + return true; + } + + for( hhModelDoor* node = GetAirLockMaster()->airlockTeam.ListHead()->Owner(); node != NULL; node = node->airlockTeam.Next() ) { + if( node != this && node->IsOpen() ) { + return false; + } + } + + return true; +} + +//-------------------------------- +// hhModelDoor::CanClose +//-------------------------------- +bool hhModelDoor::CanClose() const { + return true; +} + +//-------------------------------- +// hhModelDoor::OpenDoor +//-------------------------------- +void hhModelDoor::OpenDoor() { + if( IsClosed() && CanOpen() ) { + bTransition = true; + PostEventMS( &EV_ModelDoorOpeningBegin, 0 ); + } +} + +//-------------------------------- +// hhModelDoor::CloseDoor +//-------------------------------- +void hhModelDoor::CloseDoor() { + if( bOpen && !bTransition && CanClose() && !forcedOpen ) { + bTransition = true; + PostEventMS( &EV_ModelDoorClosingBegin, 0 ); + + // CJR: It's possible to open a door, then get back into it while it's animating closed, and then get squished + // the fix for this is to block the player from getting back in while it's closing. Projectiles will still pass through + GetPhysics()->SetContents( CONTENTS_PLAYERCLIP ); + } +} + +//-------------------------------- +// hhModelDoor::SetBlocking +//-------------------------------- +void hhModelDoor::SetBlocking( bool on ) { + GetPhysics()->SetContents( on ? CONTENTS_SOLID : 0 ); +} + +//-------------------------------- +// hhModelDoor::ClosePortal +//-------------------------------- +void hhModelDoor::ClosePortal( void ) { + if ( areaPortal ) { + gameLocal.SetPortalState( areaPortal, PS_BLOCK_VIEW ); + } +} + +//-------------------------------- +// hhModelDoor::OpenPortal +//-------------------------------- +void hhModelDoor::OpenPortal( void ) { + if ( areaPortal ) { + gameLocal.SetPortalState( areaPortal, PS_BLOCK_NONE ); + } +} + + +//-------------------------------- +// hhModelDoor::InformDone +//-------------------------------- +void hhModelDoor::InformDone() { + idThread::ObjectMoveDone( threadNum, this ); + threadNum = 0; +} + +//-------------------------------- +// hhModelDoor::Damage +//-------------------------------- +void hhModelDoor::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + if (fl.takedamage) { + if ( !bOpen && painAnim ) { + GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, painAnim, gameLocal.time, 500); + } + + hhAnimatedEntity::Damage(inflictor, attacker, dir, damageDefName, damageScale, location); + } +} + +//-------------------------------- +// hhModelDoor::Killed +//-------------------------------- +void hhModelDoor::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if (IsLocked()) { + Lock(0); + } + OpenDoor(); + fl.takedamage = false; + forcedOpen = true; +} + +//-------------------------------- +// hhModelDoor::SetBuddiesShaderParm +//-------------------------------- +void hhModelDoor::SetBuddiesShaderParm( int parm, float value ) { + idEntity* buddy = NULL; + + for( int ix = buddyNames.Num() - 1; ix >= 0; --ix ) { + if( !buddyNames[ix].Length() ) { + continue; + } + + buddy = gameLocal.FindEntity( buddyNames[ix].c_str() ); + if( !buddy ) { + continue; + } + + buddy->SetShaderParm( parm, value ); + } +} + +//-------------------------------- +// hhModelDoor::ToggleBuddiesShaderParm +//-------------------------------- +void hhModelDoor::ToggleBuddiesShaderParm( int parm, float firstValue, float secondValue, float toggleDelay ) { + SetBuddiesShaderParm( parm, firstValue ); + + CancelEvents( &EV_SetBuddiesShaderParm ); + PostEventSec( &EV_SetBuddiesShaderParm, toggleDelay, parm, secondValue ); +} + +//-------------------------------- +// hhModelDoor::DetermineTeamMaster +//-------------------------------- +idEntity* hhModelDoor::DetermineTeamMaster( const char* teamName ) { + idEntity* ent = NULL; + + if ( teamName && teamName[0] ) { + // 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(hhModelDoor::Type) && !idStr::Icmp( static_cast(ent)->GetAirLockTeamName(), teamName )) { + return ent; + } + if (ent->IsType(hhDoor::Type) && !idStr::Icmp( static_cast(ent)->GetAirLockTeamName(), teamName )) { + return ent; + } + } + } + + return NULL; +} + +//-------------------------------- +// hhModelDoor::JoinAirLockTeam +//-------------------------------- +void hhModelDoor::JoinAirLockTeam( hhModelDoor *master ) { + assert( master ); + + airlockTeam.AddToEnd( master->airlockTeam ); +} + +//-------------------------------- +// hhModelDoor::VerifyAirlockTeamStatus +//-------------------------------- +void hhModelDoor::VerifyAirlockTeamStatus() { + if( !GetAirLockMaster() ) { + return; + } + + for( hhModelDoor* node = GetAirLockMaster()->airlockTeam.ListHead()->Owner(); node != NULL; node = node->airlockTeam.Next() ) { + if( node != this && !node->IsClosed() ) { + gameLocal.Warning( "Airlock team '%s' has more than one member starting open", GetAirLockTeamName() ); + } + } +} + +//-------------------------------- +// hhModelDoor::StartOpen +//-------------------------------- +void hhModelDoor::StartOpen() { + VerifyAirlockTeamStatus(); + + bTransition = false; + bOpen = true; + OpenPortal(); + SetBlocking(false); + GetAnimator()->ClearAllAnims( gameLocal.time, 0 ); + GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, openAnim, gameLocal.time, 500); + Event_STATE_OpenBegin(); +} + +//-------------------------------- +// hhModelDoor::StartClosed +//-------------------------------- +void hhModelDoor::StartClosed() { + bTransition = false; + bOpen = false; + Event_STATE_ClosedBegin(); +} + +//-------------------------------- +// hhModelDoor::Event_SpawnNewDoorTrigger +// +// All of the parts of a door have been spawned, so create +// a trigger that encloses all of them +//-------------------------------- +void hhModelDoor::Event_SpawnNewDoorTrigger( void ) { + idBounds bounds,localbounds; + idBounds triggerBounds, soundTriggerBounds; + int i; + int best; + + // Since models bounds are overestimated, we need to use the bounds from the + // clipmodel, which was set before the over-estimation + localbounds = GetPhysics()->GetBounds(); + + // Save the original bounds in case we need to crush something + crusherBounds = GetPhysics()->GetAbsBounds(); + + // find the thinnest axis, which will be the one we expand + best = 0; + for ( i = 1 ; i < 3 ; i++ ) { + if ( localbounds[1][ i ] - localbounds[0][ i ] < localbounds[1][ best ] - localbounds[0][ best ] ) { + best = i; + } + } + triggerBounds = localbounds; + triggerBounds[1][ best ] += triggersize; + triggerBounds[0][ best ] -= triggersize; + + // Now transform into absolute coordintates + if ( GetPhysics()->GetAxis().IsRotated() ) { + bounds.FromTransformedBounds( triggerBounds, GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); + } + else { + bounds[0] = triggerBounds[0] + GetPhysics()->GetOrigin(); + bounds[1] = triggerBounds[1] + GetPhysics()->GetOrigin(); + } + + // create a trigger with this size + idDict args; + args.Set( "mins", bounds[0].ToString(0) ); + args.Set( "maxs", bounds[1].ToString(0) ); + + doorTrigger = ( hhDoorTrigger * ) gameLocal.SpawnEntityType( hhDoorTrigger::Type, &args ); + doorTrigger->GetPhysics()->SetContents( CONTENTS_TRIGGER ); + doorTrigger->door = this; + + // Disable the trigger if it is no_touch and not start_open + if ( noTouch ) { + doorTrigger->Disable(); + } + + + // ------------------------ + // Create the sound trigger + // ------------------------ + + float soundTriggerSize = triggersize * 0.3f; + soundTriggerBounds = localbounds; + soundTriggerBounds[1][ best ] += soundTriggerSize; + soundTriggerBounds[0][ best ] -= soundTriggerSize; + + // Now transform into absolute coordintates + if ( GetPhysics()->GetAxis().IsRotated() ) { + bounds.FromTransformedBounds( soundTriggerBounds, GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); + } + else { + bounds[0] = soundTriggerBounds[0] + GetPhysics()->GetOrigin(); + bounds[1] = soundTriggerBounds[1] + GetPhysics()->GetOrigin(); + } + bounds[0] -= GetPhysics()->GetOrigin(); + bounds[1] -= GetPhysics()->GetOrigin(); + + // create a trigger clip model + sndTrigger = new idClipModel( idTraceModel( bounds ) ); + sndTrigger->Link( gameLocal.clip, this, 254, GetPhysics()->GetOrigin(), mat3_identity ); + sndTrigger->SetContents( CONTENTS_TRIGGER ); + + // HACK: Also, since all the parts are now spawned, update the buddies' shaderparms + if (locked) { + float parmValue = GetDoorShaderParm( locked != 0, true ); + for ( i = 0; i < buddyNames.Num(); i++ ) { + if ( buddyNames[ i ].Length() ) { + idEntity *buddy = gameLocal.FindEntity( buddyNames[ i ] ); + if( buddy ) { + buddy->SetShaderParm(SHADERPARM_MODE, parmValue); + } + } + } + } + +} + +//-------------------------------- +// hhModelDoor::ToggleDoorState +//-------------------------------- +void hhModelDoor::ToggleDoorState( void ) { + if ( bOpen ) { + CloseDoor(); + } + else { + OpenDoor(); + } +} + +//-------------------------------- +// hhModelDoor::ForceAirLockTeamClosed +//-------------------------------- +void hhModelDoor::ForceAirLockTeamClosed() { + if( !GetAirLockMaster() ) { + return; + } + + ToggleBuddiesShaderParm( SHADERPARM_DIVERSITY, 1.0f, 0.0f, 2.0f ); + + for( hhModelDoor* node = GetAirLockMaster()->airlockTeam.ListHead()->Owner(); node != NULL; node = node->airlockTeam.Next() ) { + if( node != this && !node->IsClosed() ) { + node->CloseDoor(); + } + } +} + +//-------------------------------- +// hhModelDoor::ForceAirLockTeamOpen +//-------------------------------- +void hhModelDoor::ForceAirLockTeamOpen() { + if( !GetAirLockMaster() ) { + return; + } + + for( hhModelDoor* node = GetAirLockMaster()->airlockTeam.ListHead()->Owner(); node != NULL; node = node->airlockTeam.Next() ) { + if( node != this && !node->IsOpen() ) { + node->OpenDoor(); + } + } +} + +// State code +void hhModelDoor::Event_STATE_ClosedBegin() { + + // Alert any entities squished in the door + idEntity *touch[ MAX_GENTITIES ]; + int contentsMask = (CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_CORPSE|CONTENTS_TRIGGER); // corpses, moveableitems, spiritplayers, moveables + int num = hhUtils::EntitiesTouchingClipmodel(GetPhysics()->GetClipModel(), touch, MAX_GENTITIES, contentsMask); + for (int ix=0; ix 0.0f ) { + // HUMANHEAD mdl + // Throw spirit players back to their bodies if they get caught in a closing door + // Don't check this for MP, the damage below is enough to send them back + if ( !gameLocal.isMultiplayer && ent->IsType( hhPlayer::Type ) ) { + hhPlayer *player = reinterpret_cast< hhPlayer * > ( ent ); + if ( player->IsSpiritWalking() ) { + player->StopSpiritWalk(); + continue; + } + } + + if( ent->fl.takedamage ) { + ent->Damage( this, this, vec3_origin, "damage_doorbonk", damage, INVALID_JOINT ); + } + } + + ent->SquishedByDoor(this); + } + } + + SetBlocking(true); + ClosePortal(); + InformDone(); + + if ( idleAnim ) { + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, idleAnim, gameLocal.time, 500 ); + } + + // If we are noTouch, then disable the trigger. (Could have been enabled by GUI activation) + if ( noTouch && doorTrigger ) { + doorTrigger->Disable(); + } + + // Trigger entities if we have finished spawning + if ( !finishedSpawn ) { + return; + } + + ActivatePrefixed("triggerClosed", GetActivator()); + + if( GetAirLockMaster() ) { + // Mark the other team members as locked + for( hhModelDoor* node = airlockMaster->airlockTeam.ListHead()->Owner(); node != NULL; node = node->airlockTeam.Next() ) { + + // Skip this door and it's buddies + if( node == this || buddyNames.Find( node->name ) ) { + continue; + } + + // Don't change doors that are really locked + if( node->IsLocked() ) { + continue; + } + + // Mark as locked + node->SetBuddiesShaderParm( SHADERPARM_MISC, 0.0f ); + } + } + + HH_ASSERT( bOpen == true && bTransition == true ); + bTransition = false; + bOpen = false; +} + +void hhModelDoor::Event_STATE_OpeningBegin() { + SetBlocking(false); + OpenPortal(); + StartSound( "snd_open", SND_CHANNEL_ANY ); + + // Fire any triggerStartOpen entities + ActivatePrefixed("triggerStartOpen", GetActivator()); + + idEntity *ent; + idEntity *next; + for( ent = teamChain; ent != NULL; ent = next ) { + next = ent->GetTeamChain(); + if ( ent && ent->IsType( hhProjectile::Type ) ) { + ent->Unbind(); // bjk drops all bound projectiles such as arrows and mines + next = teamChain; + if (ent->IsType(hhProjectileSpiritArrow::Type)) { + ent->PostEventMS(&EV_Remove, 0); + } + //HUMANHEAD bjk PCF (4-28-06) - explode crawlers + if (ent->IsType(hhProjectileStickyCrawlerGrenade::Type)) { + static_cast(ent)->PostEventSec( &EV_Explode, 0.2f ); + } + } + } + + GetAnimator()->ClearAllAnims( gameLocal.time, 0 ); + GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, openAnim, gameLocal.time, 500); + int opentime = (openAnim) ? GetAnimator()->GetAnim( openAnim )->Length() : 0; + PostEventMS( &EV_ModelDoorOpenBegin, opentime ); + + if( airlockMaster ) { + // Mark the other team members as locked + for( hhModelDoor* node = airlockMaster->airlockTeam.ListHead()->Owner(); node != NULL; node = node->airlockTeam.Next() ) { + + // Skip this door and it's buddies + if( node == this || buddyNames.Find( node->name ) ) { + continue; + } + + // Don't change doors that are really locked + if( node->IsLocked() ) { + continue; + } + + // Mark as locked + node->SetBuddiesShaderParm( SHADERPARM_MISC, 1.0f ); + } + + // This prevents the our shader from being set as locked from the player moving too quickly between airlock doors + SetBuddiesShaderParm( SHADERPARM_MISC, 0.0f ); + } + + HH_ASSERT( bOpen == false && bTransition == true ); +} + +void hhModelDoor::Event_STATE_OpenBegin() { + InformDone(); + + // If we are no touch, then we were opened by something else, so let us care about the player being in the trigger/enable the trigger + if ( noTouch && doorTrigger ) { + doorTrigger->Enable(); + } + + // Trigger entities if we have finished spawning + if ( !finishedSpawn ) { + return; + } + + ActivatePrefixed("triggerOpened", GetActivator()); + + WakeTouchingEntities(); + + HH_ASSERT( bOpen == false && bTransition == true ); + bOpen = true; + bTransition = false; +} + +void hhModelDoor::Event_STATE_ClosingBegin() { + StartSound("snd_close", SND_CHANNEL_ANY); + + //GetAnimator()->ClearAllAnims( gameLocal.time, 0 ); + //FIXME: Despite the blend in/out here, it still pops when the idle is reapplied + GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, closeAnim, gameLocal.time, 500); + int closetime = (closeAnim) ? GetAnimator()->GetAnim( closeAnim )->Length() : 0; + PostEventMS( &EV_ModelDoorClosedBegin, closetime ); + + HH_ASSERT( bOpen == true && bTransition == true ); +} + +// ------------------------------------------------------------- +// Non-state code +// ------------------------------------------------------------- + +//-------------------------------- +// hhModelDoor::Event_Blocked +//-------------------------------- +void hhModelDoor::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) { + // reverse direction + ToggleDoorState(); +} + +//-------------------------------- +// hhModelDoor::Event_PartBlocked +//-------------------------------- +void hhModelDoor::Event_PartBlocked( idEntity *blockingEntity ) { + if ( damage > 0.0f ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_doorbonk", 1.0f, INVALID_JOINT ); + } +} + +//-------------------------------- +// hhModelDoor::Event_Activate +//-------------------------------- +void hhModelDoor::Event_Activate( idEntity *activator ) { + idEntity *buddy = NULL; + + if ( IsLocked() ) { + int oldLocked = locked; + Lock(0); + if ( oldLocked == 2 ) { + return; + } + + ToggleDoorState(); + CancelEvents( &EV_ModelDoorClose ); + PostEventMS( &EV_ModelDoorClose, wait * 1000 ); + } + else { + TryOpen( activator ); + } + +} + + +//-------------------------------- +// hhModelDoor::WakeTouchingEntities +//-------------------------------- +void hhModelDoor::WakeTouchingEntities() { + idEntity *touch[ MAX_GENTITIES ]; + idEntity *ent; + int num; + + num = gameLocal.clip.EntitiesTouchingBounds( GetPhysics()->GetAbsBounds(), MASK_ALL, touch, MAX_GENTITIES ); + for ( int i = 0; i < num; i++ ) { + ent = touch[ i ]; + if ( !ent || ent == this ) { + continue; + } + ent->ActivatePhysics(this); + } +} + +//-------------------------------- +// hhModelDoor::TryOpen +// If conditions are right, open. Otherwise don't open +//-------------------------------- +void hhModelDoor::TryOpen( idEntity *whoTrying ) { + + activatedBy = whoTrying; // Should thie be above OpenDoor + + // Only allow the player to affect airlock doors + if ( GetAirLockMaster() && !whoTrying->IsType( idPlayer::Type ) ) { + return; + } + + // Check if this actor is valid to open this door (if the player is spiritwalking) - cjr + if ( !whoTrying->ShouldTouchTrigger( this ) ) { + return; + } + + // If we're shuttle doors, make sure we only open for actors in vehicles. + if ( bShuttleDoors && ( ! whoTrying->IsType( idActor::Type ) || ! reinterpret_cast ( whoTrying )->InVehicle() ) ) { + return; + } + + if ( !bOpenForMonsters && whoTrying->IsType( hhMonsterAI::Type ) ) { + return; + } + + // Delay Close event since we're still in the doorway + CancelEvents( &EV_ModelDoorClose ); + PostEventMS( &EV_ModelDoorClose, wait * 1000 ); + + //This feels like ahack but I need to get this done + if( IsClosed() && !CanOpen() && GetAirLockMaster() ) { + ForceAirLockTeamClosed(); + if( gameLocal.time > nextAirLockSnd ) { + int length; + StartSound( "snd_airlock", SND_CHANNEL_BODY, 0, false, &length ); + nextAirLockSnd = gameLocal.time + length + airLockSndWait; + } + } + OpenDoor(); +} + + +//-------------------------------- +// hhModelDoor::EntitiesInTrigger +//-------------------------------- +bool hhModelDoor::EntitiesInTrigger() { + idEntity * ents[ MAX_GENTITIES ]; + idEntity * ent; + int num; + + if ( !doorTrigger ) { + return( false ); + } + + num = doorTrigger->GetEntitiesWithin( ents, MAX_GENTITIES ); + for ( int i = 0; i < num; ++i ) { + ent = ents[ i ]; + + if ( ent->fl.touchTriggers && ent->ShouldTouchTrigger( this ) ) { + + if ( !bOpenForMonsters && ent->IsType( hhMonsterAI::Type ) ) { + continue; + } + + if ( bShuttleDoors && ( ! ent->IsType( idActor::Type ) || ! reinterpret_cast ( ent )->InVehicle() ) ) { + continue; + } + + if ( !GetAirLockMaster() || ent->IsType( idPlayer::Type ) ) { // Only allow the player to affect airlock doors + return( true ); + } + } + } + + return( false ); +} + + +//-------------------------------- +// hhModelDoor::Event_Touch +//-------------------------------- +void hhModelDoor::Event_Touch( idEntity *other, trace_t* trace ) { + + if ( sndTrigger && trace->c.id == sndTrigger->GetId() ) { + if (other && other->IsType(hhPlayer::Type) && IsLocked() && gameLocal.time > nextSndTriggerTime) { + StartSound("snd_locked", SND_CHANNEL_ANY, 0, false, NULL ); + nextSndTriggerTime = gameLocal.time + 10000; + } + return; + } + + // Skip if locked, or noTouch + if( IsLocked() || noTouch ) { + // play locked sound + return; + } + + TryOpen( other ); +} + + +/* +================ +idModelDoor::GetActivator +================ +*/ +idEntity *hhModelDoor::GetActivator( void ) const { + return activatedBy.GetEntity(); +} + +/* +================ +hhModelDoor::ClientPredictionThink +================ +*/ +void hhModelDoor::ClientPredictionThink( void ) { + hhAnimatedEntity::ClientPredictionThink(); +} + +/* +================ +hhModelDoor::WriteToSnapshot +================ +*/ +void hhModelDoor::WriteToSnapshot( idBitMsgDelta &msg ) const { + hhAnimatedEntity::WriteToSnapshot(msg); + hhAnimator *animator = (hhAnimator *)GetAnimator(); + const idAnimBlend *anim = animator->CurrentAnim(ANIMCHANNEL_ALL); + if (anim) { + msg.WriteBits(1, 1); + msg.WriteBits(anim->AnimNum(), 32); + } + else { + msg.WriteBits(0, 1); + } + + msg.WriteBits(GetPhysics()->GetContents(), 32); +} + +/* +================ +hhModelDoor::ReadFromSnapshot +================ +*/ +void hhModelDoor::ReadFromSnapshot( const idBitMsgDelta &msg ) { + hhAnimatedEntity::ReadFromSnapshot(msg); + + bool hasAnim = !!msg.ReadBits(1); + if (hasAnim) { + int animNum = msg.ReadBits(32); + hhAnimator *animator = (hhAnimator *)GetAnimator(); + const idAnimBlend *anim = animator->CurrentAnim(ANIMCHANNEL_ALL); + if (!anim || anim->AnimNum() != animNum) { + animator->PlayAnim(ANIMCHANNEL_ALL, animNum, gameLocal.time, 500); + } + } + + int contents = msg.ReadBits(32); + if (contents != GetPhysics()->GetContents()) { + GetPhysics()->SetContents(contents); + } +} + + +//-------------------------------- +// hhModelDoor::Event_ToggleDoorState +//-------------------------------- +void hhModelDoor::Event_ToggleDoorState( void ) { + ToggleDoorState(); +} + +//-------------------------------- +// hhModelDoor::Event_OpenDoor +//-------------------------------- +void hhModelDoor::Event_OpenDoor() { + OpenDoor(); +} + +//-------------------------------- +// hhModelDoor::Event_CloseDoor +//-------------------------------- +void hhModelDoor::Event_CloseDoor() { + + // Added due to the fact that monsters which don't move, also don't ActivateTriggers. + // So now we check before we close if we can actually close + + // We can't actually close if are transitioning or if we have someone in our trigger that we care about + if ( bTransition || EntitiesInTrigger() ) { + // So if someone here, cancel any further events + CancelEvents( &EV_ModelDoorClose ); + // And try again in a bit. + PostEventMS( &EV_ModelDoorClose, wait * 1000 ); + return; + } + + CloseDoor(); +} + +//-------------------------------- +// hhModelDoor::Event_SetCallback +//-------------------------------- +void hhModelDoor::Event_SetCallback( void ) { + if ( !threadNum && bTransition ) { + threadNum = idThread::CurrentThreadNum(); + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +//-------------------------------- +// hhModelDoor::Event_SetBuddiesShaderParm +//-------------------------------- +void hhModelDoor::Event_SetBuddiesShaderParm( int parm, float value ) { + SetBuddiesShaderParm( parm, value ); +} diff --git a/src/Prey/game_modeldoor.h b/src/Prey/game_modeldoor.h new file mode 100644 index 0000000..b259cc8 --- /dev/null +++ b/src/Prey/game_modeldoor.h @@ -0,0 +1,139 @@ +#ifndef __GAME_MODELDOOR_H__ +#define __GAME_MODELDOOR_H__ + +extern const idEventDef EV_ModelDoorOpen; +extern const idEventDef EV_ModelDoorClose; + +extern const idEventDef EV_SetBuddiesShaderParm; + +class hhDoorTrigger; // Stupid C++ forward decl for hhModelDoor + +//-------------------------------- +// hhModelDoor +//-------------------------------- +class hhModelDoor : public hhAnimatedEntity { + +public: + CLASS_PROTOTYPE( hhModelDoor ); + + hhModelDoor(); + virtual ~hhModelDoor(); + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // Overridden methods + 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 ClientPredictionThink( void ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + void ToggleDoorState( void ); + void OpenDoor(); + void CloseDoor(); + void Lock( int f ); + bool IsLocked() const { return locked != 0; } + ID_INLINE bool IsOpen() const { return ( bOpen || bTransition ); } + ID_INLINE bool IsClosed() const { return ( !bOpen && !bTransition ); } + bool CanOpen() const; + bool CanClose() const; + void ForceAirLockTeamClosed(); + void ForceAirLockTeamOpen(); + const char* GetAirLockTeamName() const { return airlockTeamName.c_str(); } + hhModelDoor* GetAirLockMaster() const { return airlockMaster; } + idEntity * GetActivator() const; + + idLinkList airlockTeam; + +protected: + void SetBlocking( bool on ); + void ClosePortal( void ); + void OpenPortal( void ); + void InformDone(); + + void SetBuddiesShaderParm( int parm, float value ); + void ToggleBuddiesShaderParm( int parm, float firstValue, float secondValue, float toggleDelay ); + void WakeTouchingEntities(); + + idEntity* DetermineTeamMaster( const char* teamName ); + void JoinAirLockTeam( hhModelDoor *master ); + void VerifyAirlockTeamStatus(); + + void StartOpen(); + void StartClosed(); + void TryOpen( idEntity *whoTrying ); + bool EntitiesInTrigger(); + + void Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ); + void Event_PartBlocked( idEntity *blockingEntity ); + void Event_SpawnNewDoorTrigger( void ); + void Event_SetCallback( void ); + void Event_SetBuddiesShaderParm( int parm, float value ); + void Event_Touch( idEntity *other, trace_t* trace ); + void Event_Activate( idEntity *activator ); + void Event_ToggleDoorState( void ); + void Event_OpenDoor(); + void Event_CloseDoor(); + void Event_STATE_ClosedBegin(); + void Event_STATE_OpeningBegin(); + void Event_STATE_OpenBegin(); + void Event_STATE_ClosingBegin(); + +protected: + float damage; + float wait; + float triggersize; + qhandle_t areaPortal; // 0 = no portal + idList buddyNames; + int locked; + int openAnim; + int closeAnim; + int idleAnim; + int painAnim; + bool forcedOpen; // Is the door forced open + bool bOpenForMonsters; // Door opens for monsters + bool noTouch; // Can you touch this door. + bool bOpen; // HUMANHEAD mdl: True if door is open + bool bTransition; // HUMANHEAD mdl: True if door is in transition between opening and closing + bool bShuttleDoors; + int threadNum; // Thread used for sys.WaitFor() calls + hhDoorTrigger * doorTrigger; + idClipModel * sndTrigger; + float airLockSndWait; // Time in milliseconds between airlock sounds from spawnarg airlockwait + float nextAirLockSnd; // Next time to play an airlock locked door sound + int nextSndTriggerTime; // next time to play door locked sound + + idStr airlockTeamName; + hhModelDoor *airlockMaster; + + idBounds crusherBounds; + + idEntityPtr activatedBy; + + bool finishedSpawn; // Used to determine if we are still spawning in or not. + +}; + +class hhDoorTrigger : public idEntity { + CLASS_PROTOTYPE( hhDoorTrigger ); + +public: + void Disable() { enabled = false; } + void Enable() { enabled = true; } + bool IsEnabled() { return( enabled ); } + int GetEntitiesWithin( idEntity **ents, int entsLength ); + + hhModelDoor *door; + bool enabled; + +protected: + hhDoorTrigger(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Event_TriggerDoor( idEntity *other, trace_t *trace ); +}; + +#endif /* __GAME_MODELDOOR_H__ */ diff --git a/src/Prey/game_modeltoggle.cpp b/src/Prey/game_modeltoggle.cpp new file mode 100644 index 0000000..4e8937e --- /dev/null +++ b/src/Prey/game_modeltoggle.cpp @@ -0,0 +1,316 @@ +/*********************************************************************** + hhModelToggle + + Usage: + Set target to a hhViewedModel entity that will change model. + + operations: + next/prev model + next/prev anim + toggle rotation + toggle translation + toggle cycle + +***********************************************************************/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +// hhViewedModel ----------------------------------------------------------- + +CLASS_DECLARATION(hhAnimatedEntity, hhViewedModel) +END_CLASS + +void hhViewedModel::Spawn(void) { + + physicsObj.SetSelf(this); + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + SetPhysics( &physicsObj ); + + rotationAmount = 0; + BecomeActive(TH_THINK|TH_TICKER); +} + +void hhViewedModel::SetRotationAmount(float amount) { + rotationAmount = amount; +} + +void hhViewedModel::Ticker() { + idAngles ang; + + // update rotation + physicsObj.GetAngles( ang ); + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, gameLocal.msec, ang, idAngles( 0, rotationAmount * 360.0f / 60.0f, 0 ), ang_zero ); + + // update visuals so that the skeleton is drawn for non-rotating models + UpdateVisuals(); +} + + +// hhModelToggle ------------------------------------------------------------ + +const idEventDef EV_SetInitialAnims("", NULL); +const idEventDef EV_RequireTargets("", NULL); + +CLASS_DECLARATION(hhConsole, hhModelToggle) + EVENT( EV_SetInitialAnims, hhModelToggle::Event_SetInitialAnims ) + EVENT( EV_RequireTargets, hhModelToggle::Event_RequireTargets ) +END_CLASS + + +void hhModelToggle::Spawn(void) { + const idKeyValue *arg; + int i, num; + + bTranslation = false; + bRotation = false; + bCycle = true; + + // Retrieve list of definitions + defList.Clear(); + num = spawnArgs.GetNumKeyVals(); + for( i = 0; i < num; i++ ) { + arg = spawnArgs.GetKeyVal( i ); + if ( !arg->GetKey().Icmpn( "defentity", 9 ) ) { + defList.Append(arg->GetValue()); + } + } + + FillResourceList(); + BecomeActive(TH_THINK); + + PostEventMS(&EV_RequireTargets, 0); // Target list not built until after Spawn() + PostEventMS(&EV_SetInitialAnims, 0); // Can't set anims in Spawn() +} + +void hhModelToggle::FillResourceList() { + const idDict *dict; + ResourceSet set; + int i; + const idKeyValue *kv; + + for (i=0; iFindKey("model_view"); + if (!kv) { + kv = dict->FindKey("model"); + if (!kv) { + gameLocal.Warning("No model for %s", defList[i].c_str()); + continue; + } + } + + set.model = kv->GetValue(); + set.animList.Clear(); + set.animFileList.Clear(); + set.args = dict; + + const idDeclModelDef *modelDef = static_cast( declManager->FindType( DECL_MODELDEF, kv->GetValue(), false ) ); + if (modelDef) { + int num = modelDef->NumAnims(); + for (int i=1; iGetAnim(i); + idStr animName = anim->FullName(); + set.animList.Append(animName); + idStr animFileName = anim->MD5Anim(0)->Name(); + set.animFileList.Append(animFileName); + } + } + else { + kv = NULL; + while (1) { + kv = dict->MatchPrefix("anim ", kv); + if (!kv) { + break; + } + idStr animName = kv->GetKey().c_str() + 5; + set.animList.Append(animName); + set.animFileList.Append(kv->GetValue()); + } + } + + if (set.animList.Num() == 0) { + gameLocal.Warning("No anims for %s", defList[i].c_str()); + continue; + } + + resources.Append(set); + } +} + +void hhModelToggle::UpdateGUIValues() { + if (renderEntity.gui[0]) { + renderEntity.gui[0]->SetStateString("modelname", resources[currentModel].model.c_str()); + if (resources[currentModel].animList.Num() > 0) { + renderEntity.gui[0]->SetStateString("animname", resources[currentModel].animList[currentAnim].c_str()); + renderEntity.gui[0]->SetStateString("animfile", resources[currentModel].animFileList[currentAnim].c_str()); + } + renderEntity.gui[0]->SetStateInt("translation", bTranslation); + renderEntity.gui[0]->SetStateInt("rotation", bRotation); + renderEntity.gui[0]->SetStateInt("cycle", bCycle); + } +} + +void hhModelToggle::SetTargetsToModel(const char *modelname) { + for (int t=0; tSetModel(modelname); + } + } + + UpdateGUIValues(); +} + +void hhModelToggle::SetTargetsToAnim(const char *animname) { + for (int t=0; tGetAnimator()->GetAnim(animname); + + ent->GetAnimator()->ClearAllAnims( gameLocal.time, 0 ); + + if (bCycle) { + ent->GetAnimator()->CycleAnim(ANIMCHANNEL_ALL, anim, gameLocal.time, 0); + } + else { + ent->GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, anim, gameLocal.time, 0); + } + + ent->GetAnimator()->RemoveOriginOffset(!bTranslation); + } + } + + UpdateGUIValues(); +} + +void hhModelToggle::SetTargetsToRotate(bool bRotate) { + for (int t=0; tIsType(hhViewedModel::Type)) { + static_cast(ent)->SetRotationAmount(bRotate?5:0); + } + } + } + + UpdateGUIValues(); +} + + +void hhModelToggle::NextModel(void) { + if (resources.Num() > 0) { + currentModel = (currentModel + 1) % resources.Num(); + currentAnim = 0; + + SetTargetsToModel(resources[currentModel].model.c_str()); + SetTargetsToAnim(resources[currentModel].animList[currentAnim].c_str()); + } +} + +void hhModelToggle::PrevModel(void) { + if (resources.Num() > 0) { + currentModel = (currentModel - 1 + resources.Num()) % resources.Num(); + currentAnim = 0; + + SetTargetsToModel(resources[currentModel].model.c_str()); + SetTargetsToAnim(resources[currentModel].animList[currentAnim].c_str()); + } +} + +void hhModelToggle::NextAnim(void) { + int numanims = resources[currentModel].animList.Num(); + if (numanims > 0) { + currentAnim = (currentAnim + 1) % numanims; + SetTargetsToAnim(resources[currentModel].animList[currentAnim].c_str()); + } +} + +void hhModelToggle::PrevAnim(void) { + int numanims = resources[currentModel].animList.Num(); + if (numanims > 0) { + currentAnim = (currentAnim - 1 + numanims) % numanims; + SetTargetsToAnim(resources[currentModel].animList[currentAnim].c_str()); + } +} + +bool hhModelToggle::HandleSingleGuiCommand(idEntity *entityGui, idLexer *src) { + + idToken token; + + if (!src->ReadToken(&token)) { + return false; + } + + if (token == ";") { + return false; + } + + if (token.Icmp("nextmodel") == 0) { + NextModel(); + return true; + } + else if (token.Icmp("prevmodel") == 0) { + PrevModel(); + return true; + } + else if (token.Icmp("nextanim") == 0) { + NextAnim(); + return true; + } + else if (token.Icmp("prevanim") == 0) { + PrevAnim(); + return true; + } + else if (token.Icmp("togglerotation") == 0) { + // Instead, have gui call a script function and handle rotation with a mover + bRotation ^= 1; + SetTargetsToRotate(bRotation); + return true; + } + else if (token.Icmp("toggletranslation") == 0) { + bTranslation ^= 1; + SetTargetsToModel(resources[currentModel].model.c_str()); + SetTargetsToAnim(resources[currentModel].animList[currentAnim].c_str()); + return true; + } + else if (token.Icmp("togglecycle") == 0) { + bCycle ^= 1; + SetTargetsToModel(resources[currentModel].model.c_str()); + SetTargetsToAnim(resources[currentModel].animList[currentAnim].c_str()); + return true; + } + + src->UnreadToken(&token); + return false; +} + +void hhModelToggle::Event_SetInitialAnims() { + // Set to currentModel, currentAnim + currentModel = currentAnim = 0; + if (resources.Num() > 0) { + SetTargetsToModel(resources[currentModel].model.c_str()); + if (resources[currentModel].animList.Num() > 0) { + SetTargetsToAnim(resources[currentModel].animList[currentAnim].c_str()); + } + } +} + +void hhModelToggle::Event_RequireTargets() { + // Require a valid target: must be delayed after spawn because targets aren't build until then + if (!targets.Num()) { + gameLocal.Error( "ModelViewer requires a valid target." ); + PostEventMS(&EV_Remove, 0); + } +} + diff --git a/src/Prey/game_modeltoggle.h b/src/Prey/game_modeltoggle.h new file mode 100644 index 0000000..b20ccc1 --- /dev/null +++ b/src/Prey/game_modeltoggle.h @@ -0,0 +1,62 @@ +#ifndef __GAME_MODELTOGGLE_H__ +#define __GAME_MODELTOGGLE_H__ + +typedef struct ResourceSet_s{ + idStr model; + idList animList; + idList animFileList; + const idDict * args; +} ResourceSet; + + +class hhViewedModel : public hhAnimatedEntity { +public: + CLASS_PROTOTYPE( hhViewedModel ); + + void Spawn(); + void SetRotationAmount(float amount); + + idPhysics_Parametric physicsObj; + float rotationAmount; + +protected: + virtual void Ticker(); +}; + + +class hhModelToggle : public hhConsole { +public: + CLASS_PROTOTYPE( hhModelToggle ); + + void Spawn( void ); + virtual bool HandleSingleGuiCommand(idEntity *entityGui, idLexer *src); + +protected: + void NextModel(void); + void PrevModel(void); + void NextAnim(void); + void PrevAnim(void); + void SetTargetsToModel(const char *modelname); + void SetTargetsToAnim(const char *animname); + void FillResourceList(); + void UpdateGUIValues(); + void Update(); + void SetTargetsToRotate(bool bRotate); + + void Event_SetInitialAnims(); + void Event_RequireTargets(); + +protected: + idList defList; + idList resources; + + int currentModel; + int currentAnim; + bool bTranslation; + bool bRotation; + bool bCycle; + +}; + + +#endif /* __GAME_MODELTOGGLE_H__ */ diff --git a/src/Prey/game_monster_ai.cpp b/src/Prey/game_monster_ai.cpp new file mode 100644 index 0000000..2d8ed3a --- /dev/null +++ b/src/Prey/game_monster_ai.cpp @@ -0,0 +1,2156 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +//----------------------------------------------------- +// hhNoClipEnt +//----------------------------------------------------- +CLASS_DECLARATION(idEntity, hhNoClipEnt) +END_CLASS + +void hhNoClipEnt::Spawn( void ) { + if(GetPhysics()) + GetPhysics()->SetContents(0); +} + +//----------------------------------------------------- +// hhAINode +//----------------------------------------------------- +CLASS_DECLARATION( idEntity, hhAINode ) +END_CLASS + +hhAINode::hhAINode( void ) { + user.Clear(); +} + +void hhAINode::Save( idSaveGame *savefile ) const { + user.Save( savefile ); +} + +void hhAINode::Restore( idRestoreGame *savefile ) { + user.Restore( savefile ); +} + +//----------------------------------------------------- +// hhMonsterAI +//----------------------------------------------------- + +idList hhMonsterAI::allSimpleMonsters; + +hhMonsterAI::hhMonsterAI() { + lookOffset = ang_zero; + shootTarget = NULL; + bBossBar = false; + spawnThinkFlags = 0; + lastContactTime = gameLocal.time; + nextSpeechTime = gameLocal.time; + frozen = false; + soundOnModel = false; +} + +hhMonsterAI::~hhMonsterAI() { + allSimpleMonsters.Remove(this); + + if ( bBossBar && spawnArgs.GetBool( "remove_bar_on_dissolve" ) ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player && player->hud ) { + bBossBar = false; + player->hud->HandleNamedEvent("HideProgressBar"); + player->hud->StateChanged(gameLocal.time); + } + } +} + +void hhMonsterAI::Spawn() { + AI_HAS_RANGE_ATTACK = spawnArgs.GetBool("has_range_attack", "0"); + AI_HAS_MELEE_ATTACK = spawnArgs.GetBool("has_melee_attack", "0"); + + targetReaction.reactionIndex = -1; + targetReaction.entity = NULL; + + allSimpleMonsters.AddUnique(this); + bBindAxis = spawnArgs.GetBool( "bind_axis" ); + bCanFall = spawnArgs.GetBool( "can_fall", "0" ); + bSeeThroughPortals = spawnArgs.GetBool( "can_see_portals", "0" ); + bBindOrient = spawnArgs.GetBool( "bind_orient", "0" ); + hearingRange = spawnArgs.GetInt( "hearing_range", "1024" ); + bCanWallwalk = spawnArgs.GetBool( "can_wallwalk", "0" ); + fallDelay = spawnArgs.GetInt( "fall_delay" ); + bOverrideKilledByGravityZones = spawnArgs.GetBool( "overrideKilledByGravityZones" ); + bNeverTarget = spawnArgs.GetBool( "never_target", "0" ); + bNoCombat = spawnArgs.GetBool( "no_combat", "0" ); + const char *temp; + if ( spawnArgs.GetString( "fx_custom_blood", "", &temp ) ) { + bCustomBlood = true; + } else { + bCustomBlood = false; + } + + nextSpiritProxyCheck = 0; + nextTurnUpdate = 0; + frozen = false; + + //handle initial rotation and sticking on wallwalk + if ( !IsHidden() ) { + PostEventSec( &MA_InitialWallwalk, 0.1f ); + PostEventMS(&MA_EnemyOnSpawn, 10); + } + PostEventMS(&EV_PostSpawn, 0); + if ( spawnArgs.GetInt( "wander_radius" ) ) { + spawnOrigin = GetOrigin(); + } + + // CJR: Clear the DDA values used for tracking damage inflicted on the player + totalDDADamage = 0; +} + +void hhMonsterAI::Event_PostSpawn() { + CreateHealthTriggers(); + + //TODO: Move to character/girlfriend code when it exists + // Spawn earrings for girlfriend + idEntity *ent; + const char *defName = spawnArgs.GetString("def_earring", NULL); + if (defName && defName[0] && head.IsValid()) { + const char *boneNameL = spawnArgs.GetString("earringboneL"); + const char *boneNameR = spawnArgs.GetString("earringboneR"); + idDict args; + args.Clear(); + args.Set( "origin", GetPhysics()->GetOrigin().ToString() ); + + ent = gameLocal.SpawnObject(defName, &args); + if (ent) { + ent->MoveToJoint(head.GetEntity(), boneNameL); + ent->BindToJoint(head.GetEntity(), boneNameL, false); + } + + ent = gameLocal.SpawnObject(defName, &args); + if (ent) { + ent->MoveToJoint(head.GetEntity(), boneNameR); + ent->BindToJoint(head.GetEntity(), boneNameR, false); + } + } +} + + +bool hhMonsterAI::HasPathTo(const idVec3 &destPt) { + if(!GetAAS()) // || !allowAAS) //TODO: support the allowAAS functionality + return FALSE; + + idVec3 fromPos = GetPhysics()->GetOrigin(); + + int currAreaNum = PointReachableAreaNum(fromPos); + int toAreaNum = PointReachableAreaNum(destPt); + + + // Invalid #'s ? + if(toAreaNum == 0 || currAreaNum == 0) + return FALSE; + + aasPath_t path; + return PathToGoal(path, currAreaNum, fromPos, toAreaNum, destPt); +} + +/* +============================== +hhMonsterAI::LinkScriptVariables(void) +============================== +*/ +#define LinkScriptVariable( name ) name.LinkTo( scriptObject, #name ) +void hhMonsterAI::LinkScriptVariables(void) { + + idAI::LinkScriptVariables(); + + LinkScriptVariable( AI_HAS_RANGE_ATTACK ); // required for reaction system + LinkScriptVariable( AI_HAS_MELEE_ATTACK ); // required for reaction system + LinkScriptVariable( AI_USING_REACTION ); // TRUE if monster is currently using reaction + LinkScriptVariable( AI_REACTION_FAILED ); // TRUE if the last reaction attempt failed (path blocked, exclusive problem, etc) + LinkScriptVariable( AI_REACTION_ANIM ); + LinkScriptVariable( AI_BACKWARD ); + LinkScriptVariable( AI_STRAFE_LEFT ); + LinkScriptVariable( AI_STRAFE_RIGHT ); + LinkScriptVariable( AI_UPWARD ); + LinkScriptVariable( AI_DOWNWARD ); + LinkScriptVariable( AI_SHUTTLE_DOCKED ); + LinkScriptVariable( AI_VEHICLE_ATTACK ); + LinkScriptVariable( AI_VEHICLE_ALT_ATTACK ); + LinkScriptVariable( AI_WALLWALK ); + LinkScriptVariable( AI_FALLING ); + LinkScriptVariable( AI_PATHING ); + LinkScriptVariable( AI_TURN_DIR ); + LinkScriptVariable( AI_FLY_NO_SEEK ); + LinkScriptVariable( AI_FOLLOWING_PATH ); +} + +void hhMonsterAI::Think( void ) { + PROFILE_SCOPE("AI", PROFMASK_NORMAL|PROFMASK_AI); + if (ai_skipThink.GetBool()) { //HUMANHEAD rww + return; + } + + idVec3 oldOrigin = physicsObj.GetOrigin(); + idVec3 oldVelocity = physicsObj.GetLinearVelocity(); + + if ( thinkFlags & TH_THINK ) { + + // Update vehicle guns + if(AI_VEHICLE && InVehicle()) { + usercmd_t cmds; + const signed char speed = 64; // In range [0..127] + memset( &cmds, 0, sizeof(usercmd_t) ); + cmds.buttons |= (AI_VEHICLE_ATTACK) ? BUTTON_ATTACK : 0; + cmds.buttons |= (AI_VEHICLE_ALT_ATTACK) ? BUTTON_ATTACK_ALT : 0; + + if(AI_FORWARD) { + cmds.forwardmove = speed; + } else if(AI_BACKWARD) { + cmds.forwardmove = -speed; + } + + if(AI_STRAFE_RIGHT) { + cmds.rightmove = speed; + } else if(AI_STRAFE_LEFT) { + cmds.rightmove = -speed; + } + + if(AI_UPWARD) { + cmds.upmove = speed; + } else if(AI_DOWNWARD) { + cmds.upmove = -speed; + } + + GetVehicleInterfaceLocal()->BufferPilotCmds( &cmds, NULL ); + AI_SHUTTLE_DOCKED = GetVehicleInterfaceLocal()->IsVehicleDocked(); + } + + // clear out the enemy when he dies or is hidden + idActor *enemyEnt = enemy.GetEntity(); + if ( enemyEnt ) { + if ( enemyEnt->IsType( hhSpiritProxy::Type ) ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player && player->AI_DEAD ) { + EnemyDead(); + } + } else if ( enemyEnt->health <= 0 ) { + EnemyDead(); + } + } + + //HUMANHEAD: aob - vehicle updates our viewAxis + if( !InVehicle() ) { + current_yaw += deltaViewAngles.yaw; + ideal_yaw = idMath::AngleNormalize180( ideal_yaw + deltaViewAngles.yaw ); + deltaViewAngles.Zero(); + viewAxis = idAngles( 0, current_yaw, 0 ).ToMat3(); + + // Determine turn dir + if (gameLocal.time > nextTurnUpdate) { + AI_TURN_DIR = GetTurnDir(); + nextTurnUpdate = gameLocal.time + 250; + } + } + //HUMANHEAD END + + if ( num_cinematics ) { + if ( !IsHidden() && torsoAnim.AnimDone( 0 ) ) { + PlayCinematic(); + } + RunPhysics(); + } else if ( !allowHiddenMovement && IsHidden() ) { + // hidden monsters + UpdateAIScript(); + // HUMANHEAD pdm: Vehicle support + } else if ( InVehicle() ) { + UpdateEnemyPosition(); + UpdateAIScript(); + FlyMove(); + // HUMANHEAD END + + } else { + // clear the ik before we do anything else so the skeleton doesn't get updated twice + walkIK.ClearJointMods(); + + // HUMANHEAD NLA + physicsObj.ResetNumTouchEnt(0); + // HUMANHEAD END + switch( move.moveType ) { + case MOVETYPE_DEAD : + // dead monsters + UpdateAIScript(); + DeadMove(); + break; + + case MOVETYPE_FLY : + // flying monsters + UpdateEnemyPosition(); + UpdateAIScript(); + FlyMove(); + PlayChatter(); + CheckBlink(); + break; + + case MOVETYPE_STATIC : + // static monsters + UpdateEnemyPosition(); + UpdateAIScript(); + StaticMove(); + PlayChatter(); + CheckBlink(); + break; + + case MOVETYPE_ANIM : + // animation based movement + UpdateEnemyPosition(); + UpdateAIScript(); + AnimMove(); + PlayChatter(); + CheckBlink(); + break; + + case MOVETYPE_SLIDE : + // velocity based movement + UpdateEnemyPosition(); + UpdateAIScript(); + SlideMove(); + PlayChatter(); + CheckBlink(); + break; + } + // HUMANHEAD NLA + ClientImpacts(); + // HUMANHEAD END + } + + // clear pain flag so that we recieve any damage between now and the next time we run the script + AI_PAIN = false; + AI_SPECIAL_DAMAGE = 0; + AI_PUSHED = false; + } else if ( thinkFlags & TH_PHYSICS ) { + RunPhysics(); + } + + // HUMANHEAD jrm - need to call ticker function per aaron + if (thinkFlags & TH_TICKER) { + Ticker(); + } + + if ( af_push_moveables ) { + PushWithAF(); + } + + if ( fl.hidden && allowHiddenMovement ) { + // UpdateAnimation won't call frame commands when hidden, so call them here when we allow hidden movement + animator.ServiceAnims( gameLocal.previousTime, gameLocal.time ); + } + + UpdateMuzzleFlash(); + UpdateAnimation(); + UpdateParticles(); + UpdateWounds(); + Present(); + UpdateDamageEffects(); + LinkCombat(); + + CrashLand( oldOrigin, oldVelocity ); + + if(health > 0) { + idStr tmp; + if(ai_showNoAAS.GetBool() && spawnArgs.GetString("use_aas", "", tmp) && spawnArgs.GetBool("noaas_warning","1")) { + if(!aas) { + gameRenderWorld->DrawText("?", this->GetEyePosition() + idVec3(0.0f, 0.0f, 12.0f), 0.75f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3()); + } + } + } + + if ( !AI_DEAD && bCanFall && !IsHidden() && !fl.isTractored ) { + if ( physicsObj.HasContacts() || physicsObj.GetLinearVelocity().LengthSqr() < 4 ) { + lastContactTime = gameLocal.time; + if ( AI_FALLING ) { + AI_FALLING = false; + bCanFall = false; + SetState( GetScriptFunction( "state_Idle" ) ); + SetWaitState( "" ); + } + } else if ( !AI_FALLING && !af.IsActive() && physicsObj.GetLinearVelocity().LengthSqr() > 0 ) { + if ( gameLocal.time - lastContactTime > fallDelay ) { + AI_FALLING = true; + SetState( GetScriptFunction( "state_Nothing" ) ); + Event_AnimState(ANIMCHANNEL_TORSO, "Torso_Fall", 4); + Event_AnimState(ANIMCHANNEL_LEGS, "Legs_Fall", 4); + } + } + } + + //update wallwalk + if ( bCanWallwalk && !fl.isTractored ) { + trace_t TraceInfo; + gameLocal.clip.TracePoint(TraceInfo, GetOrigin(), GetOrigin() + this->GetPhysics()->GetGravityNormal() * 50, GetPhysics()->GetClipMask(), this); + if( TraceInfo.fraction < 1.0f && gameLocal.GetMatterType(TraceInfo, NULL) == SURFTYPE_WALLWALK ) { + SetGravity( -TraceInfo.c.normal * DEFAULT_GRAVITY ); + AI_WALLWALK = true; + } else if ( AI_WALLWALK ) { + SetGravity( idVec3(0,0,-DEFAULT_GRAVITY) ); + AI_WALLWALK = false; + } + } else if ( AI_WALLWALK ) { + SetGravity( idVec3(0,0,-DEFAULT_GRAVITY) ); + AI_WALLWALK = false; + } + + if ( bBossBar ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player && player->hud ) { + player->hud->SetStateFloat( "progress", idMath::ClampFloat( 0.0f, 1.0f, float(health) / (float)spawnHealth ) ); + player->hud->StateChanged(gameLocal.time); + player->hud->Redraw( gameLocal.realClientTime ); + } + } + + if( ai_debugBrain.GetInteger() > 0 && !IsHidden() ) { + if ( enemy.IsValid() && enemy->GetHealth() > 0 ) { + gameRenderWorld->DebugArrow( colorWhite, GetOrigin(), enemy->GetOrigin(), 10 ); + float dist = ( GetPhysics()->GetOrigin() - enemy->GetPhysics()->GetOrigin() ).LengthFast(); + gameRenderWorld->DrawText( va("%i", int(dist)), this->GetEyePosition() + idVec3(0.0f, 0.0f, 60.0f), 0.75f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3()); + } + if ( state ) { + if ( physicsObj.GetClipMask() == 0 ) { + gameRenderWorld->DrawText(state->Name(), GetEyePosition() + idVec3(0.0f, 0.0f, 40.0f), 0.75f, colorRed, gameLocal.GetLocalPlayer()->viewAngles.ToMat3()); + } else { + gameRenderWorld->DrawText(state->Name(), GetEyePosition() + idVec3(0.0f, 0.0f, 40.0f), 0.75f, colorGreen, gameLocal.GetLocalPlayer()->viewAngles.ToMat3()); + } + } + gameRenderWorld->DrawText(torsoAnim.state, this->GetEyePosition() + idVec3(0.0f, 0.0f, 20.0f), 0.75f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3()); + gameRenderWorld->DrawText(legsAnim.state, this->GetEyePosition() + idVec3(0.0f, 0.0f, 0.0f), 0.75f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3()); + if ( !walkIK.IsActivated() ) { + gameRenderWorld->DrawText("ik disabled", GetEyePosition() + idVec3(0.0f, 0.0f, -20.0f), 0.75f, colorRed, gameLocal.GetLocalPlayer()->viewAngles.ToMat3()); + } + } +} + +void hhMonsterAI::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + //we don't want monsters pushed around by the player, projectiles, or splashDamange, so early out if they are. + if( !af.IsActive() ) { + if ( ent && (ent->IsType( hhProjectile::Type ) || ent->IsType( idWorldspawn::Type ) || ent->IsType( idPlayer::Type )) ) { + return; + } + } + + af.GetPhysics()->UpdateTime( gameLocal.GetTime() ); + BecomeActive(TH_THINK); + + idAI::ApplyImpulse( ent, id, point, force ); +} + +// +// hhMonsterAI::CheckValidReaction +// Helper function for using reactions. +// +bool hhMonsterAI::CheckValidReaction() { + hhReaction *reaction = targetReaction.GetReaction(); + if( !reaction || !reaction->causeEntity.IsValid() ) { + return false; + } + return true; +} + +// +// hhMonsterAI::FinishReaction +// Called once a reaction has been used/failed. If it failed, set the failed flag and exit. +// Otherwise, set any 'finish_key' we have. +// +void hhMonsterAI::FinishReaction( bool bFailed ) { + AI_USING_REACTION = false; + if( bFailed ) { + AI_REACTION_FAILED = true; + } + else { + hhReaction *reaction = targetReaction.GetReaction(); + if( reaction && reaction->desc->finish_key.Length() ) { + spawnArgs.Set( reaction->desc->finish_key.c_str(), reaction->desc->finish_val.c_str() ); + } + } + + //monster is finished with this reaction. let others use it + if ( targetReaction.entity.IsValid() ) { + targetReaction.entity->spawnArgs.Set( "react_inuse", "0" ); + } +} + +void hhMonsterAI::Killed(idEntity *inflictor, idEntity *attacker, + int damage, const idVec3 &dir, int location ) +{ + if ( AI_DEAD ) { + AI_DAMAGE = true; + return; + } + + if ( bBossBar && spawnArgs.GetBool( "remove_bar_on_death" ) ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player && player->hud ) { + bBossBar = false; + player->hud->HandleNamedEvent("HideProgressBar"); + player->hud->StateChanged(gameLocal.time); + } + } + + HandleNoGore(); + + idAI::Killed(inflictor, attacker, damage, dir, location); + + fl.noPortal = 0; // CJR: Set so that killed monsters never collide with portals + + SendDamageToDDA(); // CJR DDA: Send any accumulated damage to the dda system + + // General non-item dropping (for monsters, souls, etc.) + const idKeyValue *kv = NULL; + kv = spawnArgs.MatchPrefix( "def_drops", NULL ); + while ( kv ) { + + idStr drops = kv->GetValue(); + idDict args; + + idStr last5 = kv->GetKey().Right(5); + if ( drops.Length() && idStr::Icmp( last5, "Joint" ) != 0) { + + args.Set( "classname", drops ); + + // HUMANHEAD pdm: specify monster so souls can call back to remove body when picked up + args.Set("monsterSpawnedBy", name.c_str()); + + idVec3 origin; + idMat3 axis; + idStr jointKey = kv->GetKey() + idStr("Joint"); + idStr jointName = spawnArgs.GetString( jointKey ); + idStr joint2JointKey = kv->GetKey() + idStr("Joint2Joint"); + idStr j2jName = spawnArgs.GetString( joint2JointKey ); + + idEntity *newEnt = NULL; + gameLocal.SpawnEntityDef( args, &newEnt ); + HH_ASSERT(newEnt != NULL); + + // Spin to correct heading + if(newEnt->IsType(hhMonsterAI::Type)) { + hhMonsterAI *newAI = static_cast(newEnt); + newAI->current_yaw = current_yaw; + newAI->ideal_yaw = ideal_yaw; + } + + if(jointName.Length()) { + jointHandle_t joint = GetAnimator()->GetJointHandle( jointName ); + if (!GetAnimator()->GetJointTransform( joint, gameLocal.time, origin, axis ) ) { + gameLocal.Printf( "%s refers to invalid joint '%s' on entity '%s'\n", (const char*)jointKey.c_str(), (const char*)jointName, (const char*)name ); + origin = renderEntity.origin; + axis = renderEntity.axis; + } + axis *= renderEntity.axis; + origin = renderEntity.origin + origin * renderEntity.axis; + newEnt->SetAxis(axis); + newEnt->SetOrigin(origin); + } + else { + + newEnt->SetAxis(viewAxis); + newEnt->SetOrigin(GetOrigin()); + } + + } + + kv = spawnArgs.MatchPrefix( "def_drops", kv ); + } +} + +void hhMonsterAI::Event_Remove( void ) { + if( InVehicle() ) { + GetVehicleInterface()->GetVehicle()->EjectPilot(); + } + + idAI::Event_Remove(); +} + +void hhMonsterAI::EnterVehicle( hhVehicle* vehicle ) { + if (!vehicle->WillAcceptPilot(this)) { + return; + } + spawnArgs.Set( "use_aas", spawnArgs.GetString( "aas_shuttle", "aasDroid" ) ); + SetAAS(); + idAI::EnterVehicle( vehicle ); + PostEventSec( &MA_SetVehicleState, 0.1f ); +} + +bool hhMonsterAI::TestMelee( void ) const { + trace_t trace; + idActor *enemyEnt = enemy.GetEntity(); + + if ( !enemyEnt || !melee_range ) { + return false; + } + + //FIXME: make work with gravity vector + idVec3 org = physicsObj.GetOrigin(); + const idBounds &myBounds = physicsObj.GetBounds(); + idBounds bounds; + + idBounds meleeBounds( spawnArgs.GetVector( "melee_boundmin", "0 0 0" ), spawnArgs.GetVector( "melee_boundmax", "0 0 0" ) ); + if ( meleeBounds != bounds_zero ) { + //check custom rotated meleebound + idBox meleeBox( meleeBounds, org, renderEntity.axis ); + idBounds enemyBounds = enemyEnt->GetPhysics()->GetBounds(); + enemyBounds.TranslateSelf( enemyEnt->GetPhysics()->GetOrigin() ); + idBox enemyBox( enemyBounds ); + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugBox( colorYellow, meleeBox ); + } + + if ( !enemyBox.IntersectsBox( meleeBox ) ) { + return false; + } + } else { + // expand the bounds out by our melee range + bounds[0][0] = -melee_range; + bounds[0][1] = -melee_range; + bounds[0][2] = myBounds[0][2] - 4.0f; + bounds[1][0] = melee_range; + bounds[1][1] = melee_range; + 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 ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugBounds( colorYellow, bounds, vec3_zero, gameLocal.msec ); + } + + if ( !bounds.IntersectsBounds( enemyBounds ) ) { + return false; + } + } + + idVec3 start = GetEyePosition(); + idVec3 end = enemyEnt->GetEyePosition(); + + gameLocal.clip.TracePoint( trace, start, end, MASK_SHOT_BOUNDINGBOX, this ); + if ( ( trace.fraction == 1.0f ) || ( gameLocal.GetTraceEntity( trace ) == enemyEnt ) ) { + return true; + } + + return false; +} + +bool hhMonsterAI::MoveToPosition( const idVec3 &pos, bool enemyBlocks ) { + if(AI_VEHICLE && InVehicle()) { + GetVehicleInterfaceLocal()->ThrustTowards( pos, 1.0f ); + return true; + } else { + return idAI::MoveToPosition( pos, enemyBlocks ); + } +} + +bool hhMonsterAI::TurnToward( const idVec3 &pos ) { + if (AI_VEHICLE && InVehicle()) { + GetVehicleInterfaceLocal()->OrientTowards( pos, 0.5 ); + return true; + } else { + return idAI::TurnToward( pos ); + } +} + +bool hhMonsterAI::CanSee( idEntity *ent, bool useFov ) { + trace_t tr; + idVec3 eye; + idVec3 toPos; + + if ( ent->IsHidden() ) { + return false; + } + + if ( ent->IsType( idActor::Type ) ) { + idActor *act = static_cast(ent); + + // If this actor is in a vehicle, look at the vehicle, not the actor + if(act->InVehicle()) { + ent = act->GetVehicleInterface()->GetVehicle(); + } + } + + if ( ent->IsType( idActor::Type ) ) { + toPos = ( ( idActor * )ent )->GetEyePosition(); + } else { + toPos = ent->GetPhysics()->GetOrigin(); + } + + if ( useFov && !CheckFOV( toPos ) ) { + return false; + } + + eye = GetEyePosition(); + + if ( InVehicle() ) { + gameLocal.clip.TracePoint( tr, eye, toPos, MASK_SHOT_BOUNDINGBOX, GetVehicleInterface()->GetVehicle() ); // HUMANHEAD JRM + if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == ent ) ) { + return true; + } + } else { + gameLocal.clip.TracePoint( tr, eye, toPos, MASK_SHOT_BOUNDINGBOX, this ); // HUMANHEAD JRM + if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == ent ) ) { + return true; + } else if ( bSeeThroughPortals && aas ) { + shootTarget = NULL; + int myArea = gameRenderWorld->PointInArea( GetOrigin() ); + int numPortals = gameRenderWorld->NumGamePortalsInArea( myArea ); + if ( numPortals > 0 ) { + int enemyArea = gameRenderWorld->PointInArea( ent->GetOrigin() ); + for ( int i=0;iGetSoundPortal( myArea, i ).areas[0] == enemyArea ) { + //find the portal and set it as this monster's shoottarget + idEntity *spawnedEnt = NULL; + for( spawnedEnt = gameLocal.spawnedEntities.Next(); spawnedEnt != NULL; spawnedEnt = spawnedEnt->spawnNode.Next() ) { + if ( !spawnedEnt->IsType( hhPortal::Type ) ) { + continue; + } + if ( gameRenderWorld->PointInArea( spawnedEnt->GetOrigin() ) == myArea) { + shootTarget = spawnedEnt; + return true; + } + } + } + } + } + } + } + + return false; +} + +idPlayer* hhMonsterAI::GetClosestPlayer(void) { + idEntity *closestEnt = NULL; + float closestDist = idMath::INFINITY; + + for(int i=0;iIsType(idPlayer::Type)); + + float l = (ent->GetOrigin()-GetOrigin()).Length(); + if(l < closestDist || !closestEnt) { + closestDist = l; + closestEnt = ent; + } + } + } + + return static_cast(closestEnt); +} + +void hhMonsterAI::Show() { + if ( spawnThinkFlags != 0 ) { + int temp = spawnThinkFlags; + spawnThinkFlags = 0; + BecomeActive( temp ); + } + idAI::Show(); + PostEventMS(&MA_EnemyOnSpawn, 10); + Event_InitialWallwalk(); +} + +bool hhMonsterAI::GetFacePosAngle( const idVec3 &pos, float &delta ) { + float diff; + float angle1; + float angle2; + idVec3 sourceOrigin; + idVec3 targetOrigin; + + sourceOrigin = GetPhysics()->GetOrigin(); + targetOrigin = pos;//target->GetPhysics()->GetOrigin(); + + angle1 = DEG2RAD( GetGravViewAxis()[0].ToYaw() ); // VIEWAXIS_TO_GETGRAVVIEWAXIS + angle2 = hhUtils::PointToAngle( targetOrigin.x - sourceOrigin.x, targetOrigin.y - sourceOrigin.y ); + if(angle2 > angle1) { + diff = angle2 - angle1; + + if( diff > DEG2RAD(180.0f) ) { + delta = DEG2RAD(359.9f) - diff; + return false; + } + else { + delta = diff; + return true; + } + } + else { + diff = angle1 - angle2; + if( diff > DEG2RAD(180.0f) ) { + delta = DEG2RAD(359.9f) - diff; + return true; + } + else { + delta = diff; + return false; + } + } +} + +void hhMonsterAI::Distracted( idActor *newEnemy ) { + SetEnemy( newEnemy ); +} + +void hhMonsterAI::SetEnemy( idActor *newEnemy ) { + idAI::SetEnemy( newEnemy ); +} + +idVec3 hhMonsterAI::GetTouchPos(idEntity *ent, const hhReactionDesc *desc ) { + assert(ent != NULL); + assert(desc != NULL); + + idVec3 pos = ent->GetOrigin(); + + idVec3 offset = desc->touchOffsets.GetVector("all", "0 0 0"); + if ( offset == vec3_zero ) { + // Each monster def type has its own offset etc. + // touchoffset_monster_hunter + offset = desc->touchOffsets.GetVector(GetEntityDefName(), "0 0 0"); + } + idStr touchDirType = desc->touchDir; + + // When touching this ent, move to it as if it were 'cover' + if( touchDirType == idStr("cover") && enemy.IsValid() ) { + idVec3 goalPos = ent->GetOrigin() + offset.x * (ent->GetOrigin() - GetEnemy()->GetOrigin()).ToNormal(); + goalPos.z += offset.z; + return goalPos; + } + // Move directly toward this ent from our pos + else if(touchDirType == idStr("direct")) { + idVec3 dir = GetOrigin() - pos; + dir.Normalize(); + offset *= dir.ToMat3(); + return pos + offset; + } + else if(touchDirType == idStr("object")) { + ent->GetFloorPos( 64.f, pos ); + idVec3 dir = GetOrigin() - pos; + dir.Normalize(); + return pos + (dir * offset.x); + } + else { + offset *= ent->GetAxis(); + return pos + offset; + } + + +} + +bool hhMonsterAI::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + if ( af.IsActive() ) { + af.GetPhysicsToVisualTransform( origin, axis ); + return true; + } + origin = modelOffset; + if ( GetBindMaster() && bindJoint != INVALID_JOINT ) { + idMat3 masterAxis; + idVec3 masterOrigin; + GetMasterPosition( masterOrigin, masterAxis ); + axis = physicsObj.localAxis * masterAxis; + origin = masterOrigin + physicsObj.GetLocalOrigin() * masterAxis; + } else if ( ( InVehicle() || bBindOrient ) && GetBindMaster() ) { + axis = GetBindMaster()->GetAxis(); + } else { + axis = viewAxis; + } + return true; +} + +void hhMonsterAI::UpdateModelTransform( void ) { + idVec3 origin; + idMat3 axis; + + if ( GetPhysicsToVisualTransform( origin, axis ) ) { + if ( bBindAxis && GetBindMaster() ) { + renderEntity.axis = GetBindMaster() ->GetAxis(); + } else { + renderEntity.axis = axis * GetPhysics()->GetAxis(); + } + if ( GetBindMaster() && bindJoint != INVALID_JOINT ) { + if ( head.IsValid() && head->GetPhysics() ) { + head->GetPhysics()->Evaluate(gameLocal.time-gameLocal.previousTime, gameLocal.time); + } + renderEntity.origin = origin; + } else { + if ( GetBindMaster() && head.IsValid() && head->GetPhysics() ) { + head->GetPhysics()->Evaluate(gameLocal.time-gameLocal.previousTime, gameLocal.time); + } + renderEntity.origin = GetPhysics()->GetOrigin() + origin * renderEntity.axis; + } + } else { + renderEntity.axis = GetPhysics()->GetAxis(); + renderEntity.origin = GetPhysics()->GetOrigin(); + } +} + +void hhMonsterAI::UpdateFromPhysics( bool moveBack ) { + // set master delta angles for actors + if ( GetBindMaster() ) { + if( !InVehicle() ) { + idAngles delta = GetDeltaViewAngles(); + if ( moveBack ) { + delta.yaw -= physicsObj.GetMasterDeltaYaw(); + } else { + delta.yaw += physicsObj.GetMasterDeltaYaw(); + } + + SetDeltaViewAngles( delta ); + } else { + SetAxis( GetBindMaster()->GetAxis() ); + } + } + + if ( UpdateAnimationControllers() ) { + BecomeActive( TH_ANIMATE ); + } + + UpdateVisuals(); +} + +void hhMonsterAI::CreateHealthTriggers() { + const char keyPrefix[] = "health_percent_trigger_"; + int keyPrefixLen = strlen(keyPrefix); + + const idKeyValue *kv = spawnArgs.MatchPrefix(keyPrefix, NULL); + healthTriggers.Clear(); + while(kv) { + hhMonsterHealthTrigger t; + idStr k = kv->GetKey(); + idStr perct = k.Right(k.Length() - strlen(keyPrefix)); + float p = float(atoi(perct.c_str())) * 0.01f; + t.healthThresh = int(float(health) * p); + t.triggerEnt = gameLocal.FindEntity(kv->GetValue().c_str()); + if(!t.triggerEnt.GetEntity()) { + gameLocal.Warning("%s specified %s key with entity \"%s\", but that entity does not exist!", name.c_str(), kv->GetKey().c_str(), kv->GetValue().c_str()); + } + healthTriggers.Append(t); + kv = spawnArgs.MatchPrefix(keyPrefix, kv); + } +} + +void hhMonsterAI::UpdateHealthTriggers(int oldHealth, int currHealth) { + + // Trigger them + for(int i=0;iProcessEvent(&EV_Activate, this); + healthTriggers[i].triggerCount++; + } + } + } +} + +// +// Damage() +// +void hhMonsterAI::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + // Return if we don't take damage + if ( !fl.takedamage ) { + return; + } + + if ( spawnArgs.GetBool( "noPlayerDamage", "0" ) ) { + if ( attacker && attacker->IsType( idPlayer::Type ) ) { + return; + } + } + + int oldHealth = health; + idAI::Damage(inflictor, attacker, dir, damageDefName, damageScale, location); + UpdateHealthTriggers(oldHealth, health); + + if ( bCustomBlood && inflictor ) { + hhFxInfo fxInfo; + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfoPrefixed( "fx_custom_blood", inflictor->GetOrigin(), mat3_identity, &fxInfo ); + } + + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); + if ( AI_DEAD && damageDef ) { + if ( damageDef->GetBool( "ice" ) && spawnArgs.GetBool( "can_freeze", "0" ) ) { + SetSkinByName( spawnArgs.GetString( "skin_freeze" ) ); + + if( af.IsLoaded() ) { + af.GetPhysics()->SetSelfCollision(false); + af.GetPhysics()->SetContactFrictionScale(0); + af.GetPhysics()->SetTimeScaleRamp(0,2); + + for(int i=0; i < af.GetPhysics()->GetNumConstraints(); i++) { + idAFConstraint* constraint = af.GetPhysics()->GetConstraint(i); + switch( constraint->GetType() ) { + case CONSTRAINT_BALLANDSOCKETJOINT: + static_cast(constraint)->SetFriction(200.0f); + static_cast(constraint)->SetNoLimit(); + break; + case CONSTRAINT_UNIVERSALJOINT: + static_cast(constraint)->SetFriction(200.0f); + static_cast(constraint)->SetNoLimit(); + break; + case CONSTRAINT_HINGE: + static_cast(constraint)->SetFriction(200.0f); + static_cast(constraint)->SetNoLimit(); + break; + } + } + } + + spawnArgs.Set("fx_deatheffect", spawnArgs.GetString( "fx_ice" )); + spawnArgs.Set("produces_splats", "0"); + if( modelDefHandle > 0 ) + gameRenderWorld->RemoveDecals( modelDefHandle ); + //SetDeformation(DEFORMTYPE_DEATHEFFECT, gameLocal.time + 5000, 12000); + SetShaderParm(SHADERPARM_TIME_OF_DEATH, MS2SEC(gameLocal.time+100000)); + CancelEvents( &EV_Dispose ); + PostEventSec( &EV_Dispose, 5 ); + } + if ( damageDef->GetBool( "burn" ) && !spawnArgs.GetBool( "no_burn" ) ) { + SetSkinByName( spawnArgs.GetString( "skin_burn" ) ); + spawnArgs.Set("fx_deatheffect", spawnArgs.GetString( "fx_burn" )); + if( modelDefHandle > 0 ) + gameRenderWorld->RemoveDecals( modelDefHandle ); + CancelEvents( &EV_Dispose ); + PostEventSec( &EV_Dispose, 0 ); + } + if ( damageDef->GetBool( "acid" ) && spawnArgs.GetBool( "acidburn" ) ) { + SetSkinByName( spawnArgs.GetString( "skin_acidburn" ) ); + SetDeformation(DEFORMTYPE_DEATHEFFECT, gameLocal.time + 2500, 8000); // starttime, duration + PostEventSec( &EV_StartSound, 1.5f, "snd_acid", SND_CHANNEL_ANY, 1 ); + spawnArgs.Set("fx_deatheffect", spawnArgs.GetString( "fx_acid" )); + spawnArgs.Set("mtr_splat1", spawnArgs.GetString( "mtr_acidsplat" )); + spawnArgs.Delete("mtr_splat2"); + spawnArgs.Delete("mtr_splat3"); + spawnArgs.Delete("mtr_splat4"); + spawnArgs.Set("keepDecals", "1"); + + CancelEvents( &EV_Dispose ); + PostEventSec( &EV_Dispose, 1.5 ); + } + } +} + +bool hhMonsterAI::NearEnoughTouchPos( idEntity* ent, const idVec3& targetPos, idBounds& bounds ) { + bounds.TranslateSelf( targetPos ); + + idBounds bnds( idVec3(-16.f, -16.f, -8.f), idVec3(16.f, 16.f, 64.f) ); + bnds.TranslateSelf( physicsObj.GetOrigin() ); +//uncomment these to show debug lines +// gameRenderWorld->DebugBounds( colorOrange, bounds ); +// gameRenderWorld->DebugArrow( colorRed, targetPos, targetPos + idVec3(0, 0, 10), 5 ); +// gameRenderWorld->DebugBounds( colorRed, bnds ); + if( bnds.IntersectsBounds( bounds ) ) { + return true; + } + return false; +} + +bool hhMonsterAI::GetTouchPosBound( const hhReactionDesc *desc, idBounds & bnds ) { + idStr minName, maxName; + idVec3 min, max; + + minName = va("min_%s", GetEntityDefName()); + maxName = va("max_%s", GetEntityDefName()); + + if( !desc->touchOffsets.GetVector(minName.c_str(), "0 0 0", min) || !desc->touchOffsets.GetVector(maxName.c_str(), "0 0 0", max) ) { + return false; + } + min -= idVec3( 16.f, 16.f, 8.f ); + max += idVec3( 16.f, 16.f, 64.f ); + + bnds.Clear(); + bnds[ 0 ] = min; + bnds[ 1 ] = max; + return true; +} + +int hhMonsterAI::EvaluateReaction( const hhReaction *react ) { +// Volume/Distance + // If a volume in specified, then use that as our spatial check + float distSq = ( react->causeEntity->GetOrigin() - GetOrigin() ).LengthSqr(); + + if( react->desc->listenerVolumes.Num() > 0 ) { + int count = 0; + int i; + for( i = 0; i < react->desc->listenerVolumes.Num(); i++ ) { + assert( react->desc->listenerVolumes[ i ]->GetPhysics() != NULL ); + assert( GetPhysics() != NULL ); + if( react->desc->listenerVolumes[ i ]->GetPhysics()->GetAbsBounds().IntersectsBounds( GetPhysics()->GetAbsBounds() ) ) { + //MDC-TODO: Draw bounds debugging info... + count++; //one is good enough to continue - no need to check the rest + break; + } + } + // If we are in ZERO of the listener volumes, then we can bail because this reaction doesn't apply to us + if( count == 0 ) { + return 0; + } + } + if ( react->desc->effectVolumes.Num() > 0 && GetEnemy() ) { + int count = 0; + for( int i = 0; i < react->desc->effectVolumes.Num(); i++ ) { + assert( react->desc->effectVolumes[ i ]->GetPhysics() != NULL ); + assert( GetPhysics() != NULL ); + if( react->desc->effectVolumes[ i ]->GetPhysics()->GetAbsBounds().IntersectsBounds( GetEnemy()->GetPhysics()->GetAbsBounds() ) ) { + count++; //one is good enough to return false - no need to check the rest + break; + } + } + // If enemy is in ZERO of the effect volumes, then we can bail because this reaction doesn't apply to us + if( count == 0 ) { + return 0; + } + } + if( react->desc->listenerRadius > 0.f || react->desc->listenerMinRadius > 0.f ) { + // TOO FAR + if( react->desc->listenerRadius > 0.f && distSq > react->desc->listenerRadius * react->desc->listenerRadius ) { + return 0; + } + // TOO CLOSE + if( react->desc->listenerMinRadius > 0.f && distSq < react->desc->listenerMinRadius * react->desc->listenerMinRadius ) { + return 0; + } + } +// Path-finding + if( react->desc->CauseRequiresPathfinding() ) { + if( !HasPathTo( GetTouchPos(react->causeEntity.GetEntity(), react->desc ) ) ) { + //MDC-TODO: draw path debugging + return 0; + } + else { + //MDC-TODO: draw path debugging + } + } +// Can-See + if( react->desc->flags & hhReactionDesc::flagReq_CanSee ) { + if( react->causeEntity.IsValid() ) { + if( !CanSee(react->causeEntity.GetEntity(), TRUE) ) { + return 0; + } + } + } + + return 100; +} + +int hhMonsterAI::ReactionTo( const idEntity *ent ) { + if ( bNoCombat ) { + return ATTACK_IGNORE; + } + const idActor *actor = static_cast( ent ); + if( actor && actor->IsType(hhDeathProxy::Type) ) { + return ATTACK_IGNORE; + } + + //only attack spiritwalking players if they hurt me + if ( ent->IsType( hhPlayer::Type ) ) { + const hhPlayer *player = static_cast( ent ); + if ( nextSpiritProxyCheck == 0 && player && player->IsSpiritWalking() ) { + nextSpiritProxyCheck = gameLocal.time + SEC2MS(2); + return ATTACK_ON_DAMAGE; + } + } + + if ( ent->IsType( hhSpiritProxy::Type ) ) { + if ( gameLocal.time > nextSpiritProxyCheck ) { + nextSpiritProxyCheck = 0; + //attack spiritproxy on sight if we have no enemy or if its closer than our current enemy + if ( enemy.IsValid() && enemy->IsType( hhPlayer::Type ) ) { + float distToEnemy = (enemy->GetOrigin() - GetOrigin()).LengthSqr(); + float distToProxy = (ent->GetOrigin() - GetOrigin()).LengthSqr(); + if ( distToProxy < distToEnemy ) { + return ATTACK_ON_SIGHT; + } else { + //HUMANHEAD jsh PCF 4/29/06 fixed logic error with spiritproxy checking + return ATTACK_IGNORE; + } + } else { + return ATTACK_ON_SIGHT; + } + } else { + return ATTACK_IGNORE; + } + } + + if ( ent->IsType( hhMonsterAI::Type ) ) { + const hhMonsterAI *entAI = static_cast( ent ); + if ( entAI && entAI->bNeverTarget ) { + return ATTACK_IGNORE; + } + } + + return idAI::ReactionTo( ent ); +} + +bool hhMonsterAI::UpdateAnimationControllers( void ) { + idVec3 local; + idVec3 focusPos; + idVec3 left; + idVec3 dir; + idVec3 orientationJointPos; + idVec3 localDir; + idAngles newLookAng; + idAngles diff; + idMat3 mat; + idMat3 axis; + idMat3 orientationJointAxis; + idAFAttachment *headEnt = head.GetEntity(); + idVec3 eyepos; + idVec3 pos; + int i; + idAngles jointAng; + float orientationJointYaw; + + if ( AI_DEAD ) { + return idActor::UpdateAnimationControllers(); + } + + if ( orientationJoint == INVALID_JOINT ) { + orientationJointAxis = viewAxis; + orientationJointPos = physicsObj.GetOrigin(); + orientationJointYaw = current_yaw; + } else { + GetJointWorldTransform( orientationJoint, gameLocal.time, orientationJointPos, orientationJointAxis ); + orientationJointYaw = orientationJointAxis[ 2 ].ToYaw(); + orientationJointAxis = idAngles( 0.0f, orientationJointYaw, 0.0f ).ToMat3(); + } + + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorCyan, orientationJointPos, orientationJointPos + orientationJointAxis[0] * 64.0, 10, 1 ); + } + + if ( focusJoint != INVALID_JOINT ) { + if ( headEnt ) { + headEnt->GetJointWorldTransform( focusJoint, gameLocal.time, eyepos, axis ); + } else { + // JRMMERGE_GRAVAXIS - What about GetGravAxis() are we still using/needing that? + GetJointWorldTransform( focusJoint, gameLocal.time, eyepos, axis ); + } + eyeOffset.z = eyepos.z - physicsObj.GetOrigin().z; + } else { + eyepos = GetEyePosition(); + } + + if ( headEnt ) { + CopyJointsFromBodyToHead(); + } + + // 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(); + + idEntity *focusEnt = focusEntity.GetEntity(); + //HUMANHEAD jsh allow eyefocus independent from allowJointMod + if ( ( !allowJointMod && !allowEyeFocus ) || ( gameLocal.time >= focusTime && focusTime != -1 ) || GetPhysics()->GetGravityNormal() != idVec3( 0,0,-1) ) { + focusPos = GetEyePosition() + orientationJointAxis[ 0 ] * 512.0f; + } else if ( focusEnt == NULL ) { + // keep looking at last position until focusTime is up + focusPos = currentFocusPos; + } else if ( focusEnt == enemy.GetEntity() ) { + focusPos = lastVisibleEnemyPos + lastVisibleEnemyEyeOffset - eyeVerticalOffset * enemy.GetEntity()->GetPhysics()->GetGravityNormal(); + } else if ( focusEnt->IsType( idActor::Type ) ) { + focusPos = static_cast( focusEnt )->GetEyePosition() - eyeVerticalOffset * focusEnt->GetPhysics()->GetGravityNormal(); + } else { + focusPos = focusEnt->GetPhysics()->GetOrigin(); + } + + currentFocusPos = currentFocusPos + ( focusPos - currentFocusPos ) * eyeFocusRate; + + // determine yaw from origin instead of from focus joint since joint may be offset, which can cause us to bounce between two angles + dir = focusPos - orientationJointPos; + newLookAng.yaw = idMath::AngleNormalize180( dir.ToYaw() - orientationJointYaw ); + newLookAng.roll = 0.0f; + newLookAng.pitch = 0.0f; + + newLookAng += lookOffset; + +#if 0 + gameRenderWorld->DebugLine( colorRed, orientationJointPos, focusPos, gameLocal.msec ); + gameRenderWorld->DebugLine( colorYellow, orientationJointPos, orientationJointPos + orientationJointAxis[ 0 ] * 32.0f, gameLocal.msec ); + gameRenderWorld->DebugLine( colorGreen, orientationJointPos, orientationJointPos + newLookAng.ToForward() * 48.0f, gameLocal.msec ); +#endif + +//JRMMERGE_GRAVAXIS: This changed to much to merge, see if you can get your monsters on planets changes back in here. I'll leave both versions +#if OLD_CODE + GetGravViewAxis().ProjectVector( dir, localDir ); // HUMANHEAD JRM: VIEWAXIS_TO_GETGRAVVIEWAXIS + lookAng.yaw = idMath::AngleNormalize180( localDir.ToYaw() ); + lookAng.pitch = -idMath::AngleNormalize180( localDir.ToPitch() ); + lookAng.roll = 0.0f; +#else + // determine pitch from joint position + dir = focusPos - eyepos; + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorYellow, eyepos, eyepos + dir, 10, 1 ); + } + dir.NormalizeFast(); + orientationJointAxis.ProjectVector( dir, localDir ); + newLookAng.pitch = -idMath::AngleNormalize180( localDir.ToPitch() ) + lookOffset.pitch; + newLookAng.roll = 0.0f; +#endif + + diff = newLookAng - lookAng; + + if ( eyeAng != diff ) { + eyeAng = diff; + eyeAng.Clamp( eyeMin, eyeMax ); + idAngles angDelta = diff - eyeAng; + if ( !angDelta.Compare( ang_zero, 0.1f ) ) { + alignHeadTime = gameLocal.time; + } else { + alignHeadTime = gameLocal.time + ( 0.5f + 0.5f * gameLocal.random.RandomFloat() ) * focusAlignTime; + } + } + + if ( idMath::Fabs( newLookAng.yaw ) < 0.1f ) { + alignHeadTime = gameLocal.time; + } + + if ( ( gameLocal.time >= alignHeadTime ) || ( gameLocal.time < forceAlignHeadTime ) ) { + alignHeadTime = gameLocal.time + ( 0.5f + 0.5f * gameLocal.random.RandomFloat() ) * focusAlignTime; + destLookAng = newLookAng; + destLookAng.Clamp( lookMin, lookMax ); + } + + diff = destLookAng - lookAng; + if ( ( lookMin.pitch == -180.0f ) && ( lookMax.pitch == 180.0f ) ) { + if ( ( diff.pitch > 180.0f ) || ( diff.pitch <= -180.0f ) ) { + diff.pitch = 360.0f - diff.pitch; + } + } + if ( ( lookMin.yaw == -180.0f ) && ( lookMax.yaw == 180.0f ) ) { + if ( diff.yaw > 180.0f ) { + diff.yaw -= 360.0f; + } else if ( diff.yaw <= -180.0f ) { + diff.yaw += 360.0f; + } + } + lookAng = lookAng + diff * headFocusRate; + lookAng.Normalize180(); + + jointAng.roll = 0.0f; + if ( allowJointMod ) { + for( i = 0; i < lookJoints.Num(); i++ ) { + jointAng.pitch = 0; + jointAng.yaw = lookAng.yaw * lookJointAngles[ i ].yaw; + animator.SetJointAxis( lookJoints[ i ], JOINTMOD_WORLD, jointAng.ToMat3() ); + + idMat3 pitchRot; + hhMath::BuildRotationMatrix( DEG2RAD(-lookAng.pitch * lookJointAngles[ i ].pitch), 0, pitchRot ); + animator.GetJointLocalTransform( lookJoints[ i ], gameLocal.time, pos, mat ); + animator.SetJointAxis( lookJoints[ i ], JOINTMOD_LOCAL_OVERRIDE, pitchRot * mat ); + } + } + + if ( move.moveType == MOVETYPE_FLY ) { + // lean into turns + AdjustFlyingAngles(); + } + + if ( headEnt ) { + idAnimator *headAnimator = headEnt->GetAnimator(); + + // HUMANHEAD pdm: Added support for look joints in head entities + if ( allowJointMod ) { + for( i = 0; i < headLookJoints.Num(); i++ ) { + jointAng.pitch = lookAng.pitch * headLookJointAngles[ i ].pitch; + jointAng.yaw = lookAng.yaw * headLookJointAngles[ i ].yaw; + headAnimator->SetJointAxis( headLookJoints[ i ], JOINTMOD_WORLD, jointAng.ToMat3() ); + } + } + // HUMANHEAD END + + if ( allowEyeFocus ) { + idMat3 eyeAxis = ( lookAng + eyeAng ).ToMat3(); idMat3 headTranspose = headEnt->GetPhysics()->GetAxis().Transpose(); + axis = eyeAxis * orientationJointAxis; + left = axis[ 1 ] * eyeHorizontalOffset; + eyepos -= headEnt->GetPhysics()->GetOrigin(); + headAnimator->SetJointPos( leftEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + ( axis[ 0 ] * 64.0f + left ) * headTranspose ); + headAnimator->SetJointPos( rightEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + ( axis[ 0 ] * 64.0f - left ) * headTranspose ); + + //if ( ai_debugMove.GetBool() ) { + // gameRenderWorld->DebugLine( colorRed, orientationJointPos, eyepos + ( axis[ 0 ] * 64.0f + left ) * headTranspose, gameLocal.msec ); + //} + } else { + headEnt->BecomeActive( TH_ANIMATE ); + headAnimator->ClearJoint( leftEyeJoint ); + headAnimator->ClearJoint( rightEyeJoint ); + } + } else { + if ( allowEyeFocus ) { + idMat3 eyeAxis = ( lookAng + eyeAng ).ToMat3(); + axis = eyeAxis * orientationJointAxis; + left = axis[ 1 ] * eyeHorizontalOffset; + eyepos += axis[ 0 ] * 64.0f - physicsObj.GetOrigin(); + animator.SetJointPos( leftEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + left ); + animator.SetJointPos( rightEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos - left ); + } else { + animator.ClearJoint( leftEyeJoint ); + animator.ClearJoint( rightEyeJoint ); + } + } + + //HUMANHEAD pdm jawflap + hhAnimator *theAnimator; + if (head.IsValid()) { + theAnimator = head->GetAnimator(); + } + else { + theAnimator = GetAnimator(); + } + JawFlap(theAnimator); + //END HUMANHEAD + + return true; +} + +//overridden to allow attack nodes farther away than our current enemydistance +void hhMonsterAI::Event_GetCombatNode( void ) { + int i; + float dist; + idEntity *targetEnt; + idCombatNode *node; + float bestDist; + idCombatNode *bestNode; + idActor *enemyEnt = enemy.GetEntity(); + + if ( !targets.Num() ) { + // no combat nodes + idThread::ReturnEntity( NULL ); + return; + } + + if ( !enemyEnt || !EnemyPositionValid() ) { + // don't return a combat node if we don't have an enemy or + // if we can see he's not in the last place we saw him + idThread::ReturnEntity( NULL ); + return; + } + + // find the closest attack node that can see our enemy and is closer than our enemy + bestNode = NULL; + const idVec3 &myPos = physicsObj.GetOrigin(); + bestDist = 9999999.0f ; + for( i = 0; i < targets.Num(); i++ ) { + targetEnt = targets[ i ].GetEntity(); + if ( !targetEnt || !targetEnt->IsType( idCombatNode::Type ) ) { + continue; + } + + node = static_cast( targetEnt ); + if ( !node->IsDisabled() && node->EntityInView( enemyEnt, lastVisibleEnemyPos ) ) { + idVec3 org = node->GetPhysics()->GetOrigin(); + dist = ( myPos - org ).LengthSqr(); + if ( dist < bestDist ) { + bestNode = node; + bestDist = dist; + } + } + } + + idThread::ReturnEntity( bestNode ); +} + +void hhMonsterAI::FlyTurn( void ) { //overridden for vehicle code changes + if ( move.moveCommand == MOVE_FACE_ENEMY ) { + TurnToward( lastVisibleEnemyPos ); + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + } else if ( move.speed > 0.0f ) { + const idVec3 &vel = physicsObj.GetLinearVelocity(); + if ( vel.ToVec2().LengthSqr() > 0.1f ) { + if ( InVehicle() ) { + if ( move.goalEntity.IsValid() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + } else { + TurnToward( GetVehicleInterface()->GetVehicle()->GetPhysics()->GetLinearVelocity().ToYaw() ); + } + } else { + if ( vel.ToVec2().LengthSqr() > 0.1f ) { + TurnToward( vel.ToYaw() ); + } + } + } + } + Turn(); +} + +//overridden to allow custom hearingRange +void hhMonsterAI::UpdateEnemyPosition( void ) { + idActor *enemyEnt = enemy.GetEntity(); + int enemyAreaNum; + int areaNum; + aasPath_t path; + predictedPath_t predictedPath; + idVec3 enemyPos; + bool onGround; + + if ( !enemyEnt ) { + return; + } + + const idVec3 &org = physicsObj.GetOrigin(); + + if ( move.moveType == MOVETYPE_FLY ) { + enemyPos = enemyEnt->GetPhysics()->GetOrigin(); + onGround = true; + } else { + onGround = enemyEnt->GetFloorPos( 64.0f, enemyPos ); + if ( enemyEnt->OnLadder() ) { + onGround = false; + } + } + + if ( onGround ) { + // when we don't have an AAS, we can't tell if an enemy is reachable or not, + // so just assume that he is. + if ( !aas ) { + enemyAreaNum = 0; + lastReachableEnemyPos = enemyPos; + } else { + enemyAreaNum = PointReachableAreaNum( enemyPos, 1.0f ); + if ( enemyAreaNum ) { + areaNum = PointReachableAreaNum( org ); + if ( PathToGoal( path, areaNum, org, enemyAreaNum, enemyPos ) ) { + lastReachableEnemyPos = enemyPos; + } + } + } + } + + AI_ENEMY_IN_FOV = false; + AI_ENEMY_VISIBLE = false; + + if ( CanSee( enemyEnt, false ) ) { + AI_ENEMY_VISIBLE = true; + if ( CheckFOV( enemyEnt->GetPhysics()->GetOrigin() ) ) { + AI_ENEMY_IN_FOV = true; + } + + SetEnemyPosition(); + } else { + // check if we heard any sounds in the last frame + if ( enemyEnt == gameLocal.GetAlertEntity() ) { + float dist = ( enemyEnt->GetPhysics()->GetOrigin() - org ).LengthSqr(); + //allow the sound's own radius to override hearingRange, if it is set + if ( gameLocal.lastAIAlertRadius ) { + if ( dist < Square( gameLocal.lastAIAlertRadius ) ) { + SetEnemyPosition(); + } + } else if ( dist < Square( hearingRange ) ) { + SetEnemyPosition(); + } + } + } + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugBounds( colorLtGrey, enemyEnt->GetPhysics()->GetBounds(), lastReachableEnemyPos, gameLocal.msec ); + gameRenderWorld->DebugBounds( colorWhite, enemyEnt->GetPhysics()->GetBounds(), lastVisibleReachableEnemyPos, gameLocal.msec ); + } +} + +void hhMonsterAI::BecameBound(hhBindController *b) { + SetState( GetScriptFunction( "state_Nothing" ) ); +} + +void hhMonsterAI::BecameUnbound(hhBindController *b) { + if ( health > 0 ) { + SetState( GetScriptFunction( "state_Idle" ) ); + } +} + +void hhMonsterAI::CrashLand( const idVec3 &oldOrigin, const idVec3 &oldVelocity ) { + const trace_t& trace = physicsObj.GetGroundTrace(); + if ( af.IsActive() || (!physicsObj.HasGroundContacts() || trace.fraction == 1.0f) && !IsBound() ) { + return; + } + + //aob - only check when we land on the ground + //If we get here we can assume we currently have ground contacts + if( physicsObj.HadGroundContacts() ) { + return; + } + + // if the monster wasn't going down + if ( ( oldVelocity * -physicsObj.GetGravityNormal() ) >= 0.0f ) { + return; + } + + idVec3 deltaVelocity = DetermineDeltaCollisionVelocity( oldVelocity, trace ); + float delta = (IsBound()) ? deltaVelocity.Length() : deltaVelocity * physicsObj.GetGravityNormal(); + + if ( delta < spawnArgs.GetFloat( "fatal_fall_velocity", "900" ) ) { + return; // Early out + } + if( trace.fraction == 1.0f ) { + return; + } + Damage( NULL, NULL, oldVelocity.ToNormal(), "damage_monsterfall", 1, INVALID_JOINT ); +} + +hhEntityFx* hhMonsterAI::SpawnFxLocal( const char *fxName, const idVec3 &origin, const idMat3& axis, const hhFxInfo* const fxInfo, bool forceClient ) { + //overridden to use GetJointWorldTransform to set location if binding to a bone + idDict fxArgs; + hhEntityFx * fx = NULL; + + if ( g_skipFX.GetBool() ) { + return NULL; + } + + if( !fxName || !fxName[0] ) { + return NULL; + } + + // Spawn an fx + fxArgs.Set( "fx", fxName ); + fxArgs.SetBool( "start", fxInfo ? fxInfo->StartIsSet() : true ); + fxArgs.SetVector( "origin", origin ); + fxArgs.SetMatrix( "rotation", axis ); + //HUMANHEAD: aob + if( fxInfo ) { + fxArgs.SetBool( "removeWhenDone", fxInfo->RemoveWhenDone() ); + fxArgs.SetBool( "onlyVisibleInSpirit", fxInfo->OnlyVisibleInSpirit() ); // CJR + fxArgs.SetBool( "onlyInvisibleInSpirit", fxInfo->OnlyInvisibleInSpirit() ); // tmj + fxArgs.SetBool( "toggle", fxInfo->Toggle() ); + } + //HUMANHEAD END + + //HUMANHEAD rww - use forceClient + if (forceClient) { + //this can happen on the "server" in the case of listen servers as well + fx = (hhEntityFx *)gameLocal.SpawnClientObject( "func_fx", &fxArgs ); + } + else { + assert(!gameLocal.isClient); + fx = (hhEntityFx *)gameLocal.SpawnObject( "func_fx", &fxArgs ); + } + if( fxInfo ) { + fx->SetFxInfo( *fxInfo ); + } + + if( fxInfo && fxInfo->EntityIsSet() ) { + fx->fl.noRemoveWhenUnbound = fxInfo->NoRemoveWhenUnbound(); + if( fxInfo->BindBoneIsSet() ) { + idVec3 bonePos; + idMat3 boneAxis; + GetJointWorldTransform( fxInfo->GetBindBone(), bonePos, boneAxis ); + fx->SetOrigin( bonePos ); + fx->BindToJoint( fxInfo->GetEntity(), fxInfo->GetBindBone(), true ); + } else if( fx && fx->Joint() && *fx->Joint() ) { + fx->MoveToJoint( fxInfo->GetEntity(), fx->Joint() ); + fx->BindToJoint( fxInfo->GetEntity(), fx->Joint(), true ); + } else { + fx->Bind( fxInfo->GetEntity(), true ); + } + } + + fx->Show(); + + return fx; +} + +bool hhMonsterAI::AttackMelee( const char *meleeDefName ) { + const idDict *meleeDef; + idActor *enemyEnt = enemy.GetEntity(); + const char *p; + const idSoundShader *shader; + + meleeDef = gameLocal.FindEntityDefDict( meleeDefName, false ); + if ( !meleeDef ) { + gameLocal.Error( "Unknown melee '%s'", meleeDefName ); + } + + if ( !enemyEnt ) { + p = meleeDef->GetString( "snd_miss" ); + if ( p && *p ) { + shader = declManager->FindSound( p ); + StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); + } + return false; + } + + // make sure the trace can actually hit the enemy + if ( !TestMeleeDef(meleeDefName) ) { + // missed + p = meleeDef->GetString( "snd_miss" ); + if ( p && *p ) { + shader = declManager->FindSound( p ); + StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); + } + return false; + } + + // + // 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; + + if ( spawnArgs.GetBool( "smart_knockback", "0" ) ) { + //do some traces to determine which dir to kick enemy + idAngles ang = viewAxis.ToAngles(); + idVec3 forward, right, up; + ang.ToVectors( &forward, &right, &up ); + trace_t trace; + gameLocal.clip.TraceBounds( trace, enemyEnt->GetOrigin(), enemyEnt->GetOrigin() + -forward * 400, enemyEnt->GetPhysics()->GetBounds(), MASK_SOLID, this ); + if ( trace.fraction != 1.0f ) { + //there's room to kick the player back + globalKickDir = -forward; + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugArrow( colorRed, enemyEnt->GetOrigin(), enemyEnt->GetOrigin() + globalKickDir * 500, 10, 10000 ); + } + } else { + gameLocal.clip.TraceBounds( trace, enemyEnt->GetOrigin(), enemyEnt->GetOrigin() + right * 400, enemyEnt->GetPhysics()->GetBounds(), MASK_SOLID, this ); + if ( trace.fraction != 1.0f ) { + globalKickDir = right; + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugArrow( colorRed, enemyEnt->GetOrigin(), enemyEnt->GetOrigin() + globalKickDir * 500, 10, 10000 ); + } + } else { + gameLocal.clip.TraceBounds( trace, enemyEnt->GetOrigin(), enemyEnt->GetOrigin() + right * 400, enemyEnt->GetPhysics()->GetBounds(), MASK_SOLID, this ); + if ( trace.fraction != 1.0f ) { + globalKickDir = -right; + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugArrow( colorRed, enemyEnt->GetOrigin(), enemyEnt->GetOrigin() + globalKickDir * 500, 10, 10000 ); + } + } + } + } + } + + enemyEnt->Damage( this, this, globalKickDir, meleeDefName, 1.0f, INVALID_JOINT ); + + //swap skin if we have successfully hit + idStr hitSkin = spawnArgs.GetString( "skin_melee_hit", "" ); + if ( hitSkin.Length() ) { + SetSkinByName( hitSkin.c_str() ); + } + + lastAttackTime = gameLocal.time; + + return true; +} + +bool hhMonsterAI::TestMeleeDef( const char *meleeDefName ) const { + //tests using "melee_boundmin" and "melee_boundmax" in the damage def + trace_t trace; + idActor *enemyEnt = enemy.GetEntity(); + const idDict *meleeDef; + + meleeDef = gameLocal.FindEntityDefDict( meleeDefName, false ); + if ( !meleeDef ) { + gameLocal.Error( "Unknown melee '%s'", meleeDefName ); + } + + if ( !enemyEnt || !melee_range ) { + return false; + } + + //FIXME: make work with gravity vector + idVec3 org = physicsObj.GetOrigin(); + const idBounds &myBounds = physicsObj.GetBounds(); + idBounds bounds; + + idBounds meleeBounds( meleeDef->GetVector( "melee_boundmin" ), meleeDef->GetVector( "melee_boundmax" ) ); + + if ( meleeBounds != bounds_zero ) { + //check custom rotated meleebound + idBox meleeBox( meleeBounds, org, renderEntity.axis ); + idBounds enemyBounds = enemyEnt->GetPhysics()->GetBounds(); + enemyBounds.TranslateSelf( enemyEnt->GetPhysics()->GetOrigin() ); + idBox enemyBox( enemyBounds ); + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugBox( colorYellow, meleeBox, 5000 ); + } + + if ( !enemyBox.IntersectsBox( meleeBox ) ) { + return false; + } + } else { + // expand the bounds out by our melee range + bounds[0][0] = -melee_range; + bounds[0][1] = -melee_range; + bounds[0][2] = myBounds[0][2] - 4.0f; + bounds[1][0] = melee_range; + bounds[1][1] = melee_range; + 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 ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugBounds( colorYellow, bounds, vec3_zero, gameLocal.msec ); + } + + if ( !bounds.IntersectsBounds( enemyBounds ) ) { + return false; + } + } + + idVec3 start = GetEyePosition(); + idVec3 end = enemyEnt->GetEyePosition(); + + gameLocal.clip.TracePoint( trace, start, end, MASK_SHOT_BOUNDINGBOX, this ); + if ( ( trace.fraction == 1.0f ) || ( gameLocal.GetTraceEntity( trace ) == enemyEnt ) ) { + return true; + } + + return false; +} + +void hhMonsterAI::PrintDebug() { + if ( enemy.IsValid() ) { + int dist = int(( GetPhysics()->GetOrigin() - enemy->GetPhysics()->GetOrigin() ).LengthFast()); + gameLocal.Printf( " Enemy: %s\n", enemy->GetName() ); + gameLocal.Printf( " Distance to Enemy: %d\n", dist ); + gameLocal.Printf( " Enemy Area: %i\n", PointReachableAreaNum(enemy->GetOrigin()) ); + gameLocal.Printf( " Enemy Reachable: %s\n", AI_ENEMY_REACHABLE ? "yes" : "no" ); + gameLocal.Printf( " Enemy Visible: %s\n", AI_ENEMY_VISIBLE ? "yes" : "no" ); + } else { + gameLocal.Printf( " Enemy: None\n" ); + } + gameLocal.Printf( " Current Area: %i\n", PointReachableAreaNum(GetOrigin()) ); + if ( state ) { + gameLocal.Printf( " State: %s\n", state->Name() ); + } + gameLocal.Printf( " Health: %i/%i\n", GetHealth(), spawnArgs.GetInt( "health" ) ); + gameLocal.Printf( " Torso State: %s\n", torsoAnim.state.c_str() ); + gameLocal.Printf( " Legs State: %s\n", legsAnim.state.c_str() ); + gameLocal.Printf( " IK: %s\n", walkIK.IsActivated() ? "enable" : "disabled" ); + gameLocal.Printf( " Turnrate: %f\n", turnRate ); +} + +bool hhMonsterAI::NewWanderDir( const idVec3 &dest ) { + if ( spawnArgs.GetInt( "wander_radius" ) ) { + //pick a new dest based on radius from starting origin + idVec3 offset = hhUtils::RandomVector() * float(spawnArgs.GetInt( "wander_radius" )); + offset.z = 0.0f; + const idVec3 newDest = spawnOrigin + offset; + return idAI::NewWanderDir( newDest ); + } else { + return idAI::NewWanderDir( dest ); + } +} + +/* +===================== +hhMonsterAI::Save +===================== +*/ +void hhMonsterAI::Save( idSaveGame *savefile ) const { + savefile->WriteInt( targetReaction.reactionIndex ); + targetReaction.entity.Save( savefile); + shootTarget.Save( savefile ); + currPassageway.Save( savefile ); + + savefile->WriteBool( bCanFall ); + + int i, num = healthTriggers.Num(); + savefile->WriteInt( num ); + for (i = 0; i < num; i++) { + savefile->WriteInt( healthTriggers[i].healthThresh ); + healthTriggers[i].triggerEnt.Save( savefile ); + savefile->WriteInt( healthTriggers[i].triggerCount ); + } + savefile->WriteInt( hearingRange ); + savefile->WriteInt( lastContactTime ); + savefile->WriteBool( bSeeThroughPortals ); + savefile->WriteBool( bBossBar ); + savefile->WriteInt( nextSpeechTime ); + savefile->WriteAngles( lookOffset ); + savefile->WriteVec3( spawnOrigin ); + savefile->WriteBool( bBindOrient ); + savefile->WriteInt( numDDADamageSamples ); + savefile->WriteFloat( totalDDADamage ); + savefile->WriteInt( spawnThinkFlags ); + savefile->WriteBool( bCanWallwalk ); + savefile->WriteBool( bOverrideKilledByGravityZones ); + savefile->WriteInt( fallDelay ); + savefile->WriteBool( soundOnModel ); + savefile->WriteBool( bBindAxis ); + savefile->WriteBool( bCustomBlood ); + savefile->WriteBool( bNoCombat ); + savefile->WriteBool( bNeverTarget ); + savefile->WriteInt( nextSpiritProxyCheck ); +}; + +/* +===================== +hhMonsterAI::Restore +===================== +*/ +void hhMonsterAI::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( targetReaction.reactionIndex ); + targetReaction.entity.Restore( savefile); + + shootTarget.Restore( savefile ); + currPassageway.Restore( savefile ); + + savefile->ReadBool( bCanFall ); + + int i, num; + savefile->ReadInt( num ); + healthTriggers.SetNum( num ); + for (i = 0; i < num; i++) { + savefile->ReadInt( healthTriggers[i].healthThresh ); + healthTriggers[i].triggerEnt.Restore( savefile ); + savefile->ReadInt( healthTriggers[i].triggerCount ); + } + savefile->ReadInt( hearingRange ); + savefile->ReadInt( lastContactTime ); + savefile->ReadBool( bSeeThroughPortals ); + savefile->ReadBool( bBossBar ); + savefile->ReadInt( nextSpeechTime ); + savefile->ReadAngles( lookOffset ); + savefile->ReadVec3( spawnOrigin ); + savefile->ReadBool( bBindOrient ); + savefile->ReadInt( numDDADamageSamples ); + savefile->ReadFloat( totalDDADamage ); + savefile->ReadInt( spawnThinkFlags ); + savefile->ReadBool( bCanWallwalk ); + savefile->ReadBool( bOverrideKilledByGravityZones ); + savefile->ReadInt( fallDelay ); + savefile->ReadBool( soundOnModel ); + savefile->ReadBool( bBindAxis ); + savefile->ReadBool( bCustomBlood ); + savefile->ReadBool( bNoCombat ); + savefile->ReadBool( bNeverTarget ); + savefile->ReadInt( nextSpiritProxyCheck ); + + allSimpleMonsters.AddUnique(this); + + nextTurnUpdate = 0; + + if ( AI_VEHICLE ) { + if ( GetVehicleInterface() ) { + ResetClipModel(); + hhVehicle *vehicle = GetVehicleInterface()->GetVehicle(); + spawnArgs.Set( "use_aas", spawnArgs.GetString( "aas_shuttle", "aasDroid" ) ); + SetAAS(); + SetState( GetScriptFunction( "state_VehicleCombat" ) ); + } + } +}; + +/* +===================== +hhMonsterAI::GetTurnDir +Returns -1 if left, 0 if facing ideal, and 1 if turning right +===================== +*/ +int hhMonsterAI::GetTurnDir( void ) { + float diff; + + if ( !turnRate ) { + return 0; + } + + diff = idMath::AngleNormalize180( current_yaw - ideal_yaw ); + if ( idMath::Fabs( diff ) < 0.01f ) { + // force it to be exact + current_yaw = ideal_yaw; + return 0; + } + + return (diff < 0 ? -1 : 1); +} + + + +//============================================================================= +// +// hhMonsterAI::SendDamageToDDA() +// +// Informs the player of the amount of damage inflicted upon it until this +// creature was killed. +//============================================================================= + +void hhMonsterAI::SendDamageToDDA() { + if ( gameLocal.isMultiplayer ) { + return; + } + + int ddaIndex = spawnArgs.GetInt( "ddaIndex", "0" ); + + if ( gameLocal.GetDDA() && ddaIndex >= 0 ) { // Only include certain creatures in the DDA calculation (exclude such things as crawlers and NPCs) + gameLocal.GetDDA()->DDA_AddDamage( ddaIndex, totalDDADamage ); + gameLocal.GetDDA()->DDA_AddSurvivalHealth( ddaIndex, gameLocal.GetLocalPlayer()->GetHealth() ); + } + + totalDDADamage = 0; +} + +void hhMonsterAI::FlyMove( void ) { + idVec3 goalPos; + idVec3 oldorigin; + idVec3 newDest; + + AI_BLOCKED = false; + if ( ( move.moveCommand != MOVE_NONE ) && ReachedPos( move.moveDest, move.moveCommand ) ) { + StopMove( MOVE_STATUS_DONE ); + } + + if ( move.moveCommand != MOVE_TO_POSITION_DIRECT ) { + idVec3 vel = physicsObj.GetLinearVelocity(); + + if ( GetMovePos( goalPos ) ) { + CheckObstacleAvoidance( goalPos, newDest ); + goalPos = newDest; + } + + if ( !AI_FLY_NO_SEEK && move.speed ) { + FlySeekGoal( vel, goalPos ); + } + + // add in bobbing + AddFlyBob( vel ); + + if ( enemy.GetEntity() && ( move.moveCommand != MOVE_TO_POSITION ) ) { + AdjustFlyHeight( vel, goalPos ); + } + + AdjustFlySpeed( vel ); + + 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( disableGravity ); + RunPhysics(); + + monsterMoveResult_t moveResult = physicsObj.GetMoveResult(); + if ( !af_push_moveables && attack.Length() && TestMelee() ) { + DirectDamage( attack, enemy.GetEntity() ); + } else { + idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); + if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) { + KickObstacles( viewAxis[ 0 ], kickForce, blockEnt ); + } else if ( moveResult == MM_BLOCKED ) { + move.blockTime = gameLocal.time + 500; + AI_BLOCKED = true; + } + } + + idVec3 org = physicsObj.GetOrigin(); + if ( oldorigin != org ) { + TouchTriggers(); + } + + if ( ai_debugMove.GetBool() ) { + 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, goalPos, gameLocal.msec, true ); + gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true ); + DrawRoute(); + } +} + +void hhMonsterAI::Activate( idEntity *activator ) { + if ( spawnThinkFlags != 0 ) { + int temp = spawnThinkFlags; + spawnThinkFlags = 0; + BecomeActive( temp ); + } + idAI::Activate( activator ); +} + +void hhMonsterAI::Hide() { + if ( spawnThinkFlags == 0 && ai_hideSkipThink.GetBool() ) { + if ( ( spawnArgs.GetBool( "hide" ) || spawnArgs.GetBool( "portal" ) ) ) { + spawnThinkFlags = thinkFlags; + thinkFlags = 0; + } + } + idAI::Hide(); +} + +void hhMonsterAI::HideNoDormant() { + idAI::Hide(); +} + +void hhMonsterAI::BecomeActive( int flags ) { + if ( spawnThinkFlags != 0 ) { + spawnThinkFlags |= flags; + return; + } + idAI::BecomeActive( flags ); +} + +void hhMonsterAI::HandleNoGore(void) { + if (GERMAN_VERSION || g_nogore.GetBool()) { + fl.takedamage = false; + fl.canBeTractored = false; + GetPhysics()->SetContents(0); + float time = spawnArgs.GetFloat("nogore_dispose_time", "1"); + if (time > 0.0f) { + //PostEventSec(&EV_Dispose, time); + } + } +} + +bool hhMonsterAI::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + if ( soundOnModel ) { + idVec3 boneOrigin; + idMat3 boneAxis; + jointHandle_t bone = animator.GetJointHandle( spawnArgs.GetString( "sound_joint", "b1" ) ); + if ( bone != INVALID_JOINT && animator.GetJointTransform( bone, gameLocal.time, boneOrigin, boneAxis ) ) { + origin = boneOrigin; + axis = boneAxis * viewAxis; + return true; + } + } + return idAI::GetPhysicsToSoundTransform( origin, axis ); +} + +void hhMonsterAI::AddLocalMatterWound( jointHandle_t jointNum, const idVec3 &localOrigin, const idVec3 &localNormal, const idVec3 &localDir, int damageDefIndex, const idMaterial *collisionMaterial ) { + if ( bCustomBlood ) { + return; + } + + const idDeclEntityDef *def = static_cast( declManager->DeclByIndex( DECL_ENTITYDEF, damageDefIndex ) ); + if ( def == NULL ) { + return; + } + + surfTypes_t matterType = gameLocal.GetMatterType( this, collisionMaterial, "idEntity::AddLocalMatterWound" ); + + GetWoundManager()->AddWounds( def, matterType, jointNum, localOrigin, localNormal, localDir ); + + if ( head.IsValid() ) { + head->AddLocalMatterWound( jointNum, localOrigin, localNormal, localDir, damageDefIndex, collisionMaterial ); + } + //TODO: Grab head projection code from below and put into ApplyImpactMark() +} diff --git a/src/Prey/game_monster_ai.h b/src/Prey/game_monster_ai.h new file mode 100644 index 0000000..de62c90 --- /dev/null +++ b/src/Prey/game_monster_ai.h @@ -0,0 +1,262 @@ + +#ifndef __PREY_MONSTER_AI_H__ +#define __PREY_MONSTER_AI_H__ + +extern const idEventDef MA_AttackMissileEx; +extern const idEventDef MA_FindReaction; +extern const idEventDef MA_InitialWallwalk; +extern const idEventDef MA_EnemyOnSpawn; +extern const idEventDef MA_SetVehicleState; +extern const idEventDef MA_OnProjectileLaunch; +extern const idEventDef MA_OnProjectileHit; +extern const idEventDef MA_FallNow; +extern const idEventDef MA_EnemyIsSpirit; +extern const idEventDef MA_EnemyIsPhysical; +extern const idEventDef MA_EnemyPortal; + +// +// hhNoClipEnt +// +// Dummy entity that sets contents to 0 - created for use for ForceAAS entities +// +class hhNoClipEnt : public idEntity { +public: + CLASS_PROTOTYPE(hhNoClipEnt); + + void Spawn( void ); +}; + +class hhAINode : public idEntity { +public: + CLASS_PROTOTYPE( hhAINode ); + hhAINode(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + idEntityPtr< idEntity> user; +}; + +//! Somewhat of a hack. This should in an a .h file someplace. +// Not in a .cpp file as id has it now. +//#define CONDITION( name ) name.PointTo( scriptObject, #name ) + +struct hhMonsterReaction { + hhMonsterReaction(void) : reactionIndex(-1) { } + inline hhReaction *GetReaction() const { return ( entity.IsValid() && reactionIndex >= 0 ? entity->GetReaction(reactionIndex) : NULL ); } + int reactionIndex; + idEntityPtr entity; +}; + +// +// hhMonsterHealthTrigger +// +struct hhMonsterHealthTrigger { + hhMonsterHealthTrigger() {healthThresh = 0; triggerEnt = NULL; triggerCount = 0;} + + int healthThresh; // When our health drops below this value, trigger our ent + idEntityPtr triggerEnt; // The entity to trigger + int triggerCount; // Number of times we have tripped this trigger +}; + +class hhMonsterAI : public idAI { + +public: + CLASS_PROTOTYPE(hhMonsterAI); + + hhMonsterAI(); + ~hhMonsterAI(); + virtual void Think(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + const char * GetJointForFrameCommand( const char *cmd ) { idStr tmp( cmd ); int i = tmp.Find( " " ); if ( i < 0 ) { return( va( "%s", cmd ) ); } else { return( va( "%s", tmp.Left( i ).c_str() ) ); } }; + void Spawn(); + virtual void LinkScriptVariables(void); + bool HasPathTo(const idVec3 &destPt); + void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void EnterVehicle( hhVehicle* vehicle ); + virtual bool TestMelee( void ) const; + virtual bool MoveToPosition( const idVec3 &pos, bool enemyBlocks = false ); + virtual bool TurnToward( const idVec3 &pos ); + ID_INLINE virtual bool TurnToward( float yaw ) { return idAI::TurnToward( yaw ); } // HUMANHEAD mdl: Needed because of bizarre inheritance issue that resulted in TurnToward(idVec3) being called + virtual bool CanSee( idEntity *ent, bool useFov ); + idPlayer* GetClosestPlayer( void ); + void Show(); + bool GetFacePosAngle( const idVec3 &pos, float &delta ); + virtual void SetEnemy( idActor *newEnemy ); //made public because hhAI does + void Pickup( hhPlayer *player ); + bool GiveToPlayer( hhPlayer* player ); + idVec3 GetTouchPos(idEntity *ent, const hhReactionDesc *desc ); + void UpdateFromPhysics( bool moveBack ); + virtual bool GetTouchPosBound( const hhReactionDesc *desc, idBounds& bounds ); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + void CreateHealthTriggers(); + void UpdateHealthTriggers(int oldHealth, int currHealth); + int ReactionTo( const idEntity *ent ); + virtual bool UpdateAnimationControllers( void ); + virtual bool ShouldTouchTrigger( idEntity* entity ) const { return fl.touchTriggers; } + virtual void FlyTurn( void ); + bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + void UpdateEnemyPosition( void ); + void BecameBound(hhBindController *b); + void BecameUnbound(hhBindController *b); + void UpdateModelTransform(); + virtual // HUMANHEAD CJR + void CrashLand( const idVec3 &oldOrigin, const idVec3 &oldVelocity ); + hhEntityFx* SpawnFxLocal( const char *fxName, const idVec3 &origin, const idMat3& axis = mat3_identity, const hhFxInfo* const fxInfo = NULL, bool forceClient = false ); + bool AttackMelee( const char *meleeDefName ); + bool TestMeleeDef( const char *meleeDefName ) const; + virtual void PrintDebug(); + int GetTurnDir(); + bool FacingEnemy( float range ); + virtual void Activate( idEntity *activator ); + void BecomeActive( int flags ); + bool OverrideKilledByGravityZones() { return bOverrideKilledByGravityZones; } + virtual void Hide(); + virtual void HideNoDormant(); + void AddLocalMatterWound( jointHandle_t jointNum, const idVec3 &localOrigin, const idVec3 &localNormal, const idVec3 &localDir, int damageDefIndex, const idMaterial *collisionMaterial ); + ID_INLINE virtual bool IsDead() { return AI_DEAD != 0; } + bool Pushes() { return( pushes && move.moveCommand != MOVE_NONE ); } // only push if moving + void GravClipModelAxis( bool enable ) { physicsObj.SetGravClipModelAxis( enable ); } + virtual void Distracted( idActor *newEnemy ); + +// Events. + virtual void Event_PostSpawn(); + void Event_AttackMissileEx(const char *params, int boneDir); + virtual void Event_AttackMissile( const char *jointname, const idDict *projDef, int boneDir ); + void Event_SetMeleeRange( float newMeleeRange ); + virtual void Event_FindReaction( const char* effect ); + virtual void Event_UseReaction(); + void Event_EnemyOnSide(); + void Event_HitCheck( idEntity *ent, const char *animname ); + void Event_CreateMonsterPortal(); + void Event_GetShootTarget(); + void Event_TriggerReactEnt(); + virtual void Event_Remove( void ); + void Event_OnProjectileLaunch(hhProjectile *proj); + void Event_InitialWallwalk( void ); + void Event_GetVehicle(); + void Event_EnemyAimingAtMe(); + void Event_ReachedEntity( idEntity *ent ); + void Event_EnemyOnSpawn(); + void Event_SpawnFX( char *fxFile ); + void Event_SplashDamage(char *damage); + void Event_SetVehicleState(); + void Event_TestAnimMoveTowardEnemy( const char *animname ); + void Event_GetLastReachableEnemyPos(); + void Event_FollowPath( const char *pathName ); + void Event_EnemyIsA( const char* testclass ); + void Event_Subtitle( idList* parmList ); + void Event_SubtitleOff(); + void Event_EnableHeadlook(); + void Event_DisableHeadlook(); + void Event_EnableEyelook(); + void Event_DisableEyelook(); + void Event_FacingEnemy(float range); + void Event_BossBar( int onOff ); + virtual void Event_GetCombatNode(); + void Event_FallNow(); + void Event_AllowFall( int allowFall ); + virtual void Event_EnemyIsSpirit( hhPlayer *player, hhSpiritProxy *proxy ); + virtual void Event_EnemyIsPhysical( hhPlayer *player, hhSpiritProxy *proxy ); + void Event_InPlayerFov(); + void Event_IsRagdoll(); + void Event_MoveDone(); + void Event_SetShootTarget(idEntity *ent); + void Event_EnemyInGravityZone(void); + void Event_LookAtEntity( idEntity *ent, float duration ); + void Event_LookAtEnemy( float duration ); + void Event_SetLookOffset( idAngles const &ang ); + void Event_SetHeadFocusRate( float rate ); + void Event_HeardSound( int ignore_team ); + virtual void Event_FlyZip(); + void Event_UseConsole( idEntity *ent ); + void Event_TestMeleeDef( const char *meleeDefName ) const; + void Event_EnemyInVehicle(); + void Event_EnemyOnWallwalk(); + void Event_AlertAI( idEntity *ent, float radius ); + void Event_TestAnimMoveBlocked( const char *animname ); + void Event_InGravityZone(); + void Event_StartSoundDelay( const char *soundName, int channel, int netSync, float delay ); + void Event_SetTeam( int new_team ); + virtual void Event_GetAttackPoint(); + void Event_HideNoDormant(); + void Event_SoundOnModel(); + void Event_ActivatePhysics(); + void Event_IsVehicleDocked(); + void Event_SetNeverDormant( int enable ); + void Event_EnemyInSpirit(); + void Event_EnemyPortal( idEntity *ent ); + + // CJR DDA TEST + int numDDADamageSamples; + float totalDDADamage; + + void DamagedPlayer( int damage ) { numDDADamageSamples++; totalDDADamage += damage; } + void SendDamageToDDA(); + // END CJR DDA TEST + + //passageway code + bool IsInsidePassageway(void) const {return currPassageway != NULL;} + hhAIPassageway* GetCurrPassageway(void) {return currPassageway.GetEntity();} +public: + idScriptBool AI_HAS_RANGE_ATTACK; // TRUE if this monster has a range attack (required for reaction system) + idScriptBool AI_HAS_MELEE_ATTACK; // TRUE if this monster has a melee attack (required for reaction system) + idScriptBool AI_USING_REACTION; // TRUE if monster is currently using reaction + idScriptBool AI_REACTION_FAILED; // TRUE if the last reaction attempt failed (path blocked, exclusive problem, etc) + idScriptBool AI_REACTION_ANIM; // TRUE if the current reaction is using an anim to 'cause' + idScriptBool AI_BACKWARD; + idScriptBool AI_STRAFE_LEFT; + idScriptBool AI_STRAFE_RIGHT; + idScriptBool AI_UPWARD; + idScriptBool AI_DOWNWARD; + idScriptBool AI_SHUTTLE_DOCKED; + idScriptBool AI_VEHICLE_ATTACK; + idScriptBool AI_VEHICLE_ALT_ATTACK; + idScriptBool AI_WALLWALK; + idScriptBool AI_FALLING; + idScriptBool AI_PATHING; + idScriptFloat AI_TURN_DIR; + idScriptBool AI_FLY_NO_SEEK; + idScriptBool AI_FOLLOWING_PATH; + + bool bNeverTarget; + static idList allSimpleMonsters; // Global list of all hhMonsterAI's currently created +protected: + bool NearEnoughTouchPos( idEntity* ent, const idVec3& targetPos, idBounds& bounds ); + bool CheckValidReaction(); + void FinishReaction( bool bFailed = false ); + virtual int EvaluateReaction( const hhReaction *react ); + bool NewWanderDir( const idVec3 &dest ); + void FlyMove(); + virtual void HandleNoGore(); + bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); +protected: + hhMonsterReaction targetReaction; // Info about our current reaction + idEntityPtr shootTarget; + idEntityPtr currPassageway; // The passage node this monster is currently in + idList healthTriggers; + int lastContactTime; + int hearingRange; + int nextSpeechTime; + idAngles lookOffset; + int nextTurnUpdate; + idVec3 spawnOrigin; //location of where this monster spawned + int spawnThinkFlags; + int fallDelay; + bool bCanFall; + bool bSeeThroughPortals; + bool bBossBar; + bool bBindOrient; //orient monster to its bindmaster + bool bCanWallwalk; + bool bOverrideKilledByGravityZones; + bool soundOnModel; + bool bBindAxis; + bool bCustomBlood; + bool bNoCombat; + int nextSpiritProxyCheck; +}; + + +#endif /* __PREY_MONSTER_AI_H__ */ + + diff --git a/src/Prey/game_monster_ai_events.cpp b/src/Prey/game_monster_ai_events.cpp new file mode 100644 index 0000000..d973af0 --- /dev/null +++ b/src/Prey/game_monster_ai_events.cpp @@ -0,0 +1,1302 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef MA_AttackMissileEx("", "sd",NULL); +const idEventDef MA_SetMeleeRange("setMeleeRange", "f",NULL); +const idEventDef MA_FindReaction("findReaction", "s", 'E'); +const idEventDef MA_UseReaction("useReaction"); +const idEventDef MA_EnemyOnSide("enemyOnSide", NULL, 'f'); +const idEventDef MA_HitCheck( "hitCheck", "Es", 'd' ); +const idEventDef MA_CreateMonsterPortal("createMonsterPortal", NULL, 'f'); +const idEventDef MA_GetShootTarget("getShootTarget", NULL,'E'); +const idEventDef MA_TriggerReactEnt("triggerReactEnt"); +const idEventDef MA_InitialWallwalk(""); +const idEventDef MA_GetVehicle("getVehicle",NULL,'E'); +const idEventDef MA_EnemyAimingAtMe("enemyAimingAtMe",NULL,'d'); +const idEventDef MA_ReachedEntity("reachedEntity","e",'d'); +const idEventDef MA_EnemyOnSpawn("",NULL,NULL); +const idEventDef MA_SpawnFX( "spawnFX", "s" ); +const idEventDef MA_SplashDamage( "splashDamage", "s" ); +const idEventDef MA_SetVehicleState( "",NULL,NULL ); +const idEventDef MA_FollowPath( "followPath", "s", NULL ); +const idEventDef MA_GetLastReachableEnemyPos( "getLastReachableEnemyPos", "", 'v' ); +const idEventDef MA_OnProjectileLaunch("", "e"); +const idEventDef MA_EnemyIsA("enemyIsA", "s", 'd'); +const idEventDef MA_Subtitle("subtitle", "d"); +const idEventDef MA_SubtitleOff(""); +const idEventDef MA_EnableHeadlook("enableHeadlook"); +const idEventDef MA_DisableHeadlook("disableHeadlook"); +const idEventDef MA_EnableEyelook("enableEyelook"); +const idEventDef MA_DisableEyelook("disableEyelook"); +const idEventDef MA_FacingEnemy("facingEnemy", "f", 'd'); +const idEventDef MA_BossBar("bossBar", "d"); +const idEventDef MA_FallNow(""); +const idEventDef MA_AllowFall("allowFall", "d"); //HUMANHEAD jsh PCF 5/3/06 Changed parameter from "f" to "d" +const idEventDef MA_InPlayerFov("inPlayerFov", NULL, 'd'); +const idEventDef MA_EnemyIsSpirit("", "ee"); +const idEventDef MA_EnemyIsPhysical("", "ee"); +const idEventDef MA_IsRagdoll("isRagdoll", NULL, 'd'); +const idEventDef MA_MoveDone("moveDone", NULL, 'd'); +const idEventDef MA_SetShootTarget("setShootTarget", "E"); +const idEventDef EV_EnemyInGravityZone( "enemyInGravityZone", NULL, 'f' ); +const idEventDef MA_SetLookOffset( "setLookOffset", "v" ); +const idEventDef MA_SetHeadFocusRate( "setHeadFocusRate", "f" ); +const idEventDef MA_FlyZip( "flyZip" ); +const idEventDef MA_UseConsole( "useConsole", "e" ); +const idEventDef MA_TestMeleeDef("testMeleeDef", "s",'f'); +const idEventDef MA_EnemyInVehicle("enemyInVehicle", NULL, 'd'); +const idEventDef MA_EnemyOnWallwalk("enemyOnWallwalk", NULL, 'd'); +const idEventDef MA_AlertAI("alertAI", "ef" ); +const idEventDef MA_TestAnimMoveBlocked("testAnimMoveBlocked", "s", 'e'); +const idEventDef MA_InGravityZone("inGravityZone", NULL, 'd'); +const idEventDef MA_StartSoundDelay( "startSoundDelay", "sddf", 'f' ); +const idEventDef MA_SetTeam( "setTeam", "d" ); +const idEventDef MA_GetAttackPoint ("getAttackPoint", NULL, 'v'); +const idEventDef MA_HideNoDormant("hideNoDormant" ); +const idEventDef MA_SoundOnModel("soundOnModel"); +const idEventDef MA_ActivatePhysics("activatePhysics"); +const idEventDef MA_IsVehicleDocked("isVehicleDocked"); +const idEventDef MA_EnemyInSpirit( "enemyInSpirit", NULL, 'd' ); +const idEventDef MA_GetAttackNode( "getAttackNode", NULL, 'v' ); + +CLASS_DECLARATION(idAI, hhMonsterAI) + EVENT( MA_AttackMissileEx, hhMonsterAI::Event_AttackMissileEx ) + EVENT( MA_FindReaction, hhMonsterAI::Event_FindReaction ) + EVENT( MA_UseReaction, hhMonsterAI::Event_UseReaction ) + EVENT( MA_SetMeleeRange, hhMonsterAI::Event_SetMeleeRange ) + EVENT( MA_EnemyOnSide, hhMonsterAI::Event_EnemyOnSide ) + EVENT( MA_HitCheck, hhMonsterAI::Event_HitCheck ) + EVENT( MA_CreateMonsterPortal, hhMonsterAI::Event_CreateMonsterPortal ) + EVENT( MA_GetShootTarget, hhMonsterAI::Event_GetShootTarget ) + EVENT( MA_TriggerReactEnt, hhMonsterAI::Event_TriggerReactEnt ) + EVENT( MA_InitialWallwalk, hhMonsterAI::Event_InitialWallwalk ) + EVENT( MA_GetVehicle, hhMonsterAI::Event_GetVehicle ) + EVENT( MA_EnemyAimingAtMe, hhMonsterAI::Event_EnemyAimingAtMe ) + EVENT( MA_ReachedEntity, hhMonsterAI::Event_ReachedEntity ) + EVENT( MA_EnemyOnSpawn, hhMonsterAI::Event_EnemyOnSpawn ) + EVENT( MA_SpawnFX, hhMonsterAI::Event_SpawnFX ) + EVENT( MA_SplashDamage, hhMonsterAI::Event_SplashDamage) + EVENT( MA_SetVehicleState, hhMonsterAI::Event_SetVehicleState ) + EVENT( EV_PostSpawn, hhMonsterAI::Event_PostSpawn ) + EVENT( MA_FollowPath, hhMonsterAI::Event_FollowPath ) + EVENT( MA_GetLastReachableEnemyPos, hhMonsterAI::Event_GetLastReachableEnemyPos ) + EVENT( MA_EnemyIsA, hhMonsterAI::Event_EnemyIsA ) + EVENT( MA_Subtitle, hhMonsterAI::Event_Subtitle ) + EVENT( MA_SubtitleOff, hhMonsterAI::Event_SubtitleOff ) + EVENT( MA_EnableHeadlook, hhMonsterAI::Event_EnableHeadlook ) + EVENT( MA_DisableHeadlook, hhMonsterAI::Event_DisableHeadlook ) + EVENT( MA_EnableEyelook, hhMonsterAI::Event_EnableEyelook ) + EVENT( MA_DisableEyelook, hhMonsterAI::Event_DisableEyelook ) + EVENT( MA_FacingEnemy, hhMonsterAI::Event_FacingEnemy ) + EVENT( MA_BossBar, hhMonsterAI::Event_BossBar ) + EVENT( MA_FallNow, hhMonsterAI::Event_FallNow ) + EVENT( MA_AllowFall, hhMonsterAI::Event_AllowFall ) + EVENT( MA_InPlayerFov, hhMonsterAI::Event_InPlayerFov ) + EVENT( MA_EnemyIsSpirit, hhMonsterAI::Event_EnemyIsSpirit ) + EVENT( MA_EnemyIsPhysical, hhMonsterAI::Event_EnemyIsPhysical ) + EVENT( MA_IsRagdoll, hhMonsterAI::Event_IsRagdoll ) + EVENT( MA_MoveDone, hhMonsterAI::Event_MoveDone ) + EVENT( MA_SetShootTarget, hhMonsterAI::Event_SetShootTarget) + EVENT( EV_EnemyInGravityZone, hhMonsterAI::Event_EnemyInGravityZone ) + EVENT( MA_SetLookOffset, hhMonsterAI::Event_SetLookOffset ) + EVENT( MA_SetHeadFocusRate, hhMonsterAI::Event_SetHeadFocusRate ) + EVENT( MA_FlyZip, hhMonsterAI::Event_FlyZip ) + EVENT( MA_UseConsole, hhMonsterAI::Event_UseConsole ) + EVENT( MA_TestMeleeDef, hhMonsterAI::Event_TestMeleeDef ) + EVENT( MA_EnemyInVehicle, hhMonsterAI::Event_EnemyInVehicle ) + EVENT( MA_EnemyOnWallwalk, hhMonsterAI::Event_EnemyOnWallwalk ) + EVENT( MA_AlertAI, hhMonsterAI::Event_AlertAI ) + EVENT( MA_TestAnimMoveBlocked, hhMonsterAI::Event_TestAnimMoveBlocked ) + EVENT( MA_InGravityZone, hhMonsterAI::Event_InGravityZone ) + EVENT( MA_StartSoundDelay, hhMonsterAI::Event_StartSoundDelay ) + EVENT( MA_SetTeam, hhMonsterAI::Event_SetTeam ) + EVENT( MA_GetAttackPoint, hhMonsterAI::Event_GetAttackPoint) + EVENT( MA_HideNoDormant, hhMonsterAI::Event_HideNoDormant ) + EVENT( MA_SoundOnModel, hhMonsterAI::Event_SoundOnModel ) + EVENT( MA_ActivatePhysics, hhMonsterAI::Event_ActivatePhysics ) + EVENT( MA_IsVehicleDocked, hhMonsterAI::Event_IsVehicleDocked ) + EVENT( MA_EnemyInSpirit, hhMonsterAI::Event_EnemyInSpirit ) +END_CLASS + +void hhMonsterAI::Event_EnemyAimingAtMe() { + if ( enemy.IsValid() ) { + if ( enemy->GetAxis()[0] * -(enemy->GetOrigin() - GetOrigin()).ToNormal() ) { + idThread::ReturnInt( true ); + return; + } + } + + idThread::ReturnInt( false ); +} + +//A little after Spawn() or Show(), check if monster is stuck on wallwalk. +void hhMonsterAI::Event_InitialWallwalk() { + if ( IsHidden() ) { + return; + } + idMat3 initRot = spawnArgs.GetMatrix( "rotation", "1 0 0 0 1 0 0 0 1" ); + if ( initRot == mat3_identity ) { + return; + } + trace_t TraceInfo; + gameLocal.clip.TracePoint(TraceInfo, GetOrigin(), GetOrigin() - initRot[2]*100, GetPhysics()->GetClipMask(), this); + if( TraceInfo.fraction < 1.0f && health > 0 ) { + if ( gameLocal.GetMatterType(TraceInfo, NULL) == SURFTYPE_WALLWALK ) { + DisableIK(); + SetOrigin( TraceInfo.c.point + initRot * idVec3(0,0,1) ); + GetPhysics()->SetAxis( initRot ); + viewAxis = initRot; + SetGravity( -TraceInfo.c.normal * 1066 ); + physicsObj.SetClipModelAxis(); + renderEntity.axis = initRot; + + idVec3 local_dir; + physicsObj.GetAxis().ProjectVector( initRot[0], local_dir ); + ideal_yaw = idMath::AngleNormalize180( local_dir.ToYaw() ); + current_yaw = ideal_yaw; + } + } +} + +void hhMonsterAI::Event_EnemyOnSide() { + if( enemy.IsValid() ) { + idVec3 povPos, targetPos; + povPos = enemy->GetPhysics()->GetOrigin(); + targetPos = GetPhysics()->GetOrigin(); + idVec3 povToTarget = targetPos - povPos; + povToTarget.z = 0.f; + povToTarget.Normalize(); + float dot = GetPhysics()->GetAxis()[ 1 ] * povToTarget; + idThread::ReturnFloat( dot ); + } + else { + idThread::ReturnFloat( 0.f ); + } +} + +void hhMonsterAI::Event_EnemyIsA( const char* testclass ) { + idTypeInfo* type = idClass::GetClass( testclass ); + if( type ) { + if( enemy.IsValid() ) { + if( enemy->GetType()->IsType(*type) ) { + idThread::ReturnInt( 1 ); + return; + } + } + } + idThread::ReturnInt( 0 ); +} + +void hhMonsterAI::Event_SetMeleeRange( float newRange ) { + melee_range = newRange; +} + +// +// Event_UseReaction +// +// Notes: +// + Need to support the actual various types of Causes, not just this one specific case. +// + Need to support blends (in/out) on animation causes +void hhMonsterAI::Event_UseReaction() { + if( !AI_USING_REACTION ) { + AI_USING_REACTION = true; + AI_REACTION_FAILED = false; + AI_REACTION_ANIM = false; + } + + if( !CheckValidReaction() ) { + AI_USING_REACTION = false; + AI_REACTION_FAILED = false; + AI_REACTION_ANIM = false; + return; + } + + hhReaction *reaction = targetReaction.GetReaction(); + assert(reaction); // CheckValidReaction() should ensure this is a valid pointer. + if( reaction->desc->flags & hhReactionDesc::flag_Exclusive ) { + if ( !reaction->exclusiveOwner.IsValid() ) { + reaction->exclusiveOwner = this; + } else if ( reaction->exclusiveOwner != this ) { + AI_USING_REACTION = false; + AI_REACTION_FAILED = false; + AI_REACTION_ANIM = false; + return; + } + } + + idVec3 tp = GetTouchPos( reaction->causeEntity.GetEntity(), reaction->desc ); + idBounds tpb; + bool validTpb = GetTouchPosBound( reaction->desc, tpb ); + + if( AI_REACTION_ANIM ) { + if( torsoAnim.AnimDone( 0 ) || GetAnimator()->CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0.f ) { + AI_REACTION_ANIM = false; + Event_AnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 0 ); + Event_AnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 0 ); + ProcessEvent( &AI_EnablePain ); + FinishReaction(); + } + } + else { + //play reaction sound if one exists + if ( !ai_skipSpeech.GetBool() && gameLocal.GetTime() > nextSpeechTime ) { + idStr speechDesc = idStr("snd_speech_"); + speechDesc += hhReactionDesc::EffectToStr( reaction->desc->effect ); + speechDesc += idStr( "_" ); + speechDesc += targetReaction.entity->spawnArgs.GetString( "speech_name" ); + if ( ai_debugBrain.GetBool() ) { + gameLocal.Printf( "reaction speech(%s)\n", speechDesc.c_str() ); + } + const idKeyValue *kv = spawnArgs.MatchPrefix( speechDesc ); + if( kv ) { + StartSound(kv->GetKey(), SND_CHANNEL_VOICE, 0, true, NULL); + } + nextSpeechTime = gameLocal.GetTime() + int(spawnArgs.GetFloat( "speech_wait", "2.0" ) * 1000); + } + if( (validTpb && !NearEnoughTouchPos(reaction->causeEntity.GetEntity(), tp, tpb)) || (!validTpb && !ReachedPos(tp, move.moveCommand)) ) { + if ( !MoveToPosition( tp, true ) ) { + //position is unreachable so stop using this reaction + AI_USING_REACTION = false; + AI_REACTION_FAILED = false; + AI_REACTION_ANIM = false; + } + } else { + SlideToPosition( tp, 0.5 ); + StopMove( MOVE_STATUS_DONE ); + if( reaction->desc->flags & hhReactionDesc::flag_SnapToPoint ) { + SetOrigin( idVec3(tp.x, tp.y, tp.z) ); + } + //Turn to face direction specified by node + if( reaction->causeEntity->spawnArgs.GetFloat("face_dir") ) { + idAngles faceAngles = reaction->causeEntity->GetAxis()[0].ToAngles(); + ideal_yaw = faceAngles.yaw; + current_yaw = faceAngles.yaw; + SetAxis( reaction->causeEntity->GetAxis() ); + } + else if( reaction->desc->flags & hhReactionDesc::flag_AnimFaceCauseDir ) { + idAngles faceAngles = ( reaction->causeEntity->GetOrigin() - GetOrigin() ).ToAngles(); + ideal_yaw = faceAngles.yaw; + current_yaw = faceAngles.yaw; + idAngles newAngles = GetAxis().ToAngles(); + newAngles.yaw = faceAngles.yaw; + SetAxis( newAngles.ToMat3() ); + } + idEntity *ent = NULL; + switch( reaction->desc->cause ) { + case hhReactionDesc::Cause_Touch: + FinishReaction(); + break; + case hhReactionDesc::Cause_Use: + ent = targetReaction.entity.GetEntity(); + if( ent && ent->IsType(hhConsole::Type)) { + hhConsole *cons = static_cast(ent); + if(!cons->CanUse(this)) { + return; + } + cons->Use(this); + } + // Using vehicle -- just bail! + else if(ent->IsType(hhVehicle::Type)) { + EnterVehicle( static_cast(ent) ); + FinishReaction(); + return; + } else { + gameLocal.Warning("\n AI TRIED TO USE UNUSABLE ENTITY!"); + } + FinishReaction(); + break; + case hhReactionDesc::Cause_PlayAnim: + AI_REACTION_ANIM = true; + GetAnimator()->Clear( ANIMCHANNEL_TORSO, gameLocal.GetTime(), 0 ); + GetAnimator()->Clear( ANIMCHANNEL_TORSO, gameLocal.GetTime(), 0 ); + GetAnimator()->ClearAllAnims( gameLocal.GetTime(), 0 ); + Event_AnimState( ANIMCHANNEL_TORSO, "Torso_DoNothing", 0 ); + Event_AnimState(ANIMCHANNEL_LEGS, "Legs_DoNothing", 0 ); + Event_OverrideAnim( ANIMCHANNEL_LEGS ); + Event_PlayAnim( ANIMCHANNEL_TORSO, reaction->desc->anim ); + ProcessEvent(&AI_DisablePain); + break; + default: + break; + } + } + } +} + +// +// Event_FindReaction +// +void hhMonsterAI::Event_FindReaction( const char* effect ) { + idEntity* ent; + hhReaction* react; + hhReactionDesc::Effect react_effect; + idEntity* bestEnt = NULL; + float bestDistance = -1; + int bestRank = -1, bestReactIndex = -1; + + react_effect = hhReactionDesc::StrToEffect( effect ); + + if( react_effect == hhReactionDesc::Effect_Invalid ) { + gameLocal.Warning( "unknown effect '%s' requested from FindReaction", effect ); + idThread::ReturnEntity( NULL ); + return; + } + + for ( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if( !ent || ent->fl.isDormant || !ent->fl.refreshReactions ) { + continue; + } + + for( int j = 0; j < ent->GetNumReactions(); j++ ) { + react = ent->GetReaction( j ); + if( !react || !react->IsActive() ) { + continue; + } + if( react_effect != react->desc->effect ) { + continue; + } + if( react->desc->flags & hhReactionDesc::flag_Exclusive ) { //check exclusiveness + if ( react->exclusiveOwner.IsValid() && react->exclusiveOwner->health <= 0 ) { + react->exclusiveOwner.Clear(); + } + if( react->exclusiveOwner.GetEntity() && react->exclusiveOwner != this ) { + continue; + } + } + //Skip based on flag requirements + if( (react->desc->flags & hhReactionDesc::flagReq_RangeAttack) && !AI_HAS_RANGE_ATTACK ) { + continue; + } + // Skip monsters without melee attack + if( (react->desc->flags & hhReactionDesc::flagReq_MeleeAttack) && !AI_HAS_MELEE_ATTACK ) { + continue; + } + if( react->desc->flags & hhReactionDesc::flagReq_KeyValue ) { + idStr val; + if( spawnArgs.GetString(react->desc->key, "", val) ) { + if( val != react->desc->keyVal ) { + continue; + } + } + else { + continue; + } + } + // Skip monsters without specific animation? + if( react->desc->flags & hhReactionDesc::flagReq_Anim ) { + if( !GetAnimator()->HasAnim(react->desc->anim) ) { + continue; + } + } + + // Skip monsters in vehicles? + if( (react->desc->flags & hhReactionDesc::flagReq_NoVehicle) && InVehicle() ) { + continue; + } + + // Check actual specifics for reaction type + switch( react->desc->effect ) { + case hhReactionDesc::Effect_HaveFun: + case hhReactionDesc::Effect_Vehicle: + case hhReactionDesc::Effect_VehicleDock: + case hhReactionDesc::Effect_Heal: + case hhReactionDesc::Effect_ProvideCover: + case hhReactionDesc::Effect_Climb: + case hhReactionDesc::Effect_Passageway: + break; + case hhReactionDesc::Effect_DamageEnemy: + if ( enemy.IsValid() && react->desc->effectRadius > 0 ) { + float distSq = (enemy->GetOrigin() - react->causeEntity->GetOrigin()).LengthSqr(); + if ( distSq > react->desc->effectRadius * react->desc->effectRadius ) { + continue; + } + } + break; + case hhReactionDesc::Effect_Damage: + //jshtodo temp workaround for legacy stuff. remove when old reaction system is removed + if ( !ent->IsType( hhConsole::Type ) ) { + continue; + } + break; + default: + gameLocal.Error( "effect '%s' not supported yet under simple_ai", hhReactionDesc::EffectToStr(react->desc->effect) ); + } + // if we have actually gotten this far, do our intense calculations last.. + int rank = EvaluateReaction( react ); + if( rank == 0 ) { + continue; + } + +// We have a valid reaction... + float distSq = ( react->causeEntity->GetOrigin() - GetOrigin() ).LengthSqr(); + if ( bestRank == -1 || bestRank <= rank ) { // Check the reaction rank against our best rank + if (rank == bestRank && distSq > bestDistance) { // If they are the same rank, but the current one is farther then ignore it. + continue; + } + bestEnt = ent; + bestReactIndex = j; + bestDistance = distSq; + bestRank = rank; + } + } + } + + if ( bestEnt ) { + targetReaction.entity = bestEnt; + targetReaction.reactionIndex = bestReactIndex; + hhReaction *reaction = targetReaction.GetReaction(); + if( reaction && reaction->desc->flags & hhReactionDesc::flagReq_RangeAttack ) { + shootTarget = bestEnt; + } else { + shootTarget = NULL; + } + idThread::ReturnEntity( targetReaction.entity.GetEntity() ); + } else { + idThread::ReturnEntity( NULL ); + } +} + +// +// Event_AttackMissileEx() +// +void hhMonsterAI::Event_AttackMissileEx( const char *str, int boneDir ) { + const idDict *projDef = NULL; + idList parmList; + + hhUtils::SplitString( idStr(str), parmList, ' ' ); + + if( animator.GetJointHandle(parmList[0].c_str()) == INVALID_JOINT ) { + return gameLocal.Error( "Event_AttackMissileEx: Joint '%s' not found", parmList[0].c_str() ); + } + + if( parmList.Num() == 2 ) { + projDef = gameLocal.FindEntityDefDict( parmList[1].c_str(), false ); + } + + Event_AttackMissile( parmList[0].c_str(), projDef, boneDir ); +} + +//HUMANHEAD jsh PCF 4/27/06 initialized proj and made sure ReturnEntity is called +void hhMonsterAI::Event_AttackMissile( const char *jointname, const idDict *projDef, int boneDir ) { + idProjectile *proj = NULL; + + // Bonedir launch? + if((BOOL)boneDir) { + proj = hhProjectile::SpawnProjectile(projDef); + if ( proj ) { + idMat3 axis; + idVec3 muzzle; + GetMuzzle( jointname, muzzle, axis ); + proj->Create(this, muzzle, axis); + proj->Launch(muzzle, axis, vec3_zero); + } + } + else { + if ( shootTarget.IsValid() ) { + proj = LaunchProjectile( jointname, shootTarget.GetEntity(), true, projDef ); //HUMANHEAD mdc - pass projDef on for multiple proj support + } else { + proj = LaunchProjectile( jointname, enemy.GetEntity(), true, projDef ); //HUMANHEAD mdc - pass projDef on for multiple proj support + } + } + idThread::ReturnEntity( proj ); +} + +void hhMonsterAI::Event_HitCheck( idEntity *ent, const char *animname ) { + int anim; + idVec3 dir; + idVec3 local_dir; + idVec3 fromPos; + idMat3 axis; + idVec3 start; + trace_t tr; + float distance; + + if ( !AI_ENEMY_VISIBLE || !ent ) { + idThread::ReturnInt( false ); + return; + } + + anim = GetAnim( ANIMCHANNEL_LEGS, animname ); + if ( !anim ) { + idThread::ReturnInt( false ); + return; + } + + //// just do a ray test if close enough + //if ( ent->GetPhysics()->GetAbsBounds().IntersectsBounds( physicsObj.GetAbsBounds().Expand( 16.0f ) ) ) { + // Event_CanHitEnemy(); + // return; + //} + + // calculate the world transform of the launch position + const idVec3 &org = physicsObj.GetOrigin(); + dir = ent->GetOrigin() - org; + physicsObj.GetGravityAxis().ProjectVector( dir, local_dir ); + local_dir.z = 0.0f; + local_dir.ToVec2().Normalize(); + axis = local_dir.ToMat3(); + fromPos = physicsObj.GetOrigin() + missileLaunchOffset[ anim ] * axis; + + if ( projectileClipModel == NULL ) { + CreateProjectileClipModel(); + } + + // check if the owner bounds is bigger than the projectile bounds + const idBounds &ownerBounds = physicsObj.GetAbsBounds(); + const idBounds &projBounds = projectileClipModel->GetBounds(); + 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(); + } + + gameLocal.clip.Translation( tr, start, fromPos, projectileClipModel, mat3_identity, MASK_SHOT_RENDERMODEL, this ); + fromPos = tr.endpos; + + if ( GetAimDir( fromPos, ent, this, dir ) ) { + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +void hhMonsterAI::Event_CreateMonsterPortal(void) { + static const char * passPrefix = "portal_"; + const char * portalDef; + idEntity * portal; + idDict portalArgs; + idList xferKeys; + idList xferValues; + const idKeyValue * buddyKV; + + + // Find out which portal def to spawn - If none specified, then exit; + buddyKV = spawnArgs.FindKey( "portal_buddy" ); + if ( buddyKV && buddyKV->GetValue().Length() && gameLocal.FindEntity(buddyKV->GetValue().c_str()) ) { + // Case of a valid portal_buddy key, make a real portal + portalDef = spawnArgs.GetString( "def_portal" ); + } else { + portalDef = spawnArgs.GetString( "def_fakeportal" ); + } + + if ( !portalDef || !portalDef[0] ) { + return; + } + + // Set the origin of the portal to us. + portalArgs.SetVector( "origin", GetOrigin() ); + + // Pass along any angle key, if set. + if ( spawnArgs.GetBool( "portal_face" ) && enemy.IsValid() ) { + portalArgs.SetInt( "angle", (enemy->GetOrigin() - GetOrigin() ).ToAngles().yaw ); + ideal_yaw = ( enemy->GetOrigin() - GetOrigin() ).ToAngles().yaw; + current_yaw = ideal_yaw; + } else if ( spawnArgs.GetString( "angle", NULL) ) { + portalArgs.Set( "angle", spawnArgs.GetString( "angle" ) ); + } else { + portalArgs.Set( "rotation", spawnArgs.GetString( "rotation" ) ); + } + + // Pass along all 'portal_' keys to the portal's spawnArgs; + hhUtils::GetKeysAndValues( spawnArgs, passPrefix, xferKeys, xferValues ); + for ( int i = 0; i < xferValues.Num(); ++i ) { + xferKeys[ i ].StripLeadingOnce( passPrefix ); + //gameLocal.Printf( "Passing %s => %s\n", xferKeys[ i ].c_str(), xferValues[ i ].c_str() ); + portalArgs.Set( xferKeys[ i ].c_str(), xferValues[ i ].c_str() ); + } + + // Set the name of the associated game portal so it can be turned on and off + portalArgs.Set( "gamePortalName", GetName() ); + + // Spawn the portal + portal = gameLocal.SpawnObject( portalDef, &portalArgs ); + if ( !portal ) { + return; + } + + // Move the portal up some pre determinted amt, since its origin is in the middle of it + float offset = spawnArgs.GetFloat( "offset_portal", 0 ); + portal->GetPhysics()->SetOrigin( portal->GetPhysics()->GetOrigin() + (portal->GetAxis()[2] * offset) ); + + // Move the portal by some offset vector + idVec3 vectorOffset = spawnArgs.GetVector( "offset_portal_vector", "0 0 0" ); + portal->GetPhysics()->SetOrigin( portal->GetPhysics()->GetOrigin() + (portal->GetAxis() * vectorOffset ) ); + + // Update the camera stuff + portal->ProcessEvent( &EV_UpdateCameraTarget ); + + // Open the portal - Need to delay this, so that PostSpawn gets called/sets up the partner portal + //? Should we always pass in the player? + portal->PostEventSec( &EV_Activate, 0, gameLocal.GetLocalPlayer() ); + // Maybe wait for it to finish? + + +} + +void hhMonsterAI::Event_GetShootTarget() { + idThread::ReturnEntity( shootTarget.GetEntity() ); +} + +void hhMonsterAI::Event_TriggerReactEnt(void) { + if ( targetReaction.entity.IsValid() ) { + targetReaction.entity->PostEventMS( &EV_Activate, 0, this ); + } +} + +void hhMonsterAI::Event_GetVehicle() { + idThread::ReturnEntity( vehicleInterface->GetVehicle() ); +} + +void hhMonsterAI::Event_ReachedEntity( idEntity *ent ) { + if ( ent ) { + idVec3 pos = ent->GetPhysics()->GetOrigin(); + if ( physicsObj.GetAbsBounds().IntersectsBounds( idBounds( pos ).Expand( 8.0f ) ) ) { + idThread::ReturnInt( true ); + return; + } + } + + idThread::ReturnInt( false ); +} + +void hhMonsterAI::Event_EnemyOnSpawn(void) +{ + idEntity *ent = NULL; + idStr entName; + if(spawnArgs.GetString("enemy_on_spawn"," ", entName)) + { + if ( entName == idStr("closest_player") || entName == idStr("players_in_view") ) { + ent = GetClosestPlayer(); + } + if ( !ent ) { + ent = gameLocal.FindEntity(entName); + } + if( ent && ent->IsType(idActor::Type) ) { + SetEnemy( static_cast(ent) ); + } + } +} + +void hhMonsterAI::Event_SpawnFX( char *fxFile ) { + + if ( !fxFile || !fxFile[0] ) { + return; + } + + idVec3 offset; + hhFxInfo fxInfo; + + offset = spawnArgs.GetVector( "particle_offset", "0 0 0" ); + + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfo( fxFile, GetOrigin() + offset * GetAxis(), GetAxis(), &fxInfo ); + +} + +void hhMonsterAI::Event_SplashDamage(char *damage) { + + if(!damage || !damage[0]) + return; + + idStr splash_damage; + + // Need to set takedamage to false in order to prevent infinite loop! + //fl.takedamage = false; + splash_damage = spawnArgs.GetString(damage); + if ( splash_damage.Length() ) { + gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), NULL, NULL, this, this, splash_damage ); + } +} + +void hhMonsterAI::Event_FollowPath( const char *pathName ) { + if ( scriptObject.HasObject() ) { + const function_t *func; + idThread *thread; + + func = scriptObject.GetFunction( "follow_alternate_path" ); + if ( !func ) { + gameLocal.Error( "Function 'follow_alternate_path' not found on entity '%s' for function call from '%s'", name.c_str(), name.c_str() ); + } + if ( func->type->NumParameters() != 1 ) { + gameLocal.Error( "Function 'follow_alternate_path' on entity '%s' has the wrong number of parameters for function call from '%s'", name.c_str(), name.c_str() ); + } + if ( !scriptObject.GetTypeDef()->Inherits( func->type->GetParmType( 0 ) ) ) { + gameLocal.Error( "Function 'follow_alternate_path' on entity '%s' is the wrong type for function call from '%s'", name.c_str(), name.c_str() ); + } + spawnArgs.Set( "alt_path", pathName ); + // create a thread and call the function + thread = new idThread(); + thread->CallFunction( this, func, true ); + thread->Start(); + } +} + +void hhMonsterAI::Event_SetVehicleState() { + const function_t *newstate = NULL; + if ( GetVehicleInterface()->UnderScriptControl() ) { + newstate = GetScriptFunction( "state_Nothing" ); + } + if ( newstate ) { + SetState( newstate ); + } +} + +void hhMonsterAI::Event_Subtitle( idList* parmList ) { + if ( !parmList || !parmList->Num() ) { + return; + } + idPlayer *player = gameLocal.GetLocalPlayer(); + player->hud->SetStateInt("subtitlex", 0 ); + player->hud->SetStateInt("subtitley", 400 ); + player->hud->SetStateInt("subtitlecentered", true); + player->hud->SetStateString("subtitletext", common->GetLanguageDict()->GetString( (*parmList)[0].c_str() )); + player->hud->StateChanged(gameLocal.time); + player->hud->HandleNamedEvent("DisplaySubtitle"); + + CancelEvents(&MA_SubtitleOff); + if ( parmList->Num() == 1 ) { + PostEventSec(&MA_SubtitleOff, (float)atof((*parmList)[1].c_str())); + } else { + PostEventSec(&MA_SubtitleOff, 2.0); + } +} + +void hhMonsterAI::Event_SubtitleOff() { + idPlayer *player = gameLocal.GetLocalPlayer(); + player->hud->HandleNamedEvent("RemoveSubtitleInstant"); +} + +/* +===================== +hhMonsterAI::Event_GetLastReachableEnemyPos +===================== +*/ +void hhMonsterAI::Event_GetLastReachableEnemyPos() { + idThread::ReturnVector( lastReachableEnemyPos ); +} + +void hhMonsterAI::Event_EnableEyelook() { + allowEyeFocus = true; +} + +void hhMonsterAI::Event_DisableEyelook() { + allowEyeFocus = false; +} + +void hhMonsterAI::Event_EnableHeadlook() { + allowJointMod = true; +} + +void hhMonsterAI::Event_DisableHeadlook() { + allowJointMod = false; +} + +void hhMonsterAI::Event_FacingEnemy( float range ) { + if ( FacingEnemy( range ) ) { + idThread::ReturnInt( 1 ); + } else { + idThread::ReturnInt( 0 ); + } +} + +bool hhMonsterAI::FacingEnemy( float range ) { + if ( !enemy.IsValid() ) { + return false; + } + + if ( !turnRate ) { + return true; + } + + idVec3 dir; + idVec3 local_dir; + float lengthSqr; + float local_yaw; + + dir = enemy->GetOrigin() - physicsObj.GetOrigin(); + physicsObj.GetAxis().ProjectVector( dir, local_dir ); + local_dir.z = 0.0f; + lengthSqr = local_dir.LengthSqr(); + local_yaw = idMath::AngleNormalize180( local_dir.ToYaw() ); + + float diff; + + diff = idMath::AngleNormalize180( current_yaw - local_yaw ); + return ( idMath::Fabs( diff ) < range ); +} + +void hhMonsterAI::Event_AllowFall( int allowFall ) { + bCanFall = allowFall != 0; +} + +void hhMonsterAI::Event_BossBar( int onOff ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( !player || !player->hud ) { + return; + } + + if ( bBossBar ) { + if ( !onOff ) { + player->hud->HandleNamedEvent("HideProgressBar"); + player->hud->StateChanged(gameLocal.time); + bBossBar = false; + } + } else { + if ( onOff ) { + player->hud->HandleNamedEvent("ShowProgressBar"); + player->hud->SetStateBool( "progressbar", true ); + bBossBar = true; + } + } +} + +void hhMonsterAI::Event_FallNow() { + SetGravity( DEFAULT_GRAVITY_VEC3 ); + GetPhysics()->SetLinearVelocity( idVec3( 0,0,-500 ) ); +} + +//taken from idEntity::Event_PlayerCanSee and removed trace +void hhMonsterAI::Event_InPlayerFov() { + int i; + idEntity *ent; + hhPlayer *player; + trace_t traceInfo; + bool result = false; + + // Check if this entity is in the player's PVS + if ( gameLocal.InPlayerPVS( this ) ) { + for ( i = 0; i < gameLocal.numClients ; i++ ) { + ent = gameLocal.entities[ i ]; + + if ( !ent || !ent->IsType( hhPlayer::Type ) ) { + continue; + } + + // Get the player + player = static_cast( ent ); + + // Check if the entity is in the player's FOV, based upon the "fov" key/value + if ( player->CheckYawFOV( this->GetOrigin() ) ) { + result = true; + } + } + } + + if ( result ) { + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +void hhMonsterAI::Event_EnemyIsSpirit( hhPlayer *player, hhSpiritProxy *proxy ) { + HH_ASSERT( player == enemy.GetEntity() ); + //HUMANHEAD jsh PCF 4/29/06 stop looking and make enemy not visible for a frame + Event_LookAtEntity( NULL, 0.0f ); + AI_ENEMY_VISIBLE = false; + enemy = proxy; +} + +void hhMonsterAI::Event_EnemyIsPhysical( hhPlayer *player, hhSpiritProxy *proxy ) { + // If we weren't targetting the physical player before, and we can't see them now, + // lose target + //HUMANHEAD jsh PCF 4/29/06 stop looking and make enemy not visible for a frame + Event_LookAtEntity( NULL, 0.0f ); + AI_ENEMY_VISIBLE = false; + if ( !proxy && !CanSee( player, true ) ) { + enemy = NULL; + } else { + enemy = player; + } +} + +void hhMonsterAI::Event_IsRagdoll() { + if ( af.IsActive() ) { + idThread::ReturnInt( true ); + return; + } + idThread::ReturnInt( false ); +} + +void hhMonsterAI::Event_MoveDone() { + if ( AI_MOVE_DONE ) { + idThread::ReturnInt( true ); + return; + } + idThread::ReturnInt( false ); +} + +void hhMonsterAI::Event_SetShootTarget(idEntity *ent) { + shootTarget = ent; +} + +void hhMonsterAI::Event_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; + } + + if ( duration == -1 ) { + focusTime = -1; + } else { + focusTime = gameLocal.time + SEC2MS( duration ); + } +} + +void hhMonsterAI::Event_LookAtEnemy( float duration ) { + idActor *enemyEnt; + + enemyEnt = enemy.GetEntity(); + if ( ( enemyEnt != focusEntity.GetEntity() ) || ( focusTime < gameLocal.time ) ) { + focusEntity = enemyEnt; + alignHeadTime = gameLocal.time; + forceAlignHeadTime = gameLocal.time + SEC2MS( 1 ); + blink_time = 0; + } + + if ( duration == -1 ) { + focusTime = -1; + } else { + focusTime = gameLocal.time + SEC2MS( duration ); + } +} + +void hhMonsterAI::Event_SetLookOffset( idAngles const &ang ) { + lookOffset = ang; +} + +void hhMonsterAI::Event_SetHeadFocusRate( float rate ) { + headFocusRate = rate; +} + +void hhMonsterAI::Event_EnemyInGravityZone(void) { + if ( !enemy.IsValid() ) { + idThread::ReturnFloat( 1.0f ); + return; + } + idThread::ReturnFloat( enemy->InGravityZone() ); +} + +void hhMonsterAI::Event_FlyZip() { + //if enemy is real far away, instantly teleport to a spot near him + if ( !enemy.IsValid() || move.moveType != MOVETYPE_FLY ) { + return; + } + + //find a good spot to try and teleport to + float distance = spawnArgs.GetFloat( "zip_range", "700" ); + idVec3 testPoint; + idVec3 finalPoint = vec3_zero; + bool clipped = false; + float yaw = (GetOrigin() - enemy->GetOrigin()).ToYaw(); + idBounds bounds; + idVec3 size; + bounds.Zero(); + if ( spawnArgs.GetVector( "mins", NULL, bounds[0] ) && + spawnArgs.GetVector( "maxs", NULL, bounds[1] ) ) { + } 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 ); + } else { // Default bounds + bounds.Expand( 1.0f ); + } + + //test 8 points around the enemy, starting with one directly in front of it + for ( int i=0;i<8;i++ ) { + testPoint = enemy->GetOrigin() + distance * idAngles( 0, yaw, 0 ).ToForward(); + yaw += 45; + if ( yaw > 360 ) { + yaw -= 360; + } + + int contents = hhUtils::ContentsOfBounds( bounds, testPoint, mat3_identity, this); + if ( contents & CONTENTS_MONSTERCLIP ) { + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorRed, GetOrigin(), testPoint, 10, 999999 ); + } + continue; + } + + //make sure we can path there + int toAreaNum = PointReachableAreaNum( testPoint ); + int areaNum = PointReachableAreaNum( physicsObj.GetOrigin() ); + aasPath_t path; + if ( !toAreaNum || !PathToGoal( path, areaNum, physicsObj.GetOrigin(), toAreaNum, testPoint ) ) { + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorRed, GetOrigin(), testPoint, 10, 10000 ); + } + continue; + } + + //passed all tests use this point + finalPoint = testPoint; + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorGreen, GetOrigin(), testPoint, 10, 10000 ); + } + break; + } + + if ( finalPoint != vec3_zero ) { + //do the actual teleport + GetPhysics()->SetOrigin( testPoint + idVec3( 0, 0, CM_CLIP_EPSILON ) ); + GetPhysics()->SetLinearVelocity( vec3_origin ); + viewAxis = (enemy->GetOrigin() - GetOrigin()).ToMat3(); + UpdateVisuals(); + } +} + +void hhMonsterAI::Event_UseConsole( idEntity *ent ) { + if ( !ent || !ent->IsType(hhConsole::Type) ) { + return; + } + hhConsole *cons = static_cast(ent); + if(!cons->CanUse(this)) { + return; + } + cons->Use(this); +} + +void hhMonsterAI::Event_TestMeleeDef( const char *meleeDefName ) const { + bool canMelee = TestMeleeDef( meleeDefName ); + + if ( canMelee ) { + idThread::ReturnFloat( 1.0f ); + } else { + idThread::ReturnFloat( 0.0f ); + } +} + +void hhMonsterAI::Event_EnemyInVehicle() { + if ( enemy.IsValid() && enemy->InVehicle() ) { + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +void hhMonsterAI::Event_HeardSound( int ignore_team ) { + // overridden to use hearingRange instead of AI_HEARING_RANGE + // check if we heard any sounds in the last frame + idActor *actor = gameLocal.GetAlertEntity(); + 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(); + //allow the sound's own radius to override hearingRange, if it is set + if ( gameLocal.lastAIAlertRadius ) { + if ( dist < Square( gameLocal.lastAIAlertRadius ) ) { + idThread::ReturnEntity( actor ); + return; + } + } else if ( dist < Square( hearingRange ) ) { + idThread::ReturnEntity( actor ); + return; + } + } +} + +void hhMonsterAI::Event_AlertAI( idEntity *ent, float radius ) { + if ( !ent || radius <= 0 ) { + return; + } + gameLocal.AlertAI( ent, radius ); +} + +void hhMonsterAI::Event_EnemyOnWallwalk() { + if ( enemy.IsValid() && enemy->IsType( hhPlayer::Type ) ) { + hhPlayer *player = static_cast(enemy.GetEntity()); + if ( player && player->IsWallWalking() ) { + idThread::ReturnInt( true ); + return; + } + } + + idThread::ReturnInt( false ); +} + +/* +===================== +hhMonsterAI::Event_TestAnimMoveBlocked +===================== +*/ +void hhMonsterAI::Event_TestAnimMoveBlocked( const char *animname ) { + int anim; + predictedPath_t path; + idVec3 moveVec; + + 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, ideal_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 ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugLine( colorGreen, physicsObj.GetOrigin(), physicsObj.GetOrigin() + moveVec, gameLocal.msec ); + gameRenderWorld->DebugBounds( path.endEvent == 0 ? colorYellow : colorRed, physicsObj.GetBounds(), physicsObj.GetOrigin() + moveVec, gameLocal.msec ); + } + + HH_ASSERT( path.endEvent == 0 && !path.blockingEntity || path.blockingEntity ); + + idThread::ReturnEntity( const_cast ( path.blockingEntity ) ); +} + +void hhMonsterAI::Event_InGravityZone() { + idThread::ReturnFloat( InGravityZone() ); +} + +void hhMonsterAI::Event_StartSoundDelay( const char *soundName, int channel, int netSync, float delay ) { + if ( delay < 0 ) { + delay = 0; + } + PostEventSec( &EV_StartSound, delay, soundName, channel, netSync ); +} + +void hhMonsterAI::Event_SetTeam( int new_team ) { + team = new_team; +} + +void hhMonsterAI::Event_HideNoDormant() { + HideNoDormant(); +} + +void hhMonsterAI::Event_GetAttackPoint( void ) { + //if enemy is real far away, instantly teleport to a spot near him + if ( !enemy.IsValid() ) { + idThread::ReturnVector( vec3_zero ); + return; + } + + //find a good spot to attack from + float distance = spawnArgs.GetFloat( "attack_range", "500" ); + idVec3 testPoint; + idVec3 finalPoint = vec3_zero; + bool clipped = false; + float yaw = (GetOrigin() - enemy->GetOrigin()).ToYaw(); + int num, i, j; + idClipModel *cm; + idClipModel *clipModels[ MAX_GENTITIES ]; + idBounds bounds; + + //test 8 points around the enemy, starting with one directly in front of it + idList listo; + for ( i=0;i<8;i++ ) { + testPoint = enemy->GetOrigin() + distance * idAngles( 0, yaw, 0 ).ToForward(); + testPoint.z += spawnArgs.GetFloat( "attack_z", "300" ); + yaw += 45; + if ( yaw > 360 ) { + yaw -= 360; + } + if ( yaw == 180 ) { + continue; + } + + //make sure it wont clip into anything at testPoint + clipped = false; + bounds.FromTransformedBounds( GetPhysics()->GetBounds(), testPoint, GetPhysics()->GetAxis() ); + num = gameLocal.clip.ClipModelsTouchingBounds( bounds, MASK_MONSTERSOLID, clipModels, MAX_GENTITIES ); + for ( j = 0; j < num; j++ ) { + cm = clipModels[ j ]; + // don't check render entities + if ( cm->IsRenderModel() ) { + continue; + } + idEntity *hit = cm->GetEntity(); + if ( ( hit == this ) || !hit->fl.takedamage ) { + continue; + } + if ( physicsObj.ClipContents( cm ) ) { + clipped = true; + break; + } + } + if ( clipped ) { + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugBounds( colorRed, bounds, vec3_origin, 10000 ); + } + continue; + } else { + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugBounds( colorGreen, bounds, vec3_origin, 10000 ); + } + } + + //make sure we can path there + if ( !PointReachableAreaNum( testPoint ) ) { + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorRed, GetOrigin(), testPoint, 10, 10000 ); + } + continue; + } else if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorGreen, GetOrigin(), testPoint, 10, 10000 ); + } + + listo.Append( testPoint ); + } + + if ( listo.Num() ) { + finalPoint = listo[gameLocal.random.RandomInt(listo.Num())]; + } + if ( finalPoint != vec3_zero ) { + idThread::ReturnVector( finalPoint ); + return; + } else { + idThread::ReturnVector( vec3_zero ); + return; + } +} + +void hhMonsterAI::Event_SoundOnModel() { + soundOnModel = !soundOnModel; +} + +void hhMonsterAI::Event_ActivatePhysics() { + ActivatePhysics( this ); +} + +void hhMonsterAI::Event_IsVehicleDocked() { + if ( GetVehicleInterfaceLocal()->IsVehicleDocked() ) { + idThread::ReturnInt( true ); + return; + } + idThread::ReturnInt( false ); +} + +void hhMonsterAI::Event_EnemyInSpirit() { + if ( enemy.IsValid() && enemy->IsType( hhPlayer::Type ) ) { + hhPlayer *player = static_cast( enemy.GetEntity() ); + if ( player && player->IsSpiritWalking() ) { + idThread::ReturnInt( true ); + return; + } + } + + idThread::ReturnInt( false ); +} + +void hhMonsterAI::Event_SetNeverDormant( int enable ) { + if ( head.IsValid() ) { + head->fl.neverDormant = ( enable != 0 ); + } + fl.neverDormant = ( enable != 0 ); + dormantStart = 0; +} + diff --git a/src/Prey/game_mountedgun.cpp b/src/Prey/game_mountedgun.cpp new file mode 100644 index 0000000..c46cd3c --- /dev/null +++ b/src/Prey/game_mountedgun.cpp @@ -0,0 +1,1048 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_ScanForClosestTarget( "" ); +const idEventDef EV_StartAttack( "" ); +const idEventDef EV_NextCycleAnim( "", "ds" ); + +CLASS_DECLARATION( hhAnimatedEntity, hhMountedGun ) + EVENT( EV_Activate, hhMountedGun::Event_Activate ) + EVENT( EV_Deactivate, hhMountedGun::Event_Deactivate ) + EVENT( EV_PostSpawn, hhMountedGun::Event_PostSpawn ) + EVENT( EV_ScanForClosestTarget, hhMountedGun::Event_ScanForClosestTarget ) + EVENT( EV_StartAttack, hhMountedGun::Event_StartAttack ) + + EVENT( EV_NextCycleAnim, hhMountedGun::Event_NextCycleAnim ) +END_CLASS + +/* +================ +hhMountedGun::hhMountedGun +================ +*/ +hhMountedGun::hhMountedGun() { + yawVelocity = 0.0f; +} + +/* +================ +hhMountedGun::Spawn +================ +*/ +void hhMountedGun::Spawn( void ) { + targetingLaser = NULL; + boneHub.Clear(); + boneGun.Clear(); + boneOrigin.Clear(); + + fl.takedamage = false; // Gun doesn't take damage unless it is active + + deathwalkDelay = SEC2MS( spawnArgs.GetInt( "deathwalk_delay", "1" ) ); + burstRateOfFire = SEC2MS( spawnArgs.GetFloat("burstRateOfFire") ); + shotsPerBurst = spawnArgs.GetInt("shotsPerBurst"); + shotsPerBurstCounter = 0; + + firingRange = spawnArgs.GetFloat( "firingRange" ); + nextFireTime = 0; + projectileDict = gameLocal.FindEntityDefDict( spawnArgs.GetString( "def_projectile" ) ); + if ( !projectileDict ) { + gameLocal.Error( "Unknown def_projectile: %s\n", spawnArgs.GetString( "def_projectile" ) ); + } + + // Set up muzzle flash light + memset( &muzzleFlash, 0, sizeof(renderLight_t) ); + muzzleFlash.lightId = 500 + entityNumber; + muzzleFlash.shader = declManager->FindMaterial( spawnArgs.GetString("mtr_flashShader"), false ); + muzzleFlashHandle = -1; + muzzleFlash.pointLight = true; + muzzleFlashEnd = gameLocal.GetTime(); + muzzleFlash.lightRadius = spawnArgs.GetVector( "flashSize" ); + muzzleFlashDuration = SEC2MS( spawnArgs.GetFloat("flashTime") ); + idVec3 flashColor = spawnArgs.GetVector( "flashColor" ); + muzzleFlash.shaderParms[ SHADERPARM_RED ] = flashColor[0]; + muzzleFlash.shaderParms[ SHADERPARM_GREEN ] = flashColor[1]; + muzzleFlash.shaderParms[ SHADERPARM_BLUE ] = flashColor[2]; + muzzleFlash.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; + + SpawnParts(); + + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + physicsObj.SetContents( CONTENTS_SOLID ); + physicsObj.SetClipMask( GetPhysics()->GetContents() ); + physicsObj.SetOrigin( GetOrigin() ); + physicsObj.SetAxis( GetAxis() ); + SetPhysics( &physicsObj ); + + prevIdealLookAngle.Zero(); + + pitchController.Clear(); + pitchController.Setup( this, spawnArgs.GetString("bone_Hub"), idAngles(-90, 0, 0), idAngles(90, 0, 0), idAngles(90, 0, 0), idAngles(1,0,0) ); + + channelBody = ChannelName2Num( "body" ); + channelBarrel = ChannelName2Num( "barrel" ); + + PlayCycle( channelBody, "idle", 0 ); + + PVSArea = gameLocal.pvs.GetPVSArea( GetOrigin() ); + + PostEventMS( &EV_PostSpawn, 0 ); + + gunMode = GM_DORMANT; + team = spawnArgs.GetInt( "team", "1" ); + + PlayAnim( ANIMCHANNEL_ALL, "idle_close", 0 ); +} + +void hhMountedGun::Save(idSaveGame *savefile) const { + savefile->WriteInt( team ); + savefile->WriteInt( gunMode ); + pitchController.Save( savefile ); + trackMover.Save( savefile ); + targetingLaser.Save( savefile ); + boneHub.Save( savefile ); + boneGun.Save( savefile ); + boneExhaust.Save( savefile ); + boneOrigin.Save( savefile ); + enemy.Save( savefile ); + savefile->WriteFloat( enemyRange ); + savefile->WriteFloat( firingRange ); + savefile->WriteInt( nextFireTime ); + savefile->WriteFloat( burstRateOfFire ); + savefile->WriteInt( shotsPerBurst ); + savefile->WriteInt( shotsPerBurstCounter ); + savefile->WriteRenderLight( muzzleFlash ); + //HUMANHEAD PCF mdl 05/04/06 - Don't save light handles + //savefile->WriteInt( muzzleFlashHandle ); + savefile->WriteInt( muzzleFlashEnd ); + savefile->WriteInt( muzzleFlashDuration ); + savefile->WriteAngles( idealLookAngle ); + savefile->WriteAngles( prevIdealLookAngle ); + savefile->WriteStaticObject( physicsObj ); + savefile->WriteInt( animDoneTime ); + savefile->WriteInt( PVSArea ); + savefile->WriteInt( channelBody ); + savefile->WriteInt( channelBarrel ); + savefile->WriteFloat( yawVelocity ); + savefile->WriteInt( deathwalkDelay ); +} + +void hhMountedGun::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( team ); + savefile->ReadInt( reinterpret_cast ( gunMode ) ); + pitchController.Restore( savefile ); + trackMover.Restore( savefile ); + targetingLaser.Restore( savefile ); + boneHub.Restore( savefile ); + boneGun.Restore( savefile ); + boneExhaust.Restore( savefile ); + boneOrigin.Restore( savefile ); + enemy.Restore( savefile ); + savefile->ReadFloat( enemyRange ); + savefile->ReadFloat( firingRange ); + savefile->ReadInt( nextFireTime ); + savefile->ReadFloat( burstRateOfFire ); + savefile->ReadInt( shotsPerBurst ); + savefile->ReadInt( shotsPerBurstCounter ); + savefile->ReadRenderLight( muzzleFlash ); + //HUMANHEAD PCF mdl 05/04/06 - Don't save light handles + //savefile->ReadInt( muzzleFlashHandle ); + savefile->ReadInt( muzzleFlashEnd ); + savefile->ReadInt( muzzleFlashDuration ); + savefile->ReadAngles( idealLookAngle ); + savefile->ReadAngles( prevIdealLookAngle ); + savefile->ReadStaticObject( physicsObj ); + savefile->ReadInt( animDoneTime ); + savefile->ReadInt( PVSArea ); + savefile->ReadInt( channelBody ); + savefile->ReadInt( channelBarrel ); + savefile->ReadFloat( yawVelocity ); + savefile->ReadInt( deathwalkDelay ); + + projectileDict = gameLocal.FindEntityDefDict( spawnArgs.GetString( "def_projectile" ) ); + if ( !projectileDict ) { + gameLocal.Error( "Unknown def_projectile: %s\n", spawnArgs.GetString( "def_projectile" ) ); + } + RestorePhysics( &physicsObj ); + //HUMANHEAD PCF mdl 05/04/06 - Don't save light handles + muzzleFlashHandle = -1; +} + +/* +================ +hhMountedGun::~hhMountedGun +================ +*/ +hhMountedGun::~hhMountedGun( void ) { + SAFE_REMOVE( targetingLaser ); + + SAFE_FREELIGHT( muzzleFlashHandle ); + + trackMover = NULL; +} + +/* +================ +hhMountedGun::SpawnParts +================ +*/ +void hhMountedGun::SpawnParts() { + boneHub.name = spawnArgs.GetString("bone_Hub"); + boneHub.handle = GetAnimator()->GetJointHandle( boneHub.name.c_str() ); + + boneGun.name = spawnArgs.GetString("bone_Gun"); + boneGun.handle = GetAnimator()->GetJointHandle( boneGun.name.c_str() ); + + boneOrigin.name = spawnArgs.GetString("bone_Origin"); + boneOrigin.handle = GetAnimator()->GetJointHandle( boneOrigin.name.c_str() ); + + UpdateBoneInfo(); + + targetingLaser = hhBeamSystem::SpawnBeam( boneGun.origin, spawnArgs.GetString("beam") ); + if( targetingLaser.IsValid() ) { + targetingLaser->MoveToJoint( this, boneGun.name.c_str() ); + targetingLaser->BindToJoint( this, boneGun.name.c_str(), false ); + targetingLaser->Activate( false ); + } +} + +/* +===================== +hhMountedGun::Hide +===================== +*/ +void hhMountedGun::Hide() { + hhAnimatedEntity::Hide(); + + if( targetingLaser.IsValid() ) { + targetingLaser->Hide(); + } +} + +/* +===================== +hhMountedGun::Show +===================== +*/ +void hhMountedGun::Show() { + hhAnimatedEntity::Show(); + + if( targetingLaser.IsValid() ) { + targetingLaser->Show(); + } +} + +/* +===================== +hhMountedGun::DetermineTargetingLaserEndPoint +===================== +*/ +idVec3 hhMountedGun::DetermineTargetingLaserEndPoint() { + trace_t traceInfo; + idVec3 start = boneGun.origin; + idVec3 dist = boneGun.axis[0] * CM_MAX_TRACE_DIST; + + gameLocal.clip.TracePoint( traceInfo, start, start + dist, MASK_SHOT_BOUNDINGBOX, this ); + + return (enemy.IsValid()) ? traceInfo.endpos + enemy->GetAxis()[2] * -10.0f : traceInfo.endpos; +} + +/* +===================== +hhMountedGun::Ticker +===================== +*/ +void hhMountedGun::Ticker( void ) { + if( !pitchController.IsFinishedMoving(PITCH) ) { + pitchController.Update( gameLocal.GetTime() ); + } + + UpdateBoneInfo(); + + if( targetingLaser.IsValid() && !targetingLaser->IsHidden() ) { + targetingLaser->SetTargetLocation( DetermineTargetingLaserEndPoint() ); + } + + UpdateMuzzleFlash(); +} + +/* +================ +hhMountedGun::UpdateOrientation +================ +*/ +void hhMountedGun::UpdateOrientation() { + SetAngles( idAngles( 0, idealLookAngle.yaw, 0 ) ); + pitchController.TurnTo( idealLookAngle ); +} + +/* +================ +hhMountedGun::UpdateBoneInfo +================ +*/ +void hhMountedGun::UpdateBoneInfo() { + idVec3 TempVec; + + GetJointWorldTransform( boneHub.handle, boneHub.origin, boneHub.axis ); + GetJointWorldTransform( boneGun.handle, boneGun.origin, boneGun.axis ); + + GetJointWorldTransform( boneOrigin.handle, boneOrigin.origin, boneOrigin.axis ); +} + +/* +================ +hhMountedGun::SetIdealLookAngle +================ +*/ +void hhMountedGun::SetIdealLookAngle( const idAngles& LookAngle ) { + idealLookAngle.pitch = -hhMath::AngleNormalize180( LookAngle.pitch ); + idealLookAngle.yaw = hhMath::AngleNormalize360( LookAngle.yaw ); + idealLookAngle.roll = 0.0f; + + if( prevIdealLookAngle == idealLookAngle ) { + return; + } + + prevIdealLookAngle = idealLookAngle; + + UpdateOrientation(); +} + +/* +================ +hhMountedGun::AttemptToRemoveMuzzleFlash +================ +*/ +void hhMountedGun::AttemptToRemoveMuzzleFlash() { + if( gameLocal.GetTime() >= muzzleFlashEnd ) { + SAFE_FREELIGHT( muzzleFlashHandle ); + } +} + +/* +================ +hhMountedGun::UpdateMuzzleFlash +================ +*/ +void hhMountedGun::UpdateMuzzleFlash() { + AttemptToRemoveMuzzleFlash(); + + if( muzzleFlashHandle != -1 ) { + UpdateMuzzleFlashPosition(); + gameRenderWorld->UpdateLightDef( muzzleFlashHandle, &muzzleFlash ); + } +} + +/* +================ +hhMountedGun::UpdateMuzzleFlashPosition +================ +*/ +void hhMountedGun::UpdateMuzzleFlashPosition() { + muzzleFlash.axis = boneGun.axis; + muzzleFlash.origin = boneGun.origin; + + //TEST + trace_t trace; + idVec3 flashSize = spawnArgs.GetVector( "flashSize" ); + if( gameLocal.clip.TracePoint(trace, muzzleFlash.origin, muzzleFlash.origin + muzzleFlash.axis[0] * flashSize[0], MASK_SHOT_BOUNDINGBOX, this) ) { + flashSize[0] *= trace.fraction; + } + muzzleFlash.lightRadius = flashSize; + muzzleFlash.origin += muzzleFlash.axis[0] * flashSize[0] * 0.5f; + muzzleFlash.lightCenter.Set( flashSize[0] * -0.45f, 0.0f, 0.0f ); + + hhUtils::Swap( muzzleFlash.lightRadius[0], muzzleFlash.lightRadius[2] ); + hhUtils::Swap( muzzleFlash.lightCenter[0], muzzleFlash.lightCenter[2] ); + muzzleFlash.axis = hhUtils::SwapXZ( muzzleFlash.axis ); + //TEST +} + +/* +================ +hhMountedGun::MuzzleFlash +================ +*/ +void hhMountedGun::MuzzleFlash() { + if( !muzzleFlash.lightRadius[0] ) { + return; + } + + UpdateMuzzleFlashPosition(); + + // these will be different each fire + muzzleFlash.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.GetTime() ); + muzzleFlash.shaderParms[ SHADERPARM_DIVERSITY ] = gameLocal.random.RandomFloat(); + + // the light will be removed at this time + muzzleFlashEnd = gameLocal.GetTime() + muzzleFlashDuration; + + if ( muzzleFlashHandle != -1 ) { + gameRenderWorld->UpdateLightDef( muzzleFlashHandle, &muzzleFlash ); + } else { + muzzleFlashHandle = gameRenderWorld->AddLightDef( &muzzleFlash ); + } +} + +/* +================ +hhMountedGun::TraceTargetVerified +================ +*/ +bool hhMountedGun::TraceTargetVerified( const idActor* target, int traceEntNum ) const { + int entNum = target->InVehicle() ? target->GetVehicleInterface()->GetVehicle()->entityNumber : target->entityNumber; + return entNum == traceEntNum; +} + +/* +================ +hhMountedGun::ScanForClosestTarget +================ +*/ +idActor *hhMountedGun::ScanForClosestTarget() { + trace_t TraceInfo; + idEntity *entity; + idActor *actor; + hhPlayer *player; + float firingRangeSquared = firingRange * firingRange; + float closestDistSqr = firingRangeSquared; + idActor *closestActor = NULL; + pvsHandle_t pvs; + + pvs = gameLocal.pvs.SetupCurrentPVS( GetPVSAreas(), GetNumPVSAreas() ); + + for ( entity = gameLocal.activeEntities.Next(); entity != NULL; entity = entity->activeNode.Next() ) { + if ( entity->fl.hidden || entity->fl.isDormant || !entity->IsType( idActor::Type ) ) { + continue; + } + + actor = static_cast( entity ); + + // Check if this actor is in the PVS (needed?) + if ( !gameLocal.pvs.InCurrentPVS( pvs, actor->GetPVSAreas(), actor->GetNumPVSAreas() ) ) { + continue; + } + + // Check if the actor can be damaged and should be targeted by the gun + // Also, check the actor's team, only player team (humans, player) should be targetted + if ( !actor->fl.takedamage || actor->GetHealth() <= 0 || actor->team != 0 ) { + if ( !actor->InVehicle() ) { //HUMANHEAD jsh target vehicles + continue; + } + } + + // Player-specific checks -- ignore the spirit player + if ( actor->IsType( hhPlayer::Type ) ) { + player = static_cast( actor ); + if ( !player || player->IsSpiritOrDeathwalking() ) { + continue; + } + } + + // Calculate the distance to the actor + float distSqr = (actor->GetEyePosition() - GetOrigin()).LengthSqr(); + + // Ignore actors beyond the max distance + if( distSqr > firingRangeSquared ) { + continue; + } + + // Only check if this distance is closer than the others + if( distSqr > closestDistSqr ) { + continue; + } + + // Check if this actor is visible from the gun + if( !gameLocal.clip.TracePoint(TraceInfo, GetOrigin(), actor->GetEyePosition(), MASK_SHOT_BOUNDINGBOX, this) ) { + continue; + } + + if( !TraceTargetVerified(actor, TraceInfo.c.entityNum) ) { + continue; + } + + if ( actor->IsType( hhPlayer::Type ) ) { + hhPlayer *player = static_cast(actor); + if ( player && player->GetLastResurrectTime() ) { + if ( gameLocal.time < player->GetLastResurrectTime() + deathwalkDelay ) { + continue; + } + } + } + + closestActor = actor; + closestDistSqr = distSqr; + } + + gameLocal.pvs.FreeCurrentPVS( pvs ); + + return closestActor; +} + +/* +================ +hhMountedGun::PlayAnim +================ +*/ +void hhMountedGun::PlayAnim( int iChannelNum, const char* pAnim, int iBlendTime ) { + PlayAnim( iChannelNum, GetAnimator()->GetAnim( pAnim ), iBlendTime ); +} + +/* +================ +hhMountedGun::PlayAnim +================ +*/ +void hhMountedGun::PlayAnim( int iChannelNum, int pAnim, int iBlendTime ) { + ClearAnims( iChannelNum, iBlendTime ); + + GetAnimator()->PlayAnim( iChannelNum, pAnim, gameLocal.GetTime(), iBlendTime ); + animDoneTime = GetAnimator()->CurrentAnim( iChannelNum )->Length(); + animDoneTime = (animDoneTime > iBlendTime) ? animDoneTime - iBlendTime : animDoneTime; + + animDoneTime += gameLocal.GetTime(); +} + +/* +================ +hhMountedGun::PlayCycle +================ +*/ +void hhMountedGun::PlayCycle( int iChannelNum, const char* pAnim, int iBlendTime ) { + PlayCycle( iChannelNum, GetAnimator()->GetAnim( pAnim ), iBlendTime ); +} + +/* +================ +hhMountedGun::NextCycleAnim + +// Cycles the next animation in after the current one completes +================ +*/ +void hhMountedGun::NextCycleAnim( int iChannelNum, const char* pAnim ) { + PostEventMS( &EV_NextCycleAnim, GetAnimDoneTime() - gameLocal.GetTime(), iChannelNum, pAnim ); +} + +void hhMountedGun::Event_NextCycleAnim( int iChannelNum, const char* pAnim ) { + PlayCycle( iChannelNum, pAnim, 0 ); +} + +/* +================ +hhMountedGun::PlayCycle +================ +*/ +void hhMountedGun::PlayCycle( int iChannelNum, int pAnim, int iBlendTime ) { + ClearAnims( iChannelNum, iBlendTime ); + + GetAnimator()->CycleAnim( iChannelNum, pAnim, gameLocal.GetTime(), iBlendTime ); +} + +/* +================ +hhMountedGun::ClearAnims +================ +*/ +void hhMountedGun::ClearAnims( int iChannelNum, int iBlendTime ) { + GetAnimator()->Clear( iChannelNum, gameLocal.GetTime(), iBlendTime ); + + animDoneTime = 0; +} + +/* +===================== +hhMountedGun::Event_PostSpawn +===================== +*/ +void hhMountedGun::Event_PostSpawn() { + // Find the trackmover if it exists + trackMover = gameLocal.FindEntity( spawnArgs.GetString("target") ); + if( trackMover.IsValid() && trackMover->IsType( hhTrackMover::Type ) ) { //HUMANHEAD PCF mdl 04/26/06 - Only bind to track movers, in case target is only for triggers + SetOrigin( trackMover->GetOrigin() ); + Bind( trackMover.GetEntity(), false ); + } + + SetIdealLookAngle( GetAxis().ToAngles() ); +} + + +/* +===================== +hhMountedGun::Event_Activate +===================== +*/ +void hhMountedGun::Event_Activate( idEntity* pActivator ) { + if ( gunMode == GM_DORMANT ) { + gunMode = GM_IDLE; + + BecomeActive( TH_THINK | TH_TICKER ); + + ProcessEvent( &EV_ScanForClosestTarget ); + } +} + +/* +===================== +hhMountedGun::Awaken +===================== +*/ + +void hhMountedGun::Awaken() { + if ( gunMode != GM_DEAD ) { + if( targetingLaser.IsValid() && !targetingLaser->IsActivated() ) { + targetingLaser->Activate( true ); + } + + PreAttack(); + fl.takedamage = true; // Gun can now be shot + + PlayAnim( ANIMCHANNEL_ALL, "open", 0 ); + NextCycleAnim( ANIMCHANNEL_ALL, "idle_open" ); + } +} + +/* +===================== +hhMountedGun::Event_Deactivate +===================== +*/ +void hhMountedGun::Event_Deactivate() { + if (gunMode != GM_DEAD) { + if( targetingLaser.IsValid() && targetingLaser->IsActivated() ) { + targetingLaser->Activate( false ); + } + + if( trackMover.IsValid() ) { + trackMover->ProcessEvent( &EV_Deactivate ); + } + + CancelEvents( &EV_ScanForClosestTarget ); + CancelEvents( &EV_StartAttack ); + + gunMode = GM_DORMANT; + fl.takedamage = false; + + StopSound( SND_CHANNEL_ANY, false ); + + PlayAnim( ANIMCHANNEL_ALL, "close", 0 ); + NextCycleAnim( ANIMCHANNEL_ALL, "idle_close" ); + } +} + +/* +===================== +hhMountedGun::Sleep + +// Very similar to dormant, except the gun continues to think and look for enemies while closed +===================== +*/ +void hhMountedGun::Sleep() { + if( targetingLaser.IsValid() && targetingLaser->IsActivated() ) { + targetingLaser->Activate( false ); + targetingLaser->SetShaderParm( SHADERPARM_MODE, 0 ); + } + + // Note: A sleeping gun doesn't disable the track mover + CancelEvents( &EV_StartAttack ); + + gunMode = GM_IDLE; + fl.takedamage = false; + + StopSound( SND_CHANNEL_ANY, false ); + + PlayAnim( ANIMCHANNEL_ALL, "close", 0 ); + NextCycleAnim( ANIMCHANNEL_ALL, "idle_close" ); +} + +//=============================================================================================================== +//=============================================================================================================== +//=============================================================================================================== +//=============================================================================================================== +//=============================================================================================================== + +void hhMountedGun::Think() { + hhAnimatedEntity::Think(); + + if (thinkFlags & TH_THINK) { + // Run the current state + switch ( gunMode ) { + case GM_DORMANT: + case GM_IDLE: + case GM_DEAD: + break; + case GM_PREATTACKING: + TurnTowardsEnemy( DEG2RAD( spawnArgs.GetFloat( "turnMax", "3" ) ), DEG2RAD( spawnArgs.GetFloat( "turnAccel", "0.2" ) ) ); + break; + case GM_ATTACKING: + TurnTowardsEnemy( DEG2RAD( spawnArgs.GetFloat( "attackTurnMax", "0.5" ) ), DEG2RAD( spawnArgs.GetFloat( "attackTurnAccel", "0.05" ) ) ); + Attack(); + break; + case GM_RELOADING: + // TODO: Handled by an event + if ( ReadyToFire() ) { // hack! + PreAttack(); + } + break; + } + } +} + +bool hhMountedGun::GetFacePosAngle( const idVec3 &sourceOrigin, float angle1, const idVec3 &targetOrigin, float &delta ) { + float diff; + float angle2; + + angle2 = hhUtils::PointToAngle( targetOrigin.x - sourceOrigin.x, targetOrigin.y - sourceOrigin.y ); + if(angle2 > angle1) { + diff = angle2 - angle1; + + if( diff > DEG2RAD(180.0f) ) { + delta = DEG2RAD(359.9f) - diff; + return false; + } + else { + delta = diff; + return true; + } + } + else { + diff = angle1 - angle2; + if( diff > DEG2RAD(180.0f) ) { + delta = DEG2RAD(359.9f) - diff; + return true; + } + else { + delta = diff; + return false; + } + } +} + +void hhMountedGun::TurnTowardsEnemy( float maxYawVelocity, float yawAccel ) { + idVec3 dirToEnemy; + idVec3 localDirToEnemy; + + bool dir; + float deltaYaw; + + if( !EnemyIsVisible() ) { // Enemy isn't valid or visible, so go to sleep until the enemy is available again + return; + } + + if ( !enemy->IsType( hhPlayer::Type ) ) { // Turn twice as fast if the enemy is not a player (to guarantee that the gun will kill other humans/creatures) + maxYawVelocity *= 2.0f; + yawAccel *= 2.0f; + } + + idVec3 currentVec = this->GetAxis()[0]; + float currentYaw = DEG2RAD( currentVec.ToYaw() ); + + // Face toward the enemy + dir = GetFacePosAngle( GetOrigin(), currentYaw, enemy->GetOrigin(), deltaYaw ); + + // Acceleration + if ( dir ) { + yawVelocity += yawAccel; + } else { + yawVelocity -= yawAccel; + } + + if ( yawVelocity > maxYawVelocity ) { + yawVelocity = maxYawVelocity; + } else if ( yawVelocity < -maxYawVelocity ) { + yawVelocity = -maxYawVelocity; + } + + deltaYaw = yawVelocity; + + // Compute the pitch to the enemy + dirToEnemy = ( ( enemy->GetEyePosition() + enemy->GetOrigin() ) * 0.5f ) - boneGun.origin; + dirToEnemy.Normalize(); + GetAxis().ProjectVector( dirToEnemy, localDirToEnemy ); + + SetIdealLookAngle( idAngles( localDirToEnemy.ToPitch(), RAD2DEG( currentYaw + deltaYaw ), 0.0f ) ); + + StartSound( "snd_rotate", SND_CHANNEL_BODY3 ); +} + +bool hhMountedGun::EnemyIsInRange() { + idVec3 dirToEnemy; + + if ( !enemy.IsValid() ) { + return false; + } + + dirToEnemy = enemy->GetEyePosition() - boneHub.origin; + if ( dirToEnemy.LengthSqr() > firingRange * firingRange ) { + return false; + } + + return true; +} + +bool hhMountedGun::EnemyCanBeAttacked() { + if ( !enemy.IsValid() ) { // No enemy + return false; + } + + // Check if the enemy is visible and is alive + if ( ClearLineOfFire() && enemy->health > 0 ) { + return true; + } + + return false; +} + +/* +===================== +hhMountedGun::Event_ScanForClosestTarget +===================== +*/ +void hhMountedGun::Event_ScanForClosestTarget() { + idActor *entity = ScanForClosestTarget(); + + enemy = entity; + if ( entity && gunMode == GM_IDLE ) { // Sleeping and found a target + Awaken(); + } + else if ( !entity && gunMode != GM_IDLE ) { // No target, so go to sleep if not already sleeping + Sleep(); + } + + PostEventSec( &EV_ScanForClosestTarget, spawnArgs.GetFloat("scanPeriod") ); +} + +void hhMountedGun::PreAttack() { + gunMode = GM_PREATTACKING; + + // Change the beam to something more threatening + targetingLaser->SetShaderParm( SHADERPARM_MODE, 1 ); + + // Play a preattack warning sound + StartSound( "snd_preattack", SND_CHANNEL_ANY ); + + // Delay before attacking + PostEventSec( &EV_StartAttack, spawnArgs.GetFloat( "attackDelay", "0.2" ) ); // Delay a bit before attacking +} + +void hhMountedGun::Event_StartAttack() { + gunMode = GM_ATTACKING; +} + +void hhMountedGun::Attack() { + hhPlayer *player = NULL; + + if ( !enemy.IsValid() ) { + return; + } + + if ( enemy->IsType( hhPlayer::Type ) ) { + player = static_cast( enemy.GetEntity() ); + if ( player->IsSpiritOrDeathwalking() ) { + return; + } + } + + // Actually fire the gun + if ( ReadyToFire() ) { + + Fire(); + + shotsPerBurstCounter++; + if ( shotsPerBurstCounter >= shotsPerBurst || enemy->GetHealth() <= 0 ) { + Reload(); + } + } +} + +void hhMountedGun::Reload() { + gunMode = GM_RELOADING; + PlayAnim( ANIMCHANNEL_ALL, "reload", 0 ); + + nextFireTime = gameLocal.GetTime() + SEC2MS( 2.0f ); // HACK: fake way of currently forcing a wait when reloading + shotsPerBurstCounter = 0; + + targetingLaser->SetShaderParm( SHADERPARM_MODE, 0 ); + + if ( enemy->GetHealth() <= 0 ) { // Enemy was killed, so clear it and wait for the gun to find a new enemy + enemy.Clear(); + } + + StopSound( SND_CHANNEL_BODY3 ); // Stop the rotation sound +} + +/* +===================== +hhMountedGun::DetermineNextFireTime +===================== +*/ +int hhMountedGun::DetermineNextFireTime() { + return gameLocal.GetTime() + burstRateOfFire; +} + +/* +===================== +hhMountedGun::ReadyToFire +===================== +*/ +bool hhMountedGun::ReadyToFire() { + return gameLocal.GetTime() > nextFireTime; +} + +/* +================ +hhMountedGun::Fire +================ +*/ +void hhMountedGun::Fire() { + idVec3 LaunchDir; + idMat3 LaunchAxis; + + PlayAnim( channelBarrel, "fireA", 0 ); + nextFireTime = DetermineNextFireTime(); + + MuzzleFlash(); + + hhProjectile* pProjectile = hhProjectile::SpawnProjectile( projectileDict ); + if( !pProjectile ) { + return; + } + + LaunchAxis = hhUtils::RandomSpreadDir( boneGun.axis, DEG2RAD(spawnArgs.GetFloat("spread"))).ToMat3(); + pProjectile->Create( this, boneGun.origin, LaunchAxis ); + pProjectile->Launch( boneGun.origin, LaunchAxis, vec3_zero ); +} + +/* +===================== +hhMountedGun::EnemyIsVisible + +// If the enemy is visible at any angle from the gun +===================== +*/ +bool hhMountedGun::EnemyIsVisible() { + trace_t traceInfo; + + if( !enemy.IsValid() || !EnemyIsInPVS() ) { + return false; + } + + if( !gameLocal.clip.TracePoint( traceInfo, GetOrigin(), enemy->GetEyePosition(), MASK_SHOT_BOUNDINGBOX, this ) ) { + return true; + } + + if( TraceTargetVerified( enemy.GetEntity(), traceInfo.c.entityNum ) ) { + return true; + } + + return false; +} + +/* +===================== +hhMountedGun::ClearLineOfFire + +// If the enemy can be shot (line of sight from the gun barrel to the enemy) +===================== +*/ +bool hhMountedGun::ClearLineOfFire() { + trace_t TraceInfo; + idVec3 BonePos; + idMat3 BoneAxis; + + if( !enemy.IsValid() || !EnemyIsInPVS() ) { + return false; + } + + if( !gameLocal.clip.TracePoint(TraceInfo, boneGun.origin, boneGun.origin + boneGun.axis[0] * firingRange, MASK_SHOT_BOUNDINGBOX, this) ) { + return false; + } + + if( !TraceTargetVerified(enemy.GetEntity(), TraceInfo.c.entityNum) ) { + return false; + } + + return true; +} + +/* +===================== +hhMountedGun::EnemyIsInPVS +===================== +*/ +bool hhMountedGun::EnemyIsInPVS() { + pvsHandle_t PVSHandle = gameLocal.pvs.SetupCurrentPVS( PVSArea ); + + bool bResult = gameLocal.pvs.InCurrentPVS( PVSHandle, enemy->GetPVSAreas(), enemy->GetNumPVSAreas() ); + + gameLocal.pvs.FreeCurrentPVS( PVSHandle ); + + return bResult; +} + +/* +================ +hhMountedGun::Killed +================ +*/ +void hhMountedGun::Killed( idEntity *pInflictor, idEntity *pAttacker, int iDamage, const idVec3 &Dir, int iLocation ) { + hhFxInfo fxInfo; + + if ( gunMode != GM_DEAD ) { + BecomeInactive( TH_THINK|TH_TICKER ); + CancelEvents( &EV_ScanForClosestTarget ); + CancelEvents( &EV_StartAttack ); + CancelEvents( &EV_NextCycleAnim ); + + fxInfo.SetNormal( -GetAxis()[2] ); + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfo( spawnArgs.GetString("fx_detonate"), boneHub.origin, GetAxis(), &fxInfo ); + + hhUtils::SpawnDebrisMass(spawnArgs.GetString("def_debrisspawner"), this); + + if( targetingLaser.IsValid() ) { + targetingLaser->Activate( false ); + } + SAFE_REMOVE( targetingLaser ); + SAFE_FREELIGHT( muzzleFlashHandle ); + + if( trackMover.IsValid() ) { + trackMover->ProcessEvent( &EV_Deactivate ); + } + + const char *killedModel = spawnArgs.GetString("model_killed", NULL); + if (killedModel) { + SetModel(killedModel); + UpdateVisuals(); + GetPhysics()->SetContents(0); + fl.takedamage = false; + } + + RemoveBinds(); + + StopSound( SND_CHANNEL_ANY, false ); + + StartSound( "snd_die", SND_CHANNEL_ANY); + + ActivateTargets( pAttacker ); + + gunMode = GM_DEAD; + } +} + +void hhMountedGun::jointInfo_t::Save( idSaveGame *savefile ) const { + savefile->WriteString( name ); + savefile->WriteJoint( handle ); + savefile->WriteVec3( origin ); + savefile->WriteMat3( axis ); +} + +void hhMountedGun::jointInfo_t::Restore( idRestoreGame *savefile ) { + savefile->ReadString( name ); + savefile->ReadJoint( handle ); + savefile->ReadVec3( origin ); + savefile->ReadMat3( axis ); +} + diff --git a/src/Prey/game_mountedgun.h b/src/Prey/game_mountedgun.h new file mode 100644 index 0000000..8bbae8a --- /dev/null +++ b/src/Prey/game_mountedgun.h @@ -0,0 +1,158 @@ +#ifndef __HH_MOUNTEDGUN_H +#define __HH_MOUNTEDGUN_H + +typedef enum { + GM_DORMANT, + GM_IDLE, + GM_PREATTACKING, + GM_ATTACKING, + GM_RELOADING, + GM_DEAD +} gunMode_t; + +/********************************************************************** + +hhMountedGun + +**********************************************************************/ +class hhMountedGun: public hhAnimatedEntity { + CLASS_PROTOTYPE( hhMountedGun ); + +public: + hhMountedGun(); + virtual ~hhMountedGun(); + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Hide(); + virtual void Show(); + +protected: + void SpawnParts(); + idVec3 DetermineTargetingLaserEndPoint(); + void Ticker(); + + virtual void Killed( idEntity *pInflictor, idEntity *pAttacker, int iDamage, const idVec3 &Dir, int iLocation ); + + int DetermineNextFireTime(); + bool ReadyToFire(); + void Fire(); + + + void MuzzleFlash(); + void UpdateMuzzleFlash(); + void AttemptToRemoveMuzzleFlash(); + void UpdateMuzzleFlashPosition(); + + void UpdateOrientation(); + void UpdateBoneInfo(); + + bool GetFacePosAngle( const idVec3 &sourceOrigin, float angle1, const idVec3 &targetOrigin, float &delta ); + void SetIdealLookAngle( const idAngles& LookAngle ); + + idActor *ScanForClosestTarget(); + + bool TraceTargetVerified( const idActor* target, int traceEntNum ) const; + +protected: + bool ClearLineOfFire(); + bool EnemyIsVisible(); + bool EnemyIsInPVS(); + + void PlayAnim( int iChannelNum, const char* pAnim, int iBlendTime ); + void PlayAnim( int iChannelNum, int pAnim, int iBlendTime ); + void PlayCycle( int iChannelNum, const char* pAnim, int iBlendTime ); + void PlayCycle( int iChannelNum, int pAnim, int iBlendTime ); + void ClearAnims( int iChannelNum, int iBlendTime ); + int GetAnimDoneTime() { return animDoneTime; } + + void NextCycleAnim( int iChannelNum, const char* pAnim ); + void Event_NextCycleAnim( int iChannelNum, const char* pAnim ); + +protected: + void Awaken(); + + void Sleep(); + + void Event_PostSpawn(); + void Event_Activate( idEntity* pActivator ); + void Event_Deactivate(); + void Event_StopRotating(); + + void Event_ScanForClosestTarget(); + void Event_StartAttack(); + +protected: + // CJR STUFF + int team; + gunMode_t gunMode; + void Think(); + void TurnTowardsEnemy( float maxYawVelocity, float yawAccel ); + + bool EnemyCanBeAttacked(); + bool EnemyIsInRange(); + void PreAttack(); + void Attack(); + void Reload(); + +protected: + hhBoneController pitchController; + + const idDict* projectileDict; + + idEntityPtr trackMover; + + idEntityPtr targetingLaser; + + struct jointInfo_t { + idStr name; + jointHandle_t handle; + idVec3 origin; + idMat3 axis; + + jointInfo_t() { Clear(); } + void Clear() { handle = INVALID_JOINT; origin.Zero(); axis = mat3_identity; } + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + }; + + jointInfo_t boneHub; + jointInfo_t boneGun; + jointInfo_t boneExhaust; + jointInfo_t boneOrigin; + + idEntityPtr enemy; + float enemyRange; + + float firingRange; + + int nextFireTime; + float burstRateOfFire; + int shotsPerBurst; + int shotsPerBurstCounter; + + renderLight_t muzzleFlash; + int muzzleFlashHandle; + int muzzleFlashEnd; + int muzzleFlashDuration; + + idAngles idealLookAngle; + idAngles prevIdealLookAngle; + + idPhysics_Parametric physicsObj; + + int animDoneTime; + + int PVSArea; + + // nla - Other vars + int channelBody; + int channelBarrel; + int deathwalkDelay; //time to wait in msecs after killing something + + // cjr vars + float yawVelocity; +}; + +#endif diff --git a/src/Prey/game_moveable.cpp b/src/Prey/game_moveable.cpp new file mode 100644 index 0000000..d6c6f1d --- /dev/null +++ b/src/Prey/game_moveable.cpp @@ -0,0 +1,492 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +#define PLAYER_COLLISION_PRINTF(e) if( (e)->IsType(hhPlayer::Type) ) gameLocal.Printf + +const idEventDef EV_HoverTo( "hoverTo", "v" ); +const idEventDef EV_HoverMove( "" ); +const idEventDef EV_Unhover( "unhover" ); +const idEventDef EV_FadeOutDebris( "", "f" ); + +CLASS_DECLARATION( idMoveable, hhMoveable ) + EVENT( EV_HoverTo, hhMoveable::Event_HoverTo ) + EVENT( EV_HoverMove, hhMoveable::Event_HoverMove ) + EVENT( EV_Unhover, hhMoveable::Event_Unhover ) + EVENT( EV_Touch, hhMoveable::Event_Touch ) + EVENT( EV_SpawnFxFlyLocal, hhMoveable::Event_SpawnFxFlyLocal ) + EVENT( EV_FadeOutDebris, hhMoveable::Event_StartFadingOut ) +END_CLASS + +/* +================ +hhMoveable::hhMoveable +================ +*/ +hhMoveable::hhMoveable() { +} + +hhMoveable::~hhMoveable() { + SAFE_REMOVE( fxFly ); +} + +/* +================ +hhMoveable::Spawn +================ +*/ +void hhMoveable::Spawn() { + + fl.takedamage = health > 0; + + hoverController = NULL; + nextDamageTime = 0; + + // idMoveable forces friction to (0.6f, 0.6f, friction) + float linearFriction = spawnArgs.GetFloat( "linearFriction", "0.6" ); + float angularFriction = spawnArgs.GetFloat( "angularFriction", "0.6" ); + float contactFriction = spawnArgs.GetFloat( "friction", "0.6" ); + physicsObj.SetFriction( linearFriction, angularFriction, contactFriction ); + + float gravityMagnitude = physicsObj.GetGravity().Length(); + collisionSpeed_min = hhUtils::DetermineFinalFallVelocityMagnitude( spawnArgs.GetFloat("collideDist_min"), gravityMagnitude ); + + currentChannel = SCHANNEL_ANY; + + if (spawnArgs.GetBool("walkthrough")) { + GetPhysics()->SetContents( CONTENTS_TRIGGER | CONTENTS_RENDERMODEL ); // CJR: Removed CONTENTS_CORPSE because ragdolls were colliding with these moveables + GetPhysics()->SetClipMask( MASK_SOLID | CONTENTS_MOVEABLECLIP ); + } + + // Check if the moveable wants to remove itself after a certain amount of time + float removeTime = spawnArgs.GetFloat("removeTime"); + removeOnCollision = spawnArgs.GetBool("removeOnCollision"); + + float duration = spawnArgs.GetFloat("duration"); // Ignore removeTime if duration is set -mdl + if (duration == 0.0f && !removeOnCollision && removeTime > 0.0f) { + PostEventSec(&EV_Remove, removeTime); + } + + if (!gameLocal.isClient) { + // CJR: spawn an optional flight fx system + const char *flyName = spawnArgs.GetString( "fx_fly" ); + if ( flyName && flyName[0] ) { + BroadcastFx( flyName, EV_SpawnFxFlyLocal ); + } + } + + fadeAlpha.Init(gameLocal.time, 0, 1.0f, 1.0f); + + float fadeTime = spawnArgs.GetFloat("fadeouttime"); + if (duration > 0.0f) { + if (fadeTime > duration) { + fadeTime = duration; + } + if (fadeTime > 0.0f) { + PostEventSec(&EV_FadeOutDebris, duration-fadeTime, fadeTime); + PostEventSec(&EV_Remove, duration); + } + } + + notPushableAI = spawnArgs.GetBool( "notPushableAI", "0" ); +} + +void hhMoveable::Save(idSaveGame *savefile) const { + savefile->WriteFloat( collisionSpeed_min ); + savefile->WriteInt( currentChannel ); + savefile->WriteObject( hoverController ); + savefile->WriteVec3( hoverPosition ); + savefile->WriteVec3( hoverAngle ); + savefile->WriteBool( removeOnCollision ); + savefile->WriteBool( notPushableAI ); + + savefile->WriteFloat( fadeAlpha.GetStartTime() ); // idInterpolate + savefile->WriteFloat( fadeAlpha.GetDuration() ); + savefile->WriteFloat( fadeAlpha.GetStartValue() ); + savefile->WriteFloat( fadeAlpha.GetEndValue() ); + + fxFly.Save( savefile ); + savefile->WriteInt( nextDamageTime ); +} + +void hhMoveable::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( collisionSpeed_min ); + savefile->ReadInt( currentChannel ); + savefile->ReadObject( reinterpret_cast(hoverController) ); + savefile->ReadVec3( hoverPosition ); + savefile->ReadVec3( hoverAngle ); + savefile->ReadBool( removeOnCollision ); + savefile->ReadBool( notPushableAI ); + + float set; + + savefile->ReadFloat( set ); // idInterpolate + fadeAlpha.SetStartTime( set ); + savefile->ReadFloat( set ); + fadeAlpha.SetDuration( set ); + savefile->ReadFloat( set ); + fadeAlpha.SetStartValue(set); + savefile->ReadFloat( set ); + fadeAlpha.SetEndValue( set ); + + fxFly.Restore( savefile ); + savefile->ReadInt( nextDamageTime ); +} + +/* +============ +hhMoveable::SquishedByDoor +============ +*/ +void hhMoveable::SquishedByDoor(idEntity *door) { + // Get rid of any moveables caught in doors + Killed(door, door, 0, vec3_origin, 0); +} + +/* +============ +hhMoveable::Killed +============ +*/ +void hhMoveable::Killed( idEntity *inflictor, idEntity *attacker, int damageAmt, const idVec3 &dir, int location ) { + fl.takedamage = false; + GetPhysics()->SetContents(0); // Turn collision off so other entities can spawn in + + if ( unbindOnDeath ) { + Unbind(); + } + + if ( renderEntity.gui[ 0 ] ) { + renderEntity.gui[ 0 ] = NULL; + } + + ActivateTargets(attacker); + + // nla - Taken from hhAI::Killed + const char *dropDef = spawnArgs.GetString( "def_drop", NULL); + if ( dropDef && *dropDef ) { + idDict args; + args.Set( "origin", physicsObj.GetOrigin().ToString() ); + gameLocal.SpawnObject( dropDef, &args ); + } + + const char *debrisDef = spawnArgs.GetString("def_debrisspawner", NULL); + if ( debrisDef && *debrisDef ) { + hhUtils::SpawnDebrisMass(debrisDef, this); + } + + PostEventMS( &EV_Remove, 0 ); +} + +/* +================ +hhMoveable::DetermineNextChannel +================ +*/ +s_channelType hhMoveable::DetermineNextChannel() { + static int NUM_CHANNELS = 6; + + currentChannel = (currentChannel + 1) % NUM_CHANNELS; + return (s_channelType)((currentChannel == SCHANNEL_ANY) ? ++currentChannel : currentChannel); +} + +/* +================ +hhMoveable::AttemptToPlayBounceSound +================ +*/ +void hhMoveable::AttemptToPlayBounceSound( const trace_t &collision, const idVec3 &velocity ) { + static const float minCollisionVelocity = 20.0f; + static const float maxCollisionVelocity = 650.0f; + s_channelType channel; + + float len = velocity * -collision.c.normal; + if( len > minCollisionVelocity && collision.c.material && !(collision.c.material->GetSurfaceFlags() & SURF_NOIMPACT) ) { + channel = DetermineNextChannel(); + if( StartSound("snd_bounce", channel) ) { + // Change volume only after we know the sound played + float volume = hhUtils::CalculateSoundVolume( len, minCollisionVelocity, maxCollisionVelocity ); + HH_SetSoundVolume( volume, channel ); + } + } +} + +/* +================ +hhMoveable::Collide +================ +*/ +bool hhMoveable::Collide( const trace_t &collision, const idVec3 &velocity ) { + idVec3 dir = velocity; + dir.Normalize(); + + if (removeOnCollision) { + StartSound( "snd_splat", SND_CHANNEL_ANY ); + PostEventMS(&EV_Remove, 0); + return true; + } + + AttemptToPlayBounceSound( collision, velocity ); + + idEntity* entity = ValidateEntity( collision.c.entityNum ); + if ( gameLocal.time > nextDamageTime && damage.Length() ) { + if ( entity ) { + if ( DetermineCollisionSpeed(entity, collision.c.point, entity->GetPhysics()->GetLinearVelocity(), GetOrigin(), GetPhysics()->GetLinearVelocity()) > collisionSpeed_min ) { + nextDamageTime = gameLocal.time + SEC2MS(0.5); //prevent multi-collision damage + entity->Damage( this, GetPhysics()->GetClipModel()->GetOwner(), dir, damage, 1.0f, INVALID_JOINT ); + } + } + } + + + if ( fxCollide.Length() && gameLocal.time > nextCollideFxTime ) { + float len = velocity * -collision.c.normal; + if (len > 50) { + hhFxInfo fxInfo; + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfo( fxCollide, collision.c.point, mat3_identity, &fxInfo ); + nextCollideFxTime = gameLocal.time + 1000; + } + } + + // CJR: Disable the flight fx effect the first time the moveable hits something + if ( fxFly.IsValid() ) { + fxFly->Nozzle( false ); + SAFE_REMOVE( fxFly ); + } + + return false; +} + +/* +================ +hhMoveable::ValidateEntity +================ +*/ +idEntity* hhMoveable::ValidateEntity( const int collisionEntityNum ) { + if( collisionEntityNum >= ENTITYNUM_WORLD ) { + return NULL; + } + + return gameLocal.entities[collisionEntityNum]; +} + +/* +================ +hhMoveable::DetermineCollisionSpeed + +Used so we only use linear velocity in our equations +================ +*/ +float hhMoveable::DetermineCollisionSpeed( const idEntity* entity, const idVec3& point1, const idVec3& velocity1, const idVec3& point2, const idVec3& velocity2 ) { + idVec3 originVector = point1 - point2; + originVector.Normalize(); + idVec3 reversedOriginVector = -originVector; + float vel1Dot = (velocity1 * reversedOriginVector); + float vel2Dot = (velocity2 * originVector); + + if( vel2Dot < 0.0f ) { + return 0.0f; + } + + idVec3 adjustedVel1 = vel1Dot * reversedOriginVector; + idVec3 adjustedVel2 = vel2Dot * originVector; + + //PLAYER_COLLISION_PRINTF(entity)( "Vel1: %s, Vel2: %s\n", adjustedVel1.ToString(), adjustedVel2.ToString() ); + + return (adjustedVel2 - adjustedVel1).Length(); +} + +/* +================ +hhMoveable::ApplyImpulse +================ +*/ +void hhMoveable::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse ) { + if ( notPushableAI && ent && ent->IsType( idAI::Type ) ) { + return; + } + GetPhysics()->ApplyImpulse( id, point, impulse ); +} + +void hhMoveable::AllowImpact( bool allow ) { + if ( allow ) { + physicsObj.EnableImpact(); + } else { + physicsObj.DisableImpact(); + } +} + +/* +======================== +hhMoveable::HoverTo +======================== +*/ +void hhMoveable::Event_HoverTo( const idVec3 &position ) { + + if ( !hoverController ) { + const char *controllerDef = spawnArgs.GetString( "def_hoverController", NULL ); + + if ( controllerDef ) { + hoverController = static_cast( gameLocal.SpawnObject( controllerDef ) ); + } + else { + hoverController = NULL; + } + } + + //gameLocal.Printf( "Hover to %s %.2f\n", position.ToString(), (float) GetPhysics()->GetMass() ); + if ( !hoverController ) { + gameLocal.Warning( "Event_HoverTo: Tried to hover with an invalid def_hoverController" ); + return; + } + + float tension; + tension = GetPhysics()->GetMass() / 64.0 * spawnArgs.GetFloat( "hover_tension", ".01" ); + + hoverPosition = position; + if ( ! spawnArgs.GetBool( "hover_gravity", "0" ) || gameLocal.GetGravityNormal().Compare(vec3_origin, VECTOR_EPSILON ) ) { + hoverAngle = GetPhysics()->GetOrigin() - position; + } + else { + hoverAngle = gameLocal.GetGravityNormal(); + } + hoverAngle.Normalize(); + + hoverController->SetTension( tension ); + hoverController->SetOrigin( hoverPosition ); + hoverController->Attach( this, true ); + + float rotation = spawnArgs.GetFloat( "hover_rotation", "0" ); + if ( rotation ) { + GetPhysics()->SetAngularVelocity( idVec3( rotation, rotation, rotation )); + } + + PostEventMS( &EV_HoverMove, 100 ); +} + + +/* +===================== +hhMoveable::HoverMove +===================== +*/ +void hhMoveable::Event_HoverMove( ) { + float dist; + float freq; // (in Hz) + float period; + float offset; + float normalizedT; + + + if ( !hoverController ) { + gameLocal.Warning( "Event_HoverMove: Tried to hover move with an invalid def_hoverController" ); + return; + } + + // Get the total distance to travel + //! Cache these + float height = GetPhysics()->GetBounds()[ 1 ][ 2 ] - GetPhysics()->GetBounds()[ 0 ][ 2 ]; + dist = spawnArgs.GetFloat( "hover_height_frac", ".1" ) * height; + freq = spawnArgs.GetFloat( "hover_freq", ".5" ); + + if ( !freq || !dist ) { + return; + } + + period = 1 / freq; + + // Find out what offset we should be at this point in time. + normalizedT = ( gameLocal.time % (int) ( period * 1000 ) ) / (float) ( period * 1000 ); + offset = idMath::Sin( normalizedT * idMath::TWO_PI ) * dist; + + // Set our new position + hoverController->SetOrigin( hoverPosition + hoverAngle * offset ); + + // Lets do it again in 1/10th of a second + PostEventMS( &EV_HoverMove, 100 ); +} + + +/* +===================== +hhMoveable::Unhover +===================== +*/ +void hhMoveable::Event_Unhover( ) { + + if ( !hoverController ) { + gameLocal.Warning( "Event_Unhover: Tried to unhover with an invalid def_hoverController" ); + return; + } + //gameLocal.Printf( "Unhover man" ); + + hoverController->Detach( ); + + SAFE_REMOVE( hoverController ); + CancelEvents( &EV_HoverMove ); +} + +/* +================ +hhMoveable::Event_Touch +================ +*/ +void hhMoveable::Event_Touch( idEntity *other, trace_t *trace ) { + if (spawnArgs.GetBool("walkthrough")) { + idVec3 otherVel = other->GetPhysics()->GetLinearVelocity(); + float otherSpeed = otherVel.NormalizeFast(); + if (otherSpeed > 50.0f && GetPhysics()->IsAtRest()) { + idVec3 toSide = hhUtils::RandomSign() * other->GetAxis()[1]; + idVec3 toMoveable = GetOrigin() - other->GetOrigin(); + toMoveable.NormalizeFast(); + idVec3 newVel = ( 3*otherVel + 6*toSide + hhUtils::RandomVector() ) * (1.0f/10.0f); + newVel.z = 0.1f; + newVel.NormalizeFast(); + newVel *= otherSpeed*1.5f; + GetPhysics()->SetLinearVelocity(newVel); + } + } +} + +/* +================ +hhMoveable::Event_SpawnFxFlyLocal + +CJR: Based upon the code in projectiles +================ +*/ +void hhMoveable::Event_SpawnFxFlyLocal( const char* defName ) { + if( !defName || !defName[0] ) { + return; + } + + hhFxInfo fxInfo; + + fxInfo.SetNormal( -GetAxis()[0] ); + fxInfo.SetEntity( this ); + fxInfo.RemoveWhenDone( false ); + fxFly = SpawnFxLocal( defName, GetOrigin(), GetAxis(), &fxInfo ); +} + +/* +============ +hhMoveable::Event_StartFadingOut() +============ +*/ +void hhMoveable::Event_StartFadingOut(float fadetime) { + float scale = renderEntity.shaderParms[SHADERPARM_ANY_DEFORM_PARM1]; + fadeAlpha.Init( gameLocal.time, SEC2MS(fadetime), scale > 0.0f ? scale : 1.0f, 0.01f ); + BecomeActive( TH_TICKER ); +} + +/* +============ +hhMoveable::Ticker() +============ +*/ +void hhMoveable::Ticker( void ) { + SetDeformation( DEFORMTYPE_SCALE, fadeAlpha.GetCurrentValue(gameLocal.time) ); +} + diff --git a/src/Prey/game_moveable.h b/src/Prey/game_moveable.h new file mode 100644 index 0000000..8fb69f6 --- /dev/null +++ b/src/Prey/game_moveable.h @@ -0,0 +1,56 @@ +#ifndef __HH_MOVEABLE_H +#define __HH_MOVEABLE_H + + +class hhMoveable: public idMoveable { + CLASS_PROTOTYPE( hhMoveable ); + +public: + hhMoveable(); + ~hhMoveable(); + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void SquishedByDoor(idEntity *door); + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse ); + void AllowImpact( bool allow ); + + virtual void Event_HoverTo( const idVec3& position ); + virtual void Event_HoverMove( ); + virtual void Event_Unhover( ); + void Event_Touch( idEntity *other, trace_t *trace ); + void Event_SpawnFxFlyLocal( const char* defName ); + + // HUMANHEAD mdl: For the keeper + ID_INLINE void SetDamageDef( const char *damageDef ) { damage = damageDef; } + +protected: + virtual idEntity* ValidateEntity( const int collisionEntityNum ); + virtual float DetermineCollisionSpeed( const idEntity* entity, const idVec3& point1, const idVec3& velocity1, const idVec3& point2, const idVec3& velocity2 ); + virtual s_channelType DetermineNextChannel(); + + virtual void AttemptToPlayBounceSound( const trace_t &collision, const idVec3 &velocity ); + virtual void Ticker( void ); + void Event_StartFadingOut(float fadetime); + +protected: + bool removeOnCollision; + bool notPushableAI; + + float collisionSpeed_min; + int currentChannel; + int nextDamageTime; // next time movable is allowed to cause collision damage + + hhBindController * hoverController; + idVec3 hoverPosition; + idVec3 hoverAngle; + + idEntityPtr fxFly; + idInterpolate fadeAlpha; +}; + + +#endif diff --git a/src/Prey/game_mover.cpp b/src/Prey/game_mover.cpp new file mode 100644 index 0000000..c2265e1 --- /dev/null +++ b/src/Prey/game_mover.cpp @@ -0,0 +1,241 @@ +// game_mover.cpp +// + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +//============================================================================= +// hhMover +//============================================================================= + +CLASS_DECLARATION( idMover, hhMover ) +END_CLASS + + +void hhMover::Spawn(void) { + + if ( GetPhysics()->GetClipModel() && spawnArgs.GetBool( "unblockable", "0" ) ) { + // HUMANHEAD pdm: forcing unblockable off when noclipmodel is set, not valid to have push without clipmodel + //gameLocal.Printf( "Setting to nopushsupported" ); + physicsObj.SetPusher( 0 | PUSHFL_UNBLOCKABLE ); + } + + if ( spawnArgs.GetBool( "nonsolid" ) ) { + BecomeNonSolid(); + } + +} + +void hhMover::BecomeNonSolid() { + // Somewhat copied from idMoveable + physicsObj.SetContents( CONTENTS_RENDERMODEL ); + physicsObj.SetClipMask( 0 ); +} + + +//============================================================================= +// hhMoverWallwalk +// Mover capable of doing wallwalk fading +//============================================================================= + +CLASS_DECLARATION(hhMover, hhMoverWallwalk) + EVENT(EV_Activate, hhMoverWallwalk::Event_Activate) +END_CLASS + +void hhMoverWallwalk::Spawn(void) { + wallwalkOn = spawnArgs.GetBool("active"); + flicker = spawnArgs.GetBool("flicker"); + + // Get skin references (already precached) + onSkin = declManager->FindSkin( spawnArgs.GetString("skinOn") ); + offSkin = declManager->FindSkin( spawnArgs.GetString("skinOff") ); + + if (wallwalkOn) { + SetSkin( onSkin ); + alphaOn.Init(gameLocal.time, 0, 1.0f, 1.0f); + SetShaderParm(5, 1); + } + else { + SetSkin( offSkin ); + alphaOn.Init(gameLocal.time, 0, 0.0f, 0.0f); + SetShaderParm(5, 0); + } + alphaOn.SetHermiteParms(WALLWALK_HERM_S1, WALLWALK_HERM_S2); + SetShaderParm(4, alphaOn.GetCurrentValue(gameLocal.time)); + UpdateVisuals(); +} + +void hhMoverWallwalk::Save(idSaveGame *savefile) const { + savefile->WriteFloat( alphaOn.GetStartTime() ); // hhHermiteInterpolate + savefile->WriteFloat( alphaOn.GetDuration() ); + savefile->WriteFloat( alphaOn.GetStartValue() ); + savefile->WriteFloat( alphaOn.GetEndValue() ); + savefile->WriteFloat( alphaOn.GetS1() ); + savefile->WriteFloat( alphaOn.GetS2() ); + + savefile->WriteBool(wallwalkOn); + savefile->WriteBool(flicker); + savefile->WriteSkin(onSkin); + savefile->WriteSkin(offSkin); +} + +void hhMoverWallwalk::Restore( idRestoreGame *savefile ) { + float set, set2; + + savefile->ReadFloat( set ); // hhHermiteInterpolate + alphaOn.SetStartTime( set ); + savefile->ReadFloat( set ); + alphaOn.SetDuration( set ); + savefile->ReadFloat( set ); + alphaOn.SetStartValue(set); + savefile->ReadFloat( set ); + alphaOn.SetEndValue( set ); + savefile->ReadFloat( set ); + savefile->ReadFloat( set2 ); + alphaOn.SetHermiteParms(set, set2); + + savefile->ReadBool(wallwalkOn); + savefile->ReadBool(flicker); + savefile->ReadSkin(onSkin); + savefile->ReadSkin(offSkin); +} + +void hhMoverWallwalk::Think() { + hhMover::Think(); + if (thinkFlags & TH_THINK) { + SetShaderParm(4, alphaOn.GetCurrentValue(gameLocal.time)); + if (alphaOn.IsDone(gameLocal.time)) { + BecomeInactive(TH_THINK); + if (!wallwalkOn) { + SetSkin( offSkin ); + SetShaderParm(5, 0); // Not completely on + } + else { + SetShaderParm(5, 1); // Tell material to stay on + } + } + } +} + +void hhMoverWallwalk::SetWallWalkable(bool on) { + wallwalkOn = on; + + float curAlpha = alphaOn.GetCurrentValue(gameLocal.time); + + if (wallwalkOn) { // Turning on + BecomeActive(TH_THINK); + SetSkin( onSkin ); + StartSound( "snd_powerup", SND_CHANNEL_ANY ); + alphaOn.Init(gameLocal.time, WALLWALK_TRANSITION_TIME, curAlpha, 1.0f ); + alphaOn.SetHermiteParms(WALLWALK_HERM_S1, WALLWALK_HERM_S2); + SetShaderParm(5, 0); // Not completely on + } + else { // Turning off + BecomeActive(TH_THINK); + StartSound( "snd_powerdown", SND_CHANNEL_ANY ); + alphaOn.Init(gameLocal.time, WALLWALK_TRANSITION_TIME, curAlpha, 0.0f); + alphaOn.SetHermiteParms(WALLWALK_HERM_S1, 1.0f); // no overshoot + SetShaderParm(5, 0); // Not completely on + } +} + +void hhMoverWallwalk::Event_Activate(idEntity *activator) { + SetWallWalkable(!wallwalkOn); +} + + +//============================================================================= +//============================================================================= +// +// hhExplodeMover +// +// Mover explodes from a specific point to the destination when triggered +//============================================================================= +//============================================================================= + +CLASS_DECLARATION( hhMover, hhExplodeMover ) + EVENT( EV_PostSpawn, hhExplodeMover::Event_PostSpawn ) + EVENT( EV_Activate, hhExplodeMover::Event_Trigger ) +END_CLASS + +void hhExplodeMover::Spawn(void) { + moveDelay = spawnArgs.GetFloat( "moveDelay", "0" ); + PostEventMS( &EV_PostSpawn, 0 ); +} + +void hhExplodeMover::Event_PostSpawn( void ) { + // Set up the destination location after this object is triggered + dest_position = spawnArgs.GetVector( "destOrigin", "0 0 0" ); +} + +void hhExplodeMover::Save(idSaveGame *savefile) const { + savefile->WriteInt(oldContents); + savefile->WriteInt(oldClipMask); + savefile->WriteFloat(moveDelay); +} + +void hhExplodeMover::Restore( idRestoreGame *savefile ) { + savefile->ReadInt(oldContents); + savefile->ReadInt(oldClipMask); + savefile->ReadFloat(moveDelay); +} + +void hhExplodeMover::Event_Trigger( idEntity *activator ) { + // When triggered, blast self to the original position + oldContents = GetPhysics()->GetContents(); + GetPhysics()->SetContents( 0 ); + + oldClipMask = GetPhysics()->GetClipMask(); + GetPhysics()->SetClipMask( 0 ); + + BeginMove( NULL ); +} + +void hhExplodeMover::DoneMoving( void ) { + idMover::DoneMoving(); + GetPhysics()->SetContents( oldContents ); + GetPhysics()->SetClipMask( oldClipMask ); +} + +//============================================================================= +//============================================================================= +// +// hhExplodeMoverOrigin +// +// Origin object for exploding movers. +//============================================================================= +//============================================================================= + +CLASS_DECLARATION( idEntity, hhExplodeMoverOrigin ) + EVENT( EV_Activate, hhExplodeMoverOrigin::Event_Trigger ) +END_CLASS + +void hhExplodeMoverOrigin::Spawn(void) { +} + +void hhExplodeMoverOrigin::Event_Trigger( idEntity *activator ) { + int count = 0; + +/*FIXME: Should use this format instead for speed +for ( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->IsType( idLight::Type ) ) { + idLight *light = static_cast(ent); + } +}*/ + // When triggered, locate explode movers than have this as their origin entity, and then trigger them + for (int i = 0; i < gameLocal.num_entities; i++ ) { + idEntity *ent = gameLocal.entities[i]; + if ( !ent || !ent->IsType( hhExplodeMover::Type ) ) { + continue; + } + + hhExplodeMover *mover = static_cast(ent); + if( mover->targets.Num() > 0 && mover->targets[0].GetEntity() == this ) { // Trigger all explode movers that have this origin as a target + mover->PostEventSec( &EV_Activate, mover->GetMoveDelay(), this ); + count++; + } + } +} diff --git a/src/Prey/game_mover.h b/src/Prey/game_mover.h new file mode 100644 index 0000000..9dc4557 --- /dev/null +++ b/src/Prey/game_mover.h @@ -0,0 +1,73 @@ +#ifndef __PREY_MOVER_H__ +#define __PREY_MOVER_H__ + + +class hhMover : public idMover { + CLASS_PROTOTYPE( hhMover ); + +public: + void Spawn(); + void BecomeNonSolid(); + void Save( idSaveGame *savefile ) const { } + void Restore( idRestoreGame *savefile ) { Spawn(); } +}; + + +class hhMoverWallwalk : public hhMover { + CLASS_PROTOTYPE(hhMoverWallwalk); + +public: + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void SetWallWalkable(bool on); + void Think(); + +protected: + void Event_Activate(idEntity *activator); + +protected: + hhHermiteInterpolate alphaOn; // Degree to which wallwalk is on + bool wallwalkOn; + bool flicker; + const idDeclSkin *onSkin; + const idDeclSkin *offSkin; +}; + + +//============================================================================= + +class hhExplodeMover : public hhMover { + CLASS_PROTOTYPE( hhExplodeMover ); + +public: + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void DoneMoving( void ); + + float GetMoveDelay( void ) { return moveDelay; } + +protected: + void Event_PostSpawn( void ); + void Event_Trigger( idEntity *activator ); + +protected: + int oldContents; // contents before moving (contents are none while moving) + int oldClipMask; + float moveDelay; +}; + +class hhExplodeMoverOrigin : public idEntity { + CLASS_PROTOTYPE( hhExplodeMoverOrigin ); + +public: + void Spawn(); +protected: + void Event_PostSpawn( void ); + void Event_Trigger( idEntity *activator ); +}; + +//============================================================================= + +#endif // __PREY_MOVER_H__ diff --git a/src/Prey/game_note.cpp b/src/Prey/game_note.cpp new file mode 100644 index 0000000..0514a6e --- /dev/null +++ b/src/Prey/game_note.cpp @@ -0,0 +1,29 @@ +// Game_note.cpp +// + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +CLASS_DECLARATION(hhConsole, hhNote) +END_CLASS + + +void hhNote::Spawn(void) { + + if ( renderEntity.gui[0] ) { + renderEntity.gui[0]->SetStateString("guitext", spawnArgs.GetString("text", "no text")); + + // For overriding the text that is defined in the Map file. Used when we put a note + // in as a placeholder for obsolete items. The text key may already be used (consoles), + // so use this to override the text. + idStr override; + if (spawnArgs.GetString("textoverride", "", override)) { + renderEntity.gui[0]->SetStateString("guitext", override.c_str()); + } + } +} + + diff --git a/src/Prey/game_note.h b/src/Prey/game_note.h new file mode 100644 index 0000000..8058036 --- /dev/null +++ b/src/Prey/game_note.h @@ -0,0 +1,13 @@ + +#ifndef __GAME_NOTE_H__ +#define __GAME_NOTE_H__ + +class hhNote : public hhConsole { +public: + CLASS_PROTOTYPE( hhNote ); + + void Spawn( void ); +}; + + +#endif /* __GAME_NOTE_H__ */ diff --git a/src/Prey/game_organtrigger.cpp b/src/Prey/game_organtrigger.cpp new file mode 100644 index 0000000..769429a --- /dev/null +++ b/src/Prey/game_organtrigger.cpp @@ -0,0 +1,190 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +/* +add particles to bone +make wait: -1 work (removes self now) +*/ + +#include "prey_local.h" + +const idEventDef EV_PlayPainIdle("", NULL); +const idEventDef EV_ResetOrgan("", NULL); + +CLASS_DECLARATION( hhAnimatedEntity, hhOrganTrigger ) + EVENT( EV_Enable, hhOrganTrigger::Event_Enable ) + EVENT( EV_Disable, hhOrganTrigger::Event_Disable ) + EVENT( EV_PlayIdle, hhOrganTrigger::Event_PlayIdle) + EVENT( EV_PlayPainIdle, hhOrganTrigger::Event_PlayPainIdle) + EVENT( EV_ResetOrgan, hhOrganTrigger::Event_ResetOrgan) + EVENT( EV_PostSpawn, hhOrganTrigger::Event_PostSpawn ) +END_CLASS + + +//-------------------------------- +// hhOrganTrigger::~hhOrganTrigger +//-------------------------------- +hhOrganTrigger::~hhOrganTrigger() { + SAFE_REMOVE( trigger ); +} + +//-------------------------------- +// hhOrganTrigger::Event_PostSpawn +//-------------------------------- +void hhOrganTrigger::Event_PostSpawn() { + SpawnTrigger(); +} + +//-------------------------------- +// hhOrganTrigger::Spawn +//-------------------------------- +void hhOrganTrigger::Spawn( void ) { + fl.takedamage = true; + GetPhysics()->SetContents( CONTENTS_SOLID ); + + idleAnim = GetAnimator()->GetAnim( "idle" ); + painAnim = GetAnimator()->GetAnim( "pain" ); + resetAnim = GetAnimator()->GetAnim( "reset" ); + painIdleAnim = GetAnimator()->GetAnim( "painidle" ); + + trigger = NULL; + if (!gameLocal.isClient) { + if (gameLocal.isMultiplayer) { + PostEventMS(&EV_PostSpawn, 0); + } else { //spawn right now. i don't want to break save/load stuff... + SpawnTrigger(); + } + } + + ProcessEvent( &EV_PlayIdle ); + + ProcessEvent( (spawnArgs.GetBool("enabled", "1")) ? &EV_Enable : &EV_Disable ); +} + +void hhOrganTrigger::Save(idSaveGame *savefile) const { + savefile->WriteInt( idleAnim ); + savefile->WriteInt( painAnim ); + savefile->WriteInt( resetAnim ); + savefile->WriteInt( painIdleAnim ); + trigger.Save(savefile); +} + +void hhOrganTrigger::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( idleAnim ); + savefile->ReadInt( painAnim ); + savefile->ReadInt( resetAnim ); + savefile->ReadInt( painIdleAnim ); + trigger.Restore(savefile); +} + +//-------------------------------- +// hhOrganTrigger::SpawnTrigger +//-------------------------------- +void hhOrganTrigger::SpawnTrigger() { + idDict args = spawnArgs; + + args.SetVector( "mins", idVec3(-1.0f, -1.0f, -1.0f) ); + args.SetVector( "maxs", idVec3(1.0f, 1.0f, 1.0f) ); + args.SetBool( "noTouch", true ); + args.SetBool( "enabled", false ); + args.Delete( "spawnclass" ); + args.Delete( "name" ); + args.Delete( "model" ); + trigger = gameLocal.SpawnObject( spawnArgs.GetString("def_trigger"), &args ); + if( trigger.IsValid() ) { + //Bound entities are removed when their master is removed + trigger->Bind( this, true ); + } +} + +//-------------------------------- +// hhOrganTrigger::Killed +//-------------------------------- +void hhOrganTrigger::Killed( idEntity* inflictor, idEntity* attacker, int damage, const idVec3& dir, int location ) { + GetAnimator()->ClearAllAnims( gameLocal.GetTime(), 0 ); + GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, painAnim, gameLocal.GetTime(), 100 ); + int opentime = GetAnimator()->GetAnim( painAnim )->Length(); + PostEventMS( &EV_PlayPainIdle, opentime ); + + if( !trigger.IsValid() ) { + return; + } + + trigger->TriggerAction( attacker ); + + Disable(); + + if( trigger->wait >= 0 ) { + PostEventMS( &EV_ResetOrgan, trigger->nextTriggerTime - gameLocal.GetTime() ); + } +} + +//-------------------------------- +// hhOrganTrigger::Enable +//-------------------------------- +void hhOrganTrigger::Enable() { + fl.takedamage = true; + + if( trigger.IsValid() ) { + trigger->Enable(); + } +} + +//-------------------------------- +// hhOrganTrigger::Disable +//-------------------------------- +void hhOrganTrigger::Disable() { + fl.takedamage = false; + + if( trigger.IsValid() ) { + trigger->Disable(); + } +} + +//-------------------------------- +// hhOrganTrigger::Event_Enable +//-------------------------------- +void hhOrganTrigger::Event_Enable() { + Enable(); +} + +//-------------------------------- +// hhOrganTrigger::Event_Disable +//-------------------------------- +void hhOrganTrigger::Event_Disable() { + Disable(); +} + +//-------------------------------- +// hhOrganTrigger::Event_PlayIdle +//-------------------------------- +void hhOrganTrigger::Event_PlayIdle( void ) { + Enable(); + + if( idleAnim ) { + GetAnimator()->ClearAllAnims( gameLocal.GetTime(), 0 ); + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, idleAnim, gameLocal.GetTime(), 0 ); + } +} + +//-------------------------------- +// hhOrganTrigger::Event_PlayPainIdle +//-------------------------------- +void hhOrganTrigger::Event_PlayPainIdle( void ) { + if( painIdleAnim ) { + GetAnimator()->ClearAllAnims( gameLocal.GetTime(), 0 ); + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, painIdleAnim, gameLocal.GetTime(), 100 ); + } +} + +//-------------------------------- +// hhOrganTrigger::Event_ResetOrgan +//-------------------------------- +void hhOrganTrigger::Event_ResetOrgan( void ) { + if( resetAnim ) { + GetAnimator()->ClearAllAnims( gameLocal.GetTime(), 0 ); + GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, resetAnim, gameLocal.GetTime(), 100 ); + int opentime = GetAnimator()->GetAnim( resetAnim )->Length(); + PostEventMS( &EV_PlayIdle, opentime ); + } +} \ No newline at end of file diff --git a/src/Prey/game_organtrigger.h b/src/Prey/game_organtrigger.h new file mode 100644 index 0000000..4d2d60e --- /dev/null +++ b/src/Prey/game_organtrigger.h @@ -0,0 +1,41 @@ +#ifndef __GAME_ORGANTRIGGER_H__ +#define __GAME_ORGANTRIGGER_H__ + +extern const idEventDef EV_ModelDoorOpen; +extern const idEventDef EV_ModelDoorClose; + +class hhOrganTrigger : public hhAnimatedEntity { +public: + CLASS_PROTOTYPE( hhOrganTrigger ); + + virtual ~hhOrganTrigger(); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Killed( idEntity* inflictor, idEntity* attacker, int damage, const idVec3& dir, int location ); + +protected: + void SpawnTrigger(); + + virtual void Enable(); + virtual void Disable(); + + void Event_Enable(); + void Event_Disable(); + void Event_PlayIdle( void ); + void Event_PlayPainIdle( void ); + void Event_ResetOrgan( void ); + virtual void Event_PostSpawn(); + +private: + int idleAnim; + int painAnim; + int resetAnim; + int painIdleAnim; + + idEntityPtr trigger; +}; + +#endif /* __GAME_ORGANTRIGGER_H__ */ diff --git a/src/Prey/game_player.cpp b/src/Prey/game_player.cpp new file mode 100644 index 0000000..6231228 --- /dev/null +++ b/src/Prey/game_player.cpp @@ -0,0 +1,7825 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +#define DAMAGE_INDICATOR_TIME 1100 // Update this in hud_damageindicator.guifragment too + +const idEventDef EV_PlayWeaponAnim( "playWeaponAnim", "sd" ); +const idEventDef EV_RechargeHealth( "", NULL ); +const idEventDef EV_RechargeRifleAmmo( "", NULL ); +const idEventDef EV_Cinematic( "cinematic", "dd" ); +const idEventDef EV_DialogStart( "dialogStart", "ddd" ); +const idEventDef EV_DialogStop( "dialogStop", NULL ); +const idEventDef EV_LotaTunnelMode( "lotaTunnelMode", "d" ); +const idEventDef EV_DrainSpiritPower( "", NULL ); +const idEventDef EV_SpawnDeathWraith( "", NULL ); +const idEventDef EV_PrepareToResurrect( "prepareToResurrect" ); +const idEventDef EV_ResurrectScreenFade( "" ); +const idEventDef EV_Resurrect( "resurrect" ); +const idEventDef EV_PrepareForDeathWorld( "", NULL ); +const idEventDef EV_EnterDeathWorld( "", NULL ); +const idEventDef EV_AdjustSpiritPowerDeathWalk( "" ); +const idEventDef EV_SetOverlayMaterial( "setOverlayMaterial", "sd" ); +const idEventDef EV_SetOverlayTime( "setOverlayTime", "fd" ); +const idEventDef EV_SetOverlayColor( "setOverlayColor", "ffff" ); +const idEventDef EV_DDAHeartbeat( "", NULL ); +const idEventDef EV_ShouldRemainAlignedToAxial( "shouldRemainAlignedToAxial", "d" ); +const idEventDef EV_StartHudTranslation( "" ); +const idEventDef EV_Unfreeze( "" ); +const idEventDef EV_GetSpiritPower( "getSpiritPower", "", 'f' ); //rww +const idEventDef EV_SetSpiritPower( "setSpiritPower", "f" ); //rww +const idEventDef EV_OnGround( "onGround", NULL, 'f' ); // bg +const idEventDef EV_BindUnfroze( "", "e" ); // mdl +const idEventDef EV_LockWeapon( "lockWeapon", "d" ); // mdl +const idEventDef EV_UnlockWeapon( "unlockWeapon", "d" ); // mdl +const idEventDef EV_SetPrivateCameraView( "setPrivateCameraView", "Ed" ); //rdr +const idEventDef EV_SetCinematicFOV( "setCinematicFOV", "ffff" ); //rdr +const idEventDef EV_StopSpiritWalk( "stopSpiritWalk" ); //rww +const idEventDef EV_DamagePlayer( "damagePlayer", "eevsfd" ); //rww +const idEventDef EV_GetSpiritProxy( "getSpiritProxy", "", 'e' ); //jsh +const idEventDef EV_IsSpiritWalking( "isSpiritWalking", "", 'd' ); // pdm +const idEventDef EV_IsDeathWalking( "isDeathWalking", "", 'd' ); // bjk +const idEventDef EV_GetDDAValue( "getDDAValue", "", 'f' ); // cjr +const idEventDef EV_AllowLighter( "allowLighter", "d" ); // jsh +const idEventDef EV_DisableSpirit( "disableSpirit" ); // mdl +const idEventDef EV_EnableSpirit( "enableSpirit" ); // mdl +const idEventDef EV_UpdateDDA( "" ); // cjr +const idEventDef EV_AllowDamage( "" ); // mdl +const idEventDef EV_IgnoreDamage( "" ); // mdl +const idEventDef EV_RespawnCleanup( "" ); //rww +const idEventDef EV_ReturnToWeapon( "returnToWeapon", "", 'd' ); //bjk +const idEventDef EV_CanAnimateTorso( "canAnimateTorso", "", 'd' ); //rww + +CLASS_DECLARATION( idPlayer, hhPlayer ) + EVENT( EV_PlayWeaponAnim, hhPlayer::Event_PlayWeaponAnim ) + EVENT( EV_RechargeHealth, hhPlayer::Event_RechargeHealth ) + EVENT( EV_RechargeRifleAmmo, hhPlayer::Event_RechargeRifleAmmo ) + EVENT( EV_Cinematic, hhPlayer::Event_Cinematic ) + EVENT( EV_DialogStart, hhPlayer::Event_DialogStart ) + EVENT( EV_DialogStop, hhPlayer::Event_DialogStop ) + EVENT( EV_LotaTunnelMode, hhPlayer::Event_LotaTunnelMode ) + EVENT( EV_DrainSpiritPower, hhPlayer::Event_DrainSpiritPower ) + EVENT( EV_SpawnDeathWraith, hhPlayer::Event_SpawnDeathWraith ) + EVENT( EV_PrepareToResurrect, hhPlayer::Event_PrepareToResurrect ) + EVENT( EV_ResurrectScreenFade, hhPlayer::Event_ResurrectScreenFade ) + EVENT( EV_Resurrect, hhPlayer::Event_Resurrect ) + EVENT( EV_EnterDeathWorld, hhPlayer::Event_EnterDeathWorld ) + EVENT( EV_PrepareForDeathWorld, hhPlayer::Event_PrepareForDeathWorld ) + EVENT( EV_AdjustSpiritPowerDeathWalk, hhPlayer::Event_AdjustSpiritPowerDeathWalk ) + EVENT( EV_ShouldRemainAlignedToAxial, hhPlayer::Event_ShouldRemainAlignedToAxial ) + EVENT( EV_OrientToGravity, hhPlayer::Event_OrientToGravity ) + EVENT( EV_ResetGravity, hhPlayer::Event_ResetGravity ) + EVENT( EV_SetOverlayMaterial, hhPlayer::Event_SetOverlayMaterial ) + EVENT( EV_SetOverlayTime, hhPlayer::Event_SetOverlayTime ) + EVENT( EV_SetOverlayColor, hhPlayer::Event_SetOverlayColor ) + EVENT( EV_DDAHeartbeat, hhPlayer::Event_DDAHeartBeat ) + EVENT( EV_StartHudTranslation, hhPlayer::Event_StartHUDTranslation) + EVENT( EV_Unfreeze, hhPlayer::Event_Unfreeze) + EVENT( EV_GetSpiritPower, hhPlayer::Event_GetSpiritPower) //rww + EVENT( EV_SetSpiritPower, hhPlayer::Event_SetSpiritPower) //rww + EVENT( EV_OnGround, hhPlayer::Event_OnGround ) // bg + EVENT( EV_LockWeapon, hhPlayer::Event_LockWeapon ) // mdl + EVENT( EV_UnlockWeapon, hhPlayer::Event_UnlockWeapon ) // mdl + EVENT( EV_SetPrivateCameraView, hhPlayer::Event_SetPrivateCameraView ) //rdr + EVENT( EV_SetCinematicFOV, hhPlayer::Event_SetCinematicFOV ) //rdr + EVENT( EV_StopSpiritWalk, hhPlayer::Event_StopSpiritWalk ) //rww + EVENT( EV_DamagePlayer, hhPlayer::Event_DamagePlayer ) //rww + EVENT( EV_GetSpiritProxy, hhPlayer::Event_GetSpiritProxy ) //rww + EVENT( EV_IsSpiritWalking, hhPlayer::Event_IsSpiritWalking ) //pdm + EVENT( EV_IsDeathWalking, hhPlayer::Event_IsDeathWalking ) //bjk + EVENT( EV_GetDDAValue, hhPlayer::Event_GetDDAValue ) // cjr + EVENT( EV_AllowLighter, hhPlayer::Event_AllowLighter ) // jsh + EVENT( EV_DisableSpirit, hhPlayer::Event_DisableSpirit ) // mdl + EVENT( EV_EnableSpirit, hhPlayer::Event_EnableSpirit ) // mdl + EVENT( EV_UpdateDDA, hhPlayer::Event_UpdateDDA ) // cjr + EVENT( EV_AllowDamage, hhPlayer::Event_AllowDamage ) // mdl + EVENT( EV_IgnoreDamage, hhPlayer::Event_IgnoreDamage ) // mdl + EVENT( EV_RespawnCleanup, hhPlayer::Event_RespawnCleanup ) //rww + EVENT( EV_ReturnToWeapon, hhPlayer::Event_ReturnToWeapon ) //bjk + EVENT( EV_CanAnimateTorso, hhPlayer::Event_CanAnimateTorso ) //rww +END_CLASS + +//rww +CLASS_DECLARATION( hhPlayer, hhArtificialPlayer ) +END_CLASS + +/* +================= +hhPlayer::hhPlayer +================= +*/ +hhPlayer::hhPlayer( void ) : + thirdPersonCameraClipBounds( idTraceModel(idBounds(idVec3(-4, -8, -8), idVec3(8, 8, 8))) ) { + + hand = NULL; // nla + spiritProxy = NULL; + possessedTommy = NULL; + talon = NULL; + handNext = NULL; + deathLookAtEntity = NULL; + guiWantsControls = NULL; + lighterHandle = -1; + nextTalonAttackCommentTime = 0; + bTalonAttackComment = false; + bSpiritWalk = false; + bDeathWalk = false; + bReallyDead = false; + bPossessed = false; + bInCinematic = false; + bFrozen = false; + bScopeView = false; //rww + bInDeathwalkTransition = false; + lastAppliedBobCycle = 0; +#if GAMEPAD_SUPPORT // VENOM BEGIN + lastAutoLevelTime = 0; + lastAccelTime = 0; + lastAccelFactor = 1.0f; +#endif // VENOM END + cinematicFOV.Init( gameLocal.time, 0.f, 0.f, 0.f, 90.f, 90.f ); //rdr + mpHitFeedbackTime = 0; //rww + bAllowSpirit = true; //mdl + bCollidingWithPortal = false; + bPlayingLowHealthSound = false; + bLotaTunnelMode = false; + + for (int ix=0; ix 8 ) { + // Go to the highest weapon available if no weapon is current selected (NOTE: This will be ignored if all weapons are locked) + NextBestWeapon(); //HUMANHEAD bjk + } + + //HUMANHEAD PCF mdl 04/26/06 - Made this a separate if block from above so we can disallow locked weapons + if ( ! ( weaponFlags & ( 1 << ( idealWeapon - 1 ) ) ) ) { + // If the current weapon is invalid now, try the next weapon + NextWeapon(); + if ( ! ( weaponFlags & ( 1 << ( idealWeapon - 1 ) ) ) ) { + // No weapons available + if ( weapon.GetEntity() ) { + weapon.GetEntity()->PutAway(); + weapon.GetEntity()->HideWeapon(); + } + idealWeapon = 0; + currentWeapon = -1; + } + } + + nextSpiritTime = 0; + + mpHitFeedbackTime = 0; //rww + + airAttackerTime = 0; + bDeathWalkStage2 = false; + + physicsObj.SetInwardGravity(-1); //rww +} + +void hhPlayer::RestorePersistantInfo( void ) { + int num; + + idPlayer::RestorePersistantInfo(); + + // Update persistent amoo maximums + num = GetWeaponNum("weaponobj_soulstripper"); + assert(num); + weaponInfo[ num ].ammoMax = spawnArgs.GetInt( "max_ammo_energy" ); + + num = GetWeaponNum("weaponobj_bow"); + assert(num); + weaponInfo[ num ].ammoMax = spawnArgs.GetInt( "max_ammo_spiritpower" ); +} + +/* +============== +hhPlayer::SpawnTalon() +============== +*/ +void hhPlayer::TrySpawnTalon() { + // TODO: Deal with co-op issues. Have multiple Talons in co-op, or just one? + if (inventory.requirements.bCanSummonTalon && !talon.IsValid()) { + talon = (hhTalon *)gameLocal.SpawnObject( spawnArgs.GetString( "def_hawkpower" ), NULL ); + if( talon.IsValid() ) { + talon->SetOwner( this ); + talon->SummonTalon(); + bTalonAttackComment = true; + nextTalonAttackCommentTime = 0; + } + } +} + +/* +============== +hhPlayer::TalonAttackComment + +Talon is attacking an enemy, so have Tommy make a comment +============== +*/ +void hhPlayer::TalonAttackComment() { + if ( IsDeathWalking() ) { + return; + } + + // Play a sound on the player to encourage Talon to attack + if ( bTalonAttackComment && gameLocal.GetTime() > nextTalonAttackCommentTime ) { // Player can make an attack comment + int num = spawnArgs.GetInt( "numAttackComments", "6" ); + + StartSound( va( "snd_talonattack%d", gameLocal.random.RandomInt( num ) ), SND_CHANNEL_VOICE, 0, false, NULL ); + nextTalonAttackCommentTime = gameLocal.GetTime() + spawnArgs.GetInt( "talonAttackCommentTime", "30000" ); + } +} + +/* +============== +hhPlayer::~hhPlayer() + +Release any resources used by the player. +============== +*/ +hhPlayer::~hhPlayer() { + //rww - remove me if i'm a pilot + if (InVehicle() && GetVehicleInterface() && GetVehicleInterface()->GetVehicle()) { + GetVehicleInterface()->GetVehicle()->EjectPilot(); + } + + StopSpiritWalk(); + + RemoveResources(); +} + + +void hhPlayer::RemoveResources() { + LighterOff(); + SAFE_REMOVE( hand ); //FIXME: Should this be something other than a safe remove? (ie, remove hand?) + SAFE_REMOVE( talon ); + SAFE_REMOVE( spiritProxy ); + SAFE_REMOVE( possessedTommy ); +} + + +/* +============== +hhPlayer::Init() + +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 +Called during SpawnToPoint +============== +*/ +void hhPlayer::Init() { + oldCmdAngles.Zero(); + + idPlayer::Init(); + + // initialize the script variables + AI_ASIDE = false; + + bInDeathwalkTransition = false; + bShowProgressBar = false; + progressBarValue = 0.0f; + progressBarState = 0; + progressBarGuiValue.Init(gameLocal.time, 0, 0.0f, 0.0f); + + LighterOff(); //rww - make sure lighter is removed first (primarily for mp) + + bobFrac = 0.0f; + lighterTemperature = 0; + lighterHandle = -1; + bPossessed = false; // cjr - player is unpossessed at the start + possessionTimer = 0; // cjr - player is unpossessed at the start + possessionFOV = 0; // cjr - no FOV change for possession at start + nextTalonAttackCommentTime = 0; + bTalonAttackComment = false; // cjr - If Tommy can make comments about talon attacking enemies + bSpiritWalk = false; // cjr - initialize spiritwalk variables + spiritProxy = NULL; // cjr - initialize spiritwalk variables + possessedTommy = NULL; // cjr - initialize possessed tommy + lastWeaponSpirit = 0; // cjr - initialize spiritwalk variables + guiWantsControls = NULL; // pdm - gui that controls should be routed to + bClampYaw = false; // pdm - whether do clamp yaw to master's axis (for rail rides) + maxRelativeYaw = 180.0f; // pdm - max deviation from master axis allowed when clamping yaw + maxRelativePitch = 0.0f; // rww - max deviation from master axis allowed when clamping pitch + bInCinematic = false; // pdm - our simple cinematic system (position locking) + lockView = false; + preCinematicWeapon = 0; + preCinematicWeaponFlags = 0; + guiOverlay = NULL; // pdm - current overlay gui + spiritWalkToggleTime = 0; + bLotaTunnelMode = false; + + StopSound(SND_CHANNEL_HEART, false); //rww - make sure this is stopped here, could conceivably still be playing on respawn + bPlayingLowHealthSound = false; + + // Set shuttle view off + if (entityNumber == gameLocal.localClientNum) { //rww + renderSystem->SetShuttleView( false ); + } + + if (gameLocal.isMultiplayer) { //rww - reset my really-deadness when i respawn in MP + bReallyDead = false; + SetSkinByName(NULL); //make sure we are not using spiritwalk skin. + } + + SetViewAnglesSensitivity(1.0f); + cameraInterpolator.SetSelf( this ); + + for (int ix=0; ixIsType(hhForceField::Type) || ent->IsType(hhDeathWraith::Type)) ) { + idPlayer::ApplyImpulse( ent, id, point, impulse ); + } +} + +/* +================ +hhPlayer::CL_UpdateProgress + Client function to update progress related variables + called after getting a snapshot, or directly if a local client +================ +*/ +void hhPlayer::CL_UpdateProgress(bool bBar, float value, int state) { + idUserInterface *_hud = NULL; + + if (InVehicle() && GetVehicleInterfaceLocal() && GetVehicleInterfaceLocal()->GetHUD()) { + _hud = GetVehicleInterfaceLocal()->GetHUD(); + } + else { + _hud = hud; + } + + // Update progress bar + if (bBar && !bShowProgressBar) { + _hud->HandleNamedEvent("ShowProgressBar"); + } + else if (!bBar && bShowProgressBar) { + _hud->HandleNamedEvent("HideProgressBar"); + } + + // See if we just entered a victory state + if (state != progressBarState) { + switch (state) { + case 0: + _hud->HandleNamedEvent("ProgressNone"); + break; + case 1: + _hud->HandleNamedEvent("ProgressVictory"); + break; + case 2: + _hud->HandleNamedEvent("ProgressFailure"); + break; + } + } + + // Interpolate gui value to target value + float curValue = progressBarGuiValue.GetCurrentValue(gameLocal.time); + progressBarGuiValue.Init(gameLocal.time, 500, curValue, value); + + // Update variables + bShowProgressBar = bBar; + progressBarValue = value; + progressBarState = state; +} + +// This called once per tick, put things in here that should be updated regardless of frame rate +// PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +void hhPlayer::UpdateHud( idUserInterface *_hud ) { + idPlayer *aimed; + + if ( !_hud ) { + return; + } + + if ( entityNumber != gameLocal.localClientNum ) { + return; + } + +#define PICKUPTIME_FADEIN_START 0 +#define PICKUPTIME_FADEIN_END 500 +#define PICKUPTIME_FADEOUT_START 1750 +#define PICKUPTIME_FADEOUT_END 2000 +#define MULTIPLEITEM_SPEEDUP 7 + int c = inventory.pickupItemNames.Num(); + int time; + int i; + int pickupTimeFadeInStart, pickupTimeFadeInStop, pickupTimeFadeOutStart, pickupTimeFadeOutStop; + float scale = 1.0f / (((c-1.0f)/9.0f) * (MULTIPLEITEM_SPEEDUP-1.0f) + 1.0f); + pickupTimeFadeInStart = PICKUPTIME_FADEIN_START; + pickupTimeFadeInStop = PICKUPTIME_FADEIN_END; + pickupTimeFadeOutStart = PICKUPTIME_FADEOUT_START * scale; + pickupTimeFadeOutStop = PICKUPTIME_FADEOUT_END * scale; + + // Determine fade in color for icons + if (c > 0) { + for ( i = 0; i < c; i++ ) { + inventory.pickupItemNames[i].time += gameLocal.msec; + time = inventory.pickupItemNames[i].time; + + if (time < pickupTimeFadeInStart) { + inventory.pickupItemNames[i].matcolorAlpha = 0.0f; + } + else if (time < pickupTimeFadeInStop) { + inventory.pickupItemNames[i].matcolorAlpha = ((float)(time-pickupTimeFadeInStart))/(pickupTimeFadeInStop-pickupTimeFadeInStart); + } + else { + inventory.pickupItemNames[i].matcolorAlpha = 1.0f; + } + } + + // Determine fadeout of slot zero icon + inventory.pickupItemNames[0].slotZeroTime += gameLocal.msec; + time = inventory.pickupItemNames[0].slotZeroTime; + if (time < pickupTimeFadeOutStart) { + inventory.pickupItemNames[0].matcolorAlpha = 1.0f; + } + else if (time < pickupTimeFadeOutStop) { + inventory.pickupItemNames[0].matcolorAlpha = 1.0f - ((float)(time-pickupTimeFadeOutStart))/(pickupTimeFadeOutStop-pickupTimeFadeOutStart); + } + else { + inventory.pickupItemNames[0].matcolorAlpha = 0.0f; + } + + // Remove any expired icons + time = inventory.pickupItemNames[0].slotZeroTime; + if (time > pickupTimeFadeOutStop) { + // icon has faded out, remove from list and slide them down + inventory.pickupItemNames.RemoveIndex(0); + } + } + + // HUMANHEAD pdm: Changed aim logic to always show color-coded aimee + if ( MPAim != -1 && gameLocal.entities[ MPAim ] + && gameLocal.entities[ MPAim ]->IsType( idPlayer::Type ) ) { + + aimed = static_cast< idPlayer * >( gameLocal.entities[ MPAim ] ); + _hud->SetStateString( "aim_text", gameLocal.userInfo[ MPAim ].GetString( "ui_name" ) ); + idVec4 teamColor = aimed->GetTeamColor(); + _hud->SetStateFloat( "aim_R", teamColor.x ); + _hud->SetStateFloat( "aim_G", teamColor.y ); + _hud->SetStateFloat( "aim_B", teamColor.z ); + + //HUMANHEAD rww - health display on hud for gameplay testing + if (g_showAimHealth.GetBool()) { + _hud->SetStateInt("aim_health", gameLocal.entities[MPAim]->health); + } + //HUMANHEAD END + } + else { + _hud->SetStateString( "aim_text", "" ); + + //HUMANHEAD rww + _hud->SetStateInt("aim_health", 0); + //HUMANHEAD END + } + +#if !GOLD + _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" ); + } +#endif + + if (gameLocal.isNewFrame) { //HUMANHEAD rww - only on newframe + // Update low health sound + if ( health > 0 && health < 25 && !IsSpiritOrDeathwalking() ) { + if (!bPlayingLowHealthSound) { + StartSound("snd_lowhealth", SND_CHANNEL_HEART, 0, false, NULL); + bPlayingLowHealthSound = true; + } + } + else { + if (bPlayingLowHealthSound) { + StopSound(SND_CHANNEL_HEART, false); + bPlayingLowHealthSound = false; + } + } + } + + // Handle damage indictors + idVec3 localDamageVector; + idVec3 toEnemy; + for (int ix=0; ixHandleNamedEvent( va("Attacked%i", ix) );//Notify hud we were attacked + _hud->SetStateBool("displayDamageIndicators", true); + lastAttackers[ix].displayed = true; + } + + toEnemy = lastAttackers[ix].attacker->GetOrigin() - GetOrigin(); + toEnemy.z = 0.0f; + GetAxis().ProjectVector( toEnemy, localDamageVector ); + float yawToEnemy = idMath::AngleNormalize180(localDamageVector.ToYaw()); + _hud->SetStateFloat( va("yawToEnemy%i", ix), yawToEnemy); + if (gameLocal.time > lastAttackers[ix].time + DAMAGE_INDICATOR_TIME) { + lastAttackers[ix].attacker = NULL; + + bool allExpired = true; + for (int jx=0; jxSetStateBool("displayDamageIndicators", false); + } + } + } + } + + if (gameLocal.isMultiplayer) { + bool bLagged = isLagged && gameLocal.isMultiplayer && (gameLocal.localClientNum == entityNumber); + _hud->SetStateBool("hudLag", bLagged); + } + // HUMANHEAD PCF pdm 06-26-06: Fix for multiplayer shuttle hud elements appearing in SP after a MP session + else { + _hud->SetStateBool( "ismultiplayer", false ); + } + // HUMANHEAD END +} + +// PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +void hhPlayer::UpdateHudWeapon(bool flashWeapon) { + idUserInterface *_hud = hhPlayer::hud; + + // if updating the hud of a followed client + if ( gameLocal.localClientNum >= 0 && gameLocal.entities[ gameLocal.localClientNum ] && gameLocal.entities[ gameLocal.localClientNum ]->IsType( idPlayer::Type ) ) { + idPlayer *p = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.localClientNum ] ); + if ( p->spectating && p->spectator == entityNumber ) { + assert( p->hud ); + _hud = p->hud; + } + } + + if ( _hud ) { + // HUMANHEAD pdm: changed to suit our needs + _hud->SetStateInt( "currentweapon", GetCurrentWeapon() ); + _hud->SetStateInt( "idealweapon", GetIdealWeapon() ); + //HUMANHEAD PCF mdl 05/05/06 - Added !IsLocked( GetIdealWeapon() ) + if ( flashWeapon && !IsLocked( GetIdealWeapon() ) ) { + _hud->HandleNamedEvent( "weaponChange" ); + } + } +} + +// PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +void hhPlayer::UpdateHudAmmo(idUserInterface *_hud) { + assert( _hud ); + float ammoPct, altPct; + int ammoType, altAmmoType; + float ammo, altAmmo; + bool ammoLow, altAmmoLow; + + // HUMANHEAD pdm: Weapon switch overlay, using rover to spread the expense over frames + static int rover = 0; + if (++rover > 9) { + rover = 0; + } + + if (rover) { + bool bHeld = false; + ammoPct = 0.0f; + altPct = 0.0f; + ammoType = weaponInfo[rover].ammoType; + altAmmoType = altWeaponInfo[rover].ammoType; + ammo = inventory.ammo[ammoType]; + altAmmo = inventory.ammo[altAmmoType]; + ammoLow = false; + altAmmoLow = false; + + if ( inventory.weapons & ( 1 << rover ) ) { + // have this weapon + bHeld = true; + ammoPct = ammo / weaponInfo[rover].ammoMax; + altPct = altAmmo / altWeaponInfo[rover].ammoMax; + ammoLow = ammo > 0 && ammo <= weaponInfo[rover].ammoLow; + altAmmoLow = altAmmo > 0 && altAmmo <= altWeaponInfo[rover].ammoLow; + } + + // Spirit ammo doesn't display in a bar + if ( ammoType == 1 ) { + ammoType = 0; + altAmmoType = 0; + } + + // Zero out alt-ammo bar if appropriate + if (altAmmoType == 0 || altAmmoType == ammoType) { + altPct = 0.0f; + } + + _hud->SetStateBool (va("weapon%d_held", rover), bHeld); + _hud->SetStateBool (va("weapon%d_ammolow", rover), ammoLow); + _hud->SetStateBool (va("weapon%d_altammolow", rover), altAmmoLow); + _hud->SetStateFloat(va("weapon%d_ammo", rover), ammoPct); + _hud->SetStateFloat(va("weapon%d_altammo", rover), altPct); + _hud->SetStateBool (va("weapon%d_ammoempty", rover), ammoType != 0 && ammo == 0 && altAmmo == 0); + } + + // Update the current weapon's ammo status + bool bDisallowAmmoBars = false; + ammoPct = 0.0f; + altPct = 0.0f; + ammoLow = false; + altAmmoLow = false; + ammoType = 0; + altAmmoType = 0; + ammo = 0; + altAmmo = 0; + + if( bLotaTunnelMode || privateCameraView || IsLocked(idealWeapon) || !weapon.IsValid() || !weapon->IsLinked() || currentWeapon == -1) { + // Don't display ammo bar for invalid weapons, or when weapons are locked + bDisallowAmmoBars = true; + } + else { + if (currentWeapon >= 0 && currentWeapon < 15) { + ammoType = weaponInfo[currentWeapon].ammoType; + altAmmoType = altWeaponInfo[currentWeapon].ammoType; + ammo = inventory.ammo[ammoType]; + altAmmo = inventory.ammo[altAmmoType]; + ammoPct = ammo / weaponInfo[currentWeapon].ammoMax; + altPct = altAmmo / altWeaponInfo[currentWeapon].ammoMax; + ammoLow = ammo > 0 && ammo <= weaponInfo[currentWeapon].ammoLow; + altAmmoLow = altAmmo > 0 && altAmmo <= altWeaponInfo[currentWeapon].ammoLow; + } + + if (ammoType == 1) { + bDisallowAmmoBars = true; + } + } + + if ( bDisallowAmmoBars ) { + _hud->SetStateBool( "player_ammobar", false); + _hud->SetStateBool( "player_altammobar", false ); + } + else { + _hud->SetStateBool( "player_ammobar", ammoType != 0); + _hud->SetStateBool( "player_altammobar", altAmmoType != 0 && altAmmoType != ammoType ); + + _hud->SetStateFloat( "player_ammopercent", ammoPct ); + _hud->SetStateFloat( "player_altammopercent", altPct ); + _hud->SetStateString( "player_ammoamounttext", ammo<0 ? "" : va("%d", ammo) ); + _hud->SetStateString( "player_altammoamounttext", altAmmo<0 ? "" : va("%d", altAmmo) ); + _hud->SetStateBool( "player_ammolow", ammoLow ); + _hud->SetStateBool( "player_altammolow", altAmmoLow ); + } + +} + +/* +================ +hhPlayer::UpdateHudStats + PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +================ +*/ +void hhPlayer::UpdateHudStats( idUserInterface *_hud ) { + + assert( _hud ); + + int spiritPower = GetSpiritPower(); + + // Disallow health if wrench is locked on a non-lota map --or-- if in a private camera + bool disallowHealth = bLotaTunnelMode || (IsLocked(1) && !gameLocal.IsLOTA()) || privateCameraView != NULL; + bool disallowProgress = bLotaTunnelMode || privateCameraView != NULL || IsDeathWalking(); + + if (disallowHealth) { + int blah = 0; + } + else { + int blah2 = 0; + } + + _hud->SetStateBool("invehicle", InVehicle()); + _hud->SetStateBool("deathwalking", IsDeathWalking()); + _hud->SetStateBool("spiritwalking", IsSpiritWalking()); + _hud->SetStateBool("lighter", IsLighterOn() ); + _hud->SetStateBool("showhealth", !disallowHealth); + _hud->SetStateBool("allowprogress", !disallowProgress); + _hud->SetStateBool("showspiritpower", (!disallowHealth && !InVehicle() && (inventory.requirements.bCanSpiritWalk || IsDeathWalking()))); + _hud->SetStateFloat("lightertemp", lighterTemperature); + _hud->SetStateFloat( "player_spiritpercent", ((float)spiritPower)/inventory.maxSpirit ); + _hud->SetStateFloat( "player_healthpercent", ((float)health)/inventory.maxHealth ); + _hud->SetStateFloat( "player_healthR", inventory.maxHealth > 100 ? 0.75f : 0.6f ); + _hud->SetStateFloat( "player_healthG", inventory.maxHealth > 100 ? 0.85f : 0.0f ); + _hud->SetStateFloat( "player_healthB", inventory.maxHealth > 100 ? 1.0f : 0.0f ); + _hud->SetStateFloat( "player_healthPulseR", inventory.maxHealth > 100 ? 1.0f : 1.0f ); + _hud->SetStateFloat( "player_healthPulseG", inventory.maxHealth > 100 ? 1.0f : 0.0f ); + _hud->SetStateFloat( "player_healthPulseB", inventory.maxHealth > 100 ? 1.0f : 0.0f ); + _hud->SetStateInt( "player_health", health ); + _hud->SetStateInt( "player_maxhealth", inventory.maxHealth ); //rww + _hud->SetStateString( "player_spirit", spiritPower<0 ? "0" : va("%d", spiritPower) ); + _hud->SetStateFloat( "progress", progressBarGuiValue.GetCurrentValue(gameLocal.time) ); + + if ( healthPulse ) { + _hud->HandleNamedEvent( "healthPulse" ); + StartSound( "snd_healthpulse", SND_CHANNEL_ITEM, 0, false, NULL ); + healthPulse = false; + } + if ( spiritPulse ) { + _hud->HandleNamedEvent( "spiritPulse" ); + StartSound( "snd_spiritpulse", SND_CHANNEL_ITEM, 0, false, NULL ); + spiritPulse = false; + } + + if ( inventory.ammoPulse ) { + _hud->HandleNamedEvent( "ammoPulse" ); + inventory.ammoPulse = false; + } + if ( inventory.weaponPulse ) { + // We need to update the weapon hud manually, but not + // the armor/ammo/health because they are updated every + // frame no matter what + UpdateHudWeapon(); + _hud->HandleNamedEvent( "weaponPulse" ); + inventory.weaponPulse = false; + } + + UpdateHudAmmo( _hud ); + + // Determine slots for our pickup icons + int c = inventory.pickupItemNames.Num(); + for ( int i=0; i<10; i++ ) { + if (iSetStateString( va( "itemicon%i", i ), inventory.pickupItemNames[i].icon ); + _hud->SetStateFloat( va( "itemalpha%i", i ), inventory.pickupItemNames[i].matcolorAlpha ); + _hud->SetStateBool( va( "itemwide%i", i), inventory.pickupItemNames[i].bDoubleWide ); + } + else { + _hud->SetStateString( va( "itemicon%i", i ), "" ); + _hud->SetStateFloat( va( "itemalpha%i", i ), 0.0f ); + _hud->SetStateBool( va( "itemwide%i", i), false ); + } + } +} + +/* +=============== +hhPlayer::DrawHUD + HUMANHEAD: This run only for local players + PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +=============== +*/ +void hhPlayer::DrawHUD( idUserInterface *_hud ) { + + if (guiOverlay) { + guiOverlay->Redraw(gameLocal.realClientTime); + sysEvent_t ev; + const char *command; + ev = sys->GenerateMouseMoveEvent( -2000, -2000 ); + command = guiOverlay->HandleEvent( &ev, gameLocal.time ); +// HandleGuiCommands( this, command ); + return; + } + + // HUMANHEAD pdm: removed weapon ptr check here since ours is invalid when in a vehicle + // Also removed privateCameraView, since we want subtitles when they are on + if ( gameLocal.GetCamera() || !_hud || !g_showHud.GetBool() || pm_thirdPerson.GetBool() ) { + return; + } + + UpdateHudStats( _hud ); + + + if(weapon.IsValid()) { // HUMANHEAD + bool allowGuiUpdate = true; + //rww - update the weapon gui only if the owner is being spectated by this client, or is this client + if ( gameLocal.localClientNum != entityNumber ) { + // if updating the hud for a followed client + if ( gameLocal.localClientNum >= 0 && gameLocal.entities[ gameLocal.localClientNum ] && gameLocal.entities[ gameLocal.localClientNum ]->IsType( idPlayer::Type ) ) { + idPlayer *p = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.localClientNum ] ); + if ( !p->spectating || p->spectator != entityNumber ) { + allowGuiUpdate = false; + } + } else { + allowGuiUpdate = false; + } + } + + if (allowGuiUpdate) { + weapon->UpdateGUI(); + } + } + + _hud->SetStateInt( "s_debug", cvarSystem->GetCVarInteger( "s_showLevelMeter" ) ); + + //HUMANHEAD aob: vehicle logic + if ( InVehicle() ) { + DrawHUDVehicle( _hud ); + } + else { + _hud->Redraw( gameLocal.realClientTime ); + } + //HUMANHEAD END + + // weapon targeting crosshair + if ( !GuiActive() ) { + if ( cursor ) { + UpdateCrosshairs(); + cursor->Redraw( gameLocal.realClientTime ); + } + } +} + +/* +=============== +hhPlayer::DrawHUDVehicle + HUMANHEAD: This run only for local players +=============== +*/ +void hhPlayer::DrawHUDVehicle( idUserInterface* _hud ) { + if (GetVehicleInterfaceLocal()) { + GetVehicleInterfaceLocal()->DrawHUD( _hud ); + } +} + +/* +=============== +hhPlayer::FireWeapon +=============== +*/ +void hhPlayer::FireWeapon( void ) { + idMat3 axis; + idVec3 muzzle; + + if ( privateCameraView ) { + return; + } + + if ( g_editEntityMode.GetInteger() ) { + GetViewPos( muzzle, axis ); + if ( gameLocal.editEntities->SelectEntity( muzzle, axis[0], this ) ) { + return; + } + } + + //HUMANHEAD: aob - removed ammo check because we allow weapons to change modes + if( weapon.IsValid() ) { + AI_ATTACK_HELD = true; + weapon->BeginAttack(); + } + //HUMANEHAD END +} + +//========================================================================= +// +// hhPlayer::FireWeaponAlt +// +//========================================================================= + +void hhPlayer::FireWeaponAlt( void ) { + idMat3 axis; + idVec3 muzzle; + + if ( privateCameraView ) { + return; + } + + if ( g_editEntityMode.GetBool() ) { + GetViewPos( muzzle, axis ); + if ( gameLocal.editEntities->SelectEntity( muzzle, axis[0], NULL ) ) { + return; + } + } + + if ( gameLocal.isMultiplayer && spectating ) { + static int lastSpectatorSwitch = 0; + if ( gameLocal.time > lastSpectatorSwitch + 500 ) { + spectator = gameLocal.GetNextClientNum( spectator ); + idPlayer *player = gameLocal.GetClientByNum( spectator ); + while ( player->spectating && player != this ) { + player = gameLocal.GetClientByNum(spectator); + } + lastSpectatorSwitch = gameLocal.time; + } + return; + } + + //HUMANHEAD: aob - removed ammo check because we allow weapons to change modes + if( weapon.IsValid() ) { + AI_ATTACK_HELD = true; + weapon->BeginAltAttack(); + } + //HUMANEHAD END + + if ( hud ) { + hud->HandleNamedEvent( "closeObjective" ); + } +} + +void hhPlayer::StopFiring( void ) { + idPlayer::StopFiring(); + if ( weapon.GetEntity() && weapon->IsType( hhWeaponRifle::Type ) ) { + static_cast( weapon.GetEntity() )->ZoomOut(); + } +} + +/* +=============== +hhPlayer::HasAmmo +=============== +*/ +int hhPlayer::HasAmmo( ammo_t type, int amount ) { + return inventory.HasAmmo( type, amount ); +} + +/* +=============== +hhPlayer::UseAmmo +=============== +*/ +bool hhPlayer::UseAmmo( ammo_t type, int amount ) { + return inventory.UseAmmo( type, amount ); +} + +/* +=============== +hhPlayer::NextBestWeapon +=============== +*/ +void hhPlayer::NextBestWeapon( void ) { + if ( ActiveGui() || IsSpiritOrDeathwalking() ) { + return; + } + idPlayer::NextBestWeapon(); +} + +/* +=============== +hhPlayer::SelectWeapon +=============== +*/ +void hhPlayer::SelectWeapon( int num, bool force ) { + if ( ! ( weaponFlags & ( 1 << ( num - 1 ) ) ) ) { + return; + } + if ( bFrozen || ActiveGui() || ( SkipWeapon(num) || IsSpiritOrDeathwalking() ) ) { + return; + } + idPlayer::SelectWeapon( num, force ); +} + +/* +=============== +hhPlayer::NextWeapon +=============== +*/ +void hhPlayer::NextWeapon( void ) { + if ( ActiveGui() || IsSpiritOrDeathwalking() || bFrozen ) { + // NOTE: Spirit weapon is disallowed from being cycled to using the weapon*_cycle key + return; + } + //idPlayer::NextWeapon(); + if ( !weaponEnabled || spectating || hiddenWeapon || gameLocal.inCinematic || privateCameraView || gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || health < 0 ) { + return; + } + + if ( gameLocal.isClient ) { + return; + } + + // check if we have any weapons + if ( !inventory.weapons ) { + return; + } + + const char *weap; + int w, start; + + w = idealWeapon; + start = w; + while( 1 ) { + w++; + if ( w >= MAX_WEAPONS ) { + // No weapon selected and nothing to select + if ( start == -1 ) { + idealWeapon = 0; + currentWeapon = 0; + return; + } + w = 0; + } + // Keep us from an infinite loop if no weapons are valid + if ( w == start ) { + if ( ! ( weaponFlags & ( 1 << ( w - 1 ) ) ) ) { + idealWeapon = 0; + currentWeapon = 0; + } + return; + } + 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; + } + // Make sure the weapon is valid for this level + if ( ! ( weaponFlags & ( 1 << ( w - 1 ) ) ) ) { + continue; + } + if ( inventory.HasAmmo( weap ) || inventory.HasAltAmmo( weap ) || spawnArgs.GetBool( va( "weapon%d_allowempty", w ) ) ) { + break; + } + } + + if ( ( w != currentWeapon ) && ( w != idealWeapon ) ) { + idealWeapon = w; + weaponSwitchTime = gameLocal.time + WEAPON_SWITCH_DELAY; + UpdateHudWeapon(); + } +} + +/* +=============== +hhPlayer::PrevWeapon +=============== +*/ +void hhPlayer::PrevWeapon( void ) { + if ( ActiveGui() || IsSpiritOrDeathwalking() || bFrozen ) { + // NOTE: Spirit weapon is disallowed from being cycled to using the weapon*_cycle key + return; + } + //idPlayer::PrevWeapon(); + if ( !weaponEnabled || spectating || hiddenWeapon || gameLocal.inCinematic || privateCameraView || gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || health < 0 ) { + return; + } + + if ( gameLocal.isClient ) { + return; + } + + // check if we have any weapons + if ( !inventory.weapons ) { + return; + } + + const char *weap; + int w = idealWeapon, start = w; + if (w == -1) { + w = MAX_WEAPONS - 1; + } + while( 1 ) { + w--; + if ( w < 0 ) { + // No weapon selected and nothing to select + if ( start == -1 ) { + idealWeapon = 0; + currentWeapon = 0; + return; + } + + w = MAX_WEAPONS - 1; + } + // Keep us from an infinite loop if no weapons are valid + if ( w == start ) { + if ( ! ( weaponFlags & ( 1 << ( w - 1 ) ) ) ) { + idealWeapon = 0; + currentWeapon = 0; + } + return; + } + 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; + } + // Make sure the weapon is valid for this level + if ( ! ( weaponFlags & ( 1 << ( w - 1 ) ) ) ) { + continue; + } + if ( inventory.HasAmmo( weap ) || inventory.HasAltAmmo( weap ) || spawnArgs.GetBool( va( "weapon%d_allowempty", w ) ) ) { + break; + } + } + + if ( ( w != currentWeapon ) && ( w != idealWeapon ) ) { + idealWeapon = w; + weaponSwitchTime = gameLocal.time + WEAPON_SWITCH_DELAY; + UpdateHudWeapon(); + } +} + +/* +=============== +hhPlayer::SkipWeapon +=============== +*/ +bool hhPlayer::SkipWeapon( int weaponNum ) const { + //No bow if not in spirit mode + return !IsSpiritOrDeathwalking() && weaponNum == spawnArgs.GetInt("spirit_weapon"); +} + +/* +=============== +hhPlayer::SnapDownCurrentWeapon + +HUMANHEAD: aob +=============== +*/ +void hhPlayer::SnapDownCurrentWeapon() { + if( weapon.IsValid() ) { + weapon->SnapDown(); + } +} + +/* +=============== +hhPlayer::SnapUpCurrentWeapon + +HUMANHEAD: aob +=============== +*/ +void hhPlayer::SnapUpCurrentWeapon() { + if( weapon.IsValid() ) { + weapon->SnapUp(); + } +} + +/* +=============== +hhPlayer::SelectEtherealWeapon + +HUMANHEAD: aob +=============== +*/ +void hhPlayer::SelectEtherealWeapon() { + if ( GetCurrentWeapon() != 0 ) { + lastWeaponSpirit = GetCurrentWeapon(); + } + + weaponHandState.SetPlayer( this ); + if (!gameLocal.isClient) { + int spiritWeaponIndex = spawnArgs.GetInt("spirit_weapon"); + const char *spiritWeaponName = NULL; + if (bDeathWalk || (inventory.weapons & (1 << spiritWeaponIndex)) ) { + spiritWeaponName = GetWeaponName(spiritWeaponIndex); + } + weaponHandState.Archive( spiritWeaponName, 0, (InGUIMode()) ? "guihand_normal" : NULL ); + } + + if ( weapon.IsValid() ) { + weapon->SetShaderParm( SHADERPARM_MODE, MS2SEC( gameLocal.time ) ); // Glow in the bow + weapon->SetShaderParm( SHADERPARM_MISC, 1.0f ); // Turn on the arrow + weapon->SetShaderParm( SHADERPARM_DIVERSITY, MS2SEC( gameLocal.time ) ); // Glow in the arrow + } +} + +/* +=============== +hhPlayer::PutawayEtherealWeapon + +HUMANHEAD: aob +=============== +*/ +void hhPlayer::PutawayEtherealWeapon() { + //If we haven't actually changed weapons yet, put current weapon up + if( GetCurrentWeapon() != lastWeaponSpirit ) { + SnapDownCurrentWeapon(); + } else { + SnapUpCurrentWeapon(); + } + + weaponHandState.RestoreFromArchive(); +} + +/* +=============== +hhPlayer::UpdateWeapon + PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +=============== +*/ +void hhPlayer::UpdateWeapon( void ) { + + if ( IsDead() ) { // HUMANHEAD cjr: Replaced health <= 0 with IsDead() call for deathwalk override + return; + } + + assert( !spectating ); + + if ( gameLocal.isClient ) { + // clients need to wait till the weapon and it's world model entity + // are present and synchronized ( weapon.worldModel idEntityPtr to idAnimatedEntity ) + if ( !weapon.GetEntity()->IsWorldModelReady() ) { + return; + } + } + else if (gameLocal.isMultiplayer) { //rww - projectile deferring + if (weapon.IsValid()) { + weapon->CheckDeferredProjectiles(); + } + } + + // always make sure the weapon is correctly setup before accessing it + if ( weapon.GetEntity() && !weapon.GetEntity()->IsLinked() ) { + if ( idealWeapon != -1 ) { + animPrefix = spawnArgs.GetString( va( "def_weapon%d", idealWeapon ) ); + weapon.GetEntity()->GetWeaponDef( animPrefix, inventory.clip[ idealWeapon ] ); + animPrefix.Strip( "weaponobj_" ); //HUMANHEAD rww + assert( weapon.GetEntity()->IsLinked() ); + } else { + return; + } + } + + //HUMANEHAD rww + if (weapon.IsValid() && weapon->IsType(hhWeaponSoulStripper::Type)) { + hhWeaponSoulStripper *leechGun = static_cast(weapon.GetEntity()); + leechGun->CheckCans(); + } + //HUMANHEAD END + + if (!InVehicle()) { + if ( g_dragEntity.GetBool() ) { + StopFiring(); + dragEntity.Update( this ); + if ( weapon.IsValid() ) { + weapon.GetEntity()->FreeModelDef(); + } + return; + } else if (ActiveGui()) { + // gui handling overrides weapon use + Weapon_GUI(); + } else { + Weapon_Combat(); + } + + // Determine whether we are in aside state + if ( weapon.IsValid() && weapon->IsAside() ) { + if ( !AI_ASIDE ) { + AI_ASIDE = true; + SetState( "AsideWeapon" ); + UpdateScript(); + } + } else { + AI_ASIDE = false; + } + } + + //HUMANHEAD: aob - added weapon validity check + if( weapon.IsValid() ) { + // update weapon state, particles, dlights, etc + weapon->PresentWeapon( showWeaponViewModel ); + } + + // nla + if ( hand.IsValid() ) { + hand->Present(); + } +} + +/* +=============== +hhPlayer::InGUIMode +nla - Used for the GUI hand to check if it should be up. (Coming back from spiritwalk) +=============== +*/ +bool hhPlayer::InGUIMode() { + + // Logic adapted from UpdateWeapon + if ( ActiveGui() && !InVehicle() ) { + return( true ); + } + + return( false ); +} + + +/* +=============== +hhPlayer::Weapon_Combat + PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +=============== +*/ +void hhPlayer::Weapon_Combat( void ) { + idMat3 axis; + idVec3 muzzle; + idDict args; + + if ( !weaponEnabled || gameLocal.inCinematic || privateCameraView ) { + return; + } + + // HUMANHEAD nla + if ( hand.IsValid() && hand->IsType( hhGuiHand::Type ) && hand->IsAttached() && !hand->IsLowering() ) { + hand->RemoveHand(); + } + // HUMANHEAD END + + if( idealWeapon != 0 && IsLocked(idealWeapon) ) { //HUMANHEAD bjk PCF (4-30-06) - fix wrench up in roadhouse + NextWeapon(); + } + + if ( idealWeapon == 0 ) { + return; + } + + RaiseWeapon(); + if ( weapon.IsValid() ) { + weapon.GetEntity()->PutUpright(); + } + + if ( weapon.IsValid() && weapon->IsReloading() ) { + if ( !AI_RELOAD ) { + AI_RELOAD = true; + SetState( "ReloadWeapon" ); + UpdateScript(); + } + } else { + AI_RELOAD = false; + } + + if ( idealWeapon != currentWeapon && (!gameLocal.isMultiplayer || inventory.lastShot[idealWeapon] < gameLocal.time) ) { //HUMANHEAD bjk PATCH 7-27-06 + if ( weaponCatchup ) { + assert( gameLocal.isClient ); +#ifndef HUMANHEAD //HUMANHEAD rww FIXME!!!! this also produces horrible memory leaks because of the dirty fire controller stuff + //HUMANHEAD rww - our crazy weapon system does not work ok with this. did we change the weapon dictionary parsing? + //it seems that it is very much dependant on the base class of weapon at the moment and that of course is going to be + //that of currentWeapon and not idealWeapon. + //currentWeapon = idealWeapon; + //HUMANHEAD END + weaponGone = false; + animPrefix = spawnArgs.GetString( va( "def_weapon%d", currentWeapon ) ); + weapon.GetEntity()->GetWeaponDef( animPrefix, inventory.clip[ currentWeapon ] ); + animPrefix.Strip( "weaponobj_" ); //HUMANHEAD pdm: changed from weapon_ to weaponobj_ per our naming convention + + weapon.GetEntity()->NetCatchup(); + const function_t *newstate = GetScriptFunction( "NetCatchup" ); + if ( newstate ) { + SetState( newstate ); + UpdateScript(); + } +#else + assert( idealWeapon >= 0 ); + assert( idealWeapon < MAX_WEAPONS ); + + animPrefix = spawnArgs.GetString( va( "def_weapon%d", idealWeapon ) ); + const char *currentWeaponName = weapon->spawnArgs.GetString("classname", ""); + //make sure the weapon i have from my snapshot, and the weapon i have selected are the same. + if (currentWeaponName && !idStr::Cmp(animPrefix, currentWeaponName)) { + weaponGone = false; + currentWeapon = idealWeapon; + + weapon->GetWeaponDef( animPrefix, inventory.clip[ currentWeapon ] ); + animPrefix.Strip( "weaponobj_" ); + weapon->Raise(); +#endif //HUMANHEAD END + weaponCatchup = false; + } + } else { + if ( weapon.IsValid() && (weapon->IsReady() || weapon->IsRising()) ) { + InvalidateCurrentWeapon();//Needed incase we change weapons quickly, we can go back to old weapon + weapon->PutAway(); + } + + if ( ( !weapon.IsValid() || weapon->IsHolstered() ) && !bDeathWalk && ! bReallyDead ) { + assert( idealWeapon >= 0 ); + assert( idealWeapon < MAX_WEAPONS ); + + if ( currentWeapon > 0 && !spawnArgs.GetBool( va( "weapon%d_toggle", currentWeapon ) ) ) { //HUMANHEAD bjk + previousWeapon = currentWeapon; + } + //HUMANHEAD PCF rww 05/03/06 - the local client might get skippiness between switching weapons if we + //attempt to raise the weapon again before validating that the weapon ent is of the type that we + //already desire to switch to. this bug is introduced by the concept of switching out entities when + //changing weapons (not client-friendly). + if (gameLocal.isClient && entityNumber == gameLocal.localClientNum) { + if (weapon.IsValid() && weapon->GetDict() && idStr::Icmp(weapon->GetDict()->GetString("classname"), GetWeaponName( idealWeapon )) == 0) { + currentWeapon = idealWeapon; + weaponGone = false; + animPrefix = GetWeaponName( currentWeapon ); + animPrefix.Strip( "weaponobj_" ); //HUMANHEAD pdm: changed from weapon_ to weaponobj_ per our naming convention + weapon.GetEntity()->Raise(); + } + } + //HUMANHEAD END + else { + currentWeapon = idealWeapon; + weaponGone = false; + + //HUMANHEAD: aob + animPrefix = GetWeaponName( currentWeapon ); + if (!gameLocal.isClient) { + SAFE_REMOVE( weapon ); + weapon = SpawnWeapon( animPrefix.c_str() ); + } + //HUMANHEAD END + + animPrefix.Strip( "weaponobj_" ); //HUMANHEAD pdm: changed from weapon_ to weaponobj_ per our naming convention + + //HUMANHEAD PCF rww 05/03/06 - safety check, make sure weapon is valid particularly for the client + if (weapon.IsValid()) { + weapon.GetEntity()->Raise(); + } + } + } + } + } else if (!bDeathWalk && !bReallyDead) { + weaponGone = false; // if you drop and re-get weap, you may miss the = false above + if ( weapon.IsValid() && weapon.GetEntity()->IsHolstered() ) { + if ( !weapon.GetEntity()->AmmoAvailable() ) { + // weapons can switch automatically if they have no more ammo + NextBestWeapon(); + } else if( !gameLocal.isMultiplayer || inventory.lastShot[idealWeapon] < gameLocal.time ) { //HUMANHEAD bjk PATCH 9-11-06 + weapon.GetEntity()->Raise(); + state = GetScriptFunction( "RaiseWeapon" ); + if ( state ) { + SetState( state ); + } + } + } + } + + if ( weapon.IsValid() ) { + weapon->PrecomputeTraceInfo(); + } + + // check for attack + AI_WEAPON_FIRED = false; + if ( ( usercmd.buttons & BUTTON_ATTACK ) && !weaponGone ) { + FireWeapon(); + } else if ( oldButtons & BUTTON_ATTACK ) { + AI_ATTACK_HELD = false; + if( weapon.IsValid() ) { + weapon.GetEntity()->EndAttack(); + } + } + + // HUMANHEAD + if ( ( usercmd.buttons & BUTTON_ATTACK_ALT ) && !weaponGone ) { + FireWeaponAlt(); + } else if ( oldButtons & BUTTON_ATTACK_ALT ) { + AI_ATTACK_HELD = false; + if( weapon.IsValid() ) { + weapon.GetEntity()->EndAltAttack(); + } + } + // HUMANHEAD END + + // update our ammo clip in our inventory + if ( weapon.IsValid() && ( currentWeapon >= 0 ) && ( currentWeapon < MAX_WEAPONS ) ) { + inventory.clip[ currentWeapon ] = weapon->AmmoInClip(); + inventory.altMode[ currentWeapon ] = weapon->GetAltMode(); + } +} + +/* +=============== +hhPlayer::SpawnWeapon +=============== +*/ +hhWeapon* hhPlayer::SpawnWeapon( const char* name ) { + hhWeapon* weaponPtr = NULL; + idDict args; + + args.SetVector( "origin", GetEyePosition() ); + args.SetMatrix( "rotation", viewAngles.ToMat3() ); + weaponPtr = static_cast( gameLocal.SpawnObject(name, &args) ); + weaponPtr->SetOwner( this ); + weaponPtr->Bind( this, true ); + + //HUMANHEAD: aob - removed last two params + weaponPtr->GetWeaponDef( name ); + + return weaponPtr; +} + +/* +===================== +hhPlayer::GetWeaponNum + HUMANHEAD +===================== +*/ +int hhPlayer::GetWeaponNum( const char* weaponName ) const { + for( int i = 1; i < MAX_WEAPONS; ++i ) { + if( !idStr::Icmp(GetWeaponName(i), weaponName) ) { + return i; + } + } + + return 0; +} + +/* +===================== +hhPlayer::GetWeaponName + HUMANHEAD +===================== +*/ +const char* hhPlayer::GetWeaponName( int num ) const { + if( num < 1 || num >= MAX_WEAPONS ) { + return NULL; + } + + return spawnArgs.GetString( va("def_weapon%d", num) ); +} + +/* +=============== +hhPlayer::Weapon_GUI + PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +=============== +*/ +void hhPlayer::Weapon_GUI( void ) { + + StopFiring(); + weapon.GetEntity()->LowerWeapon(); + + // NLANOTE - Same here + // disable click prediction for the GUIs. handy to check the state sync does the right thing + if ( gameLocal.isClient && !net_clientPredictGUI.GetBool() ) { + return; + } + + if ( health <= 0 || bDeathWalk ) { //HUMANHEAD bjk PCF (5-5-06) - no hand stuff when dead + return; + } + + // HUMANHEAD nla + if ( !bDeathWalk ) { // mdl - Don't do hand stuff while deathwalking + if ( hand.IsValid() && hand->IsType( hhGuiHand::Type ) ) { + //if ( !hand->IsReady() && !hand->IsRaising() ) { + if ( !hand->IsReady() && !hand->IsRaising() && !hand->IsLowering() ) { + hand->Reraise(); + } + } + else if ( (!hand.IsValid() && weapon.IsValid() && !weapon->IsAside() ) || + (hand.IsValid() && !hand->IsType( hhGuiHand::Type ) && !hand->IsLowering() ) || + (!hand.IsValid() && idealWeapon == 0 ) ) { + if (!gameLocal.isClient) { //rww + hhHand::AddHand( this, GetGuiHandInfo() ); + } + } + } + // HUMANHEAD END + + if ( hand.IsValid() && ( oldButtons ^ usercmd.buttons ) & BUTTON_ATTACK ) { //HUMANHEAD bjk: no waiting for ready + 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 && focusGUIent && ui == focusUI ) { + focusGUIent->UpdateVisuals(); + } + } + if ( gameLocal.isClient ) { + // we predict enough, but don't want to execute commands + if (focusGUIent) { + if ( hand.IsValid() && hand->IsType( hhGuiHand::Type ) && ev.evValue2) { //rww - still predict hand + hand->SetAction( focusGUIent->spawnArgs.GetString( "pressAnim", "press" ) ); //HUMANHEAD bjk + hand->Action(); + } + } + return; + } + if ( focusGUIent ) { + HandleGuiCommands( focusGUIent, command ); + // HUMANHEAD nla - Added to handle the hand stuff. = ) + if ( hand.IsValid() && hand->IsType( hhGuiHand::Type ) && ev.evValue2) { + hand->SetAction( focusGUIent->spawnArgs.GetString( "pressAnim", "press" ) ); //HUMANHEAD bjk + hand->Action(); + } + // HUMANHEAD END + } else { + HandleGuiCommands( this, command ); + } + } +} + +/* +================ +hhPlayer::UpdateCrosshairs +HUMANHEAD PDM +================ +*/ +void hhPlayer::UpdateCrosshairs() { + bool combatCrosshair = false; + bool targeting = false; + int crosshair = 0; + + if ( !privateCameraView && !IsLocked(idealWeapon) && (!hand.IsValid() || hand->IsLowered()) && !InCinematic() && weapon.IsValid() && g_crosshair.GetInteger() ) { + weapon->UpdateCrosshairs(combatCrosshair, targeting); + crosshair = g_crosshair.GetInteger(); + } + + cursor->SetStateBool( "combatcursor", targeting ? false : combatCrosshair ); + cursor->SetStateBool( "activecombatcursor", targeting ); + cursor->SetStateInt( "crosshair", crosshair ); +} + + +/* +================ +hhPlayer::UpdateFocus + +Searches nearby entities for interactive guis, possibly making one of them +the focus and sending it a mouse move event + PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +================ +*/ +void hhPlayer::UpdateFocus( void ) { + idClipModel *clipModelList[ MAX_GENTITIES ]; + idClipModel *clip; + int listedClipModels; + idEntity *oldFocus; + idEntity *ent; + idUserInterface *oldUI; + int i, j; + idVec3 start, end; + const char *command; + trace_t trace; + guiPoint_t pt; + const idKeyValue *kv; + sysEvent_t ev; + idUserInterface *ui; + + if ( gameLocal.inCinematic ) { + return; + } + + oldFocus = focusGUIent; + oldUI = focusUI; + + if ( focusTime <= gameLocal.time ) { + ClearFocus(); + } + + // don't let spectators interact with GUIs + if ( spectating ) { + return; + } + + start = GetEyePosition(); + + //HUMANHEAD rww - the actual viewAngles do not seem to be properly transformed to at this point, + //so just get a transformed direction now. + idVec3 viewDir = (untransformedViewAngles.ToMat3()*GetEyeAxis())[0]; + + end = start + viewDir * 70.0f; // was 50, doom=80 + + // HUMANHEAD pdm: Changed aim logic a bit + // player identification -> names to the hud + if ( gameLocal.isMultiplayer && entityNumber == gameLocal.localClientNum ) { + idVec3 end = start + viewDir * 768.0f; + gameLocal.clip.TracePoint( trace, start, end, MASK_SHOT_BOUNDINGBOX, this ); + if ( ( trace.fraction < 1.0f ) && ( trace.c.entityNum < MAX_CLIENTS ) ) { + MPAim = trace.c.entityNum; + } + else { + MPAim = -1; + + //HUMANHEAD rww - if we're hitting a shuttle with a pilot, set the pilot to the MPAim ent + if (trace.c.entityNum >= MAX_CLIENTS && trace.c.entityNum < MAX_GENTITIES) { + idEntity *traceEnt = gameLocal.entities[trace.c.entityNum]; + if (traceEnt && traceEnt->IsType(hhVehicle::Type)) { //is it a vehicle? + hhVehicle *traceVeh = static_cast(traceEnt); + if (traceVeh->GetPilot() && traceVeh->GetPilot()->IsType(hhPlayer::Type)) { //if it's a vehicle, does it have a player pilot? + MPAim = traceVeh->GetPilot()->entityNumber; + } + } + } + //HUMANHEAD END + } + } + + idBounds bounds( start ); + bounds.AddPoint( end ); + + listedClipModels = gameLocal.clip.ClipModelsTouchingBounds( bounds, -1, clipModelList, MAX_GENTITIES ); + + // no pretense at sorting here, just assume that there will only be one active + // gui within range along the trace + for ( i = 0; i < listedClipModels; i++ ) { + clip = clipModelList[ i ]; + ent = clip->GetEntity(); + + if ( ent->IsHidden() ) { + continue; + } + + // HUMANHEAD pdm: added support here for all guis, not just gui1 + int interactiveMask = 0; + renderEntity_t *renderEnt = ent->GetRenderEntity(); + if ( renderEnt ) { + for (int ix=0; ixgui[ix] && renderEnt->gui[ix]->IsInteractive()) { + interactiveMask |= (1<GuiTrace( ent->GetModelDefHandle(), start, end, interactiveMask ); + + if ( ent->fl.accurateGuiTrace ) { + trace_t tr; + gameLocal.clip.TracePoint(tr, start, end, CONTENTS_SOLID, this); + if (tr.fraction < pt.frac) { + continue; + } + } + + if ( pt.x != -1 ) { + // we have a hit + renderEntity_t *focusGUIrenderEntity = ent->GetRenderEntity(); + if ( !focusGUIrenderEntity ) { + continue; + } + + if ( pt.guiId == 1 ) { + ui = focusGUIrenderEntity->gui[ 0 ]; + } else if ( pt.guiId == 2 ) { + ui = focusGUIrenderEntity->gui[ 1 ]; + } else { + ui = focusGUIrenderEntity->gui[ 2 ]; + } + + if ( ui == NULL ) { + continue; + } + + ClearFocus(); + focusGUIent = ent; + focusUI = ui; + + if ( oldFocus != ent ) { + // new activation + // going to see if we have anything in inventory a gui might be interested in + // need to enumerate inventory items + focusUI->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" ); + const char *iicon = item->GetString( "inv_icon" ); + const char *itext = item->GetString( "inv_text" ); + + focusUI->SetStateString( va( "inv_name_%i", j), iname ); + focusUI->SetStateString( va( "inv_icon_%i", j), iicon ); + focusUI->SetStateString( va( "inv_text_%i", j), itext ); + kv = item->MatchPrefix("inv_id", NULL); + if ( kv ) { + focusUI->SetStateString( va( "inv_id_%i", j ), kv->GetValue() ); + } + // HUMANHEAD nla - Changed to pass all "passtogui_" keys to the gui + kv = item->MatchPrefix("passtogui_", NULL); + if ( kv ) { + focusUI->SetStateString( kv->GetKey(), kv->GetValue() ); + kv = item->MatchPrefix( "passtogui_", kv ); + } + // HUMANHEAD END + focusUI->SetStateInt( iname, 1 ); + } + + focusUI->SetStateString( "player_health", va("%i", health ) ); + focusUI->SetStateBool( "player_spiritwalking", IsSpiritWalking() ); // for hunterhand gui + + kv = focusGUIent->spawnArgs.MatchPrefix( "gui_parm", NULL ); + while ( kv ) { + focusUI->SetStateString( kv->GetKey(), kv->GetValue() ); + kv = focusGUIent->spawnArgs.MatchPrefix( "gui_parm", kv ); + } + } + + // clamp the mouse to the corner + ev = sys->GenerateMouseMoveEvent( -2000, -2000 ); + command = focusUI->HandleEvent( &ev, gameLocal.time ); + HandleGuiCommands( focusGUIent, command ); + + // move to an absolute position + ev = sys->GenerateMouseMoveEvent( pt.x * SCREEN_WIDTH, pt.y * SCREEN_HEIGHT ); + command = focusUI->HandleEvent( &ev, gameLocal.time ); + HandleGuiCommands( focusGUIent, command ); + focusTime = gameLocal.time + FOCUS_GUI_TIME; + break; + } + } + + if ( focusGUIent && focusUI ) { + if ( !oldFocus || oldFocus != focusGUIent ) { + command = focusUI->Activate( true, gameLocal.time ); + HandleGuiCommands( focusGUIent, command ); + //StartSound( "snd_guienter", SND_CHANNEL_ANY, 0, false, NULL ); + } + } else if ( oldFocus && oldUI ) { + command = oldUI->Activate( false, gameLocal.time ); + HandleGuiCommands( oldFocus, command ); + //StartSound( "snd_guiexit", SND_CHANNEL_ANY, 0, false, NULL ); + } +} + + +/* +=============== +hhPlayer::ApplyLandDeflect + +HUMANHEAD: aob +=============== +*/ +idVec3 hhPlayer::ApplyLandDeflect( const idVec3& pos, float scale ) { + idVec3 localPos( pos ); + int delta = 0; + float fraction = 0.0f; + + // add fall height + delta = gameLocal.time - landTime; + if ( delta < LAND_DEFLECT_TIME ) { + fraction = (float)delta / LAND_DEFLECT_TIME; + //HUMANHEAD: aob + fraction *= scale; + //HUMANHEAD END + localPos += cameraInterpolator.GetCurrentUpVector() * landChange * fraction; + } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) { + fraction = (float)(LAND_DEFLECT_TIME + LAND_RETURN_TIME - delta) / LAND_RETURN_TIME; + //HUMANHEAD: aob + fraction *= scale; + //HUMANHEAD END + localPos += cameraInterpolator.GetCurrentUpVector() * landChange * fraction; + } + + return localPos; +} + +/* +================= +hhPlayer::CrashLand + +Check for hard landings that generate sound events + PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +================= +*/ +void hhPlayer::CrashLand( const idVec3 &oldOrigin, const idVec3 &oldVelocity ) { + //HUMANHEAD: aob - removed unused vars + idVec3 origin; + idVec3 gravityNormal; + float delta; + waterLevel_t waterLevel; + bool noDamage; + //HUMANHEAD END + + //HUMANHEAD: aob + const trace_t& trace = physicsObj.GetGroundTrace(); + //HUMANHEAD END + + AI_SOFTLANDING = false; + AI_HARDLANDING = false; + + //HUMANHEAD: aob - added IsBound and trace check + // if the player is not on the ground + if ( (!physicsObj.HasGroundContacts() || trace.fraction == 1.0f) && !IsBound() ) { + return; + } + + //HUMANHEAD: aob - only check when we land on the ground + //If we get here we can assume we currently have ground contacts + if( physicsObj.HadGroundContacts() ) { + return; + } + //HUMANHEAD END + + 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; + } + } + + //HUMANHEAD: aob - removed velocity calculations + + //HUMANHEAD: aob + idVec3 deltaVelocity = DetermineDeltaCollisionVelocity( oldVelocity, trace ); + delta = (IsBound()) ? deltaVelocity.Length() : deltaVelocity * gravityNormal; + //HUMANHEAD END + + // reduce falling damage if there is standing water + if ( waterLevel == WATERLEVEL_WAIST ) { + delta *= 0.25f; + } + if ( waterLevel == WATERLEVEL_FEET ) { + delta *= 0.5f; + } + + if ( delta < crashlandSpeed_jump || IsSpiritOrDeathwalking() ) { + return; // Early out + } + + // Calculate landing sound volume + float soundScale = hhUtils::CalculateScale( delta, crashlandSpeed_jump, crashlandSpeed_fatal ); + landChange = -32 * soundScale; + landTime = gameLocal.time; + float min = hhMath::dB2Scale( spawnArgs.GetInt("minCrashLandVolumedB", "-20") ); + float max = hhMath::dB2Scale( spawnArgs.GetInt("maxCrashLandVolumedB", "-4") ); + soundScale = min + (max - min) * soundScale; + + idVec3 fallDir = oldVelocity; + idVec3 reverseContactNormal = -physicsObj.GetGroundContactNormal(); + idEntity* entity = NULL; + float damageScale = hhUtils::CalculateScale( delta, crashlandSpeed_soft, crashlandSpeed_fatal ); + + fallDir.Normalize(); + + if( trace.fraction == 1.0f ) { + return; + } + + if( !IsBound() ) { + PlayCrashLandSound( trace, soundScale ); + gameLocal.AlertAI( this, spawnArgs.GetFloat( "land_alert_radius", "400" ) ); + } + + // Determine damage to what you're landing on + entity = gameLocal.GetTraceEntity( trace ); + if( entity && trace.c.entityNum != ENTITYNUM_WORLD ) { + entity->ApplyImpulse( this, 0, trace.c.point, (oldVelocity * reverseContactNormal) * reverseContactNormal );//Not sure if this impulse is large enough + + const char* entityDamageName = spawnArgs.GetString( "def_damageFellOnto" ); + if( *entityDamageName && damageScale > 0.0f) { + entity->Damage( this, this, fallDir, entityDamageName, damageScale, INVALID_JOINT ); + } + } + + // Calculate damage to self + const char* selfDamageName = NULL; + if ( delta < crashlandSpeed_soft ) { // Soft Fall + AI_SOFTLANDING = true; + selfDamageName = spawnArgs.GetString( "def_damageSoftFall" ); + } + else if ( delta < crashlandSpeed_fatal ) { // Hard Fall + AI_HARDLANDING = true; + selfDamageName = spawnArgs.GetString( "def_damageHardFall" ); + } + else { // Fatal Fall + AI_HARDLANDING = true; + selfDamageName = spawnArgs.GetString( "def_damageFatalFall" ); + } + + if( *selfDamageName && damageScale > 0.0f && !noDamage ) { + pain_debounce_time = gameLocal.time + pain_delay + 1; // ignore pain since we'll play our landing anim + Damage( NULL, NULL, fallDir, selfDamageName, damageScale, INVALID_JOINT ); + } +} + +/* +=============== +hhPlayer::ApplyBobCycle + +HUMANHEAD: aob +=============== +*/ +#define BOB_TO_NOBOB_RETURN_TIME 3000.0f +idVec3 hhPlayer::ApplyBobCycle( const idVec3& pos, const idVec3& velocity ) { + float delta = 0.0f; + idVec3 localViewBob( pos ); + const float maxBob = 6.0f; + + if( bob > 0.0f && !usercmd.forwardmove && !usercmd.rightmove ) {//smoothly goto bob of zero if not already there + delta = gameLocal.time - lastAppliedBobCycle; + bob *= ( 1.0f - (delta / BOB_TO_NOBOB_RETURN_TIME) ); + if( bob < 0.0f ) { + bob = 0.0f; + } + } else { + // add bob height after any movement smoothing + lastAppliedBobCycle = gameLocal.time; + bob = bobfracsin * xyspeed * pm_bobup.GetFloat(); + if( bob > maxBob ) { + bob = maxBob; + } + } + + localViewBob -= cameraInterpolator.GetCurrentUpVector() * bob; + + return localViewBob; +} + + +/* +=============== +hhPlayer::BobCycle + PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +=============== +*/ +void hhPlayer::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; + + //HUMANHEAD: aob - changed GetGravityNormal to GetCurrentUpVector to smooth out wallwalk + gravityDir = -cameraInterpolator.GetCurrentUpVector(); + //HUMANHEAD END + vel = velocity - ( velocity * gravityDir ) * gravityDir; + xyspeed = vel.LengthFast(); + + // do not evaluate the bob for other clients + // when doing a spectate follow, don't do any weapon bobbing + if ( gameLocal.isClient && entityNumber != gameLocal.localClientNum ) { + //HUMANHEAD rww - allow bob when following players in spectator mode + bool canBob = false; + if (gameLocal.localClientNum != -1 && gameLocal.entities[gameLocal.localClientNum] && gameLocal.entities[gameLocal.localClientNum]->IsType(hhPlayer::Type)) { + hhPlayer *pl = static_cast(gameLocal.entities[gameLocal.localClientNum]); + if (pl->spectating && pl->spectator == entityNumber) { + canBob = true; + } + } + + if (!canBob) { + //HUMANHEAD END + viewBobAngles.Zero(); + viewBob.Zero(); + return; + } + } + + 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.msec ) & 255; + bobFoot = ( bobCycle & 128 ) >> 7; + bobfracsin = idMath::Fabs( sin( ( bobCycle & 127 ) / 127.0 * idMath::PI ) ); + } + + // calculate angles for view bobbing + viewBobAngles.Zero(); + + viewaxis = viewAngles.ToMat3(); + + // 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; + + //HUMANHEAD: aob +#if HUMANHEAD + viewBob = ApplyBobCycle( ApplyLandDeflect(vec3_zero, 1.0f), velocity ); +#else + // 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; + } + viewBob[2] += bob; + + // 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 ); + } +#endif //HUMANHEAD END +} + +/* +================ +hhPlayer::UpdateDeltaViewAngles +================ +*/ +void hhPlayer::UpdateDeltaViewAngles( const idAngles &angles ) { + // set the delta angle + idAngles delta; + for( int i = 0; i < 3; i++ ) { +#if 1 //rww - revert to id's method + //delta[i] = ((angles[i] - GetUntransformedViewAngles()[i]) / GetViewAnglesSensitivity()) + GetUntransformedViewAngles()[i] - SHORT2ANGLE(usercmd.angles[i]); + delta[ i ] = angles[ i ] - SHORT2ANGLE( usercmd.angles[ i ] ); +#else // HUMANHEAD mdl: If you enable this, comment out the SetOrientation() call for vehicles in hhPlayer::Restore() + delta[i] = (angles[i] - idMath::AngleNormalize180( SHORT2ANGLE(usercmd.angles[ i ]) - oldCmdAngles[i]) * GetViewAnglesSensitivity()); +#endif + } + SetDeltaViewAngles( delta ); +} + +/* +================ +hhPlayer::DetermineViewAngles +================ +*/ +idAngles hhPlayer::DetermineViewAngles( const usercmd_t& cmd, idAngles& cmdAngles ) { + int i; + idAngles localViewAngles( GetUntransformedViewAngles() ); + idAngles deltaCmdAngles; + + 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 + return GetUntransformedViewAngles(); + } + + //HUMANHEAD rww - testing + /* + if (gameLoacl.isClient && gameLocal.localClientNum != entityNumber) { + idQuat a = viewAngles.ToQuat(); + idQuat b = GetUntransformedViewAngles().ToQuat(); + idQuat c; + c.Slerp(a, b, 0.3f); + return c.ToAngles(); + } + */ + //HUMANHEAD END + + // if dead + if ( IsDead() ) { +#if 0 + if ( DoThirdPersonDeath() ) { + localViewAngles.roll = 0.0f; + localViewAngles.pitch = 30.0f; + } + else { + localViewAngles.roll = 40.0f; + localViewAngles.pitch = -15.0f; + } + return localViewAngles; +#else //HUMANHEAD PCF rww 04/26/06 - look freely while dead in MP + if ( !DoThirdPersonDeath() ) { + localViewAngles.roll = 40.0f; + localViewAngles.pitch = -15.0f; + return localViewAngles; + } +#endif //HUMANHEAD END + } + + //JSHTODO this messes up multiplayer input. remerge sensitivity code + // circularly clamp the angles with deltas + if ( usercmd.buttons & BUTTON_MLOOK ) { + for ( i = 0; i < 3; i++ ) { + cmdAngles[i] = SHORT2ANGLE( usercmd.angles[i] ); +#if 1 //rww - revert to id's method + //localViewAngles[i] = idMath::AngleNormalize180( cmdAngles[i] + deltaViewAngles[i] - GetUntransformedViewAngles()[i] ) * GetViewAnglesSensitivity() + GetUntransformedViewAngles()[i]; + localViewAngles[i] = idMath::AngleNormalize180( SHORT2ANGLE( usercmd.angles[i]) + deltaViewAngles[i] ); +#else + deltaCmdAngles[i] = idMath::AngleNormalize180( cmdAngles[i] - oldCmdAngles[i] ) * GetViewAnglesSensitivity(); + oldCmdAngles[i] = cmdAngles[i]; + localViewAngles[i] = deltaCmdAngles[i] + deltaViewAngles[i]; +#endif + } + } else { +#if 1 //rww - revert to id's method + //localViewAngles.yaw = idMath::AngleNormalize180( SHORT2ANGLE( usercmd.angles[YAW] ) + deltaViewAngles[YAW] - GetUntransformedViewAngles()[YAW] ) * GetViewAnglesSensitivity() + GetUntransformedViewAngles()[YAW]; + localViewAngles.yaw = idMath::AngleNormalize180( SHORT2ANGLE( usercmd.angles[YAW]) + deltaViewAngles[YAW] ); +#if GAMEPAD_SUPPORT // VENOM BEGIN + oldCmdAngles[YAW] = SHORT2ANGLE( usercmd.angles[YAW] ); +#endif // VENOM END +#else + deltaCmdAngles[YAW] = idMath::AngleNormalize180( SHORT2ANGLE(usercmd.angles[YAW]) - oldCmdAngles[YAW] ) * GetViewAnglesSensitivity(); + oldCmdAngles[YAW] = SHORT2ANGLE( usercmd.angles[YAW] ); + localViewAngles.yaw = deltaCmdAngles[YAW] + deltaViewAngles[YAW]; +#endif + + if (!centerView.IsDone(gameLocal.time)) { + localViewAngles.pitch = centerView.GetCurrentValue(gameLocal.time); + } + } + +#if GAMEPAD_SUPPORT // VENOM BEGIN + float fpitch = idMath::Fabs(localViewAngles.pitch); + if( idMath::Abs(usercmd.rightmove) < 10 && + idMath::Abs(usercmd.forwardmove) > 80 && + fpitch > 1.f && + idMath::Fabs(oldCmdAngles[YAW] - cmdAngles[YAW]) < 1.0f && + idMath::Fabs(oldCmdAngles[PITCH] - cmdAngles[PITCH]) < 1.0f + ) + { + // dont start the leveling until we have .5 sec of valid movement + if((gameLocal.time - lastAutoLevelTime) > 500 ) { + float fadd = 0.6f; + if(fpitch < 10.f) { + fadd *= (fpitch/10.f ); + fadd+= 0.05f; + } + + if(localViewAngles.pitch < 0 ) { + localViewAngles.pitch +=fadd; + } + else { + localViewAngles.pitch -=fadd; + } + } + } + else { + lastAutoLevelTime = gameLocal.time; + } + + oldCmdAngles[YAW] = cmdAngles[YAW]; + oldCmdAngles[PITCH] = cmdAngles[PITCH]; +#endif // VENOM END + + // clamp the pitch + if ( noclip ) { + localViewAngles.pitch = hhMath::ClampFloat( -89.0f, 89.0f, localViewAngles.pitch ); + } else { + //HUMANHEAD PCF rww 04/26/06 - look freely while dead in MP + if (IsDead() && DoThirdPersonDeath()) { + localViewAngles.pitch = hhMath::ClampFloat( -45.0f, 45.0f, localViewAngles.pitch ); + } + else { + //HUMANHEAD END + localViewAngles.pitch = hhMath::ClampFloat( pm_minviewpitch.GetFloat(), pm_maxviewpitch.GetFloat(), localViewAngles.pitch ); + } + } + + // HUMANHEAD pdm: clamp the yaw (only used for bindControllers) + if ( !noclip && bClampYaw ) { + float idealYaw=0.0f; + idVec3 masterOrigin; + idMat3 masterAxis; + + if (GetMasterPosition(masterOrigin, masterAxis)) { + idealYaw = masterAxis[0].ToYaw(); + idealYaw = idMath::AngleNormalize180( idealYaw ); + } + + // Transpose the everything to "zero" space before clamping, so idealYaw is at zero in a (-180..180) normalized space + float zeroSpaceYaw = idMath::AngleNormalize180( localViewAngles.yaw - idealYaw ); + zeroSpaceYaw = idMath::ClampFloat(-maxRelativeYaw, maxRelativeYaw, zeroSpaceYaw); + localViewAngles.yaw = idMath::AngleNormalize180( zeroSpaceYaw + idealYaw ); + } + // HUMANHEAD END + + UpdateDeltaViewAngles( localViewAngles ); + + // save in the log for analyzing weapon angle offsets + loggedViewAngles[ gameLocal.framenum & (NUM_LOGGED_VIEW_ANGLES-1) ] = localViewAngles; + + return localViewAngles; +} + +/* +================ +hhPlayer::SetViewAnglesSensitivity +================ +*/ +void hhPlayer::SetViewAnglesSensitivity( float factor ) { + viewAnglesSensitivity = factor; + if (gameLocal.localClientNum == entityNumber) { + common->SetGameSensitivityFactor(factor); + } +} + +/* +================ +hhPlayer::GetViewAnglesSensitivity +================ +*/ +float hhPlayer::GetViewAnglesSensitivity() const { + return viewAnglesSensitivity; +} + +/* +================ +hhPlayer::UpdateViewAngles +================ +*/ +void hhPlayer::UpdateViewAngles( void ) { + if( InVehicle() ) { + return; + } + + if (!noclip && (InCinematic() && lockView)) { + // 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; + } + + //HUMANHEAD: aob - moved logic into helper function so we can call this somewhere else while in a vehicle + viewAngles = DetermineViewAngles( usercmd, cmdAngles ); + + //HUMANHEAD rww - moved angle smoothing code here + // update the smoothed view angles + if (gameLocal.isClient) { + if ( gameLocal.framenum >= smoothedFrame && entityNumber != gameLocal.localClientNum ) { + idAngles anglesDiff = viewAngles - smoothedAngles; + anglesDiff.Normalize180(); + if ( idMath::Fabs( anglesDiff.yaw ) < 90.0f && idMath::Fabs( anglesDiff.pitch ) < 90.0f ) { + // smoothen by pushing back to the previous angles + viewAngles -= gameLocal.clientSmoothing * anglesDiff; + viewAngles.Normalize180(); + } + smoothedAngles = viewAngles; + } + smoothedOriginUpdated = false; + } + + // orient the model towards the direction we're looking + UpdateOrientation( viewAngles ); + //HUMANHEAD END +} + +/* +===================== +hhPlayer::UpdateOrientation + +HUMANHEAD: aob +===================== +*/ +void hhPlayer::UpdateOrientation( const idAngles& newUntransformedViewAngles ) { + idAngles angles; + //This is where the camera interpolator transforms the viewAngles to work with wallwalk. + //We also store the original untransformed view angles for use in some other code. + //idPlayer called SetAxis, but only used the yaw but because we are transforming our view angles + //we do something similier by taking the untransformed yaw and rotating by the camera interpolator's axis. + //This becomes our new axis, the camera interpolator axis plus the untransformed view angles yaw + SetUntransformedViewAngles( newUntransformedViewAngles ); + + angles = GetUntransformedViewAngles(); + if( IsWallWalking() ) { + angles.pitch = 0.0f; + } + SetUntransformedViewAxis( idAngles( 0.0f, angles.yaw, 0.0f ).ToMat3() ); + + viewAngles = cameraInterpolator.UpdateViewAngles( angles ); + SetAxis( TransformToPlayerSpace(GetUntransformedViewAxis()) ); +} + +//============================================================================= +// +// hhPlayer::CheckFOV +// +// Similar to idActor::CheckFOV, but isn't infinite along the vertical plane +// +// CJR +//============================================================================= + +bool hhPlayer::CheckFOV( const idVec3 &pos ) { + if ( fovDot == 1.0f ) { + return true; + } + + float dot; + idVec3 delta; + + delta = pos - GetEyePosition(); + delta.Normalize(); + + dot = delta * viewAngles.ToForward(); + + return ( dot >= fovDot ); +} + +//============================================================================= +// hhPlayer::CheckFOV +// jsh - Similar to CheckFOV, but only checks yaw +//============================================================================= +bool hhPlayer::CheckYawFOV( const idVec3 &pos ) { + if ( fovDot == 1.0f ) { + return true; + } + + float dot; + idVec3 delta; + idVec3 viewAng; + + delta = pos - GetEyePosition(); + delta.z = 0; + delta.Normalize(); + viewAng = viewAngles.ToForward(); + viewAng.z = 0; + dot = delta * viewAng; + + return ( dot >= fovDot ); +} + + +/* +===================== +hhPlayer::SetOrientation + +HUMANHEAD: aob +===================== +*/ +void hhPlayer::SetOrientation( const idVec3& origin, const idMat3& bboxAxis, const idVec3& lookDir, const idAngles& newUntransformedViewAngles ) { + //never let the untransformed view angles have a roll value + idAngles modifiedUntransViewAngles = newUntransformedViewAngles; + modifiedUntransViewAngles.roll = 0.0f; + + physicsObj.SetOrigin( GetLocalCoordinates(origin) ); + physicsObj.SetAxis( bboxAxis ); + physicsObj.CheckWallWalk( true ); + + SetViewAngles( modifiedUntransViewAngles ); + BufferLoggedViewAngles( modifiedUntransViewAngles ); + SetUntransformedViewAngles( modifiedUntransViewAngles ); + + SetAxis( lookDir.ToMat3() ); + SetUntransformedViewAxis( idAngles(0.0f, modifiedUntransViewAngles.yaw, 0.0f).ToMat3() ); + + cameraInterpolator.Reset( origin, bboxAxis[2], EyeHeightIdeal() ); + + UpdateViewAngles(); + UpdateVisuals(); +} +/* +===================== +hhPlayer::RestoreOrientation + +HUMANHEAD: mdl +Same as SetOrientation, except doesn't check wallwalk, since the materials may not be set before the first frame. +===================== +*/ +void hhPlayer::RestoreOrientation( const idVec3& origin, const idMat3& bboxAxis, const idVec3& lookDir, const idAngles& newUntransformedViewAngles ) { + physicsObj.SetOrigin( GetLocalCoordinates(origin) ); + physicsObj.SetAxis( bboxAxis ); + + SetViewAngles( newUntransformedViewAngles ); + BufferLoggedViewAngles( newUntransformedViewAngles ); + SetUntransformedViewAngles( newUntransformedViewAngles ); + + SetAxis( lookDir.ToMat3() ); + SetUntransformedViewAxis( idAngles(0.0f, newUntransformedViewAngles.yaw, 0.0f).ToMat3() ); + cameraInterpolator.Reset( origin, bboxAxis[2], EyeHeightIdeal() ); + + UpdateViewAngles(); + UpdateVisuals(); +} + +/* +===================== +hhPlayer::BufferLoggedViewAngles + +HUMANHEAD: aob - used when teleporting and using portals +===================== +*/ +void hhPlayer::BufferLoggedViewAngles( const idAngles& newUntransformedViewAngles ) { + idAngles deltaAngles = newUntransformedViewAngles - GetUntransformedViewAngles(); + + for( int ix = 0; ix < NUM_LOGGED_VIEW_ANGLES; ++ix ) { + loggedViewAngles[ix] += deltaAngles; + } +} + +/* +================ +hhPlayer::UpdateFromPhysics + +AOBMERGE - PERSISTANT MERGE +================ +*/ +void hhPlayer::UpdateFromPhysics( bool moveBack ) { + + // set master delta angles for actors + if ( GetBindMaster() ) { + if( !InVehicle() ) { + idAngles delta = GetDeltaViewAngles(); + if ( moveBack ) { + delta.yaw -= physicsObj.GetMasterDeltaYaw(); + } else { + delta.yaw += physicsObj.GetMasterDeltaYaw(); + } + + SetDeltaViewAngles( delta ); + } else { + SetUntransformedViewAxis( mat3_identity ); + SetUntransformedViewAngles( GetUntransformedViewAxis().ToAngles() ); + viewAngles = GetUntransformedViewAngles(); + SetAxis( GetBindMaster()->GetAxis() ); + + //AOB - now that we have an updated viewAxis we need to update the + //camera. Feels like a hack! + cameraInterpolator.SetTargetAxis( GetAxis(), INTERPOLATE_NONE ); + } + } + + UpdateVisuals(); +} + +/* +============== +hhPlayer::PerformImpulse +============== +*/ + +void hhPlayer::PerformImpulse( int impulse ) { + if( InVehicle() && GetVehicleInterface()->InvalidVehicleImpulse(impulse) ) { + return; + } + + idPlayer::PerformImpulse( impulse ); + + switch( impulse ) { + case IMPULSE_14: { + if ( weapon.IsValid() && weapon->IsType( hhWeaponZoomable::Type ) ) { + hhWeaponZoomable *weap = static_cast(weapon.GetEntity()); + if ( weap && weap->GetAltMode() ) { + weap->ZoomInStep(); + } else { + NextWeapon(); + } + } else { + NextWeapon(); + } + break; + } + case IMPULSE_15: { + if ( weapon.IsValid() && weapon->IsType( hhWeaponZoomable::Type ) ) { + hhWeaponZoomable *weap = static_cast(weapon.GetEntity()); + if ( weap && weap->GetAltMode() ) { + weap->ZoomOutStep(); + } else { + PrevWeapon(); + } + } else { + PrevWeapon(); + } + break; + } + case IMPULSE_16: + // Toggle Lighter + if (inventory.requirements.bCanUseLighter && weaponFlags != 0) { // mdl: Disable lighter if all weapons are disabled + if (!gameLocal.isClient) { + ToggleLighter(); + } + } + break; + case IMPULSE_25: + // Throw grenade + if ( weaponFlags != 0 && !ActiveGui()) { // mdl: Disable if all weapons are disabled + if (!gameLocal.isClient) { + ThrowGrenade(); + } + } + break; + case IMPULSE_54: + // Spirit Power key + if ( IsDeathWalking() ) { // CJR: Developer-only ability to quickly return from DeathWalk by hitting the spirit key + if ( developer.GetBool() && ( gameLocal.time - deathWalkTime > spawnArgs.GetInt( "deathWalkMinTime", "4000" ) ) ) { // Force the player to stay in deathwalk for a short period of time + deathWalkFlash = 0.0f; + PostEventMS( &EV_PrepareToResurrect, 0 ); + } + } + else if (inventory.requirements.bCanSpiritWalk) { + ToggleSpiritWalk(); + } + break; + + case IMPULSE_18: { +#if GAMEPAD_SUPPORT // VENOM BEGIN + idAngles localViewAngles( GetUntransformedViewAngles() ); + centerView.Init(gameLocal.time, 360, localViewAngles.pitch, 0); +#else + centerView.Init(gameLocal.time, 200, viewAngles.pitch, 0); +#endif // VENOM END + break; + } + } +} + +void hhPlayer::Present() { + idPlayer::Present(); + + if ( lighterHandle != -1 ) { + // Update oscillation position + idVec3 oscillation; + oscillation.x = 0.0f; + oscillation.y = idMath::Cos( MS2SEC(gameLocal.time) * spawnArgs.GetFloat("lighter_yspeed") ) * spawnArgs.GetFloat("lighter_ysize"); + oscillation.z = idMath::Sin( MS2SEC(gameLocal.time) * spawnArgs.GetFloat("lighter_zspeed") ) * spawnArgs.GetFloat("lighter_zsize"); + + idVec3 offset; + offset = spawnArgs.GetVector("offset_lighter"); + offset.z = EyeHeight(); + lighter.origin = GetOrigin() + GetAxis() * (offset + oscillation); + lighter.axis = mat3_identity; + + lighter.shaderParms[ SHADERPARM_DIVERSITY ] = renderEntity.shaderParms[ SHADERPARM_DIVERSITY ]; + + gameRenderWorld->UpdateLightDef( lighterHandle, &lighter ); + } +} + +void hhPlayer::LighterOn() { + if (IsSpiritOrDeathwalking() || InVehicle() || spectating || bReallyDead) { // No lighter in spirit mode, deathwalk, or when in vehicles (or when spectating or really dead -rww) + return; + } + + if (lighterHandle == -1) { + // Add the dynamic light + memset( &lighter, 0, sizeof( lighter ) ); + lighter.lightId = LIGHTID_VIEW_MUZZLE_FLASH + entityNumber; + lighter.pointLight = true; // false; ? + lighter.shader = declManager->FindMaterial( spawnArgs.GetString( "mtr_lighter" ) ); + lighter.shaderParms[ SHADERPARM_RED ] = spawnArgs.GetFloat( "lighterColorR" ); + lighter.shaderParms[ SHADERPARM_GREEN ] = spawnArgs.GetFloat( "lighterColorG" ); + lighter.shaderParms[ SHADERPARM_BLUE ] = spawnArgs.GetFloat( "lighterColorB" ); + lighter.shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + lighter.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + lighter.lightRadius = spawnArgs.GetVector( "lighter_radius" ); + + lighter.origin = GetOrigin() + GetAxis() * spawnArgs.GetVector("offset_lighter"); + lighter.axis = mat3_identity; + + lighterHandle = gameRenderWorld->AddLightDef( &lighter ); + + StartSound("snd_lighter_on", SND_CHANNEL_ITEM); + } +} + +void hhPlayer::LighterOff() { + if (lighterHandle != -1) { + gameRenderWorld->FreeLightDef( lighterHandle ); + lighterHandle = -1; + StartSound("snd_lighter_off", SND_CHANNEL_ITEM); + } +} + +bool hhPlayer::IsLighterOn() const { //HUMANHEAD PCF mdl 05/04/06 - Made const + return (lighterHandle != -1); +} + +void hhPlayer::ToggleLighter() { + if (IsLighterOn()) { + LighterOff(); + } + else { + LighterOn(); + } +} + +void hhPlayer::UpdateLighter() { + if (IsLighterOn()) { + // Increase the lighter's temperature until it overheats + if (!gameLocal.isMultiplayer) { //rww - don't bother overheating the lighter in mp + lighterTemperature += spawnArgs.GetFloat( "lighterHeatRate", "0.025" ) * MS2SEC( USERCMD_MSEC ); + if (lighterTemperature >= 1.0f) { + // Too hot, turn the lighter off + StartSound("snd_lighter_toohot", SND_CHANNEL_ANY); + lighterTemperature = 1.0f; + + if (!(godmode || noclip)) { + LighterOff(); + } + } + } + } + else { + // Lighter is off, so decrease the lighter's temperature + if (lighterTemperature > 0) { + lighterTemperature -= spawnArgs.GetFloat( "lighterCoolRate", "0.05") * MS2SEC( USERCMD_MSEC ); + } + } +} + +/* +================== +hhPlayer::PlayFootstepSound +================== +*/ +void hhPlayer::PlayFootstepSound() { + if ( IsSpiritOrDeathwalking() ) { + return; // No footstep sounds in spiritwalk mode + } + + if ( IsWallWalking() ) { + // Special case wallwalk since contacts includes some non-wallwalk types + const char* soundKey = gameLocal.MatterTypeToMatterKey( "snd_footstep", SURFTYPE_WALLWALK ); + StartSound( soundKey, SND_CHANNEL_BODY3, 0, false, NULL ); + return; + } + + idPlayer::PlayFootstepSound(); +} + +/* +===================== +hhPlayer::PlayPainSound + +HUMANHEAD: aob +===================== +*/ +void hhPlayer::PlayPainSound() { + if( IsSpiritOrDeathwalking() ) { + return; + } + + //HUMANHEAD PCF rww 09/15/06 - female mp sounds + if (IsFemale()) { + StartSound( "snd_pain_small_female", SND_CHANNEL_VOICE ); + return; + } + //HUMANHEAD END + idPlayer::PlayPainSound(); +} + +/* +=============== +hhPlayer::Give +=============== +*/ +bool hhPlayer::Give( const char *statname, const char *value ) { + if( IsDead() ) { + return false; + } + + return inventory.Give( this, spawnArgs, statname, value, &idealWeapon, true ); +} + +/* +=============== +hhPlayer::ReportAttack +=============== +*/ +void hhPlayer::ReportAttack(idEntity *attacker) { + if (gameLocal.isServer && attacker) { //rww - broadcast event for this on the server + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init(msgBuf, sizeof(msgBuf)); + msg.WriteBits(gameLocal.GetSpawnId(attacker), 32); + //unreliable since it's not super-important, and only send to this player. + ServerSendEvent(EVENT_REPORTATTACK, &msg, false, -1, entityNumber, true); + } + + int ix; + + for (ix=0; ix lastAttackers[ix].time + DAMAGE_INDICATOR_TIME) { + lastAttackers[ix].attacker = NULL; + } + } + + // Add this attacker to first free slot, if any + for (ix=0; ix 0) { + lastAttackers[oldSlot].attacker = attacker; + lastAttackers[oldSlot].time = gameLocal.time; + lastAttackers[oldSlot].displayed = false; + } +} + +/* +================== +hhPlayer::Damage +================== +*/ +void hhPlayer::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + + if( IsSpiritOrDeathwalking() ) { //Player is spirit-walking, so check for special immunities + const idKeyValue *kv = spawnArgs.MatchPrefix("immunityspirit"); + while( kv && kv->GetValue().Length() ) { + if ( !kv->GetValue().Icmp(damageDefName) ) { + return; + } + kv = spawnArgs.MatchPrefix("immunityspirit", kv); + } + + // If the player is spiritwalking, then any damage takes away spirit power instead of health + ammo_t ammo_spiritpower = idWeapon::GetAmmoNumForName( "ammo_spiritpower" ); + if ( IsSpiritWalking() ) { + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); + if ( damageDef ) { + idPlayer *player = (attacker && attacker->IsType( idPlayer::Type )) ? static_cast(attacker) : NULL; + if ( !(gameLocal.gameType == GAME_TDM + && !gameLocal.serverInfo.GetBool( "si_teamDamage" ) + && !damageDef->GetBool( "noTeam" ) + && player + && player != this // you get self damage no matter what + && player->team == team) ) { + //rww - don't damage teammates' spirit when ff off + int damageWhenSpirit = damageDef->GetInt("damageWhenSpirit", "0"); //rww - special damage for knocking players back to body in mp when shot by other things (namely spirit arrows) + int oldSpirit = inventory.ammo[ ammo_spiritpower ]; + + int spiritDamage = (damageDef->GetInt( "damage", "1" ) * spawnArgs.GetFloat( "damageScaleInSpirit" ) * damageScale )+damageWhenSpirit; + if ( spiritDamage <= 0 && damageScale > 0 ) { + spiritDamage = 1; + } + + if ( !UseAmmo( ammo_spiritpower, spiritDamage ) ) { + inventory.ammo[ ammo_spiritpower ] = 0; // Clear spiritpower amount when returning from excessive damage + } + } + } + + lastDamagedTime = gameLocal.time; // Save the damage time for the health recharge code + + // Track last attacker for use in displaying HUD hit indicator + if (!gameLocal.isClient) { + ReportAttack(attacker); + } + + //HUMANHEAD rww - damage feedback for hitting spirit players in mp + if (gameLocal.isMultiplayer && attacker && !gameLocal.isClient) { + hhPlayer *killer = NULL; + if (attacker->IsType(idPlayer::Type)) { + killer = static_cast(attacker); + } + else if (attacker->IsType(hhVehicle::Type)) { + hhVehicle *veh = static_cast(attacker); + if (veh->GetPilot() && veh->GetPilot()->IsType(idPlayer::Type)) { + killer = static_cast(veh->GetPilot()); + } + } + + if (killer && killer->entityNumber != entityNumber && killer->mpHitFeedbackTime <= gameLocal.time) { + if (killer == gameLocal.GetLocalPlayer()) { + assert(IsSpiritOrDeathwalking()); + if (gameLocal.gameType == GAME_TDM && team == killer->team) { + killer->StartSound( "snd_hitTeamFeedback", SND_CHANNEL_ITEM, 0, false, NULL ); + } + else { + killer->StartSound( "snd_hitSpiritFeedback", SND_CHANNEL_ITEM, 0, false, NULL ); + + //hardcoded health ranges for the various flash colors + float h; + if (health > 100) { + h = 0.0f; + } + else if (health > 75) { + h = 0.25f; + } + else if (health > 25) { + h = 0.50f; + } + else { + h = 0.75f; + } + SetShaderParm(3, h); + SetShaderParm(5, -MS2SEC(gameLocal.time)*2); + } + } + else { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init(msgBuf, sizeof(msgBuf)); + msg.WriteBits(gameLocal.GetSpawnId(this), 32); + killer->ServerSendEvent(EVENT_HITNOTIFICATION, &msg, false, -1, killer->entityNumber); + } + } + } + //HUMANHEAD END + + return; + } + } + + lastDamagedTime = gameLocal.time; // Save the damage time for the health recharge code + + // Track last attacker for use in displaying HUD hit indicator + if (!gameLocal.isClient) { + ReportAttack(attacker); + } + + // Check if the damage should "really kill" the player (when in deathwalk mode) + if ( IsDeathWalking() ) { + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); + if ( damageDef ) { + if ( damageDef->GetBool( "reallyKill", "0" ) ) { // Truly kill the player + ReallyKilled( inflictor, attacker, damageDef->GetInt( "damage", "9999" ), dir, location ); + return; + } else if ( damageDef->GetBool( "spiritDamage", "0" ) ) { // Drain spirit power + ammo_t ammo_spiritpower = idWeapon::GetAmmoNumForName( "ammo_spiritpower" ); + UseAmmo( ammo_spiritpower, damageDef->GetInt( "damage", "1" ) * damageScale ); + return; + } + } + } + + //HUMANHEAD rww - keep track of last person to attack me, debounce time will vary based on ground contact + if (attacker && attacker->IsType(idPlayer::Type)) { + airAttacker = attacker; + if (!AI_ONGROUND) { + airAttackerTime = gameLocal.time + 300; + } + else { //if they are on the ground do a 100ms debounce in case they fall off a ledge or something as a result of leftover attack velocity + airAttackerTime = gameLocal.time + 100; + } + } + //HUMANHEAD END + + // CJR: DDA: Only in single player + if ( !gameLocal.isMultiplayer ) { + float newDamageScale = gameLocal.GetDDAValue() * 2.0f; // Scale damage from easy to hard + newDamageScale *= damageScale; + + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); + if ( damageDef ) { + bool noGod = damageDef->GetBool("noGod", "0"); + if ( noGod ) { + // Make sure fatal damage bypasses post-deathwalk invulnerability + fl.takedamage = true; + } + } + + int oldHealth = health; + idPlayer::Damage( inflictor, attacker, dir, damageDefName, (const float)newDamageScale, location ); // CJR DDA TEST + + if ( attacker && attacker->IsType( hhMonsterAI::Type ) ) { // CJR DDA: Damaged by a monster, add the damage to the monster count + float delta = oldHealth - health; + + hhMonsterAI *monster = static_cast( attacker ); + + if ( monster ) { + monster->DamagedPlayer( delta ); + } + } + } else { + idPlayer::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + } + + //HUMANHEAD rww - hit feedback + if (gameLocal.isMultiplayer && attacker && !gameLocal.isClient) { //let's broadcast from the server only, so hit feedback is always reliable + hhPlayer *killer = NULL; + if (attacker->IsType(idPlayer::Type)) { + killer = static_cast(attacker); + } + else if (attacker->IsType(hhVehicle::Type)) { + hhVehicle *veh = static_cast(attacker); + if (veh->GetPilot() && veh->GetPilot()->IsType(idPlayer::Type)) { + killer = static_cast(veh->GetPilot()); + } + } + + if (killer && killer->entityNumber != entityNumber && killer->mpHitFeedbackTime <= gameLocal.time) { + if (killer == gameLocal.GetLocalPlayer()) { + //don't provide visual indicator when shooting a teammate + if (gameLocal.gameType == GAME_TDM && team == killer->team) { + killer->StartSound( "snd_hitTeamFeedback", SND_CHANNEL_ITEM, 0, false, NULL ); + } + else { + if (IsSpiritOrDeathwalking()) { + killer->StartSound( "snd_hitSpiritFeedback", SND_CHANNEL_ITEM, 0, false, NULL ); + } + else { + killer->StartSound( "snd_hitFeedback", SND_CHANNEL_ITEM, 0, false, NULL ); + } + + //hardcoded health ranges for the various flash colors + float h; + if (health > 100) { + h = 0.0f; + } + else if (health > 75) { + h = 0.25f; + } + else if (health > 25) { + h = 0.50f; + } + else { + h = 0.75f; + } + SetShaderParm(3, h); + SetShaderParm(5, -MS2SEC(gameLocal.time)*2); + } + } + else { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init(msgBuf, sizeof(msgBuf)); + msg.WriteBits(gameLocal.GetSpawnId(this), 32); + killer->ServerSendEvent(EVENT_HITNOTIFICATION, &msg, false, -1, killer->entityNumber); + } + + //see how this feels (and more importantly how destructive it is toward bandwidth) + //killer->mpHitFeedbackTime = gameLocal.time + USERCMD_MSEC; + } + } + //HUMANHEAD END + + if ( bFrozen ) { + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); + if ( damageDef && damageDef->GetInt( "free_cocoon", "0" ) ) { + Event_Unfreeze(); + } + } +} + +void hhPlayer::DoDeathDrop() { + // General dropping (for monsters, souls, etc.) + const idKeyValue *kv = NULL; + kv = spawnArgs.MatchPrefix( "def_drops", NULL ); + while ( kv ) { + + idStr drops = kv->GetValue(); + idDict args; + + idStr last5 = kv->GetKey().Right(5); + if ( drops.Length() && idStr::Icmp( last5, "Joint" ) != 0) { + + args.Set( "classname", drops ); + + // HUMANHEAD pdm: specify monster so souls can call back to remove body when picked up + args.Set("monsterSpawnedBy", name.c_str()); + + idVec3 origin; + idMat3 axis; + idStr jointKey = kv->GetKey() + idStr("Joint"); + idStr jointName = spawnArgs.GetString( jointKey ); + idStr joint2JointKey = kv->GetKey() + idStr("Joint2Joint"); + idStr j2jName = spawnArgs.GetString( joint2JointKey ); + + idEntity *newEnt = NULL; + gameLocal.SpawnEntityDef( args, &newEnt ); + HH_ASSERT(newEnt != NULL); + + if(jointName.Length()) { + jointHandle_t joint = GetAnimator()->GetJointHandle( jointName ); + if (!GetAnimator()->GetJointTransform( joint, gameLocal.time, origin, axis ) ) { + gameLocal.Printf( "%s refers to invalid joint '%s' on entity '%s'\n", (const char*)jointKey.c_str(), (const char*)jointName, (const char*)name ); + origin = renderEntity.origin; + axis = renderEntity.axis; + } + axis *= renderEntity.axis; + origin = renderEntity.origin + origin * renderEntity.axis; + newEnt->SetAxis(axis); + newEnt->SetOrigin(origin); + } + else { + newEnt->SetAxis(viewAxis); + newEnt->SetOrigin(GetOrigin()); + } + } + + kv = spawnArgs.MatchPrefix( "def_drops", kv ); + } +} + +//HUMANHEAD rww +/* +================== +hhPlayer::Event_RespawnCleanup +performs any necessary operations on death to prepare for a clean spawn. +only cosmetic things should be placed here, this function will not be called +for sure on death, at least for the client. +================== +*/ +void hhPlayer::Event_RespawnCleanup(void) { + //remove any fx entities which are bound to the player + idEntity *ent; + idEntity *next; + + for( ent = teamChain; ent != NULL; ent = next ) { + next = ent->GetTeamChain(); + if ( ent->GetBindMaster() == this && ent->IsType(hhEntityFx::Type) ) { + ent->Unbind(); + if( !ent->fl.noRemoveWhenUnbound ) { + ent->PostEventMS( &EV_Remove, 0 ); + if (gameLocal.isClient) { + ent->Hide(); + } + } + next = teamChain; + } + } +} +//HUMANHEAD END + +/* +================== +hhPlayer::Killed +================== +*/ +void hhPlayer::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + //you only die once + if( AI_DEAD ) { + return; + } + + CancelEvents(&EV_DamagePlayer); //rww - don't do any more posted damage events once dead + + if (gameLocal.isMultiplayer) { //rww - this was not handled at all, i guess because there are different circumstances for "death" in sp + wallwalkSoundController.StopSound( SND_CHANNEL_WALLWALK, SND_CHANNEL_WALLWALK2, false ); + spiritwalkSoundController.StopSound( SND_CHANNEL_BODY, SND_CHANNEL_BODY2 ); + + LighterOff(); + + if (!gameLocal.isClient && weapon.IsValid() && weapon->IsType(hhWeapon::Type) && weapon->CanDrop()) { //when dying in mp, toss my weapon out. + idVec3 forward, up; + + viewAngles.ToVectors( &forward, NULL, &up ); + //rww - hackishness to keep the type of ammo in the leechgun that it was dropped with + idEntity *dropped = weapon->DropItem( 50.0f * forward + 50.0f * up, 0, 60000, true ); + if (dropped && weapon->IsType(hhWeaponSoulStripper::Type)) { + dropped->spawnArgs.Set("def_droppedEnergyType", inventory.energyType); + } + } + } + + if (!gameLocal.isClient) { + DoDeathDrop(); + } + + if ( gameLocal.isMultiplayer && !gameLocal.IsCooperative() ) { + bDeathWalk = false; + bSpiritWalk = false; + bReallyDead = true; + } + else if ( !inventory.requirements.bCanDeathWalk || !gameLocal.DeathwalkMapLoaded() ) { + bDeathWalk = false; + bSpiritWalk = false; + bReallyDead = true; + } + + //First thing we do is get out of vehicle + if (InVehicle()) { + GetVehicleInterface()->GetVehicle()->EjectPilot(); + } + + // HUMANHEAD cjr: Update DDA + if ( !AI_DEAD && !gameLocal.isMultiplayer ) { + gameLocal.GetDDA()->DDA_AddDeath( this, attacker ); + } + // HUMANHEAD END + + if ( attacker && attacker->spawnArgs.GetBool("death_look_at", "0")) { + attacker->spawnArgs.GetString("death_look_at_bone", "", deathLookAtBone); + attacker->spawnArgs.GetString("death_camera_bone", "", deathCameraBone ); + deathLookAtEntity = attacker; + } + + if ( weapon.IsValid() ) { + //HUMANHEAD bjk 04/26/06 - no sniper scope when dead + if ( weapon->IsType( hhWeaponRifle::Type ) ) { + static_cast( weapon.GetEntity() )->ZoomOut(); + } + weapon->PutAway(); + } + + // HUMANHEAD nla + if ( hand.IsValid() ) { + hand->PutAway(); + } + // HUMANHEAD END + + if ( !bReallyDead ) { // Only go into deathwalk mode if the player is in the transitional death state + idVec3 origin; + idMat3 axis; + idVec3 viewDir; + idAngles angles; + idMat3 eyeAxis; + GetResurrectionPoint( origin, axis, viewDir, angles, eyeAxis, GetPhysics()->GetAbsBounds(), GetOrigin(), GetPhysics()->GetAxis(), GetAxis()[0], viewAngles ); + DeathWalk( origin, axis, viewDir.ToMat3(), angles, eyeAxis ); + } else { + if (gameLocal.isMultiplayer) { //HUMANHEAD rww + PostEventMS(&EV_RespawnCleanup, 32); + if (!gameLocal.isClient) { + StartRagdoll(); //start to rag on the player so that the proxy copies off proper af status + + GetPhysics()->SetContents( 0 ); //make non-solid + + hhMPDeathProxy *prox = (hhMPDeathProxy *)hhSpiritProxy::CreateProxy( spawnArgs.GetString("def_deathProxy_mp"), this, GetOrigin(), GetPhysics()->GetAxis(), viewAxis, viewAngles, GetEyeAxis() ); + assert(((hhSpiritProxy *)prox)->IsType(hhMPDeathProxy::Type)); + if (prox) { + idVec3 flingForce; + float capDmg = (float)damage; + if (capDmg > 200.0f) { + capDmg = 200.0f; + } + + flingForce = dir; + flingForce.Normalize(); + flingForce *= capDmg; + + prox->GetPhysics()->AddForce(0, prox->GetPhysics()->GetOrigin(0), flingForce*256.0f*256.0f); + prox->SetFling(prox->GetPhysics()->GetOrigin(0), flingForce); + } + + StopRagdoll(); //stop again, as we don't need to be ragging on the actual player + } + + Hide(); //hide player + + minRespawnTime = gameLocal.time + RAGDOLL_DEATH_TIME; + maxRespawnTime = minRespawnTime + 10000; + } else { + GetPhysics()->SetContents(0); + Hide(); + + minRespawnTime = gameLocal.time + 2000; + maxRespawnTime = minRespawnTime + 5000; + } //HUMANHEAD END + + physicsObj.SetMovementType( PM_DEAD ); + SAFE_REMOVE( weapon ); + } + + AI_DEAD = true; + + //HUMANHEAD rww + if (gameLocal.isMultiplayer) { + SetAnimState( ANIMCHANNEL_LEGS, "Legs_Death", 4 ); + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Death", 4 ); + SetWaitState( "" ); + } + //HUMANHEAD END + + //HUMANHEAD PCF rww 09/15/06 - female mp sounds + if (IsFemale()) { + StartSound( "snd_death_female", SND_CHANNEL_VOICE ); + } + else { + //HUMANHEAD END + StartSound( "snd_death", SND_CHANNEL_VOICE ); + } + + if ( gameLocal.isMultiplayer && !gameLocal.isCoop ) { + idPlayer *killer = NULL; + + if ( attacker->IsType( idPlayer::Type ) ) { + killer = static_cast(attacker); + } + else { //rww - otherwise try to credit airAttacker + if (airAttacker.IsValid() && + airAttacker.GetEntity() && + airAttackerTime > gameLocal.time) { + + if (airAttacker->IsType(idPlayer::Type)) { + killer = static_cast(airAttacker.GetEntity()); + } + else if (airAttacker->IsType(hhVehicle::Type)) { + hhVehicle *veh = static_cast(airAttacker.GetEntity()); + if (veh->GetPilot() && veh->GetPilot()->IsType(idPlayer::Type)) { + killer = static_cast(veh->GetPilot()); + } + } + } + } + gameLocal.mpGame.PlayerDeath( this, killer, inflictor, false ); //HUMANHEAD rww - pass inflictor + } + + UpdateVisuals(); + + airAttackerTime = 0; //HUMANHEAD rww - reset air attacker time once dead +} + +/* +============== +hhPlayer::Kill +============== +*/ +void hhPlayer::Kill( bool delayRespawn, bool nodamage ) { + if (noclip) { // HUMANHEAD pdm: Because of deathwalk, this shouldn't be allowed + return; + } + + if ( health > 0 ) { + //HUMANHEAD rww + if (IsSpiritOrDeathwalking()) { + if (!IsSpiritWalking()) { //don't "kill" when dead. + return; + } + StopSpiritWalk(true); + } + //HUMANHEAD END + + godmode = false; + health = 0; + //HUMANHEAD rww - if in a vehicle, eject now + if (InVehicle()) { + if (vehicleInterfaceLocal.GetVehicle()) { + vehicleInterfaceLocal.GetVehicle()->EjectPilot(); + } + } + //HUMANHEAD END + Damage( NULL, NULL, vec3_origin, "damage_suicide", 1.0f, INVALID_JOINT ); + } +} + +/* +=============== +hhPlayer::DetermineOwnerPosition + +HUMANHEAD: aob +=============== +*/ +void hhPlayer::DetermineOwnerPosition( idVec3 &ownerOrigin, idMat3 &ownerAxis ) { + ownerAxis = TransformToPlayerSpace( GetUntransformedViewAxis() ); + ownerOrigin = cameraInterpolator.GetCurrentPosition() + idVec3(g_gun_x.GetFloat(), g_gun_y.GetFloat(), g_gun_z.GetFloat()) * GetAxis(); +} + +/* +=============== +hhPlayer::GetViewPos +=============== +*/ +//HUMANHEAD bjk +void hhPlayer::GetViewPos( idVec3 &origin, idMat3 &axis ) { + idAngles angles; + + // if dead, fix the angle and don't add any kick + // HUMANHEAD cjr: Replaced health <= 0 with IsDead() call for deathwalk override + if ( IsDead() && !gameLocal.isMultiplayer ) { //rww - don't want this in mp. + // HUMANHEAD END + angles.yaw = viewAngles.yaw; + angles.roll = 40; + angles.pitch = -15; + axis = angles.ToMat3(); + origin = GetEyePosition(); + } else { + assert(kickSpring < 500.0f && kickSpring > 0.0f); + assert(kickDamping < 500.0f && kickDamping > 0.0f); + origin = viewBob + TransformToPlayerSpace( idVec3(g_viewNodalX.GetFloat(), g_viewNodalZ.GetFloat(), g_viewNodalZ.GetFloat() + EyeHeight()) ); + axis = TransformToPlayerSpace( (GetUntransformedViewAngles() + viewBobAngles + playerView.AngleOffset(kickSpring, kickDamping)).ToMat3() ); + } +} + +/* +=============== +hhPlayer::CalculateRenderView +=============== +*/ +void hhPlayer::CalculateRenderView( void ) { + idPlayer::CalculateRenderView(); + + // HUMANHEAD cjr + if ( IsSpiritOrDeathwalking() ) { // If spiritwalking, then allow the player to see special objects + renderView->viewSpiritEntities = true; + } +} + + +/* +=============== +hhPlayer::OffsetThirdPersonView + PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +=============== +*/ +void hhPlayer::OffsetThirdPersonView( float angle, float range, float height, bool clip ) { + idVec3 view; + trace_t trace; + idMat3 lookAxis; + idVec3 origin; + idAngles angles( GetUntransformedViewAngles() ); + + if ( angle ) { + angles.pitch = 0.0f; + } + + if ( angles.pitch > 45.0f && !InVehicle() ) { + angles.pitch = 45.0f; // don't go too far overhead + } + + //HUMANHEAD: aob + lookAxis = TransformToPlayerSpace( angles.ToMat3() * idAngles(0.0f, angle, 0.0f).ToMat3() ); + if( InVehicle() ) { + origin = GetOrigin() + lookAxis[2] * (height + EyeHeight()); + } else { + origin = GetEyePosition() + GetEyeAxis()[2] * height; + } + view = origin + lookAxis[0] * -range; + // trace a ray from the origin to the viewpoint to make sure the view isn't + // in a solid block. Use an 16 by 16 block to prevent the view from near clipping anything + if( !noclip ) { + int mask = MASK_SHOT_BOUNDINGBOX; + + if (gameLocal.isMultiplayer) { //rww - don't want this hitting corpses. also, shouldn't we pay attention to "clip"? + mask = MASK_PLAYERSOLID; + } + + thirdPersonCameraClipBounds.SetOwner( this ); + idEntity* ignore = (InVehicle()) ? GetVehicleInterface()->GetVehicle() : (idEntity*)this; + gameLocal.clip.Translation( trace, origin, view, &thirdPersonCameraClipBounds, lookAxis, mask, ignore ); + range *= trace.fraction; + } + + renderView->vieworg = origin + lookAxis[0] * -range; + renderView->viewaxis = lookAxis; + //HUMANHEAD END + renderView->viewID = 0; +} + + +/* +============================== +hhPlayer::GetGuiHandInfo + Retrns the proper gui hand info for a given gui +============================== +*/ +const char *hhPlayer::GetGuiHandInfo() { + const char *attrib; + idDict *item = NULL; + const char *handString; + + // Set the gui to: + // "required_attribute" "Hunter Hand" + // If item exists in player inventory, player uses guihand specified by item + if (!IsSpiritWalking() && focusGUIent && focusGUIent->spawnArgs.GetString("required_attribute", NULL, &attrib)) { + item = FindInventoryItem( attrib ); + if ( item ) { + if (item->GetString("def_guihand", NULL, &handString)) { + return handString; + } + } + } + + // If no required attribute/player doesn't have the item, check for a def_guihand string + if ( focusGUIent && focusGUIent->spawnArgs.GetString( "def_guihand", NULL, &handString ) ) { + return( handString ); + } + + // Otherwise, use the default player hand + return spawnArgs.GetString("def_guihand"); + +} + + +//============================================================================= +// +// Spirit walk functions +// +//============================================================================= + +void hhPlayer::StartSpiritWalk( const bool bThrust, bool force ) { + hhFxInfo fxInfo; + idVec3 origin; + idMat3 axis; + + //rww - don't do anything on the client + if (gameLocal.isClient) { + return; + } + + if ( bReallyDead ) { // Don't allow spiritwalking if truly dead + if (force) { + gameLocal.Error("Attempted to force spirit walk when dead.\n"); + } + return; + } + + // Make sure spirit walk isn't disabled + if ( !bAllowSpirit ) { + StartSound("snd_spiritWalkDenied", SND_CHANNEL_ANY); + return; + } + + spiritWalkToggleTime = gameLocal.time; + + if ( gameLocal.isMultiplayer ) { // CJR: Don't allow spiritwalking in MP unless the player has spirit power + if ( GetSpiritPower() <= 0 ) { + StartSound("snd_spiritWalkDenied", SND_CHANNEL_ANY, 0, true); + return; + } + } + + if ( !force && nextSpiritTime > gameLocal.time ) { // mdl: Make sure they didn't just get knocked back into their body by a wraith + StartSound("snd_spiritWalkDenied", SND_CHANNEL_ANY); + return; + } + + if (!IsSpiritOrDeathwalking()) { + if ( !gameLocal.IsLOTA() ) { // In LOTA, spirit power never drains + PostEventMS( &EV_DrainSpiritPower, spiritDrainHeartbeatMS ); + } + + if ( !bThrust ) { // Normal spiritwalking + EnableEthereal( spawnArgs.GetString( "def_spiritProxy" ), GetOrigin(), GetPhysics()->GetAxis(), viewAxis, viewAngles, GetEyeAxis() ); + } else { // Knocked out by a wraith + EnableEthereal( spawnArgs.GetString( "def_possessedProxy" ), GetOrigin(), GetPhysics()->GetAxis(), viewAxis, viewAngles, GetEyeAxis() ); + } + + SelectEtherealWeapon(); + + spiritwalkSoundController.StartSound( SND_CHANNEL_BODY, SND_CHANNEL_BODY2 ); + + // Update HUD + if (hud) { + hud->HandleNamedEvent("SwitchToEthereal"); + } + + // Set the player's skin to a glowy effect + SetSkinByName( spawnArgs.GetString("skin_Spiritwalk") ); + SetShaderParm( SHADERPARM_TIMEOFFSET, 1.0f ); // TEMP: cjr - Required by the forcefield material. Can remove when a proper spiritwalk texture is made + + // Spawn in a flash + GetViewPos( origin, axis ); + fxInfo.SetEntity( this ); + fxInfo.RemoveWhenDone( true ); + fxInfo.SetBindBone( "origin" ); + BroadcastFxInfoPrefixed( "fx_spiritWalkFlash", origin, axis, &fxInfo ); + + // Thrust the player backwards out of the body + if ( bThrust ) { + idVec3 vec = GetPhysics()->GetAxis()[0] * -200.0f + GetPhysics()->GetAxis()[2] * 50.0f; + + physicsObj.SetLinearVelocity( physicsObj.GetLinearVelocity() + vec ); + // set the timer so that the player can't cancel out the movement immediately + physicsObj.SetKnockBack( 100 ); + } + + // bg - trigger map entity, provides a simple hook for map/scripts + idEntity *swTrig = gameLocal.FindEntity( "sw_spiritWalkEntered" ); + if( swTrig ) { + swTrig->PostEventMS( &EV_Activate, 0, this ); + } + } +} + +void hhPlayer::StopSpiritWalk(bool forceAllowance) { + hhFxInfo fxInfo; + idVec3 origin; + idMat3 axis; + + //rww - don't do anything on the client + if (gameLocal.isClient) { + return; + } + + if ( IsPossessed() ) { // If possessed and the player stops spiritwalking, they die + PossessKilled(); + return; + } + + spiritWalkToggleTime = gameLocal.time; + + if ( bReallyDead ) { // Don't allow spiritwalking if truly dead + return; + } + + CancelEvents(&EV_SetOverlayMaterial); //HUMANHEAD rww - if being forced back out of spirit mode quickly enough, don't set overlay afterward + + if (IsSpiritOrDeathwalking()) { + CancelEvents(&EV_DrainSpiritPower); + if( weapon.IsValid() && weapon->IsType(hhWeaponSpiritBow::Type) ) { + hhWeaponSpiritBow *bow = static_cast( weapon.GetEntity() ); + if( bow->BowVisionIsEnabled() ) { + bow->StopBowVision(); + } + } + DisableEthereal(); + spiritwalkSoundController.StopSound( SND_CHANNEL_BODY, SND_CHANNEL_BODY2 ); + SetSkin( NULL ); + + // bg - trigger map entity, provides a simple hook for map/scripts + idEntity *swTrig = gameLocal.FindEntity( "sw_spiritWalkExited" ); + if( swTrig ) { + swTrig->PostEventMS( &EV_Activate, 0, this ); + } + + // Update HUD + if (hud) { + hud->HandleNamedEvent("SwitchFromEthereal"); + } + buttonMask |= BUTTON_ATTACK; //HUMANHEAD bjk + + // Spawn in a flash + GetViewPos( origin, axis ); + fxInfo.SetEntity( this ); + fxInfo.RemoveWhenDone( true ); + fxInfo.SetBindBone( "origin" ); + BroadcastFxInfoPrefixed( "fx_spiritWalkFlash", origin, axis, &fxInfo ); + } +} + +void hhPlayer::ToggleSpiritWalk( void ) { + if (spectating) { //rww - do not allow spectators to spirit walk. + return; + } + + if ( bPossessed ) { // Cannot toggle away from spiritwalk when possessed + StartSound("snd_spiritWalkDenied", SND_CHANNEL_ANY); + return; + } + + // spiritwalk time check -- force a delay between spiritwalking and not + if (gameLocal.time < spiritWalkToggleTime + 250) { + return; + } + + if( IsSpiritOrDeathwalking() ) { + StopSpiritWalk(); + } else { + StartSpiritWalk( false ); + } +} + +//HUMANHEAD rww +void hhPlayer::RestorePlayerLocationFromDeathwalk( const idVec3& origin, const idMat3& bboxAxis, const idVec3& viewDir, const idAngles& angles ) { + idVec3 newOrigin; + idMat3 newAxis; + idVec3 newViewDir; + idAngles newAngles; + + if( deathwalkLastCrouching ) { + ForceCrouching(); + } + + SetEyeAxis(deathwalkLastEyeAxis); + GetResurrectionPoint( newOrigin, newAxis, newViewDir, newAngles, deathwalkLastEyeAxis, GetPhysics()->GetAbsBounds(), deathwalkLastOrigin, deathwalkLastBBoxAxis, viewDir, angles ); + Teleport( newOrigin, newAxis, newViewDir, (newAngles.ToMat3() * deathwalkLastEyeAxis.Transpose()).ToAngles(), NULL ); +} +//HUMANHEAD END + +//============================================================================= +// +// hhPlayer::EnableEthereal +// +// Sets the player into an ethereal state (used for both spiritwalk and deathwalk) +// This function does the following: +// - Fade volume down on all class 0 sounds +// - Sets bSpiritWalk (since this is true for both spirit and death walks) +// - Spawns a proxy +// - Sets the new collision +// - Disables the weapons (the bow is enabled seperately for spiritwalk) +// - Disables the lighter +// - Unpossesses the player (if possessed) +// - Updates bindings +//============================================================================= + +void hhPlayer::EnableEthereal( const char *proxyName, const idVec3& origin, const idMat3& bboxAxis, const idMat3& newViewAxis, const idAngles& newViewAngles, const idMat3& newEyeAxis ) { + //rww - don't do anything on the client - DO NOT PUT ANYTHING ABOVE HERE + if (gameLocal.isClient) { + return; + } + + // Turn on low-pass effect + if (gameLocal.localClientNum == entityNumber) { //rww - don't do this for anyone but the local client on listen servers + gameLocal.SpiritWalkSoundMode( true ); + } + + // Spawn proxy Tommy actor + spiritProxy = hhSpiritProxy::CreateProxy( proxyName, this, origin, bboxAxis, newViewAxis, newViewAngles, newEyeAxis ); + + // Change player's collision type (to allow walking through forcefields, etc) + GetPhysics()->SetClipMask( MASK_SPIRITPLAYER ); + fl.acceptsWounds = false; + spawnArgs.Set("produces_splats", "0"); + + // HUMANHEAD mdl: Let our enemies know about the spirit proxy + for ( int i = 0; i < hhMonsterAI::allSimpleMonsters.Num(); i++ ) { + if ( hhMonsterAI::allSimpleMonsters[i]->GetEnemy() == this ) { + hhMonsterAI::allSimpleMonsters[i]->ProcessEvent( &MA_EnemyIsSpirit, this, spiritProxy.GetEntity() ); + } + } + + // If bound to another object (like on a rail ride), swap bindings with the proxy + if ( GetBindMaster() != NULL ) { + if (GetBindMaster()->IsType(hhBindController::Type)) { + hhBindController *binder = static_cast(GetBindMaster()); + bool loose = binder->IsLoose(); + binder->Detach(); + binder->Attach( spiritProxy.GetEntity(), loose ); + } else { + gameLocal.Warning("Handle this: spiritwalking while bound to non-rail"); + SwapBindInfo( spiritProxy.GetEntity() ); // untested + } + } + + SnapDownCurrentWeapon(); + + LighterOff(); + + // Set the player into spiritwalk mode + bSpiritWalk = true; +} + +//============================================================================= +// +// hhPlayer::DisableEthereal +// +// Returns the player from an ethereal state (used for both spiritwalk and deathwalk) +// This function does the following: +// - Clears bSpiritWalk (since this is true for both spirit and death walks) +// - Removes the proxy +// - Sets the new collision +// - Re-enables the weapons +// - Updates bindings +// - Fade volume back up on all class 0 sounds +//============================================================================= + +void hhPlayer::DisableEthereal( void ) { + hhFxInfo fxInfo; + idVec3 boneOffset; + idMat3 boneAxis; + + //rww - don't do anything on the client - DO NOT PUT ANYTHING ABOVE HERE + if (gameLocal.isClient) { + return; + } + + bSpiritWalk = false; + + // Spawn in an effect when the spirit is snapped back + GetJointWorldTransform( "waist", boneOffset, boneAxis ); + fxInfo.RemoveWhenDone( true ); + fxInfo.SetNormal( boneAxis[1] ); + BroadcastFxInfo( spawnArgs.GetString( "fx_spiritReturn" ), boneOffset, GetAxis(), &fxInfo ); + + if( spiritProxy.IsValid() ) { + // Handle case of returning to a body on a rail ride + idEntity *spiritMaster = spiritProxy->GetBindMaster(); + if (spiritMaster != NULL) { + if (spiritMaster->IsType(hhBindController::Type)) { + hhBindController *binder = static_cast(spiritMaster); + bool loose = binder->IsLoose(); + binder->Detach(); + spiritProxy->DeactivateProxy(); + binder->Attach(this, loose); + } else if (spiritMaster->IsType(hhMonsterAI::Type)) { + if (reinterpret_cast (spiritMaster)->GetEnemy() != static_cast (spiritProxy.GetEntity())) { + gameLocal.Warning("Monster has spirit proxy bound to it but spirit proxy is not it's enemy!\n"); + } + spiritProxy->DeactivateProxy(); + } else { + gameLocal.Warning("Handle this: spiritwalking while bound to non-rail"); + idEntity *temp = spiritProxy.GetEntity(); // this removed by DeactivateProxy + spiritProxy->DeactivateProxy(); + SwapBindInfo(temp); // untested + } + } + else { // Normal case + spiritProxy->DeactivateProxy(); + } + } + + PutawayEtherealWeapon(); + + GetPhysics()->SetClipMask( MASK_PLAYERSOLID ); + fl.acceptsWounds = true; + spawnArgs.Set("produces_splats", "1"); + + // Turn off low-pass effect + if (gameLocal.localClientNum == entityNumber) { //rww - don't do this for anyone but the local client on listen servers + gameLocal.SpiritWalkSoundMode( false ); + } + + // HUMANHEAD mdl: Let our enemies know the spirit proxy is going away + for ( int i = 0; i < hhMonsterAI::allSimpleMonsters.Num(); i++ ) { + if ( hhMonsterAI::allSimpleMonsters[i]->GetEnemy() == spiritProxy.GetEntity() ) { + hhMonsterAI::allSimpleMonsters[i]->ProcessEvent( &MA_EnemyIsPhysical, this, spiritProxy.GetEntity() ); + } else if ( hhMonsterAI::allSimpleMonsters[i]->GetEnemy() == this ) { // Targetting spirit that is going away + hhMonsterAI::allSimpleMonsters[i]->ProcessEvent( &MA_EnemyIsPhysical, this, NULL ); + } + } + + //HUMANHEAD rww - clear the focus now that we've changed positions by switching out of spirit mode + focusTime = 0; + UpdateFocus(); + //HUMANHEAD END + + spiritProxy = NULL; +} + +//============================================================================= +// +// hhPlayer::GetResurrectionPoint +// +// Get the position/orientation for resurrection after deathwalk +//============================================================================= +void hhPlayer::GetResurrectionPoint( idVec3& origin, idMat3& axis, idVec3& viewDir, idAngles& angles, idMat3& eyeAxis, const idBounds& absBounds, const idVec3& defaultOrigin, const idMat3& defaultAxis, const idVec3& defaultViewDir, const idAngles& defaultAngles ) { + idClipModel *clipModelList[ MAX_GENTITIES ]; + idClipModel* clipModel = NULL; + idEntity* entity = NULL; + hhSafeResurrectionVolume* volume = NULL; + + int num = gameLocal.clip.ClipModelsTouchingBounds( absBounds, CONTENTS_DEATHVOLUME, clipModelList, MAX_GENTITIES ); + for( int ix = 0; ix < num; ++ix ) { + clipModel = clipModelList[ix]; + + if( !clipModel ) { + continue; + } + + entity = clipModel->GetEntity(); + if( !entity || !entity->IsType(hhSafeResurrectionVolume::Type) ) { + continue; + } + + volume = static_cast( entity ); + volume->PickRandomPoint( origin, axis ); + viewDir = axis[0]; + angles = axis.ToAngles(); + eyeAxis = mat3_identity; + return; + } + + eyeAxis = GetEyeAxis(); + origin = defaultOrigin; + axis = defaultAxis; + viewDir = defaultViewDir; + angles = defaultAngles; + +// gameLocal.Printf("Deathwalk @:\n"); +// gameLocal.Printf(" origin: %s\n", origin.ToString(0)); +// gameLocal.Printf(" axis: %s\n", axis.ToString(2)); +// gameLocal.Printf(" viewDir: %s\n", viewDir.ToString(2)); +// gameLocal.Printf(" angles: %s\n", angles.ToString(0)); +} + + +//============================================================================= +// +// hhPlayer::SquishedByDoor +// +//============================================================================= +void hhPlayer::SquishedByDoor(idEntity *door) { + if ( door == this ) { // Don't allow the squished code to send a player back it the squisher is the player itself + return; + } + + // If there is a spirit player in the door, make him go back to physical so he doesn't become stuck + if (IsSpiritWalking()) { + StopSpiritWalk(); + } +} + +//============================================================================= +// +// hhPlayer::UpdatePossession +// +// Updates the possession effects (if the player is possessed) +// +// HUMANHEAD cjr +//============================================================================= + +void hhPlayer::UpdatePossession( void ) { + + if ( !IsPossessed() ) { // Player is not possessed + return; + } + +/* todo: + possessionTimer -= MS2SEC( USERCMD_MSEC ); + + // Update a shaderparm for the view screen to update based upon possessionTimer + possessionMax = spawnArgs.GetFloat( "possessionTime", "8" ); + possessionScale = possessionTimer / possessionMax; + playerView.SetViewOverlayColor( idVec4( 1.0f, 1.0f, 1.0f, possessionScale ) ); + + // Mess with the player's FOV while they are being possessed + possessionFOV = g_fov.GetFloat() + (1.0f - possessionScale) * 60 + 1.5f * sin( MS2SEC(gameLocal.time) * 3 ); + + if ( possessionTimer <= 0 ) { // The player is now fully possessed, kill them + // TODO: Extra code to show the possessed player running around and such? + Unpossess(); + Damage( NULL, NULL, vec3_origin, "damage_crush", 1.0f, INVALID_JOINT ); + } +*/ +} + +//============================================================================= +// +// hhPlayer::RechargeHealth +// +// Recharges the player's health to 25% +// +// This is done to avoid gameplay issues where the player has only a few points +// of health. The game will still be tense if the player is at 25%, but +// will play more fairly. +// +// HUMANHEAD cjr +//============================================================================= + +void hhPlayer::Event_RechargeHealth( void ) { + + if ( health > 0 && health < spawnArgs.GetInt( "healthRecharge", "25" ) && !IsSpiritOrDeathwalking() ) { // Only recharge if the player is alive and in the critical zone + if ( gameLocal.time - lastDamagedTime >= spawnArgs.GetInt( "healthRechargeDelay", "1500" ) ) { // Delay before recharging + health++; + } + } + + PostEventSec( &EV_RechargeHealth, spawnArgs.GetFloat( "healthRechargeRate", "0.5" ) ); +} + +//============================================================================= +// +// hhPlayer::RechargeRifleAmmo +// +// Recharges rifle ammo if the player is very low. This is done to guarantee +// that the player has at least some ammo to solve puzzles that require shooting +// something such as a gravity switch. +// +// HUMANHEAD cjr +//============================================================================= + +void hhPlayer::Event_RechargeRifleAmmo( void ) { + + int ammoIndex = inventory.AmmoIndexForAmmoClass( "ammo_rifle" ); + int ammoCount = inventory.ammo[ ammoIndex ]; + + int maxAmmo = spawnArgs.GetInt( "rifleAmmoRechargeMax", "20" ); + if ( ammoCount < maxAmmo && !AI_ATTACK_HELD ) { // CJR PCF 04/26/06 + inventory.ammo[ ammoIndex ] += 2; + if ( inventory.ammo[ ammoIndex ] > maxAmmo ) { + inventory.ammo[ ammoIndex ] = maxAmmo; + } + } + + PostEventSec( &EV_RechargeRifleAmmo, spawnArgs.GetFloat( "rifleAmmoRechargeRate", "2" ) ); +} + +//============================================================================= +// +// hhPlayer::Event_DrainSpiritPower +// +// HUMANHEAD pdm: 10 Hz timer used to drain spirit power at differing rates +//============================================================================= +void hhPlayer::Event_DrainSpiritPower() { + + // JRM: God mode, don't drain! + if(godmode) { + return; + } + + ammo_t ammo_spiritpower = idWeapon::GetAmmoNumForName( "ammo_spiritpower" ); + + if ( gameLocal.isMultiplayer ) { // CJR: Drain spirit power in MP + if ( !UseAmmo( ammo_spiritpower, 1 ) ) { + StopSpiritWalk(); + return; + } + } + + // spirit bow alt mode drain + if( weapon.IsValid() && weapon->IsType(hhWeaponSpiritBow::Type) ) { + hhWeaponSpiritBow *bow = static_cast( weapon.GetEntity() ); + if( bow->BowVisionIsEnabled() ) { + if( !UseAmmo(ammo_spiritpower, 1) ) { // Bow vision drains spirit power + return; + } + } + } + + PostEventMS( &EV_DrainSpiritPower, spiritDrainHeartbeatMS ); +} + +//============================================================================= +// +// hhPlayer::DeathWalk +// +//============================================================================= + +void hhPlayer::DeathWalk( const idVec3& resurrectOrigin, const idMat3& resurrectBBoxAxis, const idMat3& resurrectViewAxis, const idAngles& resurrectViewAngles, const idMat3& resurrectEyeAxis ) { + if ( IsSpiritWalking() ) { // Ensure that two proxies cannot be active at the same time + StopSpiritWalk(); + } + + bDeathWalk = true; + deathWalkTime = gameLocal.time; // Get the time of death + + health = 0; // Force health to zero + + deathWalkPower = spawnArgs.GetInt( "deathWalkPowerStart" ); // Power in deathwalk. When full, the player will return + fl.takedamage = false; // can no longer be damaged in deathwalk mode + + if (hud) { + hud->HandleNamedEvent("SwitchToEthereal"); + } + + // Disable clip until we get to deathwalk + bInDeathwalkTransition = true; + + //rww - for deathwalk, let's store these values on the player instead of relying on the death proxy + deathwalkLastOrigin = resurrectOrigin; + deathwalkLastBBoxAxis = resurrectBBoxAxis; + deathwalkLastViewAxis = resurrectViewAxis; + deathwalkLastViewAngles = resurrectViewAngles; + deathwalkLastEyeAxis = resurrectEyeAxis; + deathwalkLastCrouching = IsCrouching(); + + EnableEthereal( spawnArgs.GetString("def_deathProxy"), resurrectOrigin, resurrectBBoxAxis, resurrectViewAxis, resurrectViewAngles, resurrectEyeAxis ); + // Set the overlay material for DeathWalk + playerView.SetViewOverlayColor( colorWhite ); // Guarantee that the color is reset + PostEventMS( &EV_SetOverlayMaterial, 1, spawnArgs.GetString("mtr_deathWalk"), false ); + + //Don't allow weapon use for a bit + if ( weapon.GetEntity() ) { + weapon.GetEntity()->PutAway(); + } + + // Alert the scripts that death has occured so they can manage music + idEntity *dwJustDied = gameLocal.FindEntity("dw_justdied"); + if (dwJustDied) { + dwJustDied->PostEventMS(&EV_Activate, 0, this); + } + + deathwalkSoundController.StartSound( SND_CHANNEL_BODY, SND_CHANNEL_BODY2 ); + + // Flash the screen before entering death world + playerView.SetViewOverlayColor( idVec4( 1.0f, 1.0f, 1.0f, 0.0f ) ); + deathWalkFlash = 0.0f; + possessionFOV = 90.0f; + + CancelEvents( &EV_RechargeHealth ); + CancelEvents( &EV_RechargeRifleAmmo ); + + // If this is our first time, tell the script + if (!gameLocal.IsCompetitive() && !inventory.bHasDeathwalked) { + const function_t *firstFunc = gameLocal.program.FindFunction( "map_deathwalk::FirstDeathwalk" ); + if ( firstFunc ) { + idThread *thread = new idThread(); + thread->CallFunction( firstFunc, false ); + thread->DelayedStart( 0 ); + } + } + + //rww - if there is a deathwalk portal, let's create a target for it and activate it + if (!gameLocal.FindEntity("dw_deathLocation")) { //check to see if we've made ourselves a target yet + idEntity *dwPortal = gameLocal.FindEntity( "dw_deathPortal" ); + if (dwPortal) { + trace_t tr; + idVec3 camPos, camDir; + idAngles camAngles; + idDict args; + + //FIXME more complex logic that allows angles to change based on collision circumstances + //do wider trace that takes the actual view frustum of camera into account + //check for collisions with the actual view "plane" + camPos = resurrectOrigin; + camPos += resurrectBBoxAxis[2] * 64.0f; + gameLocal.clip.TracePoint(tr, resurrectOrigin, camPos, CONTENTS_SOLID, this); + camDir = resurrectOrigin-camPos; + camAngles = camDir.ToAngles(); + + args.Clear(); + args.SetVector("origin", tr.endpos); + args.SetAngles("angles", camAngles); + args.SetMatrix("rotation", camAngles.ToMat3()); + args.Set("name", "dw_deathLocation"); + gameLocal.SpawnObject("info_null", &args); + + dwPortal->ProcessEvent(&EV_UpdateCameraTarget); //update for the newly placed cameraTarget + dwPortal->PostEventMS(&EV_Activate, 0, this); + if (dwPortal->cameraTarget) { //if camera target was legitimate, force an update to the remoteRenderView now + dwPortal->GetRenderEntity()->remoteRenderView = dwPortal->cameraTarget->GetRenderView(); + } + } + } + + Hide(); // Don't show the player when they are in the limbo state between alive and dead + PostEventSec( &EV_PrepareForDeathWorld, spawnArgs.GetFloat( "prepareForDeathWorldDelay", "1.5" ) ); +} + +//============================================================================= +// +// hhPlayer::Event_PrepareForDeathWorld +// +// Flashes the screen / fades the FOV +//============================================================================= + +void hhPlayer::Event_PrepareForDeathWorld() { + deathWalkFlash += spawnArgs.GetFloat( "deathWalkFlashChange", "0.05" ); + if ( deathWalkFlash >= 1.0f ) { + deathWalkFlash = 1.0f; + PostEventSec( &EV_EnterDeathWorld, 0.0f ); // Actually send player into the deathworld + } else { + PostEventSec( &EV_PrepareForDeathWorld, 0.02f ); + } + + possessionFOV += spawnArgs.GetFloat( "deathWalkFOVChange", "3.0" ); + playerView.SetViewOverlayColor( idVec4( 1.0f, 1.0f, 1.0f, deathWalkFlash ) ); +} + +//============================================================================= +// +// hhPlayer::Event_EnterDeathWorld +// +// - Teleports the player to the death world +// - Spawns the death wraiths +// - Gives the player a weapon +//============================================================================= + +void hhPlayer::Event_EnterDeathWorld() { + // Reset player view information + playerView.SetViewOverlayColor( idVec4( 1.0f, 1.0f, 1.0f, 0.0f ) ); // Turn off the flash + possessionFOV = 0.0f; + bDeathWalkStage2 = false; + + // Spawn in DeathWraiths after a short period of time + if ( !inventory.bHasDeathwalked ) { // Delay the wraiths the first time the player deathwalks + int firstDelay = spawnArgs.GetInt( "deathWalk_firstTimeDelay", "15000" ); + PostEventMS( &EV_SpawnDeathWraith, firstDelay ); // CJR: delay the deathwraiths for several seconds while the Grandfather DW speech is going on + PostEventMS( &EV_AdjustSpiritPowerDeathWalk, firstDelay + 2000 ); + inventory.bHasDeathwalked = true; + } else { + PostEventMS( &EV_SpawnDeathWraith, 0 ); // CJR: Non-first time, spawn the wraiths instantly + PostEventMS( &EV_AdjustSpiritPowerDeathWalk, 2000 ); + } + + // Give the player the spirit bow + SelectEtherealWeapon(); + + // get the entity which tells us where we want to place our dw proxy + idEntity *dwProxyPosEnt = gameLocal.FindEntity( "dw_floatingBodyMarker" ); + if (dwProxyPosEnt) { + //if we found that, then this is the new deathwalk, so create the deathwalk proxy. + hhDeathWalkProxy *dwProxy = (hhDeathWalkProxy *)gameLocal.SpawnObject( spawnArgs.GetString("def_deathWalkProxy"), NULL ); + if (!dwProxy) { + gameLocal.Error("hhPlayer::Event_EnterDeathWorld: Could not create hhDeathWalkProxy"); + return; + } + idAngles dwProxyAngles; + dwProxyPosEnt->spawnArgs.GetAngles("angles", "0 0 0", dwProxyAngles); + dwProxy->ActivateProxy(this, dwProxyPosEnt->GetOrigin(), dwProxyAngles.ToMat3(), dwProxyAngles.ToMat3(), dwProxyAngles, GetEyeAxis()); + } + + idEntity* deathWalkPlayerStartManager = gameLocal.FindEntity( "dw_deathWalkPlayerStartManager" ); + if( !deathWalkPlayerStartManager ) { + return; + } + + idEntity* deathWalkStart = deathWalkPlayerStartManager->PickRandomTarget(); + if( deathWalkStart ) { + deathWalkStart->ProcessEvent( &EV_Activate, this ); + } + + // Trigger the 'DeathWalkEntered' entity + // This allows the scripters to do something upon entering deathwalk + idEntity *dwe = gameLocal.FindEntity("dw_DeathWalkEntered"); + if (dwe) { + dwe->PostEventMS(&EV_Activate, 0, this); + } + + // Allow collisions again now that we're in the death world + bInDeathwalkTransition = false; + Show(); // The player is visible in deathmode + + // Reset the player's health and spirit power + ammo_t ammo_spiritpower = idWeapon::GetAmmoNumForName( "ammo_spiritpower" ); + inventory.ammo[ ammo_spiritpower ] = 0; + SetHealth( hhMath::hhMax( health, spawnArgs.GetInt("minResurrectHealth", "50") ) ); +} + +//============================================================================= +// +// hhPlayer::Event_AdjustSpiritPowerDeathWalk +// +//============================================================================= +void hhPlayer::Event_AdjustSpiritPowerDeathWalk() { + if( !IsDeathWalking() ) { + return; + } + + // Increase deathwalk power + int power = GetDeathWalkPower(); + power += spawnArgs.GetInt( "deathWraithPowerAmount" ); + SetDeathWalkPower( power ); + + // Check if deathWalkPower has exceeded the maximum + if ( deathWalkPower >= spawnArgs.GetInt( "deathWalkPowerMax", "1000") ) { + DeathWalkSuccess(); + return; + } + + CancelEvents( &EV_AdjustSpiritPowerDeathWalk ); + PostEventSec( &EV_AdjustSpiritPowerDeathWalk, spawnArgs.GetFloat("deathWalkDeathPowerIncreaseRate") ); +} + +//============================================================================= +// +// hhPlayer::DeathWalkSuccess +// +// Called when deathwalk timer runs out or we run out of spirit power +//============================================================================= +void hhPlayer::DeathWalkSuccess() { + CancelEvents( &EV_AdjustSpiritPowerDeathWalk ); + bDeathWalkStage2 = true; + + idEntity *dw_deathWalkResult = gameLocal.FindEntity("dw_deathWalkSuccess"); + if (dw_deathWalkResult) { //if we have a result, activate it. + dw_deathWalkResult->ProcessEvent(&EV_Activate, this); + } +} + +//============================================================================= +// +// hhPlayer::ReallyKilled +// +// Called when the player should be truly killed +// Funnels through Killed(), but disabled the deathwalk functionality +//============================================================================= + +void hhPlayer::ReallyKilled( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( !IsSpiritOrDeathwalking() ) { + return; + } + + bDeathWalk = false; + bSpiritWalk = false; + + bReallyDead = true; + + // Turn off low-pass effect + if (!gameLocal.isClient) { //rww + if (gameLocal.localClientNum == entityNumber) { //rww - don't do this for anyone but the local client on listen servers + gameLocal.SpiritWalkSoundMode( false ); + gameLocal.DialogSoundMode( false ); + } + } + + if (hud) { + hud->HandleNamedEvent("SwitchFromEthereal"); + } + + Killed( inflictor, attacker, damage, dir, location ); +} + +//============================================================================= +// +// hhPlayer::Event_SpawnDeathWraith +// +// Spawns in a special type of wraith that is only visible in deathwalk +//============================================================================= + +void hhPlayer::Event_SpawnDeathWraith() { + idDict args; + idEntity *ent; + int maxDeathWalkWraiths = spawnArgs.GetInt( "deathWalkMaxWraiths" ); + + if ( !IsDeathWalking() ) { // Don't spawn in a wraith if the player has resurrected + return; + } + + float angle = hhMath::PI * gameLocal.random.RandomFloat(); + idVec3 startPoint = idVec3( hhMath::Sin(angle) * 600.0f, hhMath::Cos(angle) * 600.0f, 0.0f ) - GetAxis()[2] * 100.0f; + + args.Clear(); + args.SetVector( "origin", GetEyePosition() + startPoint ); + args.SetMatrix( "rotation", idAngles( 0.0f, (-viewAxis[0]).ToYaw(), 0.0f ).ToMat3() ); + ent = gameLocal.SpawnObject( spawnArgs.GetString("def_deathWraith"), &args ); + if ( ent ) { + hhDeathWraith *wraith = static_cast< hhDeathWraith * > ( ent ); + wraith->SetEnemy( this ); + } + + // Check the number of deathwraiths in the world + int count = 0; + for( ent = gameLocal.activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + if ( ent && ent->IsType( hhDeathWraith::Type ) ) { + count++; + } + } + + if ( count < maxDeathWalkWraiths ) { + PostEventMS( &EV_SpawnDeathWraith, 0 ); + } +} + +//============================================================================= +// +// hhPlayer::GetDeathwalkEnergyDestination +// +//============================================================================= +idEntity *hhPlayer::GetDeathwalkEnergyDestination() { + return gameLocal.FindEntityOfType(hhDeathWalkProxy::Type, NULL); +} + +//============================================================================= +// +// hhPlayer::DeathWalkDamagedByWraith +// +//============================================================================= +void hhPlayer::DeathWalkDamagedByWraith(idEntity *attacker, const char *damageType) { + Damage( attacker, attacker, vec3_origin, damageType, spawnArgs.GetFloat( "deathWalkWraithDamage", "10" ), INVALID_JOINT ); + + CancelEvents( &EV_SpawnDeathWraith ); + PostEventMS( &EV_SpawnDeathWraith, 0 ); +} + +//============================================================================= +// +// hhPlayer::KilledDeathWraith +// +//============================================================================= + +void hhPlayer::KilledDeathWraith( void ) { + CancelEvents( &EV_SpawnDeathWraith ); + PostEventMS( &EV_SpawnDeathWraith, 0 ); +} + +//============================================================================= +// +// hhPlayer::DeathWraithEnergyArived +// +//============================================================================= + +void hhPlayer::DeathWraithEnergyArived(bool energyHealth) { + if (energyHealth) { + const char *healthAmount = spawnArgs.GetString("deathWraithHealthAmount"); + Give( "health", healthAmount ); + } + else { + Give( "ammo_spiritpower", spawnArgs.GetString( "deathWraithSpiritAmount" ) ); + } + + // Bump the deathwalk power up a bit so the player lowers faster + int power = GetDeathWalkPower(); + power += spawnArgs.GetInt( "deathWalkPowerEnergyBoost", "100" ); + SetDeathWalkPower( power ); +} + +//============================================================================= +// +// hhPlayer::Resurrect +// +//============================================================================= + +void hhPlayer::Resurrect( void ) { + + bDeathWalk = false; + + possessionFOV = 0.0f; + + PostEventSec( &EV_AllowDamage, 3 ); // Re-enable damage in 3 seconds + fl.noknockback = false; // Restore knockback ability + + // Cancel the impending translation to deathworld (only happens if the player resurrect cheats early) + CancelEvents( &EV_PrepareForDeathWorld ); + CancelEvents( &EV_EnterDeathWorld ); + CancelEvents( &EV_SpawnDeathWraith ); + + DisableEthereal(); + + // Reset the view overlay + playerView.SetViewOverlayTime( 0, true ); + playerView.SetViewOverlayColor( idVec4( 1.0f, 1.0f, 1.0f, 0.0f ) ); + + //rww - deathwalk position is restored from the player now. + RestorePlayerLocationFromDeathwalk(deathwalkLastOrigin, deathwalkLastBBoxAxis, deathwalkLastViewAxis[0], deathwalkLastViewAngles); + + deathwalkSoundController.StopSound( SND_CHANNEL_BODY, SND_CHANNEL_BODY2 ); + + ProcessEvent( &EV_RechargeHealth ); + ProcessEvent( &EV_RechargeRifleAmmo ); + + if (hud) { + hud->HandleNamedEvent("SwitchFromEthereal"); + } + + // TODO: Build in an autoimmune time? + lastResurrectTime = gameLocal.GetTime(); + + // Radius blast (doesn't damage enemies / objects, only shoves them back) + gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), this, this, this, this, spawnArgs.GetString("def_resurrect_damage") ); + + Show(); // Ensure that the player is visible when they resurrect + + // trigger the 'DeathWalkExited' entity + idEntity *dwe = gameLocal.FindEntity("dw_DeathWalkExited"); + if (dwe) { + dwe->PostEventMS(&EV_Activate, 0, this); + } + + // bg: Visual effect for returning from DeathWalk uses landing camera movement. + landChange = -10; + landTime = gameLocal.time; + + //rww - deactivate dw portal and remove cam target + idEntity *dwPortal = gameLocal.FindEntity( "dw_deathPortal" ); + if (dwPortal && dwPortal->IsActive()) { + dwPortal->cameraTarget = NULL; //reset the camera target + dwPortal->PostEventMS(&EV_Activate, 0, dwPortal); + } + idEntity *dwCamTarget = gameLocal.FindEntity( "dw_deathLocation" ); + if (dwCamTarget) { + dwCamTarget->PostEventMS(&EV_Remove, 50); //make sure this is removed after the portal has stopped. + } +} + +/* +============ +hhPlayer::IsWallWalking +============ +*/ +bool hhPlayer::IsWallWalking( void ) const { + return( physicsObj.IsWallWalking() ); +} + +/* +============ +hhPlayer::WasWallWalking +============ +*/ +bool hhPlayer::WasWallWalking( void ) const { + return( physicsObj.WasWallWalking() ); +} + +/* +============ +hhPlayer::MangleControls +============ +*/ +void hhPlayer::MangleControls( usercmd_t *cmd ) { + // When frozen in a cinematic, we want to restrict any movement and disallow many features + if (guiWantsControls.IsValid()) { + if (cmd->buttons & BUTTON_ATTACK_ALT) { + guiWantsControls->LockedGuiReleased(this); + guiWantsControls = NULL; // release + } + else { + guiWantsControls->PlayerControls(cmd); + } + cmd->forwardmove = 0; + cmd->rightmove = 0; + cmd->upmove = 0; + cmd->buttons &= ~(BUTTON_ATTACK|BUTTON_ZOOM|BUTTON_ATTACK_ALT); + cmd->impulse = 0; + } + else if (InCinematic()) { + cmd->forwardmove = 0; + cmd->rightmove = 0; + cmd->upmove = 0; + cmd->buttons &= ~(BUTTON_ATTACK|BUTTON_ZOOM|BUTTON_ATTACK_ALT); + cmd->impulse = 0; + } +} + +/* +============ +hhPlayer::GetPilotInput + +Called from PilotVehicleInterface +============ +*/ +void hhPlayer::GetPilotInput( usercmd_t& pilotCmds, idAngles& pilotViewAngles ) { + pilotCmds = gameLocal.usercmds[ entityNumber ]; + pilotViewAngles = DetermineViewAngles( pilotCmds, cmdAngles ); +} + +/* +============== +hhPlayer::Think + PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +============== +*/ +void hhPlayer::Think( void ) { + renderEntity_t *headRenderEnt; + + UpdatePossession(); + + UpdatePlayerIcons(); + + // latch button actions + oldButtons = usercmd.buttons; + + // grab out usercmd + usercmd_t oldCmd = usercmd; + usercmd = gameLocal.usercmds[ entityNumber ]; + buttonMask &= usercmd.buttons; + usercmd.buttons &= ~buttonMask; + + if ( gameLocal.inCinematic && gameLocal.skipCinematic ) { + return; + } + + //HUMANHEAD rww - keep air attacker timer refreshed + if (!AI_ONGROUND && airAttackerTime > gameLocal.time) { + airAttackerTime = gameLocal.time + 300; + } + + // HUMANHEAD pdm: Depending on our mode, might want to modify the input + MangleControls( &usercmd ); + // HUMANHEAD END + + // 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 ) { + usercmd.forwardmove = 0; + usercmd.rightmove = 0; + usercmd.upmove = 0; + } + + // 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 ); + } + + // zooming + if ( ( usercmd.buttons ^ oldCmd.buttons ) & BUTTON_ZOOM ) { + if ( ( usercmd.buttons & BUTTON_ZOOM ) && weapon.GetEntity() ) { + zoomFov.Init( gameLocal.time, 200.0f, CalcFov( false ), weapon.GetEntity()->GetZoomFov() ); + } else { + zoomFov.Init( gameLocal.time, 200.0f, zoomFov.GetCurrentValue( gameLocal.time ), DefaultFov() ); + } + } + + if ( g_fov.IsModified() ) { + idEntity *weaponEnt = weapon.GetEntity(); + if ( ! ( weaponEnt && + weaponEnt->IsType( hhWeaponZoomable::Type ) && + reinterpret_cast (weaponEnt)->IsZoomed() ) ) + { + GetZoomFov().Init( gameLocal.GetTime(), 0.0f, CalcFov(true), g_fov.GetInteger() ); + g_fov.ClearModified(); + } + } + + // 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.GetEntity() ) { + weapon.GetEntity()->SetPushVelocity( physicsObj.GetPushedLinearVelocity() ); + } + + EvaluateControls(); + + if ( !af.IsActive() ) { + AdjustBodyAngles(); + CopyJointsFromBodyToHead(); + } + + //HUMANHEAD: aob - added vehicle check. Vehicle moves us + if( !InVehicle() ) { + Move(); + } + //HUMANHEAD END + + if ( !g_stopTime.GetBool() ) { + //HUMANHEAD: aob - changed heath check to IsDead check. Player needs to touch triggers when deathwalking. + if ( !noclip && !spectating && !IsDead() && !IsHidden() ) { + TouchTriggers(); + } + + UpdateLighter(); + + // update GUIs, Items, and character interactions + UpdateFocus(); + + UpdateLocation(); + + // update player script + UpdateScript(); + + // service animations + if ( !spectating && !af.IsActive() && !gameLocal.inCinematic ) { + 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 + AI_PAIN = false; + } + + // 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 ) { + if (!gameLocal.isClient) { + UpdateSpectating(); + } + //HUMANHEAD: aob - changed heath check to IsDead check. Player needs to update weapon when deathwalking + } else if ( !IsDead() && !bFrozen ) { //HUMANHEAD bjk PCF (4-27-06) - no setting unnecessary weapon state + if ( !gameLocal.isClient || weapon.GetEntity()) { + UpdateWeapon(); + } + } + + if (InVehicle()) { + UpdateHud( GetVehicleInterfaceLocal()->GetHUD() ); + } + else { + UpdateHud( hud ); + } + + UpdateDeathSkin( false ); + + if ( gameLocal.isMultiplayer ) { + DrawPlayerIcons(); + } + + if ( head.GetEntity() ) { + headRenderEnt = head.GetEntity()->GetRenderEntity(); + } else { + headRenderEnt = NULL; + } + + if ( gameLocal.isMultiplayer || g_showPlayerShadow.GetBool() ) { + renderEntity.suppressShadowInViewID = 0; + if ( headRenderEnt ) { + headRenderEnt->suppressShadowInViewID = 0; + } + } else { + renderEntity.suppressShadowInViewID = entityNumber+1; + if ( headRenderEnt ) { + headRenderEnt->suppressShadowInViewID = entityNumber+1; + } + } + // never cast shadows from our first-person muzzle flashes + renderEntity.suppressShadowInLightID = LIGHTID_VIEW_MUZZLE_FLASH + entityNumber; + if ( headRenderEnt ) { + headRenderEnt->suppressShadowInLightID = LIGHTID_VIEW_MUZZLE_FLASH + entityNumber; + } + + if ( !g_stopTime.GetBool() ) { + UpdateAnimation(); + Present(); + UpdateDamageEffects(); + if (gameLocal.isMultiplayer) { //rww + UpdateWounds(); + } + LinkCombat(); + playerView.CalculateShake(); + } + + if ( g_showEnemies.GetBool() ) { + idActor *ent; + int num = 0; + for( ent = enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) { + gameLocal.Printf( "enemy (%d)'%s'\n", ent->entityNumber, ent->name.c_str() ); + gameRenderWorld->DebugBounds( colorRed, ent->GetPhysics()->GetBounds().Expand( 2 ), ent->GetPhysics()->GetOrigin() ); + num++; + } + gameLocal.Printf( "%d: enemies\n", num ); + } +} + +/* +============== +hhPlayer::AdjustBodyAngles +============== +*/ +void hhPlayer::AdjustBodyAngles( void ) { + if (InVehicle()) { + return; + } + + if (bClampYaw) { //rww - for slabs + //first, clamp the viewangles while bound to a slab + if (maxRelativePitch >= 0.0f) { + if (untransformedViewAngles.pitch > maxRelativePitch) { + untransformedViewAngles.pitch = maxRelativePitch; + SetViewAngles(untransformedViewAngles); + } + else if (untransformedViewAngles.pitch < -maxRelativePitch) { + untransformedViewAngles.pitch = -maxRelativePitch; + SetViewAngles(untransformedViewAngles); + } + } + + animator.SetJointAxis( hipJoint, JOINTMOD_WORLD, mat3_identity ); //no leg offset while slabbed + +#if 0 //old headlook code + if (head.IsValid() && head.GetEntity()) { //has a head + idVec3 origin; + idMat3 axis; + + head->GetPhysics()->Evaluate(gameLocal.time-gameLocal.previousTime, gameLocal.time); //make sure it is bolted up to date + + if (GetMasterPosition(origin, axis)) { + idAngles masterAngles = axis.ToAngles(); //bound angles + idAngles ang; + idMat3 rot; + + //determine our "look" angles + ang.yaw = viewAngles.yaw-masterAngles.yaw; + ang.pitch = viewAngles.pitch; + ang.roll = 0.0f; + + ang = ang.Normalize180(); + ang *= 0.5f; //scale the angles down so the head doesn't go completely sideways or anything + + //set the yaw only + animator.SetJointAxis(headJoint, JOINTMOD_WORLD, idAngles(0.0f, ang.yaw, 0.0f).ToMat3()); + ang.yaw = 0.0f; + + hhMath::BuildRotationMatrix(DEG2RAD(-ang.pitch), 0, rot); //create a rotation matrix for the pitch + + //get the local axis to multiply it by the pitch rotation matrix + animator.GetJointLocalTransform(headJoint, gameLocal.time, origin, axis); + + //use the multiplied axis + animator.SetJointAxis(headJoint, JOINTMOD_LOCAL, rot*axis); //point head in proper direction + } + } +#endif + } + else { +#if 0 //old headlook code + //rww - reset head joint override when not on a slab + animator.SetJointAxis( headJoint, JOINTMOD_LOCAL, idAngles( 0.0f, 0.0f, 0.0f ).ToMat3() ); +#endif + idPlayer::AdjustBodyAngles(); + } +} + +/* +============== +hhPlayer::Move + PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +============== +*/ +void hhPlayer::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( GetStepHeight() );//HUMANHEAD: aob + physicsObj.SetMaxJumpHeight( pm_jumpheight.GetFloat() ); + + if ( noclip ) { + physicsObj.SetContents( 0 ); + physicsObj.SetMovementType( PM_NOCLIP ); + } else if ( spectating ) { + physicsObj.SetContents( 0 ); + physicsObj.SetMovementType( PM_SPECTATOR ); + } else if ( bInDeathwalkTransition ) { // In between life and death, don't allow ragdoll to collide with player + physicsObj.SetContents( 0 ); + physicsObj.SetMovementType( PM_NORMAL ); + } else if ( IsDead() ) { // HUMANHEAD cjr: Replaced health <= 0 with IsDead() call for deathwalk override + if (gameLocal.isMultiplayer) { // HUMANHEAD rww - contents 0 because we use a dead body proxy in mp + physicsObj.SetContents(0); + } + else { + physicsObj.SetContents( CONTENTS_CORPSE | CONTENTS_MONSTERCLIP ); + } //HUMANHEAD END + physicsObj.SetMovementType( PM_DEAD ); + } else if ( gameLocal.inCinematic || gameLocal.GetCamera() || privateCameraView ) { + physicsObj.SetContents( CONTENTS_BODY ); + physicsObj.SetMovementType( PM_FREEZE ); + } else if ( bFrozen && !IsSpiritOrDeathwalking() ) { // HUMANHEAD: freeze support + physicsObj.SetContents( CONTENTS_BODY ); + physicsObj.SetMovementType( PM_FREEZE ); + } else { + physicsObj.SetContents( CONTENTS_BODY ); + physicsObj.SetMovementType( PM_NORMAL ); + } + + if ( spectating ) { + physicsObj.SetClipMask( MASK_SPECTATOR ); + } else if ( IsDead() ) { // HUMANHEAD cjr: Replaced health <= 0 with IsDead() call for deathwalk override + physicsObj.SetClipMask( MASK_DEADSOLID ); + // HUMANHEAD cjr: allow spirit walk + } else if ( IsSpiritOrDeathwalking() ) { + physicsObj.SetClipMask( MASK_SPIRITPLAYER ); + // HUMANHEAD END + } else { + physicsObj.SetClipMask( MASK_PLAYERSOLID ); + } + + physicsObj.SetDebugLevel( g_debugMove.GetBool() ); + physicsObj.SetPlayerInput( usercmd, viewAngles ); + + //HUMANHEAD: aob - moved down a few lines + // FIXME: physics gets disabled somehow + //BecomeActive( TH_PHYSICS ); + //RunPhysics(); + //HUMANHEAD END + + // update our last valid AAS location for the AI + SetAASLocation(); + + if ( spectating ) { + newEyeOffset = 0.0f; + // HUMANHEAD cjr: Replaced health <= 0 with IsDead() call for deathwalk override + } else if ( IsDead() ) { + newEyeOffset = pm_deadviewheight.GetFloat(); + } else if ( physicsObj.IsCrouching() ) { + newEyeOffset = pm_crouchviewheight.GetFloat(); + } else if ( GetBindMaster() && GetBindMaster()->IsType( idAFEntity_Vehicle::Type ) ) { + newEyeOffset = 0.0f; + } else { + newEyeOffset = pm_normalviewheight.GetFloat(); + } + + if ( EyeHeight() != newEyeOffset ) { + //AOB: camera does smoothing + SetEyeHeight( newEyeOffset ); + } + + // HUMANHEAD aob + float camRotScale = p_camRotRateScale.GetFloat(); + //rww - if on a wallwalk mover, increase the scale + if (GetPhysics() == &physicsObj && physicsObj.GetGroundTrace().fraction < 1.0f) { + const trace_t &grTr = physicsObj.GetGroundTrace(); + if (grTr.c.entityNum >= 0 && grTr.c.entityNum < MAX_GENTITIES && gameLocal.entities[grTr.c.entityNum] && gameLocal.entities[grTr.c.entityNum]->IsType(hhMover::Type) && grTr.c.material && grTr.c.material->GetSurfaceType() == SURFTYPE_WALLWALK) { + camRotScale *= 5.0f; + } + } + cameraInterpolator.Setup( camRotScale, IT_VariableMidPointSinusoidal );//Also set in constructor + + // moved here to allow weapons physics get correct eyeOffset + BecomeActive( TH_PHYSICS ); //rww - prevent the bug that id so lovingly left for us + RunPhysics(); + // HUMANHEAD END + + // HUMANHEAD pdm: Experimental: allow player to jump up onto wallwalk +#define WALLWALK_EXPERIMENT 0 +#if WALLWALK_EXPERIMENT + if (!physicsObj.IsWallWalking() && physicsObj.HasJumped()) { + trace_t trace; + memset(&trace, 0, sizeof(trace)); + idVec3 start, end; + start = physicsObj.GetOrigin(); + end = start + idVec3(0,0,100); //fixme: jumpheight + if (gameLocal.clip.TracePoint( trace, start, end, MASK_SOLID, this )) { + if (gameLocal.GetMatterType(trace, NULL) == SURFTYPE_WALLWALK) { + gameLocal.Printf("Trying to flip over for wallwalk\n"); + + // Clear velocity, so won't miss wallwalk above + physicsObj.SetLinearVelocity(vec3_origin); + + SetGravity(-trace.c.normal * DEFAULT_GRAVITY); + ProcessEvent( &EV_ShouldRemainAlignedToAxial, (int)false ); + ProcessEvent( &EV_OrientToGravity, (int)true ); + + physicsObj.IsWallWalking( true ); + +/* idVec3 flippedOrigin = trace.endpos; + idMat3 flippedAxis; + flippedAxis[0] = GetAxis()[0]; + flippedAxis[2] = trace.c.normal; + flippedAxis[1] = flippedAxis[0].Cross(flippedAxis[2]); + +// cameraInterpolator.SetInterpolationType(flags); +// cameraInterpolator.SetTargetEyeOffset(idealEyeOffset, flags); +// cameraInterpolator.SetTargetPosition(idealPosition, flags); + + // Flip the physics object + physicsObj.SetOrigin(flippedOrigin); + physicsObj.SetAxis(flippedAxis); + physicsObj.CheckWallWalk(true);*/ + } + } + } +#endif + // HUMANHEAD END + + if ( noclip || gameLocal.inCinematic ) { + AI_CROUCH = false; + AI_ONGROUND = false; + AI_ONLADDER = false; + AI_JUMP = false; + AI_REALLYFALL = true; //HUMANHEAD rww - well, it fits with ONGROUND false + } else { + AI_CROUCH = physicsObj.IsCrouching(); + AI_ONGROUND = physicsObj.HasGroundContacts(); + AI_ONLADDER = physicsObj.OnLadder(); + AI_JUMP = physicsObj.HasJumped(); + //HUMANHEAD rww + if (!physicsObj.IsInwardGravity()) { + AI_REALLYFALL = !AI_ONGROUND; + } + else if (!AI_ONGROUND) { //for inward gravity zones, do an extra ground check when in-air + AI_REALLYFALL = !physicsObj.ExtraGroundCheck(); + } + //HUMANHEAD END + + // 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::Type ) ) { + 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_walkspeed.GetFloat(); + } else { + // give em a push in the direction they're going + vel *= 1.1f; + } + physicsObj.SetLinearVelocity( vel ); + } + } + + if ( AI_JUMP ) { + // bounce the view weapon + 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; + } + + BobCycle( pushVelocity ); + if ( !noclip ) { // HUMANHEAD: Only crashland if not spiritwalking or not deathwalking + CrashLand( oldOrigin, oldVelocity ); + } + + //HUMANHEAD: aob - put this into helper func + if (!gameLocal.isClient) { + if( IsWallWalking() && !WasWallWalking() ) { + wallwalkSoundController.StartSound( SND_CHANNEL_WALLWALK, SND_CHANNEL_WALLWALK2, SSF_LOOPING, false ); + } else if( !IsWallWalking() && WasWallWalking() ) { + wallwalkSoundController.StopSound( SND_CHANNEL_WALLWALK, SND_CHANNEL_WALLWALK2, false ); + } + } + physicsObj.WasWallWalking( IsWallWalking() ); //rww - moved here + //HUMANHEAD END +} + +/* +=============== +hhPlayer::ShouldTouchTrigger +=============== +*/ +bool hhPlayer::ShouldTouchTrigger( idEntity* entity ) const { + if( !entity ) { + return false; + } + + if ( entity->fl.onlySpiritWalkTouch ) { // cjr - Trigger can only be touched by spirit + return IsSpiritWalking(); + } + + if( IsSpiritOrDeathwalking() && !entity->fl.allowSpiritWalkTouch ) { // Trigger can be touched by either physical or spirit + return false; // jrm - reversed logic + } + + return true; +} + +/* +=============== +hhPlayer::HandleSingleGuiCommand +=============== +*/ +bool hhPlayer::HandleSingleGuiCommand(idEntity *entityGui, idLexer *src) { + + idToken token; + + if (!src->ReadToken(&token)) { + return false; + } + + if (token == ";") { + return false; + } + + if (token.Icmp("guilockplayer") == 0) { + //Now locked in place: + // manglecontrols() will route controls to current gui focus + if (entityGui->IsType(hhConsole::Type)) { + guiWantsControls = static_cast(entityGui); + } + return true; + } + + src->UnreadToken(&token); + return idPlayer::HandleSingleGuiCommand(entityGui, src); +} + +void hhPlayer::SetOverlayGui(const char *guiName) { + if (guiName && *guiName && guiOverlay == NULL) { + guiOverlay = uiManager->FindGui(guiName, true); + if (guiOverlay) { + guiOverlay->Activate(true, gameLocal.time); + } + } + else { + guiOverlay = NULL; + } +} + +/* +============ +hhPlayer::ForceWeapon +nla: used to instantly force the spirit weapon, without lowering and raising +============ +*/ +void hhPlayer::ForceWeapon( int weaponNum ) { + const char * weaponDef; + hhWeapon * newWeapon; + + if (spectating) { //rww - if spectating this is bad. + gameLocal.Error("hhPlayer::ForceWeapon called on spectator. (client %i)", entityNumber); + } + + // HUMANHEAD mdl: Special case - coming out of spirit mode when no physical weapon is available + if ( weaponNum == -1 ) { + idealWeapon = 0; + currentWeapon = -1; + return; + } + + if ( weaponNum < 1 ) { + gameLocal.Warning( "Error: Illegal weapon num passed: %d\n", weaponNum ); + } + + weaponDef = GetWeaponName( weaponNum ); + + newWeapon = SpawnWeapon( weaponDef ); + + weapon = newWeapon; + + idealWeapon = currentWeapon = weaponNum; + + animPrefix = weaponDef; + animPrefix.Strip( "weaponobj_" ); +} + + +/* +============ +hhPlayer::ForceWeapon +============ +*/ +void hhPlayer::ForceWeapon( hhWeapon *newWeapon ) { + int weaponNum; + + + weapon = newWeapon; + + weaponNum = GetWeaponNum( weapon->GetDict()->GetString( "classname" ) ); + + idealWeapon = currentWeapon = weaponNum; + +} + +/* +=========== +hhPlayer::Teleport + +HUMANHEAD cjr: Now calls TeleportNoKillBox, then applies a kill box +============ +*/ +void hhPlayer::Teleport( const idVec3& origin, const idAngles& angles, idEntity *destination ) { + //pdm - converted this to call our other version so it doesn't unalign the clip model + TeleportNoKillBox( origin, mat3_identity, angles.ToForward(), angles ); + + // mdl: Moved here from TeleportNoBox, so player can't avoid falling damage by toggling spirit mode quickly + physicsObj.SetLinearVelocity( vec3_origin ); + physicsObj.SetKnockBack( 250 ); // Slow the player down after a teleport for a moment + + // kill anything at the new position + gameLocal.KillBox( this, destination != NULL ); + + teleportEntity = destination; +} + +/* +=========== +hhPlayer::Teleport + +HUMANHEAD aob: Needed for coming back from deathwalk +============ +*/ +void hhPlayer::Teleport( const idVec3& origin, const idMat3& bboxAxis, const idVec3& viewDir, const idAngles& newUntransformedViewAngles, idEntity *destination ) { + TeleportNoKillBox( origin, bboxAxis, viewDir, newUntransformedViewAngles ); + + // kill anything at the new position + gameLocal.KillBox( this, destination != NULL ); + + teleportEntity = destination; +} + +/* +=========== +hhPlayer::TeleportNoKillBox + +HUMANHEAD cjr: Called from SpiritProxy +============ +*/ +void hhPlayer::TeleportNoKillBox( const idVec3& origin, const idMat3& bboxAxis, const idVec3& viewDir, const idAngles& newUntransformedViewAngles ) { + DisableIK(); + + SetOrientation( origin, bboxAxis, viewDir, newUntransformedViewAngles ); + + CancelEvents( &EV_ResetGravity ); + ProcessEvent( &EV_ResetGravity ); // Guarantee that gravity is instantly reset after this teleport + + EnableIK(); +} + +/* +=========== +hhPlayer::TeleportNoKillBox + +HUMANHEAD cjr: Teleport, but don't telefrag anything +============ +*/ + +void hhPlayer::TeleportNoKillBox( const idVec3& origin, const idMat3& bboxAxis ) { + TeleportNoKillBox( origin, bboxAxis, GetAxis()[0], GetUntransformedViewAngles() ); +} + +/* +===================== +hhPlayer::Possess + +//HUMANHEAD: cjr - this is the entry point for possession. +===================== +*/ +void hhPlayer::Possess( idEntity* possessor ) { + bPossessed = true; + + /* TODO: + - needs a new spirit proxy (a little hidden object just as storage that is linked to the possessed human) + - disallow the player from returning until the possessed tommy has been "killed" + + + possessed tommy: + - attacks whatever + - health ticking down -- when it reaches zero, ragdoll and instantly kill the spirit player + - if killed by the spirit bow, then the wraith is removed (seperate health?) + */ + + // If we're spirit walking when we're possessed, snap back to our body for a moment, then get thrown back out. + //TODO this isn't ideal + if ( IsSpiritOrDeathwalking() ) { + DisableEthereal(); + } + + // Must allow spirit walking at this point + HH_ASSERT( bAllowSpirit ); + + // Force the player into spirit and thrust them backwards + StartSpiritWalk( true, true ); + + // test: spawn in a possessed version + // need: effects here...flash or whatever + possessedTommy = (hhPossessedTommy *)gameLocal.SpawnObject( "monster_possessed_tommy", NULL ); + + if ( possessedTommy.IsValid() ) { // Copy player stats to the possessed Tommy + /*if ( IsSpiritOrDeathwalking() ) { //TODO mdl: Remove this if we go with wraiths knocking players back to their body + // This should means we were called by + possessedTommy->SetOrigin( spiritProxy->GetOrigin() ); + possessedTommy->SetAxis( spiritProxy->GetAxis() ); + } else*/ { + possessedTommy->SetOrigin( GetOrigin() ); + possessedTommy->SetAxis( GetAxis() ); + } + possessedTommy->SetPossessedProxy( spiritProxy.GetEntity() ); + } +} + +/* +===================== +hhPlayer::Unpossess + +//HUMANHEAD: cjr +=====================*/ + +void hhPlayer::Unpossess() { + bPossessed = false; + + possessedTommy = NULL; +/* + idDict args; + idEntity *ent; + + playerView.SetViewOverlayMaterial( NULL ); + possessionFOV = 0; // Restore the original FOV + + // Spawn a wraith to fly out of the player's origin + args.Clear(); + args.SetVector( "origin", GetOrigin() + GetEyePosition() + GetAxis()[0] * 50 ); + args.SetMatrix( "rotation", GetAxis() ); +//TODO: Externalize this monster_wraith + args.Set( "classname", "monster_wraith" ); + + gameLocal.SpawnEntityDef( args, &ent ); + ((hhWraith *)ent)->SetEnemy( this ); + + // TODO: Unpossessed by portalling, so kill the wraith +*/ +} + +//============================================================================= +// +// hhPlayer::CanBePossessed +// +// Players can only be possessed if they are are not currently possessed +//============================================================================= + +bool hhPlayer::CanBePossessed( void ) { + if ( godmode || noclip || !bAllowSpirit ) { // Don't possess if in god mode or noclipping + return false; + } + + return !(IsPossessed() || InVehicle() || IsSpiritOrDeathwalking() ); +} + +//============================================================================= +// +// hhPlayer::PossessKilled +// +// Killed when possessed (spirit power ran out) +// +// - Ragdoll the possessed body +// - Return to the location of the possessed tommy and die +//============================================================================= + +void hhPlayer::PossessKilled( void ) { + if ( possessedTommy.IsValid() ) { + possessedTommy->PostEventMS( &EV_Remove, 0 ); + } + + Unpossess(); + StopSpiritWalk(); + Kill( 0, 0 ); // FIXME: Some other way to kill self (instead of suicide?) +} + +//============================================================================= +// +// hhPlayer::Portalled +// +// Player just portalled +//============================================================================= + +void hhPlayer::Portalled( idEntity *portal ) { + if ( talon.IsValid() && talon->GetBindMaster() != this ) { // If Talon isn't bound to Tommy, portal him with the player + talon->Portalled( portal ); + } + + if (gameLocal.isClient) { //HUMANHEAD rww - compensate for angle jump + bBufferNextSnapAngles = true; + smoothedAngles = viewAngles; + } + smoothedFrame = 0; //HUMANHEAD rww - skip smoothing on the portalled frame + walkIK.InvalidateHeights(); //HUMANHEAD rww - don't try to interpolate ik positions between two equal planes on either side of a portal + if (gameLocal.isServer) { //HUMANHEAD rww - send an unreliable event in the coming snapshot + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init(msgBuf, sizeof(msgBuf)); + msg.WriteBits(gameLocal.GetSpawnId(portal), 32); + ServerSendEvent(EVENT_PORTALLED, &msg, false, -1, -1, true); //only send it to the client who portalled + } +} + +/* +================ +hhPlayer::UpdateModelTransform +================ +*/ +void hhPlayer::UpdateModelTransform( void ) { + idVec3 origin; + idMat3 axis; + + if( GetPhysicsToVisualTransform(origin, axis) ) { + //HUMANHEAD: aob + GetRenderEntity()->axis = axis; + idVec3 absOrigin = TransformToPlayerSpaceNotInterpolated( origin ); //rww - don't use interpolated origin + GetRenderEntity()->origin = absOrigin; + //HUMANHEAD END + } else { + //HUMANHEAD: aob + GetRenderEntity()->axis = GetAxis(); + GetRenderEntity()->origin = GetOrigin(); + //HUMANHEAD END + } +} + +/* +==================== +hhPlayer::CalcFov + +Fixed fov at intermissions, otherwise account for fov variable and zooms. +Takes possession FOV into account + +HUMANHEAD cjr +==================== +*/ +float hhPlayer::CalcFov( bool honorZoom ) { + float fov; + + // HUMANHEAD mdl: Refactored this to work properly with possessionFOV. Being zoomed in will ignore possessionFOV, however. + idEntity *weaponEnt = weapon.GetEntity(); + if ( possessionFOV > 0.0f && + ! ( weaponEnt && + weaponEnt->IsType( hhWeaponZoomable::Type ) && + reinterpret_cast (weaponEnt)->IsZoomed() ) ) + { + fov = possessionFOV; + } else if ( IsDeathWalking() ) { + fov = spawnArgs.GetFloat("deathwalkFOV", "90"); + } else if ( pm_thirdPerson.GetBool() ) { + fov = g_fov.GetFloat(); + } else if ( InCinematic() ) { + fov = cinematicFOV.GetCurrentValue(gameLocal.time); + } else { + fov = zoomFov.GetCurrentValue(gameLocal.time); + } + + //HUMANHEAD: aob + fov = hhMath::ClampFloat( 1.0f, 179.0f, fov ); + //HUMANHEAD END + + return fov; +} + +/* +=============== +hhPlayer::EnterVehicle +=============== +*/ +void hhPlayer::EnterVehicle( hhVehicle* vehicle ) { + if (!gameLocal.isClient) { //HUMANHEAD PCF rww 05/04/06 - do not do the check on the client, just listen to what the snapshot says. + if (!vehicle->WillAcceptPilot(this)) { + return; + } + } + + // Cancel any pending damage allowing events, since we'll be turning damage off + CancelEvents(&EV_AllowDamage); + + // Set shuttle view + if (entityNumber == gameLocal.localClientNum) { //rww + renderSystem->SetShuttleView( true ); + } + + // Move eye to proper height + SetEyeHeight( vehicle->spawnArgs.GetFloat("pilot_eyeHeight") ); + + // Turn off any illegal behaviors + if( IsSpiritWalking() ) { + StopSpiritWalk(); + } + + LighterOff(); + + // nla - Remove any offset cause by jumping. (Fixes the jumping and getting in the shuttle bug) + SetViewBob( vec3_origin ); + + // CJR: Inform Talon that the player has entered a vehicle + if( talon.IsValid() ) { + talon->OwnerEnteredVehicle(); + } + + ShouldRemainAlignedToAxial( false ); + + cameraInterpolator.SetInterpolationType( IT_None ); + SetOrientation( GetOrigin(), mat3_identity, vehicle->GetAxis()[0], vehicle->GetAxis()[0].ToAngles() ); + //Need to clear untransformedViewAxis so cameraInterpolator doesn't do any unnessacary transforms + SetUntransformedViewAxis( mat3_identity ); + + //Hack + cameraInterpolator.Reset( GetOrigin(), mat3_identity[2], EyeHeightIdeal() ); + + idPlayer::EnterVehicle( vehicle ); +} + +/* +=============== +hhPlayer::ExitVehicle + +This should only be called from vehicle +=============== +*/ +void hhPlayer::ExitVehicle( hhVehicle* vehicle ) { + + // Allow model in player's view again + if (vehicle) { + vehicle->GetRenderEntity()->suppressSurfaceInViewID = 0; + } + + // Set shuttle view off + if (entityNumber == gameLocal.localClientNum) { //rww + renderSystem->SetShuttleView( false ); + } + + ShouldRemainAlignedToAxial( true ); + + // CJR: Inform Talon that the player has left a vehicle + if( talon.IsValid() ) { + talon->OwnerExitedVehicle(); + } + + idPlayer::ExitVehicle( vehicle ); + + buttonMask |= BUTTON_ATTACK_ALT; //HUMANHEAD bjk + + // Reset the animPrefix + animPrefix = spawnArgs.GetString( va( "def_weapon%d", currentWeapon ) ); + animPrefix.Strip( "weaponobj_" ); + + if (vehicle) { + SetOrientation( GetOrigin(), mat3_identity, vehicle->GetAxis()[0], vehicle->GetAxis()[0].ToAngles().Normalize180() ); + } + cameraInterpolator.SetInterpolationType( IT_VariableMidPointSinusoidal ); +} + +// +// ResetClipModel() +// +// HUMANHEAD: aob +// +void hhPlayer::ResetClipModel() { + //Needed for touching triggers. Our bbox is its original size. + SetClipModel(); +} + +/* +=============== +hhPlayer::BecameBound +=============== +*/ +void hhPlayer::BecameBound(hhBindController *b) { + if( !gameLocal.isMultiplayer && weapon.IsValid() ) { //rww - can use weapon while bound in mp + weapon->Hide(); + } +} + +/* +=============== +hhPlayer::BecameUnbound +=============== +*/ +void hhPlayer::BecameUnbound(hhBindController *b) { + if( !gameLocal.isMultiplayer && weapon.IsValid() ) { //rww - can use weapon while bound in mp + weapon->Show(); + } +} + +void hhPlayer::GetLocationText( idStr &locationString ) { + idLocationEntity* locationEntity = gameLocal.LocationForPoint( GetEyePosition() ); + if( locationEntity ) { + locationString = locationEntity->GetLocation(); + } + else { + locationString = common->GetLanguageDict()->GetString( "#str_02911" ); + } +} + +void hhPlayer::UpdateLocation( void ) { + if( hud ) { + hud->SetStateBool("showlocations", developer.GetBool()); + if (developer.GetBool()) { + idStr locationString; + GetLocationText(locationString); + hud->SetStateString( "location", locationString.c_str() ); + hud->SetStateInt( "areanum", gameRenderWorld->PointInArea( GetEyePosition() )); + } + } +} + +/* +=============== +hhPlayer::FillDebugVars +=============== +*/ +void hhPlayer::FillDebugVars(idDict *args, int page) { + idStr text; + + switch(page) { + case 1: + args->SetInt("deathpower", GetDeathWalkPower()); + args->SetInt("spiritpower", GetSpiritPower()); + args->SetBool("AI_CROUCH", AI_CROUCH != 0); + args->SetBool("AI_ONGROUND", AI_ONGROUND != 0); + args->SetBool("AI_JUMP", AI_JUMP != 0); + args->SetBool("AI_SOFTLANDING", AI_SOFTLANDING != 0); + args->SetBool("AI_HARDLANDING", AI_HARDLANDING != 0); + args->SetBool("AI_FORWARD", AI_FORWARD != 0); + args->SetBool("AI_BACKWARD", AI_BACKWARD != 0); + args->SetBool("AI_STRAFE_LEFT", AI_STRAFE_LEFT != 0); + args->SetBool("AI_STRAFE_RIGHT", AI_STRAFE_RIGHT != 0); + args->SetBool("AI_ATTACK_HELD", AI_ATTACK_HELD != 0); + args->SetBool("AI_WEAPON_FIRED", AI_WEAPON_FIRED != 0); + args->SetBool("AI_JUMP", AI_JUMP != 0); + args->SetBool("AI_DEAD", AI_DEAD != 0); + args->SetBool("AI_PAIN", AI_PAIN != 0); + args->SetBool("AI_RELOAD", AI_RELOAD != 0); + args->SetBool("AI_TELEPORT", AI_TELEPORT != 0); + args->SetBool("AI_TURN_LEFT", AI_TURN_LEFT != 0); + args->SetBool("AI_TURN_RIGHT", AI_TURN_RIGHT != 0); + args->SetBool("AI_ASIDE", AI_ASIDE != 0); + args->SetBool("AI_REALLYFALL", AI_REALLYFALL != 0); //HUMANHEAD rww + args->SetBool("HasGroundContacts", physicsObj.HasGroundContacts()); + args->Set("animPrefix", animPrefix.c_str()); + break; + case 2: + args->Set("Physics Axis", GetPhysics()->GetAxis().ToAngles().ToString()); + args->Set("viewAxis", viewAxis.ToAngles().ToString()); + args->Set("cmdAngles", cmdAngles.ToString()); + args->Set("deltaViewAngles", GetDeltaViewAngles().ToString() ); + args->Set("viewAngles", viewAngles.ToString()); + args->Set("untransAngles", GetUntransformedViewAngles().ToString()); + args->Set("xPhysics Axis", GetPhysics()->GetAxis().ToString()); + args->Set("xviewAxis", viewAxis.ToString()); + args->Set("idealLegsYaw", va("%.1f", idealLegsYaw)); + args->SetInt("usercmd.forwardmove", usercmd.forwardmove); + args->SetInt("usercmd.rightmove", usercmd.rightmove); + args->SetInt("legsForward", legsForward); + + switch(physicsObj.GetWaterLevel()) { + case WATERLEVEL_NONE: text = "none"; break; + case WATERLEVEL_FEET: text = "feet"; break; + case WATERLEVEL_WAIST: text = "waist"; break; + case WATERLEVEL_HEAD: text = "head"; break; + } + args->Set("waterlevel", text); + text = collisionModelManager->ContentsName(physicsObj.GetWaterType()); + args->Set("watertype", text); + break; + case 3: + break; + } + idPlayer::FillDebugVars(args, page); +} + +// +// GetAimPosition() +// +idVec3 hhPlayer::GetAimPosition() const { + return GetEyePosition(); +} + +/* +=============== +hhPlayer::WriteToSnapshot +=============== +*/ +void hhPlayer::WriteToSnapshot( idBitMsgDelta &msg ) const { + bool vehControlling = vehicleInterfaceLocal.ControllingVehicle(); + + msg.WriteBits(vehControlling, 1); + + //rww - our stuff is now intermingled with id's for more ideal send ordering + physicsObj.WriteToSnapshot( msg, vehControlling ); + + if (!vehControlling) { + //not syncing these causes denormalization/nan issues, FIXME + msg.WriteFloat(untransformedViewAngles[0]); + msg.WriteFloat(untransformedViewAngles[1]); + msg.WriteFloat(untransformedViewAngles[2]); + idCQuat q = untransformedViewAxis.ToCQuat(); + msg.WriteFloat(q.x); + msg.WriteFloat(q.y); + msg.WriteFloat(q.z); + + //rww - write cameraInterpolator + cameraInterpolator.WriteToSnapshot(msg, this); + + //still need delta angles + //msg.WriteDeltaFloat( 0.0f, deltaViewAngles[0] ); + //msg.WriteDeltaFloat( 0.0f, deltaViewAngles[1] ); + //msg.WriteDeltaFloat( 0.0f, deltaViewAngles[2] ); + } +#if 0 //for debugging differences in snapshot + else { + msg.WriteFloat(0.0f); + msg.WriteFloat(0.0f); + msg.WriteFloat(0.0f); + msg.WriteFloat(0.0f); + msg.WriteFloat(0.0f); + msg.WriteFloat(0.0f); + + msg.WriteFloat(0.0f); + msg.WriteFloat(0.0f); + msg.WriteFloat(0.0f); + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteFloat(0.0f, 4, 4); + + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteFloat(0.0f, 4, 4); + + msg.WriteFloat(0.0f); + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteFloat(0.0f, 4, 4); + + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteDeltaFloat(0.0f, 0.0f); + } +#endif + + msg.WriteDeltaFloat( 0.0f, deltaViewAngles[0] ); + msg.WriteDeltaFloat( 0.0f, deltaViewAngles[1] ); + msg.WriteDeltaFloat( 0.0f, deltaViewAngles[2] ); + + msg.WriteShort( health ); + msg.WriteBits( gameLocal.ServerRemapDecl( -1, DECL_ENTITYDEF, 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( weapon.GetSpawnId(), 32 ); + msg.WriteBits( spectator, idMath::BitsForInteger( MAX_CLIENTS ) ); + msg.WriteBits( lastHitToggle, 1 ); + msg.WriteBits( weaponGone, 1 ); + WriteBindToSnapshot( msg ); +//===================END OF ID DATA + msg.WriteShort(inventory.maxHealth); //rww - since maxhealth can go above 100 in mp now + + msg.WriteBits( spiritProxy.GetSpawnId(), 32 ); + + //rww - more spiritwalk stuff + msg.WriteBits(lastWeaponSpirit, 32); + msg.WriteBits(bSpiritWalk, 1); + + //not needed anymore + /* + msg.WriteBits( bShowProgressBar, 1 ); + msg.WriteFloat( progressBarValue ); + msg.WriteBits( progressBarState, 2 ); // possible values [0..2] + */ + + //rww - send ammo for current weapon - this is because our weapon routines rely on if we have ammo + //in scripts and so on, and will be predicted wrong for other players (we get our own ammo in the playerstate) + //rwwFIXME: if we have proper weapon switching when you run out of ammo will this actually be necessary? + //i don't think it's all that costly, but still. + if (weapon.IsValid()) { + ammo_t ammoType = weapon->GetAmmoType(); + if (ammoType > 0) { + msg.WriteBits(inventory.ammo[ammoType], ASYNC_PLAYER_INV_AMMO_BITS); + } + else { + msg.WriteBits(0, ASYNC_PLAYER_INV_AMMO_BITS); + } + ammoType = weapon->GetAltAmmoType(); + if (ammoType > 0) { + msg.WriteBits(inventory.ammo[ammoType], ASYNC_PLAYER_INV_AMMO_BITS); + } + else { + msg.WriteBits(0, ASYNC_PLAYER_INV_AMMO_BITS); + } + } + else { + msg.WriteBits(0, ASYNC_PLAYER_INV_AMMO_BITS); + msg.WriteBits(0, ASYNC_PLAYER_INV_AMMO_BITS); + } + + //need to sync buttonMask since we're using it for some state-based things + msg.WriteBits(buttonMask, 8); + + msg.WriteFloat(EyeHeight()); + + //HUMANHEAD PCF rww 05/04/06 - do not sync AI_VEHICLE, it is now based purely on the clientside + //enter/exit of vehicles, with the vehControlling stack bool determining if we should be in a + //vehicle on the client or not. + //msg.WriteBits(AI_VEHICLE, 1); + + //rww - hand stuff + //weaponHandState.WriteToSnapshot(msg); + msg.WriteBits(hand.GetSpawnId(), 32); + msg.WriteBits(handNext.GetSpawnId(), 32); + + //jsh - vehicle stuff + msg.WriteBits( vehicleInterfaceLocal.GetVehicleSpawnId(), 32 ); + msg.WriteBits( vehicleInterfaceLocal.GetHandSpawnId(), 32 ); + //vehicleInterfaceLocal.GetWeaponHandState()->WriteToSnapshot(msg); + + //rww - lighter sync validation + msg.WriteBits((lighterHandle != -1), 1); + + //rww - tractor + msg.WriteBits(fl.isTractored, 1); + + spiritwalkSoundController.WriteToSnapshot(msg); + //deathwalkSoundController.WriteToSnapshot(msg); + wallwalkSoundController.WriteToSnapshot(msg); + + //HUMANHEAD rww - leechgun energy ammo + //note - current weapon's ammo always sent now + //ammo_t energyammo = hhWeaponFireController::GetAmmoType("ammo_energy"); + //msg.WriteBits(inventory.ammo[energyammo], ASYNC_PLAYER_INV_AMMO_BITS); +} + +/* +=============== +hhPlayer::ReadFromSnapshot +=============== +*/ +void hhPlayer::ReadFromSnapshot( const idBitMsgDelta &msg ) { + int i, oldHealth, newIdealWeapon, weaponSpawnId; + bool newHitToggle, stateHitch; + + bool vehControlling = !!msg.ReadBits(1); + + if ( snapshotSequence - lastSnapshotSequence > 1 ) { + stateHitch = true; + } else { + stateHitch = false; + } + lastSnapshotSequence = snapshotSequence; + + oldHealth = health; + + physicsObj.ReadFromSnapshot( msg, vehControlling ); + + if (!vehControlling) { + //not syncing these causes denormalization/nan issues, FIXME + if (bBufferNextSnapAngles) { + idAngles n; + n[0] = msg.ReadFloat(); + n[1] = msg.ReadFloat(); + n[2] = msg.ReadFloat(); + BufferLoggedViewAngles(n); + untransformedViewAngles = n; + bBufferNextSnapAngles = false; + } + else { + untransformedViewAngles[0] = msg.ReadFloat(); + untransformedViewAngles[1] = msg.ReadFloat(); + untransformedViewAngles[2] = msg.ReadFloat(); + } + idCQuat q; + q.x = msg.ReadFloat(); + q.y = msg.ReadFloat(); + q.z = msg.ReadFloat(); + untransformedViewAxis = q.ToMat3(); + + //rww - read cameraInterpolator + cameraInterpolator.ReadFromSnapshot(msg, this); + + //deltaViewAngles[0] = msg.ReadDeltaFloat( 0.0f ); + //deltaViewAngles[1] = msg.ReadDeltaFloat( 0.0f ); + //deltaViewAngles[2] = msg.ReadDeltaFloat( 0.0f ); + } +#if 0 //for debugging differences in snapshot + else { + msg.ReadFloat(); + msg.ReadFloat(); + msg.ReadFloat(); + msg.ReadFloat(); + msg.ReadFloat(); + msg.ReadFloat(); + + msg.ReadFloat(); + msg.ReadFloat(); + msg.ReadFloat(); + msg.ReadDeltaFloat(0.0f); + msg.ReadDeltaFloat(0.0f); + msg.ReadDeltaFloat(0.0f); + msg.ReadDeltaFloat(0.0f); + msg.ReadDeltaFloat(0.0f); + msg.ReadDeltaFloat(0.0f); + msg.ReadFloat(4, 4); + + msg.ReadDeltaFloat(0.0f); + msg.ReadDeltaFloat(0.0f); + msg.ReadDeltaFloat(0.0f); + msg.ReadDeltaFloat(0.0f); + msg.ReadDeltaFloat(0.0f); + msg.ReadDeltaFloat(0.0f); + msg.ReadDeltaFloat(0.0f); + msg.ReadDeltaFloat(0.0f); + msg.ReadDeltaFloat(0.0f); + msg.ReadFloat(4, 4); + + msg.ReadFloat(); + msg.ReadDeltaFloat(0.0f); + msg.ReadDeltaFloat(0.0f); + msg.ReadFloat(4, 4); + + msg.ReadDeltaFloat(0.0f); + msg.ReadDeltaFloat(0.0f); + msg.ReadDeltaFloat(0.0f); + } +#endif + + deltaViewAngles[0] = msg.ReadDeltaFloat( 0.0f ); + deltaViewAngles[1] = msg.ReadDeltaFloat( 0.0f ); + deltaViewAngles[2] = msg.ReadDeltaFloat( 0.0f ); + + health = msg.ReadShort(); + lastDamageDef = gameLocal.ClientRemapDecl( DECL_ENTITYDEF, 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 ); + spectator = msg.ReadBits( idMath::BitsForInteger( MAX_CLIENTS ) ); + newHitToggle = msg.ReadBits( 1 ) != 0; + weaponGone = msg.ReadBits( 1 ) != 0; + ReadBindFromSnapshot( msg ); + +//===================END OF ID DATA + + inventory.maxHealth = msg.ReadShort(); //rww - since maxhealth can go above 100 in mp now + + idQuat quat; + + /* + if( spiritProxy.SetSpawnId( msg.ReadBits( 32 ) ) ) { + if (spiritProxy.GetEntity() && spiritProxy.GetEntity()->IsType(hhSpiritProxy::Type)) + { + spiritProxy.GetEntity()->ActivateProxy( this, GetOrigin(), GetPhysics()->GetAxis(), viewAxis, viewAngles ); + } + }*/ + spiritProxy.SetSpawnId( msg.ReadBits( 32 ) ); + + //rww - more spiritwalk stuff + lastWeaponSpirit = msg.ReadBits(32); + + bool spiritWalking = !!msg.ReadBits(1); + if (spiritWalking != bSpiritWalk && (weapon.IsValid() || !spiritWalking)) { + bSpiritWalk = spiritWalking; + + LighterOff(); //make sure lighter is off when switching spiritwalk + + if (bSpiritWalk) { + if (gameLocal.localClientNum == entityNumber) { + if (hud) { + hud->HandleNamedEvent("SwitchToEthereal"); + } + gameLocal.SpiritWalkSoundMode( true ); + } + + // Set the player's skin to a glowy effect + SetSkinByName( spawnArgs.GetString("skin_Spiritwalk") ); + SetShaderParm( SHADERPARM_TIMEOFFSET, 1.0f ); // TEMP: cjr - Required by the forcefield material. Can remove when a proper spiritwalk texture is made + + //put bow in proper state + SelectEtherealWeapon(); + } + else { + if (gameLocal.localClientNum == entityNumber) { + if (hud) { + hud->HandleNamedEvent("SwitchFromEthereal"); + } + gameLocal.SpiritWalkSoundMode( false ); + } + + SetSkinByName( NULL ); + } + } + + //not needed anymore + /* + // Handle progress bar + bool bBar = msg.ReadBits(1) != 0; + float value = msg.ReadFloat(); + int state = msg.ReadBits(2); + CL_UpdateProgress(bBar, value, state); + */ + + // if not a local client assume the client has all ammo types + if ( entityNumber != gameLocal.localClientNum ) { + for( i = 0; i < AMMO_NUMTYPES; i++ ) { + inventory.ammo[ i ] = 999; + } + } + + //rww - send ammo for current weapon - this is because our weapon routines rely on if we have ammo + //in scripts and so on, and will be predicted wrong for other players (we get our own ammo in the playerstate) + //rwwFIXME: if we have proper weapon switching when you run out of ammo will this actually be necessary? + //i don't think it's all that costly, but still. + int primAmmo = msg.ReadBits(ASYNC_PLAYER_INV_AMMO_BITS); + int altAmmo = msg.ReadBits(ASYNC_PLAYER_INV_AMMO_BITS); + if (entityNumber != gameLocal.localClientNum) { + //since we have our own ammo in the playerstate, don't want to stomp it + if (weapon.IsValid()) { + ammo_t ammoType = weapon->GetAmmoType(); + if (ammoType > 0) { + inventory.ammo[ammoType] = primAmmo; + } + ammoType = weapon->GetAltAmmoType(); + if (ammoType > 0) { + inventory.ammo[ammoType] = altAmmo; + } + } + } + + //need to sync buttonMask since we're using it for some state-based things + buttonMask = msg.ReadBits(8); + + SetEyeHeight(msg.ReadFloat()); + + //HUMANHEAD PCF rww 05/04/06 - do not sync AI_VEHICLE, it is now based purely on the clientside + //enter/exit of vehicles, with the vehControlling stack bool determining if we should be in a + //vehicle on the client or not. + //AI_VEHICLE = !!msg.ReadBits(1); + + //rww - hand stuff + //weaponHandState.ReadFromSnapshot(msg); + hand.SetSpawnId(msg.ReadBits(32)); + handNext.SetSpawnId(msg.ReadBits(32)); + + //HUMANHEAD PCF rww 05/04/06 - base this check on AI_VEHICLE and make sure the player is exited, + //even if the vehicle is not in the snapshot at this point. + if (!vehControlling && AI_VEHICLE) { //then exit on the client + if (vehicleInterfaceLocal.ControllingVehicle()) { + hhVehicle *veh = GetVehicleInterface()->GetVehicle(); + if (veh && veh->GetPilot() == this) { + veh->EjectPilot(); + } + } + ExitVehicle(NULL); //be extra safe in case vehicle is no longer around + } + + //jsh - vehicle stuff + int vehSpawnId = msg.ReadBits( 32 ); + //HUMANHEAD PCF rww 05/04/06 - base this check ON AI_VEHICLE, in case snapshot where controlling and + //where the vehicle is set do not exactly coincide. + vehicleInterfaceLocal.SetVehicleSpawnId( vehSpawnId ); + if( !AI_VEHICLE ) { + if (vehControlling) { + hhVehicle *veh = vehicleInterfaceLocal.GetVehicle(); + if (veh && veh->IsType(hhVehicle::Type)) { + //GetVehicleInterface()->TakeControl( vehicleInterfaceLocal.GetVehicle(), this ); + EnterVehicle( vehicleInterfaceLocal.GetVehicle() ); + } + } + } + + /* + if( vehicleInterfaceLocal.SetHandSpawnId( msg.ReadBits( 32 ) ) ) { + vehicleInterfaceLocal.GetHandEntity()->AttachHand( this, true ); + } + */ + vehicleInterfaceLocal.SetHandSpawnId( msg.ReadBits( 32 ) ); + //vehicleInterfaceLocal.GetWeaponHandState()->ReadFromSnapshot(msg); + + //rww - lighter sync validation + bool newLighterOn = !!msg.ReadBits(1); + if (newLighterOn != IsLighterOn()) { + ToggleLighter(); + } + + //rww - tractor + fl.isTractored = !!msg.ReadBits(1); + + spiritwalkSoundController.ReadFromSnapshot(msg); + //deathwalkSoundController.ReadFromSnapshot(msg); + wallwalkSoundController.ReadFromSnapshot(msg); + + //=========================================== start id code + if ( weapon.SetSpawnId( weaponSpawnId ) ) { + //HUMANHEAD rww - i am getting crashes here and the stack is all messed up, + //claiming that SetOwner is off into null, when supposedly the entity is not. + //rearranging this code so it's easier to see what's going on in a crash. + hhWeapon *weapEnt = weapon.GetEntity(); + if ( weapEnt ) { + // maintain ownership locally + weapEnt->SetOwner( this ); + } + currentWeapon = -1; + } + + //HUMANHEAD rww - but we do want ammo count for the leechgun on other players for prediction + //note - current weapon's ammo always sent now + //ammo_t energyammo = hhWeaponFireController::GetAmmoType("ammo_energy"); + //inventory.ammo[energyammo] = msg.ReadBits(ASYNC_PLAYER_INV_AMMO_BITS); + + if ( oldHealth > 0 && health <= 0 ) { + if ( stateHitch ) { + // so we just hide and don't show a death skin + UpdateDeathSkin( true ); + } + // die + AI_DEAD = true; + SetAnimState( ANIMCHANNEL_LEGS, "Legs_Death", 4 ); + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Death", 4 ); + SetWaitState( "" ); + animator.ClearAllJoints(); + + //HUMANHEAD rww - don't want this + /* + if ( entityNumber == gameLocal.localClientNum ) { + playerView.Fade( colorBlack, 12000 ); + } + */ + //HUMANHEAD END + + fl.clientEvents = true; //client event hackery for non-critical cosmetic cleanups + PostEventMS(&EV_RespawnCleanup, 32); + fl.clientEvents = false; + StartRagdoll(); + physicsObj.SetMovementType( PM_DEAD ); + if ( !stateHitch ) { + //HUMANHEAD PCF rww 09/15/06 - female mp sounds + if (IsFemale()) { + StartSound( "snd_death_female", SND_CHANNEL_VOICE, 0, false, NULL ); + } + else { + //HUMANHEAD END + StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL ); + } + } + if ( weapon.GetEntity() ) { + weapon.GetEntity()->OwnerDied(); + } + + //HUMANHEAD rww + GetPhysics()->SetContents(0); + Hide(); + + //so camera can operate on client. + minRespawnTime = gameLocal.time + RAGDOLL_DEATH_TIME; + maxRespawnTime = minRespawnTime + 10000; + //HUMANHEAD END + } else if ( oldHealth <= 0 && health > 0 ) { + // respawn + Init(); + StopRagdoll(); + SetPhysics( &physicsObj ); + physicsObj.EnableClip(); + SetCombatContents( true ); + //HUMANHEAD rww + if (!spectating) { + Show(); + } + //HUMANHEAD END + } else if ( health < oldHealth && health > 0 ) { + if ( stateHitch ) { + lastDmgTime = gameLocal.time; + } else { + // damage feedback + const idDeclEntityDef *def = static_cast( declManager->DeclByIndex( DECL_ENTITYDEF, lastDamageDef, false ) ); + if ( def ) { + playerView.DamageImpulse( lastDamageDir * viewAxis.Transpose(), &def->dict ); + AI_PAIN = Pain( NULL, NULL, oldHealth - health, lastDamageDir, lastDamageLocation ); + lastDmgTime = gameLocal.time; + } else { + common->Warning( "NET: no damage def for damage feedback '%s'\n", lastDamageDef ); + } + } + } + + // If the player is alive, restore proper physics object + if ( health > 0 && IsActiveAF() ) { + StopRagdoll(); + SetPhysics( &physicsObj ); + physicsObj.EnableClip(); + SetCombatContents( true ); + } + + if ( idealWeapon != newIdealWeapon ) { + if ( stateHitch ) { + weaponCatchup = true; + } + idealWeapon = newIdealWeapon; + UpdateHudWeapon(); + } + + if ( lastHitToggle != newHitToggle ) { + SetLastHitTime( gameLocal.realClientTime ); + } + //=========================================== end id code + + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + +/* +================ +hhPlayer::ServerReceiveEvent +================ +*/ +bool hhPlayer::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( 6 ); + PerformImpulse(impulse); + if (vehicleInterfaceLocal.ControllingVehicle()) { + hhVehicle *veh = vehicleInterfaceLocal.GetVehicle(); + if (veh) { + veh->DoPlayerImpulse(impulse); + } + } + return true; + } + default: { + return false; + } + } +} + +/* +================ +hhPlayer::WritePlayerStateToSnapshot +================ +*/ +void hhPlayer::WritePlayerStateToSnapshot( idBitMsgDelta &msg ) const { + idPlayer::WritePlayerStateToSnapshot(msg); + + //rww - extra playerView stuff + playerView.WriteToSnapshot(msg); + + //rww - scope view + msg.WriteBits(bScopeView, 1); + + //rww - zoom fov + msg.WriteFloat(zoomFov.GetDuration()); + msg.WriteFloat(zoomFov.GetEndValue()); + msg.WriteFloat(zoomFov.GetStartTime()); + msg.WriteFloat(zoomFov.GetStartValue()); + + //rww - view angle sensitivity + msg.WriteFloat(GetViewAnglesSensitivity()); +} + +/* +================ +hhPlayer::ReadPlayerStateFromSnapshot +================ +*/ +void hhPlayer::ReadPlayerStateFromSnapshot( const idBitMsgDelta &msg ) { + idPlayer::ReadPlayerStateFromSnapshot(msg); + + //rww - extra playerView stuff + playerView.ReadFromSnapshot(msg); + + //rww - scope and fov handling + bool canOverrideView = true; + if (weapon.IsValid() && weapon->IsType(hhWeaponZoomable::Type)) { //if we have a zoomed weapon, don't override with snapshot while the prediction fudge timer is on + hhWeaponZoomable *weap = static_cast(weapon.GetEntity()); + if (weap->clientZoomTime >= gameLocal.time) { + canOverrideView = false; + } + } + + bool scopeView = !!msg.ReadBits(1); + float zfDur = msg.ReadFloat(); + float zfEnd = msg.ReadFloat(); + float zfStT = msg.ReadFloat(); + float zfStV = msg.ReadFloat(); + + renderSystem->SetShuttleView( InVehicle() ); + + if (canOverrideView) { //if we are currently allowed to override with snapshot values, do so. + bScopeView = scopeView; + if (bScopeView != renderSystem->IsScopeView()) { + renderSystem->SetScopeView(bScopeView); + } + zoomFov.SetDuration(zfDur); + zoomFov.SetEndValue(zfEnd); + zoomFov.SetStartTime(zfStT); + zoomFov.SetStartValue(zfStV); + } + + //rww - view angle sensitivity + SetViewAnglesSensitivity(msg.ReadFloat()); +} + +/* +================ +hhPlayer::ClientPredictionThink +================ +*/ +void hhPlayer::ClientPredictionThink( void ) { + if (gameLocal.localClientNum != entityNumber && forcePredictionButtons) { + //used by some weapons which rely on prediction-based projectiles, to ensure we don't miss a launch + gameLocal.usercmds[ entityNumber ].buttons |= forcePredictionButtons; + if (gameLocal.isNewFrame) { + forcePredictionButtons = 0; + } + } + + idPlayer::ClientPredictionThink(); + + if (InVehicle()) { + UpdateHud( GetVehicleInterfaceLocal()->GetHUD() ); + } + else { + UpdateHud( hud ); + } +} + +/* +================ +hhPlayer::GetPhysicsToVisualTransform +================ +*/ +bool hhPlayer::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + if ( af.IsActive() ) { + af.GetPhysicsToVisualTransform( origin, axis ); + return true; + } + + //rww - adopted id's smoothing code + if ( gameLocal.isClient && !bindMaster && gameLocal.framenum >= smoothedFrame && ( entityNumber != gameLocal.localClientNum || selfSmooth ) ) { + if (!smoothedOriginUpdated) { + idVec3 renderOrigin = TransformToPlayerSpace(modelOffset); + idVec3 originalOrigin = renderOrigin; + + //id's cheesy smooth code (changed to vec3 because there is no "down" in prey) + idVec3 originDiff = renderOrigin - smoothedOrigin; + if (smoothedFrame == 0) { //for teleporting + originDiff = vec3_origin; + } + smoothedOrigin = renderOrigin; + if ( originDiff.LengthSqr() < Square( 100.0f ) ) { + // smoothen by pushing back to the previous position + if ( selfSmooth ) { + assert( entityNumber == gameLocal.localClientNum ); + renderOrigin -= net_clientSelfSmoothing.GetFloat() * originDiff; + } else { + renderOrigin -= gameLocal.clientSmoothing * originDiff; + } + + //get rid of the smoothing on the "vertical" axis + idVec3 a(renderOrigin.x*viewAxis[2].x, renderOrigin.y*viewAxis[2].y, renderOrigin.z*viewAxis[2].z); + idVec3 b(originalOrigin.x*viewAxis[2].x, originalOrigin.y*viewAxis[2].y, originalOrigin.z*viewAxis[2].z); + renderOrigin -= (a-b); + } + + smoothedFrame = gameLocal.framenum; + smoothedOriginUpdated = true; + } + + axis = viewAxis; + origin = ( smoothedOrigin - GetPhysics()->GetOrigin() ) * GetEyeAxis().Transpose(); + return true; + } + + if (bClampYaw) { //rww - lock model angles + idVec3 masterOrigin; + idMat3 masterAxis; + + if (GetMasterPosition(masterOrigin, masterAxis)) { + axis = masterAxis; + origin = modelOffset; + + return true; + } + } + + axis = viewAxis; + origin = modelOffset; + + return true; +} + +bool hhPlayer::UpdateAnimationControllers( void ) { + bool retValue = idPlayer::UpdateAnimationControllers(); + + hhAnimator *theAnimator; + if (head.IsValid()) { + theAnimator = head->GetAnimator(); + } + else { + theAnimator = GetAnimator(); + } + JawFlap(theAnimator); + + return retValue; +} + +/* +=============== +hhPlayer::Event_PlayWeaponAnim +=============== +*/ +void hhPlayer::Event_PlayWeaponAnim( const char* animName, int numTries ) { + //AOB: I would like a better solution then constantly banging until weapon is valid. + if( (!weapon.IsValid() || GetCurrentWeapon() != idealWeapon) && numTries > 0 ) { + CancelEvents( &EV_PlayWeaponAnim ); + PostEventMS( &EV_PlayWeaponAnim, 50, animName, numTries - 1 ); + return; + } + + CancelEvents( &EV_PlayWeaponAnim ); + if( weapon.IsValid() ) { + weapon->ProcessEvent( &EV_PlayAnimWhenReady, animName ); + } +} + +void hhPlayer::DialogStart(bool bDisallowPlayerDeath, bool bVoiceDucking, bool bLowerWeapon) { + bDialogDamageMode = bDisallowPlayerDeath; + bDialogWeaponMode = bLowerWeapon; + gameLocal.DialogSoundMode(bVoiceDucking); + if (bDialogWeaponMode) { // Lock weapon + preCinematicWeaponFlags = weaponFlags; + preCinematicWeapon = GetIdealWeapon(); + LockWeapon(-1); + } +} + +void hhPlayer::DialogStop() { + bDialogDamageMode = false; + gameLocal.DialogSoundMode(false); + if (bDialogWeaponMode) { + weaponFlags = preCinematicWeaponFlags; + SelectWeapon(preCinematicWeapon, true); + bDialogWeaponMode = false; + } +} + +/* +=============== +hhPlayer::Event_DialogStart + HUMANHEAD pdm +=============== +*/ +void hhPlayer::Event_DialogStart( int bDisallowPlayerDeath, int bVoiceDucking, int bLowerWeapon ) { + DialogStart(bDisallowPlayerDeath != 0, bVoiceDucking != 0, bLowerWeapon != 0); +} + +/* +=============== +hhPlayer::Event_DialogStop + HUMANHEAD pdm +=============== +*/ +void hhPlayer::Event_DialogStop() { + DialogStop(); +} + +/* +=============== +hhPlayer::Event_LotaTunnelMode + HUMANHEAD pdm +=============== +*/ +void hhPlayer::Event_LotaTunnelMode(bool on) { + bLotaTunnelMode = on; +} + +/* +=============== +hhPlayer::Event_Cinematic + HUMANHEAD pdm +=============== +*/ +void hhPlayer::Event_Cinematic( int on, int lockView ) { + bool cinematic = (on != 0); + InCinematic( cinematic ); + playerView.SetLetterBox(cinematic); + if (cinematic) { + if ( IsSpiritOrDeathwalking() ) { + StopSpiritWalk(); + } + if ( IsPossessed() ) { + Unpossess(); + } + + // Lock weapon + preCinematicWeaponFlags = weaponFlags; + preCinematicWeapon = GetCurrentWeapon(); + LockWeapon(-1); + + // disable damage + fl.takedamage = false; + this->lockView = (lockView != 0); + } + else { + fl.takedamage = true; + this->lockView = false; + + //UnlockWeapon(preCinematicWeapon); + weaponFlags = preCinematicWeaponFlags; + //SetCurrentWeapon(preCinematicWeapon); + SelectWeapon(preCinematicWeapon, true); + } +} + +//============================================================================= +// +// hhPlayer::Event_PrepareToResurrect +// +// Flashes the screen / fades the FOV +//============================================================================= + +void hhPlayer::Event_PrepareToResurrect() { + deathWalkFlash = 0; + possessionFOV = 90.0f; + PostEventSec( &EV_ResurrectScreenFade, 0 ); +} + +//============================================================================= +// +// hhPlayer::Event_ResurrectScreenFade +// +// The actual code to incrementally flash the screen +//============================================================================= + +void hhPlayer::Event_ResurrectScreenFade() { + deathWalkFlash += spawnArgs.GetFloat( "resurrectFlashChange", "0.04" ); + if ( deathWalkFlash >= 1.0f ) { + deathWalkFlash = 1.0f; + PostEventSec( &EV_Resurrect, 0.0f ); // Actually send player back to the physical realm + } else { + PostEventSec( &EV_ResurrectScreenFade, 0.02f ); + } + + possessionFOV += spawnArgs.GetFloat( "resurrectFOVChange", "1.0" ); + playerView.SetViewOverlayColor( idVec4( 1.0f, 1.0f, 1.0f, deathWalkFlash ) ); +} + +//============================================================================= +// +// hhPlayer::Event_Resurrect +// +//============================================================================= +void hhPlayer::Event_Resurrect() { + Resurrect(); +} + +//============================================================================= +// +// hhPlayer::Event_Event_ShouldRemainAlignedToAxial +// +//============================================================================= +void hhPlayer::Event_ShouldRemainAlignedToAxial( bool remainAligned ) { + ShouldRemainAlignedToAxial( remainAligned ); +} + +//============================================================================= +// +// hhPlayer::Event_OrientToGravity +// +//============================================================================= +void hhPlayer::Event_OrientToGravity( bool orient ) { + OrientToGravity( orient ); +} + +//============================================================================= +// +// hhPlayer::Event_ResetGravity +// HUMANHEAD: pdm: Posted when entity is leaving a gravity zone +//============================================================================= +void hhPlayer::Event_ResetGravity() { + if( IsWallWalking() ) { + return; // Don't reset if wallwalking + } + + if (spectating) { //HUMANHEAD rww - don't reset if spectating + return; + } + + idPlayer::Event_ResetGravity(); + + OrientToGravity( true ); // let it reset orientation +} + +//============================================================================= +// +// hhPlayer::Event_SetOverlayMaterial +// +//============================================================================= +void hhPlayer::Event_SetOverlayMaterial( const char *mtrName, const int requiresScratch ) { + if ( mtrName && mtrName[0] ) { + playerView.SetViewOverlayMaterial( declManager->FindMaterial(mtrName), requiresScratch ); + } else { + playerView.SetViewOverlayMaterial( NULL ); + } +} + +//============================================================================= +// +// hhPlayer::Event_SetOverlayTime +// +//============================================================================= +void hhPlayer::Event_SetOverlayTime( const float newTime, const int requiresScratch ) { + playerView.SetViewOverlayTime( newTime, requiresScratch ); +} + +//============================================================================= +// +// hhPlayer::Event_SetOverlayColor +// +//============================================================================= +void hhPlayer::Event_SetOverlayColor( const float r, const float g, const float b, const float a ) { + idVec4 color; + + color.x = r; + color.y = g; + color.z = b; + color.w = a; + + playerView.SetViewOverlayColor( color ); +} + +//============================================================================= +// +// hhPlayer::Event_DDAHeartBeat +// +//============================================================================= + +void hhPlayer::Event_DDAHeartBeat() { + gameLocal.GetDDA()->DDA_Heartbeat( this ); + PostEventMS( &EV_DDAHeartbeat, ddaHeartbeatMS ); +} + +//================ +//hhPlayer::Save +//================ +void hhPlayer::Save( idSaveGame *savefile ) const { + // Public vars + savefile->Write(weaponInfo, sizeof(weaponInfo_t)*15); + savefile->Write(altWeaponInfo, sizeof(weaponInfo_t)*15); + savefile->WriteFloat( lighterTemperature ); + savefile->WriteRenderLight( lighter ); + //HUMANHEAD PCF mdl 05/04/06 - Don't save light handles + //savefile->WriteInt( lighterHandle ); + spiritProxy.Save( savefile ); + savefile->WriteInt( lastWeaponSpirit ); + talon.Save( savefile ); + savefile->WriteInt( nextTalonAttackCommentTime ); + savefile->WriteBool( bTalonAttackComment ); + savefile->WriteBool( bSpiritWalk ); + savefile->WriteBool( bDeathWalk ); + savefile->WriteBool( bReallyDead ); + guiWantsControls.Save( savefile ); + savefile->WriteFloat( deathWalkFlash ); + savefile->WriteInt( deathWalkTime ); + savefile->WriteInt( deathWalkPower ); + savefile->WriteBool( bInDeathwalkTransition ); + savefile->WriteBool( bInCinematic ); + savefile->WriteBool( bPlayingLowHealthSound ); + savefile->WriteBool( bPossessed ); + savefile->WriteFloat( possessionTimer ); + savefile->WriteFloat( possessionFOV ); + savefile->WriteInt( preCinematicWeapon ); + savefile->WriteInt( preCinematicWeaponFlags ); + savefile->WriteInt( lastDamagedTime ); + savefile->WriteStaticObject( weaponHandState ); + hand.Save( savefile ); + handNext.Save( savefile ); + possessedTommy.Save( savefile ); + + savefile->WriteStaticObject( vehicleInterfaceLocal ); + savefile->WriteObject( vehicleInterfaceLocal.GetVehicle() ); + + deathLookAtEntity.Save( savefile ); + savefile->WriteString( deathLookAtBone ); + savefile->WriteString( deathCameraBone ); + savefile->WriteStaticObject( spiritwalkSoundController ); + savefile->WriteStaticObject( deathwalkSoundController ); + savefile->WriteStaticObject( wallwalkSoundController ); + savefile->WriteBool( bShowProgressBar ); + savefile->WriteFloat( progressBarValue ); + + savefile->WriteFloat( progressBarGuiValue.GetStartTime() ); // idInterpolate + savefile->WriteFloat( progressBarGuiValue.GetDuration() ); + savefile->WriteFloat( progressBarGuiValue.GetStartValue() ); + savefile->WriteFloat( progressBarGuiValue.GetEndValue() ); + + savefile->WriteInt( progressBarState ); + savefile->WriteBool( bClampYaw ); + savefile->WriteFloat( maxRelativeYaw ); + savefile->WriteFloat( maxRelativePitch ); + savefile->WriteFloat( bob ); + savefile->WriteInt( lastAppliedBobCycle ); + savefile->WriteInt( prevStepUpTime ); + savefile->WriteVec3( prevStepUpOrigin ); + savefile->WriteFloat( crashlandSpeed_fatal ); + savefile->WriteFloat( crashlandSpeed_soft ); + savefile->WriteFloat( crashlandSpeed_jump ); + // Protected vars + savefile->WriteUserInterface( guiOverlay, false ); + thirdPersonCameraClipBounds.Save( savefile ); + savefile->WriteFloat( viewAnglesSensitivity ); + savefile->WriteInt( lastResurrectTime ); + savefile->WriteInt( spiritDrainHeartbeatMS ); + savefile->WriteInt( ddaHeartbeatMS ); + + savefile->WriteInt( spiritWalkToggleTime ); + savefile->WriteBool( bDeathWalkStage2 ); + savefile->WriteBool( bFrozen ); + savefile->WriteAngles( untransformedViewAngles ); + savefile->WriteMat3( untransformedViewAxis ); + savefile->WriteInt( nextSpiritTime ); + + savefile->WriteFloat( cinematicFOV.GetStartTime() ); + savefile->WriteFloat( cinematicFOV.GetAcceleration() ); + savefile->WriteFloat( cinematicFOV.GetDeceleration() ); + savefile->WriteFloat( cinematicFOV.GetDuration() ); + savefile->WriteFloat( cinematicFOV.GetStartValue() ); + savefile->WriteFloat( cinematicFOV.GetEndValue() ); + savefile->WriteBool( bAllowSpirit ); + savefile->WriteInt( airAttackerTime ); + savefile->WriteBool( bScopeView ); + savefile->WriteInt( ddaNumEnemies ); + savefile->WriteFloat( ddaProbabilityAccum ); + savefile->WriteInt( weaponFlags ); + savefile->WriteBool( lockView ); + + savefile->WriteBool( bCollidingWithPortal ); + savefile->WriteBool( bLotaTunnelMode ); + savefile->WriteInt( forcePredictionButtons ); + + for (int ix=0; ixWriteInt( lastAttackers[ix].time ); + savefile->WriteBool( lastAttackers[ix].displayed ); + } + + if ( InVehicle() ) { + savefile->WriteMat3( vehicleInterfaceLocal.GetVehicle()->GetPhysics()->GetAxis() ); + savefile->WriteVec3( vehicleInterfaceLocal.GetVehicle()->GetAxis()[0] ); + } + + //HUMANHEAD PCF mdl 04/28/06 - Moved camera interpolater down here to fix jump off wallwalk view angle problem + cameraInterpolator.Save( savefile ); + //HUMANHEAD PCF mdl 05/04/06 - Save whether the light handle is active + savefile->WriteBool( IsLighterOn() ); +} + +//================ +//hhPlayer::Restore +//================ +void hhPlayer::Restore( idRestoreGame *savefile ) { + // Public vars + savefile->Read(weaponInfo, sizeof(weaponInfo_t)*15); + savefile->Read(altWeaponInfo, sizeof(weaponInfo_t)*15); + savefile->ReadFloat( lighterTemperature ); + savefile->ReadRenderLight( lighter ); + //HUMANHEAD PCF mdl 05/04/06 - Don't save light handles + //savefile->ReadInt( lighterHandle ); + spiritProxy.Restore( savefile ); + savefile->ReadInt( lastWeaponSpirit ); + talon.Restore( savefile ); + savefile->ReadInt( nextTalonAttackCommentTime ); + savefile->ReadBool( bTalonAttackComment ); + savefile->ReadBool( bSpiritWalk ); + savefile->ReadBool( bDeathWalk ); + savefile->ReadBool( bReallyDead ); + guiWantsControls.Restore( savefile ); + savefile->ReadFloat( deathWalkFlash ); + savefile->ReadInt( deathWalkTime ); + savefile->ReadInt( deathWalkPower ); + savefile->ReadBool( bInDeathwalkTransition ); + savefile->ReadBool( bInCinematic ); + savefile->ReadBool( bPlayingLowHealthSound ); + savefile->ReadBool( bPossessed ); + savefile->ReadFloat( possessionTimer ); + savefile->ReadFloat( possessionFOV ); + savefile->ReadInt( preCinematicWeapon ); + savefile->ReadInt( preCinematicWeaponFlags ); + savefile->ReadInt( lastDamagedTime ); + savefile->ReadStaticObject( weaponHandState ); + hand.Restore( savefile ); + handNext.Restore( savefile ); + possessedTommy.Restore( savefile ); + + savefile->ReadStaticObject( vehicleInterfaceLocal ); + SetVehicleInterface( &vehicleInterfaceLocal ); + + hhVehicle *vehicle; + savefile->ReadObject( reinterpret_cast ( vehicle ) ); + if( vehicle ) { + vehicle->RestorePilot( &vehicleInterfaceLocal ); + } + + deathLookAtEntity.Restore( savefile ); + savefile->ReadString( deathLookAtBone ); + savefile->ReadString( deathCameraBone ); + savefile->ReadStaticObject( spiritwalkSoundController ); + savefile->ReadStaticObject( deathwalkSoundController ); + savefile->ReadStaticObject( wallwalkSoundController ); + savefile->ReadBool( bShowProgressBar ); + savefile->ReadFloat( progressBarValue ); + + float set; + savefile->ReadFloat( set ); // idInterpolate + progressBarGuiValue.SetStartTime( set ); + savefile->ReadFloat( set ); + progressBarGuiValue.SetDuration( set ); + savefile->ReadFloat( set ); + progressBarGuiValue.SetStartValue(set); + savefile->ReadFloat( set ); + progressBarGuiValue.SetEndValue( set ); + + savefile->ReadInt( progressBarState ); + savefile->ReadBool( bClampYaw ); + savefile->ReadFloat( maxRelativeYaw ); + savefile->ReadFloat( maxRelativePitch ); + savefile->ReadFloat( bob ); + savefile->ReadInt( lastAppliedBobCycle ); + savefile->ReadInt( prevStepUpTime ); + savefile->ReadVec3( prevStepUpOrigin ); + savefile->ReadFloat( crashlandSpeed_fatal ); + savefile->ReadFloat( crashlandSpeed_soft ); + savefile->ReadFloat( crashlandSpeed_jump ); + // Protected vars + savefile->ReadUserInterface( guiOverlay ); + + thirdPersonCameraClipBounds.Restore( savefile ); + savefile->ReadFloat( viewAnglesSensitivity ); + savefile->ReadInt( lastResurrectTime ); + savefile->ReadInt( spiritDrainHeartbeatMS ); + savefile->ReadInt( ddaHeartbeatMS ); + + savefile->ReadInt( spiritWalkToggleTime ); + savefile->ReadBool( bDeathWalkStage2 ); + savefile->ReadBool( bFrozen ); + savefile->ReadAngles( untransformedViewAngles ); + savefile->ReadMat3( untransformedViewAxis ); + savefile->ReadInt( nextSpiritTime ); + + float startTime, accelTime, decelTime, duration, startPos, endPos; + + savefile->ReadFloat( startTime ); + savefile->ReadFloat( accelTime ); + savefile->ReadFloat( decelTime ); + savefile->ReadFloat( duration ); + savefile->ReadFloat( startPos ); + savefile->ReadFloat( endPos ); + cinematicFOV.Init( startTime, accelTime, decelTime, duration, startPos, endPos ); + savefile->ReadBool( bAllowSpirit ); + savefile->ReadInt( airAttackerTime ); + savefile->ReadBool( bScopeView ); + savefile->ReadInt( ddaNumEnemies ); + savefile->ReadFloat( ddaProbabilityAccum ); + savefile->ReadInt( weaponFlags ); + savefile->ReadBool( lockView ); + + savefile->ReadBool( bCollidingWithPortal ); + savefile->ReadBool( bLotaTunnelMode ); + savefile->ReadInt( forcePredictionButtons ); + + for (int ix=0; ixReadInt( lastAttackers[ix].time ); + savefile->ReadBool( lastAttackers[ix].displayed ); + } + + // We don't want to preserve these + memset( &oldCmdAngles, 0, sizeof( oldCmdAngles ) ); + + kickSpring = spawnArgs.GetFloat( "kickSpring" ); //HUMANHEAD bjk + kickDamping = spawnArgs.GetFloat( "kickDamping" ); //HUMANHEAD bjk + + if( InVehicle() ) { + hhVehicle *vehicle = vehicleInterfaceLocal.GetVehicle(); + HH_ASSERT( vehicle ); + + idMat3 axis; + idVec3 axis0; + + // This prevents a crashbug if the vehicle's physics doesn't exist yet. + savefile->ReadMat3( axis ); // vehicle->GetPhysics()->GetAxis() + savefile->ReadVec3( axis0 ); // vehicle->GetAxis()[0] + + // Keep orientation correct in vehicles + RestoreOrientation( GetOrigin(), axis, axis0, axis0.ToAngles() ); + + //HUMANHEAD PCF mdl 05/02/06 - Added this to re-enable shuttle view + if (renderSystem) { + // Set shuttle view + renderSystem->SetShuttleView( true ); + } + } else { + // Keep orientation correct for walkwalk and gravity rooms + RestoreOrientation( GetOrigin(), physicsObj.GetAxis(), viewAngles.ToMat3()[0], untransformedViewAngles ); + } + + //HUMANHEAD PCF mdl 04/28/06 - Moved camera interpolater down here to fix jump off wallwalk view angle problem + cameraInterpolator.Restore( savefile ); + + //HUMANHEAD PCF mdl 05/04/06 - Restore lighter + bool bLighter; + savefile->ReadBool( bLighter ); + if ( bLighter ) { + lighterHandle = gameRenderWorld->AddLightDef( &lighter ); + } +} + +int hhPlayer::GetSpiritPower() { + ammo_t ammo_spiritpower = idWeapon::GetAmmoNumForName("ammo_spiritpower"); + return inventory.ammo[ammo_spiritpower]; +} + +void hhPlayer::SetSpiritPower(int amount) { + ammo_t ammo_spiritpower = idWeapon::GetAmmoNumForName("ammo_spiritpower"); + inventory.ammo[ammo_spiritpower] = amount; + if (inventory.ammo[ammo_spiritpower] > inventory.maxSpirit) { + inventory.ammo[ammo_spiritpower] = inventory.maxSpirit; + } +} + +void hhPlayer::Event_StartHUDTranslation() { + vehicleInterfaceLocal.StartHUDTranslation(); +} + +void hhPlayer::Freeze(float unfreezeDelay) { + bFrozen = true; + StopFiring(); + if ( weapon.IsValid() ) { + weapon->PutAway(); + } + if ( unfreezeDelay > 0.0f ) { + PostEventSec( &EV_Unfreeze, unfreezeDelay ); + } +} + +void hhPlayer::Unfreeze(void) { + bFrozen = false; + if ( weapon.IsValid() ) { + weapon->PostEventMS( &EV_Show, 1000 ); + weapon->PostEventMS( &EV_Weapon_WeaponRising, 1000 ); + } +} + +void hhPlayer::Event_Unfreeze() { + Unfreeze(); + if ( bindMaster && bindMaster->RespondsTo( EV_BindUnfroze ) ) { + GetBindMaster()->PostEventMS( &EV_BindUnfroze, 0, this ); + } +} + +void hhPlayer::Event_GetSpiritPower() { //rww + idThread::ReturnFloat((float)GetSpiritPower()); +} + +void hhPlayer::Event_SetSpiritPower(const float s) { //rww + SetSpiritPower((int)s); +} + +void hhPlayer::Event_OnGround() { // bg + idThread::ReturnFloat( physicsObj.HasGroundContacts() ); +} + +void hhPlayer::SetupWeaponFlags( void ) { + const char *mapName = gameLocal.serverInfo.GetString( "si_map" ); + const idDecl *mapDecl = declManager->FindType(DECL_MAPDEF, mapName, false ); + if ( !mapDecl ) { + weaponFlags = -1; // No map decls? Allow all weapons + return; + } + const idDeclEntityDef *mapInfo = static_cast(mapDecl); + + const char *weaponList = mapInfo->dict.GetString( "disableWeapons" ); + weaponFlags = 0; + if ( weaponList && weaponList[0] ) { + if ( !idStr::Icmp( weaponList, "all" ) ) { + // No weapons are enabled, so just return + return; + } + + if ( idStr::FindText( weaponList, "wrench", false ) == -1 ) { + weaponFlags |= HH_WEAPON_WRENCH; + } + if ( idStr::FindText( weaponList, "rifle", false ) == -1 ) { + weaponFlags |= HH_WEAPON_RIFLE; + } + if ( idStr::FindText( weaponList, "crawler", false ) == -1 ) { + weaponFlags |= HH_WEAPON_CRAWLER; + } + if ( idStr::FindText( weaponList, "autocannon", false ) == -1 ) { + weaponFlags |= HH_WEAPON_AUTOCANNON; + } + if ( idStr::FindText( weaponList, "hiderweapon", false ) == -1 ) { + weaponFlags |= HH_WEAPON_HIDERWEAPON; + } + if ( idStr::FindText( weaponList, "rocketlauncher", false ) == -1 ) { + weaponFlags |= HH_WEAPON_ROCKETLAUNCHER; + } + if ( idStr::FindText( weaponList, "soulstripper", false ) == -1 ){ + weaponFlags |= HH_WEAPON_SOULSTRIPPER; + } + } + + if ( weaponFlags == 0 ) { + // Allow all weapons by default + weaponFlags = -1; + } +} + +void hhPlayer::LockWeapon( int weaponNum ) { + // Special case, -1 locks all weapons AND the lighter + if ( weaponNum == -1 ) { + if (IsLighterOn()) { + LighterOff(); + } + if (weapon.IsValid()) { + weapon->PutAway(); + } + weaponFlags = 0; + idealWeapon = 0; + currentWeapon = 0; + return; + } + + if ( weaponNum <= 0 || weaponNum >= MAX_WEAPONS ) { + gameLocal.Error( "Attempted to unlock unknown weapon '%d'", weaponNum ); + } + + int flag = ( 1 << ( weaponNum - 1 ) ); + if ( weaponFlags & flag ) { // Only lock if not already locked + weaponFlags &= ~flag; + if ( idealWeapon == weaponNum ) { + if (weapon.IsValid()) { + weapon->PutAway(); + } + NextWeapon(); + } + } +} + +void hhPlayer::UnlockWeapon( int weaponNum ) { + // Special case, -1 unlocks all weapons + if ( weaponNum == -1 ) { + weaponFlags = -1; + if ( idealWeapon == 0 ) { + idealWeapon = 1; // Default to the wrench + } + return; + } + + if ( weaponNum <= 0 || weaponNum >= MAX_WEAPONS ) { + gameLocal.Error( "Attempted to unlock unknown weapon '%d'", weaponNum ); + } + + int flag = ( 1 << ( weaponNum - 1 ) ); + if ( weaponFlags & flag ) { + // Already unlocked + return; + } + + weaponFlags |= flag; + idealWeapon = weaponNum; +} + +bool hhPlayer::IsLocked(int weaponNum) { + //HUMANHEAD PCF mdl 05/05/06 - Changed to < 1 to catch weaponNum = -1 + if (weaponNum < 1) { + return true; // CJR: if the player has no weapons, then consider all weapons locked + } + return !(weaponFlags & (1 << (weaponNum-1))); +} + +void hhPlayer::Event_LockWeapon( int weaponNum ) { + LockWeapon( weaponNum ); +} + +void hhPlayer::Event_UnlockWeapon( int weaponNum ) { + UnlockWeapon( weaponNum ); +} + +//HUMANHEAD rdr +void hhPlayer::Event_SetPrivateCameraView( idEntity *camView, int noHide ) { + if ( camView && camView->IsType( idCamera::Type ) ) { + idPlayer::SetPrivateCameraView( static_cast( camView ), noHide != 0 ); + } + else { + idPlayer::SetPrivateCameraView( NULL ); + } +} + +//HUMANHEAD rdr +void hhPlayer::Event_SetCinematicFOV( float fieldOfView, float accelTime, float decelTime, float duration ) { + if ( duration > 0.f ) { + cinematicFOV.Init( gameLocal.time, SEC2MS( accelTime ), SEC2MS( decelTime ), SEC2MS( duration ), cinematicFOV.GetCurrentValue( gameLocal.GetTime() ), fieldOfView ); + } else { + cinematicFOV.Init( gameLocal.time, 0.f, 0.f, 0.f, cinematicFOV.GetEndValue(), fieldOfView ); + } +} + +//HUMANHEAD rww +void hhPlayer::Event_StopSpiritWalk() { + StopSpiritWalk(); +} + +void hhPlayer::Event_DamagePlayer(idEntity *inflictor, idEntity *attacker, const idVec3 &dir, char *damageDefName, float damageScale, int location) { + Damage(inflictor, attacker, dir, damageDefName, damageScale, location); +} +//HUMANHEAD END + +void hhPlayer::Event_GetSpiritProxy() { + if ( spiritProxy.IsValid() ) { + idThread::ReturnEntity( spiritProxy.GetEntity() ); + } else { + idThread::ReturnEntity( NULL ); + } +} + +void hhPlayer::Event_IsSpiritWalking() { + idThread::ReturnInt( IsSpiritWalking() ? 1 : 0 ); +} + +void hhPlayer::Event_IsDeathWalking() { + idThread::ReturnInt( IsDeathWalking() ? 1 : 0 ); +} + +void hhPlayer::Event_AllowLighter( bool allow ) { + inventory.requirements.bCanUseLighter = allow; + if ( !allow && IsLighterOn() ) { + LighterOff(); + } +} + +//============================================================================= +// +// hhPlayer::Event_GetDDAValue +// +// Returns the current DDA value from zero to one +//============================================================================= + +void hhPlayer::Event_GetDDAValue() { + idThread::ReturnFloat( gameLocal.GetDDAValue() ); +} + +//HUMANHEAD rww +hhArtificialPlayer::hhArtificialPlayer(void) { + memset(&lastAICmd, 0, sizeof(lastAICmd)); + testCrouchActive = false; + testCrouchTime = 0; +} + +void hhArtificialPlayer::Spawn( void ) { + idDict apUI; + gameLocal.GetAPUserInfo(apUI, entityNumber); + gameLocal.SetUserInfo(entityNumber, apUI, gameLocal.isClient, false); + + SetPlayerModel(false); //force an update on the player model +} + +void hhArtificialPlayer::Think( void ) { + PROFILE_START("hhArtificialPlayer::Think", PROFMASK_NORMAL); + bool iFeelHoppy = false; + usercmd_t *nextCmd = &gameLocal.usercmds[ entityNumber ]; + idAngles idealAngles = GetViewAngles(); + + nextCmd->gameFrame = gameLocal.framenum; + nextCmd->gameTime = gameLocal.time; + + nextCmd->forwardmove = idMath::ClampChar(127); + + //this is not meant to resemble proper ai logic, it's just for testing. + idEntity *closestEnt = NULL; + float closestDist = idMath::INFINITY; + + int numSourceAreas, sourceAreas[ idEntity::MAX_PVS_AREAS ]; + numSourceAreas = gameRenderWorld->BoundsInAreas( GetPlayerPhysics()->GetAbsBounds(), sourceAreas, idEntity::MAX_PVS_AREAS ); + pvsHandle_t pvsHandle = gameLocal.pvs.SetupCurrentPVS( sourceAreas, numSourceAreas, PVS_NORMAL ); + + for(int i=0;iIsType(hhPlayer::Type)); + hhPlayer *plEnt = static_cast(ent); + + float l = (ent->GetOrigin()-GetOrigin()).Length(); + if(l < 4096.0f && (l < closestDist || !closestEnt) && ent->PhysicsTeamInPVS(pvsHandle)) { + trace_t tr; + + gameLocal.clip.TracePoint(tr, GetOrigin(), ent->GetOrigin(), GetPhysics()->GetClipMask(), this); + if (tr.c.entityNum == ent->entityNumber) { //if we hit the thing then it's visible. + if (ent->health > 0) { + if (gameLocal.gameType != GAME_TDM || plEnt->team != team) { + closestDist = l; + closestEnt = ent; + } + } + } + } + } + } + + gameLocal.pvs.FreeCurrentPVS( pvsHandle ); + + if (closestEnt) { + idealAngles = (closestEnt->GetOrigin()-GetOrigin()).ToAngles(); + } + else { //if no one to run mindlessly at, do some stuff. + const float testDist = 64.0f; + idVec3 fwd = GetOrigin()+(idealAngles.ToForward()*testDist); + + trace_t tr; + gameLocal.clip.TraceBounds(tr, GetOrigin(), fwd, GetPhysics()->GetBounds(), GetPhysics()->GetClipMask(), this); + if (tr.fraction != 1.0f) { //if we hit something set the yaw to a new random angle. + iFeelHoppy = true; + + idealAngles.yaw = rand()%360; + } + } + + if ((closestEnt || health <= 0) && rand()%2 == 1) { + nextCmd->buttons |= BUTTON_ATTACK; + } + else { + nextCmd->buttons &= ~BUTTON_ATTACK; + } + + if (testCrouchTime < gameLocal.time) { + testCrouchTime = gameLocal.time + rand()%4000; + testCrouchActive = !testCrouchActive; + } + + if (iFeelHoppy) { + nextCmd->upmove = idMath::ClampChar(127); + } + else { + if (testCrouchActive) { + nextCmd->upmove = idMath::ClampChar(-127); + } + else { + nextCmd->upmove = 0; + } + } + + idealAngles.pitch = idMath::AngleNormalize180(idealAngles.pitch); + idealAngles.yaw = idMath::AngleNormalize180(idealAngles.yaw); + idealAngles.roll = idMath::AngleNormalize180(idealAngles.roll); + SetUntransformedViewAngles(idAngles(0.0f, 0.0f, 0.0f)); + UpdateDeltaViewAngles(idealAngles); + UpdateOrientation(idealAngles); + PROFILE_STOP("hhArtificialPlayer::Think", PROFMASK_NORMAL); + hhPlayer::Think(); +} + +void hhArtificialPlayer::ClientPredictionThink( void ) { + gameLocal.usercmds[entityNumber] = lastAICmd; //copy over the last manually snapshotted usercmd first + hhPlayer::ClientPredictionThink(); +} + +void hhArtificialPlayer::WriteToSnapshot( idBitMsgDelta &msg ) const { + //instead of sync'ing, just mirror what the server has in ::Spawn + /* + msg.WriteDict(*gameLocal.GetUserInfo(entityNumber)); //ap's don't broadcast their info to the server, so the server otherwise + //will never broadcast it out to other clients + */ + + hhPlayer::WriteToSnapshot(msg); + + usercmd_t &cmd = gameLocal.usercmds[entityNumber]; + msg.WriteLong( cmd.gameTime ); + msg.WriteByte( cmd.buttons ); + msg.WriteShort( cmd.mx ); + msg.WriteShort( cmd.my ); + msg.WriteChar( cmd.forwardmove ); + msg.WriteChar( cmd.rightmove ); + msg.WriteChar( cmd.upmove ); + msg.WriteShort( cmd.angles[0] ); + msg.WriteShort( cmd.angles[1] ); + msg.WriteShort( cmd.angles[2] ); + + msg.WriteBits(spectating, 1); +} + +void hhArtificialPlayer::ReadFromSnapshot( const idBitMsgDelta &msg ) { + /* + ((idBitMsgDelta)msg).ReadDict(*((idDict *)gameLocal.GetUserInfo(entityNumber))); + if (!clientReceivedUI) { + UserInfoChanged( false ); + clientReceivedUI = true; + } + */ + + hhPlayer::ReadFromSnapshot(msg); + + usercmd_t &cmd = lastAICmd; + cmd.gameTime = msg.ReadLong(); + cmd.buttons = msg.ReadByte(); + cmd.mx = msg.ReadShort(); + cmd.my = msg.ReadShort(); + cmd.forwardmove = msg.ReadChar(); + cmd.rightmove = msg.ReadChar(); + cmd.upmove = msg.ReadChar(); + cmd.angles[0] = msg.ReadShort(); + cmd.angles[1] = msg.ReadShort(); + cmd.angles[2] = msg.ReadShort(); + + bool spec = !!msg.ReadBits(1); //doesn't go through right for ap's or something based on events sometimes, hack fix + if (spec != spectating) { + Spectate(spec); + } +} +//HUMANHEAD END + +void hhPlayer::DisableSpiritWalk(int timeout) { + nextSpiritTime = gameLocal.time + SEC2MS(timeout); + StopSpiritWalk(); +} + +//============================================================================= +// +// hhPlayer::Event_UpdateDDA +// +// CJR: Update the probability the player will die this tick, and +// adjust the difficulty accordingly +//============================================================================= + +void hhPlayer::Event_UpdateDDA() { + idEntity *ent; + int updateFlags; + + // Find the number of alive, non-dormant creatures attacking the player + ddaNumEnemies = 0; + ddaProbabilityAccum = 1.0f; + updateFlags = 0; + + for( ent = gameLocal.activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + if ( ent->fl.isDormant || !ent->IsType( hhMonsterAI::Type ) || ent->fl.hidden || ent->health <= 0 ) { + continue; + } + + hhMonsterAI *monster = static_cast(ent); + if ( monster->GetEnemy() != this ) { + continue; + } + + // PVS Check: only include creatures that the player can attack or be attacked by + if ( !gameLocal.InPlayerPVS( ent ) ) { + continue; + } + + // Accumulate the survival rate against each enemy + int index = monster->spawnArgs.GetInt( "ddaIndex", "0" ); + + if ( index == -1 ) { // Skip certain monsters, such as the vacuum or the crawlers + continue; + } + + float prob = gameLocal.GetDDA()->DDA_GetProbability( index, GetHealth() ); + + ddaProbabilityAccum *= prob; + + // Recompute the actual difficulty based upon the creatures in view + updateFlags |= ( 1 << index ); + + ddaNumEnemies++; + } + + gameLocal.GetDDA()->RecalculateDifficulty( updateFlags ); + + PostEventSec( &EV_UpdateDDA, spawnArgs.GetFloat( "updateDDARate", "0.25" ) ); + + if ( g_printDDA.GetBool() ) { + if ( ddaNumEnemies > 0 ){ + common->Printf("Probability [%.2f]\n", ddaProbabilityAccum ); + } + } +} + +void hhPlayer::Event_DisableSpirit(void) { + // HUMANHEAD PCF pdm 05-17-06: Removed assert, as scripters were potentially calling during deathwalk + // HUMANHEAD PCF pdm 05-17-06: Only exit spirit realm if spiritwalking, not deathwalking + + if ( IsSpiritWalking() ) { + StopSpiritWalk(); + } + + bAllowSpirit = false; +} + +void hhPlayer::Event_EnableSpirit(void) { + // HUMANHEAD PCF pdm 05-17-06: Removed assert + + bAllowSpirit = true; +} + +//HUMANHEAD bjk +void hhPlayer::ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ) { + // no need for these in sp + if( gameLocal.isMultiplayer ) { + idActor::ProjectOverlay( origin, dir, size, material ); + } +} + +// HUMANHEAD mdl: Ripped from idAI for short invulnerability after returning from deathwalk +void hhPlayer::Event_AllowDamage( void ) { + fl.takedamage = true; +} + +void hhPlayer::Event_IgnoreDamage( void ) { + fl.takedamage = false; +} + +// Precompute all the weapons' info for faster HUD updates +void hhPlayer::SetupWeaponInfo() { + const char *fireInfoName = NULL; + const idDeclEntityDef *decl = NULL; + const char *ammoName = NULL; + float maxAmmo; + + memset(weaponInfo, 0, sizeof(weaponInfo_t)*15); + memset(altWeaponInfo, 0, sizeof(weaponInfo_t)*15); + + for (int ix=0; ix<15; ix++) { + const char *weaponClassName = spawnArgs.GetString( va( "def_weapon%d", ix ), NULL ); + if ( weaponClassName && *weaponClassName ) { + + const idDeclEntityDef *weaponObjDecl = gameLocal.FindEntityDef( weaponClassName, false ); + if ( weaponObjDecl ) { + fireInfoName = weaponObjDecl->dict.GetString("def_fireInfo"); + decl = gameLocal.FindEntityDef( fireInfoName, false ); + if ( decl ) { + weaponInfo[ix].ammoType = inventory.AmmoIndexForAmmoClass( decl->dict.GetString( "ammoType" ) ); + weaponInfo[ix].ammoRequired = decl->dict.GetInt("ammoRequired"); + weaponInfo[ix].ammoLow = decl->dict.GetInt("lowAmmo"); + + ammoName = idWeapon::GetAmmoNameForNum( weaponInfo[ix].ammoType ); + maxAmmo = inventory.MaxAmmoForAmmoClass( this, ammoName ); + weaponInfo[ix].ammoMax = max(1, maxAmmo); + } + + fireInfoName = weaponObjDecl->dict.GetString("def_altFireInfo"); + decl = gameLocal.FindEntityDef( fireInfoName, false ); + if ( decl ) { + altWeaponInfo[ix].ammoType = inventory.AmmoIndexForAmmoClass( decl->dict.GetString( "ammoType" ) ); + altWeaponInfo[ix].ammoRequired = decl->dict.GetInt("ammoRequired"); + altWeaponInfo[ix].ammoLow = decl->dict.GetInt("lowAmmo"); + + ammoName = idWeapon::GetAmmoNameForNum( altWeaponInfo[ix].ammoType ); + maxAmmo = inventory.MaxAmmoForAmmoClass( this, ammoName ); + altWeaponInfo[ix].ammoMax = max(1, maxAmmo); + } + } + } + } +} + +/* +=============== +hhPlayer::ThrowGrenade +HUMANHEAD bjk +=============== +*/ +void hhPlayer::ThrowGrenade( void ) { + idMat3 axis; + idVec3 muzzle; + + if( inventory.HasAmmo(inventory.AmmoIndexForAmmoClass( "ammo_crawler" ), 1) == false ) + return; + + if (IsSpiritOrDeathwalking() || InVehicle() || bReallyDead) { + return; + } + + if ( privateCameraView || !weaponEnabled || spectating || gameLocal.inCinematic || health < 0 || gameLocal.isClient ) + return; + + if( weapon.IsValid() ) { + if ( inventory.weapons & ( 1 << 12 ) && currentWeapon != 12 && currentWeapon != 3 && idealWeapon == currentWeapon ) { + idealWeapon = 12; + previousWeapon = currentWeapon; + } + else if( currentWeapon == 3 ) { + FireWeapon(); + usercmd.buttons = usercmd.buttons | BUTTON_ATTACK; + } + } +} + +//HUMANHEAD bjk +void hhPlayer::Event_ReturnToWeapon() { + const char *weap; + + if ( !weaponEnabled || spectating || gameLocal.inCinematic || health < 0 ) { + idThread::ReturnInt(0); + return; + } + + if ( ( previousWeapon < 0 ) || ( previousWeapon >= MAX_WEAPONS ) ) { + idThread::ReturnInt(0); + return; + } + + if ( gameLocal.isClient ) { + idThread::ReturnInt(0); + return; + } + + weap = spawnArgs.GetString( va( "def_weapon%d", previousWeapon ) ); + if ( !weap[ 0 ] ) { + gameLocal.Printf( "Invalid weapon\n" ); + idThread::ReturnInt(0); + return; + } + + if ( inventory.weapons & ( 1 << previousWeapon ) ) { + idealWeapon = previousWeapon; + } + + idThread::ReturnInt(1); +} + +//HUMANHEAD rww +void hhPlayer::Event_CanAnimateTorso(void) { + if (!gameLocal.isMultiplayer) { //don't worry about it + idThread::ReturnInt(1); + return; + } + + if (!weapon.IsValid() || !weapon->IsType(hhWeapon::Type)) { //bad + idThread::ReturnInt(0); + return; + } + + idThread::ReturnInt(1); +} + +void hhPlayer::Show(void) { + idActor::Show(); + + hhWeapon *weap; + weap = weapon.GetEntity(); + if ( weap ) { + if (!IsLocked(currentWeapon)) { + weap->ShowWorldModel(); + } else { + weap->HideWorldModel(); + } + } +} diff --git a/src/Prey/game_player.h b/src/Prey/game_player.h new file mode 100644 index 0000000..740933f --- /dev/null +++ b/src/Prey/game_player.h @@ -0,0 +1,771 @@ + +#ifndef __PREY_GAME_PLAYER_H__ +#define __PREY_GAME_PLAYER_H__ + +extern const idEventDef EV_PlayWeaponAnim; +extern const idEventDef EV_Resurrect; +extern const idEventDef EV_PrepareToResurrect; +extern const idEventDef EV_SetOverlayMaterial; +extern const idEventDef EV_SetOverlayTime; +extern const idEventDef EV_SetOverlayColor; +extern const idEventDef EV_ShouldRemainAlignedToAxial; +extern const idEventDef EV_StartHudTranslation; +extern const idEventDef EV_StopSpiritWalk; //rww +extern const idEventDef EV_DamagePlayer; //rww + +// HUMANHEAD IMPULSES SHOULD START AT 50 +const int IMPULSE_50 = 50; // Unused +const int IMPULSE_51 = 51; // Unused +const int IMPULSE_52 = 52; // Unused +const int IMPULSE_53 = 53; // Unused +const int IMPULSE_54 = 54; // Spirit power toggle + +//Declared in idPlayer.cpp +extern const int RAGDOLL_DEATH_TIME; +extern const int LADDER_RUNG_DISTANCE; +extern const int HEALTH_PER_DOSE; +extern const int WEAPON_DROP_TIME; +extern const int WEAPON_SWITCH_DELAY; +extern const int SPECTATE_RAISE; +extern const int HEALTHPULSE_TIME; +extern const float MIN_BOB_SPEED; + +// Forward declaration +class hhSpiritProxy; +class hhConsole; +class hhTalon; +class hhPossessedTommy; + +#define MAX_HEALTH_NORMAL_MP 100 //rww - a probably temporary hack for trying out the pipe-as-armor concept + +#define HH_WEAPON_WRENCH 1 +#define HH_WEAPON_RIFLE 2 +#define HH_WEAPON_CRAWLER 4 +#define HH_WEAPON_AUTOCANNON 8 +#define HH_WEAPON_HIDERWEAPON 16 +#define HH_WEAPON_ROCKETLAUNCHER 32 +#define HH_WEAPON_SOULSTRIPPER 64 + +// Structure to hold all info about the weapons to remove the dictionary lookups every tick +typedef struct weaponInfo_s { + int ammoType; + int ammoLow; + int ammoMax; + int ammoRequired; +} weaponInfo_t; + +#define MAX_TRACKED_ATTACKERS 3 +typedef struct attackInfo_s { + idEntityPtr attacker; + int time; + bool displayed; +} attackInfo_t; + + +class hhPlayer : public idPlayer { + CLASS_PROTOTYPE(hhPlayer); + +public: + weaponInfo_t weaponInfo[15]; + weaponInfo_t altWeaponInfo[15]; + float lighterTemperature; // Temp of the lighter. 0 = cold, 1 = too hot to use + renderLight_t lighter; // lighter + int lighterHandle; + + idEntityPtr spiritProxy; // Proxy player when the player is spiritwalking + int lastWeaponSpirit; // Last weapon the player held before entering spirit mode + idEntityPtr talon; // Talon, the spirit hawk + int nextTalonAttackCommentTime; // Time between when Tommy can make talon comments + bool bTalonAttackComment; // If true, Tommy can randomly make comments when Talon is attacking + bool bSpiritWalk; // True if the player is spirit walking + bool bDeathWalk; // True if the player is dead and DeathWalking + bool bReallyDead; // True if the player truly died (only when in deathwalk mode) + idEntityPtr guiWantsControls; // Gui console that controls should be routed to + idEntityPtr possessedTommy; // ptr to the possessed version of the player + + //rww - keep track of who attacked us in air, so if we die from unnatural causes other than a player, + //the person who last attacked will be credited. + idEntityPtr airAttacker; + int airAttackerTime; + + float deathWalkFlash; + int deathWalkTime; // time the player entered deathwalk + int deathWalkPower; // Power in deathwalk - when full, the player wins deathwalk + bool bInDeathwalkTransition; + + + bool bInCinematic; + bool lockView; + + bool bPlayingLowHealthSound; + bool bPossessed; // True if the player can be possessed + float possessionTimer; // Countdown timer until the player is fully possessed + float possessionFOV; // FOV for possession + + int preCinematicWeapon; // selected weapon before cinematic started for restoration + int preCinematicWeaponFlags; // flags to restore after cinematic completes + + int lastDamagedTime; // Used to determine when to recharge health + + void ReportAttack(idEntity *attacker); + attackInfo_t lastAttackers[MAX_TRACKED_ATTACKERS]; // Used for tracking attackers for HUD + + //Make these idEntityPtr just to be safe + hhWeaponHandState weaponHandState; + idEntityPtr hand; + idEntityPtr handNext; // Next hand to pop up + hhPlayerVehicleInterface vehicleInterfaceLocal; + + // Death Variables + idEntityPtr deathLookAtEntity; // nla - Entity to look at when dead + idStr deathLookAtBone; // nla - Entity's bone to look at when dead + idStr deathCameraBone; + + hhCameraInterpolator cameraInterpolator; + idAngles oldCmdAngles; // HUMANHEAD: aob + bool bClampYaw; // HUMANHEAD pdm + float maxRelativeYaw; // HUMANHEAD pdm + float maxRelativePitch; // HUMANHEAD rww + + hhSoundLeadInController spiritwalkSoundController; + hhSoundLeadInController deathwalkSoundController; + hhSoundLeadInController wallwalkSoundController; + + bool bShowProgressBar; // Whether to display progress bar on HUDs + float progressBarValue; // Current progress (set by script) + idInterpolate progressBarGuiValue; // Interpolates towards progressBarValue + int progressBarState; // 0=none, 1=success, 2=failure + + bool bScopeView; // HUMANHEAD rww + + // HUMANHEAD bjk + float kickSpring; + float kickDamping; + // HUMANHEAD END + +#if GAMEPAD_SUPPORT // VENOM BEGIN +//SAVETODO: save these + int lastAutoLevelTime; + int lastAccelTime; + float lastAccelFactor; +#endif // VENOM END + + float bob; + int lastAppliedBobCycle; + int prevStepUpTime; + idVec3 prevStepUpOrigin; + + //Crashland + float crashlandSpeed_fatal; + float crashlandSpeed_soft; + float crashlandSpeed_jump; + + // CJR: DDA variables + int ddaNumEnemies; // CJR + float ddaProbabilityAccum; + + int forcePredictionButtons; //HUMANHEAD rww + + idScriptBool AI_ASIDE; + + // Methods + hhPlayer(); + void Spawn( void ); + virtual ~hhPlayer(); + + + // Overridden Methods + virtual void RestorePersistantInfo( void ); + virtual void SquishedByDoor(idEntity *door); + virtual void Init(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + virtual void SpawnToPoint( const idVec3 &spawn_origin, const idAngles &spawn_angles ); + virtual void Think( void ); + virtual void AdjustBodyAngles( void ); + virtual void Move( void ); + virtual void Teleport( const idVec3 &origin, const idAngles &angles, idEntity *destination ); + virtual void Teleport( const idVec3& origin, const idMat3& bboxAxis, const idVec3& viewDir, const idAngles& newUntransformedViewAngles, idEntity *destination ); + virtual void TeleportNoKillBox( const idVec3& origin, const idMat3& bboxAxis ); // HUMANHEAD cjr: A teleport that doesn't telefrag + virtual void TeleportNoKillBox( const idVec3& origin, const idMat3& bboxAxis, const idVec3& viewDir, const idAngles& newUntransformedViewAngles ); + virtual void LinkScriptVariables( void ); + virtual void Present(); + + virtual void UpdateFromPhysics( bool moveBack ); + virtual bool UpdateAnimationControllers( void ); + + virtual bool Give( const char *statname, const char *value ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + virtual bool ServerReceiveEvent( int event, int time, const idBitMsg &msg ); //rww - override idPlayer + + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); //rww + + virtual void WritePlayerStateToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadPlayerStateFromSnapshot( const idBitMsgDelta &msg ); + //rww - our own prediction function + virtual void ClientPredictionThink( void ); + + bool SetWeaponSpawnId( int id ) { return weapon.SetSpawnId( id ); } + + virtual void NextBestWeapon( void ); + virtual void SelectWeapon( int num, bool force ); + virtual void NextWeapon( void ); + virtual void PrevWeapon( void ); + virtual void UpdateWeapon( void ); + virtual void UpdateFocus( void ); + virtual void UpdateViewAngles( void ); + virtual void UpdateDeltaViewAngles( const idAngles &angles ); + virtual idAngles DetermineViewAngles( const usercmd_t& cmd, idAngles& cmdAngles ); + virtual bool HandleSingleGuiCommand(idEntity *entityGui, idLexer *src); + virtual void GetViewPos( idVec3 &origin, idMat3 &axis ); //HUMANHEAD bjk + + virtual void UpdateHud( idUserInterface *_hud ); + virtual void UpdateHudWeapon(bool flashWeapon = true); + virtual void UpdateHudAmmo(idUserInterface *_hud); + virtual void UpdateHudStats( idUserInterface *_hud ); + virtual void DrawHUD( idUserInterface *_hud ); + virtual void DrawHUDVehicle( idUserInterface* _hud );//HUMANHEAD: aob + virtual void Weapon_Combat( void ); + virtual void Weapon_GUI( void ); + virtual void PerformImpulse(int impulse); + virtual void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse ); + + virtual int HasAmmo( ammo_t type, int amount ); + virtual bool UseAmmo( ammo_t type, int amount ); + + virtual void CrashLand( const idVec3 &oldOrigin, const idVec3 &oldVelocity ); + virtual void BobCycle( const idVec3 &pushVelocity ); + virtual void OffsetThirdPersonView( float angle, float range, float height, bool clip ); + virtual void CalculateRenderView( 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 bool IsWallWalking( void ) const; + virtual bool WasWallWalking( void ) const; + virtual float GetStepHeight() const; + + virtual int GetWeaponNum( const char* weaponName ) const; + virtual const char* GetWeaponName( int num ) const; + + virtual idAngles GunTurningOffset( void ) { return idPlayer::GunTurningOffset(); } + virtual idVec3 GunAcceleratingOffset( void ) { return idPlayer::GunAcceleratingOffset(); } + + virtual void SetupWeaponEntity( void ); + + virtual void SetUntransformedViewAngles( const idAngles& newUntransformedViewAngles ) { untransformedViewAngles = newUntransformedViewAngles; } + virtual void SetUntransformedViewAxis( const idMat3& newUntransformedViewAxis ) { untransformedViewAxis = newUntransformedViewAxis; } + virtual const idAngles& GetUntransformedViewAngles() const; + virtual const idMat3& GetUntransformedViewAxis() const; + virtual idAngles GetViewAngles() const; + virtual void SetEyeAxis( const idMat3 &axis ); + virtual void SetEyeHeight( float height ); + virtual float EyeHeight( void ) const; + virtual idVec3 EyeOffset( void ) const; + virtual idMat3 GetEyeAxis() const; + virtual idAngles GetEyeAxisAsAngles() const; + virtual idQuat GetEyeAxisAsQuat() const; + void ShouldRemainAlignedToAxial( bool remainAligned ); + void OrientToGravity( bool bRotateToGravity ); + void SetOrientation( const idVec3& origin, const idMat3& bboxAxis, const idVec3& lookDir, const idAngles& newUntransformedViewAngles ); + void RestoreOrientation( const idVec3& origin, const idMat3& bboxAxis, const idVec3& lookDir, const idAngles& newUntransformedViewAngles ); + void UpdateOrientation( const idAngles& newUntransformedViewAngles ); + virtual bool CheckFOV( const idVec3 &pos ); + virtual bool CheckYawFOV( const idVec3 &pos ); + virtual idVec3 GetEyePosition( void ) const; + virtual idVec3 TransformToPlayerSpace( const idVec3& origin ) const; + virtual idVec3 TransformToPlayerSpaceNotInterpolated( const idVec3& origin ) const; //rww + virtual idMat3 TransformToPlayerSpace( const idMat3& axis ) const; + virtual idAngles TransformToPlayerSpace( const idAngles& angles ) const; + virtual bool ShouldTouchTrigger( idEntity* entity ) const; + + virtual void EnterVehicle( hhVehicle* vehicle ); + virtual void ExitVehicle( hhVehicle* vehicle ); + virtual void ResetClipModel(); + hhPlayerVehicleInterface* GetVehicleInterfaceLocal() { return &vehicleInterfaceLocal; } + virtual void BecameBound(hhBindController *b); // Just attached to a bindController + virtual void BecameUnbound(hhBindController *b); // Just detached from a bindController + + virtual void Possess( idEntity* possessor ); // cjr + virtual void Unpossess(); // cjr + virtual bool CanBePossessed( void ); // cjr + void PossessKilled( void ); // cjr + + virtual idVec3 GetPortalPoint( void ); // cjr: The entity will portal when this point crosses the portal plane. Origin for most, eye location for players + virtual void Portalled( idEntity *portal ); + void SetPortalColliding( bool b ) { bCollidingWithPortal = b; } + bool IsPortalColliding( void ) { return bCollidingWithPortal; } + + virtual void UpdateModelTransform( void );//aob + + virtual void PlayFootstepSound();//aob + virtual void PlayPainSound();//aob + + bool IsCrouching() const { return physicsObj.IsCrouching(); } + virtual void ForceCrouching() { physicsObj.ForceCrouching(); }//aob + virtual void FillDebugVars( idDict *args, int page ); + virtual bool ChangingWeapons() const { return idealWeapon != currentWeapon; } + void BufferLoggedViewAngles( const idAngles& newUntransformedViewAngles ); + virtual void GetPilotInput( usercmd_t& pilotCmds, idAngles& pilotViewAngles ); + + void UpdateDDA(); // CJR DDA + + virtual void UpdateLocation( void ); + + // Unique Methods + void SetupWeaponInfo(); + void DialogStart(bool bDisallowPlayerDeath, bool bVoiceDucking, bool bLowerWeapon); + void DialogStop(); + void DoDeathDrop(); + void GetLocationText( idStr &locationString ); + void TrySpawnTalon(); + void TalonAttackComment(); // cjr - comment on Talon attacking + void RemoveResources(); + int GetCurrentWeapon( void ) const; + void SetCurrentWeapon( const int _currentWeapon ); + int GetIdealWeapon( void ) const; + void InvalidateCurrentWeapon() { currentWeapon = -1; } + virtual bool ShouldRemainAlignedToAxial() const { return physicsObj.ShouldRemainAlignedToAxial(); } + virtual idVec3 ApplyLandDeflect( const idVec3& pos, float scale ); + virtual idVec3 ApplyBobCycle( const idVec3& pos, const idVec3& velocity ); + virtual void DetermineOwnerPosition( idVec3 &ownerOrigin, idMat3 &ownerAxis ); + void StartSpiritWalk( const bool bThrust, bool force = false ); // mdl: Added force for when the player is possessed + void StopSpiritWalk(bool forceAllowance = false); //rww - added forceAllowance + void ToggleSpiritWalk( void ); + + int GetDeathWalkPower( void ) { return deathWalkPower; } // cjr: used for determining winning deathwalk + void SetDeathWalkPower( int newPower ) { deathWalkPower = newPower; } // cjr: used for determining winning deathwalk + + void ToggleLighter( void ); + void LighterOn(); + void LighterOff(); + bool IsLighterOn() const; //HUMANHEAD PCF mdl 05/04/06 - Made const + void UpdateLighter(); + void ThrowGrenade(); + void Event_ReturnToWeapon(); + void EnableEthereal( const char *proxyName, const idVec3& origin, const idMat3& bboxAxis, const idMat3& newViewAxis, const idAngles& newViewAngles, const idMat3& newEyeAxis ); + void DisableEthereal( void ); + void DisableSpiritWalk( int timeout ); // Number of seconds to disable ethereal mode + void GetResurrectionPoint( idVec3& origin, idMat3& axis, idVec3& viewDir, idAngles& angles, idMat3& eyeAxis, const idBounds& absBounds, const idVec3& defaultOrigin, const idMat3& defaultAxis, const idVec3& defaultViewDir, const idAngles& defaultAngles ); + void UpdatePossession( void ); // cjr + virtual void Kill( bool delayRespawn, bool nodamage ); // aob + void DeathWalk( const idVec3& resurrectOrigin, const idMat3& resurrectAxis, const idMat3& resurrectViewAxis, const idAngles& resurrectAngles, const idMat3& resurrectEyeAxis ); // cjr: entering deathwalk mode + idEntity * GetDeathwalkEnergyDestination(); + void DeathWraithEnergyArived(bool energyHealth); + void DeathWalkDamagedByWraith(idEntity *attacker, const char *damageType); + void DeathWalkSuccess(); + bool DeathWalkStage2() { return bDeathWalkStage2; } + void ReallyKilled( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); // cjr: truly dead (killed while in deathwalk mode) + void Resurrect( void ); + void KilledDeathWraith( void ); // cjr: called when the player kills a deathwraith (determines if the player should resurrect) + virtual bool IsDead() const { return (!bDeathWalk && health <= 0); } // True if the player is really dead + virtual bool IsSpiritOrDeathwalking() const { return ( bSpiritWalk || bDeathWalk ); } // True if the player is spiritwalking or deathwalking + virtual bool IsSpiritWalking() const { return ( bSpiritWalk && !bDeathWalk ); } // True if the player is spiritwalking, but not deathwalking + virtual bool IsDeathWalking() const { return bDeathWalk; } // True if the player is deathwalking + virtual bool IsPossessed() const { return bPossessed; } + void SnapDownCurrentWeapon(); + void SnapUpCurrentWeapon(); + void SelectEtherealWeapon(); + void PutawayEtherealWeapon(); + bool SkipWeapon( int weaponNum ) const; + hhWeapon* SpawnWeapon( const char* name ); + virtual bool InGUIMode( ); // nla + void CL_UpdateProgress(bool bBar, float value, int state); + int GetSpiritPower(); + void SetSpiritPower(int amount); + + void ResetZoomFov() { zoomFov.Init( gameLocal.GetTime(), 0, g_fov.GetFloat(), g_fov.GetFloat() ); } + virtual float CalcFov( bool honorZoom ); + virtual void MangleControls(usercmd_t *cmd); + virtual void UpdateCrosshairs( void ); + + void SetOverlayGui(const char *guiName); + + void InCinematic( bool cinematic ) { bInCinematic = cinematic; } + bool InCinematic() const { return bInCinematic; } + + const char * GetGuiHandInfo(); // nla + + idInterpolate& GetZoomFov() { return zoomFov; } + + void SetViewBob(const idVec3 &v) {viewBob = v;} + int GetLastResurrectTime(void) const {return lastResurrectTime;} // Time stamp indicating when the player last was resurrected + virtual idVec3 GetAimPosition() const; // jrm + + void SetViewAnglesSensitivity( float factor ); + float GetViewAnglesSensitivity() const; + + //? Is there a better way of doing this? + // Force the weapon num... + virtual void ForceWeapon( int weaponNum ); + virtual void ForceWeapon( hhWeapon *newWeapon ); + void Freeze( float unfreezeDelay = 0.0f ); + void Unfreeze(void); + bool IsFrozen() { return bFrozen; }; + + void LockWeapon( int weaponNum ); + void UnlockWeapon( int weaponNum ); + bool IsLocked( int weaponNum ); + hhSpiritProxy* GetSpiritProxy() { return spiritProxy.IsValid() ? spiritProxy.GetEntity() : NULL; } + + //rww - instead of doing this through the death proxy, this is safer (being an af, the death proxy could be oriented strangely or removed in numerous ways) + virtual void RestorePlayerLocationFromDeathwalk( const idVec3& origin, const idMat3& bboxAxis, const idVec3& viewDir, const idAngles& angles ); + + virtual void ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ); + + int mpHitFeedbackTime; //HUMANHEAD rww + + virtual void Show(); + +protected: + idUserInterface * guiOverlay; + idClipModel thirdPersonCameraClipBounds; + float viewAnglesSensitivity; + int lastResurrectTime; + int spiritDrainHeartbeatMS; + int ddaHeartbeatMS; + int spiritWalkToggleTime; + bool bDeathWalkStage2; + bool bFrozen; + int weaponFlags; // Flags to limit weapons on specific level -mdl + int nextSpiritTime; // Disable ethereal mode unless gameLocal.time is greater than this + idInterpolateAccelDecelSine cinematicFOV; // HUMANHEAD rdr: Interpolated FOV for scripter use + bool bAllowSpirit; + + bool bCollidingWithPortal; // CJR + bool bLotaTunnelMode; // Used to disable hud during LOTA tunnel transitions + + // HUMANHEAD: aob + idAngles untransformedViewAngles; + idMat3 untransformedViewAxis; + // HUMANHEAD END + + //HUMANHEAD rww + idVec3 deathwalkLastOrigin; + idMat3 deathwalkLastBBoxAxis; + idMat3 deathwalkLastViewAxis; + idAngles deathwalkLastViewAngles; + idMat3 deathwalkLastEyeAxis; + bool deathwalkLastCrouching; + //HUMANHEAD END + + // Overridden Methods + virtual int GetMaxHealth() { return inventory.maxHealth; } + + // Unique Methods + virtual void FireWeapon( void ); + virtual void FireWeaponAlt( void ); + + void StopFiring( void ); + + void SetupWeaponFlags( void ); + + void Event_LotaTunnelMode(bool on); + void Event_PlayWeaponAnim( const char* animName, int numTries = 0 ); + void Event_DrainSpiritPower(); // HUMANHEAD pdm + + void Event_RechargeHealth( void ); // cjr + void Event_RechargeRifleAmmo( void ); // cjr + + void Event_SpawnDeathWraith(); // cjr + void Event_AdjustSpiritPowerDeathWalk(); + void Event_PrepareForDeathWorld(); + void Event_EnterDeathWorld(); // cjr: set-up for teleporting the player to the deathworld + void Event_PrepareToResurrect(); + void Event_ResurrectScreenFade(); + void Event_Resurrect(); + virtual void Event_GetSpiritPower(); //rww + virtual void Event_SetSpiritPower(const float s); //rww + void Event_OnGround(); // bg + + void Event_Cinematic( int on, int lockView ); // pdm + void Event_DialogStart( int bDisallowPlayerDeath, int bVoiceDucking, int bLowerWeapon ); + void Event_DialogStop(); + void Event_ShouldRemainAlignedToAxial( bool remainAligned ); + void Event_OrientToGravity( bool orient ); + virtual void Event_ResetGravity(); + void Event_SetOverlayMaterial( const char *mtrName, const int requiresScratch ); + void Event_SetOverlayTime( const float newTime, const int requiresScratch ); + void Event_SetOverlayColor( const float r, const float g, const float b, const float a ); + + void Event_DDAHeartBeat(); + void Event_StartHUDTranslation(); + void Event_Unfreeze(); + void Event_LockWeapon( int weaponNum ); + void Event_UnlockWeapon( int weaponNum ); + + void Event_SetPrivateCameraView( idEntity *camView, int noHide ); //HUMANHEAD rdr + void Event_SetCinematicFOV( float fieldOfView, float accelTime, float decelTime, float duration ); //HUMANHEAD rdr + void Event_StopSpiritWalk(); //rww + void Event_DamagePlayer(idEntity *inflictor, idEntity *attacker, const idVec3 &dir, char *damageDefName, float damageScale, int location); //rww + void Event_GetSpiritProxy(); + void Event_IsSpiritWalking(); + void Event_IsDeathWalking(); //bjk + void Event_GetDDAValue(); // cjr + void Event_AllowLighter( bool allow ); + void Event_DisableSpirit(); + void Event_EnableSpirit(); + void Event_CanAnimateTorso(void); //HUMANHEAD rww + + void Event_UpdateDDA(); // cjr + + void Event_AllowDamage(); // mdl + void Event_IgnoreDamage(); // mdl + + void Event_RespawnCleanup(void); //HUMANHEAD rww +}; + +/* +===================== +hhPlayer::SetEyeAxis +===================== +*/ +ID_INLINE void hhPlayer::SetEyeAxis( const idMat3 &axis ) { + if (!cameraInterpolator.GetIdealAxis().Compare(axis)) { + cameraInterpolator.SetTargetAxis( axis, INTERPOLATE_EYEOFFSET ); + } +} + +/* +===================== +hhPlayer::SetEyeHeight +===================== +*/ +ID_INLINE void hhPlayer::SetEyeHeight( float height ) { + idPlayer::SetEyeHeight( height ); + + cameraInterpolator.SetTargetEyeOffset( height, INTERPOLATE_EYEOFFSET ); +} + +/* +===================== +hhPlayer::EyeHeight +===================== +*/ +ID_INLINE float hhPlayer::EyeHeight( void ) const { + return cameraInterpolator.GetCurrentEyeHeight(); +} + +/* +===================== +hhPlayer::EyeOffset +===================== +*/ +ID_INLINE idVec3 hhPlayer::EyeOffset( void ) const {//HUMANHEAD + return cameraInterpolator.GetCurrentEyeOffset(); +} + +/* +===================== +hhPlayer::ShouldRemainAlignedToAxial + HUMANHEAD +===================== +*/ +ID_INLINE void hhPlayer::ShouldRemainAlignedToAxial( bool remainAligned ) {//HUMANHEAD + physicsObj.ShouldRemainAlignedToAxial( remainAligned ); +} + +/* +===================== +hhPlayer::OrientToGravity + HUMANHEAD +===================== +*/ +ID_INLINE void hhPlayer::OrientToGravity( bool orientToGravity ) {//HUMANHEAD + physicsObj.OrientToGravity( orientToGravity ); +} + +/* +===================== +hhPlayer::EyePosition + HUMANHEAD +===================== +*/ +ID_INLINE idVec3 hhPlayer::GetEyePosition( void ) const {//HUMANHEAD + return cameraInterpolator.GetEyePosition(); +} + +/* +===================== +hhPlayer::GetEyeAxis + HUMANHEAD +===================== +*/ +ID_INLINE idMat3 hhPlayer::GetEyeAxis() const { + return cameraInterpolator.GetCurrentAxis(); +} + +/* +===================== +hhPlayer::GetEyeAxisAsAngles + HUMANHEAD +===================== +*/ +ID_INLINE idAngles hhPlayer::GetEyeAxisAsAngles() const { + return cameraInterpolator.GetCurrentAngles(); +} + +/* +===================== +hhPlayer::GetEyeAxisAsQuat + HUMANHEAD +===================== +*/ +ID_INLINE idQuat hhPlayer::GetEyeAxisAsQuat() const { + return cameraInterpolator.GetCurrentRotation(); +} + +/* +===================== +hhPlayer::GetStepHeight + HUMANHEAD +===================== +*/ +ID_INLINE float hhPlayer::GetStepHeight() const { + if( IsWallWalking() ) { + return pm_wallwalkstepsize.GetFloat(); + } + + return pm_stepsize.GetFloat(); +} + +/* +===================== +hhPlayer::TransformToPlayerSpace + HUMANHEAD +===================== +*/ +ID_INLINE idVec3 hhPlayer::TransformToPlayerSpace( const idVec3& origin ) const { + return cameraInterpolator.GetCurrentPosition() + origin * GetEyeAxis(); +} + +/* +===================== +hhPlayer::TransformToPlayerSpaceNotInterpolated + HUMANHEAD rww +===================== +*/ +ID_INLINE idVec3 hhPlayer::TransformToPlayerSpaceNotInterpolated( const idVec3& origin ) const { + return cameraInterpolator.GetIdealPosition() + origin * cameraInterpolator.GetIdealAxis(); +} + +/* +===================== +hhPlayer::TransformToPlayerSpace + HUMANHEAD +===================== +*/ +ID_INLINE idMat3 hhPlayer::TransformToPlayerSpace( const idMat3& axis ) const { + return axis * GetEyeAxis(); +} + +/* +===================== +hhPlayer::TransformToPlayerSpace + HUMANHEAD +===================== +*/ +ID_INLINE idAngles hhPlayer::TransformToPlayerSpace( const idAngles& angles ) const { + return (angles.ToMat3() * GetEyeAxis()).ToAngles(); +} + +/* +===================== +hhPlayer::GetCurrentWeapon + HUMANHEAD +===================== +*/ +ID_INLINE int hhPlayer::GetCurrentWeapon( void ) const { + return currentWeapon; +} + +/* +===================== +hhPlayer::SetCurrentWeapon + HUMANHEAD +===================== +*/ +ID_INLINE void hhPlayer::SetCurrentWeapon( const int _currentWeapon ) { + if( _currentWeapon >= 0 && _currentWeapon < MAX_WEAPONS ) { + currentWeapon = _currentWeapon; + } +} + +/* +===================== +hhPlayer::GetIdealWeapon + HUMANHEAD +===================== +*/ +ID_INLINE int hhPlayer::GetIdealWeapon( void ) const { + return idealWeapon; +} + +/* +===================== +hhPlayer::GetUntransformedViewAngles + HUMANHEAD +===================== +*/ +ID_INLINE const idAngles& hhPlayer::GetUntransformedViewAngles() const { + return untransformedViewAngles; +} + +/* +===================== +hhPlayer::GetUntransformedViewAxis + HUMANHEAD +===================== +*/ +ID_INLINE const idMat3& hhPlayer::GetUntransformedViewAxis() const { + return untransformedViewAxis; +} + +/* +===================== +hhPlayer::GetViewAngles + HUMANHEAD +===================== +*/ +ID_INLINE idAngles hhPlayer::GetViewAngles() const { + return viewAngles; +} + +/* +===================== +hhPlayer::GetPortalPoint + HUMANHEAD +===================== +*/ +ID_INLINE idVec3 hhPlayer::GetPortalPoint( void ) { + return idEntity::GetPortalPoint(); +/* + idVec3 origin; + idMat3 axis; + + GetViewPos( origin, axis ); + return origin; // Compensate for the near-clip plane +*/ +} + +//HUMANHEAD rww +class hhArtificialPlayer : public hhPlayer { + CLASS_PROTOTYPE(hhArtificialPlayer); +public: + hhArtificialPlayer(void); + + void Spawn( void ); + virtual void Think( void ); + + virtual void ClientPredictionThink( void ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + +protected: + usercmd_t lastAICmd; + + bool testCrouchActive; + int testCrouchTime; +}; +//HUMANHEAD END +#endif /* __PREY_GAME_PLAYER_H__ */ diff --git a/src/Prey/game_playerview.cpp b/src/Prey/game_playerview.cpp new file mode 100644 index 0000000..43db729 --- /dev/null +++ b/src/Prey/game_playerview.cpp @@ -0,0 +1,748 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +#define LETTERBOX_HEIGHT_TOP 50 +#define LETTERBOX_HEIGHT_BOTTOM 50 + +const int IMPULSE_DELAY = 150; + +//HUMANHEAD rww - render demo madness +#if _HH_RENDERDEMO_HACKS + void RENDER_DEMO_VIEWRENDER(const renderView_t *view, const hhPlayerView *pView) { + if (!view) { + return; + } + + const renderView_t *v = view; + + if (pView) { + static renderView_t hackedView = *view; + hackedView.viewaxis = hackedView.viewaxis * pView->ShakeAxis(); + + v = &hackedView; + } + + renderSystem->LogViewRender(v); + } + + void RENDER_DEMO_VIEWRENDER_END(void) { + renderSystem->LogViewRender(NULL); + } +#endif +//HUMANHEAD END + +hhPlayerView::hhPlayerView() { + // HUMANHEAD pdm: we don't use the tunnel vision or armor + bLetterBox = false; + letterboxMaterial = declManager->FindMaterial( "_black" ); + dirDmgLeftMaterial = declManager->FindMaterial( "textures/interface/directionalDamageLeft" ); + dirDmgFrontMaterial = declManager->FindMaterial( "textures/interface/directionalDamageFront" ); + spiritMaterial = NULL; + viewOverlayMaterial = NULL; + viewOverlayColor = colorWhite; + voTotalTime = -1; + voRequiresScratchBuffer = false; + viewOffset.Zero(); + + kickSpeed.Zero(); + kickLastTime = 0; + hurtValue = 100.f; +} + +void hhPlayerView::Save(idSaveGame *savefile) const { + idPlayerView::Save( savefile ); + + savefile->WriteBool( bLetterBox ); + savefile->WriteFloat( mbAmplitude ); + savefile->WriteInt( mbFinishTime ); + savefile->WriteInt( mbTotalTime ); + savefile->WriteVec3( mbDirection ); + savefile->WriteVec3( viewOffset ); + savefile->WriteMaterial( viewOverlayMaterial ); + savefile->WriteVec4( viewOverlayColor ); + savefile->WriteInt( voFinishTime ); + savefile->WriteInt( voTotalTime ); + savefile->WriteInt( voRequiresScratchBuffer ); + savefile->WriteMaterial( letterboxMaterial ); + savefile->WriteMaterial( dirDmgLeftMaterial ); + savefile->WriteMaterial( dirDmgFrontMaterial ); + savefile->WriteMaterial( spiritMaterial ); + savefile->WriteVec3( lastDamageLocation ); + savefile->WriteInt( kickLastTime - gameLocal.time ); + savefile->WriteAngles( kickSpeed ); + savefile->WriteFloat( hurtValue ); +} + +void hhPlayerView::Restore( idRestoreGame *savefile ) { + idPlayerView::Restore( savefile ); + + savefile->ReadBool( bLetterBox ); + savefile->ReadFloat( mbAmplitude ); + savefile->ReadInt( mbFinishTime ); + savefile->ReadInt( mbTotalTime ); + savefile->ReadVec3( mbDirection ); + savefile->ReadVec3( viewOffset ); + savefile->ReadMaterial( viewOverlayMaterial ); + savefile->ReadVec4( viewOverlayColor ); + savefile->ReadInt( voFinishTime ); + savefile->ReadInt( voTotalTime ); + savefile->ReadInt( voRequiresScratchBuffer ); + savefile->ReadMaterial( letterboxMaterial ); + savefile->ReadMaterial( dirDmgLeftMaterial ); + savefile->ReadMaterial( dirDmgFrontMaterial ); + savefile->ReadMaterial( spiritMaterial ); + savefile->ReadVec3( lastDamageLocation ); + savefile->ReadInt( kickLastTime ); + kickLastTime += gameLocal.time; + savefile->ReadAngles( kickSpeed ); + savefile->ReadFloat( hurtValue ); +} + +#define MAX_SCREEN_BLOBS_SYNC 2//MAX_SCREEN_BLOBS + +//rwwFIXME it may be possible to remove a lot of this junk by assuring events happen on the client as they do on the server +//concerning damage etc. +void hhPlayerView::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteFloat(viewOffset.x); + msg.WriteFloat(viewOffset.y); + msg.WriteFloat(viewOffset.z); + + msg.WriteBits(voTotalTime, 32); + msg.WriteBits(voFinishTime, 32); + msg.WriteBits(voRequiresScratchBuffer, 1); + + if (viewOverlayMaterial) { + msg.WriteLong(gameLocal.ServerRemapDecl(-1, DECL_MATERIAL, viewOverlayMaterial->Index())); + } + else { + msg.WriteLong(-1); + } + + //write screen blobs to snapshot + for (int i = 0; i < MAX_SCREEN_BLOBS_SYNC; i++) { + const screenBlob_t *blob = &screenBlobs[i]; + + //i'm disabling this since it might work against delta compression being effective + /* + if ( blob->finishTime <= gameLocal.time ) { + msg.WriteBits(0, 1); //no need to do anymore, continue + continue; + } + + //otherwise continue writing + msg.WriteBits(1, 1); + */ + + if (blob->material) { + msg.WriteLong(gameLocal.ServerRemapDecl(-1, DECL_MATERIAL, blob->material->Index())); + } + else { + msg.WriteLong(-1); + } + + msg.WriteFloat(blob->x); + msg.WriteFloat(blob->y); + msg.WriteFloat(blob->w); + msg.WriteFloat(blob->h); + + msg.WriteFloat(blob->s1); + msg.WriteFloat(blob->t1); + msg.WriteFloat(blob->s2); + msg.WriteFloat(blob->t2); + + msg.WriteBits(blob->finishTime, 32); + msg.WriteBits(blob->startFadeTime, 32); + + msg.WriteFloat(blob->driftAmount); + } + + //motion blur + msg.WriteFloat(mbAmplitude); + msg.WriteBits(mbFinishTime, 32); + msg.WriteBits(mbTotalTime, 32); + msg.WriteFloat(mbDirection.x); + msg.WriteFloat(mbDirection.y); + msg.WriteFloat(mbDirection.z); + + msg.WriteFloat(viewOverlayColor.w); + msg.WriteFloat(viewOverlayColor.x); + msg.WriteFloat(viewOverlayColor.y); + msg.WriteFloat(viewOverlayColor.z); +} + +void hhPlayerView::ReadFromSnapshot( const idBitMsgDelta &msg ) { + viewOffset.x = msg.ReadFloat(); + viewOffset.y = msg.ReadFloat(); + viewOffset.z = msg.ReadFloat(); + + voTotalTime = msg.ReadBits(32); + voFinishTime = msg.ReadBits(32); + voRequiresScratchBuffer = !!msg.ReadBits(1); + + int matIndex = msg.ReadLong(); + if (matIndex == -1) { + viewOverlayMaterial = NULL; + } + else { + int mappedIndex; + + if (voTotalTime != -1 && gameLocal.time >= voFinishTime) { //it's timed out so force it off + voTotalTime = -1; + voRequiresScratchBuffer = false; + viewOverlayMaterial = NULL; + mappedIndex = -1; + } + else { + mappedIndex = gameLocal.ClientRemapDecl(DECL_MATERIAL, matIndex); + } + + if (mappedIndex != -1) { + viewOverlayMaterial = static_cast(declManager->DeclByIndex(DECL_MATERIAL, mappedIndex)); + } + else { + viewOverlayMaterial = NULL; + } + } + + //read screen blobs from snapshot + for (int i = 0; i < MAX_SCREEN_BLOBS_SYNC; i++) { + screenBlob_t *blob = &screenBlobs[i]; + + //i'm disabling this since it might work against delta compression being effective + /* + bool valid = !!msg.ReadBits(1); + if (!valid) { + continue; + } + */ + + int blobMat = msg.ReadLong(); + if (blobMat == -1) { + blob->material = NULL; + } + else { + int mappedIndex = gameLocal.ClientRemapDecl(DECL_MATERIAL, blobMat); + if (mappedIndex != -1) { + blob->material = static_cast(declManager->DeclByIndex(DECL_MATERIAL, mappedIndex)); + } + else { + blob->material = NULL; + } + } + + blob->x = msg.ReadFloat(); + blob->y = msg.ReadFloat(); + blob->w = msg.ReadFloat(); + blob->h = msg.ReadFloat(); + + blob->s1 = msg.ReadFloat(); + blob->t1 = msg.ReadFloat(); + blob->s2 = msg.ReadFloat(); + blob->t2 = msg.ReadFloat(); + + blob->finishTime = msg.ReadBits(32); + blob->startFadeTime = msg.ReadBits(32); + + blob->driftAmount = msg.ReadFloat(); + } + + //motion blur + mbAmplitude = msg.ReadFloat(); + mbFinishTime = msg.ReadBits(32); + mbTotalTime = msg.ReadBits(32); + mbDirection.x = msg.ReadFloat(); + mbDirection.y = msg.ReadFloat(); + mbDirection.z = msg.ReadFloat(); + + viewOverlayColor.w = msg.ReadFloat(); + viewOverlayColor.x = msg.ReadFloat(); + viewOverlayColor.y = msg.ReadFloat(); + viewOverlayColor.z = msg.ReadFloat(); +} + +void hhPlayerView::ClearEffects() { + idPlayerView::ClearEffects(); + + // HUMANHEAD pdm + mbFinishTime = gameLocal.time; +} + + +void hhPlayerView::SetDamageLoc(const idVec3 &damageLoc) { + // This called before damageImpulse() so store the location + lastDamageLocation = damageLoc; +} + + +//------------------------------------------------------ +// +// DamageImpulse +// +// LocalKickDir is the direction of force in the player's coordinate system, +// which will determine the head kick direction +//------------------------------------------------------ +void hhPlayerView::DamageImpulse( idVec3 localKickDir, const idDict *damageDef ) { + + // No screen damage effects when in third person + if (pm_thirdPerson.GetBool()) { + return; + } + + if ( lastDamageTime > 0.0f && SEC2MS( lastDamageTime ) + IMPULSE_DELAY > gameLocal.time ) { + // keep shotgun from obliterating the view + return; + } + + if (!player->InVehicle()) { + // + // Motion Blur effect + // HUMANHEAD pdm + float mbTime = damageDef->GetFloat( "mb_time" ); + float severity = damageDef->GetFloat( "mb_amplitude" ); + if ( mbTime ) { + idVec3 blurDirection; + blurDirection.y = localKickDir[0]; // forward/back kick will blur vertically + blurDirection.x = localKickDir[1]; // side kick will blur horizontally + blurDirection.y += localKickDir[2]; // up/down kick will add to vertical + MotionBlur(mbTime, severity, blurDirection); + } + + // + // head angle kick + // + 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; + } + } + + // + // HUMANHEAD: Screen blobs, changed functionality + // + 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 ); + + // Scale blob by 100% +/- blob_devscale% + float scale = ( 256.0f + ( 256.0f*damageDef->GetFloat("blob_devscale")*gameLocal.random.CRandomFloat() ) ) / 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; + + if (damageDef->GetBool("blob_projected") && player->renderView) { + //rww - renderView null check added. I am not clear on when this may be null, but it apparently was. however, if a player were + //to take damage before thinking, it seems very possible this would occur. the renderView will not be initialized until after + //the first think. + // Project hit location onto screen + idVec3 localDamageLocation = hhUtils::ProjectOntoScreen(lastDamageLocation, *player->renderView); + blob->x = localDamageLocation.x - blob->w * 0.5f; + blob->y = localDamageLocation.y - blob->h * 0.5f; + } + else if (damageDef->GetBool("blob_directional")) { + // Directional blobs to show where damage is coming from (360 degree) + float dirDist = damageDef->GetFloat("blob_dirdistance"); + blob->x = 320.0f + (dirDist * 320.0f * localKickDir.y) - (blob->w * 0.5f); + blob->y = 240.0f + (dirDist * 240.0f * localKickDir.x) - (blob->h * 0.5f); + } + else { + // Place blob centered at (blob_x,blob_y) + blob->x = damageDef->GetFloat( "blob_x" ) - blob->w * 0.5f; + blob->y = damageDef->GetFloat( "blob_y" ) - blob->h * 0.5f; + } + + // Deviate blob +/- (blob_devx,blob_devy) + blob->x += damageDef->GetFloat( "blob_devx" ) * gameLocal.random.CRandomFloat(); + blob->y += damageDef->GetFloat( "blob_devy" ) * gameLocal.random.CRandomFloat(); + } + } + + + // + // Global directional damage system + // +/* + const int directionDamageTime = 1000; + screenBlob_t *blob = GetScreenBlob(); + blob->startFadeTime = gameLocal.time; + blob->finishTime = gameLocal.time + directionDamageTime; + + blob->s1 = 0; + blob->t1 = 0; + blob->s2 = 1; + blob->t2 = 1; + if (idMath::Fabs(localKickDir[0]) >= idMath::Fabs(localKickDir[1])) { + // More in X direction + blob->material = dirDmgFrontMaterial; + blob->w = 500.0f; + blob->h = 80.0f; + blob->x = 320.0f - blob->w * 0.5f; + if (localKickDir[0] >= 0.0f) { + // From Rear + blob->y = 480.0f - blob->h; + idSwap(blob->t1, blob->t2); + } + else { + // From Front + blob->y = 0.0f; + } + } + else { + // More in Y direction + blob->material = dirDmgLeftMaterial; + blob->w = 80.0f; + blob->h = 400.0f; + blob->y = 240.0f - blob->h * 0.5f; + if (localKickDir[1] >= 0.0f) { + // From Right + blob->x = 640.0f - blob->w; + idSwap(blob->s1, blob->s2); + } + else { + // From Left + blob->x = 0.0f; + } + } +*/ + + // + // save lastDamageTime for tunnel vision accentuation + // + lastDamageTime = MS2SEC( gameLocal.time ); + +} + +//------------------------------------------------------ +// WeaponFireFeedback +// HUMANHEAD bjk +//------------------------------------------------------ +void hhPlayerView::WeaponFireFeedback( const idDict *weaponDef ) { + idAngles angles; + idVec2 pitch, yaw, viewSpring; + weaponDef->GetVec2( "recoilPitch", "0 0", pitch ); + weaponDef->GetVec2( "recoilYaw", "0 0", yaw ); + weaponDef->GetVec2( "viewSpring", "150 11", viewSpring ); + + player->kickSpring = viewSpring.x; + player->kickDamping = viewSpring.y; + + pitch.x = pitch.x + gameLocal.random.RandomFloat()*(pitch.y - pitch.x); + yaw.x = yaw.x + gameLocal.random.RandomFloat()*(yaw.y - yaw.x); + angles=idAngles(-pitch.x,-yaw.x,0); + kickSpeed+=angles; + assert(!FLOAT_IS_NAN(kickSpeed.pitch) && !FLOAT_IS_NAN(kickSpeed.yaw) && !FLOAT_IS_NAN(kickSpeed.roll)); +} + +//------------------------------------------------------ +// AngleOffset +// HUMANHEAD bjk +//------------------------------------------------------ +idAngles hhPlayerView::AngleOffset(float kickSpring, float kickDamping) { + //HUMANHEAD rww - for other clients, do not add angle offset, it isn't predicted well + if (gameLocal.isMultiplayer && !gameLocal.isServer && gameLocal.localClientNum != -1 && player && gameLocal.localClientNum != player->entityNumber) { + kickSpeed.Zero(); + kickAngles.Zero(); + return kickAngles; + } + //HUMANHEAD END + + float frametime = (gameLocal.time - kickLastTime); + kickLastTime = gameLocal.time; + //kickAngles = kickAngles*(1-offset*kickSpeed)+kickBlendTo*offset*kickSpeed; + //kickBlendTo = kickBlendTo*(1-offset*kickReturnSpeed)+ang*offset*kickReturnSpeed; + + //HUMANHEAD PCF rww 04/27/06 - these values can get unreasonable at times +#if 0 + for (int i = 0; i < 3; i++) { + if (fabsf(kickSpeed[i]) > 9999.0f) { + kickSpeed[i] = 0.0f; + } + if (fabsf(kickAngles[i]) > 9999.0f) { + kickAngles[i] = 0.0f; + } + } +#endif + if (frametime > 48.0f) { + frametime = 48.0f; + } + //HUMANHEAD END + + assert(!FLOAT_IS_NAN(kickSpeed.pitch) && !FLOAT_IS_NAN(kickSpeed.yaw) && !FLOAT_IS_NAN(kickSpeed.roll)); + assert(!FLOAT_IS_NAN(kickAngles.pitch) && !FLOAT_IS_NAN(kickAngles.yaw) && !FLOAT_IS_NAN(kickAngles.roll)); + + kickSpeed-=kickDamping*kickSpeed*frametime/1000; + kickSpeed-=kickSpring*kickAngles*frametime/1000; + + if (gameLocal.isNewFrame) { //HUMANHEAD rww + kickAngles+=kickSpeed*frametime/1000; + } + + return kickAngles; +} + +//------------------------------------------------------ +// SingleView +//------------------------------------------------------ +void hhPlayerView::SingleView( idUserInterface *hud, const renderView_t *view ) { + // normal rendering + + if ( !view ) { + return; + } + + // place the sound origin for the player + gameSoundWorld->PlaceListener( view->vieworg, view->viewaxis, player->entityNumber + 1, gameLocal.time, hud ? hud->State().GetString( "location" ) : "Undefined" ); + + // hack the shake in at the very last moment, so it can't cause any consistency problems + renderView_t hackedView = *view; + hackedView.viewaxis = hackedView.viewaxis * ShakeAxis(); + + gameRenderWorld->RenderScene( &hackedView ); + + if ( player->spectating ) { + 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 ); + } + } + + // HUMANHEAD: CJR + if ( voTotalTime != -1 ) { // Check if the viewOverlay should time out + if ( gameLocal.time >= voFinishTime ) { + voTotalTime = -1; + voRequiresScratchBuffer = false; + viewOverlayMaterial = NULL; // Remove the overlay + } + } + + //HUMANHEAD: aob + if( viewOverlayMaterial ) { + renderSystem->SetColor4( viewOverlayColor[0], viewOverlayColor[1], viewOverlayColor[2], viewOverlayColor[3] ); + renderSystem->DrawStretchPic(0, 0, 640, 480, 0, 1, 1, 0, viewOverlayMaterial); + } + //HUMANHEAD END + + if ( player->health > 0 && player->health < 25 && static_cast(player)->IsSpiritOrDeathwalking()==false ) { + hurtValue += 0.05f*(player->health - hurtValue); + if( player->health > 30 ) + hurtValue = player->health; + + renderSystem->SetColor4( hurtValue/25.0f, 1, 1, 1 ); + renderSystem->DrawStretchPic(0, 0, 640, 480, 0, 1, 1, 0, hurtMaterial); + } + } + + // If this level has a sun corona, then attempt to draw it - CJR + if ( !g_skipViewEffects.GetBool() && gameLocal.GetSunCorona() ) { + gameLocal.GetSunCorona()->Draw( (hhPlayer *)player ); + } + + // 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 ); + } + } +} + +// Hermite() +// Hermite Interpolator +// Returns an alpha value [0..1] based on Hermite Parameters N1, N2, S1, S2 and an input alpha 't' +float Hermite(float t, float N1, float N2, float S1, float S2) { + float tSquared = t*t; + float tCubed = tSquared*t; + return (2*tCubed - 3*tSquared + 1)*N1 + + (-2*tCubed + 3*tSquared)*N2 + + (tCubed - 2*tSquared + t)*S1 + + (tCubed - tSquared)*S2; +} + + +//------------------------------------------------------ +// MotionBlurVision +// HUMANHEAD pdm +//------------------------------------------------------ + +void hhPlayerView::MotionBlurVision(idUserInterface *hud, const renderView_t *view) { + if ( !g_doubleVision.GetBool() ) { + SingleView( hud, view ); + return; + } + + float alpha; + const float N1 = 0.2f; + const float N2 = 0.0f; + const float S1 = 1.0f; + const float S2 = 1.0f; + + float remainingTime = mbFinishTime - gameLocal.time; + float elapsedTime = mbTotalTime - remainingTime; + float scale = remainingTime / mbTotalTime; + + // Render to a texture + RENDER_DEMO_VIEWRENDER(view, this); //HUMANHEAD rww + renderSystem->CropRenderSize( 512, 256, true ); + SingleView( hud, view ); + renderSystem->CaptureRenderToImage( "_scratch" ); + renderSystem->UnCrop(); + RENDER_DEMO_VIEWRENDER_END(); //HUMANHEAD rww + + // Motion blur + float xshift, yshift; + for (int index=0; index1?1:(a)) + renderSystem->SetColor4( 1,1,1, index==0 ? 1.0f : 0.2f ); + renderSystem->DrawStretchPic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, + CLIP(xshift), CLIP(1+yshift), CLIP(1+xshift), CLIP(yshift), scratchMaterial ); // clipped + } +} + +//------------------------------------------------------ +// SpiritVision +// HUMANHEAD cjr +//------------------------------------------------------ + +void hhPlayerView::SpiritVision( idUserInterface *hud, const renderView_t *view ) { + int oldTime = voTotalTime; + const idMaterial *oldMaterial = viewOverlayMaterial; + + voTotalTime = -1; + viewOverlayMaterial = NULL; + + SingleView( hud, view ); + + // Draw the spiritwalk image over the top + if ( player && !spiritMaterial ) { + spiritMaterial = declManager->FindMaterial( player->spawnArgs.GetString( "mtr_spiritwalk" ) ); + } + + renderSystem->SetColor4( 1, 1, 1, 1 ); + renderSystem->DrawStretchPic(0, 0, 640, 480, 0, 1, 1, 0, spiritMaterial ); + + voTotalTime = oldTime; + viewOverlayMaterial = oldMaterial; +} + +//------------------------------------------------------ +// ApplyLetterbox +// HUMANHEAD pdm +//------------------------------------------------------ +void hhPlayerView::ApplyLetterBox(const renderView_t *view) { + if (bLetterBox) { + renderSystem->SetColor4( 1.0f, 1.0f, 1.0f, 1.0f ); + renderSystem->DrawStretchPic(0, 0, 640, LETTERBOX_HEIGHT_TOP, 0, 0, 1, 1, letterboxMaterial); + renderSystem->DrawStretchPic(0, 480-LETTERBOX_HEIGHT_BOTTOM, 640, LETTERBOX_HEIGHT_BOTTOM, 0, 0, 1, 1, letterboxMaterial); + } +} + +//------------------------------------------------------ +// MotionBlur +// HUMANHEAD pdm +//------------------------------------------------------ +void hhPlayerView::MotionBlur(int mbTime, float severity, idVec3 &direction) { + mbTotalTime = mbTime; + mbFinishTime = gameLocal.time + mbTotalTime; + mbAmplitude = severity; + mbDirection = direction; +} + +//------------------------------------------------------ +// SetLetterBox +// HUMANHEAD pdm +//------------------------------------------------------ +void hhPlayerView::SetLetterBox(bool on) { + bLetterBox = on; +} + +//------------------------------------------------------ +// RenderPlayerView +//------------------------------------------------------ +void hhPlayerView::RenderPlayerView( idUserInterface *hud ) { + const renderView_t *view = player->GetRenderView(); + + if ( g_skipViewEffects.GetBool() ) { + SingleView( hud, view ); + } else { + if (gameLocal.time < mbFinishTime) { + MotionBlurVision(hud, view); + } else if ( ((hhPlayer *)player)->IsSpiritWalking() ) { + SpiritVision( hud, view ); + } else { + SingleView(hud, view); + } + ScreenFade(); + + // HUMANHEAD pdm: letterbox + ApplyLetterBox(view); + } + + // HUMANHEAD: Draw the HUD after all over overlay effects. + if (hud) { + hud->SetStateBool("letterbox", bLetterBox); + player->DrawHUD( hud ); + } + + if ( net_clientLagOMeter.GetBool() && lagoMaterial && gameLocal.isClient ) { + 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 ); + } +} diff --git a/src/Prey/game_playerview.h b/src/Prey/game_playerview.h new file mode 100644 index 0000000..8067193 --- /dev/null +++ b/src/Prey/game_playerview.h @@ -0,0 +1,108 @@ +#ifndef __PREY_GAME_PLAYERVIEW_H__ +#define __PREY_GAME_PLAYERVIEW_H__ + +class hhPlayerView : public idPlayerView { +public: + hhPlayerView(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void ClearEffects( void ); + virtual void DamageImpulse( idVec3 localKickDir, const idDict *damageDef ); + virtual void RenderPlayerView( idUserInterface *hud ); + + void WeaponFireFeedback( const idDict *weaponDef ); // HUMANHEAD bjk + idAngles AngleOffset( float kickSpeed, float kickReturnSpeed ); // HUMANHEAD bjk + + // HUMANHEAD + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + void MotionBlur(int mbTime, float severity, idVec3 &direction); + idVec3 ViewOffset() { return viewOffset; }//HUMANHEAD: aob + void SetViewOverlayMaterial( const idMaterial* material, int scratchBuffer = false ); + void SetViewOverlayColor( idVec4 color ); // HUMANHEAD cjr + void SetViewOverlayTime( int time, int scratchBuffer = false ); + void SetLetterBox(bool on); + void SetDamageLoc(const idVec3 &damageLoc); + // HUMANHEAD END + +protected: + virtual void SingleView(idUserInterface *hud, const renderView_t *view); + + // HUMANHEAD pdm + void MotionBlurVision(idUserInterface *hud, const renderView_t *view); + void ApplyLetterBox(const renderView_t *view); + void SpiritVision( idUserInterface *hud, const renderView_t *view ); + +protected: + bool bLetterBox; // HUMANHEAD pdm: whether we are in letterbox mode + float mbAmplitude; + int mbFinishTime; + int mbTotalTime; + idVec3 mbDirection; + int kickLastTime; // HUMANHEAD bjk + idAngles kickSpeed; // HUMANHEAD bjk + idVec3 viewOffset; // HUMANHEAD: aob + const idMaterial * viewOverlayMaterial; // HUMANHEAD: aob + idVec4 viewOverlayColor; // HUMANHEAD cjr: Used to pass colors/parms to the view overlay + int voFinishTime; // HUMANHEAD cjr: Used to set the time that the view overlay stays on + int voTotalTime; // HUMANHEAD cjr: Used to set the time that the view overlay stays on + int voRequiresScratchBuffer;// HUMANHEAD cjr: requires the screen rendered to the scratch buffer + const idMaterial *letterboxMaterial; // HUMANHEAD pdm + const idMaterial *dirDmgLeftMaterial; // HUMANHEAD pdm + const idMaterial *dirDmgFrontMaterial; // HUMANHEAD pdm + const idMaterial *spiritMaterial; // HUMANHEAD cjr + idVec3 lastDamageLocation; // HUMANHEAD PDM: saved damage location + float hurtValue; // HUMANHEAD bjk: to smooth the health +}; + +//HUMANHEAD rww - render demo madness +#if _HH_RENDERDEMO_HACKS + void RENDER_DEMO_VIEWRENDER(const renderView_t *view, const hhPlayerView *pView = NULL); + void RENDER_DEMO_VIEWRENDER_END(void); +#else + #define RENDER_DEMO_VIEWRENDER(a, b)//ignore + #define RENDER_DEMO_VIEWRENDER_END() //ignore +#endif +//HUMANHEAD END + + +//------------------------------------------------------ +// hhPlayerView::SetViewOverlayMaterial +// +// HUMANHEAD: aob +//------------------------------------------------------ +ID_INLINE void hhPlayerView::SetViewOverlayMaterial( const idMaterial* material, int scratchBuffer ) { + viewOverlayMaterial = material; + + voTotalTime = -1; + voFinishTime = gameLocal.time + voTotalTime; + voRequiresScratchBuffer = scratchBuffer; +} + +//------------------------------------------------------ +// hhPlayerView::SetViewOverlayColor +// +// HUMANHEAD: cjr +//------------------------------------------------------ +ID_INLINE void hhPlayerView::SetViewOverlayColor( idVec4 color ) { + viewOverlayColor = color; +} + +//------------------------------------------------------ +// hhPlayerView::SetViewOverlayTime +// +// HUMANHEAD: cjr +// +// Time can be set in SetViewOverlayMaterial +// or set/updated with this function call +//------------------------------------------------------ +ID_INLINE void hhPlayerView::SetViewOverlayTime( int time, int scratchBuffer ) { + voTotalTime = time; + voFinishTime = gameLocal.time + voTotalTime; + voRequiresScratchBuffer = scratchBuffer; +} + +#endif /* __PREY_GAME_PLAYERVIEW_H__ */ + diff --git a/src/Prey/game_pod.cpp b/src/Prey/game_pod.cpp new file mode 100644 index 0000000..94904f1 --- /dev/null +++ b/src/Prey/game_pod.cpp @@ -0,0 +1,157 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +//========================================================================== +// +// hhPod +// +// type of mine that can be spawned from a pod spawner +//========================================================================== + +CLASS_DECLARATION(hhMine, hhPod) +END_CLASS + +hhPod::hhPod() { + bMoverThink = false; + additionalAxis.Identity(); +} + +void hhPod::Spawn(void) { + // Uses clipModel because it is a moveable, which requires it + + // Turn off collision with corpses + int oldMask = GetPhysics()->GetClipMask(); + GetPhysics()->SetClipMask( oldMask & (~CONTENTS_CORPSE) ); + + // Rolling support + radius = (GetPhysics()->GetBounds()[1][0] - GetPhysics()->GetBounds()[0][0]) * 0.5f; + lastOrigin = GetPhysics()->GetOrigin(); + additionalAxis.Identity(); + BecomeActive(TH_THINK); +} + +void hhPod::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( radius ); + savefile->WriteVec3( lastOrigin ); + savefile->WriteMat3( additionalAxis ); + savefile->WriteBool( bMoverThink ); +} + +void hhPod::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( radius ); + savefile->ReadVec3( lastOrigin ); + savefile->ReadMat3( additionalAxis ); + savefile->ReadBool( bMoverThink ); +} + +bool hhPod::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + StartSound( "snd_pain", SND_CHANNEL_ANY ); + + float scale = 1.5f - (0.5f * health / spawnArgs.GetFloat("health")); + scale = idMath::ClampFloat(1.0f, 1.5f, scale); + SetShaderParm(SHADERPARM_ANY_DEFORM_PARM2, scale); + + return( idEntity::Pain(inflictor, attacker, damage, dir, location) ); +} + +void hhPod::Release() { + idVec3 initLinearVelocity, initAngularVelocity; + spawnArgs.GetVector( "init_velocity", "0 0 0", initLinearVelocity ); + spawnArgs.GetVector( "init_avelocity", "0 0 0", initAngularVelocity ); + + // Make the pod physics kick in + physicsObj.EnableImpact(); + physicsObj.Activate(); + physicsObj.SetLinearVelocity( initLinearVelocity ); + physicsObj.SetAngularVelocity( initAngularVelocity ); + + // Start deforming again (disabled on pod spawners) + float parm1 = spawnArgs.GetFloat("deformParm1"); + float parm2 = spawnArgs.GetFloat("deformParm2"); + SetDeformation(DEFORMTYPE_POD, parm1, parm2); +} + + +bool hhPod::Collide( const trace_t &collision, const idVec3 &velocity ) { + AttemptToPlayBounceSound( collision, velocity ); + + return hhMine::Collide( collision, velocity ); +} + + +void hhPod::RollThink( void ) { + float movedDistance, angle; + idVec3 curOrigin, gravityNormal, dir; + + bool wasAtRest = IsAtRest(); + + RunPhysics(); + + // only need to give the visual model an additional rotation if the physics were run + if ( !wasAtRest ) { + + // current physics state + curOrigin = GetPhysics()->GetOrigin(); + + dir = curOrigin - lastOrigin; + float movedDistanceSquared = dir.LengthSqr(); + + // if the pod moved + if ( movedDistanceSquared > 0.0f && movedDistanceSquared < 100.0f) { + + gravityNormal = GetPhysics()->GetGravityNormal(); + + // movement since last frame + movedDistance = idMath::Sqrt( movedDistanceSquared ); + dir *= 1.0f / movedDistance; + + // Get local coordinate axes + idVec3 right = -dir.Cross(gravityNormal); + + // Rotate about it proportional to the distance moved using axis/angle + angle = 180.0f * movedDistance / (radius*idMath::PI); + additionalAxis *= (idRotation( vec3_origin, right, angle).ToMat3()); + } + + // save state for next think + lastOrigin = curOrigin; + } + + Present(); +} + +void hhPod::Event_HoverTo( const idVec3 &position ) { + bMoverThink = true; + hhMine::Event_HoverTo( position ); +} + +void hhPod::Event_Unhover() { + bDetonateOnCollision = true; + bMoverThink = false; + hhMine::Event_Unhover(); +} + +void hhPod::Think() { + if ( bMoverThink ) { + hhMoveable::Think(); + } else { + RollThink(); + } +} + +void hhPod::ClientPredictionThink() { + if ( bMoverThink ) { + hhMoveable::Think(); + } else { + RollThink(); + } +} + +bool hhPod::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + origin = vec3_origin; + axis = additionalAxis * GetPhysics()->GetAxis().Inverse(); + return true; +} + diff --git a/src/Prey/game_pod.h b/src/Prey/game_pod.h new file mode 100644 index 0000000..e8813e2 --- /dev/null +++ b/src/Prey/game_pod.h @@ -0,0 +1,34 @@ + +#ifndef __GAME_POD_H__ +#define __GAME_POD_H__ + +class hhPod : public hhMine { +public: + CLASS_PROTOTYPE( hhPod ); + + hhPod(); + 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 ); + void Release(); + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + virtual void Think( void ); + virtual void ClientPredictionThink( void ); + + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual void Event_HoverTo( const idVec3 &position ); + virtual void Event_Unhover(); +protected: + void RollThink( void ); + +private: + float radius; // radius of barrel + idVec3 lastOrigin; // origin of the barrel the last frame + idMat3 additionalAxis; // transformation for visual model + bool bMoverThink; +}; + + +#endif /* __GAME_POD_H__ */ + diff --git a/src/Prey/game_podspawner.cpp b/src/Prey/game_podspawner.cpp new file mode 100644 index 0000000..8038c40 --- /dev/null +++ b/src/Prey/game_podspawner.cpp @@ -0,0 +1,141 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +//========================================================================== +// +// hhPodSpawner +// +// type of mine spawner that animates and spawns pods +//========================================================================== + +const idEventDef EV_DropPod("", NULL); +const idEventDef EV_DoneSpawning("", NULL); + +CLASS_DECLARATION(hhMineSpawner, hhPodSpawner) + EVENT( EV_DropPod, hhPodSpawner::Event_DropPod) + EVENT( EV_PlayIdle, hhPodSpawner::Event_PlayIdle) + EVENT( EV_DoneSpawning, hhPodSpawner::Event_DoneSpawning) +END_CLASS + + +void hhPodSpawner::Spawn(void) { + fl.takedamage = false; + GetPhysics()->SetContents( CONTENTS_BODY ); + + pod = NULL; + spawning = false; + idleAnim = GetAnimator()->GetAnim("idle"); + painAnim = GetAnimator()->GetAnim("pain"); + spawnAnim = GetAnimator()->GetAnim("spawn"); + + PostEventMS(&EV_PlayIdle, 0); +} + +void hhPodSpawner::Save(idSaveGame *savefile) const { + savefile->WriteInt( idleAnim ); + savefile->WriteInt( painAnim ); + savefile->WriteInt( spawnAnim ); + savefile->WriteBool( spawning ); + savefile->WriteObject(pod); +} + +void hhPodSpawner::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( idleAnim ); + savefile->ReadInt( painAnim ); + savefile->ReadInt( spawnAnim ); + savefile->ReadBool( spawning ); + savefile->ReadObject( reinterpret_cast(pod) ); +} + +void hhPodSpawner::SpawnMine() { + idVec3 offset; + + if (spawning || health < 0 || fl.isDormant) { + return; + } + + population++; + spawning = true; + + offset = idVec3(0, 0, -64); // Move up to top + + idDict args; + args.Clear(); + args.Set( "origin", (GetPhysics()->GetOrigin() + offset).ToString() ); + args.Set( "nodrop", "1" ); // Don't put on the floor, wait for release + args.Set( "deformType", "0" ); // Don't start deforming until released + + // pass along any spawn keys + const idKeyValue *arg = spawnArgs.MatchPrefix("spawn_"); + while( arg ) { + args.Set( arg->GetKey().Right( arg->GetKey().Length() - 6 ), arg->GetValue() ); + arg = spawnArgs.MatchPrefix( "spawn_", arg ); + } + + // spawn a pod + pod = static_cast(gameLocal.SpawnObject(spawnArgs.GetString("def_pod"), &args)); + + // attach to bone + const char *podBone = "PodPosition"; + pod->MoveToJoint(this, podBone); + pod->AlignToJoint(this, podBone); + pod->BindToJoint(this, podBone, true); + pod->fl.takedamage = false; // No damage while attached + pod->SetSpawner(this); + + // Due to the animation, bone isn't in position yet, so wait a little + // before making it visible + pod->Hide(); + pod->PostEventMS(&EV_Show, 500); + + // start animation + if (spawnAnim) { + GetAnimator()->ClearAllAnims(gameLocal.time, 0); + GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, spawnAnim, gameLocal.time, 500); + int opentime = GetAnimator()->GetAnim( spawnAnim )->Length(); + PostEventMS( &EV_PlayIdle, opentime ); + + //TODO: Move this to a frame command (event) + PostEventMS( &EV_DropPod, 3700 ); + + PostEventMS( &EV_DoneSpawning, opentime ); + + StartSound( "snd_spawn", SND_CHANNEL_ANY ); + } +} + +void hhPodSpawner::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + // Don't actually take damage, but give feedback + + // Play pain + if (painAnim) { + GetAnimator()->ClearAllAnims(gameLocal.time, 0); + GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, painAnim, gameLocal.time, 500); + int opentime = GetAnimator()->GetAnim( painAnim )->Length(); + PostEventMS( &EV_PlayIdle, opentime ); + StartSound( "snd_pain", SND_CHANNEL_ANY ); + } +} + +void hhPodSpawner::Event_PlayIdle() { + if (idleAnim) { + GetAnimator()->ClearAllAnims(gameLocal.time, 0); + GetAnimator()->CycleAnim(ANIMCHANNEL_ALL, idleAnim, gameLocal.time, 0); + } +} + +void hhPodSpawner::Event_DropPod() { + if (pod) { + pod->Unbind(); + pod->fl.takedamage = true; + pod->Release(); + } +} + +void hhPodSpawner::Event_DoneSpawning() { + spawning = false; + CheckPopulation(); +} + diff --git a/src/Prey/game_podspawner.h b/src/Prey/game_podspawner.h new file mode 100644 index 0000000..c182585 --- /dev/null +++ b/src/Prey/game_podspawner.h @@ -0,0 +1,31 @@ + +#ifndef __GAME_PODSPAWNER_H__ +#define __GAME_PODSPAWNER_H__ + +class hhPodSpawner : public hhMineSpawner { +public: + CLASS_PROTOTYPE( hhPodSpawner ); + + void Spawn( void ); + 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 ); + void SpawnMine(); + +protected: + void Event_PlayIdle(); + void Event_DropPod(); + void Event_DoneSpawning(); + +private: + int idleAnim; + int painAnim; + int spawnAnim; + + bool spawning; + hhPod *pod; +}; + + +#endif /* __GAME_PODSPAWNER_H__ */ diff --git a/src/Prey/game_poker.cpp b/src/Prey/game_poker.cpp new file mode 100644 index 0000000..f4d8c67 --- /dev/null +++ b/src/Prey/game_poker.cpp @@ -0,0 +1,807 @@ +// game_poker.cpp +// + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +// Static declaration of our hands array +const hand_t localhands[NUM_POKER_HANDS] = { + { HAND_ROYALFLUSH, 5000, PREQ_ROYALS|PREQ_FLUSH|PREQ_STRAIGHT }, + { HAND_STRAIGHTFLUSH, 1000, PREQ_FLUSH|PREQ_STRAIGHT }, + { HAND_FOUROFKIND, 100, PREQ_4MATCH }, + { HAND_FULLHOUSE, 50, PREQ_FULLHOUSE }, + { HAND_FLUSH, 20, PREQ_FLUSH }, + { HAND_STRAIGHT, 10, PREQ_STRAIGHT }, + { HAND_THREEOFKIND, 5, PREQ_3MATCH }, + { HAND_TWOPAIR, 3, PREQ_2PAIR }, + // Jacks or better is hardcoded at 2 + { HAND_PAIR, 1, PREQ_2MATCH }, + { HAND_NOTHING, 0, 0 } +}; + +// Assign reference to our local array to our static class +const hand_t * hhPoker::hands = localhands; + + +//============================================================== +// hhPokerHand utility class +//============================================================== + +CLASS_DECLARATION(idClass, hhPokerHand) +END_CLASS + +hhPokerHand::hhPokerHand() { + Clear(); +} + +void hhPokerHand::Save(idSaveGame *savefile) const { + savefile->Write(values, sizeof(suitHist_t)*NUM_SUITS); + savefile->Write(suits, sizeof(valueHist_t)*NUM_CARD_VALUES); + + savefile->WriteInt(cards.Num()); // idList + for (int i=0; iWrite(&cards[i], sizeof(hhCard)); + } +} + +void hhPokerHand::Restore( idRestoreGame *savefile ) { + int i, num; + hhCard card; + + savefile->Read(values, sizeof(suitHist_t)*NUM_SUITS); + savefile->Read(suits, sizeof(valueHist_t)*NUM_CARD_VALUES); + + cards.Clear(); + savefile->ReadInt( num ); + cards.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->Read(&card, sizeof(hhCard)); + cards[i] = card; + } +} + +void hhPokerHand::operator=(hhPokerHand &other) { + Clear(); + for (int ix=0; ix maxcount) { + maxcount = values[ix].count; + } + } + return maxcount; +} + +int hhPokerHand::ValueOfMaxCount() { + int maxcount = 0; + int maxcountvalue = -1; + for (int ix=0; ix maxcount) { + maxcount = values[ix].count; + maxcountvalue = ix; + } + } + return maxcountvalue; +} + +int hhPokerHand::GetLowValue() { + int first = -1; + for (int ix=0; ix 0) { + first = ix; + break; + } + } + return first; +} + +int hhPokerHand::GetHighValue() { + int last = -1; + for (int ix=0; ix 0) { + last = ix; + } + } + return last; +} + +bool hhPokerHand::HasValue(int value) { + return values[value].count > 0; +} + +int hhPokerHand::NumUniqueValues() { + int numvalues = 0; + for (int ix=0; ix 0) { + numvalues++; + } + } + return numvalues; +} + +int hhPokerHand::NumOfValue(int value) { + assert(value>=0 && value 0) { + count++; + } + } + return count; +} + +int hhPokerHand::SuitOfMaxCount() { + int maxcount = 0; + int maxsuit = -1; + for (int ix=0; ix maxcount) { + maxcount = suits[ix].count; + maxsuit = ix; + } + } + return maxsuit; +} + +bool hhPokerHand::Search(hhCard &card) { + for (int ix=0; ix", NULL); + +CLASS_DECLARATION(hhConsole, hhPoker) + EVENT( EV_Deal, hhPoker::Event_Deal) + EVENT( EV_Draw, hhPoker::Event_Draw) + EVENT( EV_UpdateView, hhPoker::Event_UpdateView) +END_CLASS + +void hhPoker::Spawn() { + + Reset(); +} + +void hhPoker::Reset() { + bCanDeal = true; + bCanIncBet = true; + bCanDecBet = true; + bCanDraw = false; + bGameOver = false; + Bet = PlayerBet = 1; + memset(markedCards, 0, sizeof(markedCards)); + victoryAmount = spawnArgs.GetInt("victory"); + PlayerCredits = spawnArgs.GetInt("credits"); + currentHandIndex = -1; + creditsWon = 0; + + bCanMark1 = bCanMark2 = bCanMark3 = bCanMark4 = bCanMark5 = false; + PlayerHand.Clear(); + + UpdateView(); +} + +void hhPoker::Save(idSaveGame *savefile) const { + savefile->WriteStaticObject(deck); + savefile->WriteStaticObject(PlayerHand); + savefile->Write(markedCards, sizeof(bool)*5); + savefile->WriteInt(Bet); + savefile->WriteInt(PlayerBet); + savefile->WriteInt(PlayerCredits); + savefile->WriteInt(victoryAmount); + savefile->WriteInt(currentHandIndex); + + savefile->WriteBool( bGameOver ); + savefile->WriteBool( bCanDeal ); + savefile->WriteBool( bCanIncBet ); + savefile->WriteBool( bCanDecBet ); + savefile->WriteBool( bCanDraw ); + savefile->WriteBool( bCanMark1 ); + savefile->WriteBool( bCanMark2 ); + savefile->WriteBool( bCanMark3 ); + savefile->WriteBool( bCanMark4 ); + savefile->WriteBool( bCanMark5 ); + savefile->WriteInt( creditsWon ); +} + +void hhPoker::Restore( idRestoreGame *savefile ) { + savefile->ReadStaticObject(deck); + savefile->ReadStaticObject(PlayerHand); + savefile->Read(markedCards, sizeof(bool)*5); + savefile->ReadInt(Bet); + savefile->ReadInt(PlayerBet); + savefile->ReadInt(PlayerCredits); + savefile->ReadInt(victoryAmount); + savefile->ReadInt(currentHandIndex); + + savefile->ReadBool( bGameOver ); + savefile->ReadBool( bCanDeal ); + savefile->ReadBool( bCanIncBet ); + savefile->ReadBool( bCanDecBet ); + savefile->ReadBool( bCanDraw ); + savefile->ReadBool( bCanMark1 ); + savefile->ReadBool( bCanMark2 ); + savefile->ReadBool( bCanMark3 ); + savefile->ReadBool( bCanMark4 ); + savefile->ReadBool( bCanMark5 ); + savefile->ReadInt( creditsWon ); +} + +void hhPoker::Deal() { + + // Shuffle + deck.Generate(); + deck.Shuffle(); + + Bet = PlayerBet; + PlayerCredits -= Bet; + creditsWon = 0; + + // Deal initial hand + PlayerHand.Clear(); + PlayerHand.AddCard(deck.GetCard()); + PlayerHand.AddCard(deck.GetCard()); + PlayerHand.AddCard(deck.GetCard()); + PlayerHand.AddCard(deck.GetCard()); + PlayerHand.AddCard(deck.GetCard()); + + EvaluateHand(false); + + bCanDeal = bCanIncBet = bCanDecBet = false; + bCanDraw = bCanMark1 = bCanMark2 = bCanMark3 = bCanMark4 = bCanMark5 = true; + for (int ix=0; ix<5; ix++) { + markedCards[ix] = true; + } + + UpdateView(); +} + +void hhPoker::Draw() { + bCanDeal = bCanIncBet = bCanDecBet = true; + bCanDraw = bCanMark1 = bCanMark2 = bCanMark3 = bCanMark4 = bCanMark5 = false; + + float luck = idMath::ClampFloat(0.0f, 1.0f, spawnArgs.GetFloat("luck")); + if (luck) { + BestHand(luck); + } + else { + // Replace marked cards + for (int ix=0; ix<5; ix++) { + if (markedCards[ix]) { + PlayerHand.cards[ix] = deck.GetCard(); + markedCards[ix] = false; + } + } + hhPokerHand temp; + temp = PlayerHand; + PlayerHand = temp; // Rebuild player hand for correct stats + } + + EvaluateHand(true); + UpdateView(); +} + +void hhPoker::Mark(int card) { + markedCards[card] ^= 1; + UpdateView(); +} + +void hhPoker::IncBet() { + + int amount = 1; + idUserInterface *gui = renderEntity.gui[0]; + if (gui) { + amount = gui->GetStateInt("increment"); + } + + if (bCanIncBet) { + int oldBet = PlayerBet; + PlayerBet = idMath::ClampInt(PlayerBet, PlayerCredits, PlayerBet+amount); + PlayerBet = idMath::ClampInt(0, 999999, PlayerBet); + if (PlayerBet != oldBet) { + StartSound( "snd_betchange", SND_CHANNEL_ANY ); + } + } + UpdateView(); +} + +void hhPoker::DecBet() { + int amount = 1; + idUserInterface *gui = renderEntity.gui[0]; + if (gui) { + amount = gui->GetStateInt("increment"); + } + + if (bCanDecBet) { + int oldBet = PlayerBet; + if (PlayerBet > amount) { + PlayerBet -= amount; + } + else if (PlayerBet > 1) { + PlayerBet = 1; + } + if (PlayerBet != oldBet) { + StartSound( "snd_betchange", SND_CHANNEL_ANY ); + } + } + UpdateView(); +} + + +/* + hhPoker utility functions +*/ + +void hhPoker::EvaluateHand(bool score) { + int ix; + currentHandIndex = -1; + + int requirements = 0; + int low = PlayerHand.GetLowValue(); + int high = PlayerHand.GetHighValue(); + int maxcount = PlayerHand.MaxCountOfValues(); + + if (low >= CARD_TEN) { + requirements |= PREQ_ROYALS; + } + + if (PlayerHand.NumUniqueSuits() == 1) { + requirements |= PREQ_FLUSH; + } + + // Check for straight + if (maxcount==1) { + requirements |= PREQ_STRAIGHT; + int cur = low; + for (ix=1; ix<5; ix++) { + int next = cur+1; + if (low == CARD_DEUCE && next == CARD_SIX && PlayerHand.HasValue(CARD_ACE)) { + // If looking for a '6', accept an 'Ace' also for Ace low straights + } + else if (!PlayerHand.HasValue(next)) { + requirements &= ~PREQ_STRAIGHT; + break; + } + cur = next; + } + } + + // Check for Pairs, etc. + for (ix=0; ix= CARD_JACK) { + payoff = 2; + } + + creditsWon = Bet * payoff; + PlayerCredits += creditsWon; + PlayerCredits = idMath::ClampInt(0, 999999999, PlayerCredits); + if (PlayerCredits < PlayerBet) { + PlayerBet = PlayerCredits; + } + + if (PlayerCredits <= 0) { + bCanIncBet = bCanDecBet = bCanDraw = bCanDeal = bCanMark1 = bCanMark2 = bCanMark3 = bCanMark4 = bCanMark5 = false; + bGameOver = true; + } + + if (victoryAmount && PlayerCredits >= victoryAmount) { + StartSound( "snd_victory", SND_CHANNEL_ANY ); + ActivateTargets( gameLocal.GetLocalPlayer() ); + victoryAmount = 0; + } + else if (payoff > 10) { + StartSound( "snd_winbig", SND_CHANNEL_ANY ); + } + else if (payoff > 0) { + StartSound( "snd_win", SND_CHANNEL_ANY ); + } + else { + StartSound( "snd_lose", SND_CHANNEL_ANY ); + } + } +} + +void hhPoker::UpdateView() { + // UpdateView() is posted as an event because sometimes we're already nested down in the gui handling code when it is called + // and it in turn re-enters the gui handling code with HandleNamedEvent() + CancelEvents(&EV_UpdateView); + PostEventMS(&EV_UpdateView, 0); +} + +void hhPoker::Event_UpdateView() { + int ix; + idUserInterface *gui = renderEntity.gui[0]; + + if (gui) { + + bool atLeastOneCardKept = !(markedCards[0] && markedCards[1] && markedCards[2] && markedCards[3] && markedCards[4]); + + gui->SetStateBool("bgameover", bGameOver); + gui->SetStateBool("bcanincbet", bCanIncBet); + gui->SetStateBool("bcandecbet", bCanDecBet); + gui->SetStateBool("bcandraw", bCanDraw);// && atLeastOneCardKept); + gui->SetStateBool("bcandeal", bCanDeal); + gui->SetStateBool("bcanmark1", bCanMark1); + gui->SetStateBool("bcanmark2", bCanMark2); + gui->SetStateBool("bcanmark3", bCanMark3); + gui->SetStateBool("bcanmark4", bCanMark4); + gui->SetStateBool("bcanmark5", bCanMark5); + gui->SetStateInt("currentbet", PlayerBet); + gui->SetStateInt("credits", PlayerCredits); + gui->SetStateInt("hand", currentHandIndex); + gui->SetStateInt("creditswon", creditsWon); + + for (ix=0; ix<5; ix++) { + gui->SetStateInt(va("Player%d_Visible", ix+1), 0); + } + + // Show player hand + for (ix=0; ixSetStateInt(va("Player%d_Visible", ix+1), 1); + gui->SetStateInt(va("Player%d_Suit", ix+1), PlayerHand.cards[ix].Suit()); + char cardChar = PlayerHand.cards[ix].ValueName(); + if (cardChar == 'T') { + gui->SetStateString(va("Player%d_Card", ix+1), "10"); + } + else { + gui->SetStateString(va("Player%d_Card", ix+1), va("%c", cardChar)); + } + gui->SetStateInt(va("Player%d_Red", ix+1), PlayerHand.cards[ix].Suit()==SUIT_HEARTS || PlayerHand.cards[ix].Suit()==SUIT_DIAMONDS ? 1 : 0); + gui->SetStateString(va("Player%d_Mark", ix+1), markedCards[ix] ? "X" : ""); + gui->SetStateBool(va("Player%d_Marked", ix+1), markedCards[ix] ); + } + + gui->StateChanged(gameLocal.time, true); + CallNamedEvent("Update"); + } +} + +bool hhPoker::HandleSingleGuiCommand(idEntity *entityGui, idLexer *src) { + + idToken token; + + if (!src->ReadToken(&token)) { + return false; + } + + if (token == ";") { + return false; + } + + if (token.Icmp("deal") == 0) { + Deal(); + } + else if (token.Icmp("draw") == 0) { + Draw(); + } + else if (token.Icmp("mark1") == 0) { + Mark(0); + } + else if (token.Icmp("mark2") == 0) { + Mark(1); + } + else if (token.Icmp("mark3") == 0) { + Mark(2); + } + else if (token.Icmp("mark4") == 0) { + Mark(3); + } + else if (token.Icmp("mark5") == 0) { + Mark(4); + } + else if (token.Icmp("incbet") == 0) { + IncBet(); + } + else if (token.Icmp("decbet") == 0) { + DecBet(); + } + else if (token.Icmp("reset") == 0) { + Reset(); + } + else if (token.Icmp("restart") == 0) { + bCanDeal = bCanIncBet = bCanDecBet = true; + bCanDraw = bCanMark1 = bCanMark2 = bCanMark3 = bCanMark4 = bCanMark5 = false; + PlayerCredits = spawnArgs.GetInt("credits"); + bGameOver = false; + Bet = PlayerBet = 1; + UpdateView(); + } + else { + src->UnreadToken(&token); + return false; + } + + return true; +} + +void hhPoker::Event_Deal() { + Deal(); +} + +void hhPoker::Event_Draw() { + Draw(); +} + +// Experiment: get best hand given discards +void hhPoker::BestHand(float probability) { + int ix; + + hhPokerHand hand; + hhPokerHand discards; + hhPokerHand originalHand; + + hand.Clear(); + originalHand.Clear(); + discards.Clear(); + + for (ix=0; ix<5; ix++) { + if (markedCards[ix]) { + discards.AddCard(PlayerHand.cards[ix]); + } + else { + hand.AddCard(PlayerHand.cards[ix]); + originalHand.AddCard(PlayerHand.cards[ix]); + } + } + + int possibilities = 0xFF; + + // Check for possibilities of requirements using hands + for (ix=0; ix 1) { + possibilities &= ~PREQ_STRAIGHT; + } + if (first != -1 && last != -1 && last-first>=5) { + if (hand.HasValue(CARD_ACE)) { // Extra check to allow for ace low straight + last = first; + first = -1; // put ace at begining, find new last and try again + for (ix=0; ix 0) { + last = ix; + } + } + if (last-first>=5) { + possibilities &= ~PREQ_STRAIGHT; + } + } + else { + possibilities &= ~PREQ_STRAIGHT; + } + } + + // Check for full house possibility + if (hand.NumUniqueValues() > 2) { + possibilities &= ~PREQ_FULLHOUSE; + possibilities &= ~PREQ_2PAIR; + } + + // check for 'x of a kind' possibility + if (maxcount + discards.cards.Num() < 4) { + possibilities &= ~PREQ_4MATCH; + } + if (maxcount + discards.cards.Num() < 3) { + possibilities &= ~PREQ_3MATCH; + } + if (maxcount + discards.cards.Num() < 2) { + possibilities &= ~PREQ_2MATCH; + } + + // Go through hands in decending order of greatness looking for possibilities + for (ix=0; ixCARD_TEN ? CARD_TEN : first; // Start at ten or lower so it will fit + } + while (hand.HasValue(value)) { + value++; + } + } + + int hotvalue; + if (requirements&PREQ_FULLHOUSE) { + hotvalue = hand.ValueOfMaxCount(); + if (hotvalue != -1) { + if (hand.NumOfValue(hotvalue) < 3) { + value = hotvalue; + } + else { + value = (hotvalue==first) ? last : first; + } + } + } + + if (requirements&PREQ_2PAIR) { + if (hand.NumOfValue(first) < 2) { + value = first; + } + else if (hand.NumOfValue(last) < 2) { + value = last; + } + else { + value = CARD_ACE; + while (hand.HasValue(value)) { + value--; + } + } + } + + if ((hand.MaxCountOfValues()<4) && (requirements&PREQ_4MATCH)) { + value = hand.ValueOfMaxCount(); + } + if ((hand.MaxCountOfValues()<3) && (requirements&PREQ_3MATCH)) { + value = hand.ValueOfMaxCount(); + } + if ((hand.MaxCountOfValues()<2) && (requirements&PREQ_2MATCH)) { + value = hand.ValueOfMaxCount(); + } + + // Determine suit properties + if (requirements&PREQ_FLUSH) { + suit = hand.SuitOfMaxCount(); + if (suit == -1) { + suit = gameLocal.random.RandomInt() % NUM_SUITS; // random fixed suit + } + if (value == -1) { // Need a value + value = CARD_ACE; + while (!deck.HasCard(hhCard(value, suit))) { + value--; + } + } + } + else if (value != -1) { + // Choose a suit different from held values + suit = 0; + while ( !deck.HasCard(hhCard(value, suit)) ) { + if (++suit >= NUM_SUITS) { + break; + } + } + if (suit >= NUM_SUITS) { // Can't make our hand - set back to a used card and it will break out to next possibility below + suit = SUIT_DIAMONDS; + } + } + + // Add card to hand + if (value == -1) { // This means any card is okay, give one from deck + hand.AddCard(deck.GetCard()); + } + else if (deck.HasCard(hhCard(value,suit))) { + hand.AddCard(deck.GetCard(value, suit)); + } + else { + for (int jx=originalHand.cards.Num(); jx probability) { + hand.cards[ix] = deck.GetCard(); + bHandCompromised = true; // only compromise hand with one card + } + } + + // Copy into playerhand + hhPokerHand temp; + int drawcard = originalHand.cards.Num(); + for (ix=0; ix<5; ix++) { + if (markedCards[ix]) { + PlayerHand.cards[ix] = hand.cards[drawcard++]; + markedCards[ix] = false; + } + temp.AddCard(PlayerHand.cards[ix]); + } + PlayerHand.Clear(); // Rebuild player hand for correct stats + for (ix=0; ix<5; ix++) { + PlayerHand.AddCard(temp.cards[ix]); + } +} + diff --git a/src/Prey/game_poker.h b/src/Prey/game_poker.h new file mode 100644 index 0000000..4ba7d98 --- /dev/null +++ b/src/Prey/game_poker.h @@ -0,0 +1,123 @@ + +#ifndef __GAME_POKER_H__ +#define __GAME_POKER_H__ + +extern const idEventDef EV_UpdateView; + +#define PREQ_FLUSH 0x00000001 +#define PREQ_STRAIGHT 0x00000002 +#define PREQ_ROYALS 0x00000004 +#define PREQ_4MATCH 0x00000008 +#define PREQ_3MATCH 0x00000010 +#define PREQ_2MATCH 0x00000020 +#define PREQ_FULLHOUSE 0x00000040 +#define PREQ_2PAIR 0x00000080 + +typedef enum pokerhand_e { + HAND_ROYALFLUSH=0, + HAND_STRAIGHTFLUSH, + HAND_FOUROFKIND, + HAND_FULLHOUSE, + HAND_FLUSH, + HAND_STRAIGHT, + HAND_THREEOFKIND, + HAND_TWOPAIR, + HAND_PAIR, + HAND_NOTHING, + NUM_POKER_HANDS +}pokerhand_t; + +typedef struct hand_s { + pokerhand_t hand; + int payoff; + int requirement; +} hand_t; + + +class hhPokerHand : public idClass { + CLASS_PROTOTYPE( hhPokerHand ); + +public: + typedef struct valueHist_s { + int count; + } valueHist_t; + + typedef struct suitHist_s { + int count; + } suitHist_t; + + hhPokerHand(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void Clear(); + int MaxCountOfValues(); + int ValueOfMaxCount(); + int GetLowValue(); + int GetHighValue(); + bool HasValue(int value); + int NumOfValue(int value); + int SuitOfMaxCount(); + int NumUniqueValues(); + int NumUniqueSuits(); + void AddCard(hhCard card); + bool Search(hhCard &card); + void operator=(hhPokerHand &other); + +public: + suitHist_t suits[NUM_SUITS]; + valueHist_t values[NUM_CARD_VALUES]; + idList cards; + +}; + +class hhPoker : public hhConsole { + CLASS_PROTOTYPE( hhPoker ); +public: + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Deal(); + void Draw(); + void Mark(int card); + void IncBet(); + void DecBet(); + void BestHand(float probability); + void Reset(); + + void EvaluateHand(bool score); + void UpdateView(); + bool HandleSingleGuiCommand(idEntity *entityGui, idLexer *src); + +protected: + void Event_Deal(); + void Event_Draw(); + void Event_UpdateView(); + +private: + static const hand_t *hands; + hhDeck deck; + hhPokerHand PlayerHand; + + bool markedCards[5]; + int Bet; + int PlayerBet; + int PlayerCredits; + int victoryAmount; + int currentHandIndex; + int creditsWon; + + bool bGameOver; + bool bCanDeal; + bool bCanIncBet; + bool bCanDecBet; + bool bCanDraw; + bool bCanMark1; + bool bCanMark2; + bool bCanMark3; + bool bCanMark4; + bool bCanMark5; +}; + +#endif /* __GAME_POKER_H__ */ diff --git a/src/Prey/game_portal.cpp b/src/Prey/game_portal.cpp new file mode 100644 index 0000000..d66a30e --- /dev/null +++ b/src/Prey/game_portal.cpp @@ -0,0 +1,1164 @@ +//************************************************************************** +//** +//** GAME_PORTAL.CPP +//** +//** Game code for Prey-specific portals +//** +//************************************************************************** + +// HEADER FILES ------------------------------------------------------------ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +#define MP_PORTAL_RANGE_DEFAULT 356.0f //rww - was 512, and then it was 256, and now it is 356 +#define MAX_PORTAL_BOUNDS 256.0f //used for unchanging bounds in mp + +//========================================================================== +// +// hhArtificialPortal +// +//========================================================================== + +const idEventDef EV_SetGamePortalState("setPortalState", "dd"); + +#if GAMEPORTAL_PVS + +CLASS_DECLARATION(idEntity, hhArtificialPortal) + EVENT( EV_SetGamePortalState, hhArtificialPortal::Event_SetPortalState ) +END_CLASS + +void hhArtificialPortal::Spawn() { + areaPortal = gameRenderWorld->FindGamePortal( GetName() ); + if (areaPortal && !gameLocal.isClient) { + SetPortalState(spawnArgs.GetBool("startOpenPVS"), spawnArgs.GetBool("startOpenSound")); + } + fl.networkSync = true; +} + +void hhArtificialPortal::SetPortalState(bool openPVS, bool openSound) { + if (areaPortal && !gameLocal.isClient) { + int blockMask = (openPVS ? PS_BLOCK_NONE : PS_BLOCK_VIEW) | (openSound ? PS_BLOCK_NONE : PS_BLOCK_SOUND); + gameLocal.SetPortalState( areaPortal, blockMask ); + } +} + +void hhArtificialPortal::Event_SetPortalState(bool openPVS, bool openSound) { + SetPortalState(openPVS, openSound); +} + +void hhArtificialPortal::Save(idSaveGame *savefile) const { + savefile->WriteInt(areaPortal); + if ( areaPortal ) { + savefile->WriteInt( gameRenderWorld->GetPortalState( areaPortal ) ); + } +} + +void hhArtificialPortal::Restore(idRestoreGame *savefile) { + savefile->ReadInt(areaPortal); + if ( areaPortal ) { + int portalState; + savefile->ReadInt( portalState ); + gameLocal.SetPortalState( areaPortal, portalState ); + } +} + + +#endif + + +//========================================================================== +// +// hhPortal +// +//========================================================================== + +const idEventDef EV_Opened("", NULL); +const idEventDef EV_Closed("", NULL); +const idEventDef EV_PortalSpark("", NULL ); +const idEventDef EV_PortalSparkEnd("", NULL ); +const idEventDef EV_ShowGlowPortal( "showGlowPortal", NULL ); +const idEventDef EV_HideGlowPortal( "hideGlowPortal", NULL ); + +CLASS_DECLARATION(hhAnimatedEntity, hhPortal) + EVENT( EV_PostSpawn, hhPortal::PostSpawn ) + EVENT( EV_Activate, hhPortal::Event_Trigger) + EVENT( EV_Opened, hhPortal::Event_Opened ) + EVENT( EV_Closed, hhPortal::Event_Closed ) + EVENT( EV_ResetGravity, hhPortal::Event_ResetGravity ) + EVENT( EV_PortalSpark, hhPortal::Event_PortalSpark ) + EVENT( EV_PortalSparkEnd, hhPortal::Event_PortalSparkEnd ) + EVENT( EV_ShowGlowPortal, hhPortal::Event_ShowGlowPortal ) + EVENT( EV_HideGlowPortal, hhPortal::Event_HideGlowPortal ) +END_CLASS + +hhPortal::hhPortal(void) { + areaPortal = 0; +} + +hhPortal::~hhPortal() { + proximityEntities.Clear(); // Clear the list of potential entities to be portalled + SAFE_REMOVE(m_portalIdleFx); + +#if GAMEPORTAL_PVS + if (areaPortal && !gameLocal.isClient) { + gameLocal.SetPortalState( areaPortal, PS_BLOCK_ALL ); + } +#endif +} + +void hhPortal::Spawn(void) { + idBounds bounds; + + fl.clientEvents = true; + + bNoTeleport = spawnArgs.GetBool( "noTeleport"); + bGlowPortal = spawnArgs.GetBool( "glowPortal"); // This is a glow portal, and so will have fx, sound, particles, etc + closeDelay = spawnArgs.GetFloat( "closeDelay"); + monsterportal = spawnArgs.GetBool( "monsterportal" ); + distanceToggle = spawnArgs.GetFloat( "distanceToggle" ); + if (gameLocal.isMultiplayer && bGlowPortal/* && distanceToggle == 0.0f*/) { //mp has a default shutoff distance for glow portals. + //now ignoring distance toggle keys in mp, and forcing it. + distanceToggle = MP_PORTAL_RANGE_DEFAULT; + } + if (!spawnArgs.GetFloat( "distanceCull", "0.0", distanceCull )) { //rww - distance to shut off the vis gameportal but not the render portal + //if not value specified, default to shaderParm5+4 + if (!renderEntity.shaderParms[5]) { + distanceCull = 0.0f; + } + else { + distanceCull = renderEntity.shaderParms[5]+4.0f; + } + } + areaPortalCulling = false; + if ( !gameLocal.isMultiplayer && bGlowPortal ) { + alertMonsters = spawnArgs.GetBool( "alertMonsters", "0" ); + } else { + alertMonsters = false; + } + + if ( bNoTeleport ) { + GetPhysics()->SetContents( 0 ); + } else { + GetPhysics()->SetContents( CONTENTS_SOLID ); + } + + // Setup the initial state for the portal + if(spawnArgs.GetBool("startActive")) { // Start Active + portalState = PORTAL_OPENED; + + if ( bGlowPortal ) { + int anim = GetAnimator()->GetAnim("opened"); + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, 0 ); + StartSound( "snd_loop", SND_CHANNEL_ANY ); + PostEventSec( &EV_PortalSpark, gameLocal.random.RandomFloat() ); + SetSkinByName( spawnArgs.GetString( "skin" ) ); + } + } else { // Start closed + Hide(); + portalState = PORTAL_CLOSED; + GetPhysics()->SetContents( 0 ); //rww - if closed, ensure things will not hit and go through + } + +#if GAMEPORTAL_PVS + const char *gamePortalName = spawnArgs.GetString("gamePortalName", GetName()); + areaPortal = gameRenderWorld->FindGamePortal( gamePortalName ); + if (areaPortal && !gameLocal.isClient) { + gameLocal.SetPortalState( areaPortal, portalState == PORTAL_CLOSED ? PS_BLOCK_ALL : PS_BLOCK_NONE ); + } +#endif + + // Default to normal gravity. This could be changed by any zones the portal is within + SetGravity( gameLocal.GetGravity() ); + + BecomeActive( TH_THINK | TH_UPDATEVISUALS | TH_ANIMATE ); + + UpdateVisuals(); + + PostEventMS( &EV_PostSpawn, 0 ); + + fl.networkSync = true; //rww + + if (gameLocal.isMultiplayer) { //rww - if portals grab their renderEntity bounds from the animation, they can cause client-server pvs discrepancies + renderEntity.bounds[0] = idVec3(-MAX_PORTAL_BOUNDS, -MAX_PORTAL_BOUNDS, -MAX_PORTAL_BOUNDS); + renderEntity.bounds[1] = idVec3(MAX_PORTAL_BOUNDS, MAX_PORTAL_BOUNDS, MAX_PORTAL_BOUNDS); + } + + proximityEntities.Clear(); // Clear the list of potential entities to be portalled +} + + +void hhPortal::PostSpawn( void ) { + CheckForBuddy(); +} + +void hhPortal::Save(idSaveGame *savefile) const { +#if GAMEPORTAL_PVS + savefile->WriteInt( areaPortal ); + if ( areaPortal ) { + savefile->WriteInt( gameRenderWorld->GetPortalState( areaPortal ) ); + } +#endif + savefile->WriteInt( portalState ); + savefile->WriteBool( bNoTeleport ); + savefile->WriteBool( bGlowPortal ); + savefile->WriteVec3( portalGravity ); + savefile->WriteFloat( closeDelay ); + savefile->WriteBool( monsterportal ); + savefile->WriteBool( alertMonsters ); + + savefile->WriteInt( slavePortals.Num() ); // idList > + for (int i=0; iWriteFloat(distanceToggle); + savefile->WriteFloat(distanceCull); + savefile->WriteBool(areaPortalCulling); + + savefile->WriteInt( proximityEntities.Num() ); + for( int i = 0; i < proximityEntities.Num(); i++ ) { + proximityEntities[i].entity.Save( savefile ); + savefile->WriteVec3( proximityEntities[i].lastPortalPoint ); + } + + m_portalIdleFx.Save(savefile); +} + +void hhPortal::Restore( idRestoreGame *savefile ) { + int i, num; + +#if GAMEPORTAL_PVS + savefile->ReadInt( areaPortal ); + if ( areaPortal ) { + int portalState; + savefile->ReadInt( portalState ); + gameLocal.SetPortalState( areaPortal, portalState ); + } +#endif + savefile->ReadInt( (int &)portalState ); + savefile->ReadBool( bNoTeleport ); + savefile->ReadBool( bGlowPortal ); + savefile->ReadVec3( portalGravity ); + savefile->ReadFloat( closeDelay ); + savefile->ReadBool( monsterportal ); + savefile->ReadBool( alertMonsters ); + + slavePortals.Clear(); + savefile->ReadInt( num ); // idList > + slavePortals.SetNum( num ); + for (i=0; iReadFloat(distanceToggle); + savefile->ReadFloat(distanceCull); + savefile->ReadBool(areaPortalCulling); + + savefile->ReadInt( num ); + proximityEntities.SetNum( num ); + for( i = 0; i < num; i++ ) { + proximityEntities[i].entity.Restore( savefile ); + savefile->ReadVec3( proximityEntities[i].lastPortalPoint ); + } + + m_portalIdleFx.Restore(savefile); +} + +void hhPortal::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteFloat(portalGravity.x); + msg.WriteFloat(portalGravity.y); + msg.WriteFloat(portalGravity.z); + msg.WriteBits(masterPortal.GetSpawnId(), 32); + + msg.WriteFloat(renderEntity.shaderParms[SHADERPARM_MODE]); + msg.WriteFloat(renderEntity.shaderParms[SHADERPARM_TIMEOFFSET]); + + msg.WriteBits(portalState, 4); +} + +void hhPortal::ReadFromSnapshot( const idBitMsgDelta &msg ) { + portalGravity.x = msg.ReadFloat(); + portalGravity.y = msg.ReadFloat(); + portalGravity.z = msg.ReadFloat(); + masterPortal.SetSpawnId(msg.ReadBits(32)); + + renderEntity.shaderParms[SHADERPARM_MODE] = msg.ReadFloat(); + renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = msg.ReadFloat(); + + portalStates_t newPortalState = (portalStates_t)msg.ReadBits(4); + if ((newPortalState == PORTAL_CLOSED || newPortalState == PORTAL_OPENED) && + newPortalState != portalState && portalState != PORTAL_CLOSING && portalState != PORTAL_OPENING) { + Trigger(this); + } +} + +void hhPortal::ClientPredictionThink( void ) { + Think(); +} + +void hhPortal::CheckPlayerDistances(void) { + float closest = distanceToggle+distanceCull; + hhPortal *targetPortal = NULL; + + if (cameraTarget && cameraTarget->IsType(hhPortal::Type)) { //we want to measure distance from the target portal too if we have one + targetPortal = static_cast(cameraTarget); + } + + for (int i = 0; i < gameLocal.numClients; i++) { //loop through any active clients and get the closest distance to one. + idEntity *ent = gameLocal.entities[i]; + if (ent && ent->IsType(hhPlayer::Type)) { + hhPlayer *pl = static_cast(ent); + + if (pl->health > 0 && !pl->spectating && !pl->InVehicle()) { //don't open for spectators or dead players or players in vehicles + float d = (pl->GetOrigin()-GetOrigin()).Length(); + if (d < closest) { + closest = d; + } + if (targetPortal) { //check distance from the target portal + d = (pl->GetOrigin()-targetPortal->GetOrigin()).Length(); + if (d < closest) { + closest = d; + } + } + } + } + } + + if (distanceToggle != 0.0f) { //toggling portal based on distance of player + if (closest < distanceToggle) { //should be open + if (portalState == PORTAL_CLOSED) { + Trigger(this); + } + } + else { //should be closed + if (portalState == PORTAL_OPENED) { + Trigger(this); + } + } + } + + if (distanceCull != 0.0f) { //toggling only area portal based on distance of player + if (closest >= distanceCull) { //should be closed + if (!areaPortalCulling) { + areaPortalCulling = true; + if (!gameLocal.isClient) { + gameLocal.SetPortalState( areaPortal, PS_BLOCK_ALL ); + } + } + } + else if (areaPortalCulling && (portalState == PORTAL_OPENING || portalState == PORTAL_OPENED)) { //otherwise make sure it's on (if the portal is open) + if (!gameLocal.isClient) { + gameLocal.SetPortalState( areaPortal, PS_BLOCK_NONE ); + } + areaPortalCulling = false; + } + } +} + + +//========================================================================== +// +// hhPortal::Think +// +//========================================================================== + +#define NEAR_CLIP 0 //6.5 + +void hhPortal::Think( void ) { + int i; + idEntity *hit; + idPlane plane; + + if ((distanceCull != 0.0f || distanceToggle != 0.0f) && areaPortal) { //check to turn the areaportal on and off based on distance. + if (!gameLocal.isClient) { + CheckPlayerDistances(); + } + else { //since this is not sync'd, and we don't need to sync it, do an extra check here (for mp) + if (portalState == PORTAL_OPENED && renderEntity.customSkin && renderEntity.customSkin == declManager->FindSkin(spawnArgs.GetString( "skin_onlyWarp" ))) { + SetSkinByName( spawnArgs.GetString( "skin" ) ); + } + } + + if (distanceToggle != 0.0f) { //for pop-open portals, check fx state + if ((portalState == PORTAL_CLOSED || portalState == PORTAL_CLOSING) && bGlowPortal) { + //rww - broadcast fx while closed + if (!m_portalIdleFx.IsValid()) { + const char *portalIdleFx = spawnArgs.GetString("fx_idleclosed", "fx/portal_closed_idle"); + if (portalIdleFx[0]) { + hhFxInfo fxInfo; + fxInfo.SetNormal( GetAxis()[2] ); + fxInfo.RemoveWhenDone(false); + + m_portalIdleFx = SpawnFxLocal(portalIdleFx, GetOrigin(), GetAxis(), &fxInfo, true); + if (!m_portalIdleFx.IsValid()) { //spawn failure? + gameLocal.Warning("hhPortal::Think: portal could not spawn fx for fx_idleclosed (%s).", portalIdleFx); + } + } + } + + if (m_portalIdleFx.IsValid() && !m_portalIdleFx->IsActive(TH_THINK)) { + m_portalIdleFx->Nozzle(true); + } + } + else if (m_portalIdleFx.IsValid() && m_portalIdleFx->IsActive(TH_THINK)) { //if it's not closed/closing and has fx, then stop them + m_portalIdleFx->Nozzle(false); + } + } + } + + hhAnimatedEntity::Think(); + + if (gameLocal.isMultiplayer) { //rww - if portals grab their renderEntity bounds from the animation, they can cause client-server pvs discrepancies + renderEntity.bounds[0] = idVec3(-MAX_PORTAL_BOUNDS, -MAX_PORTAL_BOUNDS, -MAX_PORTAL_BOUNDS); + renderEntity.bounds[1] = idVec3(MAX_PORTAL_BOUNDS, MAX_PORTAL_BOUNDS, MAX_PORTAL_BOUNDS); + } + + if( portalState == PORTAL_CLOSED || bNoTeleport ) { + return; + } + + // Force visuals to update until a remote renderview has been created for this portal + if ( !renderEntity.remoteRenderView ) { + BecomeActive( TH_UPDATEVISUALS ); + } + + // Bit of a hack for noclipping players: If they are close to the portal, then add them to the proximity list automatically + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player && player->noclip ) { //rww - note that the local player is NULL for dedicated servers. + if ( (player->GetOrigin() - GetOrigin()).LengthFast() < 256.0f ) { + AddProximityEntity( player ); + } + } + + // Build a plane for the portal surface + plane.SetNormal(GetPhysics()->GetAxis()[0]); + plane.FitThroughPoint( GetPhysics()->GetOrigin() + plane.Normal() * NEAR_CLIP ); + + idVec3 origin = GetOrigin(); + idMat3 axis = GetAxis(); + + for ( i = 0; i < proximityEntities.Num(); i++ ) { + if ( !proximityEntities[i].entity.IsValid() ) { + // Remove this entity from the list + proximityEntities.RemoveIndex( i ); + continue; + } + + hit = proximityEntities[i].entity.GetEntity(); + idVec3 location = proximityEntities[i].lastPortalPoint; + idVec3 nextLocation = hit->GetPortalPoint(); + proximityEntities[i].lastPortalPoint = nextLocation; + if ( !AttemptPortal( plane, hit, location, nextLocation ) ) { + proximityEntities.RemoveIndex( i ); + + // If the entity is a player, then inform the player that they are no longer close to a portal + if ( hit->IsType( hhPlayer::Type ) ) { + hhPlayer *player = static_cast(hit); + player->SetPortalColliding( false ); + } + } + } +} + +bool hhPortal::AttemptPortal( idPlane &plane, idEntity *hit, idVec3 location, idVec3 nextLocation ) { + + // Don't try to portal self + if( hit == this ) { + return false; + } + + if ( hit->IsBound() ) { // Do not portal bound objects -- let the master portalling handle bound entities + return false; + } + + // Don't allow idMovers to portal. TODO: Restrict other entities? + if ( hit->IsType( idMover::Type ) || hit->IsType(hhVehicle::Type) ) { //rww - do not allow shuttles either + return false; + } + + if (hit->IsType(hhPlayer::Type)) { //rww - don't portal dead players + hhPlayer *pl = static_cast(hit); + if (pl->health <= 0) { + return false; + } + + // Check if the player is intersecting this portal. If not, then don't try to portal it + if ( !GetPhysics()->GetAbsBounds().IntersectsBounds( pl->GetPhysics()->GetAbsBounds() ) ) { + return false; + } + } + + int side = plane.Side( location ); + if ( side == PLANESIDE_ON || side == PLANESIDE_CROSS ) { + side = PLANESIDE_BACK; + } + + int nextSide = plane.Side( nextLocation ); + if ( nextSide == PLANESIDE_ON || nextSide == PLANESIDE_CROSS ) { + nextSide = PLANESIDE_BACK; + } + + if( side == PLANESIDE_BACK ) { // On the backside, remove this entity from the list + return false; + } else if ( side == nextSide ) { // Entirely on one side + // Check if the entity is too far from the plane and remove it from the list + if ( !GetPhysics()->GetAbsBounds().IntersectsBounds( hit->GetPhysics()->GetAbsBounds() ) ) { + return false; + } + + return true; + } + + // Compute the location on the plane where the entity would hit + float scale; + idVec3 dir = nextLocation - location; + plane.RayIntersection( location, dir, scale ); + + // Portal the entity + PortalEntity( hit, location + dir * scale * 1.01f ); + + // Add this entity to the destination portal's proximityEntity list + if (cameraTarget && cameraTarget->IsType(hhPortal::Type)) { + hhPortal *targetPortal = static_cast(cameraTarget); + targetPortal->CollideWithPortal( hit ); // CJR PCF 04/26/06: Previously CheckPortal + } + + return false; +} + +void hhPortal::PortalProjectile( hhProjectile *projectile, idVec3 collideLocation, idVec3 nextLocation ) { + idPlane plane; + plane.SetNormal(GetPhysics()->GetAxis()[0]); + plane.FitThroughPoint( GetPhysics()->GetOrigin() ); + + AttemptPortal( plane, projectile, collideLocation, nextLocation ); +} + +//========================================================================== +//========================================================================== +void hhPortal::CheckForBuddy() { + const char * buddyName; + idEntity * foundEntity; + hhPortal* buddy; + + // Check for a buddy portal name + buddyName = spawnArgs.GetString( "partner", NULL ); + if ( buddyName ) { + gameLocal.Warning( "Portal %s has 'partner' key. Please change this to 'buddy'", GetName() ); + } + else { + buddyName = spawnArgs.GetString( "buddy" ); + } + if ( !buddyName || !buddyName[ 0 ] ) { + return; + } + + // Get the actual entity + foundEntity = gameLocal.FindEntity( buddyName ); + if ( !foundEntity ) { + return; + } + + if ( foundEntity->IsType( hhPortal::Type ) ) { + + buddy = (hhPortal *) foundEntity; + + if (buddy->spawnArgs.FindKey("buddy")) { + gameLocal.Error( "Portals %s and %s both have 'buddy' key set. Use only on master", GetName(), buddy->GetName() ); + return; + } + + // Don't link up to buddy if a monster portal, just points to portal room + if ( !monsterportal || closeDelay == 0.0f ) { + //? OK, let's be lazy, assume only 2 portals are buddied. Make us the master + buddy->SetMasterPortal( this ); + //! Set the cameraTargets of each, then update each + buddy->spawnArgs.Set( "cameraTarget", name.c_str() ); + buddy->ProcessEvent( &EV_UpdateCameraTarget ); + } + + AddSlavePortal( buddy ); + } + + spawnArgs.Set( "cameraTarget", buddyName ); + ProcessEvent( &EV_UpdateCameraTarget ); +} + + +//============================= +// hhPortal::TriggerTargets +//============================= +void hhPortal::TriggerTargets() { + const char * key = ""; + idEntity * entity = NULL; + idList< idStr > entityNames; + + + // Find the right key based on our state + if ( portalState == PORTAL_OPENING ) { + key = "targetOpening"; + } + else if ( portalState == PORTAL_OPENED ) { + key = "targetOpened"; + } + else if ( portalState == PORTAL_CLOSING ) { + key = "targetClosing"; + } + else if ( portalState == PORTAL_CLOSED ) { + key = "targetClosed"; + } + + //gameLocal.Printf( "Keying %s\n", key ); + + // Loop through the entities, spawning each one + hhUtils::GetValues( spawnArgs, key, entityNames, true ); + + for ( int i = 0; i < entityNames.Num(); ++i ) { + //gameLocal.Printf( "Gonna trigger %s\n", entityNames[ i ].c_str() ); + entity = gameLocal.FindEntity( entityNames[ i ] ); + if ( entity ) { + entity->PostEventMS( &EV_Activate, 0, this ); + } + } +} + + +//========================================================================== +// +// hhPortal::CheckPortal +// +// If this entity can be portaled, typically called from the low-level physics clip functions +// +// CJR PCF 04/26/06: Changed this function to separate CheckPortal and CollideWithPortal +//========================================================================== + +bool hhPortal::CheckPortal( const idEntity *other, int contentMask ) { + if ( contentMask & CONTENTS_GAME_PORTAL ) { // Check if the other entity should clip against the portal + return false; + } + + if ( !other ) { + return true; + } + + if ( other->fl.noPortal ) { + return false; // Do not allow this entity to portal, make it collide with the portal instead + } + + return true; +} + +bool hhPortal::CheckPortal( const idClipModel *mdl, int contentMask ) { + if ( contentMask & CONTENTS_GAME_PORTAL ) { // Check if the other entity should clip against the portal + return false; + } + + if ( !mdl ) { + return true; + } + + return CheckPortal( mdl->GetEntity(), contentMask ); +} + +//========================================================================== +// +// hhPortal::CollideWithPortal +// +// Called from low-level clip functions, actually collide the entity with the portal +// +// CJR PCF 04/26/06: Formerly part of CheckPortal +//========================================================================== + +void hhPortal::CollideWithPortal( const idEntity *other ) { + if ( !other ) { + return; + } + + if ( other->IsType( hhProjectile::Type ) ) { // Projectiles move so fast that they should get portaled next time they think + // Only add this projectile to the collided list if it hits the front side of the portal + idPlane plane; + plane.SetNormal(GetPhysics()->GetAxis()[0]); + plane.FitThroughPoint( GetPhysics()->GetOrigin() ); + + int side = plane.Side( other->GetOrigin() ); + if ( side == PLANESIDE_FRONT ) { // It's on the front, so add this portal to the list + hhProjectile *projectile = (hhProjectile *)(other); + + // Check if the projectile will actually impact the portal + idVec3 end = projectile->GetOrigin() + projectile->GetPhysics()->GetLinearVelocity(); + + if ( plane.LineIntersection( projectile->GetOrigin(), end ) ) { // Projectile collides with the portal + projectile->SetCollidedPortal( this, projectile->GetPortalPoint(), projectile->GetPhysics()->GetLinearVelocity() ); + } + } + } else { + AddProximityEntity( other ); + } +} + +void hhPortal::CollideWithPortal( const idClipModel *mdl ) { + if ( !mdl ) { + return; + } + + CollideWithPortal( mdl->GetEntity() ); +} + +//========================================================================== +// +// hhPortal::AddProximityEntity +// +// Saves this entity on a list, and will check if it can be portalled +// the next time the portal thinks +//========================================================================== + +void hhPortal::AddProximityEntity( const idEntity *other) { + // Go through the list and guarantee that this entity isn't in multiple times + // note: cannot use IdList::AddUnique, because the lastPortalPoint might be different during this add + for( int i = 0; i < proximityEntities.Num(); i++ ) { + if ( proximityEntities[i].entity.GetEntity() == other ) { + return; + } + } + + // Add this entity to the potential portal list + proximityEntity_t prox; + prox.entity = other; + prox.lastPortalPoint = ((idEntity *)(other))->GetPortalPoint(); + + proximityEntities.Append( prox ); + + // If the entity is a player, then inform the player that they are close to this portal + // needed for weapon projectile firing + if ( other->IsType( hhPlayer::Type ) ) { + hhPlayer *player = (hhPlayer *)(other); + + player->SetPortalColliding( true ); + } +} + +//========================================================================== +// +// hhPortal::PortalEntity +// +// To rotate a vector from one portal space into another: +// transform vector into Source Portal Space (mul by axis transpose) +// flip X & Y (only flip X for angles, not for position) +// transform vector into Destination Portal Space +//========================================================================== + +void PortalRotate( idVec3 &vec, const idMat3 &sourceTranspose, const idMat3 &dest, const bool flipX ) { + vec *= sourceTranspose; + vec.y *= -1; + if ( flipX ) { + vec.x *= -1; + } + vec *= dest; +} + +bool hhPortal::PortalEntity( idEntity *ent, const idVec3 &point ) { + idMat3 sourceAxis; + idMat3 destAxis; + idMat3 newEntAxis; + + if ( !ent ) { + return(false); + } + + if ( cameraTarget ) { + sourceAxis = GetAxis().Transpose(); + destAxis = cameraTarget->GetAxis(); + + // Compute new location + idVec3 newLocation = point - GetOrigin(); + PortalRotate( newLocation, sourceAxis, destAxis, false ); + newLocation += cameraTarget->GetOrigin(); + + // Compute new axis + newEntAxis = ent->GetAxis(); + + // Rotate the vector into new portal space + PortalRotate( newEntAxis[0], sourceAxis, destAxis, true ); + PortalRotate( newEntAxis[1], sourceAxis, destAxis, true ); + PortalRotate( newEntAxis[2], sourceAxis, destAxis, true ); + + // Actually attempt to portal the entity + if ( PortalTeleport( ent, newLocation, newEntAxis, sourceAxis, destAxis ) ) { + if ( alertMonsters && !gameLocal.isMultiplayer && ent->IsType( idPlayer::Type ) ) { + gameLocal.SendMessageAI( this, GetOrigin(), 2000, MA_EnemyPortal ); + } + ent->Portalled( this ); // Inform the actor that it was just portalled + return(true); // Portal succeeded + } + } + + return(false); // Portal failed +} + +//========================================================================== +// +// hhPortal::PortalTeleport +// +//========================================================================== + +bool hhPortal::PortalTeleport( idEntity *ent, const idVec3 &origin, const idMat3 &axis, const idMat3 &sourceAxis, const idMat3 &destAxis ) { + idClipModel *clip = ent->GetPhysics()->GetClipModel(); + + if ( !clip ) { + return false; + } + + // Properly set velocity relative to the new portal + idVec3 vel = ent->GetPhysics()->GetLinearVelocity(); + PortalRotate( vel, sourceAxis, destAxis, true ); + ent->GetPhysics()->SetLinearVelocity( vel ); + + //rww - check if this new orientation is going to be in solid or not. if it is, try displacing the new origin based on the portal + idVec3 useOrigin = origin; + trace_t transCheck; + if (gameLocal.clip.TranslationWithExceptions(transCheck, useOrigin, useOrigin, NULL, clip, axis, ent->GetPhysics()->GetClipMask(), ent)) { + if (cameraTarget) { + bool safeSpot = true; + const float distExtrusion = 2.0f; + const float heightAdjust = (ent->GetPhysics()->GetBounds()[1].z-fabsf(ent->GetPhysics()->GetBounds()[0].z))/2.0f; + idVec3 testOrigin = cameraTarget->GetOrigin(); + testOrigin += cameraTarget->GetAxis()[0]*distExtrusion; + testOrigin -= cameraTarget->GetAxis()[2]*heightAdjust; + if (gameLocal.clip.TranslationWithExceptions(transCheck, testOrigin, testOrigin, NULL, clip, axis, ent->GetPhysics()->GetClipMask(), ent)) { + testOrigin += cameraTarget->GetAxis()[2]*(heightAdjust*2.0f); //then try going up further (could be upside-down or something) + + if (gameLocal.clip.TranslationWithExceptions(transCheck, testOrigin, testOrigin, NULL, clip, axis, ent->GetPhysics()->GetClipMask(), ent)) { + //damn, this portal is really busted. + safeSpot = false; + } + } + if (safeSpot) { + useOrigin = testOrigin; //got a safe spot + if (ent->IsType(hhPlayer::Type)) { + hhPlayer *plEnt = static_cast(ent); + if (plEnt->IsWallWalking()) { //if it's a wallwalking player, try to trace down on the other side for wallwalk + idVec3 downPoint = useOrigin - (axis[2]*64.0f); + if (gameLocal.clip.Translation(transCheck, useOrigin, downPoint, clip, axis, ent->GetPhysics()->GetClipMask(), ent)) { + if (gameLocal.GetMatterType(transCheck, NULL) == SURFTYPE_WALLWALK) { //if we hit wallwalk, use the trace endpoint + useOrigin = transCheck.endpos; + } + } + } + } + } + else { + testOrigin = cameraTarget->GetOrigin(); + testOrigin += cameraTarget->GetAxis()[0]*distExtrusion; + testOrigin -= cameraTarget->GetAxis()[2]*heightAdjust; + + useOrigin = testOrigin; + +#if !GOLD + if (developer.GetBool()) { + const char *entName = ent->GetName(); + if (!entName || !entName[0]) { + entName = ""; + } + gameLocal.Warning("Ent '%s' could not get a clean trace on the other side of gameportal at (%f %f %f).", entName, useOrigin.x, useOrigin.y, useOrigin.z); + hhUtils::DebugAxis( testOrigin, cameraTarget->GetAxis(), 32.0f, 5000 ); + } +#endif + } + } + } + + // If the actor is a player, then disable camera interpolation for this move. This must be done before linking + if( ent->IsType(hhPlayer::Type) ) { + hhPlayer* player = static_cast( ent ); + + + idVec3 viewDir = player->GetAxis()[0]; + PortalRotate( viewDir, sourceAxis, destAxis, true ); + player->TeleportNoKillBox( useOrigin, axis, viewDir, player->GetUntransformedViewAngles() ); + } else { + // Valid move. This code is done in SetOrientation for the player. + ent->SetOrigin( useOrigin ); + ent->SetAxis( axis ); + } + + // Re-link the actor into the clip tree + clip->Link( gameLocal.clip ); + + if ( ent->IsType( idActor::Type ) ) { + static_cast(ent)->LinkCombat(); + } + + // players telefrag anything at the new position + if ( ent->IsType( hhPlayer::Type ) ) { + gameLocal.KillBox( ent ); + } + + // Set the gravity correctly on the entity that was portaled, based upon the destination portal's gravity + ent->CancelEvents( &EV_ResetGravity ); + + if (!ent->IsType(hhVehicle::Type)) { + ent->SetGravity( cameraTarget->GetGravity() ); + } + + // If the actor is a player, then check for wallwalk. This should be done after linking and setting gravity + if( ent->IsType(hhPlayer::Type) ) { + hhPlayer* player = static_cast(ent); + if (player->GetPhysics() && player->GetPhysics()->IsType(hhPhysics_Player::Type)) { + static_cast(player->GetPhysics())->CheckWallWalk( true ); + } + } + + // Sound issues + if ( bGlowPortal ) { + StartSound( "snd_portal_entity", SND_CHANNEL_ANY ); // Play the portal sound at the origin portal + if(this->cameraTarget) { // Play the portal sound at the destination portal as well + const idSoundShader *def = declManager->FindSound(spawnArgs.GetString("snd_portal_entity"));//gameSoundWorld->FinishShader(spawnArgs.GetString("snd_portal_entity")); + this->cameraTarget->StartSoundShader( def, SND_CHANNEL_ANY ); + } + } + + return(true); +} + +//========================================================================== +// +// hhPortal::Event_Trigger +// +// Toggle portal state. +//========================================================================== + +void hhPortal::Event_Trigger(idEntity *activator) { + + // If we have a master portal, have him trigger everyone + if ( masterPortal.IsValid() ) { + masterPortal->Event_Trigger( activator ); + return; + } + + // If we have slave portals, trigger them first + if ( slavePortals.Num() > 0 ) { + for ( int i = 0; i < slavePortals.Num(); ++i ) { + slavePortals[ i ]->Trigger( activator ); + } + } + + Trigger( activator ); +} + + +void hhPortal::Trigger( idEntity *activator ) { + + hhFxInfo fxInfo; + + fxInfo.SetNormal( GetAxis()[2] ); + fxInfo.RemoveWhenDone( true ); + + if(portalState == PORTAL_CLOSED) { + portalState = PORTAL_OPENING; + if (!bNoTeleport) { //rww - if teleporting, ensure things collide with me while i am opening + GetPhysics()->SetContents( CONTENTS_SOLID ); + } +#if GAMEPORTAL_PVS + if (areaPortal && !gameLocal.isClient) { + gameLocal.SetPortalState( areaPortal, PS_BLOCK_NONE ); + } +#endif + TriggerTargets(); // nla + renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = MS2SEC( gameLocal.time ); + + if ( bGlowPortal ) { + if (!gameLocal.isMultiplayer) { //rww - superfast portals don't use fx + BroadcastFxInfo( spawnArgs.GetString("fx_open"), GetOrigin(), GetAxis(), &fxInfo ); + } + + StartSound( "snd_open", SND_CHANNEL_ANY ); + + if ( spawnArgs.GetBool( "fast_open", "0" ) ) { + SetSkinByName( spawnArgs.GetString( "skin" ) ); + } else { + SetSkinByName( spawnArgs.GetString( "skin_onlyWarp" ) ); // Only show the warp when first opening + } + + int anim = GetAnimator()->GetAnim(spawnArgs.GetString("open_anim", "open")); + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, 0 ); + PostEventMS( &EV_Opened, GetAnimator()->AnimLength( anim ) ); + SetShaderParm( SHADERPARM_MODE, 0 ); // ensure that sparking is off + + fl.neverDormant = true; // Don't allow the portal to go dormant while opening/closing + + } else { + PostEventMS( &EV_Opened, 500 ); + } + + PostEventMS( &EV_Show, 10 ); // Delay showing for a frame so the skin and registers are properly set beforehand + } else if(portalState == PORTAL_OPENED || portalState == PORTAL_OPENING) { + portalState = PORTAL_CLOSING; + TriggerTargets(); // nla + + renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time ); + + if ( bGlowPortal ) { + if (!gameLocal.isMultiplayer) { //rww - superfast portals don't use fx + BroadcastFxInfo( spawnArgs.GetString("fx_close"), GetOrigin(), GetAxis(), &fxInfo ); + } + + StopSound( SND_CHANNEL_ANY ); + StartSound( "snd_close", SND_CHANNEL_ANY ); + + int anim = GetAnimator()->GetAnim("close"); + GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, 0.5 ); + PostEventMS( &EV_Closed, GetAnimator()->AnimLength( anim ) ); + + fl.neverDormant = true; // Don't allow the portal to go dormant while opening/closing + + } else { + PostEventMS( &EV_Closed, 400 ); + } + } +} + +//========================================================================== +// +// hhPortal::Event_Opened +// +//========================================================================== + +void hhPortal::Event_Opened( void ) { + portalState = PORTAL_OPENED; + if (!bNoTeleport) { //rww - if teleporting, ensure things collide with me while i am open + GetPhysics()->SetContents( CONTENTS_SOLID ); + } + TriggerTargets(); // nla + + if ( bGlowPortal ) { + StartSound( "snd_loop", SND_CHANNEL_ANY ); + int anim = GetAnimator()->GetAnim("opened"); + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, 0 ); + + fl.neverDormant = false; // The portal can go dormant once opened/closed + + PostEventSec( &EV_PortalSpark, gameLocal.random.RandomFloat() ); + } + + UpdateVisuals(); + + // If we are open, and should close automatically, post an event to trigger us closed - nla + if ( closeDelay > 0.0f ) { + PostEventSec( &EV_Activate, closeDelay, NULL ); + + // Remove the portal now that it has done it's job + if (monsterportal) { +#ifdef _DEBUG + // If another portal is camera targetting me it will cause a crash so check for it + for( idEntity *ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if (ent->cameraTarget && ent->cameraTarget == this) { + assert(0); + } + } + +#endif + PostEventSec( &EV_Remove, closeDelay+5.0f ); + } + } +} + +//========================================================================== +// +// hhPortal::Event_Closed +// +//========================================================================== + +void hhPortal::Event_Closed( void ) { + renderEntity.shaderParms[SHADERPARM_MODE] = 1.0f; + portalState = PORTAL_CLOSED; + GetPhysics()->SetContents( 0 ); //rww - do not collide with things while closed +#if GAMEPORTAL_PVS + if (areaPortal && !gameLocal.isClient) { + gameLocal.SetPortalState( areaPortal, PS_BLOCK_ALL ); + } +#endif + TriggerTargets(); // nla + + fl.neverDormant = false; // The portal can go dormant once opened/closed + + Hide(); + UpdateVisuals(); + + //? Have an option to remove the portals? - nla + if ( spawnArgs.GetBool( "remove_on_close", "0" ) ) { + PostEventMS( &EV_Remove, 0 ); + } +} + +//========================================================================== +// +// hhPortal::Event_PortalSpark +// +//========================================================================== + +void hhPortal::Event_PortalSpark( void ) { + int spark; + float nextTime; + + if ( this->IsHidden() ) { // No sparking if hidden + SetShaderParm( SHADERPARM_MODE, 0 ); // set the material parm (one-based) + return; + } + + spark = gameLocal.random.RandomInt( spawnArgs.GetInt( "sparkCount" ) ); + + // Set the shader parm as needed + SetShaderParm( SHADERPARM_MODE, spark + 1 ); // set the material parm (one-based) + + if ( gameLocal.random.RandomFloat() < 0.1f ) { // 1/10th of a chance that another spark will happen quickly after this one + nextTime = 0.2f; + } else { // Normal random time between sparks. + nextTime = spawnArgs.GetFloat( "sparkTimeMin" ) + gameLocal.random.RandomFloat() * spawnArgs.GetFloat( "sparkTimeRnd" ); + } + + StartSound( "snd_portal_spark", SND_CHANNEL_ANY ); + + PostEventSec( &EV_PortalSpark, nextTime ); + PostEventSec( &EV_PortalSparkEnd, 0.1f ); // spark only lasts for 0.1 sec + +} + +//========================================================================== +// +// hhPortal::Event_PortalSparkEnd +// +//========================================================================== + +void hhPortal::Event_PortalSparkEnd( void ) { + SetShaderParm( SHADERPARM_MODE, 0 ); // Disable the spark parm +} + +//========================================================================== +// +// hhPortal::Event_ShowGlowPortal +// +// Simply sets the skin on the portal back to the default or to the +// specified "skin" +//========================================================================== + +void hhPortal::Event_ShowGlowPortal( void ) { + SetSkinByName( spawnArgs.GetString( "skin" ) ); +} + +//========================================================================== +// +// hhPortal::Event_HideGlowPortal +// +// Sets the skin on the portal to a specific skin that hides the glowy parts +//========================================================================== + +void hhPortal::Event_HideGlowPortal( void ) { + SetSkinByName( spawnArgs.GetString( "skin_onlyWarp" ) ); +} diff --git a/src/Prey/game_portal.h b/src/Prey/game_portal.h new file mode 100644 index 0000000..ea59f61 --- /dev/null +++ b/src/Prey/game_portal.h @@ -0,0 +1,125 @@ + +#ifndef __GAME_PORTAL_H__ +#define __GAME_PORTAL_H__ + +#if GAMEPORTAL_PVS + +// Allows us to create a PVS link between two areas +class hhArtificialPortal : public idEntity { + CLASS_PROTOTYPE( hhArtificialPortal ); +public: + void Spawn(); + void SetPortalState(bool openPVS, bool openSound); + + void Save(idSaveGame *savefile) const; + void Restore(idRestoreGame *savefile); + +protected: + void Event_SetPortalState(bool openPVS, bool openSound); + + qhandle_t areaPortal; // 0 = no portal +}; + +#endif + +typedef struct proximityEntity_s { + idEntityPtr entity; + idVec3 lastPortalPoint; +} proximityEntity_t; + +// jsh - Changed from idEntity to hhAnimatedEntity +class hhPortal : public hhAnimatedEntity { +public: + CLASS_PROTOTYPE( hhPortal ); + + typedef enum { + PORTAL_OPENING, + PORTAL_OPENED, + PORTAL_CLOSING, + PORTAL_CLOSED, + } portalStates_t; + + + hhPortal(void); + ~hhPortal(); + + void Spawn(void); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - network code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + + void CheckPlayerDistances(void); + void Think( void ); + bool PortalEntity( idEntity *ent, const idVec3 &point ); + bool PortalTeleport( idEntity *ent, const idVec3 &origin, const idMat3 &axis, const idMat3 &sourceAxis, const idMat3 &destAxis ); + bool IsActive(void) { return(portalState < PORTAL_CLOSING); } + virtual void SetGravity( const idVec3& newGravity ) { portalGravity = newGravity; } + const idVec3& GetGravity( void ) const { return portalGravity; } + void PostSpawn( void ); + + bool AttemptPortal( idPlane &plane, idEntity *hit, idVec3 location, idVec3 nextLocation ); + + void PortalProjectile( hhProjectile *projectile, idVec3 collideLocation, idVec3 nextLocation ); + + virtual bool CheckPortal( const idEntity *other, int contentMask ); + virtual bool CheckPortal( const idClipModel *mdl, int contentMask ); + virtual void CollideWithPortal( const idEntity *other ); // CJR PCF 04/26/06 + virtual void CollideWithPortal( const idClipModel *mdl ); // CJR PCF 04/26/06 + + void AddProximityEntity( const idEntity *other); + +protected: + void Event_Opened( void ); + void Event_Closed( void ); + void Event_Trigger(idEntity *activator); + void Event_ResetGravity() { portalGravity = hhUtils::GetLocalGravity(GetOrigin(), GetPhysics()->GetBounds(), gameLocal.GetGravity() ); } + + void Event_PortalSpark( void ); + void Event_PortalSparkEnd( void ); + + void Event_ShowGlowPortal( void ); + void Event_HideGlowPortal( void ); + + // Actual logic for the trigger functionality. (Needed to break out for partering :) + void Trigger( idEntity *activator ); + + // Methods used to sync up portals + hhPortal * GetMasterPortal() { return( masterPortal.GetEntity() ); } + void SetMasterPortal( hhPortal *master ) { masterPortal = master; } + void AddSlavePortal( hhPortal *slave ) { slavePortals.Append( slave ); } + void CheckForBuddy(); + + void TriggerTargets(); + +private: +#if GAMEPORTAL_PVS + qhandle_t areaPortal; // 0 = no portal +#endif + + portalStates_t portalState; + + bool bNoTeleport; // Is purely a visual portal. Will not try to teleport anything near it + bool bGlowPortal; // This portal has visual/audio effects + + idVec3 portalGravity; // Local gravity to this portal. Will be changed by any zones the portal is within + + float closeDelay; // Secs to wait before automatically closing when opened - nla + float distanceToggle; //rww - toggles the associated game portal on and off based on nearest player distance, 0.0 means not enabled, otherwise value is the distance to turn on at. + float distanceCull; //rww - distance to shut off the vis gameportal but not the render portal + bool areaPortalCulling; //rww - if the portal is currently "culled" (off) due to distanceCull checking + bool monsterportal; + bool alertMonsters; + + idList > slavePortals; + idEntityPtr masterPortal; + + idList proximityEntities; + + idEntityPtr m_portalIdleFx; //rww - primarily for mp, the effect closed pop-open portals play +}; + +#endif /* __GAME_PORTAL_H__ */ diff --git a/src/Prey/game_portalframe.cpp b/src/Prey/game_portalframe.cpp new file mode 100644 index 0000000..d48755f --- /dev/null +++ b/src/Prey/game_portalframe.cpp @@ -0,0 +1,20 @@ +// hhPortalFrame +// +// Frame for our portal which accepts commands from GUIs which change shader parm5 +// based on the command, as well as trigger some given entities each level, and finally +// trigger all it's own targets upon the victory command + + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION(idEntity, hhPortalFrame) +END_CLASS + + +void hhPortalFrame::Spawn() { + GetPhysics()->SetContents(CONTENTS_SOLID); +} + diff --git a/src/Prey/game_portalframe.h b/src/Prey/game_portalframe.h new file mode 100644 index 0000000..708570a --- /dev/null +++ b/src/Prey/game_portalframe.h @@ -0,0 +1,14 @@ + +#ifndef __GAME_PORTALFRAME_H__ +#define __GAME_PORTALFRAME_H__ + + +class hhPortalFrame : public idEntity { +public: + CLASS_PROTOTYPE( hhPortalFrame ); + + void Spawn( void ); +}; + + +#endif /* __GAME_PORTALFRAME_H__ */ diff --git a/src/Prey/game_proxdoor.cpp b/src/Prey/game_proxdoor.cpp new file mode 100644 index 0000000..9a558ec --- /dev/null +++ b/src/Prey/game_proxdoor.cpp @@ -0,0 +1,1163 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +const idEventDef EV_PollForExit("", NULL); +const float proxDoorRefreshMS = 50.f; //refresh rate of door while open (controls how frequently to check if someone is still close enough) + + +ABSTRACT_DECLARATION( idEntity, hhProxDoorSection ) +END_CLASS + +void hhProxDoorSection::Spawn( void ) { + fl.networkSync = true; + proximity = 0.0f; + hasNetData = false; + proxyParent = NULL; +} + +void hhProxDoorSection::ClientPredictionThink( void ) { + Show(); + idEntity::ClientPredictionThink(); +} + +void hhProxDoorSection::WriteToSnapshot( idBitMsgDelta &msg ) const { + WriteBindToSnapshot(msg); + GetPhysics()->WriteToSnapshot(msg); +} + +void hhProxDoorSection::ReadFromSnapshot( const idBitMsgDelta &msg ) { + ReadBindFromSnapshot(msg); + GetPhysics()->ReadFromSnapshot(msg); + + if (msg.HasChanged()) { + Show(); + UpdateVisuals(); + Present(); + GetPhysics()->LinkClip(); + } +} + + + +//------------------------------------------------------------------------------------------------- +// hhProxDoor. +//------------------------------------------------------------------------------------------------- +CLASS_DECLARATION( idEntity, hhProxDoor ) + EVENT( EV_Touch, hhProxDoor::Event_Touch ) + EVENT( EV_PollForExit, hhProxDoor::Event_PollForExit ) + EVENT( EV_PostSpawn, hhProxDoor::Event_PostSpawn ) + EVENT( EV_Activate, hhProxDoor::Event_Activate ) +END_CLASS + +//-------------------------------- +// hhProxDoor::hhProxDoor +//-------------------------------- +hhProxDoor::hhProxDoor() { + areaPortal = 0; + proxState = PROXSTATE_Inactive; + doorTrigger = NULL; + aas_area_closed = false; + hasNetData = false; + sndTrigger = NULL; + nextSndTriggerTime = 0; + //HUMANHEAD PCF rww 06/06/06 - fix for snappy prox doors + lastAmount = 0.0f; + //HUMANHEAD END +} + +//-------------------------------- +// hhProxDoor::hhProxDoor +//-------------------------------- +hhProxDoor::~hhProxDoor() { + if (doorTrigger) { + doorTrigger->Unlink(); + delete doorTrigger; + doorTrigger = NULL; + } + if ( sndTrigger ) { + sndTrigger->Unlink(); + delete sndTrigger; + sndTrigger = NULL; + } +} + +//-------------------------------- +// hhProxDoor::Spawn +//-------------------------------- +void hhProxDoor::Spawn() { + proxState = PROXSTATE_Inactive; + doorSndState = PDOORSND_Closed; + + spawnArgs.GetBool( "startlocked", "0", doorLocked ); + SetShaderParm( SHADERPARM_MODE, GetDoorShaderParm( doorLocked, true ) ); // 2=locked, 1=unlocked, 0=never locked + + if( !spawnArgs.GetFloat("trigger_distance", "0.0", maxDistance) ) { + common->Warning( "No 'trigger_distance' specified for entityDef '%s'", this->GetEntityDefName() ); + } + if( !spawnArgs.GetFloat("movement_distance", "0.0", movementDistance) ) { + common->Warning( "No 'movement_distance' specified for entityDef '%s'", this->GetEntityDefName() ); + } + lastDistance = movementDistance + 1.0f; // Safe default -mdl + + if( !spawnArgs.GetFloat("stop_distance", "0.0", stopDistance) ) { + common->Warning( "No 'stop_distance' specificed for entityDef '%s'", this->GetEntityDefName() ); + } + + spawnArgs.GetBool( "openForMonsters", "1", openForMonsters ); + spawnArgs.GetFloat( "damage", "1", damage ); + + //Find any portal boundary we are on + areaPortal = gameRenderWorld->FindPortal( GetPhysics()->GetAbsBounds() ); + + if (gameLocal.isMultiplayer) { + fl.networkSync = true; + + if (gameLocal.isClient) { + doorTrigger = new idClipModel( idTraceModel(idBounds(vec3_origin).Expand(maxDistance)) ); + doorTrigger->Link( gameLocal.clip, this, 255, GetOrigin(), GetAxis() ); + doorTrigger->SetContents( CONTENTS_TRIGGER ); + + SetAASAreaState( doorLocked ); //close the area state if we are locked... + SetDoorState( PROXSTATE_Inactive ); + } + else { + PostEventMS( &EV_PostSpawn, 50 ); //rww - this must be delayed or it will happen on the first server frame and we DON'T want that. + } + } + else { //just call it now. some sp maps might target a door piece in the spawn function of an entity or something crazy like that. + Event_PostSpawn(); + } +} + +//-------------------------------- +// hhProxDoor::Event_PostSpawn +//-------------------------------- +void hhProxDoor::Event_PostSpawn( void ) { + int numSubObjs; + int i; + const char* objDef; + +//Parse and spawn our door sections + numSubObjs = spawnArgs.GetInt( "num_doorobjs", "0" ); + for( i = 0; i < numSubObjs; i++ ) { + if( !spawnArgs.GetString( va("doorobject%i", i+1), "", &objDef ) ) { + common->Warning( "failed to find doorobject%i", i+1 ); + } + else { + //Set our default rotation/origin. + idDict args; + args.SetVector( "origin", this->GetOrigin() ); + args.SetMatrix( "rotation", this->GetAxis() ); + hhProxDoorSection* ent = static_cast ( gameLocal.SpawnObject(objDef, &args) ); + if( !ent ) { + common->Warning( "failed to spawn doorobject%i for entityDef '%s'", i+1, GetEntityDefName() ); + } + else { + ent->proxyParent = this; + doorPieces.Append( ent ); + } + } + } +//Spawn our trigger and link it up +// if( doorLocked && !spawnArgs.GetBool("locktrigger") ) { + //we would never be able to open the door, so don't spawn a trigger +// } +// else { + doorTrigger = new idClipModel( idTraceModel(idBounds(vec3_origin).Expand(maxDistance)) ); + doorTrigger->Link( gameLocal.clip, this, 255, GetOrigin(), GetAxis() ); + doorTrigger->SetContents( CONTENTS_TRIGGER ); +// } + + SpawnSoundTrigger(); + + SetAASAreaState( doorLocked ); //close the area state if we are locked... + SetDoorState( PROXSTATE_Inactive ); +} + +void hhProxDoor::SpawnSoundTrigger() { + idBounds bounds,localbounds; + int i; + int best; + + // Since models bounds are overestimated, we need to use the bounds from the + // clipmodel, which was set before the over-estimation + localbounds = GetPhysics()->GetBounds(); + + // find the thinnest axis, which will be the one we expand + best = 0; + for ( i = 1 ; i < 3 ; i++ ) { + if ( localbounds[1][ i ] - localbounds[0][ i ] < localbounds[1][ best ] - localbounds[0][ best ] ) { + best = i; + } + } + localbounds[1][ best ] += 50; + localbounds[0][ best ] -= 50; + + // Now transform into absolute coordintates + if ( GetPhysics()->GetAxis().IsRotated() ) { + bounds.FromTransformedBounds( localbounds, GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); + } + else { + bounds[0] = localbounds[0] + GetPhysics()->GetOrigin(); + bounds[1] = localbounds[1] + GetPhysics()->GetOrigin(); + } + bounds[0] -= GetPhysics()->GetOrigin(); + bounds[1] -= GetPhysics()->GetOrigin(); + + // create a trigger clip model + sndTrigger = new idClipModel( idTraceModel( bounds ) ); + sndTrigger->Link( gameLocal.clip, this, 254, GetPhysics()->GetOrigin(), mat3_identity ); + sndTrigger->SetContents( CONTENTS_TRIGGER ); +} + +void hhProxDoor::Save(idSaveGame *savefile) const { + int i; + + savefile->WriteInt( proxState ); + savefile->WriteInt( doorSndState ); + + savefile->WriteInt( doorPieces.Num() ); // idList + for (i=0; iWriteObject( doorPieces[i] ); + doorPieces[i].Save(savefile); + } + + savefile->WriteClipModel( doorTrigger ); + savefile->WriteFloat( entDistanceSq ); + savefile->WriteFloat( maxDistance ); + savefile->WriteFloat( movementDistance ); + savefile->WriteFloat( stopDistance ); + savefile->WriteInt( areaPortal ); + if ( areaPortal ) { + savefile->WriteInt( gameRenderWorld->GetPortalState( areaPortal ) ); + } + savefile->WriteBool( doorLocked ); + savefile->WriteFloat( lastAmount ); + savefile->WriteBool( openForMonsters ); + savefile->WriteBool( aas_area_closed ); + savefile->WriteFloat( lastDistance ); + savefile->WriteClipModel( sndTrigger ); + savefile->WriteInt( nextSndTriggerTime ); +} + +void hhProxDoor::Restore( idRestoreGame *savefile ) { + int i, num; + + savefile->ReadInt( (int &)proxState ); + savefile->ReadInt( (int &)doorSndState ); + + doorPieces.Clear(); + savefile->ReadInt( num ); // idList + doorPieces.SetNum( num ); + for (i=0; iReadObject( reinterpret_cast(doorPieces[i]) ); + doorPieces[i].Restore(savefile); + } + + savefile->ReadClipModel( doorTrigger ); + savefile->ReadFloat( entDistanceSq ); + savefile->ReadFloat( maxDistance ); + savefile->ReadFloat( movementDistance ); + savefile->ReadFloat( stopDistance ); + savefile->ReadInt( (int &)areaPortal ); + if ( areaPortal ) { + int portalState; + savefile->ReadInt( portalState ); + gameLocal.SetPortalState( areaPortal, portalState ); + } + savefile->ReadBool( doorLocked ); + savefile->ReadFloat( lastAmount ); + savefile->ReadBool( openForMonsters ); + savefile->ReadBool( aas_area_closed ); + savefile->ReadFloat( lastDistance ); + + SetAASAreaState( aas_area_closed ); + + spawnArgs.GetFloat( "damage", "1", damage ); + savefile->ReadClipModel( sndTrigger ); + savefile->ReadInt( nextSndTriggerTime ); +} + +void hhProxDoor::ClientPredictionThink( void ) { + Think(); +} + +void hhProxDoor::WriteToSnapshot( idBitMsgDelta &msg ) const { + WriteBindToSnapshot(msg); + GetPhysics()->WriteToSnapshot(msg); + + msg.WriteBits(doorPieces.Num(), 8); + for (int i = 0; i < doorPieces.Num(); i++) { + msg.WriteBits(doorPieces[i].GetSpawnId(), 32); + } + + msg.WriteBits(proxState, 8); + + msg.WriteFloat(lastAmount); + + msg.WriteBits(aas_area_closed, 1); + + //don't need this, predicting door movement. + //msg.WriteBits(doorSndState, 8); +} + +void hhProxDoor::ReadFromSnapshot( const idBitMsgDelta &msg ) { + ReadBindFromSnapshot(msg); + GetPhysics()->ReadFromSnapshot(msg); + + int num = msg.ReadBits(8); + doorPieces.SetNum(num); + for (int i = 0; i < num; i++) { + int spawnId = msg.ReadBits(32); + if (!spawnId) { + doorPieces[i] = NULL; + } + else { + doorPieces[i].SetSpawnId(spawnId); + } + } + + EProxState newProxState = (EProxState)msg.ReadBits(8); + if (proxState != newProxState) { + SetDoorState(newProxState); + } + + lastAmount = msg.ReadFloat(); + + bool closed = !!msg.ReadBits(1); + if (aas_area_closed != closed) { + SetAASAreaState(closed); + } + + /* + EPDoorSound newSndState = (EPDoorSound)msg.ReadBits(8); + if (newSndState != doorSndState) { + UpdateSoundState(newSndState); + } + */ + + hasNetData = true; +} + +//-------------------------------- +// hhProxDoor::Think +//-------------------------------- +void hhProxDoor::Ticker( void ) { + int i; + float bestDistSq, bestDist = idMath::INFINITY, amount; + + //HUMANHEAD PCF mdl 04/27/06 - Locked doors are forced inactive + if ( doorLocked && proxState == PROXSTATE_Active ) { + proxState = PROXSTATE_GoingInactive; + } + + if ( proxState == PROXSTATE_GoingInactive ) { + float fullyOpen = (movementDistance - stopDistance) / movementDistance; + //HUMANHEAD PCF mdl 04/27/06 - Added 30 HZ multiplier + float step = (0.2f * ( 1.0f / ( 1000.0f / 60.0f ) ) ) * (60.0f * USERCMD_ONE_OVER_HZ); + float low = idMath::ClampFloat( 0.0f, 1.0f, lastAmount - step ); + float high = idMath::ClampFloat( 0.0f, 1.0f, lastAmount + step ); + amount = idMath::ClampFloat( low, high, 0.0f ); + //HUMANHEAD PCF mdl 04/27/06 - Removed old lock code that was here + for( i = 0; i < doorPieces.Num(); i++ ) { + if (doorPieces[i].IsValid()) { //rww - added in case something decides to remove one of the pieces externally. + if (gameLocal.isClient && areaPortal && gameRenderWorld->GetPortalState(areaPortal) != PS_BLOCK_NONE) { + amount = 0.0f; //rww - hax for portal state sync'ing on client and server + } + doorPieces[ i ]->SetProximity( amount ); + } + } + + if ( amount == 0.0f ) { + SetDoorState( PROXSTATE_Inactive ); + UpdateSoundState( PDOORSND_Closed ); + } else { + UpdateSoundState( PDOORSND_Closing ); + } + + } else { + + bestDist = movementDistance; + bestDistSq = PollClosestEntity(); + if( bestDistSq >= 0.f ) { + bestDist = idMath::Sqrt( bestDistSq ); + } + + if ( bestDistSq == -1.0f ) { + SetDoorState( PROXSTATE_GoingInactive ); + return; + } + + // Set the default amount to no change + amount = lastAmount; + if( bestDist < movementDistance ) { + float fullyOpen = (movementDistance - stopDistance) / movementDistance; + amount = idMath::ClampFloat( 0.f, fullyOpen, (1.f - ( bestDist / movementDistance )) ); + //HUMANHEAD PCF mdl 04/27/06 - Added 30 HZ multiplier + float step = (0.2f * ( 1.0f / ( 1000.0f / 60.0f ) ) ) * (60.0f * USERCMD_ONE_OVER_HZ); + float low = idMath::ClampFloat( 0.0f, 1.0f, lastAmount - step ); + float high = idMath::ClampFloat( 0.0f, 1.0f, lastAmount + step ); + amount = idMath::ClampFloat( low, high, amount ); + if ( doorLocked ) { + for( i = 0; i < doorPieces.Num(); i++ ) { + if (doorPieces[i].IsValid()) { + doorPieces[ i ]->SetProximity( 0.0f ); + } + } + } else { + for( i = 0; i < doorPieces.Num(); i++ ) { + if (doorPieces[i].IsValid()) { //rww - added in case something decides to remove one of the pieces externally. + if (gameLocal.isClient && areaPortal && gameRenderWorld->GetPortalState(areaPortal) != PS_BLOCK_NONE) { + amount = 0.0f; //rww - hax for portal state sync'ing on client and server + } + doorPieces[ i ]->SetProximity( amount ); + } + } + } + if( lastAmount == -1 && amount >= 0.f ) { //was closed, just starting to open + UpdateSoundState( PDOORSND_Opened ); + } + else if( lastAmount > amount ) { //closing + UpdateSoundState( PDOORSND_Closing ); + } + else if( lastAmount < amount ) { //opening + UpdateSoundState( PDOORSND_Opening ); + } + else if( amount == fullyOpen ) { //fully open + UpdateSoundState( PDOORSND_FullyOpened ); + } + else { //stop sounds, since we aren't moving + UpdateSoundState( PDOORSND_Stopped ); + } + } + else { + SetDoorState( PROXSTATE_GoingInactive ); + UpdateSoundState( PDOORSND_Closing ); + } + } + + // If we're closing and 75% closed or more, crush anything unlucky enough to be inside us (don't do this on the client -rww) + if ( !gameLocal.isClient && lastAmount < 0.25f && lastAmount != -1.0f && amount != -1.0f && amount < lastAmount ) { + CrushEntities(); + } + lastAmount = amount; + lastDistance = bestDist; +} + +void hhProxDoor::UpdateSoundState( EPDoorSound newState ) { + if( doorSndState == newState ) { + return; + } + switch( newState ) { + case PDOORSND_Opening: + StopSound( SND_CHANNEL_BODY ); + StartSound( "snd_closing", SND_CHANNEL_BODY ); + break; + + case PDOORSND_Closing: + StopSound( SND_CHANNEL_BODY ); + StartSound( "snd_opening", SND_CHANNEL_BODY ); + break; + + case PDOORSND_Closed: + StopSound( SND_CHANNEL_BODY2 ); + StopSound( SND_CHANNEL_BODY ); + StartSound( "snd_closed", SND_CHANNEL_BODY2 ); + break; + + case PDOORSND_Opened: + StopSound( SND_CHANNEL_BODY2 ); + StartSound( "snd_opened", SND_CHANNEL_BODY2 ); + break; + + case PDOORSND_Stopped: + StopSound( SND_CHANNEL_BODY ); + break; + + case PDOORSND_FullyOpened: + StopSound( SND_CHANNEL_BODY ); + StopSound( SND_CHANNEL_BODY2 ); + StartSound( "snd_fullyopened", SND_CHANNEL_BODY2 ); + break; + } + doorSndState = newState; +} + +//-------------------------------- +// hhProxDoor::OpenPortal +//-------------------------------- +void hhProxDoor::OpenPortal( void ) { + if( areaPortal && !gameLocal.isClient ) { + gameLocal.SetPortalState( areaPortal, PS_BLOCK_NONE ); + } + SetAASAreaState( false ); +} + +//-------------------------------- +// hhProxDoor::ClosePortal +//-------------------------------- +void hhProxDoor::ClosePortal( void ) { + if( areaPortal && !gameLocal.isClient ) { + gameLocal.SetPortalState( areaPortal, PS_BLOCK_VIEW ); + } + SetAASAreaState( IsLocked() ); +} + +//-------------------------------- +// hhProxDoor::SetAASAreaState +//-------------------------------- +void hhProxDoor::SetAASAreaState( bool closed ) { + aas_area_closed = closed; + if( !openForMonsters ) { + aas_area_closed = true; + } + gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_CLUSTERPORTAL | AREACONTENTS_OBSTACLE, aas_area_closed ); +} + +//-------------------------------- +// hhProxDoor::Event_PollForExit +//-------------------------------- +void hhProxDoor::Event_PollForExit() { + if( PollClosestEntity() == -1.f ) { + SetDoorState( PROXSTATE_GoingInactive ); + return; + } + CancelEvents( &EV_PollForExit ); + PostEventMS( &EV_PollForExit, proxDoorRefreshMS ); +} + +//-------------------------------- +// hhProxDoor::PollClosestEntity +//-------------------------------- +float hhProxDoor::PollClosestEntity() { + int num; + int i; + idEntity* ents[MAX_GENTITIES]; + float bestLen; + float distSq; + + if (!doorTrigger) { + return 0.0f; + } + + num = gameLocal.clip.EntitiesTouchingBounds( doorTrigger->GetAbsBounds().Expand( pm_bboxwidth.GetFloat() ), MASK_SHOT_BOUNDINGBOX, ents, MAX_GENTITIES ); + + float thinkDistanceSq = (maxDistance + pm_bboxwidth.GetFloat()) * (maxDistance + pm_bboxwidth.GetFloat()); + bestLen = thinkDistanceSq; + idEntity* bestEnt = NULL; + + for( i = 0; i < num; i++ ) { + if( ents[ i ] && ents[ i ] != this && ents[ i ]->fl.touchTriggers && !ents[ i ]->IsType(hhProxDoorSection::Type) ) { + if( ents[ i ]->IsType(hhPlayer::Type) || ents[ i ]->IsType(hhSpiritProxy::Type) || (openForMonsters && ents[ i ]->IsType(idAI::Type) && ents[ i ]->GetHealth() > 0 ) ) { + if (ents[ i ]->IsType(hhPlayer::Type) && !fl.allowSpiritWalkTouch) { //rww - we don't want spirits to open proxy doors. + hhPlayer *pl = static_cast(ents[i]); + if (pl->IsSpiritWalking()) { + continue; + } + } + distSq = ( ents[ i ]->GetOrigin() - GetOrigin() ).LengthSqr(); + if( distSq < bestLen ) { + bestLen = distSq; + bestEnt = ents[ i ]; + } + } + } + } + if( bestLen < thinkDistanceSq ) { + entDistanceSq = bestLen; + } + else { + entDistanceSq = -1.f; + } + return entDistanceSq; +} + +//-------------------------------- +// hhProxDoor::SetDoorState +//-------------------------------- +void hhProxDoor::SetDoorState( EProxState doorState ) { + int i; + + //HUMANHEAD PCF mdl 04/27/06 - Don't allow locked doors to become active + if ( doorLocked && doorState == PROXSTATE_Active ) { + return; + } + + switch( doorState ) { + case PROXSTATE_Active: + BecomeActive( TH_TICKER ); + CancelEvents( &EV_PollForExit ); + PostEventMS( &EV_PollForExit, 500 ); + OpenPortal(); + break; + + case PROXSTATE_GoingInactive: + break; + + case PROXSTATE_Inactive: + // Guarantee the door is closed + for( i = 0; i < doorPieces.Num(); i++ ) { + if (doorPieces[i].IsValid()) { + doorPieces[ i ]->SetProximity( 0.0 ); + } + } + ClosePortal(); + CancelEvents( &EV_PollForExit ); + BecomeInactive( TH_TICKER ); + break; + } + + proxState = doorState; +} + +//-------------------------------- +// hhProxDoor::IsLocked +//-------------------------------- +bool hhProxDoor::IsLocked() { + return doorLocked; +} + +//-------------------------------- +// hhProxDoor::Lock +//-------------------------------- +void hhProxDoor::Lock( int f ) { + doorLocked = f > 0 ? true : false; + //HUMANHEAD PCF mdl 04/27/06 - Changed PROXSTATE_Inactive to PROXSTATE_GoingInactive + SetDoorState( doorLocked ? PROXSTATE_GoingInactive : PROXSTATE_Active ); + SetShaderParm( SHADERPARM_MODE, GetDoorShaderParm( doorLocked, false ) ); // 2=locked, 1=unlocked, 0=never locked + StopSound( SND_CHANNEL_ANY ); +} + +//-------------------------------- +// hhProxDoor::CrushEntities +//-------------------------------- +void hhProxDoor::CrushEntities() { + int num; + int i; + idEntity* ents[MAX_GENTITIES]; + + num = gameLocal.clip.EntitiesTouchingBounds( GetPhysics()->GetAbsBounds(), MASK_SHOT_BOUNDINGBOX|CONTENTS_RENDERMODEL, ents, MAX_GENTITIES ); + + const char *damageType = spawnArgs.GetString("damageType"); + for( i = 0; i < num; i++ ) { + if( ents[ i ] != this && !ents[ i ]->IsType(hhProxDoorSection::Type) && !ents[ i ]->IsType(hhPlayer::Type) && !ents[ i ]->IsType(idAFAttachment::Type) ) { // Check for the player and idAFAttachment (head) in the case of spirit walk + ents[ i ]->Damage( this, this, vec3_origin, damageType, damage, INVALID_JOINT ); + ents[ i ]->SquishedByDoor( this ); + } + } +} + +//-------------------------------- +// hhProxDoor::Event_Touch +//-------------------------------- +void hhProxDoor::Event_Touch( idEntity *other, trace_t *trace ) { + if ( sndTrigger && trace->c.id == sndTrigger->GetId() ) { + if (other && other->IsType(hhPlayer::Type) && IsLocked() && gameLocal.time > nextSndTriggerTime) { + StartSound("snd_locked", SND_CHANNEL_ANY, 0, false, NULL ); + nextSndTriggerTime = gameLocal.time + 10000; + } + return; + } + if( proxState == PROXSTATE_Active ) { + return; + } + + if ( !other ) { + gameLocal.Warning("hhProxDoor: Event_Touch given NULL for other\n"); + return; + } + + float dist = ( other->GetOrigin() - GetOrigin() ).Length(); + if (dist > movementDistance) { + return; + } + + if( !IsLocked() ) { + SetDoorState( PROXSTATE_Active ); + } +} + +//-------------------------------- +// hhProxDoor::Event_Activate +//-------------------------------- +void hhProxDoor::Event_Activate( idEntity* activator ) { + if( spawnArgs.GetBool("locktrigger") ) { + Lock(!doorLocked); + SetAASAreaState( doorLocked ); + } +} + + +//------------------------------------------------------------------------------------------------- +// hhProxDoorTranslator. +//------------------------------------------------------------------------------------------------- +CLASS_DECLARATION( hhProxDoorSection, hhProxDoorTranslator ) +END_CLASS + +//-------------------------------- +// hhProxDoorTranslator::Spawn +//-------------------------------- +void hhProxDoorTranslator::Spawn( void ) { + idVec3 sectionOffset; + + sectionType = PROXSECTION_Translator; + + if( !spawnArgs.GetVector("section_offset", "0 0 0", sectionOffset) ) { + common->Warning( "No section offset found for '%s'", this->GetEntityDefName() ); + } + targetOrigin = sectionOffset; + baseOrigin = GetOrigin(); + + fl.networkSync = true; +} + +//-------------------------------- +// hhProxDoorTranslator::Save +//-------------------------------- +void hhProxDoorTranslator::Save(idSaveGame *savefile) const { + savefile->WriteVec3( baseOrigin ); + savefile->WriteVec3( targetOrigin ); +} + +//-------------------------------- +// hhProxDoorTranslator::Restore +//-------------------------------- +void hhProxDoorTranslator::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( baseOrigin ); + savefile->ReadVec3( targetOrigin ); +} + +//-------------------------------- +// hhProxDoorTranslator::ClientPredictionThink +//-------------------------------- +void hhProxDoorTranslator::ClientPredictionThink( void ) { + hhProxDoorSection::ClientPredictionThink(); +} + +//-------------------------------- +// hhProxDoorTranslator::WriteToSnapshot +//-------------------------------- +void hhProxDoorTranslator::WriteToSnapshot( idBitMsgDelta &msg ) const { + //hhProxDoorSection::WriteToSnapshot(msg); +#ifdef _SYNC_PROXDOORS + msg.WriteFloat(baseOrigin.x); + msg.WriteFloat(baseOrigin.y); + msg.WriteFloat(baseOrigin.z); + msg.WriteFloat(targetOrigin.x); + msg.WriteFloat(targetOrigin.y); + msg.WriteFloat(targetOrigin.z); + + if (GetPhysics()->IsType(idPhysics_Static::Type)) { + idPhysics_Static *phys = static_cast(GetPhysics()); + staticPState_t *state = phys->GetPState(); + idCQuat q = state->axis.ToCQuat(); + msg.WriteFloat(q.x); + msg.WriteFloat(q.y); + msg.WriteFloat(q.z); + } + + msg.WriteFloat(proximity); +#else + WriteBindToSnapshot(msg); + msg.WriteBits(proxyParent.GetSpawnId(), 32); +#endif +} + +//-------------------------------- +// hhProxDoorTranslator::ReadFromSnapshot +//-------------------------------- +void hhProxDoorTranslator::ReadFromSnapshot( const idBitMsgDelta &msg ) { + //hhProxDoorSection::ReadFromSnapshot(msg); +#ifdef _SYNC_PROXDOORS + baseOrigin.x = msg.ReadFloat(); + baseOrigin.y = msg.ReadFloat(); + baseOrigin.z = msg.ReadFloat(); + targetOrigin.x = msg.ReadFloat(); + targetOrigin.y = msg.ReadFloat(); + targetOrigin.z = msg.ReadFloat(); + + if (GetPhysics()->IsType(idPhysics_Static::Type)) { + idPhysics_Static *phys = static_cast(GetPhysics()); + staticPState_t *state = phys->GetPState(); + idCQuat q; + q.x = msg.ReadFloat(); + q.y = msg.ReadFloat(); + q.z = msg.ReadFloat(); + state->axis = q.ToMat3(); + } + + float prox = msg.ReadFloat(); + SetProximity(prox); +#else + ReadBindFromSnapshot(msg); + proxyParent.SetSpawnId(msg.ReadBits(32)); + if (proxyParent.IsValid() && proxyParent->IsType(hhProxDoor::Type)) { + hhProxDoor *parentPtr = static_cast(proxyParent.GetEntity()); + + if (parentPtr->hasNetData) { + if (!hasNetData) { + idVec3 parentOrigin; + idMat3 parentAxis; + + parentOrigin = proxyParent->GetOrigin();//proxyParent->spawnArgs.GetVector("origin", "0 0 0", parentOrigin); + parentAxis = proxyParent->GetAxis();//proxyParent->spawnArgs.GetMatrix("rotation", "1 0 0 0 1 0 0 0 1", parentAxis); + + SetOrigin(parentOrigin); + SetAxis(parentAxis); + + baseOrigin = GetOrigin(); + + spawnArgs.SetVector("origin", parentOrigin); + spawnArgs.SetMatrix("rotation", parentAxis); + + hasNetData = true; + } + } + else { + hasNetData = false; + } + } +#endif +} + +//-------------------------------- +// hhProxDoorTranslator::SetProximity +//-------------------------------- +void hhProxDoorTranslator::SetProximity( float prox ) { + SetOrigin( baseOrigin + (targetOrigin * prox) ); + proximity = prox; +} + + +//------------------------------------------------------------------------------------------------- +// hhProxDoorRotator. +//------------------------------------------------------------------------------------------------- +CLASS_DECLARATION( hhProxDoorSection, hhProxDoorRotator ) + EVENT( EV_PostSpawn, hhProxDoorRotator::Event_PostSpawn ) +END_CLASS + +//-------------------------------- +// hhProxDoorRotator::Spawn +//-------------------------------- +void hhProxDoorRotator::Spawn( void ) { + sectionType = PROXSECTION_Rotator; + + if (gameLocal.isMultiplayer) { + if (!gameLocal.isClient) { + PostEventMS( &EV_PostSpawn, 0 ); + fl.networkSync = true; + } + } + else { //just call it now. some sp maps might target a door piece in the spawn function of an entity or something crazy like that. + Event_PostSpawn(); + } +} + +void hhProxDoorRotator::Event_PostSpawn( void ) { + idVec3 sectionOffset; + idVec3 rotVector; + float rotAngle; + + if( !spawnArgs.GetVector("section_offset", "0 0 0", sectionOffset) ) { + common->Warning( "No section offset found for '%s'", this->GetEntityDefName() ); + } + if( !spawnArgs.GetVector("rot_vector", "0 0 0", rotVector) ) { + common->Warning( "No rotation vector found for '%s'", this->GetEntityDefName() ); + } + if( !spawnArgs.GetFloat("rot_angle", "0 0 0", rotAngle) ) { + common->Warning( "No rotation angle found for '%s'", this->GetEntityDefName() ); + } + + idDict args; + args.SetVector( "origin", GetOrigin() + (sectionOffset * GetAxis()) ); + args.SetMatrix( "rotation", GetAxis() ); + args.SetVector( "rot_vector", rotVector ); + args.SetFloat( "rot_angle", rotAngle ); + bindParent = static_cast( gameLocal.SpawnEntityType(hhProxDoorRotMaster::Type, &args) ); + if( !bindParent.IsValid() ) { + common->Warning( "Failed to spawn bindParent for '%s'", GetEntityDefName() ); + } + else { + bindParent->proxyParent = this; + } + Bind( bindParent.GetEntity(), true ); +} + +//-------------------------------- +// hhProxDoorRotator::Save +//-------------------------------- +void hhProxDoorRotator::Save(idSaveGame *savefile) const { + bindParent.Save(savefile); +} + +//-------------------------------- +// hhProxDoorRotator::Restore +//-------------------------------- +void hhProxDoorRotator::Restore( idRestoreGame *savefile ) { + bindParent.Restore(savefile); +} + +//-------------------------------- +// hhProxDoorRotator::ClientPredictionThink +//-------------------------------- +void hhProxDoorRotator::ClientPredictionThink( void ) { + hhProxDoorSection::ClientPredictionThink(); +} + +//-------------------------------- +// hhProxDoorRotator::WriteToSnapshot +//-------------------------------- +void hhProxDoorRotator::WriteToSnapshot( idBitMsgDelta &msg ) const { +// hhProxDoorSection::WriteToSnapshot(msg); +#ifdef _SYNC_PROXDOORS + WriteBindToSnapshot(msg); + if (GetPhysics()->IsType(idPhysics_Static::Type)) { + idPhysics_Static *phys = static_cast(GetPhysics()); + staticPState_t *state = phys->GetPState(); + + msg.WriteFloat(state->origin.x); + msg.WriteFloat(state->origin.y); + msg.WriteFloat(state->origin.z); + msg.WriteFloat(state->localOrigin.x); + msg.WriteFloat(state->localOrigin.y); + msg.WriteFloat(state->localOrigin.z); + idCQuat q = state->localAxis.ToCQuat(); + msg.WriteFloat(q.x); + msg.WriteFloat(q.y); + msg.WriteFloat(q.z); + } +#else + msg.WriteBits(proxyParent.GetSpawnId(), 32); + WriteBindToSnapshot(msg); + msg.WriteBits(bindParent.GetSpawnId(), 32); +#endif + //not needed without prediction + //msg.WriteBits(bindParent.GetSpawnId(), 32); +} + +//-------------------------------- +// hhProxDoorRotator::ReadFromSnapshot +//-------------------------------- +void hhProxDoorRotator::ReadFromSnapshot( const idBitMsgDelta &msg ) { +// hhProxDoorSection::ReadFromSnapshot(msg); +#ifdef _SYNC_PROXDOORS + ReadBindFromSnapshot(msg); + if (GetPhysics()->IsType(idPhysics_Static::Type)) { + idPhysics_Static *phys = static_cast(GetPhysics()); + staticPState_t *state = phys->GetPState(); + + state->origin.x = msg.ReadFloat(); + state->origin.y = msg.ReadFloat(); + state->origin.z = msg.ReadFloat(); + state->localOrigin.x = msg.ReadFloat(); + state->localOrigin.y = msg.ReadFloat(); + state->localOrigin.z = msg.ReadFloat(); + idCQuat q; + q.x = msg.ReadFloat(); + q.y = msg.ReadFloat(); + q.z = msg.ReadFloat(); + state->localAxis = q.ToMat3(); + } +#else + proxyParent.SetSpawnId(msg.ReadBits(32)); + if (proxyParent.IsValid() && proxyParent->IsType(hhProxDoor::Type)) { + hhProxDoor *parentPtr = static_cast(proxyParent.GetEntity()); + + if (parentPtr->hasNetData) { + if (!hasNetData) { + idVec3 parentOrigin; + idMat3 parentAxis; + + parentOrigin = proxyParent->GetOrigin();//proxyParent->spawnArgs.GetVector("origin", "0 0 0", parentOrigin); + parentAxis = proxyParent->GetAxis();//proxyParent->spawnArgs.GetMatrix("rotation", "1 0 0 0 1 0 0 0 1", parentAxis); + + SetOrigin(parentOrigin); + SetAxis(parentAxis); + + spawnArgs.SetVector("origin", parentOrigin); + spawnArgs.SetMatrix("rotation", parentAxis); + + hasNetData = true; + } + } + else { + hasNetData = false; + } + } + ReadBindFromSnapshot(msg); + bindParent.SetSpawnId(msg.ReadBits(32)); +#endif + //not needed without prediction + /* + int spawnId = msg.ReadBits(32); + if (!spawnId) { + bindParent = NULL; + } + else { + bindParent.SetSpawnId(spawnId); + } + */ +} + +//-------------------------------- +// hhProxDoorRotator::SetProximity +//-------------------------------- +void hhProxDoorRotator::SetProximity( float prox ) { + if( bindParent.IsValid() ) { + bindParent->SetProximity( prox ); + } + proximity = prox; +} + +//------------------------------------------------------------------------------------------------- +// hhProxDoorRotMaster. +//------------------------------------------------------------------------------------------------- +CLASS_DECLARATION( hhProxDoorSection, hhProxDoorRotMaster ) +END_CLASS + +//-------------------------------- +// hhProxDoorRotMaster::Spawn +//-------------------------------- +void hhProxDoorRotMaster::Spawn( void ) { + idVec3 rot_vector; + spawnArgs.GetVector("rot_vector", "0 0 1", rot_vector); + baseAxis = GetAxis(); + rotVector = GetAxis() * rot_vector; + + spawnArgs.GetFloat("rot_angle", "0.0", rotAngle); + + fl.networkSync = true; +} + +//-------------------------------- +// hhProxDoorRotMaster::Save +//-------------------------------- +void hhProxDoorRotMaster::Save(idSaveGame *savefile) const { + savefile->WriteVec3( rotVector ); + savefile->WriteFloat( rotAngle ); + savefile->WriteMat3( baseAxis ); +} + +//-------------------------------- +// hhProxDoorRotMaster::Restore +//-------------------------------- +void hhProxDoorRotMaster::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( rotVector ); + savefile->ReadFloat( rotAngle ); + savefile->ReadMat3( baseAxis ); +} + +//-------------------------------- +// hhProxDoorRotMaster::ClientPredictionThink +//-------------------------------- +void hhProxDoorRotMaster::ClientPredictionThink( void ) { + hhProxDoorSection::ClientPredictionThink(); +} + +//-------------------------------- +// hhProxDoorRotMaster::WriteToSnapshot +//-------------------------------- +void hhProxDoorRotMaster::WriteToSnapshot( idBitMsgDelta &msg ) const { + //hhProxDoorSection::WriteToSnapshot(msg); +#ifdef _SYNC_PROXDOORS + msg.WriteFloat(rotVector.x); + msg.WriteFloat(rotVector.y); + msg.WriteFloat(rotVector.z); + + msg.WriteFloat(rotAngle); + + idCQuat q = baseAxis.ToCQuat(); + msg.WriteFloat(q.x); + msg.WriteFloat(q.y); + msg.WriteFloat(q.z); + + if (GetPhysics()->IsType(idPhysics_Static::Type)) { + idPhysics_Static *phys = static_cast(GetPhysics()); + staticPState_t *state = phys->GetPState(); + msg.WriteFloat(state->origin.x); + msg.WriteFloat(state->origin.y); + msg.WriteFloat(state->origin.z); + } + msg.WriteFloat(proximity); +#else + WriteBindToSnapshot(msg); + msg.WriteBits(proxyParent.GetSpawnId(), 32); +#endif +} + +//-------------------------------- +// hhProxDoorRotMaster::ReadFromSnapshot +//-------------------------------- +void hhProxDoorRotMaster::ReadFromSnapshot( const idBitMsgDelta &msg ) { + //hhProxDoorSection::ReadFromSnapshot(msg); +#ifdef _SYNC_PROXDOORS + rotVector.x = msg.ReadFloat(); + rotVector.y = msg.ReadFloat(); + rotVector.z = msg.ReadFloat(); + + rotAngle = msg.ReadFloat(); + + idCQuat q; + q.x = msg.ReadFloat(); + q.y = msg.ReadFloat(); + q.z = msg.ReadFloat(); + baseAxis = q.ToMat3(); + + if (GetPhysics()->IsType(idPhysics_Static::Type)) { + idPhysics_Static *phys = static_cast(GetPhysics()); + staticPState_t *state = phys->GetPState(); + state->origin.x = msg.ReadFloat(); + state->origin.y = msg.ReadFloat(); + state->origin.z = msg.ReadFloat(); + } + float prox = msg.ReadFloat(); + SetProximity(prox); +#else + ReadBindFromSnapshot(msg); + proxyParent.SetSpawnId(msg.ReadBits(32)); + if (proxyParent.IsValid() && proxyParent->IsType(hhProxDoorRotator::Type)) { + hhProxDoorRotator *parentPtr = static_cast(proxyParent.GetEntity()); + if (parentPtr->hasNetData) { + if (!hasNetData) { + idVec3 sectionOffset, rot_vector; + idVec3 parentOrigin; + idMat3 parentAxis; + + proxyParent->spawnArgs.GetVector("origin", "0 0 0", parentOrigin); + proxyParent->spawnArgs.GetMatrix("rotation", "1 0 0 0 1 0 0 0 1", parentAxis); + + proxyParent->spawnArgs.GetVector("section_offset", "0 0 0", sectionOffset); + proxyParent->spawnArgs.GetVector("rot_vector", "0 0 0", rot_vector); + proxyParent->spawnArgs.GetFloat("rot_angle", "0.0", rotAngle); + SetOrigin(parentOrigin+(sectionOffset*parentAxis)); + SetAxis(parentAxis); + + baseAxis = GetAxis(); + rotVector = GetAxis() * rot_vector; + + hasNetData = true; + } + } + else { + parentPtr->hasNetData = false; + } + } +#endif +} + +//-------------------------------- +// hhProxDoorRotMaster::SetProximity +//-------------------------------- +void hhProxDoorRotMaster::SetProximity( float prox ) { + idRotation desRotation( vec3_origin, rotVector, prox*rotAngle ); + SetAxis( baseAxis * desRotation.ToMat3() ); + + //rww - proxy door pieces keep getting stuck, so make sure this bastard keeps running physics + if (!IsActive(TH_PHYSICS)) { + BecomeActive(TH_PHYSICS); + } + proximity = prox; +} diff --git a/src/Prey/game_proxdoor.h b/src/Prey/game_proxdoor.h new file mode 100644 index 0000000..dda01ad --- /dev/null +++ b/src/Prey/game_proxdoor.h @@ -0,0 +1,204 @@ +#ifndef __PREY_PROXDOOR_H__ +#define __PREY_PROXDOOR_H__ + +class hhProxDoorSection; +typedef idEntityPtr doorSectionPtr_t; //rww - so that the list of door sections can be safe. + +/*********************************************************************** + hhProxDoor. + The main door control. Has a model associated with it (the frame). +***********************************************************************/ +class hhProxDoor : public idEntity { + CLASS_PROTOTYPE( hhProxDoor ); + + public: + hhProxDoor(); + virtual ~hhProxDoor(); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void SpawnSoundTrigger(); + + //rww - netcode + virtual void ClientPredictionThink( void ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + virtual void Ticker( void ); + + //Events. + void Event_Touch( idEntity *other, trace_t *trace ); + void Event_PollForExit( void ); + virtual void Event_PostSpawn( void ); + void Event_Activate( idEntity* activator ); + + //Portal Control. + void OpenPortal( void ); + void ClosePortal( void ); + + //AAS Control. + void SetAASAreaState( bool closed ); + + //Lock Control + bool IsLocked( void ); + void Lock( int f ); + + bool hasNetData; //rww - net stuff + +protected: + enum EProxState { + PROXSTATE_Active, + PROXSTATE_Inactive, + PROXSTATE_GoingInactive, + }; + + enum EPDoorSound { + PDOORSND_Opening, + PDOORSND_Closing, + PDOORSND_Closed, + PDOORSND_Opened, + PDOORSND_FullyOpened, + PDOORSND_Stopped, + }; + + void SetDoorState( EProxState doorState ); + void UpdateSoundState( EPDoorSound newState ); + float PollClosestEntity(); + void CrushEntities(); + +protected: + EProxState proxState; //our state + EPDoorSound doorSndState; //state of our door sound + float lastAmount; //last amount [mainly used for sound] + idList doorPieces; //all of the pieces that make up the door + idClipModel* doorTrigger; //trigger that wakes us up + idClipModel* sndTrigger; //trigger for playing locked sound + int nextSndTriggerTime; // next time to play door locked sound + float entDistanceSq; //squared distance of the closest entity + float maxDistance; //distance of trigger + float movementDistance; //distance to start movement at + float lastDistance; //previous distance + float stopDistance; //distance to stop movement at + float damage; // Amount of crush damage we do + qhandle_t areaPortal; // 0 = no portal, > 0 = our portal we are by + bool doorLocked; + bool openForMonsters; + bool aas_area_closed; + +}; + +/*********************************************************************** + hhProxDoorSection. + Base class for all of the pieces used to make up a hhProxDoor. +***********************************************************************/ +class hhProxDoorSection : public idEntity { + ABSTRACT_PROTOTYPE( hhProxDoorSection ); + +public: + void Spawn( void ); + + void Save( idSaveGame *savefile ) const { savefile->WriteInt(sectionType); savefile->WriteFloat( proximity ); } + void Restore( idRestoreGame *savefile ) { savefile->ReadInt((int&)sectionType); savefile->ReadFloat( proximity ); } + + //rww - netcode + virtual void ClientPredictionThink( void ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + virtual void SetProximity( float prox ) = 0; + + idEntityPtr proxyParent; //rww - for horrible network hacks + + bool hasNetData; //rww - net stuff + + float proximity; +protected: + enum EProxSection { + PROXSECTION_None, + PROXSECTION_Rotator, + PROXSECTION_Translator, + PROXSECTION_RotatorMaster, + }; + + EProxSection sectionType; +}; + +/*********************************************************************** + hhProxDoorRotator. + A rotating door piece. Uses an offset DoorRotMaster to bind to. +***********************************************************************/ +class hhProxDoorRotator : public hhProxDoorSection { + CLASS_PROTOTYPE( hhProxDoorRotator ); + +public: + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - netcode + virtual void ClientPredictionThink( void ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + virtual void SetProximity( float prox ); + + virtual void Event_PostSpawn( void ); + +protected: + idEntityPtr bindParent; +}; + +/*********************************************************************** + hhProxDoorTranslator. + A translating door piece. +***********************************************************************/ +class hhProxDoorTranslator : public hhProxDoorSection { + CLASS_PROTOTYPE( hhProxDoorTranslator ); + +public: + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - netcode + virtual void ClientPredictionThink( void ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + virtual void SetProximity( float prox ); + +protected: + idVec3 baseOrigin; + idVec3 targetOrigin; + + +}; + +/*********************************************************************** + hhProxDoorRotMaster + The master that DoorRotators bind to. +***********************************************************************/ +class hhProxDoorRotMaster : public hhProxDoorSection { + CLASS_PROTOTYPE( hhProxDoorRotMaster ); + +public: + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - netcode + virtual void ClientPredictionThink( void ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + virtual void SetProximity( float prox ); + +protected: + idVec3 rotVector; + float rotAngle; + idMat3 baseAxis; +}; + + +#endif //__PREY_PROXDOOR_H__ \ No newline at end of file diff --git a/src/Prey/game_rail.cpp b/src/Prey/game_rail.cpp new file mode 100644 index 0000000..3d30b9f --- /dev/null +++ b/src/Prey/game_rail.cpp @@ -0,0 +1,85 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/* TODO: + could optionally allow multiple bindcontroller bones, for coop +*/ + +//========================================================================== +// +// hhRailRide +// +//========================================================================== + +CLASS_DECLARATION(hhAnimated, hhRailRide) + EVENT( EV_Activate, hhRailRide::Event_Activate ) + EVENT( EV_BindAttach, hhRailRide::Event_Attach ) + EVENT( EV_BindDetach, hhRailRide::Event_Detach ) +END_CLASS + +void hhRailRide::Spawn() { + fl.takedamage = spawnArgs.GetBool("dropOnPain"); + float tension = spawnArgs.GetFloat("tension", "0.01"); + float yawLimit = spawnArgs.GetFloat("yawlimit", "180"); + float pitchLimit = spawnArgs.GetFloat("pitchlimit", "0.0"); + const char *handName = spawnArgs.GetString("def_hand"); + const char *animName = spawnArgs.GetString("boundanim"); + + // spawn a bind controller at railbone + const char *bonename = spawnArgs.GetString("railbone"); + bindController = static_cast( gameLocal.SpawnObject(spawnArgs.GetString("def_bindController")) ); + assert(bindController); + bindController->MoveToJoint(this, bonename); + bindController->BindToJoint(this, bonename, true); + bindController->SetTension(tension); + bindController->SetRiderParameters(animName, handName, yawLimit, pitchLimit); +} + +void hhRailRide::Save(idSaveGame *savefile) const { + savefile->WriteObject( bindController ); +} + +void hhRailRide::Restore( idRestoreGame *savefile ) { + savefile->ReadObject( reinterpret_cast( bindController ) ); +} + +bool hhRailRide::Pain(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location) { + bindController->Detach(); + + return( true ); +} + +void hhRailRide::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + bindController->Detach(); +} + +bool hhRailRide::ValidRider(idEntity *entity) { + return (entity && entity->spawnArgs.GetBool("RailAttachable")); +} + +void hhRailRide::Attach(idEntity *rider, bool loose) { + if (ValidRider(rider)) { + bindController->Attach(rider, loose); + } +} + +void hhRailRide::Detach() { + bindController->Detach(); +} + +void hhRailRide::Event_Activate( idEntity *activator ) { + Attach(activator, false); + hhAnimated::Event_Activate(activator); // allow animation to start +} + +void hhRailRide::Event_Attach(idEntity *rider, bool loose) { + Attach(rider, loose); +} + +void hhRailRide::Event_Detach() { + Detach(); +} + + diff --git a/src/Prey/game_rail.h b/src/Prey/game_rail.h new file mode 100644 index 0000000..4577627 --- /dev/null +++ b/src/Prey/game_rail.h @@ -0,0 +1,41 @@ + +#ifndef __HH_GAME_RAIL_H__ +#define __HH_GAME_RAIL_H__ + +class hhBindController; + +/* +=================================================================================== + + hhRailRide + + An animating object that controls player position and orientation. + Player can look around within some restricted cone of vision. + +=================================================================================== +*/ + +class hhRailRide : public hhAnimated { + +public: + CLASS_PROTOTYPE( hhRailRide ); + + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + 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 ); + void Attach(idEntity *rider, bool loose); + void Detach(); + bool ValidRider(idEntity *entity); + +protected: + void Event_Activate( idEntity *activator ); + void Event_Attach(idEntity *rider, bool loose); + void Event_Detach(); + + hhBindController * bindController; +}; + + +#endif \ No newline at end of file diff --git a/src/Prey/game_railshuttle.cpp b/src/Prey/game_railshuttle.cpp new file mode 100644 index 0000000..81ff2e8 --- /dev/null +++ b/src/Prey/game_railshuttle.cpp @@ -0,0 +1,620 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +//========================================================================== +// +// hhRailShuttle +// +//========================================================================== + +#define VCOCKPIT_PARM_POWER 4 +#define VCOCKPIT_PARM_FIRE 5 +#define VCOCKPIT_PARM_JUMP 6 +#define VCOCKPIT_PARM_MOVE 7 + +const idEventDef EV_SetOrbitRadius("setOrbitRadius", "fd"); +const idEventDef EV_RailShuttle_Jump("railShuttleJump"); +const idEventDef EV_RailShuttle_FinalJump("railShuttleFinalJump"); +const idEventDef EV_RailShuttle_IsJumping("railShuttleIsJumping", NULL, 'f'); +const idEventDef EV_RailShuttle_Disengage("railShuttleDisengage"); +const idEventDef EV_HideArcs(""); +const idEventDef EV_RestorePower(""); +const idEventDef EV_GetCockpit( "getCockpit", "", 'e' ); //rdr + +CLASS_DECLARATION(hhVehicle, hhRailShuttle) + EVENT( EV_SetOrbitRadius, hhRailShuttle::Event_SetOrbitRadius ) + EVENT( EV_PostSpawn, hhRailShuttle::Event_PostSpawn ) + EVENT( EV_RailShuttle_Jump, hhRailShuttle::Event_Jump ) + EVENT( EV_RailShuttle_FinalJump, hhRailShuttle::Event_FinalJump ) + EVENT( EV_RailShuttle_IsJumping, hhRailShuttle::Event_IsJumping ) + EVENT( EV_RailShuttle_Disengage, hhRailShuttle::Event_Disengage ) + EVENT( EV_HideArcs, hhRailShuttle::Event_HideArcs ) + EVENT( EV_RestorePower, hhRailShuttle::Event_RestorePower ) + EVENT( EV_GetCockpit, hhRailShuttle::Event_GetCockpit ) +END_CLASS + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + +void hhRailShuttle::Spawn() { + fl.networkSync = true; + + turretPitchLimit = spawnArgs.GetFloat("turretPitchLimit"); + turretYawLimit = spawnArgs.GetFloat("turretYawLimit"); + + defaultRadius = spawnArgs.GetFloat("sphereRadius", "1000"); + sphereRadius.Init(gameLocal.time, 0, defaultRadius, defaultRadius); + + sphereCenter = spawnArgs.GetVector("sphereCenter"); + sphereAngles = ang_zero; + sphereVelocity = ang_zero; + spherePitchMin = spawnArgs.GetFloat("spherePitchMin"); + spherePitchMax = spawnArgs.GetFloat("spherePitchMax"); + + SetPhysics( NULL ); // No collision, no expensive shuttle physics + GetPhysics()->SetContents(0); + + linearFriction = spawnArgs.GetFloat("friction_linear"); + terminalVelocity = spawnArgs.GetFloat("terminalVelocity"); + + bJumping = false; + bDisengaged = false; + jumpStartTime = 0; + jumpInitSpeed = spawnArgs.GetFloat("jumpInitSpeed"); + jumpAccel = spawnArgs.GetFloat("jumpAccel"); + jumpAccelTime = spawnArgs.GetFloat("jumpAccelTime"); + jumpReturnForce = spawnArgs.GetFloat("jumpReturnForce"); + jumpBounceCount = spawnArgs.GetInt("jumpBounceCount"); + jumpBounceFactor = spawnArgs.GetFloat("jumpBounceFactor"); + jumpThrustMovementScale = spawnArgs.GetFloat("jumpThrustMovementScale"); + jumpBounceMovementScale = spawnArgs.GetFloat("jumpBounceMovementScale"); + bWallMovementSoundPlaying = false; + bAirMovementSoundPlaying = false; + bBounced = false; + + localViewAngles = ang_zero; + turret = NULL; + canopy = NULL; + leftArc = NULL; + rightArc = NULL; + + //HUMANHEAD PCF mdl 04/29/06 - Added bUpdateViewAngles to prevent view angles from changing on load + bUpdateViewAngles = false; + + PostEventMS(&EV_PostSpawn, 0); +} + +void hhRailShuttle::Event_PostSpawn() { + // Spawn the turret + turret = gameLocal.SpawnObject(spawnArgs.GetString("def_turret")); + if (turret.IsValid()) { + turret->SetOrigin( GetOrigin() + spawnArgs.GetVector("offset_turret") * GetAxis()); + turret->SetAxis(GetAxis()); + turret->Bind(this, false); + turret->GetPhysics()->SetContents(0); + turret->Hide(); + } + // Spawn the canopy + canopy = gameLocal.SpawnObject(spawnArgs.GetString("def_turret")); + if (canopy.IsValid()) { + canopy->SetOrigin( GetOrigin() + spawnArgs.GetVector("offset_turret") * GetAxis()); + canopy->SetAxis(GetAxis()); + canopy->Bind(this, true); + canopy->GetPhysics()->SetContents(0); //temp: need to make it turn solid after player enters + canopy->Hide(); + } + + // Spawn the arc beams + idVec3 leftPos1 = GetOrigin() + spawnArgs.GetVector("offset_leftArcStart") * GetAxis(); + idVec3 leftPos2 = GetOrigin() + spawnArgs.GetVector("offset_leftArcEnd") * GetAxis(); + idVec3 leftDir = leftPos2 - leftPos1; + leftDir.Normalize(); + leftArc = hhBeamSystem::SpawnBeam(leftPos1, spawnArgs.GetString("beam_arc"), mat3_identity, true); + HH_ASSERT( leftArc.IsValid() ); + leftArc->SetOrigin(leftPos1); + leftArc->SetAxis(leftDir.ToMat3()); + leftArc->Bind(turret.GetEntity(), true); + leftArc->Activate( false ); + leftArc->fl.neverDormant = true; + leftArc->ToggleBeamLength(true); + leftArc->SetTargetLocation( leftPos2 ); + + idVec3 rightPos1 = GetOrigin() + spawnArgs.GetVector("offset_rightArcStart") * GetAxis(); + idVec3 rightPos2 = GetOrigin() + spawnArgs.GetVector("offset_rightArcEnd") * GetAxis(); + idVec3 rightDir = rightPos2 - rightPos1; + rightDir.Normalize(); + rightArc = hhBeamSystem::SpawnBeam(rightPos1, spawnArgs.GetString("beam_arc"), mat3_identity, true); + HH_ASSERT( rightArc.IsValid() ); + rightArc->SetOrigin(rightPos1); + rightArc->SetAxis(rightDir.ToMat3()); + rightArc->Bind(turret.GetEntity(), true); + rightArc->Activate( false ); + rightArc->fl.neverDormant = true; + rightArc->ToggleBeamLength(true); + rightArc->SetTargetLocation( rightPos2 ); +} + +void hhRailShuttle::Save(idSaveGame *savefile) const { + savefile->WriteFloat( defaultRadius ); + + savefile->WriteFloat( sphereRadius.GetStartTime() ); // idInterpolate + savefile->WriteFloat( sphereRadius.GetDuration() ); + savefile->WriteFloat( sphereRadius.GetStartValue() ); + savefile->WriteFloat( sphereRadius.GetEndValue() ); + + savefile->WriteAngles( sphereAngles ); + savefile->WriteAngles( sphereVelocity ); + savefile->WriteAngles( sphereAcceleration ); + savefile->WriteVec3( sphereCenter ); + savefile->WriteFloat( spherePitchMin ); + savefile->WriteFloat( spherePitchMax ); + savefile->WriteFloat( turretYawLimit ); + savefile->WriteFloat( turretPitchLimit ); + savefile->WriteFloat( linearFriction ); + savefile->WriteFloat( terminalVelocity ); + savefile->WriteAngles( localViewAngles ); + savefile->WriteAngles( oldViewAngles ); + + turret.Save( savefile ); + canopy.Save( savefile ); + leftArc.Save( savefile ); + rightArc.Save( savefile ); + + savefile->WriteBool( bDisengaged ); + savefile->WriteBool( bJumping ); + savefile->WriteBool( bBounced ); + savefile->WriteInt( jumpStartTime ); + savefile->WriteInt( jumpStage ); + savefile->WriteFloat( jumpSpeed ); + savefile->WriteFloat( jumpPosition ); + savefile->WriteInt( jumpNumBounces ); + savefile->WriteFloat( jumpInitSpeed ); + savefile->WriteFloat( jumpAccel ); + savefile->WriteFloat( jumpAccelTime ); + savefile->WriteFloat( jumpReturnForce ); + savefile->WriteInt( jumpBounceCount ); + savefile->WriteFloat( jumpBounceFactor ); + savefile->WriteFloat( jumpThrustMovementScale ); + savefile->WriteFloat( jumpBounceMovementScale ); + savefile->WriteBool( bWallMovementSoundPlaying ); + savefile->WriteBool( bAirMovementSoundPlaying ); +} + +void hhRailShuttle::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( defaultRadius ); + + float set; + savefile->ReadFloat( set ); // idInterpolate + sphereRadius.SetStartTime( set ); + savefile->ReadFloat( set ); + sphereRadius.SetDuration( set ); + savefile->ReadFloat( set ); + sphereRadius.SetStartValue( set ); + savefile->ReadFloat( set ); + sphereRadius.SetEndValue( set ); + + savefile->ReadAngles( sphereAngles ); + savefile->ReadAngles( sphereVelocity ); + savefile->ReadAngles( sphereAcceleration ); + savefile->ReadVec3( sphereCenter ); + savefile->ReadFloat( spherePitchMin ); + savefile->ReadFloat( spherePitchMax ); + savefile->ReadFloat( turretYawLimit ); + savefile->ReadFloat( turretPitchLimit ); + savefile->ReadFloat( linearFriction ); + savefile->ReadFloat( terminalVelocity ); + savefile->ReadAngles( localViewAngles ); + savefile->ReadAngles( oldViewAngles ); + + turret.Restore( savefile ); + canopy.Restore( savefile ); + leftArc.Restore( savefile ); + rightArc.Restore( savefile ); + + savefile->ReadBool( bDisengaged ); + savefile->ReadBool( bJumping ); + savefile->ReadBool( bBounced ); + savefile->ReadInt( jumpStartTime ); + savefile->ReadInt( jumpStage ); + savefile->ReadFloat( jumpSpeed ); + savefile->ReadFloat( jumpPosition ); + savefile->ReadInt( jumpNumBounces ); + savefile->ReadFloat( jumpInitSpeed ); + savefile->ReadFloat( jumpAccel ); + savefile->ReadFloat( jumpAccelTime ); + savefile->ReadFloat( jumpReturnForce ); + savefile->ReadInt( jumpBounceCount ); + savefile->ReadFloat( jumpBounceFactor ); + savefile->ReadFloat( jumpThrustMovementScale ); + savefile->ReadFloat( jumpBounceMovementScale ); + savefile->ReadBool( bWallMovementSoundPlaying ); + savefile->ReadBool( bAirMovementSoundPlaying ); + + SetPhysics( NULL ); // No collision, no expensive shuttle physics + GetPhysics()->SetContents(0); + + //HUMANHEAD PCF mdl 04/29/06 - Added bUpdateViewAngles to prevent view angles from changing on load + bUpdateViewAngles = true; +} + +void hhRailShuttle::WriteToSnapshot( idBitMsgDelta &msg ) const { + hhVehicle::WriteToSnapshot(msg); +} + +void hhRailShuttle::ReadFromSnapshot( const idBitMsgDelta &msg ) { + hhVehicle::ReadFromSnapshot(msg); +} + +void hhRailShuttle::ClientPredictionThink( void ) { + Think(); + + UpdateVisuals(); + Present(); +} + +void hhRailShuttle::BecomeConsole() { + if (turret.IsValid()) { + turret->Hide(); + } + if (canopy.IsValid()) { + canopy->Hide(); + } + if (leftArc.IsValid()) { + leftArc->Activate( false ); + } + if (rightArc.IsValid()) { + rightArc->Activate( false ); + } + hhVehicle::BecomeConsole(); +} + +void hhRailShuttle::BecomeVehicle() { + if (turret.IsValid()) { + turret->Show(); + } + if (canopy.IsValid()) { + canopy->Show(); + } + + hhVehicle::BecomeVehicle(); +} + +// Static function to determine if a pilot is suitable +bool hhRailShuttle::ValidPilot( idActor *act ) { + if (act && act->health > 0) { + if (act->IsType(hhPlayer::Type)) { + hhPlayer *player = static_cast(act); + if( !player->IsSpiritOrDeathwalking() && !player->IsPossessed() ) { + return true; + } + } + else if (act->IsType(idAI::Type)) { + idAI *ai = static_cast(act); + if (ai->spawnArgs.GetBool("canPilotShuttle")) { + return true; + } + } + } + return false; +} + +bool hhRailShuttle::WillAcceptPilot( idActor *act ) { + return IsConsole() && hhRailShuttle::ValidPilot( act ); +} + +void hhRailShuttle::AcceptPilot( hhPilotVehicleInterface* pilotInterface ) { + hhVehicle::AcceptPilot(pilotInterface); + + // supress model in player views, but allow it in mirrors and remote views + if (turret.IsValid() && GetPilot()->IsType(hhPlayer::Type)) { + turret->GetRenderEntity()->suppressSurfaceInViewID = GetPilot()->entityNumber + 1; + } + + oldViewAngles = GetPilot()->viewAxis.ToAngles(); +} + +idVec3 hhRailShuttle::GetFireOrigin() { + return turret->GetOrigin(); +} + +idMat3 hhRailShuttle::GetFireAxis() { + return turret->GetAxis(); +} + +void hhRailShuttle::ProcessPilotInput( const usercmd_t* cmds, const idAngles* viewAngles ) { + float movementScale; + + if ( cmds ) { + ProcessButtons( *cmds ); + + //Check vehicle here because we could have exited the vehicle in ProcessButtons + if( !IsVehicle() ) { + return; + } + + ProcessImpulses( *cmds ); + + // Apply booster to allow key taps to be very low thrust + thrustScale = idMath::ClampFloat( thrustMin, thrustMax, thrustScale * thrustAccel ); + + // Apply our forces as orbit velocity + if ((cmds->rightmove || cmds->upmove || cmds->forwardmove) && HasPower(1) && !bDisengaged) { + sphereAcceleration.yaw = cmds->rightmove * thrustFactor * thrustScale * (60.0f * USERCMD_ONE_OVER_HZ); + sphereAcceleration.pitch = (cmds->upmove ? cmds->upmove : cmds->forwardmove) * thrustFactor * thrustScale * (60.0f * USERCMD_ONE_OVER_HZ); + sphereAcceleration.roll = 0; + sphereAcceleration += -sphereVelocity * linearFriction * (60.0f * USERCMD_ONE_OVER_HZ); + SetCockpitParm( VCOCKPIT_PARM_MOVE, -MS2SEC( gameLocal.time ) ); + if (bJumping) { + PlayAirMovementSound(); + } + else { + PlayWallMovementSound(); + } + } + else { + sphereAcceleration = -sphereVelocity * linearFriction * (60.0f * USERCMD_ONE_OVER_HZ); + thrustScale = thrustMin; + if (bJumping) { + StopAirMovementSound(); + } + else { + StopWallMovementSound(); + } + } + + sphereVelocity += sphereAcceleration * (60.0f * USERCMD_ONE_OVER_HZ); + sphereVelocity.pitch = idMath::ClampFloat(-terminalVelocity, terminalVelocity, sphereVelocity.pitch); + sphereVelocity.yaw = idMath::ClampFloat(-terminalVelocity, terminalVelocity, sphereVelocity.yaw); + + movementScale = bJumping ? ( bBounced ? jumpBounceMovementScale : jumpThrustMovementScale ) : 1.f; + sphereAngles += sphereVelocity * movementScale * (60.0f * USERCMD_ONE_OVER_HZ); + if (sphereAngles.pitch <= spherePitchMin || sphereAngles.pitch >= spherePitchMax) { + sphereAngles.pitch = idMath::ClampFloat(spherePitchMin, spherePitchMax, sphereAngles.pitch); + sphereVelocity.pitch = 0; + } + sphereAngles = sphereAngles.Normalize180(); + + float radius = sphereRadius.GetCurrentValue(gameLocal.time) - UpdateJump( cmds ); + + idMat3 sphereAxis = sphereAngles.ToMat3(); + SetOrigin(sphereCenter - sphereAxis[0] * radius); + SetAxis(sphereAxis); + + memcpy( &oldCmds, cmds, sizeof(usercmd_t) ); + } + + // Apply viewAngles to turret + if ( viewAngles ) { + //HUMANHEAD PCF mdl 04/29/06 - Added bUpdateViewAngles to prevent view angles from changing on load + if ( bUpdateViewAngles ) { + // Update the old view angles after loading from a savegame + bUpdateViewAngles = false; + } else { + idAngles deltaViewAngles; + deltaViewAngles = *viewAngles - oldViewAngles; + localViewAngles += deltaViewAngles; + + // Clamp localViewAngles + if ( !IsNoClipping() ) { + localViewAngles.pitch = idMath::AngleNormalize180( localViewAngles.pitch ); + localViewAngles.pitch = idMath::ClampFloat(-turretPitchLimit, turretPitchLimit, localViewAngles.pitch); + localViewAngles.pitch = idMath::AngleNormalize180( localViewAngles.pitch ); + + localViewAngles.yaw = idMath::AngleNormalize180( localViewAngles.yaw ); + localViewAngles.yaw = idMath::ClampFloat(-turretYawLimit, turretYawLimit, localViewAngles.yaw); + localViewAngles.yaw = idMath::AngleNormalize180( localViewAngles.yaw ); + } + + idAngles turretAngles = sphereAngles + localViewAngles; + turret->SetAxis( turretAngles.ToMat3() ); + } + + memcpy( &oldViewAngles, viewAngles, sizeof(idAngles) ); + } +} + +/* +float hhRailShuttle::UpdateJump() { + float jumpDist; + + jumpDist = 0; + if( bJumping ) { + float elapsedTime = MS2SEC( gameLocal.time - jumpStartTime ); + float jumpDuration = spawnArgs.GetFloat( "jumpDuration" ); + if( elapsedTime < jumpDuration ) { + jumpDist = sin( elapsedTime * idMath::PI / jumpDuration ) * spawnArgs.GetFloat( "jumpDistance" ); + } + else { + bJumping = false; + } + } + return jumpDist; +} +*/ + +float hhRailShuttle::UpdateJump( const usercmd_t* cmds ) { + float elapsedTime; + + if( !bJumping) { + return 0; + } + + elapsedTime = MS2SEC( gameLocal.time - jumpStartTime ); + if( jumpStage == 1 ) { + jumpSpeed += jumpAccel * (60.0f * USERCMD_ONE_OVER_HZ); + if( elapsedTime > jumpAccelTime ) { + jumpStage = 2; + } + } + jumpSpeed -= jumpReturnForce * (60.0f * USERCMD_ONE_OVER_HZ); + jumpPosition += jumpSpeed * (60.0f * USERCMD_ONE_OVER_HZ); + if( jumpPosition < 0 ) { + jumpPosition = 0; + if( ++jumpNumBounces > jumpBounceCount ) { + bJumping = false; + StopAirMovementSound(); + StartSound( "snd_jumpland", SND_CHANNEL_ANY ); + } + else { + bBounced = true; + jumpSpeed = -jumpSpeed * jumpBounceFactor; + StartSound( "snd_jumpbounce", SND_CHANNEL_ANY ); + if( (cmds->buttons & BUTTON_ATTACK_ALT) && sphereRadius.IsDone(gameLocal.time) && HasPower(1) && !bDisengaged ) { + Jump(); + } + } + } + SetCockpitParm( VCOCKPIT_PARM_JUMP, jumpPosition ); + return jumpPosition; +} + +void hhRailShuttle::PlayWallMovementSound() { + if (!bWallMovementSoundPlaying) { + StartSound("snd_wallmovestart", SND_CHANNEL_ANY, 0, true); + StartSound("snd_wallmove", SND_CHANNEL_MISC5, 0, true); + bWallMovementSoundPlaying = true; + } +} + +void hhRailShuttle::StopWallMovementSound() { + if (bWallMovementSoundPlaying) { + StopSound(SND_CHANNEL_MISC5, true); + if (!bJumping) { + StartSound("snd_wallmovestop", SND_CHANNEL_ANY, 0, true); + } + bWallMovementSoundPlaying = false; + } +} + +void hhRailShuttle::PlayAirMovementSound() { + if (!bAirMovementSoundPlaying) { + StartSound("snd_thrust", SND_CHANNEL_THRUSTERS, 0, true); + bAirMovementSoundPlaying = true; + } +} + +void hhRailShuttle::StopAirMovementSound() { + if (bAirMovementSoundPlaying) { + StopSound(SND_CHANNEL_THRUSTERS, true); + bAirMovementSoundPlaying = false; + } +} + +void hhRailShuttle::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + if( !HasPower(1) || bDisengaged ) { + return; + } + if (!InGodMode()) { + if (idStr::Icmp(damageDefName, "damage_railbeam") == 0) { + ConsumePower(currentPower); + PostEventMS(&EV_RestorePower, SEC2MS(spawnArgs.GetFloat("powerOutageDuration"))); + StartSound( "snd_damagestart", SND_CHANNEL_ANY ); + StartSound( "snd_damageloop", SND_CHANNEL_MISC3, 0, true ); + + idEntity *ent = gameLocal.FindEntity( spawnArgs.GetString( "triggerDamage" ) ); + if( ent ) { + ent->PostEventMS( &EV_Activate, 0, inflictor ); + } + + SetCockpitParm( VCOCKPIT_PARM_POWER, -MS2SEC( gameLocal.time ) ); + } + } + //rww - don't want this thing to ever take real damage + //hhVehicle::Damage( inflictor, attacker, dir, damageDefName, 1.0f, location ); +} + +void hhRailShuttle::Event_SetOrbitRadius(float radius, int lerpTime) { + defaultRadius = radius; + float currentRadius = sphereRadius.GetCurrentValue(gameLocal.time); + sphereRadius.Init(gameLocal.time, lerpTime, currentRadius, defaultRadius); + bJumping = false; // Cancel any jumping +} + +void hhRailShuttle::Event_Jump() { + if (!bJumping && sphereRadius.IsDone(gameLocal.time) && HasPower(1) && !bDisengaged) { + Jump(); + } +} + +void hhRailShuttle::Event_FinalJump() { + jumpInitSpeed = spawnArgs.GetFloat("endingInitSpeed"); + jumpAccel = spawnArgs.GetFloat("endingAccel"); + jumpAccelTime = spawnArgs.GetFloat("endingAccelTime"); + jumpReturnForce = spawnArgs.GetFloat("endingReturnForce"); + Jump(); +} + +void hhRailShuttle::Event_IsJumping() { + idThread::ReturnFloat(bJumping); +} + +void hhRailShuttle::Event_Disengage() { + bDisengaged = true; +} + +void hhRailShuttle::Jump() { + bJumping = true; + bBounced = false; + jumpStartTime = gameLocal.time; + jumpStage = 1; + jumpSpeed = jumpInitSpeed; + jumpPosition = 0; + jumpNumBounces = 0; + StopWallMovementSound(); + if( !bDisengaged ) { + StartSound( "snd_jump", SND_CHANNEL_MISC2 ); + } +} + +void hhRailShuttle::Event_FireCannon() { + if( HasPower(1) && !bDisengaged && fireController && fireController->LaunchProjectiles(vec3_zero) ) { + StartSound( "snd_cannon", SND_CHANNEL_ANY ); + fireController->MuzzleFlash(); + fireController->WeaponFeedback(); + SetCockpitParm( VCOCKPIT_PARM_FIRE, -MS2SEC( gameLocal.time ) ); + + idEntity *ent = gameLocal.FindEntity( spawnArgs.GetString( "triggerFire" ) ); + if( ent ) { + ent->PostEventMS( &EV_Activate, 0, this ); + } + + leftArc->Activate( true ); + rightArc->Activate( true ); + + CancelEvents(&EV_HideArcs); + PostEventMS(&EV_HideArcs, spawnArgs.GetFloat("arcDuration")); + } +} + +void hhRailShuttle::Event_HideArcs() { + leftArc->Activate( false ); + rightArc->Activate( false ); +} + +void hhRailShuttle::Event_RestorePower() { + StopSound( SND_CHANNEL_MISC3, true ); + StartSound( "snd_damagestop", SND_CHANNEL_ANY ); + GivePower(100); +} + +void hhRailShuttle::SetCockpitParm( int parmNumber, float value ) { + if( GetPilotInterface() && GetPilotInterface()->IsType( hhPlayerVehicleInterface::Type ) ) { + hhControlHand *hand = static_cast(GetPilotInterface())->GetHandEntity(); + if( hand ) { + hand->SetShaderParm( parmNumber, value ); + } + } +} + +void hhRailShuttle::Event_GetCockpit() { + if( GetPilotInterface() && GetPilotInterface()->IsType( hhPlayerVehicleInterface::Type ) ) { + hhControlHand *hand = static_cast(GetPilotInterface())->GetHandEntity(); + if( hand ) { + idThread::ReturnEntity( hand ); + } else { + idThread::ReturnEntity( NULL ); + } + } +} +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/src/Prey/game_railshuttle.h b/src/Prey/game_railshuttle.h new file mode 100644 index 0000000..ecf2101 --- /dev/null +++ b/src/Prey/game_railshuttle.h @@ -0,0 +1,106 @@ +//========================================================================== +// +// hhShuttle +// +//========================================================================== + +class hhRailShuttle : public hhVehicle { + CLASS_PROTOTYPE( hhRailShuttle ); + +#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + virtual void Event_FireCannon() {}; + void Event_HideArcs() {}; + void Event_PostSpawn() {}; + void Event_SetOrbitRadius(float radius, int lerpTime) {}; + void Event_Jump() {}; + void Event_FinalJump() {}; + void Event_IsJumping() {}; + void Event_Disengage() {}; + void Event_RestorePower() {}; + void Event_GetCockpit() {}; + idEntity * GetTurret() { return NULL; } + virtual bool WillAcceptPilot( idActor *act ) { return false; } +#else + +public: + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + + // Static pilot assessor, for queries when you don't have a vehicle instantiated + static bool ValidPilot( idActor *act ); + virtual bool WillAcceptPilot( idActor *act ); + virtual void AcceptPilot( hhPilotVehicleInterface* pilotInterface ); + virtual void ProcessPilotInput( const usercmd_t* cmds, const idAngles* viewAngles ); + virtual void BecomeConsole(); + virtual void BecomeVehicle(); + idEntity * GetTurret() { return turret.GetEntity(); } + + virtual idVec3 GetFireOrigin(); // Called by firecontroller + virtual idMat3 GetFireAxis(); // Called by firecontroller + +protected: + void PlayWallMovementSound(); + void StopWallMovementSound(); + void PlayAirMovementSound(); + void StopAirMovementSound(); + virtual void Event_FireCannon(); + void Event_HideArcs(); + void Event_PostSpawn(); + void Event_SetOrbitRadius(float radius, int lerpTime); + void Event_Jump(); + void Event_FinalJump(); + void Event_IsJumping(); + void Event_Disengage(); + void Jump(); + void Event_RestorePower(); + float UpdateJump( const usercmd_t* cmds ); + void SetCockpitParm( int parmNumber, float value ); + void Event_GetCockpit(); + +protected: + float defaultRadius; + idInterpolate sphereRadius; + idAngles sphereAngles; + idAngles sphereVelocity; + idAngles sphereAcceleration; + idVec3 sphereCenter; + float spherePitchMin; + float spherePitchMax; + float turretYawLimit; + float turretPitchLimit; + float linearFriction; + float terminalVelocity; + idAngles localViewAngles; + idAngles oldViewAngles; + idEntityPtr turret; + idEntityPtr canopy; + idEntityPtr leftArc; + idEntityPtr rightArc; + bool bDisengaged; + bool bJumping; + bool bBounced; + int jumpStartTime; + int jumpStage; + float jumpSpeed; + float jumpPosition; + int jumpNumBounces; + float jumpInitSpeed; + float jumpAccel; + float jumpAccelTime; + float jumpReturnForce; + int jumpBounceCount; + float jumpBounceFactor; + float jumpThrustMovementScale; + float jumpBounceMovementScale; + bool bWallMovementSoundPlaying; + bool bAirMovementSoundPlaying; + //HUMANHEAD PCF mdl 04/29/06 - Added bUpdateViewAngles to prevent view angles from changing on load + bool bUpdateViewAngles; +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +}; diff --git a/src/Prey/game_renderentity.cpp b/src/Prey/game_renderentity.cpp new file mode 100644 index 0000000..ae8211a --- /dev/null +++ b/src/Prey/game_renderentity.cpp @@ -0,0 +1,141 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_ModelDefHandleIsValid( "" ); + +CLASS_DECLARATION( idEntity, hhRenderEntity ) + EVENT( EV_ModelDefHandleIsValid, hhRenderEntity::Event_ModelDefHandleIsValid ) +END_CLASS + +/* +============== +hhRenderEntity::hhRenderEntity +============== +*/ +hhRenderEntity::hhRenderEntity() { + combatModel = NULL; +} + +/* +============== +hhRenderEntity::~hhRenderEntity +============== +*/ +hhRenderEntity::~hhRenderEntity() { + SAFE_DELETE_PTR( combatModel ); +} + +void hhRenderEntity::Save(idSaveGame *savefile) const { + savefile->WriteClipModel( combatModel ); +} + +void hhRenderEntity::Restore( idRestoreGame *savefile ) { + savefile->ReadClipModel( combatModel ); + + if( combatModel ) { + const renderEntity_t *renderEntity = gameRenderWorld->GetRenderEntity( GetModelDefHandle() ); + if( renderEntity ) { + combatModel->Link( gameLocal.clip, this, 0, renderEntity->origin, renderEntity->axis, GetModelDefHandle() ); + } + } +} + +/* +============== +hhRenderEntity::Think +============== +*/ +void hhRenderEntity::Think() { + idEntity::Think(); + + LinkCombatModel( this, GetModelDefHandle() ); +} + +/* +============== +hhRenderEntity::InitCombatModel +============== +*/ +void hhRenderEntity::InitCombatModel( const int renderModelHandle ) { + if ( combatModel ) { + combatModel->Unlink(); + combatModel->LoadModel( renderModelHandle ); + combatModel->Link( gameLocal.clip );//Force a reclip because our origin and axis hasn't changed + } else { + combatModel = new idClipModel( renderModelHandle ); + HH_ASSERT( combatModel ); + } +} + +/* +============== +hhRenderEntity::LinkCombatModel +============== +*/ +void hhRenderEntity::LinkCombatModel( idEntity* self, const int renderModelHandle ) { + if( combatModel && self && renderModelHandle != -1 ) { + const renderEntity_t *renderEntity = gameRenderWorld->GetRenderEntity( renderModelHandle ); + if( !renderEntity ) { + return; + } + + if( combatModel->GetOrigin() != renderEntity->origin || combatModel->GetAxis() != renderEntity->axis || combatModel->GetBounds() != renderEntity->bounds ) { + combatModel->Link( gameLocal.clip, self, 0, renderEntity->origin, renderEntity->axis, renderModelHandle ); + } + } +} + +/* +============== +hhRenderEntity::Present + +AOBMERGE: PLEASE REMOVE THIS WHEN WE GET idRenderEntity +============== +*/ +void hhRenderEntity::Present( void ) { + PROFILE_SCOPE("Present", PROFMASK_NORMAL); + + if ( !gameLocal.isNewFrame ) { + 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 + if ( cameraTarget && gameLocal.InPlayerPVS( this ) ) { + 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 ); + //HUMANHEAD: aob - needed for combat models + PostEventMS( &EV_ModelDefHandleIsValid, 0 ); + //HUMANHEAD END + } else { + gameRenderWorld->UpdateEntityDef( modelDefHandle, &renderEntity ); + } +} + +/* +============== +hhRenderEntity::Event_ModelDefHandleIsValid +============== +*/ +void hhRenderEntity::Event_ModelDefHandleIsValid() { + if( spawnArgs.GetBool("useCombatModel") ) { + InitCombatModel( GetModelDefHandle() ); + LinkCombatModel( this, GetModelDefHandle() ); + } +} \ No newline at end of file diff --git a/src/Prey/game_renderentity.h b/src/Prey/game_renderentity.h new file mode 100644 index 0000000..f867d88 --- /dev/null +++ b/src/Prey/game_renderentity.h @@ -0,0 +1,31 @@ +#ifndef __GAME_RENDERENTITY_H +#define __GAME_RENDERENTITY_H + +extern const idEventDef EV_ModelDefHandleIsValid; + +class hhRenderEntity : public idEntity { + CLASS_PROTOTYPE( hhRenderEntity ); + + protected: + hhRenderEntity(); + virtual ~hhRenderEntity(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think(); + + virtual void InitCombatModel( const int renderModelHandle ); + virtual void LinkCombatModel( idEntity* self, const int renderModelHandle ); + + public: + virtual void Present(); + + protected: + void Event_ModelDefHandleIsValid(); + + protected: + idClipModel* combatModel; +}; + +#endif \ No newline at end of file diff --git a/src/Prey/game_safeDeathVolume.cpp b/src/Prey/game_safeDeathVolume.cpp new file mode 100644 index 0000000..cf2bf21 --- /dev/null +++ b/src/Prey/game_safeDeathVolume.cpp @@ -0,0 +1,136 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +//----------------------------------------------------------------------- +// +// hhSafeResurrectionVolume +// +//----------------------------------------------------------------------- + +CLASS_DECLARATION( idEntity, hhSafeResurrectionVolume ) + EVENT( EV_Enable, hhSafeResurrectionVolume::Event_Enable ) + EVENT( EV_Disable, hhSafeResurrectionVolume::Event_Disable ) +END_CLASS + +/* +=============== +hhSafeResurrectionVolume::Spawn +=============== +*/ +void hhSafeResurrectionVolume::Spawn() { + PostEventMS( (spawnArgs.GetBool("enabled", "1")) ? &EV_Enable : &EV_Disable, 0); +} + +/* +=============== +hhSafeResurrectionVolume::PickRandomPoint +=============== +*/ +void hhSafeResurrectionVolume::PickRandomPoint( idVec3& origin, idMat3& axis ) { + idEntity* entity = PickRandomTarget(); + + if ( !entity ) { // Error message that means something to the designers - cjr + gameLocal.Error( "hhSafeResurrectionZone::PickRandomPoint: No targets found within resurrection zone.\n" ); + } + + origin = entity->GetOrigin(); + axis = entity->GetAxis(); +} + +/* +=============== +hhSafeResurrectionVolume::DetermineContents +=============== +*/ +int hhSafeResurrectionVolume::DetermineContents() const { + return CONTENTS_DEATHVOLUME; +} + +/* +=============== +hhSafeResurrectionVolume::Event_Enable +=============== +*/ +void hhSafeResurrectionVolume::Event_Enable() { + GetPhysics()->EnableClip(); + GetPhysics()->SetContents( DetermineContents() ); +} + +/* +=============== +hhSafeResurrectionVolume::Event_Disable +=============== +*/ +void hhSafeResurrectionVolume::Event_Disable() { + GetPhysics()->DisableClip(); + GetPhysics()->SetContents( 0 ); +} + +//----------------------------------------------------------------------- +// +// hhSafeDeathVolume +// +//----------------------------------------------------------------------- + +CLASS_DECLARATION( hhSafeResurrectionVolume, hhSafeDeathVolume ) + EVENT( EV_Touch, hhSafeDeathVolume::Event_Touch ) +END_CLASS + +/* +=============== +hhSafeDeathVolume::Spawn +=============== +*/ +void hhSafeDeathVolume::Spawn() { + GetPhysics()->SetContents( DetermineContents() ); +} + +/* +=============== +hhSafeDeathVolume::DetermineContents +=============== +*/ +int hhSafeDeathVolume::DetermineContents() const { + return CONTENTS_DEATHVOLUME | CONTENTS_TRIGGER; +} + +/* +=============== +hhSafeDeathVolume::IsValid +=============== +*/ +bool hhSafeDeathVolume::IsValid( const hhPlayer* player ) { + if( !player || player->IsDeathWalking() || player->InVehicle() ) { + return false; + } + + return true; +} + +/* +=============== +hhSafeDeathVolume::Event_Touch +=============== +*/ +void hhSafeDeathVolume::Event_Touch( idEntity *other, trace_t *trace ) { + if( !other ) { + return; + } + + if( !other->IsType(hhPlayer::Type) ) { + return; + } + + hhPlayer* player = static_cast( other ); + if( !IsValid(player) ) { + return; + } + + if( player->IsSpiritOrDeathwalking() ) { + player->StopSpiritWalk(); + } else { + player->Kill( false, false ); + } +} \ No newline at end of file diff --git a/src/Prey/game_safeDeathVolume.h b/src/Prey/game_safeDeathVolume.h new file mode 100644 index 0000000..b3311dc --- /dev/null +++ b/src/Prey/game_safeDeathVolume.h @@ -0,0 +1,43 @@ +#ifndef __HH_SAFE_DEATH_VOLUME_H +#define __HH_SAFE_DEATH_VOLUME_H + +//----------------------------------------------------------------------- +// +// hhSafeResurrectionVolume +// +//----------------------------------------------------------------------- +class hhSafeResurrectionVolume : public idEntity { + CLASS_PROTOTYPE( hhSafeResurrectionVolume ); + + public: + void Spawn(); + + void PickRandomPoint( idVec3& origin, idMat3& axis ); + + protected: + virtual int DetermineContents() const; + + void Event_Enable(); + void Event_Disable(); +}; + +//----------------------------------------------------------------------- +// +// hhSafeResurrectionVolume +// +//----------------------------------------------------------------------- +class hhSafeDeathVolume : public hhSafeResurrectionVolume { + CLASS_PROTOTYPE( hhSafeDeathVolume ); + + public: + void Spawn(); + + protected: + virtual bool IsValid( const hhPlayer* player ); + virtual int DetermineContents() const; + + protected: + void Event_Touch( idEntity *other, trace_t *trace ); +}; + +#endif \ No newline at end of file diff --git a/src/Prey/game_securityeye.cpp b/src/Prey/game_securityeye.cpp new file mode 100644 index 0000000..7ec97cc --- /dev/null +++ b/src/Prey/game_securityeye.cpp @@ -0,0 +1,801 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +/********************************************************************** + +hhSecurityEyeBase + +**********************************************************************/ + +CLASS_DECLARATION( idEntity, hhSecurityEyeBase ) + EVENT( EV_Enable, hhSecurityEyeBase::Event_Enable ) + EVENT( EV_Disable, hhSecurityEyeBase::Event_Disable ) + EVENT( EV_Activate, hhSecurityEyeBase::Event_Activate ) +END_CLASS + +void hhSecurityEyeBase::Spawn() { + SpawnEye(); +} + +void hhSecurityEyeBase::Save(idSaveGame *savefile) const { + m_pEye.Save(savefile); +} + +void hhSecurityEyeBase::Restore( idRestoreGame *savefile ) { + m_pEye.Restore(savefile); +} + +void hhSecurityEyeBase::TransferArg(idDict &Args, const char *key) { + const idKeyValue *kv = NULL; + do { + kv = spawnArgs.MatchPrefix(key, kv); + if (kv) { + Args.Set(kv->GetKey().c_str(), kv->GetValue().c_str()); + } + } while (kv != NULL); +} + +void hhSecurityEyeBase::SpawnEye() { + + idVec3 origin = GetOrigin(); + idMat3 axis = GetAxis(); + idVec3 offset = spawnArgs.GetVector("offset_eye"); + + idDict Args; + Args.SetVector( "origin", origin + offset*axis ); + Args.SetMatrix( "rotation", axis ); + + TransferArg(Args, "minPitch"); + TransferArg(Args, "maxPitch"); + TransferArg(Args, "minYaw"); + TransferArg(Args, "maxYaw"); + TransferArg(Args, "startPitch"); + TransferArg(Args, "startYaw"); + TransferArg(Args, "pitchRate"); + TransferArg(Args, "yawRate"); + TransferArg(Args, "fov"); + TransferArg(Args, "pathScanRate"); + TransferArg(Args, "usePathScan"); + TransferArg(Args, "triggerOnce"); + TransferArg(Args, "lengthBeam"); + TransferArg(Args, "enabled"); + TransferArg(Args, "health"); + TransferArg(Args, "pathScanNode"); + TransferArg(Args, "call"); + TransferArg(Args, "callRef"); + TransferArg(Args, "callRefActivator"); + TransferArg(Args, "triggerBehavior"); + TransferArg(Args, "target"); + + m_pEye = gameLocal.SpawnObject( spawnArgs.GetString("def_eye"), &Args ); + HH_ASSERT( m_pEye.IsValid() && m_pEye.GetEntity() ); + + m_pEye->fl.noRemoveWhenUnbound = true; + m_pEye->Bind(this, true); + + if (m_pEye->IsType(hhSecurityEye::Type)) { + static_cast(m_pEye.GetEntity())->SetBase(this); + } + if (spawnArgs.GetBool("nobase")) { + Hide(); + } +} + +void hhSecurityEyeBase::Event_Activate(idEntity *pActivator) { + if (m_pEye.IsValid()) { + m_pEye->ProcessEvent(&EV_Activate, pActivator); // Pass activate messages to eye + } +} +void hhSecurityEyeBase::Event_Enable() { + if (m_pEye.IsValid()) { + m_pEye->ProcessEvent(&EV_Enable); // Pass enable messages to eye + } +} +void hhSecurityEyeBase::Event_Disable() { + if (m_pEye.IsValid()) { + m_pEye->ProcessEvent(&EV_Disable); // Pass disable messages to eye + } +} +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + +/********************************************************************** + +hhSecurityEye + +**********************************************************************/ +const idEventDef EV_Notify("notify", "e"); + +CLASS_DECLARATION( idEntity, hhSecurityEye ) + EVENT( EV_Enable, hhSecurityEye::Event_Enable ) + EVENT( EV_Disable, hhSecurityEye::Event_Disable ) + EVENT( EV_Notify, hhSecurityEye::Event_Notify ) + EVENT( EV_Activate, hhSecurityEye::Event_Activate ) +END_CLASS + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +/* +================ +hhSecurityEye::Spawn +================ +*/ +void hhSecurityEye::Spawn() { + m_pBase = NULL; + m_pTrigger = NULL; + m_bTriggerOnce = spawnArgs.GetBool("triggerOnce"); + m_fCachedScanFovCos = idMath::Cos( DEG2RAD(spawnArgs.GetFloat("fov")) ); + m_lengthBeam = spawnArgs.GetFloat("lengthBeam", "4096"); + m_offsetTrigger = spawnArgs.GetVector("offset_trigger"); + + SetupRotationParms(); + SpawnTrigger(); + m_bUsePathScan = InitPathList(); + + GetPhysics()->SetContents(CONTENTS_SHOOTABLE|CONTENTS_SHOOTABLEBYARROW); + fl.takedamage = false; // CJR: Set to be non-destructible at 3DR's request + + m_iPVSArea = gameLocal.pvs.GetPVSArea( GetOrigin() ); + + if( spawnArgs.GetBool("enabled", "1") ) { + StartScanning(); + } else { + EnterIdleState(); + } + + BecomeActive( TH_TICKER ); +} + +void hhSecurityEye::Save(idSaveGame *savefile) const { + savefile->WriteInt( state ); + savefile->WriteObject(m_pTrigger); + savefile->WriteBool(m_bTriggerOnce); + savefile->WriteFloat(m_fCachedScanFovCos); + savefile->WriteInt(m_iPVSArea); + savefile->WriteAngles(m_StartAngles); + savefile->WriteAngles(m_MaxLookAngles); + savefile->WriteAngles(m_MinLookAngles); + savefile->WriteBool(m_bPitchDirection); + savefile->WriteBool(m_bYawDirection); + savefile->WriteStringList(m_PathScanNodes); + savefile->WriteInt(m_iPathScanNodeIndex); + savefile->WriteBool(m_bUsePathScan); + savefile->WriteFloat(m_fPathScanRate); + savefile->WriteObject(m_pBase); + m_Target.Save(savefile); + + savefile->WriteFloat( currentYaw.GetStartTime() ); // idInterpolate + savefile->WriteFloat( currentYaw.GetDuration() ); + savefile->WriteFloat( currentYaw.GetStartValue() ); + savefile->WriteFloat( currentYaw.GetEndValue() ); + + savefile->WriteFloat( currentPitch.GetStartTime() ); // idInterpolate + savefile->WriteFloat( currentPitch.GetDuration() ); + savefile->WriteFloat( currentPitch.GetStartValue() ); + savefile->WriteFloat( currentPitch.GetEndValue() ); + + savefile->WriteAngles(m_RotationRate); + savefile->WriteFloat(m_lengthBeam); + savefile->WriteVec3(m_offsetTrigger); + + savefile->WriteAngles(m_LookAngles); +} + +void hhSecurityEye::Restore( idRestoreGame *savefile ) { + float set; + + savefile->ReadInt( reinterpret_cast ( state ) ); + savefile->ReadObject(reinterpret_cast (m_pTrigger)); + savefile->ReadBool(m_bTriggerOnce); + savefile->ReadFloat(m_fCachedScanFovCos); + savefile->ReadInt(m_iPVSArea); + savefile->ReadAngles(m_StartAngles); + savefile->ReadAngles(m_MaxLookAngles); + savefile->ReadAngles(m_MinLookAngles); + savefile->ReadBool(m_bPitchDirection); + savefile->ReadBool(m_bYawDirection); + savefile->ReadStringList(m_PathScanNodes); + savefile->ReadInt(m_iPathScanNodeIndex); + savefile->ReadBool(m_bUsePathScan); + savefile->ReadFloat(m_fPathScanRate); + savefile->ReadObject(reinterpret_cast (m_pBase)); + m_Target.Restore(savefile); + + savefile->ReadFloat( set ); // idInterpolate + currentYaw.SetStartTime( set ); + savefile->ReadFloat( set ); + currentYaw.SetDuration( set ); + savefile->ReadFloat( set ); + currentYaw.SetStartValue( set ); + savefile->ReadFloat( set ); + currentYaw.SetEndValue( set ); + + savefile->ReadFloat( set ); // idInterpolate + currentPitch.SetStartTime( set ); + savefile->ReadFloat( set ); + currentPitch.SetDuration( set ); + savefile->ReadFloat( set ); + currentPitch.SetStartValue( set ); + savefile->ReadFloat( set ); + currentPitch.SetEndValue( set ); + + savefile->ReadAngles(m_RotationRate); + savefile->ReadFloat(m_lengthBeam); + savefile->ReadVec3(m_offsetTrigger); + + savefile->ReadAngles(m_LookAngles); +} + +/* +================ +hhSecurityEye::~hhSecurityEye +================ +*/ +hhSecurityEye::~hhSecurityEye() { + SAFE_REMOVE( m_pTrigger ); + + //Used in case you use the remove command from the console + StopSound( SND_CHANNEL_ANY ); +} + +/* +===================== +hhSecurityEye::DormantBegin +===================== +*/ +void hhSecurityEye::DormantBegin() { + idEntity::DormantBegin(); + + if( state == StatePathScanning || state == StateAreaScanning ) { + DisableTrigger(); + } +} + +/* +===================== +hhSecurityEye::DormantEnd +===================== +*/ +void hhSecurityEye::DormantEnd() { + idEntity::DormantEnd(); + + if( state == StatePathScanning || state == StateAreaScanning ) { + EnableTrigger(); + } +} + +/* +===================== +hhSecurityEye::SetupRotationParms +===================== +*/ +void hhSecurityEye::SetupRotationParms() { + m_bPitchDirection = 1; + m_bYawDirection = 1; + m_StartAngles.Set( spawnArgs.GetFloat("startPitch"), spawnArgs.GetFloat("startYaw"), 0.0f ); + m_LookAngles = m_StartAngles; + + m_MaxLookAngles.Set( spawnArgs.GetFloat("maxPitch"), spawnArgs.GetFloat("maxYaw"), 0.0f ); + m_MinLookAngles.Set( spawnArgs.GetFloat("minPitch"), spawnArgs.GetFloat("minYaw"), 0.0f ); + m_RotationRate.Set ( spawnArgs.GetFloat("pitchRate"), spawnArgs.GetFloat("yawRate"), 0.0f ); + + currentPitch.Init(gameLocal.time, 0, m_StartAngles.pitch, m_StartAngles.pitch); + currentYaw.Init(gameLocal.time, 0, m_StartAngles.yaw, m_StartAngles.yaw); +} + +// Turn to angles using standard rates, angles should be in -180..180 local space +void hhSecurityEye::TurnTo(idAngles &ang) { + float curYaw, curPitch, delta; + int duration; + + if (m_RotationRate.yaw) { + curYaw = currentYaw.GetCurrentValue(gameLocal.time); + delta = idMath::Fabs(curYaw - ang.yaw); + duration = SEC2MS(delta / m_RotationRate.yaw); + currentYaw.Init(gameLocal.time, duration, curYaw, ang.yaw); + } + + if (m_RotationRate.pitch) { + curPitch = currentPitch.GetCurrentValue(gameLocal.time); + delta = idMath::Fabs(curYaw - ang.yaw); + duration = SEC2MS(delta / m_RotationRate.pitch); + currentPitch.Init(gameLocal.time, duration, curPitch, ang.pitch); + } +} + +void hhSecurityEye::LookAtLinear(idVec3 &pos) { + float curYaw, curPitch, delta; + int durationYaw, durationPitch, duration; + idVec3 localDir; + idVec3 dir = pos - GetOrigin(); + dir.Normalize(); + GetBaseAxis().ProjectVector(dir, localDir); + + idAngles localAngles; + localAngles.roll = 0.0f; + localAngles.yaw = idMath::AngleNormalize180( localDir.ToYaw() ); + localAngles.pitch = -idMath::AngleNormalize180( localDir.ToPitch() ); + + curYaw = currentYaw.GetCurrentValue(gameLocal.time); + delta = idMath::Fabs(curYaw - localAngles.yaw); + durationYaw = SEC2MS(delta / m_fPathScanRate); + + curPitch = currentPitch.GetCurrentValue(gameLocal.time); + delta = idMath::Fabs(curYaw - localAngles.yaw); + durationPitch = SEC2MS(delta / m_fPathScanRate); + + // Both converge together at the same time + duration = max(durationYaw, durationPitch); + currentYaw.Init(gameLocal.time, duration, curYaw, localAngles.yaw); + currentPitch.Init(gameLocal.time, duration, curPitch, localAngles.pitch); +} + +// Returns base rotation, all the local angles are relative to this +idMat3 hhSecurityEye::GetBaseAxis() { + assert(m_pBase); + return m_pBase->GetAxis(); +} + +// Set rotation of eye based on local angles +void hhSecurityEye::UpdateRotation() { + idAngles localAngles; + localAngles.Set(currentPitch.GetCurrentValue(gameLocal.time), currentYaw.GetCurrentValue(gameLocal.time), 0.0f); + SetAxis( localAngles.ToMat3() ); +} + +void hhSecurityEye::SetBase(idEntity *ent) { + m_pBase = ent; +} + + +/* +===================== +hhSecurityEye::GetRenderView +===================== +*/ +renderView_t* hhSecurityEye::GetRenderView() { + + renderView = idEntity::GetRenderView(); + + idVec3 ViewOffset = spawnArgs.GetVector("offset_view"); + idMat3 ViewAxis = GetAxis(); + idVec3 ViewOrigin = GetOrigin() + ViewOffset*ViewAxis; + + gameLocal.CalcFov( spawnArgs.GetFloat("fov", "90"), renderView->fov_x, renderView->fov_y ); //HUMANHEAD premerge bjk + renderView->viewaxis = ViewAxis; + renderView->vieworg = ViewOrigin; + + return renderView; +} + +/* +================ +hhSecurityEye::GetTriggerArgs +================ +*/ +idDict* hhSecurityEye::GetTriggerArgs( idDict* pArgs ) { + assert( pArgs ); + + const idKeyValue* pKeyValue = NULL; + + pArgs->Set( "call", spawnArgs.GetString("call") ); + pArgs->Set( "callRef", spawnArgs.GetString("callRef") ); + pArgs->Set( "callRefActivator", spawnArgs.GetString("callRefActivator") ); + + pArgs->Set( "triggerBehavior", spawnArgs.GetString("triggerBehavior") ); + for( int iIndex = spawnArgs.GetNumKeyVals() - 1; iIndex >= 0 ; --iIndex ) { + pKeyValue = spawnArgs.GetKeyVal( iIndex ); + if ( !pKeyValue->GetKey().Cmpn( "trigger_class", 13 ) ) { + pArgs->Set( pKeyValue->GetKey(), pKeyValue->GetValue() ); + } + else if ( !pKeyValue->GetKey().Cmpn( "target", 6 ) ) { + pArgs->Set( pKeyValue->GetKey(), pKeyValue->GetValue() ); + } + } + + //Tripwire args + pArgs->SetFloat( "lengthBeam", m_lengthBeam ); + pArgs->SetBool( "rigidBeamLength", 1 ); + pArgs->Set( "beam", spawnArgs.GetString("beam") ); + //Tripwire args end + + return pArgs; +} + +/* +================ +hhSecurityEye::SpawnTrigger +================ +*/ +void hhSecurityEye::SpawnTrigger() { + + idVec3 origin = GetPhysics()->GetOrigin(); + idMat3 axis = GetPhysics()->GetAxis(); + + idDict Args; + Args.SetVector( "origin", origin + m_offsetTrigger*axis ); + Args.SetMatrix( "rotation", axis ); + m_pTrigger = static_cast( gameLocal.SpawnObject(spawnArgs.GetString("def_trigger"), GetTriggerArgs( &Args )) ); + HH_ASSERT( m_pTrigger ); + + m_pTrigger->SetOwner( this ); + m_pTrigger->Bind( this, true ); +} + +/* +============ +hhSecurityEye::InitPathList +============ +*/ +bool hhSecurityEye::InitPathList() { + const idKeyValue* pKeyValue = NULL; + + m_PathScanNodes.Clear(); + for( int iIndex = spawnArgs.GetNumKeyVals() - 1; iIndex >= 0; --iIndex ) { + pKeyValue = spawnArgs.GetKeyVal( iIndex ); + if ( !pKeyValue->GetKey().Cmpn( "pathScanNode", 12 ) && pKeyValue->GetValue().Length() ) { + m_PathScanNodes.Append( pKeyValue->GetValue() ); + } + } + + m_iPathScanNodeIndex = 0; + m_fPathScanRate = spawnArgs.GetFloat("pathScanRate"); + + return ( m_PathScanNodes.Num() && spawnArgs.GetBool( "usePathScan" ) ); +} + +/* +================ +hhSecurityEye::Ticker +================ +*/ +void hhSecurityEye::Ticker() { + switch( state ) { + case StateIdle: + if (currentYaw.IsDone(gameLocal.time) && currentPitch.IsDone(gameLocal.time)) { + BecomeInactive( TH_TICKER ); + } + break; + case StateTracking: + break; + + case StateAreaScanning: + if (currentPitch.IsDone(gameLocal.time)) { + m_bPitchDirection ^= 1; + if( !m_bPitchDirection ) { + m_LookAngles.pitch = m_MinLookAngles.pitch; + } else { + m_LookAngles.pitch = m_MaxLookAngles.pitch; + } + TurnTo( m_LookAngles ); + } + + if (currentYaw.IsDone(gameLocal.time)) { + m_bYawDirection ^= 1; + if( !m_bYawDirection ) { + m_LookAngles.yaw = m_MinLookAngles.yaw; + } else { + m_LookAngles.yaw = m_MaxLookAngles.yaw; + } + TurnTo( m_LookAngles ); + } + break; + + case StatePathScanning: + idEntity* pEntity = NULL; + + if (currentYaw.IsDone(gameLocal.time) && currentPitch.IsDone(gameLocal.time)) { + pEntity = VerifyPathScanNode( m_iPathScanNodeIndex ); + if( pEntity ) { + idVec3 origin = pEntity->GetPhysics()->GetOrigin(); + LookAtLinear( origin ); + + m_iPathScanNodeIndex = (m_iPathScanNodeIndex + 1) % m_PathScanNodes.Num(); + } + } + break; + } + + UpdateRotation(); +} + +/* +============ +hhSecurityEye::DetermineEntityPosition +============ +*/ +idVec3 hhSecurityEye::DetermineEntityPosition( idEntity* pEntity ) { + if( pEntity && pEntity->IsType( idActor::Type ) ) { + return static_cast(pEntity)->GetEyePosition(); + } + + return pEntity->GetPhysics()->GetOrigin(); +} + +/* +================ +hhSecurityEye::CanSeeTarget +================ +*/ +bool hhSecurityEye::CanSeeTarget( idEntity* pEntity ) { + float fDist; + trace_t TraceInfo; + idVec3 Dir; + pvsHandle_t PVSHandle; + idVec3 EyePos; + idMat3 EyeAxis; + idVec3 TraceEnd; + + PVSHandle = gameLocal.pvs.SetupCurrentPVS( m_iPVSArea ); + + if ( !pEntity || ( pEntity->fl.notarget ) ) { + goto CANT_SEE; + } + + // if there is no way we can see this player + if( !gameLocal.pvs.InCurrentPVS( PVSHandle, pEntity->GetPVSAreas(), pEntity->GetNumPVSAreas() ) ) { + goto CANT_SEE; + } + + TraceEnd = (pEntity->IsType(idActor::Type)) ? static_cast(pEntity)->GetEyePosition() : pEntity->GetPhysics()->GetOrigin(); + Dir = TraceEnd - GetPhysics()->GetOrigin(); + fDist = Dir.Normalize(); + + if( fDist > m_lengthBeam ) { + goto CANT_SEE; + } + + EyeAxis = GetAxis(); + EyePos = GetOrigin() + m_offsetTrigger * EyeAxis; + + if( Dir * EyeAxis[0] < m_fCachedScanFovCos ) { + goto CANT_SEE; + } + + gameLocal.clip.TracePoint( TraceInfo, EyePos, TraceEnd, MASK_VISIBILITY, this ); + if( TraceInfo.fraction == 1.0f || TraceInfo.c.entityNum == pEntity->entityNumber ) { + gameLocal.pvs.FreeCurrentPVS( PVSHandle ); + return true; + } + +CANT_SEE: + gameLocal.pvs.FreeCurrentPVS( PVSHandle ); + + return false; +} + +/* +===================== +hhSecurityEye::EnableTrigger +===================== +*/ +void hhSecurityEye::EnableTrigger() { + if( m_pTrigger ) { + m_pTrigger->ProcessEvent( &EV_Enable ); + } +} + +/* +===================== +hhSecurityEye::DisableTrigger +===================== +*/ +void hhSecurityEye::DisableTrigger() { + if( m_pTrigger ) { + m_pTrigger->ProcessEvent( &EV_Disable ); + } +} + +/* +============ +hhSecurityEye::Killed +============ +*/ +void hhSecurityEye::Killed( idEntity *pInflictor, idEntity *pAttacker, int iDamage, const idVec3 &Dir, int iLocation ) { + if( state != StateDead ) { + //Only trigger targets when active + if( state != StateIdle ) { + ActivateTargets( pAttacker ); + } + + EnterDeadState(); + } +} + +/* +============ +hhSecurityEye::StartScanning +============ +*/ +void hhSecurityEye::StartScanning() { + if( !m_bUsePathScan ) { + EnterAreaScanningState(); + } else { + EnterPathScanningState(); + } +} + +/* +============ +hhSecurityEye::VerifyLookTarget +============ +*/ +idEntity* hhSecurityEye::VerifyPathScanNode( int& iIndex ) { + return gameLocal.FindEntity( m_PathScanNodes[iIndex].c_str() ); +} + +/* +============ +hhSecurityEye::Event_Enable +============ +*/ +void hhSecurityEye::Event_Enable() { + switch( state ) { + case StateIdle: + StartScanning(); + break; + } +} + +/* +============ +hhSecurityEye::Event_Disable +============ +*/ +void hhSecurityEye::Event_Disable() { + switch( state ) { + case StateTracking: + case StatePathScanning: + case StateAreaScanning: + EnterIdleState(); + break; + } +} + +/* +============ +hhSecurityEye::Event_Notify +============ +*/ +void hhSecurityEye::Event_Notify( idEntity* pActivator ) { + //Not sure of the behavior wanted if m_bTriggerOnce is true + if( pActivator && pActivator->IsType(idActor::Type) ) { + const idActor* pActor = static_cast( pActivator ); + } + + switch( state ) { + case StateAreaScanning: + case StatePathScanning: + HH_ASSERT( pActivator ); + m_Target = pActivator; + EnterTrackingState(); + break; + case StateTracking: + break; + } +} + +/* +============ +hhSecurityEye::Event_Activate +============ +*/ +void hhSecurityEye::Event_Activate( idEntity* pActivator ) { + switch( state ) { + case StateIdle: + StartScanning(); + break; + case StatePathScanning: + case StateAreaScanning: + case StateTracking: + m_Target = NULL; + EnterIdleState(); + break; + } +} + +/* +================ +hhSecurityEye::EnterDeadState +================ +*/ +void hhSecurityEye::EnterDeadState() { + state = StateDead; + + hhFxInfo fxInfo; + + DisableTrigger(); + + StopSound( SND_CHANNEL_ANY ); + + fxInfo.SetNormal( GetAxis()[0] ); + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfoPrefixed( "fx_detonate", m_pBase->GetOrigin(), GetAxis(), &fxInfo ); + + idVec3 Position = spawnArgs.GetVector("offset_debris"); + Position = GetPhysics()->GetOrigin() + Position * GetPhysics()->GetAxis(); + idVec3 Forward = GetAxis()[0]; + idVec3 Velocity = Forward*10; + hhUtils::SpawnDebrisMass(spawnArgs.GetString("def_debrisspawner"), Position, &Forward, &Velocity, 1); + + // Cause the base to flicker when the ball is destroyed + if( m_pBase ) { + m_pBase->SetShaderParm( SHADERPARM_MODE, 1 ); // Activate the flicker effect + m_pBase->SetShaderParm( SHADERPARM_TIMEOFFSET, -MS2SEC( gameLocal.time )); + } + + Hide(); + GetPhysics()->DisableClip(); + int length = 0; + StartSound("snd_explode", SND_CHANNEL_ANY, 0, false, &length); + PostEventMS( &EV_Remove, length ); + + BecomeActive( TH_TICKER ); +} + +/* +================ +hhSecurityEye::EnterIdleState +================ +*/ +void hhSecurityEye::EnterIdleState() { + state = StateIdle; + + DisableTrigger(); + + TurnTo( m_StartAngles ); + + StopSound( SND_CHANNEL_ANY ); +} + +/* +================ +hhSecurityEye::EnterAreaScanningState +================ +*/ +void hhSecurityEye::EnterAreaScanningState() { + state = StateAreaScanning; + + m_LookAngles = m_StartAngles; + TurnTo( m_StartAngles ); + + EnableTrigger(); + + StartSound("snd_move", SND_CHANNEL_ANY); + + BecomeActive( TH_TICKER ); +} + +/* +================ +hhSecurityEye::EnterPathScanningState +================ +*/ +void hhSecurityEye::EnterPathScanningState() { + state = StatePathScanning; + + HH_ASSERT( m_PathScanNodes.Num() ); + + EnableTrigger(); + + StartSound("snd_move", SND_CHANNEL_ANY); + + BecomeActive( TH_TICKER ); +} + +/* +================ +hhSecurityEye::EnterTrackingState +================ +*/ +void hhSecurityEye::EnterTrackingState() { + state = StateTracking; + + DisableTrigger(); + StopSound( SND_CHANNEL_ANY ); + + BecomeActive( TH_TICKER ); +} + +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/src/Prey/game_securityeye.h b/src/Prey/game_securityeye.h new file mode 100644 index 0000000..2983d1b --- /dev/null +++ b/src/Prey/game_securityeye.h @@ -0,0 +1,134 @@ +#ifndef __HH_SECURITYEYE_H +#define __HH_SECURITYEYE_H + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +extern const idEventDef EV_Notify; + +/********************************************************************** + +hhSecurityEyeBase + +**********************************************************************/ +class hhSecurityEyeBase : public idEntity { + CLASS_PROTOTYPE( hhSecurityEyeBase ); + +public: + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void SpawnEye(); + void TransferArg(idDict &Args, const char *key); + +protected: + void Event_Activate( idEntity* pActivator ); + void Event_Enable(); + void Event_Disable(); + +protected: + idEntityPtr m_pEye; +}; +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + +/********************************************************************** + +hhSecurityEye + +**********************************************************************/ +class hhSecurityEye : public idEntity { + CLASS_PROTOTYPE( hhSecurityEye ); +#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + void Event_Enable() {}; + void Event_Disable() {}; + void Event_Notify( idEntity* pActivator ) {}; + void Event_Activate( idEntity* pActivator ) {}; +#else + public: + hhSecurityEye() : m_pTrigger( NULL ) { } + virtual ~hhSecurityEye(); + + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void SetBase(idEntity *ent); + + virtual renderView_t* GetRenderView(); + + protected: + void Ticker(); + idDict* GetTriggerArgs( idDict* pArgs ); + void SpawnTrigger(); + void SpawnBase(); + bool InitPathList(); + + virtual void DormantBegin(); + virtual void DormantEnd(); + + void EnableTrigger(); + void DisableTrigger(); + + idVec3 DetermineEntityPosition( idEntity* pEntity ); + bool CanSeeTarget( idEntity* pEntity ); + virtual void Killed( idEntity *pInflictor, idEntity *pAttacker, int iDamage, const idVec3 &Dir, int iLocation ); + + void StartScanning(); + idEntity* VerifyPathScanNode( int& iIndex ); + + protected: + void Event_Enable(); + void Event_Disable(); + void Event_Notify( idEntity* pActivator ); + void Event_Activate( idEntity* pActivator ); + + protected: + enum States { + StateIdle = 0, + StateAreaScanning, + StatePathScanning, + StateTracking, // Tracking state is now just a stopover state that means it was triggered. + // It is needed so that the next time it's triggered, it will become inactive + StateDead + } state; + + void EnterDeadState(); + void EnterIdleState(); + void EnterAreaScanningState(); + void EnterPathScanningState(); + void EnterTrackingState(); + + void UpdateRotation(); + void LookAtLinear(idVec3 &pos); + void TurnTo(idAngles &ang); + void SetupRotationParms(); + idMat3 GetBaseAxis(); + + protected: + hhTriggerTripwire* m_pTrigger; + + bool m_bTriggerOnce; + float m_fCachedScanFovCos; + int m_iPVSArea; + + idAngles m_StartAngles; + idAngles m_MaxLookAngles; + idAngles m_MinLookAngles; + idAngles m_LookAngles; + bool m_bPitchDirection; + bool m_bYawDirection; + + idList m_PathScanNodes; + int m_iPathScanNodeIndex; + bool m_bUsePathScan; + float m_fPathScanRate; + + idEntity *m_pBase; + idEntityPtr m_Target; + + idInterpolate currentYaw; // These are local angles, relative to base rotation + idInterpolate currentPitch; // These are local angles, relative to base rotation + idAngles m_RotationRate; + float m_lengthBeam; + idVec3 m_offsetTrigger; +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +}; + +#endif \ No newline at end of file diff --git a/src/Prey/game_shuttle.cpp b/src/Prey/game_shuttle.cpp new file mode 100644 index 0000000..50dc5e9 --- /dev/null +++ b/src/Prey/game_shuttle.cpp @@ -0,0 +1,1057 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/* + method: + walk up to ship console, activate gui + gui sends command to vehicle to take control + vehicle animates out of console around you + control hand is added + + model specs: + min: -64 -50 -64 + max: 64 50 64 + + needs to be centered about the origin (in all axes) + needs to fit a pilot collision box inside in his "piloting" animation + needs to be collapsable into the bottom console +*/ + + +//========================================================================== +// +// hhTractorBeam +// +//========================================================================== + +CLASS_DECLARATION(idClass, hhTractorBeam) +END_CLASS + +hhTractorBeam::hhTractorBeam() { + bAllow = true; + owner = NULL; + bindController = NULL; + traceController = NULL; + traceTarget = NULL; + beam = NULL; + targetLocalOffset = vec3_origin; + targetID = 0; + bActive = false; + beamClientPredictTime = 0; +} + +void hhTractorBeam::Save(idSaveGame *savefile) const { + owner.Save(savefile); + beam.Save(savefile); + traceTarget.Save(savefile); + bindController.Save(savefile); + traceController.Save(savefile); + + savefile->WriteStaticObject(feedbackForce); + savefile->WriteInt(tractorCost); + savefile->WriteBool(bAllow); + savefile->WriteBool(bActive); + savefile->WriteVec3(offsetTraceStart); + savefile->WriteVec3(offsetTraceEnd); + savefile->WriteVec3(offsetEquilibrium); + savefile->WriteVec3(targetLocalOffset); + savefile->WriteInt(targetID); +} + +void hhTractorBeam::Restore( idRestoreGame *savefile ) { + owner.Restore(savefile); + beam.Restore(savefile); + traceTarget.Restore(savefile); + bindController.Restore(savefile); + traceController.Restore(savefile); + + savefile->ReadStaticObject(feedbackForce); + savefile->ReadInt(tractorCost); + savefile->ReadBool(bAllow); + savefile->ReadBool(bActive); + savefile->ReadVec3(offsetTraceStart); + savefile->ReadVec3(offsetTraceEnd); + savefile->ReadVec3(offsetEquilibrium); + savefile->ReadVec3(targetLocalOffset); + savefile->ReadInt(targetID); +} + +void hhTractorBeam::WriteToSnapshot( idBitMsgDelta &msg ) const { + //don't need to sync the beam and other components that are purely client-side + + msg.WriteBits(owner.GetSpawnId(), 32); + msg.WriteBits(traceTarget.GetSpawnId(), 32); + msg.WriteBits(targetID, 32); + + msg.WriteBits(bActive, 1); + msg.WriteBits(bAllow, 1); +} + +void hhTractorBeam::ReadFromSnapshot( const idBitMsgDelta &msg ) { + owner.SetSpawnId(msg.ReadBits(32)); + traceTarget.SetSpawnId(msg.ReadBits(32)); + targetID = msg.ReadBits(32); + + bool beamActive = !!msg.ReadBits(1); + if (beamActive != bActive && beamClientPredictTime <= gameLocal.time) { + if (beamActive) { + Activate(); + } + else { + Deactivate(); + } + } + bAllow = !!msg.ReadBits(1); +} + +void hhTractorBeam::SpawnComponents(hhShuttle *shuttle) { + SetOwner(shuttle); + + idVec3 ownerOrigin = owner->GetOrigin(); + idMat3 ownerAxis = owner->GetAxis(); + idDict *ownerArgs = &owner->spawnArgs; + + tractorCost = ownerArgs->GetInt("tractorcost"); + offsetTraceStart = ownerArgs->GetVector("offset_tractortracestart"); + offsetTraceEnd = ownerArgs->GetVector("offset_tractortraceend"); + offsetEquilibrium = ownerArgs->GetVector("offset_tractorequilibrium"); + + float tension = ownerArgs->GetFloat("tension", "1.0"); + float slack; + if (gameLocal.isMultiplayer) { + slack = ownerArgs->GetFloat("slack_mp", "1.0"); + } + else { + slack = ownerArgs->GetFloat("slack", "1.0"); + } + float yawLimit = ownerArgs->GetFloat("yawlimit", "180"); + const char *handName = ownerArgs->GetString("def_tractorhand"); + const char *animName = ownerArgs->GetString("boundanim"); + + if (gameLocal.isClient) { + bindController = static_cast( gameLocal.SpawnClientObject(ownerArgs->GetString("def_bindController"), NULL) ); + } + else { + bindController = static_cast( gameLocal.SpawnObject(ownerArgs->GetString("def_bindController")) ); + } + HH_ASSERT( bindController.IsValid() ); + bindController->fl.networkSync = false; + + bindController->SetTension(tension); + bindController->SetSlack(slack); + bindController->SetRiderParameters(animName, handName, yawLimit, 0.0f); + bindController->SetOrigin(ownerOrigin + offsetEquilibrium * ownerAxis); + bindController->Bind(owner.GetEntity(), true); +// bindController->fl.robustDormant = true; // This beam could be going through walls, so set it to a robust dormancy check + bindController->fl.neverDormant = true; + bindController->SetShuttle(true); + + if (gameLocal.isClient) { + traceController = gameLocal.SpawnClientObject( ownerArgs->GetString("def_traceController"), NULL ); + } + else { + traceController = gameLocal.SpawnObject( ownerArgs->GetString("def_traceController") ); + } + HH_ASSERT( traceController.IsValid() ); + traceController->fl.networkSync = false; + + traceController->SetOrigin(ownerOrigin + offsetTraceEnd * ownerAxis); + traceController->Bind(owner.GetEntity(), true); +// traceController->fl.robustDormant = true; // This beam could be going through walls, so set it to a robust dormancy check + traceController->fl.neverDormant = true; + + idVec3 offsetBeamPosition = ownerArgs->GetVector("offset_tractorbeamposition"); + idVec3 pos = ownerOrigin + offsetBeamPosition * ownerAxis; + beam = hhBeamSystem::SpawnBeam(pos, ownerArgs->GetString("beam_tractor"), mat3_identity, true); + HH_ASSERT( beam.IsValid() ); + beam->SetOrigin(pos); + beam->SetAxis(ownerAxis); + beam->Bind(owner.GetEntity(), false); + beam->Activate( false ); + beam->fl.robustDormant = true; // This beam could be going through walls, so set it to a robust dormancy check + + float feedbacktension = ownerArgs->GetFloat("feedbacktension"); + feedbackForce.SetRestoreFactor(feedbacktension); + float feedbackslack = ownerArgs->GetFloat("feedbackslack"); + feedbackForce.SetRestoreSlack(feedbackslack); +} + +bool hhTractorBeam::Exists() { + return (owner.IsValid() && bindController.IsValid() && traceController.IsValid() && beam.IsValid()); +} + +bool hhTractorBeam::IsAllowed() { + return (Exists() && bAllow && /*!owner->IsDocked() &&*/ owner->HasPower(tractorCost) && owner->noTractorTime <= gameLocal.time); +} + +bool hhTractorBeam::IsActive() { + return (Exists() && bActive); +} + +bool hhTractorBeam::IsLocked() { + return (IsActive() && GetTarget() != NULL); +} + +idEntity *hhTractorBeam::GetTarget() { + if (!bindController.IsValid() || !bindController.GetEntity()) { //rww - in the case of no tractor beam (testing concept for mp), this controller may not exist, so check it. + return NULL; + } + return bindController->GetRider(); +} + +void hhTractorBeam::RequestState(bool wantsActive) { + if (wantsActive && !IsActive() && IsAllowed()) { + Activate(); + } + else if (!wantsActive && IsActive()) { + Deactivate(); + } +} + +void hhTractorBeam::Activate() { + bActive = true; + beamClientPredictTime = gameLocal.time + USERCMD_MSEC*4; + owner->StartSound("snd_tractor", SND_CHANNEL_TRACTORBEAM); + owner->StartSound("snd_tractor_start", SND_CHANNEL_ANY); +} + +void hhTractorBeam::Deactivate() { + if ( !gameLocal.isMultiplayer && GetTarget() && GetTarget()->IsType( hhMonsterAI::Type ) ) { + idVec3 velocity = GetTarget()->GetPhysics()->GetLinearVelocity(); + hhMonsterAI *targetAI = static_cast( GetTarget() ); + if ( targetAI && owner.IsValid() ) { + targetAI->SetEnemy( owner.GetEntity()->GetPilot() ); + } + if ( velocity.Length() > GetTarget()->spawnArgs.GetFloat( "tractor_kill_speed", "800" ) ) { + if ( targetAI ) { + targetAI->Damage( NULL, NULL, idVec3( 0,0,0 ), "damage_monsterfall", 1, INVALID_JOINT ); + if ( targetAI->GetAFPhysics() ) { + targetAI->GetAFPhysics()->SetLinearVelocity( velocity * GetTarget()->spawnArgs.GetFloat( "tractor_speed_scale", "15" ) ); + } + } + } + } + bActive = false; + beamClientPredictTime = gameLocal.time + USERCMD_MSEC*4; + if (beam.IsValid()) { + beam->Activate( false ); + beam->SetTargetEntity(NULL); + } + if (owner.IsValid()) { + owner->StopSound(SND_CHANNEL_TRACTORBEAM); + owner->StartSound("snd_tractor_stop", SND_CHANNEL_ANY); + } + if (GetTarget()) { + GetTarget()->fl.isTractored = false; //rww + } + if (bindController.IsValid()) { + bindController->Detach(); + } + feedbackForce.SetEntity(NULL); +} + +bool hhTractorBeam::ValidTarget(idEntity *ent) { + bool b = ent && + !(ent->IsType(idActor::Type) && static_cast(ent)->InVehicle()) && + ent->GetPhysics() && + ent->GetPhysics()->GetContents() != 0 && + !ent->IsHidden() && + ent->fl.canBeTractored; + + if (gameLocal.isMultiplayer && b) { //rww - if mp and a valid target thus far, perform mp-only check(s) + if (ent->IsType(hhPlayer::Type)) { + hhPlayer *pl = static_cast(ent); + if (pl->IsDead()) { //if he died, let go. + b = false; + } + else if (pl->IsWallWalking()) { //don't allow grabbing wallwalking players in mp + b = false; + } + } + else if (ent->IsType(hhMPDeathProxy::Type)) { //don't grab temporary client-based corpses + b = false; + } + } + + return b; +} + +void hhTractorBeam::ReleaseTarget() { + // Release the target, but stay active + beam->SetTargetEntity(NULL); + if (GetTarget()) { + GetTarget()->fl.isTractored = false; //rww + } + bindController->Detach(); + feedbackForce.SetEntity(NULL); +} + +void hhTractorBeam::LaunchTarget(float speed) { + idEntity *target = bindController->GetRider(); + ReleaseTarget(); + if (target) { + idVec3 vel = owner->GetAxis()[0] * speed; + target->GetPhysics()->SetLinearVelocity(vel); + } +} + +idEntity *hhTractorBeam::TraceForTarget(trace_t &results) { + idVec3 start = owner->GetOrigin() + offsetTraceStart * owner->GetAxis(); + idVec3 end; + if (GetTarget() && results.c.entityNum != ENTITYNUM_NONE) { + end = GetTarget()->GetOrigin(); + } + else { + end = traceController->GetOrigin(); + } + gameLocal.clip.TracePoint( results, start, end, MASK_TRACTORBEAM, owner.GetEntity() ); + + idEntity *validEntity = NULL; + if (results.fraction < 1.0f) { + idEntity *entity = gameLocal.GetTraceEntity(results); + if (ValidTarget(entity)) { + if (entity && (entity == GetTarget() || !entity->fl.isTractored)) { //rww - if i'm not grabbing it already, don't grab it if it's tractored already. + validEntity = entity; + } + } + } + + if (beam.IsValid() && !GetTarget()) { + beam->SetTargetLocation(results.endpos); //rww - in any case, set the beam's target pos to the trace endpos + } + + return validEntity; +} + +void hhTractorBeam::AttachTarget(idEntity *target, trace_t &results) { + if (target->IsType(idAFEntity_Base::Type) && // Active ragdoll + static_cast(target)->IsActiveAF()) { + idAFEntity_Base *af = static_cast(target); + //int joint = CLIPMODEL_ID_TO_JOINT_HANDLE( results.c.id ); + targetID = af->BodyForClipModelId(results.c.id); + targetLocalOffset = vec3_origin; // apply to center of mass + } + else if (target->IsType(idActor::Type)) { // Non-ragdolled actor + targetID = 0; + targetLocalOffset = target->GetPhysics()->GetBounds().GetCenter();// - target->GetOrigin(); + + //rww - for mp kill credits + if (gameLocal.isMultiplayer) { + if (target->IsType(hhPlayer::Type) && owner->GetPilot() && owner->GetPilot()->IsType(hhPlayer::Type)) { + hhPlayer *plTarget = static_cast(target); + plTarget->airAttacker = owner->GetPilot(); + plTarget->airAttackerTime = gameLocal.time + 300; + } + else if (target->IsType(hhSpiritProxy::Type)) { //in mp, snap player back to their body when their prox is tractored + target->Damage(owner.GetEntity(), owner.GetEntity(), vec3_origin, "damage_generic", 0.0f, -1); //call damage, but don't actually do damage. + } + } + } + else { // Everything else + targetID = results.c.id; + targetLocalOffset = ( results.c.point - target->GetOrigin(targetID) ) * target->GetAxis(targetID).Transpose(); + } + bindController->Attach(target, true, targetID, targetLocalOffset); + beam->SetTargetEntity(target, targetID, targetLocalOffset); + beam->SetArcVector(owner->GetAxis()[0]); + feedbackForce.SetEntity(owner.GetEntity()); + + target->fl.isTractored = true; //rww +} + +void hhTractorBeam::Update() { + trace_t results; + + if (Exists()) { + if (IsActive()) { + if (!IsAllowed() || !owner->ConsumePower(tractorCost)) { + // Ran out of power or no longer allowed + Deactivate(); + } + + if (IsLocked()) { + if (!ValidTarget(GetTarget())) { + // Locked target is no longer valid + ReleaseTarget(); + } + else if (gameLocal.isMultiplayer && GetTarget() && GetTarget()->IsType(hhPlayer::Type)) { + //rww - just hint the player in the direction of the shuttle that has them picked up for mp (help with disorientation) + float frametime = ((float)(gameLocal.time - gameLocal.previousTime))/USERCMD_MSEC; + hhPlayer *targetPl = static_cast(GetTarget()); + + + idQuat plOri = targetPl->GetViewAngles().ToQuat(); + idVec3 targetPos = owner->GetOrigin(); + targetPos.z -= 80.0f; + + idVec3 t = (targetPos-targetPl->GetOrigin()); + t.Normalize(); + idQuat idealOri = t.ToMat3().ToQuat(); + + idQuat finalOri; + finalOri.Slerp(plOri, idealOri, 0.02f*frametime); + + targetPl->SetOrientation(targetPl->GetOrigin(), targetPl->GetPhysics()->GetAxis(), finalOri.ToMat3()[0], finalOri.ToAngles()); + } + } + } + + // Do the trace as long as we're not already locked, so the GUI can use it to show the cursor + if (!IsAllowed() || IsLocked()) { + //traceTarget = NULL; + //rww - changed to continue tracing even after grabbing something + + results.c.entityNum = 0; //try tracing to the target + traceTarget = TraceForTarget(results); + if (traceTarget != GetTarget()) { + results.c.entityNum = ENTITYNUM_NONE; //try tracing forward along beam + traceTarget = TraceForTarget(results); + if (traceTarget != GetTarget()) { + ReleaseTarget(); + if (beam.IsValid()) { + beam->SetTargetLocation(results.endpos); + } + } + } + } + else { + traceTarget = TraceForTarget(results); + + if (IsActive()) { + if (traceTarget != NULL) { + AttachTarget(traceTarget.GetEntity(), results); + } + //rww - instead of doing this and giving a misleading trace result, setting to the actual trace.endpos + /* + else { + // No target found, but leave beam out there + beam->SetTargetEntity(traceController.GetEntity()); + } + */ + } + } + + if (IsActive()) { + // Update beam based on shuttle orientation + if (!beam->IsActivated()) { + beam->Activate( true ); + } + beam->SetArcVector( owner->GetAxis()[0] ); + } + + // Update the feedback force + if (IsLocked()) { + idVec3 p = GetTarget()->GetOrigin(targetID) + targetLocalOffset * GetTarget()->GetAxis(targetID); + idVec3 a = owner->GetOrigin() - p; + a.Normalize(); + a *= offsetEquilibrium.Length(); + idVec3 idealPosition = p + a; + feedbackForce.SetTarget(idealPosition); + feedbackForce.Evaluate(gameLocal.time); + } + } +} + + + +//========================================================================== +// +// hhShuttle +// +//========================================================================== +const idEventDef EV_Vehicle_TractorBeam( "tractorBeam" ); +const idEventDef EV_Vehicle_TractorBeamDone( "tractorBeamDone" ); + +CLASS_DECLARATION(hhVehicle, hhShuttle) + EVENT( EV_PostSpawn, hhShuttle::Event_PostSpawn ) + EVENT( EV_Vehicle_FireCannon, hhShuttle::Event_FireCannon ) + EVENT( EV_Vehicle_TractorBeam, hhShuttle::Event_ActivateTractorBeam ) + EVENT( EV_Vehicle_TractorBeamDone, hhShuttle::Event_DeactivateTractorBeam ) +END_CLASS + + +hhShuttle::~hhShuttle() { + if (renderEntity.gui[0]) { + gameLocal.FocusGUICleanup(renderEntity.gui[0]); //HUMANHEAD rww + uiManager->DeAlloc(renderEntity.gui[0]); + renderEntity.gui[0] = NULL; + } +} + +void hhShuttle::Spawn() { + bSputtering = false; + malfunctionThrustFactor = spawnArgs.GetFloat( "malfunctionThrustFactor" ); + + idealTipState = TIP_STATE_NONE; + curTipState = TIP_STATE_NONE; + nextTipChange = gameLocal.time; + + // Fade console in + SetShaderParm(4, -MS2SEC(gameLocal.time)); // time + SetShaderParm(5, -1.0f); // Dir + + teleportDest = GetOrigin() + idVec3(-64, 0, 0) * GetPhysics()->GetAxis(); + teleportAngles = idAngles( 0.0f, GetPhysics()->GetAxis().ToAngles().yaw, 0.0f ); + + terminalVelocitySquared = spawnArgs.GetFloat( "terminalvelocity" ); + terminalVelocitySquared *= terminalVelocitySquared; + + noTractorTime = 0; //rww + + fl.clientEvents = true; //rww - allow events to be posted on client for this entity + + PostEventMS( &EV_PostSpawn, 0 ); + + fl.networkSync = true; +} + +void hhShuttle::Event_PostSpawn() { + // Spawn all sub-entities after initial population for multiplayer's sake + + // Spawn components for tractor beam + const idKeyValue *kv1 = spawnArgs.FindKey("attackFunc"); + const idKeyValue *kv2 = spawnArgs.FindKey("altAttackFunc"); + if ((kv1 && !kv1->GetValue().Icmp("tractorBeam")) || (kv2 && !kv2->GetValue().Icmp("tractorBeam")) ) { + tractor.SpawnComponents( this ); + } + + // Spawn Model based thrusters + const char *thrusterDef = spawnArgs.GetString("def_thruster"); + if (thrusterDef && *thrusterDef) { + // Spawn thrusters with appropriate direction vectors + idVec3 offset; + char suffix[THRUSTER_DIRECTIONS] = { 'F', 'B', 'L', 'R', 'U', 'D' }; + int directions[THRUSTER_DIRECTIONS] = { MASK_POSX, MASK_NEGX, MASK_POSY, MASK_NEGY, MASK_POSZ, MASK_NEGZ }; + for (int i=0; iWriteStaticObject( tractor ); + for (int i=0; iWriteFloat( terminalVelocitySquared ); + savefile->WriteVec3( teleportDest ); + savefile->WriteAngles( teleportAngles ); + savefile->WriteFloat( malfunctionThrustFactor ); + savefile->WriteBool( bSputtering ); + savefile->WriteInt(noTractorTime); + savefile->WriteInt( idealTipState ); + savefile->WriteInt( curTipState ); + savefile->WriteInt( nextTipChange ); +} + +void hhShuttle::Restore( idRestoreGame *savefile ) { + savefile->ReadStaticObject( tractor ); + for (int i=0; iReadFloat( terminalVelocitySquared ); + savefile->ReadVec3( teleportDest ); + savefile->ReadAngles( teleportAngles ); + savefile->ReadFloat( malfunctionThrustFactor ); + savefile->ReadBool( bSputtering ); + savefile->ReadInt(noTractorTime); + savefile->ReadInt( idealTipState ); + savefile->ReadInt( curTipState ); + savefile->ReadInt( nextTipChange ); +} + +void hhShuttle::WriteToSnapshot( idBitMsgDelta &msg ) const { + hhVehicle::WriteToSnapshot(msg); + + /* + int i; + for (i = 0; i < THRUSTER_DIRECTIONS; i++) { + msg.WriteBits(thrusters[i].GetSpawnId(), 32); + } + */ + + msg.WriteFloat(renderEntity.shaderParms[4]); + msg.WriteFloat(renderEntity.shaderParms[5]); + + msg.WriteBits(bSputtering, 1); + msg.WriteBits(noTractorTime, 32); + + tractor.WriteToSnapshot(msg); +} + +void hhShuttle::ReadFromSnapshot( const idBitMsgDelta &msg ) { + hhVehicle::ReadFromSnapshot(msg); + + /* + int i; + for (i = 0; i < THRUSTER_DIRECTIONS; i++) { + int spawnId = msg.ReadBits(32); + if (!spawnId) { + thrusters[i] = NULL; + } + else { + thrusters[i].SetSpawnId(spawnId); + } + } + */ + + SetShaderParm(4, msg.ReadFloat()); + SetShaderParm(5, msg.ReadFloat()); + + bSputtering = !!msg.ReadBits(1); + noTractorTime = msg.ReadBits(32); + + tractor.ReadFromSnapshot(msg); +} + +void hhShuttle::ClientPredictionThink( void ) { + if (fl.hidden) { + return; + } + + Think(); + + UpdateVisuals(); + Present(); +} + + +// These for model based thrusters ------------------------------------------------ + +hhVehicleThruster *hhShuttle::SpawnThruster(idVec3 &offset, idVec3 &dir, const char *thrusterName, bool master) { + idVec3 pos = GetOrigin() + offset * GetAxis(); + idVec3 direction = dir * GetAxis(); + idMat3 axis = direction.ToMat3(); + + idDict args; + args.SetVector("origin", pos); + args.SetBool("soundmaster", master); + hhVehicleThruster *thruster; + if (gameLocal.isClient) { + thruster = static_cast(gameLocal.SpawnClientObject(thrusterName, &args)); + } + else { + thruster = static_cast(gameLocal.SpawnObject(thrusterName, &args)); + } + if (thruster) { + thruster->fl.networkSync = false; + + thruster->SetOrigin(pos); + thruster->SetAxis(axis); + thruster->Bind(this, true); + thruster->SetOwner(this); + thruster->Hide(); + thruster->SetSmoker(master, offset, dir); + } + return thruster; +} + +void hhShuttle::FireThrusters( const idVec3& impulse ) { + idVec3 curVel = physicsObj.GetLinearVelocity(); + idVec3 excess( vec3_zero ); + idVec3 Iu( vec3_zero ); + idVec3 Vu( vec3_zero ); + float impulseLength = 0.0f; + idVec3 finalImpulse = impulse; + + // Subtract portion of impulse out that is in direction of current terminal velocity + //FIXME: not sure if this should be in the physics code + if( !IsNoClipping() && curVel.LengthSqr() > terminalVelocitySquared ) { + Iu = finalImpulse; + impulseLength = Iu.Normalize(); + Vu = curVel; + Vu.Normalize(); + excess = (impulseLength * (Iu * Vu)) * Vu; + finalImpulse -= excess; + } + + // Apply wacky controls when dying + if (health <= 0) { + finalImpulse += hhUtils::RandomVector() * 127.0f * malfunctionThrustFactor; + } + + if (finalImpulse.Length() > VECTOR_EPSILON) { + ConsumePower(thrusterCost); + } + + // Play sputter sound if trying to move without enough power + if (!HasPower(thrusterCost)) { + if (finalImpulse != vec3_origin && !bSputtering) { + StartSound("snd_sputter", SND_CHANNEL_ANY); + bSputtering = true; + } + else { + bSputtering = false; + } + + finalImpulse.Zero(); + } + + ApplyImpulse( finalImpulse ); +} + +void hhShuttle::ApplyBoost( float magnitude ) { + ApplyImpulse( GetAxis()[0] * magnitude ); +} + +// Update all tick based effects +void hhShuttle::UpdateEffects( const idVec3& localThrust ) { + // Update thrusters on/off state based on direction of velocity vector + int directionMask = localThrust.DirectionMask(); + if ( GetPilot() && GetPilot()->IsType( idAI::Type ) ) { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + if ( -GetAxis()[2] * vel > 0 ) { + thrusters[THRUSTER_TOP]->SetThruster( true ); + thrusters[THRUSTER_TOP]->Update( localThrust ); + } else { + thrusters[THRUSTER_TOP]->SetThruster( false ); + thrusters[THRUSTER_TOP]->Update( localThrust ); + } + if ( GetAxis()[2] * vel > 0 ) { + thrusters[THRUSTER_BOTTOM]->SetThruster( true ); + thrusters[THRUSTER_BOTTOM]->Update( localThrust ); + } else { + thrusters[THRUSTER_BOTTOM]->SetThruster( false ); + thrusters[THRUSTER_BOTTOM]->Update( localThrust ); + } + if ( GetAxis()[1] * vel > 0 ) { + thrusters[THRUSTER_RIGHT]->SetThruster( true ); + thrusters[THRUSTER_RIGHT]->Update( localThrust ); + } else { + thrusters[THRUSTER_RIGHT]->SetThruster( false ); + thrusters[THRUSTER_RIGHT]->Update( localThrust ); + } + if ( -GetAxis()[1] * vel > 0 ) { + thrusters[THRUSTER_LEFT]->SetThruster( true ); + thrusters[THRUSTER_LEFT]->Update( localThrust ); + } else { + thrusters[THRUSTER_LEFT]->SetThruster( false ); + thrusters[THRUSTER_LEFT]->Update( localThrust ); + } + } else { + for (int thrusterIndex=0; thrusterIndexSetThruster( INDEX_IN_MASK(directionMask, thrusterIndex) ); + thrusters[thrusterIndex]->Update( localThrust ); + } + } + } +} + +// Static function to determine if a pilot is suitable +bool hhShuttle::ValidPilot( idActor *act ) { + if (act && act->health > 0) { + if (act->IsType(hhPlayer::Type)) { + hhPlayer *player = static_cast(act); + if( !player->IsSpiritOrDeathwalking() && !player->IsPossessed() ) { + return true; + } + } + else if (act->IsType(idAI::Type)) { + idAI *ai = static_cast(act); + if (ai->spawnArgs.GetBool("canPilotShuttle")) { + return true; + } + } + } + return false; +} + +bool hhShuttle::WillAcceptPilot( idActor *act ) { + return IsConsole() && hhShuttle::ValidPilot( act ) && CanBecomeVehicle(act); +} + +void hhShuttle::AcceptPilot( hhPilotVehicleInterface* pilotInterface ) { + hhVehicle::AcceptPilot( pilotInterface ); + + // Fade the console out + SetShaderParm(4, -MS2SEC(gameLocal.time)); // time + SetShaderParm(5, 1.0f); // dir + + // Alert the hud that control has been taken (currently unused) + if (renderEntity.gui[0]) { + renderEntity.gui[0]->Trigger(gameLocal.time); + } +} + +void hhShuttle::EjectPilot() { + + // Fade ship out + SetShaderParm(4, -MS2SEC(gameLocal.time)); // time + SetShaderParm(5, -1.0f); // dir + + tractor.RequestState(false); + + for (int ix=0; ixSetDying(false); + thrusters[ix]->SetThruster(false); + thrusters[ix]->Update(vec3_origin); + } + } + + hhVehicle::EjectPilot(); +} + +void hhShuttle::ProcessPilotInput( const usercmd_t* cmds, const idAngles* viewAngles ) { + hhVehicle::ProcessPilotInput( cmds, viewAngles ); + + if( cmds && IsVehicle() ) { + UpdateEffects( idVec3(cmds->forwardmove, -cmds->rightmove, cmds->upmove) ); + } +} + +/* +bool hhShuttle::UsesCrosshair() const { + return (!IsDocked() || dock->AllowsFiring()) && hhVehicle::UsesCrosshair(); +} +*/ + +void hhShuttle::Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location) { + for(int ix=0; ixSetDying(true); + } + } + + hhVehicle::Killed(inflictor, attacker, damage, dir, location); +} + +void hhShuttle::DrawHUD( idUserInterface* _hud ) { + if( !_hud ) { + return; + } + + float spawnPower = spawnArgs.GetInt( "maxPower" ); + bool bDocked = dock.IsValid(); + bool bDockedInTransporter = bDocked && dock->IsType(hhShuttleTransport::Type); + bool bDockLocked = bDocked && dock->IsLocked(); + bool bNeedsRecharge = (health < spawnHealth) || (currentPower < spawnPower); + _hud->SetStateBool( "docked", bDocked ); + _hud->SetStateBool( "dockedtransporter", bDockedInTransporter ); + _hud->SetStateBool( "docklocked", bDockLocked ); + _hud->SetStateBool( "recharging", bNeedsRecharge && bDocked && dock->Recharges() ); + _hud->SetStateBool( "crosshair", fireController && fireController->UsesCrosshair() ); + _hud->SetStateBool( "lowhealth", health/(float)spawnHealth < 0.20f ); + _hud->SetStateBool( "lowpower", currentPower/spawnPower < 0.20f ); + + float velfrac = physicsObj.GetLinearVelocity().LengthSqr() / terminalVelocitySquared; +// float velfrac = controller.GetThrustBooster(); + velfrac = idMath::ClampFloat( 0.0f, 1.0f, velfrac ); + _hud->SetStateFloat( "velocityfraction", velfrac ); + + bool bHasTractor = tractor.Exists(); + bool bTractorActive = tractor.IsActive(); + bool bTractorLocked = tractor.IsLocked(); + bool bTractorSighted = tractor.IsAllowed() && (tractor.GetTraceTarget() != NULL); + bool bTractorAllowed = tractor.IsAllowed(); + _hud->SetStateBool( "hastractor", bHasTractor ); + _hud->SetStateBool( "tractoractive", bTractorActive ); + _hud->SetStateBool( "tractorsighted", bTractorSighted ); + _hud->SetStateBool( "tractorlocked", bTractorLocked ); + _hud->SetStateBool( "tractorallowed", bTractorAllowed ); + + // HUMANHEAD PCF pdm 05-18-06: Removed non-octal mass from shuttle hud +/* if( bHasTractor && bTractorLocked ) { + float mass = tractor.GetTarget()->GetPhysics()->GetMass(); + _hud->SetStateInt("tractormass", (int) mass); + const char *massFormatter = common->GetLanguageDict()->GetString("#str_41161"); + _hud->SetStateString("tractormasstext", va(massFormatter, (int)mass)); + }*/ + // HUMANHEAD END + + if (GetPilot() && GetPilot()->IsType(hhPlayer::Type)) { + + if (!g_tips.GetBool()) { + idealTipState = TIP_STATE_NONE; + nextTipChange = gameLocal.time; + } + + // Transition to desired tip state, always passing through TIP_STATE_NONE + if ( gameLocal.time >= nextTipChange && idealTipState != curTipState ) { + + if (curTipState != TIP_STATE_NONE) { // Turn off old tip + _hud->HandleNamedEvent( "shuttleTipWindowDown" ); + nextTipChange = gameLocal.time + 300; + curTipState = TIP_STATE_NONE; + } + else { // Turn on new tip + const char *tip = NULL; + switch(idealTipState) { + case TIP_STATE_NONE: + break; + case TIP_STATE_DOCKED: + gameLocal.SetTip(_hud, NULL, "", NULL, NULL, "tip1"); + tip = spawnArgs.GetString("text_exittip"); + gameLocal.SetTip(_hud, "_attackalt", tip, NULL, NULL, "tip2"); + _hud->HandleNamedEvent( "shuttleTipWindowUp" ); + curTipState = idealTipState; + break; + case TIP_STATE_UNDOCKED: + tip = spawnArgs.GetString("text_cannontip"); + gameLocal.SetTip(_hud, "_attack", tip, NULL, NULL, "tip1"); + tip = spawnArgs.GetString("text_tractortip"); + gameLocal.SetTip(_hud, "_attackalt", tip, NULL, NULL, "tip2"); + _hud->HandleNamedEvent( "shuttleTipWindowUp" ); + curTipState = idealTipState; + + // Go away in a little while + idealTipState = TIP_STATE_NONE; + nextTipChange = gameLocal.time + 3000; + break; + } + } + + } + } + + _hud->StateChanged(gameLocal.time); + hhVehicle::DrawHUD( _hud ); +} + +void hhShuttle::SetDock( const hhDock* dock ) { + hhVehicle::SetDock(dock); + + bool bDocked = (dock != NULL); + bool bDockedInTransporter = bDocked && dock->IsType(hhShuttleTransport::Type); + + if (bDocked && !bDockedInTransporter) { + // Just entered dock, display tip + idealTipState = TIP_STATE_DOCKED; + nextTipChange = gameLocal.time; + + // Just entered dock, set this one as my respawn point + idVec3 location = dock->GetOrigin() + dock->spawnArgs.GetVector("offset_console") * dock->GetAxis(); + teleportDest = location + idVec3(-64, 0, 0) * GetPhysics()->GetAxis(); + teleportAngles = idAngles( 0.0f, dock->GetAxis().ToAngles().yaw, 0.0f ); + } +} + +void hhShuttle::Undock() { + hhVehicle::Undock(); + + // Just left dock, display tip + idealTipState = TIP_STATE_UNDOCKED; + nextTipChange = gameLocal.time; +} + +void hhShuttle::Ticker() { + // Handle noclipping + physicsObj.SetContents(IsNoClipping() ? 0 : vehicleContents ); + physicsObj.SetClipMask(IsNoClipping() ? 0 : vehicleClipMask ); + + if( IsVehicle() && IsDocked() ) { + dock->UpdateAxis( GetAxis() ); + } + + tractor.Update(); +} + +void hhShuttle::RemoveVehicle() { + if (IsDocked()) { + dock->ShuttleExit(this); + dock = NULL; + } + GetPhysics()->SetGravity( vec3_origin ); // so shuttle doesn't bounce on ground and make noise + hhVehicle::RemoveVehicle(); +} + +void hhShuttle::SetConsoleModel() { + hhVehicle::SetConsoleModel(); + + // Fade console in + SetShaderParm(4, -MS2SEC(gameLocal.time)); // time + SetShaderParm(5, -1.0f); // Dir + // Need to add gui back(SetModel strips it) + if (!renderEntity.gui[0]) { //rww - don't double up guis + renderEntity.gui[0] = uiManager->FindGui(spawnArgs.GetString("gui"), true, true); + } +} + +void hhShuttle::SetVehicleModel() { + hhVehicle::SetVehicleModel(); + + // Fade shuttle in + SetShaderParm(4, -MS2SEC(gameLocal.time)); // time + SetShaderParm(5, 1.0f); // Dir + validThrustTime = gameLocal.time + spawnArgs.GetInt("delay_thrust"); +} + +void hhShuttle::RecoverFromDying() { + StopSound(SND_CHANNEL_DYING); + CancelEvents(&EV_VehicleExplode); + if (domelight.IsValid()) { + domelight->SetLightParm(7, 0.0f); + } + for(int ix=0; ixSetDying(false); + } + } +} + +void hhShuttle::PerformDeathAction(int deathAction, idActor *savedPilot, idEntity *attacker, idVec3 &dir) { + switch(deathAction) { + case 2: // Teleport pilot + if (!gameLocal.isMultiplayer) { //rww - fall through on purpose + if (savedPilot->IsType(hhPlayer::Type)) { + idVec3 origin; + idMat3 axis; + idVec3 viewDir; + idAngles angles; + idMat3 eyeAxis; + static_cast(savedPilot)->GetResurrectionPoint( origin, axis, viewDir, angles, eyeAxis, savedPilot->GetPhysics()->GetAbsBounds(), teleportDest, teleportAngles.ToMat3(), teleportAngles.ToForward(), teleportAngles ); + static_cast(savedPilot)->DeathWalk( origin, axis, viewDir.ToMat3(), angles, eyeAxis ); + } + else { + savedPilot->Teleport(teleportDest, teleportAngles, NULL); + } + break; + } + default: + hhVehicle::PerformDeathAction(deathAction, savedPilot, attacker, dir); + break; + } +} + +void hhShuttle::InitiateRecharging() { +} + +void hhShuttle::FinishRecharging() { + if (GetPilot() != NULL) { + fl.takedamage = true; + } +} + +void hhShuttle::Event_FireCannon() { + if (IsDocked() && GetPilot() && GetPilot()->IsType(idAI::Type) && !dock->AllowsFiring()) { + // Let monsters boost out of docks a bit + ApplyBoost( 127.0f * 0.25f ); + return; + } + + if( IsDocked() && spawnArgs.GetBool("dockBoost") && !dock->AllowsFiring() ) { + ApplyBoost( 127.0f * dockBoostFactor ); + return; + } + if( spawnArgs.GetBool("tractorlaunch") && GetTractorTarget() ) { + LaunchTractorTarget(spawnArgs.GetFloat("launch_speed")); + bDisallowAttackUntilRelease = true; + bDisallowAltAttackUntilRelease = true; + return; + } + if ( !IsDocked() || dock->AllowsFiring()) { + hhVehicle::Event_FireCannon(); + } +} + +void hhShuttle::Event_ActivateTractorBeam() { + tractor.RequestState( true ); +} + +void hhShuttle::Event_DeactivateTractorBeam() { + tractor.RequestState( false ); +} diff --git a/src/Prey/game_shuttle.h b/src/Prey/game_shuttle.h new file mode 100644 index 0000000..382f68f --- /dev/null +++ b/src/Prey/game_shuttle.h @@ -0,0 +1,152 @@ +#ifndef __GAME_SHUTTLE_H__ +#define __GAME_SHUTTLE_H__ + +class hhBindController; +class hhShuttle; + +#define TIP_STATE_NONE 0 +#define TIP_STATE_DOCKED 1 +#define TIP_STATE_UNDOCKED 2 + +//========================================================================== +// +// hhTractorBeam +// +//========================================================================== + +class hhTractorBeam : public idClass { + CLASS_PROTOTYPE( hhTractorBeam ); + + friend hhShuttle; + +public: + hhTractorBeam(); + void SpawnComponents( hhShuttle *shuttle ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - network code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + bool Exists(); + bool IsAllowed(); + bool IsActive(); + bool IsLocked(); + + idEntity * GetTarget(); + void SetAllow( bool allow ) { bAllow = allow; } + void Update(); + idEntity * GetTraceTarget() { return traceTarget.GetEntity(); } + void RequestState( bool wantsActive ); + idEntity * TraceForTarget( trace_t &results ); + void LaunchTarget(float speed); + +protected: + void SetOwner( hhShuttle *shuttle ) { owner = shuttle; } + void Activate(); + void Deactivate(); + void AttachTarget( idEntity *ent, trace_t &results ); + bool ValidTarget( idEntity *ent ); + void ReleaseTarget(); + +protected: + idEntityPtr owner; + idEntityPtr beam; + idEntityPtr traceTarget; // Results of target trace this frame + idEntityPtr bindController; // Bind controller for tractor beam + idEntityPtr traceController; // Point for traces + hhForce_Converge feedbackForce; // feedback force applied to shuttle + int tractorCost; // Power cost of tractor at 10 Hz + bool bAllow; // false if tractor beam is not allowed + bool bActive; // whether beam is active + int beamClientPredictTime; //rww - don't flicker the beam due to snapshot timings + idVec3 offsetTraceStart; // Offset to start of trace + idVec3 offsetTraceEnd; // Offset to end of trace + idVec3 offsetEquilibrium; // Offset to equilibrium point for targets + idVec3 targetLocalOffset; // Offset to attach point in entity local coordinates + int targetID; // Body ID of target entity +}; + +//========================================================================== +// +// hhShuttle +// +//========================================================================== + +class hhShuttle : public hhVehicle { + CLASS_PROTOTYPE( hhShuttle ); + + friend hhTractorBeam; + +public: + virtual ~hhShuttle(); + + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - network code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + + idEntity * GetTractorTarget() { return tractor.GetTarget(); } + void ReleaseTractorTarget() { tractor.ReleaseTarget(); } + void AllowTractor( bool allow ) { tractor.SetAllow(allow); } + virtual bool TractorIsActive(void) { return tractor.IsActive(); } //rww + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void PerformDeathAction( int deathAction, idActor *savedPilot, idEntity *attacker, idVec3 &dir ); + void LaunchTractorTarget(float mag) { tractor.LaunchTarget(mag); } + + // Static pilot assessor, for queries when you don't have a vehicle instantiated + static bool ValidPilot( idActor *act ); + + virtual bool WillAcceptPilot( idActor *act ); + virtual void ProcessPilotInput( const usercmd_t* cmds, const idAngles* viewAngles ); + virtual void AcceptPilot( hhPilotVehicleInterface* pilotInterface ); + virtual void EjectPilot(); + + virtual void FireThrusters( const idVec3& impulse ); + void ApplyBoost( float magnitude ); + + virtual void DrawHUD( idUserInterface* _hud ); + + virtual void RemoveVehicle(); + + virtual void InitiateRecharging(); + virtual void FinishRecharging(); + virtual void SetDock( const hhDock* dock ); + virtual void Undock(); + void RecoverFromDying(); + + int noTractorTime; //HUMANHEAD rww +protected: + virtual void Ticker(); + hhVehicleThruster * SpawnThruster( idVec3 &offset, idVec3 &dir, const char *thrusterName, bool master ); + void UpdateEffects( const idVec3& impulse ); + + virtual void SetConsoleModel(); + virtual void SetVehicleModel(); + +protected: + void Event_PostSpawn(); + virtual void Event_FireCannon(); + void Event_ActivateTractorBeam(); + void Event_DeactivateTractorBeam(); + +protected: + hhTractorBeam tractor; + idEntityPtr thrusters[ THRUSTER_DIRECTIONS ]; + float terminalVelocitySquared; // Maximum speed of vehicle + idVec3 teleportDest; // teleport destination upon death + idAngles teleportAngles; // teleport destination upon death + float malfunctionThrustFactor; + bool bSputtering; + int idealTipState; // desired tip state + int curTipState; // current tip state + int nextTipChange; // next time a tip change can happen +}; + +#endif diff --git a/src/Prey/game_shuttledock.cpp b/src/Prey/game_shuttledock.cpp new file mode 100644 index 0000000..b18898f --- /dev/null +++ b/src/Prey/game_shuttledock.cpp @@ -0,0 +1,454 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +//----------------------------------------------------------------------- +// +// hhShuttleDock +// +//----------------------------------------------------------------------- +const idEventDef EV_TrySpawnConsole("", NULL); + +CLASS_DECLARATION(hhDock, hhShuttleDock) + EVENT( EV_TrySpawnConsole, hhShuttleDock::Event_SpawnConsole) + EVENT( EV_Remove, hhShuttleDock::Event_Remove) + EVENT( EV_PostSpawn, hhShuttleDock::Event_PostSpawn ) +END_CLASS + +hhShuttleDock::hhShuttleDock() { + dockingBeam = NULL; + dockedShuttle = NULL; + shuttleCount = 0; + lastConsoleAttempt = 0; + bLocked = false; + bPlayingRechargeSound = false; +} + +void hhShuttleDock::Spawn() { + + amountHealth = spawnArgs.GetInt("amounthealth"); + amountPower = spawnArgs.GetInt("amountpower"); + + bCanExitLocked = spawnArgs.GetBool("canExitLocked"); + bLockOnEntry = spawnArgs.GetBool("lockOnEntry"); + offsetNozzle = spawnArgs.GetVector("offset_nozzle"); + offsetConsole = spawnArgs.GetVector("offset_console"); + offsetShuttlePoint = spawnArgs.GetVector("offset_shuttlepoint"); + maxDistance = spawnArgs.GetFloat("maxdistance"); + + dockingForce.SetRestoreFactor(spawnArgs.GetFloat("dockingforce")); + dockingForce.SetTarget(GetOrigin() + offsetShuttlePoint * GetAxis()); + + if (maxDistance) { + float shuttleMass = dockedShuttle->GetPhysics()->GetMass(); + float weaponKick = dockedShuttle->GetWeaponRecoil(); + float fireRate = dockedShuttle->GetWeaponFireDelay(); + float shuttleThrust = dockedShuttle->GetThrustFactor() + (weaponKick / 128.0f * fireRate); + float restorationFactor = 128.0f * shuttleMass * shuttleThrust / maxDistance; + restorationFactor *= 60.0f; // hack factor to allow for cannon recoil and inaccuracy + dockingForce.SetRestoreFactor(restorationFactor); + } + + GetPhysics()->SetContents(CONTENTS_SOLID); + + // Start with dock inactive (faded in) + SetShaderParm(4, -MS2SEC(gameLocal.time-10000)); + SetShaderParm(5, -1); + + fl.clientEvents = true; + + CancelEvents(&EV_PostSpawn); // hhDock may have already posted one + PostEventMS( &EV_PostSpawn, 0 ); + + fl.networkSync = true; //rww +} + +void hhShuttleDock::Event_PostSpawn() { + dockingBeam = SpawnDockingBeam(offsetNozzle); + if (!gameLocal.isClient) { + hhDock::Event_PostSpawn(); + } +} + +void hhShuttleDock::Save(idSaveGame *savefile) const { + savefile->WriteVec3( offsetNozzle ); + savefile->WriteVec3( offsetConsole ); + savefile->WriteVec3( offsetShuttlePoint ); + dockedShuttle.Save( savefile ); + dockingBeam.Save( savefile ); + savefile->WriteStaticObject( dockingForce ); + savefile->WriteInt( shuttleCount ); + savefile->WriteInt( lastConsoleAttempt ); + savefile->WriteInt( amountHealth ); + savefile->WriteInt( amountPower ); + savefile->WriteFloat( maxDistance ); + savefile->WriteBool( bLocked ); + savefile->WriteBool( bLockOnEntry ); + savefile->WriteBool( bCanExitLocked ); + savefile->WriteBool( bPlayingRechargeSound ); +} + +void hhShuttleDock::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( offsetNozzle ); + savefile->ReadVec3( offsetConsole ); + savefile->ReadVec3( offsetShuttlePoint ); + dockedShuttle.Restore( savefile ); + dockingBeam.Restore( savefile ); + savefile->ReadStaticObject( dockingForce ); + savefile->ReadInt( shuttleCount ); + savefile->ReadInt( lastConsoleAttempt ); + savefile->ReadInt( amountHealth ); + savefile->ReadInt( amountPower ); + savefile->ReadFloat( maxDistance ); + savefile->ReadBool( bLocked ); + savefile->ReadBool( bLockOnEntry ); + savefile->ReadBool( bCanExitLocked ); + savefile->ReadBool( bPlayingRechargeSound ); +} + +void hhShuttleDock::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteBits(dockingZone.GetSpawnId(), 32); + msg.WriteBits(dockedShuttle.GetSpawnId(), 32); + //msg.WriteBits(dockingBeam.GetSpawnId(), 32); + bool dockingBeamActive = false; + if (dockingBeam.IsValid()) { + dockingBeamActive = dockingBeam->IsActivated(); + } + msg.WriteBits(dockingBeamActive, 1); +} + +void hhShuttleDock::ReadFromSnapshot( const idBitMsgDelta &msg ) { + int spawnId; + + spawnId = msg.ReadBits(32); + if (!spawnId) { + dockingZone = NULL; + } + else { + dockingZone.SetSpawnId(spawnId); + } + + idEntityPtr newShuttle; + + spawnId = msg.ReadBits(32); + if (!spawnId) { + newShuttle = NULL; + } + else { + newShuttle.SetSpawnId(spawnId); + } + + if (newShuttle != dockedShuttle) { + if (dockedShuttle.IsValid()) { + DetachShuttle(dockedShuttle.GetEntity()); + dockedShuttle = NULL; + } + if (newShuttle.IsValid() && !newShuttle->IsHidden()) { + AttachShuttle(newShuttle.GetEntity()); + } + } + + bool dockingBeamActive = !!msg.ReadBits(1); + if (dockingBeam.IsValid()) { + if (dockingBeamActive != dockingBeam->IsActivated()) { + dockingBeam->Activate(dockingBeamActive); + } + } +} + +void hhShuttleDock::ClientPredictionThink( void ) { + //hhDock::ClientPredictionThink(); + if (dockedShuttle.IsValid() && dockedShuttle.GetEntity()) { //hax + BecomeActive(TH_THINK); + } + else { + BecomeInactive(TH_THINK); + } + Think(); +} + +void hhShuttleDock::SpawnConsole() { + idVec3 location = GetOrigin() + offsetConsole * GetAxis(); + + StartSound("snd_spawn", SND_CHANNEL_ANY); + + if ( !gameLocal.isClient ) { + idDict args; + args.SetVector("origin", location); + args.SetMatrix("rotation", GetAxis()); + args.Set("startDock", name.c_str()); + if (gameLocal.isMultiplayer) { //rww + const char *shuttleDef = spawnArgs.GetString("def_shuttle_mp"); + if (shuttleDef && shuttleDef[0]) { + gameLocal.SpawnObject(shuttleDef, &args); + return; + } + } + gameLocal.SpawnObject(spawnArgs.GetString("def_shuttle"), &args); + } +} + +hhBeamSystem *hhShuttleDock::SpawnDockingBeam(idVec3 &offset) { + idVec3 pos = GetPhysics()->GetOrigin() + offset * GetPhysics()->GetAxis(); + hhBeamSystem *beam = hhBeamSystem::SpawnBeam(pos, spawnArgs.GetString("beam_docking"), mat3_identity, true); + assert(beam); + beam->fl.networkSync = false; + beam->SetOrigin(pos); + beam->SetAxis( GetPhysics()->GetAxis()[2].ToMat3() ); + beam->Bind(this, false); + beam->Activate( false ); + return beam; +} + +bool hhShuttleDock::ValidEntity(idEntity *ent) { + if (ent->IsType(hhPlayer::Type)) { + int junk=0; + } + if (gameLocal.isMultiplayer) { //rww + return ( !idStr::Icmp(ent->GetEntityDefName(), spawnArgs.GetString("def_shuttle_mp")) && !ent->IsHidden() ); + } + return ( !idStr::Icmp(ent->GetEntityDefName(), spawnArgs.GetString("def_shuttle")) && !ent->IsHidden() ); +} + +void hhShuttleDock::EntityEntered(idEntity *ent) { + if (ent->IsType(hhShuttle::Type) && !dockedShuttle.IsValid()) { + shuttleCount++; + hhShuttle *shuttle = static_cast(ent); + if (shuttle->IsVehicle()) { + AttachShuttle(shuttle); + } + } +} + +void hhShuttleDock::EntityEncroaching(idEntity *ent) { + // If we contain a pilot, but no shuttle: spawn one + if (ent->IsType(idActor::Type) && !static_cast(ent)->InVehicle() && shuttleCount <= 0) { + if (gameLocal.time > lastConsoleAttempt) { + PostEventMS(&EV_TrySpawnConsole, 0); + lastConsoleAttempt = gameLocal.time + 100; + } + } + + else if (ent->IsType(hhShuttle::Type)) { + hhShuttle *shuttle = static_cast(ent); + + // Check for any shuttles that just became valid + if (!dockedShuttle.IsValid() && shuttle->IsVehicle()) { + AttachShuttle(shuttle); + } + + // Recharge docked shuttles + if (dockedShuttle == shuttle) { + if( shuttle->IsConsole() ) { + DetachShuttle( shuttle ); + } else { + shuttle->RecoverFromDying(); // Recover every time, just to be sure + if (shuttle->IsDamaged() || shuttle->NeedsPower()) { + + //HUMANHEAD bjk PCF (4-27-06) - shuttle recharge was slow + if(USERCMD_HZ == 30) { + shuttle->GiveHealth(2*amountHealth); + shuttle->GivePower(2*amountPower); + } else { + shuttle->GiveHealth(amountHealth); + shuttle->GivePower(amountPower); + } + + if (!bPlayingRechargeSound) { + StartSound("snd_recharge", SND_CHANNEL_RECHARGE); + bPlayingRechargeSound = true; + } + } + else { + if (bPlayingRechargeSound) { + StopSound(SND_CHANNEL_RECHARGE); + bPlayingRechargeSound = false; + } + } + } + } + } +} + +void hhShuttleDock::EntityLeaving(idEntity *ent) { + if (ent->IsType(hhShuttle::Type)) { + shuttleCount--; + shuttleCount = idMath::ClampInt(0, shuttleCount, shuttleCount); + if (dockedShuttle == ent && !IsLocked()) { + DetachShuttle(dockedShuttle.GetEntity()); + } + } +} + +void hhShuttleDock::AttachShuttle(hhShuttle *shuttle) { + if (!dockedShuttle.IsValid()) { + assert(!shuttle->IsHidden()); + + dockedShuttle = shuttle; + dockingForce.SetEntity(dockedShuttle.GetEntity()); + dockingBeam->SetTargetEntity(dockedShuttle.GetEntity(), 0, dockedShuttle->spawnArgs.GetVector("offset_dockingpoint")); + dockingBeam->Activate( true ); + dockedShuttle->SetDock( this ); + + if (amountHealth || amountPower) { + if ( shuttle->GetPilot() && !shuttle->GetPilot()->IsType( idAI::Type ) ) { + dockedShuttle->InitiateRecharging(); + } + } + + StartSound("snd_looper", SND_CHANNEL_DOCKED); + + if (bLockOnEntry) { + Lock(); + } + + if (spawnArgs.GetBool("nonsolidwhenactive")) { + GetPhysics()->SetContents(0); + } + + ActivateTargets( shuttle ); // Fire triggers + BecomeActive(TH_THINK); + + // Dock is active, make dock fade out + SetShaderParm(4, -MS2SEC(gameLocal.time)); + SetShaderParm(5, 1); + } +} + +void hhShuttleDock::DetachShuttle(hhShuttle *shuttle) { + assert(dockingBeam.IsValid()); + assert(shuttle); + + dockingForce.SetEntity(NULL); + dockingBeam->SetTargetEntity(NULL); + dockingBeam->Activate( false ); + shuttle->Undock(); + + shuttle->FinishRecharging(); + dockedShuttle = NULL; + StopSound(SND_CHANNEL_DOCKED); + StopSound(SND_CHANNEL_RECHARGE); + bPlayingRechargeSound = false; + Unlock(); // Shouldn't get in here when locked except when a shuttle dies within + BecomeInactive(TH_THINK); // Keep thinking so the restore position is updated + + if (spawnArgs.GetBool("nonsolidwhenactive")) { + GetPhysics()->SetContents(CONTENTS_SOLID); + } + + // Dock is inactive, make dock fade in + SetShaderParm(4, -MS2SEC(gameLocal.time)); + SetShaderParm(5, -1); +} + +void hhShuttleDock::Think() { + hhDock::Think(); + if (thinkFlags & TH_THINK) { + // Apply docking force to shuttle if docked, even if not in zone + assert(dockedShuttle == dockingForce.GetEntity()); + idVec3 target = GetOrigin() + offsetShuttlePoint * GetAxis(); + dockingForce.SetTarget(target); + dockingForce.Evaluate(gameLocal.time); + + if (maxDistance) { + // Drop the player if forced outside of our threshold + float threshold = maxDistance * 1.5f; + if ( (dockingForce.GetEntity()->GetOrigin() - target).LengthSqr() >= threshold*threshold) { + // exit + dockedShuttle->EjectPilot(); + } + } + } +} + +void hhShuttleDock::ShuttleExit(hhShuttle *shuttle) { + DetachShuttle(shuttle); + + // NOTE: Since zones don't get exit messages for entities that are destroyed, this is needed to + // decrease the shuttle count manually. + // However, since the shuttle physics shrinks when released, it's possible to get "nearly" outside + // the zone before releasing, in which case the exit DOES get called, screwing up the count. + // So... we bound the count to zero. + shuttleCount--; + shuttleCount = idMath::ClampInt(0, shuttleCount, shuttleCount); +} + +bool hhShuttleDock::AllowsExit() { + return !bLocked || CanExitLocked(); +} + +void hhShuttleDock::Lock() { + bLocked = true; + dockingForce.SetRestoreFactor(spawnArgs.GetFloat("dockingforce_locked")); +} + +void hhShuttleDock::Unlock() { + bLocked = false; + dockingForce.SetRestoreFactor(spawnArgs.GetFloat("dockingforce")); +} + +void hhShuttleDock::Event_SpawnConsole() { + if ( !spawnArgs.GetBool( "consolespawn" ) ) { + return; + } + + // Check to make sure there's room + idEntity *touch[ MAX_GENTITIES ]; + idBounds bounds, localBounds; + + idVec3 location = GetOrigin() + offsetConsole * GetAxis(); + localBounds[0] = spawnArgs.GetVector("consoleMins"); + localBounds[1] = spawnArgs.GetVector("consoleMaxs"); + + if ( GetAxis().IsRotated() ) { + bounds.FromTransformedBounds( localBounds, location, GetAxis() ); + } + else { + bounds[0] = localBounds[0] + location; + bounds[1] = localBounds[1] + location; + } + + int num = gameLocal.clip.EntitiesTouchingBounds( bounds, CLIPMASK_VEHICLE, touch, MAX_GENTITIES ); + bool blocked = false; + for (int i=0; iGetPhysics()->GetContents() & CLIPMASK_VEHICLE)) { + blocked = true; + break; + } + } + + if (blocked) { + // Now do more expensive check to see if there's really something blocking us. + // All the EntitiesTouchingBounds() check does is check the extents. + // Make bounds into clip model, and use to test contents at our spawn location + idTraceModel trm; + trm.SetupBox( localBounds ); + idClipModel *clipModel = new idClipModel( trm ); + int contents = gameLocal.clip.Contents(location, clipModel, GetAxis(), CLIPMASK_VEHICLE, this); + delete clipModel; + blocked = contents != 0; + } + + if (!blocked) { + SpawnConsole(); + } +} + +void hhShuttleDock::Event_Remove() { + if (IsLocked()) { + Unlock(); + } + if (dockedShuttle.IsValid()) { + DetachShuttle(dockedShuttle.GetEntity()); + } + hhDock::Event_Remove(); +} + +hhShuttle *hhShuttleDock::GetDockedShuttle(void) { + if (!dockedShuttle.IsValid()) { + return NULL; + } + return dockedShuttle.GetEntity(); +} diff --git a/src/Prey/game_shuttledock.h b/src/Prey/game_shuttledock.h new file mode 100644 index 0000000..acfecff --- /dev/null +++ b/src/Prey/game_shuttledock.h @@ -0,0 +1,67 @@ + +#ifndef __GAME_SHUTTLEDOCK_H__ +#define __GAME_SHUTTLEDOCK_H__ + +class hhShuttleDock : public hhDock { +public: + CLASS_PROTOTYPE( hhShuttleDock ); + + hhShuttleDock(); + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - network code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + + virtual void Think(); + virtual bool ValidEntity(idEntity *ent); + virtual void EntityEntered(idEntity *ent); + virtual void EntityEncroaching(idEntity *ent); + virtual void EntityLeaving(idEntity *ent); + virtual void ShuttleExit(hhShuttle *shuttle); + virtual bool IsLocked() { return bLocked; } + virtual bool CanExitLocked() { return bCanExitLocked; } + virtual bool AllowsBoost() { return !bLocked; } + virtual bool AllowsFiring() { return false; } + virtual bool AllowsExit(); + virtual bool IsTeleportDest(){ return true; } + virtual void UpdateAxis( const idMat3 &newAxis ) { } + + virtual bool Recharges() const { return true; } + + virtual hhShuttle *GetDockedShuttle(void); + +protected: + void AttachShuttle(hhShuttle *shuttle); + void DetachShuttle(hhShuttle *shuttle); + hhBeamSystem * SpawnDockingBeam(idVec3 &offset); + void SpawnConsole(); + virtual void Lock(); + virtual void Unlock(); + + void Event_SpawnConsole(); + virtual void Event_Remove(); + virtual void Event_PostSpawn(); + +protected: + idVec3 offsetNozzle; + idVec3 offsetConsole; + idVec3 offsetShuttlePoint; + idEntityPtr dockedShuttle; + idEntityPtr dockingBeam; + hhForce_Converge dockingForce; + int shuttleCount; + int lastConsoleAttempt; + int amountHealth; + int amountPower; + float maxDistance; + bool bLocked; + bool bLockOnEntry; + bool bCanExitLocked; + bool bPlayingRechargeSound; +}; + +#endif diff --git a/src/Prey/game_shuttletransport.cpp b/src/Prey/game_shuttletransport.cpp new file mode 100644 index 0000000..79757f6 --- /dev/null +++ b/src/Prey/game_shuttletransport.cpp @@ -0,0 +1,244 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +//----------------------------------------------------------------------- +// +// hhShuttleTransport +// +//----------------------------------------------------------------------- + +const idEventDef EV_FadeOutTransporter("fadeOutTransporter"); + +CLASS_DECLARATION(hhDock, hhShuttleTransport) + EVENT( EV_DockLock, hhShuttleTransport::Event_Lock) + EVENT( EV_DockUnlock, hhShuttleTransport::Event_Unlock) + EVENT( EV_Activate, hhShuttleTransport::Event_Activate) + EVENT( EV_FadeOutTransporter, hhShuttleTransport::Event_FadeOut) + EVENT( EV_Remove, hhShuttleTransport::Event_Remove) +END_CLASS + +void hhShuttleTransport::Spawn() { + dockingBeam = NULL; + dockedShuttle = NULL; + shuttleCount = 0; + bLocked = false; + + amountHealth = spawnArgs.GetInt("amounthealth"); + amountPower = spawnArgs.GetInt("amountpower"); + + bCanExitLocked = spawnArgs.GetBool("canExitLocked"); + bLockOnEntry = spawnArgs.GetBool("lockOnEntry"); + bAllowFiring = spawnArgs.GetBool("allowFiring"); + offsetNozzle = spawnArgs.GetVector("offset_nozzle1"); +// offsetNozzle2 = spawnArgs.GetVector("offset_nozzle2"); + offsetShuttlePoint = spawnArgs.GetVector("offset_shuttlepoint"); + +// dockingBeam = SpawnDockingBeam(offsetNozzle); + + dockingForce.SetRestoreFactor(spawnArgs.GetFloat("dockingforce")); + dockingForce.SetTarget(GetOrigin() + offsetShuttlePoint * GetAxis()); + + // Fade in + SetShaderParm(SHADERPARM_TIMEOFFSET, -MS2SEC(gameLocal.time)); // Growth start time + SetShaderParm(5, 1.0f); // Growth direction (in) + SetShaderParm(6, 1.0f); // Make Beam opaque + StartSound("snd_fadein", SND_CHANNEL_ANY); +} + +void hhShuttleTransport::Save(idSaveGame *savefile) const { + savefile->WriteVec3( offsetNozzle ); + savefile->WriteVec3( offsetShuttlePoint ); + savefile->WriteObject( dockedShuttle ); + savefile->WriteObject( dockingBeam ); + savefile->WriteStaticObject( dockingForce ); + savefile->WriteInt( shuttleCount ); + savefile->WriteInt( amountHealth ); + savefile->WriteInt( amountPower ); + savefile->WriteBool( bLocked ); + savefile->WriteBool( bLockOnEntry ); + savefile->WriteBool( bCanExitLocked ); +} + +void hhShuttleTransport::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( offsetNozzle ); + savefile->ReadVec3( offsetShuttlePoint ); + savefile->ReadObject( reinterpret_cast(dockedShuttle) ); + savefile->ReadObject( reinterpret_cast(dockingBeam) ); + savefile->ReadStaticObject( dockingForce ); + savefile->ReadInt( shuttleCount ); + savefile->ReadInt( amountHealth ); + savefile->ReadInt( amountPower ); + savefile->ReadBool( bLocked ); + savefile->ReadBool( bLockOnEntry ); + savefile->ReadBool( bCanExitLocked ); +} + +hhBeamSystem *hhShuttleTransport::SpawnDockingBeam(idVec3 &offset) { + idVec3 pos = GetPhysics()->GetOrigin() + offset * GetPhysics()->GetAxis(); + hhBeamSystem *beam = hhBeamSystem::SpawnBeam(pos, spawnArgs.GetString("beam_docking")); + assert(beam); + beam->SetOrigin(pos); + beam->SetAxis(GetPhysics()->GetAxis()); + beam->Bind(this, false); + beam->Hide(); + return beam; +} + +bool hhShuttleTransport::ValidEntity(idEntity *ent) { + return (ent->IsType(hhShuttle::Type) && !ent->IsHidden()); +} + +void hhShuttleTransport::EntityEntered(idEntity *ent) { + if (ent->IsType(hhShuttle::Type)) { + shuttleCount++; + hhShuttle *shuttle = static_cast(ent); + if (shuttle->IsVehicle()) { + AttachShuttle(shuttle); + } + } +} + +void hhShuttleTransport::EntityEncroaching(idEntity *ent) { + + if (ent->IsType(hhShuttle::Type)) { + hhShuttle *shuttle = static_cast(ent); + + // Check for any shuttles that just became valid + if (dockedShuttle == NULL && shuttle->IsVehicle()) { + AttachShuttle(shuttle); + } + + // Recharge docked shuttles + if (shuttle == dockedShuttle) { + + //HUMANHEAD bjk PCF (4-27-06) - shuttle recharge was slow + if(USERCMD_HZ == 30) { + shuttle->GiveHealth(2*amountHealth); + shuttle->GivePower(2*amountPower); + } else { + shuttle->GiveHealth(amountHealth); + shuttle->GivePower(amountPower); + } + } + } +} + +void hhShuttleTransport::EntityLeaving(idEntity *ent) { + if (ent->IsType(hhShuttle::Type)) { + shuttleCount--; + shuttleCount = idMath::ClampInt(0, shuttleCount, shuttleCount); + if (ent == dockedShuttle && !IsLocked()) { + DetachShuttle(dockedShuttle); + } + } +} + +void hhShuttleTransport::AttachShuttle(hhShuttle *shuttle) { + if (dockedShuttle == NULL) { + dockedShuttle = shuttle; + dockingForce.SetEntity(dockedShuttle); + if (dockingBeam) { + dockingBeam->SetTargetEntity(dockedShuttle, 0, dockedShuttle->spawnArgs.GetVector("offset_dockingpoint")); + dockingBeam->Activate( true ); + } + + dockedShuttle->SetDock(this); + dockedShuttle->InitiateRecharging(); + StartSound("snd_getin", SND_CHANNEL_ANY); + StartSound("snd_looper", SND_CHANNEL_DOCKED); + + if (bLockOnEntry) { + Lock(); + } + ActivateTargets( shuttle ); // Fire triggers + BecomeActive(TH_THINK); + SetShaderParm(6, 0.0f); // Make beam transparent + } +} + +void hhShuttleTransport::DetachShuttle(hhShuttle *shuttle) { + assert (shuttle==dockedShuttle); + if (dockedShuttle != NULL) { + dockingForce.SetEntity(NULL); + if (dockingBeam) { + dockingBeam->SetTargetEntity(NULL); + dockingBeam->Activate( false ); + } + + dockedShuttle->SetDock(NULL); + dockedShuttle->FinishRecharging(); + dockedShuttle = NULL; + StartSound("snd_getout", SND_CHANNEL_ANY); + StopSound(SND_CHANNEL_DOCKED); + Unlock(); // Shouldn't get in here when locked except when a shuttle dies within + BecomeInactive(TH_THINK); // Keep thinking so the restore position is updated + SetShaderParm(6, 1.0f); // Make beam opaque + } +} + +void hhShuttleTransport::Think() { + hhDock::Think(); + if (thinkFlags & TH_THINK) { + // Apply docking force to shuttle if docked, even if not in zone + assert(dockingForce.GetEntity() == dockedShuttle); + dockingForce.SetTarget(GetOrigin() + offsetShuttlePoint * GetAxis()); + dockingForce.Evaluate(gameLocal.time); + } +} + +void hhShuttleTransport::ShuttleExit(hhShuttle *shuttle) { + DetachShuttle(shuttle); + + // NOTE: Since zones don't get exit messages for entities that are destroyed, this is needed to + // decrease the shuttle count manually. + // However, since the shuttle physics shrinks when released, it's possible to get "nearly" outside + // the zone before releasing, in which case the exit DOES get called, screwing up the count. + // So... we bound the count to zero. + shuttleCount--; + shuttleCount = idMath::ClampInt(0, shuttleCount, shuttleCount); +} + +void hhShuttleTransport::UpdateAxis( const idMat3 &newAxis ) { + // Yaw transport to match yaw of shuttle + idAngles ang = newAxis.ToAngles(); + ang.pitch = ang.roll = 0.0f; + SetAxis(ang.ToMat3()); +} + +void hhShuttleTransport::Lock() { + bLocked = true; + dockedShuttle->SetOrigin(GetOrigin() + offsetShuttlePoint * GetAxis()); + dockingForce.SetRestoreFactor(spawnArgs.GetFloat("dockingforce_locked")); + dockedShuttle->Bind(this, false); +} + +void hhShuttleTransport::Unlock() { + bLocked = false; + dockingForce.SetRestoreFactor(spawnArgs.GetFloat("dockingforce")); + if (dockedShuttle) { + dockedShuttle->Unbind(); + } +} + +void hhShuttleTransport::Event_FadeOut() { + // Fade out + SetShaderParm(SHADERPARM_TIMEOFFSET, -MS2SEC(gameLocal.time)); // Growth start time + SetShaderParm(5, -1.0f); // Growth direction (out) + SetShaderParm(6, 0.0f); // Make Beam translucent + StartSound("snd_fadeout", SND_CHANNEL_ANY); + + PostEventMS(&EV_Remove, 1000); +} + +void hhShuttleTransport::Event_Remove() { + if (IsLocked()) { + Unlock(); + } + if (dockedShuttle) { + DetachShuttle(dockedShuttle); + } + hhDock::Event_Remove(); +} diff --git a/src/Prey/game_shuttletransport.h b/src/Prey/game_shuttletransport.h new file mode 100644 index 0000000..efe0e8f --- /dev/null +++ b/src/Prey/game_shuttletransport.h @@ -0,0 +1,51 @@ + +#ifndef __GAME_SHUTTLETRANSPORT_H__ +#define __GAME_SHUTTLETRANSPORT_H__ + +class hhShuttleTransport : public hhDock { +public: + CLASS_PROTOTYPE( hhShuttleTransport ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think(); + virtual bool ValidEntity(idEntity *ent); + virtual void EntityEntered(idEntity *ent); + virtual void EntityEncroaching(idEntity *ent); + virtual void EntityLeaving(idEntity *ent); + virtual void ShuttleExit(hhShuttle *shuttle); + virtual bool IsLocked() { return bLocked; } + virtual bool CanExitLocked() { return bCanExitLocked; } + virtual bool AllowsBoost() { return false; } + virtual bool AllowsFiring() { return bAllowFiring; } + virtual bool AllowsExit() { return false; } + virtual bool IsTeleportDest(){ return false; } + virtual void UpdateAxis( const idMat3 &newAxis ); + +protected: + void AttachShuttle(hhShuttle *shuttle); + void DetachShuttle(hhShuttle *shuttle); + hhBeamSystem * SpawnDockingBeam(idVec3 &offset); + virtual void Lock(); + virtual void Unlock(); + void Event_FadeOut(); + virtual void Event_Remove(); + +protected: + idVec3 offsetNozzle; + idVec3 offsetShuttlePoint; + hhShuttle * dockedShuttle; + hhBeamSystem * dockingBeam; + hhForce_Converge dockingForce; + int shuttleCount; + int amountHealth; + int amountPower; + bool bLocked; + bool bLockOnEntry; + bool bCanExitLocked; + bool bAllowFiring; +}; + +#endif diff --git a/src/Prey/game_skybox.cpp b/src/Prey/game_skybox.cpp new file mode 100644 index 0000000..53b1c82 --- /dev/null +++ b/src/Prey/game_skybox.cpp @@ -0,0 +1,91 @@ +//************************************************************************** +//** +//** GAME_SKYBOX.CPP +//** +//** Game code for Prey-specific skyboxes +//** +//************************************************************************** + +// HEADER FILES ------------------------------------------------------------ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +// MACROS ------------------------------------------------------------------ + +// TYPES ------------------------------------------------------------------- + +// CLASS DECLARATIONS ------------------------------------------------------ + +CLASS_DECLARATION(idEntity, hhSkybox) +END_CLASS + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// hhSkybox::Spawn +// +//========================================================================== + +void hhSkybox::Spawn(void) { + // Setup the initial state for the skybox + SetSkin( NULL ); + + BecomeActive( TH_UPDATEVISUALS ); + + UpdateVisuals(); +} + +/* +================ +hhSkybox::Present + +Present is called to allow entities to generate refEntities, lights, etc for the renderer. +================ +*/ +void hhSkybox::Present( void ) { + PROFILE_SCOPE("Present", PROFMASK_NORMAL); + + if ( !gameLocal.isNewFrame ) { + 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 + // HUMANHEAD tmj: this is the only change from idEntity::Present. Don't care if the + // skybox is in the PVS since we only build the remoteRenderView on the first think + // before the skybox goes inactive. + 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/src/Prey/game_skybox.h b/src/Prey/game_skybox.h new file mode 100644 index 0000000..b3e67dc --- /dev/null +++ b/src/Prey/game_skybox.h @@ -0,0 +1,14 @@ + +#ifndef __GAME_SKYBOX_H__ +#define __GAME_SKYBOX_H__ + +class hhSkybox : public idEntity { +public: + CLASS_PROTOTYPE( hhSkybox ); + + void Spawn(void); + + virtual void Present( void ); +}; + +#endif /* __GAME_SKYBOX_H__ */ diff --git a/src/Prey/game_slots.cpp b/src/Prey/game_slots.cpp new file mode 100644 index 0000000..2090721 --- /dev/null +++ b/src/Prey/game_slots.cpp @@ -0,0 +1,417 @@ +// hhSlots +// + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +enum { + SLOTRESULT_NONE=0, + SLOTRESULT_LOSE=1, + SLOTRESULT_WIN=2 +}; + +const idEventDef EV_Spin("spin", NULL); + +CLASS_DECLARATION(hhConsole, hhSlots) + EVENT( EV_Spin, hhSlots::Event_Spin) +END_CLASS + + +void hhSlots::Spawn() { + int ix; + + fruitTextures[FRUIT_CHERRY] = spawnArgs.GetString("mtr_cherry"); + fruitTextures[FRUIT_ORANGE] = spawnArgs.GetString("mtr_orange"); + fruitTextures[FRUIT_LEMON] = spawnArgs.GetString("mtr_lemon"); + fruitTextures[FRUIT_APPLE] = spawnArgs.GetString("mtr_apple"); + fruitTextures[FRUIT_GRAPE] = spawnArgs.GetString("mtr_grape"); + fruitTextures[FRUIT_MELON] = spawnArgs.GetString("mtr_melon"); + fruitTextures[FRUIT_BAR] = spawnArgs.GetString("mtr_bar"); + fruitTextures[FRUIT_BARBAR] = spawnArgs.GetString("mtr_barbar"); + fruitTextures[FRUIT_BARBARBAR] = spawnArgs.GetString("mtr_barbarbar"); + + for (ix=0; ixWriteInt( Bet ); + savefile->WriteInt( PlayerBet ); + savefile->WriteInt( PlayerCredits ); + savefile->WriteInt( result ); + savefile->WriteInt( creditsWon ); + savefile->WriteInt( victoryAmount ); + for (i=0; iWriteString( fruitTextures[i] ); + } + + for (i=0; iWriteInt( reel1[i] ); + savefile->WriteInt( reel2[i] ); + savefile->WriteInt( reel3[i] ); + } + + savefile->WriteFloat( reelPos1 ); + savefile->WriteFloat( reelPos2 ); + savefile->WriteFloat( reelPos3 ); + + savefile->WriteFloat( reelRate1 ); + savefile->WriteFloat( reelRate2 ); + savefile->WriteFloat( reelRate3 ); + savefile->WriteBool( bSpinning ); + savefile->WriteBool( bCanSpin ); + savefile->WriteBool( bCanIncBet ); + savefile->WriteBool( bCanDecBet ); +} + +void hhSlots::Restore( idRestoreGame *savefile ) { + int i; + savefile->ReadInt( Bet ); + savefile->ReadInt( PlayerBet ); + savefile->ReadInt( PlayerCredits ); + savefile->ReadInt( result ); + savefile->ReadInt( creditsWon ); + savefile->ReadInt( victoryAmount ); + for (i=0; iReadString( fruitTextures[i] ); + } + + for (i=0; iReadInt( (int&)reel1[i] ); + savefile->ReadInt( (int&)reel2[i] ); + savefile->ReadInt( (int&)reel3[i] ); + } + + savefile->ReadFloat( reelPos1 ); + savefile->ReadFloat( reelPos2 ); + savefile->ReadFloat( reelPos3 ); + + savefile->ReadFloat( reelRate1 ); + savefile->ReadFloat( reelRate2 ); + savefile->ReadFloat( reelRate3 ); + savefile->ReadBool( bSpinning ); + savefile->ReadBool( bCanSpin ); + savefile->ReadBool( bCanIncBet ); + savefile->ReadBool( bCanDecBet ); +} + +void hhSlots::Spin() { + bSpinning = true; + Bet = PlayerBet; + bCanSpin = bCanIncBet = bCanDecBet = false; + reelRate1 = 2000+gameLocal.random.RandomFloat()*100; + reelRate2 = 3000+gameLocal.random.RandomFloat()*100; + reelRate3 = 4000+gameLocal.random.RandomFloat()*100; + result = SLOTRESULT_NONE; + creditsWon = 0; +} + +void hhSlots::IncBet() { + int amount = 1; + idUserInterface *gui = renderEntity.gui[0]; + if (gui) { + amount = gui->GetStateInt("increment"); + } + + if (bCanIncBet) { + int oldBet = PlayerBet; + PlayerBet = idMath::ClampInt(PlayerBet, PlayerCredits, PlayerBet+amount); + PlayerBet = idMath::ClampInt(0, 999999, PlayerBet); + if (PlayerBet != oldBet) { + StartSound( "snd_betchange", SND_CHANNEL_ANY ); + } + } + UpdateView(); +} + +void hhSlots::DecBet() { + int amount = 1; + idUserInterface *gui = renderEntity.gui[0]; + if (gui) { + amount = gui->GetStateInt("increment"); + } + + if (bCanDecBet) { + int oldBet = PlayerBet; + if (PlayerBet > amount) { + PlayerBet -= amount; + } + else if (PlayerBet > 1) { + PlayerBet = 1; + } + if (PlayerBet != oldBet) { + StartSound( "snd_betchange", SND_CHANNEL_ANY ); + } + } + UpdateView(); +} + +int MaskForFruit(fruit_t fruit) { + int mask = -1; + switch(fruit) { + case FRUIT_CHERRY: mask = MASK_CHERRY; break; + case FRUIT_ORANGE: mask = MASK_ORANGE; break; + case FRUIT_LEMON: mask = MASK_LEMON; break; + case FRUIT_APPLE: mask = MASK_APPLE; break; + case FRUIT_GRAPE: mask = MASK_GRAPE; break; + case FRUIT_MELON: mask = MASK_MELON; break; + case FRUIT_BAR: mask = MASK_BAR; break; + case FRUIT_BARBAR: mask = MASK_BARBAR; break; + case FRUIT_BARBARBAR: mask = MASK_BARBARBAR; break; + } + assert(mask != -1); + return mask; +} + +int ReelPos2Slot(int rpos) { + return ((rpos + REEL_LENGTH) % REEL_LENGTH) / SLOT_HEIGHT; +} + +int ReelPos2SlotPos(int rpos) { + return ((rpos + REEL_LENGTH) % REEL_LENGTH) % SLOT_HEIGHT; +} + + +void hhSlots::UpdateView() { + bool bGameOver = false; + idUserInterface *gui = renderEntity.gui[0]; + + if (gui) { + if (PlayerCredits <= 0) { + bCanIncBet = bCanDecBet = bCanSpin = false; + bGameOver = true; + } + + gui->SetStateBool("bgameover", bGameOver); + gui->SetStateBool("bcanincbet", bCanIncBet); + gui->SetStateBool("bcandecbet", bCanDecBet); + gui->SetStateBool("bcanspin", bCanSpin); + gui->SetStateInt("currentbet", PlayerBet); + gui->SetStateInt("credits", PlayerCredits); + gui->SetStateInt("result", result); + gui->SetStateInt("creditswon", creditsWon); + + gui->SetStateInt("reel1pos", (int)reelPos1); + gui->SetStateInt("reel2pos", (int)reelPos2); + gui->SetStateInt("reel3pos", (int)reelPos3); + + gui->SetStateInt("reel1rate", (int)reelRate1); + gui->SetStateInt("reel2rate", (int)reelRate2); + gui->SetStateInt("reel3rate", (int)reelRate3); + + // Clear + int fruit1, fruit2, fruit3; + + // Reel 1 + fruit1 = reel1[ ReelPos2Slot( reelPos1-SLOT_HEIGHT )]; + fruit2 = reel1[ ReelPos2Slot( reelPos1 )]; + fruit3 = reel1[ ReelPos2Slot( reelPos1+SLOT_HEIGHT )]; + gui->SetStateString("r1s1_texture", fruitTextures[fruit1]); + gui->SetStateString("r1s2_texture", fruitTextures[fruit2]); + gui->SetStateString("r1s3_texture", fruitTextures[fruit3]); + + // Reel 2 + fruit1 = reel2[ ReelPos2Slot( reelPos2-SLOT_HEIGHT )]; + fruit2 = reel2[ ReelPos2Slot( reelPos2 )]; + fruit3 = reel2[ ReelPos2Slot( reelPos2+SLOT_HEIGHT )]; + gui->SetStateString("r2s1_texture", fruitTextures[fruit1]); + gui->SetStateString("r2s2_texture", fruitTextures[fruit2]); + gui->SetStateString("r2s3_texture", fruitTextures[fruit3]); + + // Reel 3 + fruit1 = reel3[ ReelPos2Slot( reelPos3-SLOT_HEIGHT )]; + fruit2 = reel3[ ReelPos2Slot( reelPos3 )]; + fruit3 = reel3[ ReelPos2Slot( reelPos3+SLOT_HEIGHT )]; + gui->SetStateString("r3s1_texture", fruitTextures[fruit1]); + gui->SetStateString("r3s2_texture", fruitTextures[fruit2]); + gui->SetStateString("r3s3_texture", fruitTextures[fruit3]); + + gui->StateChanged(gameLocal.time, true); + CallNamedEvent("Update"); + } +} + +void hhSlots::CheckVictory() { + + #define NUM_VICTORIES 16 + static victory_t victoryTable[NUM_VICTORIES] = { + { MASK_BARBARBAR, MASK_BARBARBAR, MASK_BARBARBAR, 10000}, + { MASK_BARBAR, MASK_BARBAR, MASK_BARBAR, 1000}, + { MASK_BAR, MASK_BAR, MASK_BAR, 500}, + { MASK_ANYBAR, MASK_ANYBAR, MASK_ANYBAR, 100}, + + { MASK_MELON, MASK_MELON, MASK_MELON, 60}, + { MASK_GRAPE, MASK_GRAPE, MASK_GRAPE, 50}, + { MASK_APPLE, MASK_APPLE, MASK_APPLE, 40}, + { MASK_LEMON, MASK_LEMON, MASK_LEMON, 30}, + { MASK_ORANGE, MASK_ORANGE, MASK_ORANGE, 20}, + { MASK_CHERRY, MASK_CHERRY, MASK_CHERRY, 10}, + + { MASK_CHERRY, MASK_CHERRY, MASK_ANY, 5}, + { MASK_ANY, MASK_CHERRY, MASK_CHERRY, 5}, + { MASK_CHERRY, MASK_ANY, MASK_CHERRY, 5}, + + { MASK_CHERRY, MASK_ANY, MASK_ANY, 2}, + { MASK_ANY, MASK_CHERRY, MASK_ANY, 2}, + { MASK_ANY, MASK_ANY, MASK_CHERRY, 2} + }; + + PlayerCredits -= Bet; + result = SLOTRESULT_LOSE; + creditsWon = 0; + for (int ix=0; ix= victoryAmount) { + StartSound( "snd_victory", SND_CHANNEL_ANY ); + ActivateTargets( gameLocal.GetLocalPlayer() ); + victoryAmount = 0; + } + else if (victoryTable[ix].payoff > 5) { + StartSound( "snd_winbig", SND_CHANNEL_ANY ); + } + else { + StartSound( "snd_win", SND_CHANNEL_ANY ); + } + break; + } + } + + PlayerBet = idMath::ClampInt(0, PlayerCredits, PlayerBet); +} + + +bool hhSlots::HandleSingleGuiCommand(idEntity *entityGui, idLexer *src) { + + idToken token; + + if (!src->ReadToken(&token)) { + return false; + } + + if (token == ";") { + return false; + } + + if (token.Icmp("spin") == 0) { + BecomeActive(TH_MISC3); + StartSound( "snd_spin", SND_CHANNEL_ANY ); + Spin(); + } + else if (token.Icmp("incbet") == 0) { + IncBet(); + } + else if (token.Icmp("decbet") == 0) { + DecBet(); + } + else if (token.Icmp("reset") == 0) { + Reset(); + } + else if (token.Icmp("restart") == 0) { + bCanSpin = 1; + bCanIncBet = 1; + bCanDecBet = 1; + PlayerCredits = spawnArgs.GetInt("credits"); + Bet = PlayerBet = 1; + UpdateView(); + } + else { + src->UnreadToken(&token); + return false; + } + + return true; +} + +void hhSlots::Think() { + hhConsole::Think(); + + if (thinkFlags & TH_MISC3) { + if (bSpinning) { + + float deltaTime = MS2SEC(gameLocal.msec); + if (reelRate1 > 0.0f) { + reelPos1 = ((int)(reelPos1 - reelRate1 * deltaTime) + REEL_LENGTH) % REEL_LENGTH; + reelRate1 *= 0.98f; + if (reelRate1 < MINIMUM_REEL_RATE) { + reelRate1 = 0.0f; + StartSound( "snd_stop", SND_CHANNEL_ANY ); + } + } + if (reelRate2 > 0.0f) { + reelPos2 = ((int)(reelPos2 - reelRate2 * deltaTime) + REEL_LENGTH) % REEL_LENGTH; + reelRate2 *= 0.98f; + if (reelRate2 < MINIMUM_REEL_RATE) { + reelRate2 = 0.0f; + StartSound( "snd_stop", SND_CHANNEL_ANY ); + } + } + if (reelRate3 > 0.0f) { + reelPos3 = ((int)(reelPos3 - reelRate3 * deltaTime) + REEL_LENGTH) % REEL_LENGTH; + reelRate3 *= 0.98f; + if (reelRate3 < MINIMUM_REEL_RATE) { + reelRate3 = 0.0f; + StartSound( "snd_stop", SND_CHANNEL_ANY ); + } + } + + if (reelRate1==0.0f && reelRate2==0.0f && reelRate3==0.0f) { + CheckVictory(); + bSpinning = false; + bCanSpin = bCanIncBet = bCanDecBet = true; + BecomeInactive(TH_MISC3); + } + + UpdateView(); + } + } +} + +void hhSlots::Event_Spin() { + Spin(); +} \ No newline at end of file diff --git a/src/Prey/game_slots.h b/src/Prey/game_slots.h new file mode 100644 index 0000000..59dcb11 --- /dev/null +++ b/src/Prey/game_slots.h @@ -0,0 +1,86 @@ + +#ifndef __GAME_SLOTS_H__ +#define __GAME_SLOTS_H__ + + #define SLOT_HEIGHT 100 + #define SLOTS_IN_REEL 10 + #define MINIMUM_REEL_RATE (SLOT_HEIGHT/2) + #define REEL_LENGTH (SLOTS_IN_REEL*SLOT_HEIGHT) + + #define MASK_CHERRY 0x00000001 + #define MASK_ORANGE 0x00000002 + #define MASK_LEMON 0x00000004 + #define MASK_APPLE 0x00000008 + #define MASK_GRAPE 0x00000010 + #define MASK_MELON 0x00000020 + #define MASK_BAR 0x00000100 + #define MASK_BARBAR 0x00000200 + #define MASK_BARBARBAR 0x00000400 + + #define MASK_ANYBAR 0x00000700 + #define MASK_ANY 0xffffffff + + typedef enum { + FRUIT_CHERRY=0, + FRUIT_ORANGE, + FRUIT_LEMON, + FRUIT_APPLE, + FRUIT_GRAPE, + FRUIT_MELON, + FRUIT_BAR, + FRUIT_BARBAR, + FRUIT_BARBARBAR, + NUM_FRUITS + }fruit_t; + + typedef struct victory_s { + int f1, f2, f3; + int payoff; + }victory_t; + + +class hhSlots : public hhConsole { +public: + CLASS_PROTOTYPE( hhSlots ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spin(); + void IncBet(); + void DecBet(); + void CheckVictory(); + + void Reset(); + void UpdateView(); + bool HandleSingleGuiCommand(idEntity *entityGui, idLexer *src); + +protected: + virtual void Think( void ); + void Event_Spin(); + +private: + int Bet; + int PlayerBet; + int PlayerCredits; + int result; + int creditsWon; + int victoryAmount; + + idStr fruitTextures[NUM_FRUITS]; + + fruit_t reel1[SLOTS_IN_REEL]; + fruit_t reel2[SLOTS_IN_REEL]; + fruit_t reel3[SLOTS_IN_REEL]; + float reelPos1, reelPos2, reelPos3; + float reelRate1, reelRate2, reelRate3; + bool bSpinning; + + bool bCanSpin; + bool bCanIncBet; + bool bCanDecBet; +}; + + +#endif /* __GAME_SLOTS_H__ */ diff --git a/src/Prey/game_sphere.cpp b/src/Prey/game_sphere.cpp new file mode 100644 index 0000000..c4fd166 --- /dev/null +++ b/src/Prey/game_sphere.cpp @@ -0,0 +1,151 @@ +// Game_Sphere.cpp +// +// pushable exploding sphere + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + + +CLASS_DECLARATION(hhMoveable, hhSphere) + EVENT( EV_Touch, hhSphere::Event_Touch ) +END_CLASS + + +hhSphere::hhSphere() { + additionalAxis.Identity(); +} + +void hhSphere::Spawn(void) { + + radius = (GetPhysics()->GetBounds()[1][0] - GetPhysics()->GetBounds()[0][0]) * 0.5f; + lastOrigin = GetPhysics()->GetOrigin(); + additionalAxis.Identity(); + CreateLight(); + + fl.takedamage = false; + BecomeActive(TH_THINK); +} + +void hhSphere::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( radius ); + savefile->WriteVec3( lastOrigin ); + savefile->WriteMat3( additionalAxis ); + light.Save(savefile); +} + +void hhSphere::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( radius ); + savefile->ReadVec3( lastOrigin ); + savefile->ReadMat3( additionalAxis ); + light.Restore(savefile); +} + +void hhSphere::CreateLight() { + if ( spawnArgs.GetBool("haslight") ) { + idStr light_shader = spawnArgs.GetString("mtr_light"); + idVec3 light_color = spawnArgs.GetVector("light_color"); + idVec3 light_frustum = spawnArgs.GetVector("light_frustum"); + idVec3 light_offset = spawnArgs.GetVector("offset_light"); + idVec3 light_target = spawnArgs.GetVector("offset_lighttarget"); + + idDict args; + idVec3 lightOrigin = GetPhysics()->GetOrigin() + light_offset * GetPhysics()->GetAxis(); + light_target.Normalize(); + idMat3 lightAxis = (light_target * GetPhysics()->GetAxis()).hhToMat3(); + + if ( light_shader.Length() ) { + args.Set( "texture", light_shader ); + } + args.SetVector( "origin", lightOrigin ); + args.Set ("angles", lightAxis.ToAngles().ToString()); + args.SetVector( "_color", light_color ); + args.SetVector( "light_target", lightAxis[0] * light_frustum.x ); + args.SetVector( "light_right", lightAxis[1] * light_frustum.y ); + args.SetVector( "light_up", lightAxis[2] * light_frustum.z ); + light = ( idLight * )gameLocal.SpawnEntityType( idLight::Type, &args ); + light->Bind(this, true); + light->SetLightParm( 6, 1.0f ); + light->SetLightParm( SHADERPARM_TIMEOFFSET, -MS2SEC(gameLocal.time) ); + } +} + +void hhSphere::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + // skip idMoveable::Damage which handles damage differently + idEntity::Damage(inflictor, attacker, dir, damageDefName, damageScale, location); +} + + +void hhSphere::RollThink( void ) { + float movedDistance, angle; + idVec3 curOrigin, gravityNormal, dir; + + bool wasAtRest = IsAtRest(); + + RunPhysics(); + + // only need to give the visual model an additional rotation if the physics were run + if ( !wasAtRest ) { + + // current physics state + curOrigin = GetPhysics()->GetOrigin(); + + dir = curOrigin - lastOrigin; + float movedDistanceSquared = dir.LengthSqr(); + + // if the sphere moved + if ( movedDistanceSquared > 0.0f && movedDistanceSquared < 20.0f) { + + gravityNormal = GetPhysics()->GetGravityNormal(); + + // movement since last frame + movedDistance = idMath::Sqrt( movedDistanceSquared ); + dir *= 1.0f / movedDistance; + + // Get local coordinate axes + idVec3 right = -dir.Cross(gravityNormal); + + // Rotate about it proportional to the distance moved using axis/angle + angle = 180.0f * movedDistance / (radius*idMath::PI); + additionalAxis *= (idRotation( vec3_origin, right, angle).ToMat3()); + } + + // save state for next think + lastOrigin = curOrigin; + } + + Present(); +} + +void hhSphere::Think() { + RollThink(); +} + +void hhSphere::ClientPredictionThink( void ) { + RollThink(); +} + +bool hhSphere::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + origin = vec3_origin; + axis = additionalAxis * GetPhysics()->GetAxis().Inverse(); + return true; +} + +void hhSphere::Event_Touch( idEntity *other, trace_t *trace ) { + if (spawnArgs.GetBool("walkthrough")) { + idVec3 otherVel = other->GetPhysics()->GetLinearVelocity(); + float otherSpeed = otherVel.NormalizeFast(); + if (otherSpeed > 50.0f) { // && GetPhysics()->IsAtRest()) { + idVec3 toSide = hhUtils::RandomSign() * other->GetAxis()[1]; + idVec3 toMoveable = GetOrigin() - other->GetOrigin(); + toMoveable.NormalizeFast(); + idVec3 newVel = ( 3*otherVel + toSide + hhUtils::RandomVector() ) * (1.0f/5.0f); + newVel.z = 0.0f; + newVel.NormalizeFast(); + newVel *= otherSpeed*1.5f; + GetPhysics()->SetLinearVelocity(newVel); + } + } +} diff --git a/src/Prey/game_sphere.h b/src/Prey/game_sphere.h new file mode 100644 index 0000000..ca48f4b --- /dev/null +++ b/src/Prey/game_sphere.h @@ -0,0 +1,31 @@ + +#ifndef __GAME_SPHERE_H__ +#define __GAME_SPHERE_H__ + +class hhSphere : public hhMoveable { +public: + CLASS_PROTOTYPE( hhSphere ); + + hhSphere(); + void Spawn( void ); + 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 bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + virtual void Think( void ); + virtual void ClientPredictionThink( void ); + +protected: + void RollThink( void ); + void CreateLight(); + void Event_Touch( idEntity *other, trace_t *trace ); + +private: + float radius; // radius of sphere + idVec3 lastOrigin; // origin last frame + idMat3 additionalAxis; // transformation for visual model + idEntityPtr light; // light +}; + + +#endif /* __GAME_SPHERE_H__ */ diff --git a/src/Prey/game_spherepart.cpp b/src/Prey/game_spherepart.cpp new file mode 100644 index 0000000..c2b7db2 --- /dev/null +++ b/src/Prey/game_spherepart.cpp @@ -0,0 +1,319 @@ +//************************************************************************** +//** +//** GAME_SPHEREPART.CPP +//** +//** SphereParts are generic environment objects that animate and can be +//** interacted with in simple general ways: Touched, shot / killed. +//** +//** Pulse Tubes should be able to: +//** - Animate +//** - Randomly play other anims +//** - Take Pain +//** - Die (?) +//************************************************************************** + +// HEADER FILES ------------------------------------------------------------ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +// MACROS ------------------------------------------------------------------ + +// TYPES ------------------------------------------------------------------- + +// CLASS DECLARATIONS ------------------------------------------------------ + +const idEventDef EV_Pulse("pulse", NULL); +const idEventDef EV_PlayIdle("", NULL); + +CLASS_DECLARATION( hhAnimatedEntity, hhSpherePart ) + EVENT( EV_Pulse, hhSpherePart::Event_Pulse ) + EVENT( EV_Activate, hhSpherePart::Event_Trigger ) + EVENT( EV_PlayIdle, hhSpherePart::Event_PlayIdle ) +END_CLASS + +// STATE DECLARATIONS ------------------------------------------------------- + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// hhSpherePart::Spawn +// +//========================================================================== + +void hhSpherePart::Spawn(void) { + GetPhysics()->SetContents( CONTENTS_BODY ); + + fl.takedamage = true; // Allow the spherepart to be damaged + + spawnArgs.GetFloat("pulsetime", "10", pulseTime); // A pulsetime of zero will ensure that the object never pulses + spawnArgs.GetFloat("pulserandom", "5", pulseRandom); + + idleAnim = GetAnimator()->GetAnim("idle"); + painAnim = GetAnimator()->GetAnim("pain"); + pulseAnim = GetAnimator()->GetAnim("pulse"); + triggerAnim = GetAnimator()->GetAnim("trigger"); + + BecomeActive(TH_THINK); + + GetAnimator()->CycleAnim(ANIMCHANNEL_ALL, idleAnim, gameLocal.time, 100); + StartSound( "snd_idle", SND_CHANNEL_ANY ); + + if ( pulseAnim && pulseTime ) { + PostEventSec(&EV_Pulse, pulseTime + gameLocal.random.RandomFloat() * pulseRandom); + } +} + +void hhSpherePart::Save(idSaveGame *savefile) const { + savefile->WriteFloat( pulseTime ); + savefile->WriteFloat( pulseRandom ); + savefile->WriteInt( idleAnim ); + savefile->WriteInt( painAnim ); + savefile->WriteInt( pulseAnim ); + savefile->WriteInt( triggerAnim ); +} + +void hhSpherePart::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( pulseTime ); + savefile->ReadFloat( pulseRandom ); + savefile->ReadInt( idleAnim ); + savefile->ReadInt( painAnim ); + savefile->ReadInt( pulseAnim ); + savefile->ReadInt( triggerAnim ); +} + +//========================================================================== +// +// hhSpherePart::~hhSpherePart +// +//========================================================================== +hhSpherePart::~hhSpherePart() { +} + +//========================================================================== +// +// hhSpherePart::Damage +// +//========================================================================== + +void hhSpherePart::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + int startTime; + + + if ( painAnim && !GetAnimator()->IsAnimPlaying( GetAnimator()->GetAnim( painAnim ) ) ) { + StartSound( "snd_pain", SND_CHANNEL_ANY ); + GetAnimator()->ClearAllAnims( gameLocal.time, 100 ); + GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, painAnim, gameLocal.time, 100); + + startTime = GetAnimator()->CurrentAnim( ANIMCHANNEL_ALL )->Length(); + PostEventMS( &EV_PlayIdle, startTime ); + } +} + +//========================================================================== +// +// hhSpherePart::Event_Trigger +// +//========================================================================== + +void hhSpherePart::Event_Trigger( idEntity *activator ) { + int startTime; + + if(triggerAnim) { + GetAnimator()->ClearAllAnims( gameLocal.time, 500 ); + GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, triggerAnim, gameLocal.time, 500); + + startTime = GetAnimator()->GetAnim( triggerAnim )->Length(); + PostEventMS( &EV_PlayIdle, startTime ); + } +} + +//========================================================================== +// +// hhSpherePart::Event_Pulse +// +//========================================================================== + +void hhSpherePart::Event_Pulse(void) { + int startTime; + + if ( pulseAnim && pulseTime ) { + StartSound( "snd_pulse", SND_CHANNEL_ANY ); + + GetAnimator()->ClearAllAnims( gameLocal.time, 500 ); + GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, pulseAnim, gameLocal.time, 500); + + startTime = GetAnimator()->GetAnim( pulseAnim )->Length(); + PostEventMS( &EV_PlayIdle, startTime ); + + PostEventSec(&EV_Pulse, pulseTime + gameLocal.random.RandomFloat() * pulseRandom); + } +} + +//========================================================================== +// +// hhSpherePart::Event_PlayIdle +// +//========================================================================== + +void hhSpherePart::Event_PlayIdle() { + // We are playing the idle, cancel any pending idles + CancelEvents( &EV_PlayIdle ); + + GetAnimator()->ClearAllAnims( gameLocal.time, 100 ); + GetAnimator()->CycleAnim(ANIMCHANNEL_ALL, idleAnim, gameLocal.time, 100); +} + +/********************************************************************** + +hhGenericAnimatedPart + +**********************************************************************/ +CLASS_DECLARATION( hhAnimatedEntity, hhGenericAnimatedPart ) + EVENT( EV_PostSpawn, hhGenericAnimatedPart::Event_PostSpawn ) +END_CLASS + +/* +============ +hhGenericAnimatedPart::Spawn +============ +*/ +void hhGenericAnimatedPart::Spawn() { + if( spawnArgs.GetBool("solid", "1") ) { + fl.takedamage = true; + GetPhysics()->SetContents( CONTENTS_SOLID ); + } else { + fl.takedamage = false; + GetPhysics()->SetContents( 0 ); + GetPhysics()->UnlinkClip(); + } + + //rww - make sure it's sent + fl.networkSync = true; + + PostEventMS( &EV_PostSpawn, 0 ); +} + +void hhGenericAnimatedPart::Save(idSaveGame *savefile) const { + owner.Save(savefile); +} + +void hhGenericAnimatedPart::Restore( idRestoreGame *savefile ) { + owner.Restore(savefile); +} + +void hhGenericAnimatedPart::WriteToSnapshot( idBitMsgDelta &msg ) const +{ + GetPhysics()->WriteToSnapshot(msg); + msg.WriteBits(owner.GetSpawnId(), 32); +} + +void hhGenericAnimatedPart::ReadFromSnapshot( const idBitMsgDelta &msg ) +{ + GetPhysics()->ReadFromSnapshot(msg); + owner.SetSpawnId(msg.ReadBits(32)); +} + +void hhGenericAnimatedPart::ClientPredictionThink( void ) +{ + Think(); +} + + +/* +============ +hhGenericAnimatedPart::SetOwner +============ +*/ +void hhGenericAnimatedPart::SetOwner( idEntity* pOwner ) { + owner = pOwner; +} + +/* +============ +hhGenericAnimatedPart::LinkCombatModel +============ +*/ +void hhGenericAnimatedPart::LinkCombatModel( idEntity* self, const int renderModelHandle ) { + hhAnimatedEntity::LinkCombatModel( (owner.IsValid()) ? owner.GetEntity() : this, renderModelHandle ); +} + +/* +============ +hhGenericAnimatedPart::Damage +============ +*/ +void hhGenericAnimatedPart::Damage( idEntity* inflictor, idEntity* attacker, const idVec3& dir, const char* damageDefName, const float damageScale, const int location ) { + if( owner.IsValid() && spawnArgs.GetBool("transferDamage") ) { + owner->Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + } else { + hhAnimatedEntity::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + } +} + +/* +================ +hhGenericAnimatedPart::PlayAnim +================ +*/ +int hhGenericAnimatedPart::PlayAnim( const char* animName, int channel ) { + ClearAllAnims(); + + int anim = GetAnimator()->GetAnim( animName ); + if( !anim ) { + return 0; + } + + GetAnimator()->PlayAnim( channel, anim, gameLocal.GetTime(), 0 ); + return GetAnimator()->CurrentAnim( channel )->GetEndTime() - gameLocal.GetTime(); +} + +/* +================ +hhGenericAnimatedPart::CycleAnim +================ +*/ +void hhGenericAnimatedPart::CycleAnim( const char* animName, int channel ) { + ClearAllAnims(); + + int anim = GetAnimator()->GetAnim( animName ); + if( !anim ) { + return; + } + + GetAnimator()->CycleAnim( channel, anim, gameLocal.GetTime(), 0 ); +} + +/* +================ +hhGenericAnimatedPart::ClearAllAnims +================ +*/ +void hhGenericAnimatedPart::ClearAllAnims() { + GetAnimator()->ClearAllAnims( gameLocal.GetTime(), 0 ); +} + +/* +============ +hhGenericAnimatedPart::Event_PostSpawn +============ +*/ +void hhGenericAnimatedPart::Event_PostSpawn() { + idStr ownerName = spawnArgs.GetString( "owner" ); + if( ownerName.Length() ) { + SetOwner( gameLocal.FindEntity(ownerName.c_str()) ); + } +} diff --git a/src/Prey/game_spherepart.h b/src/Prey/game_spherepart.h new file mode 100644 index 0000000..b005344 --- /dev/null +++ b/src/Prey/game_spherepart.h @@ -0,0 +1,71 @@ + +#ifndef __GAME_SPHEREPART_H__ +#define __GAME_SPHEREPART_H__ + +extern const idEventDef EV_Pulse; +extern const idEventDef EV_PlayIdle; + +class hhSpherePart : public hhAnimatedEntity { +public: + CLASS_PROTOTYPE(hhSpherePart); + + virtual ~hhSpherePart(); + + void Spawn(void); + 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 ); + +protected: + void Event_Pulse(void); + void Event_Trigger( idEntity *activator ); + void Event_PlayIdle(); + +protected: + float pulseTime; // Time between pulses. Slight randomness is added + float pulseRandom; + + int idleAnim; + int painAnim; + int pulseAnim; + int triggerAnim; +}; + +/********************************************************************** + +hhGenericAnimatedPart + +**********************************************************************/ +class hhGenericAnimatedPart: public hhAnimatedEntity { + CLASS_PROTOTYPE( hhGenericAnimatedPart ); + +public: + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - networking + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + + virtual void Damage( idEntity* inflictor, idEntity* attacker, const idVec3& dir, const char* damageDefName, const float damageScale, const int location ); + + virtual int PlayAnim( const char* animName, int channel ); + virtual void CycleAnim( const char* animName, int channel ); + virtual void ClearAllAnims(); + + void SetOwner( idEntity* pOwner ); + +protected: + virtual void LinkCombatModel( idEntity* self, const int renderModelHandle ); + +protected: + void Event_PostSpawn(); + +protected: + idEntityPtr owner; +}; + +#endif /* __GAME_SPHEREPART_H__ */ diff --git a/src/Prey/game_spring.cpp b/src/Prey/game_spring.cpp new file mode 100644 index 0000000..c3ad56e --- /dev/null +++ b/src/Prey/game_spring.cpp @@ -0,0 +1,191 @@ +/* +game_spring.cpp +*/ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +/*********************************************************************** + + hhSpring + +***********************************************************************/ + +const idEventDef EV_HHLinkSpring( "linkspring", "" ); +const idEventDef EV_HHUnlinkSpring( "unlinkspring", "" ); + +CLASS_DECLARATION( idEntity, hhSpring ) + EVENT( EV_HHLinkSpring, hhSpring::Event_LinkSpring ) + EVENT( EV_HHUnlinkSpring, hhSpring::Event_UnlinkSpring ) +END_CLASS + + +/* +================ +hhSpring::Spawn +================ +*/ +void hhSpring::Spawn( void ) { + float Kstretch, damping, restLength, Kcompress; + + spawnArgs.GetString( "ent1", "", name1 ); + spawnArgs.GetString( "ent2", "", name2 ); + 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 ); + spawnArgs.GetFloat( "compress", "0.0f", Kcompress ); + + // HUMANHEAD: Added compression constant + spring.InitSpring( Kstretch, Kcompress, damping, restLength ); + + PostEventMS( &EV_HHLinkSpring, 0 ); +} + +void hhSpring::Save(idSaveGame *savefile) const { + savefile->WriteString( name1 ); + savefile->WriteString( name2 ); + physics1.Save( savefile ); + physics2.Save( savefile ); + savefile->WriteInt( id1 ); + savefile->WriteInt( id2 ); + savefile->WriteVec3( p1 ); + savefile->WriteVec3( p2 ); + savefile->WriteStaticObject( spring ); +} + +void hhSpring::Restore( idRestoreGame *savefile ) { + savefile->ReadString( name1 ); + savefile->ReadString( name2 ); + physics1.Restore( savefile ); + physics2.Restore( savefile ); + savefile->ReadInt( id1 ); + savefile->ReadInt( id2 ); + savefile->ReadVec3( p1 ); + savefile->ReadVec3( p2 ); + savefile->ReadStaticObject( spring ); +} + +/* +================ +hhSpring::Think + HUMANHEAD +================ +*/ +#define TEST_SPRINGS +void hhSpring::Think() { + if (thinkFlags & TH_THINK) { + spring.Evaluate( gameLocal.time ); + +#ifdef TEST_SPRINGS + idVec3 start, end, origin; + idMat3 axis; + + start = p1; + if ( physics1.IsValid() ) { + axis = physics1->GetPhysics()->GetAxis(id1); + origin = physics1->GetPhysics()->GetOrigin(id1); + start = origin + p1 * axis; + } + + end = p2; + if ( physics2.IsValid() ) { + axis = physics2->GetPhysics()->GetAxis(id2); + origin = physics2->GetPhysics()->GetOrigin(id2); + end = origin + p2 * axis; + } + + gameRenderWorld->DebugLine( colorYellow, start, end, 0, true ); +#endif + } +} + +/* +================ +hhSpring::LinkSpring + HUMANHEAD +================ +*/ +void hhSpring::LinkSpring(idEntity *ent1, idEntity *ent2 ) { + LinkSpringAll(ent1, id1, p1, ent2, id2, p2); +} + +/* +================ +hhSpring::LinkSpringIDs + HUMANHEAD +================ +*/ +void hhSpring::LinkSpringIDs(idEntity *ent1, int bodyID1, idEntity *ent2, int bodyID2) { + id1 = bodyID1; + id2 = bodyID2; + LinkSpringAll(ent1, id1, p1, ent2, id2, p2); +} + +/* +================ +hhSpring::LinkSpringAll + HUMANHEAD +================ +*/ +void hhSpring::LinkSpringAll(idEntity *ent1, int bodyID1, idVec3 &offset1, + idEntity *ent2, int bodyID2, idVec3 &offset2) { + + physics1 = ent1; + physics2 = ent2; + spring.SetPosition( physics1.GetEntity(), bodyID1, offset1, physics2.GetEntity(), bodyID2, offset2 ); + BecomeActive(TH_THINK); +} + +/* +================ +hhSpring::UnLinkSpring + HUMANHEAD +================ +*/ +void hhSpring::UnLinkSpring() { + BecomeInactive(TH_THINK); +} + +/* +================ +hhSpring::SpringSettings + HUMANHEAD +================ +*/ +void hhSpring::SpringSettings(float kStretch, float kCompress, float damping, float restLength) { + spring.InitSpring(kStretch, kCompress, damping, restLength); +} + +/* +================ +hhSpring::Event_LinkSpring +================ +*/ +void hhSpring::Event_LinkSpring( void ) { + idEntity *ent1, *ent2; + + ent1 = gameLocal.FindEntity( name1 ); + if ( !ent1 ) { + ent1 = gameLocal.entities[ENTITYNUM_WORLD]; + } + ent2 = gameLocal.FindEntity( name2 ); + if ( !ent2 ) { + ent2 = gameLocal.entities[ENTITYNUM_WORLD]; + } + LinkSpring(ent1, ent2); +} + +/* +================ +hhSpring::Event_UnlinkSpring +================ +*/ +void hhSpring::Event_UnlinkSpring( void ) { + UnLinkSpring(); +} diff --git a/src/Prey/game_spring.h b/src/Prey/game_spring.h new file mode 100644 index 0000000..3d34e85 --- /dev/null +++ b/src/Prey/game_spring.h @@ -0,0 +1,45 @@ + +#ifndef __GAME_SPRING_H__ +#define __GAME_SPRING_H__ + +/*********************************************************************** + +hhSpring + +***********************************************************************/ +extern const idEventDef EV_HHLinkSpring; +extern const idEventDef EV_HHUnlinkSpring; + +class hhSpring : public idEntity { + CLASS_PROTOTYPE( hhSpring ); + +public: + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + + // HUMANHEAD pdm + void LinkSpring(idEntity *ent1, idEntity *ent2 ); + void LinkSpringIDs(idEntity *ent1, int bodyID1, idEntity *ent2, int bodyID2); + void LinkSpringAll(idEntity *ent1, int bodyID1, idVec3 &offset1, idEntity *ent2, int bodyID2, idVec3 &offset2); + void UnLinkSpring(); + void SpringSettings(float kStretch, float kCompress, float damping, float restLength); + // HUMANHEAD END + +protected: + void Event_LinkSpring( void ); + void Event_UnlinkSpring( void ); + +protected: + idStr name1, name2; + idEntityPtr physics1, physics2; + int id1, id2; + idVec3 p1, p2; + idForce_Spring spring; +}; + + +#endif /* !__GAME_SPRING_H__ */ diff --git a/src/Prey/game_sunCorona.cpp b/src/Prey/game_sunCorona.cpp new file mode 100644 index 0000000..ae64993 --- /dev/null +++ b/src/Prey/game_sunCorona.cpp @@ -0,0 +1,123 @@ +//***************************************************************************** +//** +//** GAME_SUNCORONA.CPP +//** +//** Game code for Sun Coronas +//** +//** TODO: +//** - Implement corona scale (or just leave this for the shader?) +//** - Check if 4096 is good enough for distance -- issues in very large rooms? +//** - Verify if the noFragment check is valid for checking for skyboxes +//** - Make it function corrected with the rifle zoom view +//***************************************************************************** + +// HEADER FILES --------------------------------------------------------------- + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +// MACROS --------------------------------------------------------------------- + +// TYPES ---------------------------------------------------------------------- + +// CLASS DECLARATIONS --------------------------------------------------------- + +CLASS_DECLARATION( idEntity, hhSunCorona ) +END_CLASS + +// STATE DECLARATIONS --------------------------------------------------------- + +// EXTERNAL FUNCTION PROTOTYPES ----------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES ------------------------------------------------ + +// EXTERNAL DATA DECLARATIONS ------------------------------------------------- + +// PUBLIC DATA DEFINITIONS ---------------------------------------------------- + +// PRIVATE DATA DEFINITIONS --------------------------------------------------- + +// CODE ----------------------------------------------------------------------- + +//============================================================================= +// +// hhSunCorona::Spawn +// +//============================================================================= + +void hhSunCorona::Spawn(void) { + corona = declManager->FindMaterial( spawnArgs.GetString( "mtr_corona" ) ); + scale = spawnArgs.GetFloat( "scale", "1" ); + sunVector = spawnArgs.GetVector( "sunVector", "0 0 -1" ); + sunVector.Normalize(); + sunDistance = spawnArgs.GetFloat( "sunDistance", "4096" ); + + GetPhysics()->SetContents(0); + Hide(); + BecomeInactive(TH_THINK); + + gameLocal.SetSunCorona( this ); +} + +void hhSunCorona::Save(idSaveGame *savefile) const { + savefile->WriteMaterial( corona ); + savefile->WriteFloat( scale ); + savefile->WriteVec3( sunVector ); + savefile->WriteFloat( sunDistance ); +} + +void hhSunCorona::Restore( idRestoreGame *savefile ) { + savefile->ReadMaterial( corona ); + savefile->ReadFloat( scale ); + savefile->ReadVec3( sunVector ); + savefile->ReadFloat( sunDistance ); +} + +//============================================================================= +// +// hhSunCorona::~hhSunCorona +// +//============================================================================= + +hhSunCorona::~hhSunCorona() { + corona = NULL; +} + +//============================================================================= +// +// hhhSunCorona::Draw +// +//============================================================================= + +void hhSunCorona::Draw( hhPlayer *player ) { +/* Commented out for E3 demo. Need a better method for visibility, instead of a trace. -cjr + idVec3 v[3]; + float dot[3]; + player->viewAngles.ToVectors( &v[0], &v[1], &v[2] ); + + for( int i = 0; i < 3; i++ ) { + dot[i] = v[i] * sunVector; + } + + if ( dot[0] < 0 ) { + trace_t trace; + idVec3 origin; + idMat3 axis; + player->GetViewPos( origin, axis ); + idVec3 end = origin - sunVector * sunDistance; + gameLocal.clip.TracePoint( trace, origin, end, MASK_SOLID, player ); + + if ( trace.c.material->NoFragment() ) { // Trace succeeded, or it hit a skybox + // Draw a corona on the screen + renderSystem->SetColor4( -dot[0], -dot[0], -dot[0], -dot[0] ); + float hFov = idMath::Sin( g_fov.GetFloat() * 0.5 ); + float vFov = idMath::Sin( g_fov.GetFloat() * 0.5 * 0.75 ); + float sx = ( ( dot[1] - hFov ) / ( -2 * hFov ) ) * 640 - 640; + float sy = ( dot[2] + vFov ) / ( 2 * vFov ) * 480 - 480; + renderSystem->DrawStretchPic( sx, sy, 1280, 960, 0, 0, 1, 1, corona ); + } + } +*/ +} diff --git a/src/Prey/game_sunCorona.h b/src/Prey/game_sunCorona.h new file mode 100644 index 0000000..4e99cd4 --- /dev/null +++ b/src/Prey/game_sunCorona.h @@ -0,0 +1,24 @@ + +#ifndef __GAME_SUNCORONA_H__ +#define __GAME_SUNCORONA_H__ + +//----------------------------------------------------------------------------- + +class hhSunCorona : public idEntity { +public: + CLASS_PROTOTYPE(hhSunCorona); + + ~hhSunCorona(); //fixme: does this need to be virtual ? + void Spawn(void); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void Draw( hhPlayer *player ); + +protected: + const idMaterial *corona; + float scale; + idVec3 sunVector; + float sunDistance; +}; + +#endif /* __GAME_SUNCORONA_H__ */ diff --git a/src/Prey/game_talon.cpp b/src/Prey/game_talon.cpp new file mode 100644 index 0000000..8a88de6 --- /dev/null +++ b/src/Prey/game_talon.cpp @@ -0,0 +1,1777 @@ +//***************************************************************************** +//** +//** GAME_TALON.CPP +//** +//** Game code for Tommy's sidekick, Talon the Hawk +//** +//***************************************************************************** + +// HEADER FILES --------------------------------------------------------------- + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +// MACROS --------------------------------------------------------------------- + +#define PERCH_ROTATION 3.5 +#define ROTATION_SPEED 3.5 +#define ROTATION_SPEED_FAST 5 +#define TALON_BLEND 100 + +// TYPES ---------------------------------------------------------------------- + +// CLASS DECLARATIONS --------------------------------------------------------- + +const idEventDef EV_PerchTicker("", NULL); +const idEventDef EV_PerchChatter("perchChatter", NULL); +const idEventDef EV_PerchSquawk("", NULL); + +const idEventDef EV_CheckForTarget( "", NULL ); +const idEventDef EV_CheckForEnemy( "", NULL ); + +const idEventDef EV_TalonAction("talonaction", "ed"); + +// Anim Events +const idEventDef EV_LandAnim("", NULL); +const idEventDef EV_PreLandAnim("", NULL); +const idEventDef EV_IdleAnim("", NULL); +const idEventDef EV_TommyIdleAnim("", NULL); +const idEventDef EV_FlyAnim("", NULL); +const idEventDef EV_GlideAnim("", NULL); +const idEventDef EV_TakeOffAnim("", NULL); +const idEventDef EV_TakeOffAnimB("", NULL); + +CLASS_DECLARATION( hhMonsterAI, hhTalon ) + EVENT(EV_PerchChatter, hhTalon::Event_PerchChatter) + EVENT(EV_PerchSquawk, hhTalon::Event_PerchSquawk) + EVENT(EV_CheckForTarget, hhTalon::Event_CheckForTarget) + EVENT(EV_CheckForEnemy, hhTalon::Event_CheckForEnemy) + + // Anims + EVENT(EV_LandAnim, hhTalon::Event_LandAnim) + EVENT(EV_PreLandAnim, hhTalon::Event_PreLandAnim) + EVENT(EV_IdleAnim, hhTalon::Event_IdleAnim) + EVENT(EV_TommyIdleAnim, hhTalon::Event_TommyIdleAnim) + EVENT(EV_FlyAnim, hhTalon::Event_FlyAnim) + EVENT(EV_GlideAnim, hhTalon::Event_GlideAnim) + EVENT(EV_TakeOffAnim, hhTalon::Event_TakeOffAnim) + EVENT(EV_TakeOffAnimB, hhTalon::Event_TakeOffAnimB) +END_CLASS + +const idEventDef EV_CallTalon("callTalon", "fff" ); +const idEventDef EV_ReleaseTalon("releaseTalon", NULL ); +const idEventDef EV_SetPerchState("setPerchState", "f" ); + +CLASS_DECLARATION( idEntity, hhTalonTarget ) + EVENT( EV_CallTalon, hhTalonTarget::Event_CallTalon ) + EVENT( EV_ReleaseTalon, hhTalonTarget::Event_ReleaseTalon ) + EVENT( EV_SetPerchState, hhTalonTarget::Event_SetPerchState ) +END_CLASS + +// STATE DECLARATIONS --------------------------------------------------------- + +// EXTERNAL FUNCTION PROTOTYPES ----------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES ------------------------------------------------ + +// EXTERNAL DATA DECLARATIONS ------------------------------------------------- + +// PUBLIC DATA DEFINITIONS ---------------------------------------------------- + +// PRIVATE DATA DEFINITIONS --------------------------------------------------- + +// CODE ----------------------------------------------------------------------- + +//============================================================================= +// +// hhTalon::Spawn +// +//============================================================================= + +void hhTalon::Spawn(void) { + flyAnim = GetAnimator()->GetAnim("fly"); + glideAnim = GetAnimator()->GetAnim("glide"); + prelandAnim = GetAnimator()->GetAnim("preland"); + landAnim = GetAnimator()->GetAnim("land"); + idleAnim = GetAnimator()->GetAnim("idle"); + tommyIdleAnim = GetAnimator()->GetAnim("tommy_idle"); + squawkAnim = GetAnimator()->GetAnim("squawk"); + stepAnim = GetAnimator()->GetAnim("step"); + takeOffAnim = GetAnimator()->GetAnim("takeoffA"); + takeOffAnimB = GetAnimator()->GetAnim("takeoffB"); + attackAnim = GetAnimator()->GetAnim("attack1"); + preAttackAnim = GetAnimator()->GetAnim("preattack"); + + fl.neverDormant = true; + fl.takedamage = false; + fl.notarget = true; + + allowHiddenMovement = true; // Allow Talon to move while hidden + + health = 1; // Talon need some health for enemies to consider it alive + + //AOB + GetAnimator()->RemoveOriginOffset( true ); + + // Register the wings skins + openWingsSkin = declManager->FindSkin( spawnArgs.GetString("skin_openWings") ); + closedWingsSkin = declManager->FindSkin( spawnArgs.GetString("skin_closeWings") ); + + Event_SetMoveType(MOVETYPE_FLY); + + GetPhysics()->SetContents( 0 ); // No collisions + GetPhysics()->SetClipMask( 0 ); // No collisions + Hide(); + BecomeInactive(TH_THINK); +} + +void hhTalon::Save(idSaveGame *savefile) const { + owner.Save(savefile); + savefile->WriteVec3(velocity); + savefile->WriteVec3(acceleration); + + savefile->WriteObject(talonTarget); + savefile->WriteVec3(talonTargetLoc); + savefile->WriteMat3(talonTargetAxis); + savefile->WriteVec3(lastCheckOrigin); + + savefile->WriteFloat(checkTraceTime); + savefile->WriteFloat(checkFlyTime); + savefile->WriteFloat(flyStraightTime); + + savefile->WriteBool(bLanding); + savefile->WriteBool(bReturnToTommy); + savefile->WriteBool( bForcedTarget ); + savefile->WriteBool( bClawingAtEnemy ); + + savefile->WriteFloat( velocityFactor ); + savefile->WriteFloat( rotationFactor ); + savefile->WriteFloat( perchRotationFactor ); + + savefile->WriteInt(flyAnim); + savefile->WriteInt(glideAnim); + savefile->WriteInt(prelandAnim); + savefile->WriteInt(landAnim); + savefile->WriteInt(idleAnim); + savefile->WriteInt(tommyIdleAnim); + savefile->WriteInt(squawkAnim); + savefile->WriteInt(stepAnim); + savefile->WriteInt(takeOffAnim); + savefile->WriteInt(takeOffAnimB); + savefile->WriteInt(attackAnim); + savefile->WriteInt(preAttackAnim); + + savefile->WriteSkin(openWingsSkin); + savefile->WriteSkin(closedWingsSkin); + + enemy.Save( savefile ); + trailFx.Save( savefile ); + + savefile->WriteInt( state ); +} + +void hhTalon::Restore( idRestoreGame *savefile ) { + owner.Restore(savefile); + savefile->ReadVec3(velocity); + savefile->ReadVec3(acceleration); + + savefile->ReadObject( reinterpret_cast(talonTarget) ); + savefile->ReadVec3(talonTargetLoc); + savefile->ReadMat3(talonTargetAxis); + savefile->ReadVec3(lastCheckOrigin); + + savefile->ReadFloat(checkTraceTime); + savefile->ReadFloat(checkFlyTime); + savefile->ReadFloat(flyStraightTime); + + savefile->ReadBool(bLanding); + savefile->ReadBool(bReturnToTommy); + savefile->ReadBool( bForcedTarget ); + savefile->ReadBool( bClawingAtEnemy ); + + savefile->ReadFloat( velocityFactor ); + savefile->ReadFloat( rotationFactor ); + savefile->ReadFloat( perchRotationFactor ); + + savefile->ReadInt(flyAnim); + savefile->ReadInt(glideAnim); + savefile->ReadInt(prelandAnim); + savefile->ReadInt(landAnim); + savefile->ReadInt(idleAnim); + savefile->ReadInt(tommyIdleAnim); + savefile->ReadInt(squawkAnim); + savefile->ReadInt(stepAnim); + savefile->ReadInt(takeOffAnim); + savefile->ReadInt(takeOffAnimB); + savefile->ReadInt(attackAnim); + savefile->ReadInt(preAttackAnim); + + savefile->ReadSkin(openWingsSkin); + savefile->ReadSkin(closedWingsSkin); + + enemy.Restore( savefile ); + trailFx.Restore( savefile ); + + savefile->ReadInt( reinterpret_cast (state) ); +} + +//============================================================================= +// +// hhTalon::hhTalon +// +//============================================================================= + +hhTalon::hhTalon() { + owner = NULL; + talonTarget = NULL; + enemy.Clear(); + trailFx.Clear(); +} + +//============================================================================= +// +// hhTalon::~hhTalon +// +//============================================================================= + +hhTalon::~hhTalon() { + if( owner.IsValid() ) { + owner->talon = NULL; + } + + if ( trailFx.IsValid() ) { + trailFx->Nozzle( false ); + SAFE_REMOVE( trailFx ); + } +} + +//============================================================================= +// +// hhTalon::SummonTalon +// +//============================================================================= + +void hhTalon::SummonTalon(void) { + hhFxInfo fxInfo; + + state = StateNone; + + BecomeActive(TH_THINK); + Show(); + + checkTraceTime = spawnArgs.GetFloat( "traceCheckPulse", "0.1" ); + lastCheckOrigin = GetOrigin(); + + FindTalonTarget(NULL, NULL, true); + + CalculateTalonTargetLocation(); // Must recalculate the target location, as the target may have moved + SetOrigin(talonTargetLoc); + viewAxis = talonTargetAxis; + + bLanding = false; + + SetSkin( closedWingsSkin ); // The only time this is set in code, otherwise it's set by the land anim + + fl.neverDormant = true; + + SetForcedTarget( false ); + + velocityFactor = 1.0f; + rotationFactor = 1.0f; + perchRotationFactor = 1.0f; + + SetShaderParm( SHADERPARM_DIVERSITY, 0 ); + + EnterTommyState(); + TommyTicker(); // Force Talon to tick once to adjust his orientation +} + +//============================================================================= +// +// hhTalon::SetOwner +// +//============================================================================= + +void hhTalon::SetOwner( hhPlayer *newOwner ) { + owner = newOwner; + Event_SetOwner( newOwner ); +} + +void hhTalon::OwnerEnteredVehicle( void ) { + if ( talonTarget ) { + talonTarget->Left( this ); + } + + SummonTalon(); + EnterVehicleState(); +} + +void hhTalon::OwnerExitedVehicle( void ) { + ExitVehicleState(); + SummonTalon(); +} + +//============================================================================= +// +// hhTalon::FindTalonTarget +// +//============================================================================= + +void hhTalon::FindTalonTarget( idEntity *skipEntity, hhTalonTarget *forceTarget, bool bForcePlayer ) { + int i; + int count; + float dist; + float bestDist; + hhTalonTarget *ent; + idVec3 dir; + + talonTarget = NULL; + bestDist = spawnArgs.GetFloat( "perchDistance", "250" ); + + if ( owner->InVehicle() ) { + bestDist *= 10; + } + + if( bReturnToTommy) { + bForcePlayer = true; + } + + if ( forceTarget ) { + talonTarget = forceTarget; + } + + // Iterate through valid perch spots and find the best choice + if( !bForcePlayer && !forceTarget ) { + count = gameLocal.talonTargets.Num(); + for(i = 0; i < count; i++) { + ent = (hhTalonTarget *)gameLocal.talonTargets[i]; + + if ( !ent->bValidForTalon ) { // Not a valid target + continue; + } + + // ignore if there is no way we can see this perch spot + if( ent == skipEntity || !gameLocal.InPlayerPVS( ent ) || ent->priority == -1) { + continue; + } + + if ( owner.IsValid() && owner->IsSpiritOrDeathwalking() && ent->bNotInSpiritWalk ) { // If the owner is spiritwalking, then don't try to fly to this target (useful for lifeforce pickups) + continue; + } + + // Calculate the distance from the player to the perch spot + dist = ( owner->GetEyePosition() - ent->GetPhysics()->GetOrigin() ).Length(); + + // Adjust the distance based upon the priority (ranges from 0 - 2, -1 to completely skip). + dist *= ent->priority; + + if(dist > bestDist) { // Farther than the nearest perch spot + continue; + } + + talonTarget = ent; + bestDist = dist; + } + } + + if( !talonTarget && owner.IsValid() ) { // No perch spot, so use a spot close to the player + owner->GetJointWorldTransform( "FX_bird", talonTargetLoc, talonTargetAxis ); + talonTargetAxis = owner->viewAxis; + } else { + CalculateTalonTargetLocation(); + } +} + +//============================================================================= +// +// hhTalon::CalculateTalonTargetLocation +// +//============================================================================= + +void hhTalon::CalculateTalonTargetLocation() { + trace_t tr; + + if ( !talonTarget ) { + return; + } + + // Trace down a bit to get the exact perch location + if( gameLocal.clip.TracePoint( tr, talonTarget->GetPhysics()->GetOrigin() + idVec3(0, 0, 4), talonTarget->GetPhysics()->GetOrigin() - idVec3(0, 0, 16), MASK_SOLID, this )) { + talonTargetLoc = tr.endpos - idVec3( 0, 0, 1 ); // End position, minus a tiny bit so Talon perches on railings + } else { // No collision, just use the current floating spot + talonTargetLoc = talonTarget->GetPhysics()->GetOrigin(); + } + + talonTargetAxis = talonTarget->GetPhysics()->GetAxis(); +} + +//============================================================================= +// +// CheckReachedTarget +// +//============================================================================= + +bool hhTalon::CheckReachedTarget( float distance ) { + idVec3 vec; + + if(distance < spawnArgs.GetFloat( "distanceSlow", "80" ) ) { + if(talonTarget) { + velocity = (talonTargetLoc - GetOrigin()) * 0.05f; // Move more slowly when about to perch + + if( !GetAnimator()->IsAnimPlaying( GetAnimator()->GetAnim( prelandAnim ) ) ) { + Event_PreLandAnim(); + StartSound( "snd_preland", SND_CHANNEL_BODY ); + } + bLanding = true; + } + + if(talonTarget) { + if(distance < spawnArgs.GetFloat( "distancePerchEpsilon", "3" ) ) { // Perch on this spot + Event_LandAnim(); + StartSound( "snd_land", SND_CHANNEL_BODY ); + + talonTarget->Reached(this); + EnterPerchState(); + + return true; // No need to continue the fly ticker + } + } else if(distance < spawnArgs.GetFloat( "distanceTommyEpsilon", "15.0" ) ) { // No perch spot, so perch on Tommy around this location + Event_LandAnim(); + EnterTommyState(); + return true; // No need to continue the fly ticker + } + } else if(distance >= spawnArgs.GetFloat( "distanceTurbo", "2500" ) ) { // Distance is so far, we should kick Talon into turbo mode + velocity *= 10; + } + + return false; +} + +//============================================================================= +// +// hhTalon::CheckCollisions +// +//============================================================================= + +void hhTalon::CheckCollisions(float deltaTime) { + idVec3 oldOrigin; + trace_t tr; + + checkTraceTime -= deltaTime; + + if(checkTraceTime > 0) { + return; + } + + checkTraceTime = spawnArgs.GetFloat( "traceCheckPulse", "0.1" ); + + idVec3 nextLocation = GetOrigin() + this->velocity * 0.5f; + + // Trace to determine if the hawk is just about to fly into a wall + if ( gameLocal.clip.TracePoint( tr, GetOrigin(), nextLocation, MASK_SOLID, owner.GetEntity() ) ) { // Struck something solid + // Glow + SetShaderParm( SHADERPARM_TIMEOFFSET, MS2SEC( gameLocal.time ) ); + } else if ( gameLocal.clip.TracePoint( tr, GetOrigin(), lastCheckOrigin, MASK_SOLID, owner.GetEntity() ) ) { // Exited something solid + // Glow + SetShaderParm( SHADERPARM_TIMEOFFSET, MS2SEC( gameLocal.time ) ); + } + + lastCheckOrigin = GetOrigin(); +} + +//============================================================================= +// +// hhTalon::Think +// +//============================================================================= + +void hhTalon::Think(void) { + + if ( owner->InGravityZone() ) { // don't perch in gravity zones, unless gravity is "normal" + idVec3 gravity = owner->GetGravity(); + gravity.Normalize(); + + float dot = gravity * idVec3( 0.0f, 0.0f, -1.0f ); + if ( dot < 1.0f ) { + if ( talonTarget ) { + talonTarget->Left( this ); + } + + ReturnToTommy(); + } + } + + // Shadow suppression based upon the player + if ( g_showPlayerShadow.GetBool() ) { + renderEntity.suppressShadowInViewID = 0; + } + else { + renderEntity.suppressShadowInViewID = owner->entityNumber + 1; + } + + hhMonsterAI::Think(); +} + +//============================================================================= +// +// hhTalon::FlyMove +// +//============================================================================= + +void hhTalon::FlyMove( void ) { + // Run the specific task Ticker + switch( state ) { + case StateTommy: + TommyTicker(); + break; + case StateFly: + FlyTicker(); + break; + case StateVehicle: + VehicleTicker(); + break; + case StatePerch: + PerchTicker(); + break; + case StateAttack: + AttackTicker(); + break; + } + + // run the physics for this frame + physicsObj.UseFlyMove( true ); + physicsObj.UseVelocityMove( false ); + physicsObj.SetDelta( vec3_zero ); + physicsObj.ForceDeltaMove( disableGravity ); + RunPhysics(); +} + +//============================================================================= +// +// hhTalon::AdjustFlyingAngles +// +// No need to adjust flying angles on Talon +//============================================================================= + +void hhTalon::AdjustFlyingAngles() { + return; +} + +//============================================================================= +// +// hhTalon::GetPhysicsToVisualTransform +// +//============================================================================= + +bool hhTalon::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + origin = modelOffset; + if ( GetBindMaster() && bindJoint != INVALID_JOINT ) { + idMat3 masterAxis; + idVec3 masterOrigin; + GetMasterPosition( masterOrigin, masterAxis ); + + origin = masterOrigin + physicsObj.GetLocalOrigin() * masterAxis; + axis = GetBindMaster()->GetAxis(); + } else { + axis = viewAxis; + } + return true; +} + +//============================================================================= +// +// hhTalon::CrashLand +// +// No need to check crash landing on Talon +//============================================================================= + +void hhTalon::CrashLand( const idVec3 &oldOrigin, const idVec3 &oldVelocity ) { + return; +} + +//============================================================================= +// +// hhTalon::Damage +// +// Talon cannot be damaged +//============================================================================= + +void hhTalon::Damage( idEntity *inflictor, idEntity *attack, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + return; +} + +//============================================================================= +// +// hhTalon::Killed +// +// Talon cannot be killed +//============================================================================= + +void hhTalon::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + return; +} + +//============================================================================= +// +// hhTalon::Portalled +// +//============================================================================= + +void hhTalon::Portalled( idEntity *portal ) { + if ( state == StateTommy ) { // No need to portal if Talon is already on Tommy's shoulder + return; + } + + CancelEvents( &EV_GlideAnim ); // Could possibly have a glide anim queue + CancelEvents( &EV_TakeOffAnimB ); + + // If Talon is currently on a gui, inform the gui he is leaving + if ( talonTarget && ( state == StatePerch || state == StateVehicle ) ) { // CJR PCF 050306: Only call Left() if Talon is perching on a console + talonTarget->Left( this ); + } + + StopAttackFX(); + + // Force Talon to Tommy's shoulder after he portals + talonTarget = NULL; + bLanding = false; + SetSkin( openWingsSkin ); // The only time this is set in code, otherwise it's set by the land anim + Event_LandAnim(); + SetForcedTarget( false ); + EnterTommyState(); +} + +//============================================================================= +// +// hhTalon::EnterFlyState +// +//============================================================================= + +void hhTalon::EnterFlyState(void) { + EndState(); + state = StateFly; + checkFlyTime = spawnArgs.GetFloat( "checkFlyTime", "4.0" ); + flyStraightTime = spawnArgs.GetFloat( "flyStraightTime", "0.5" ); + bLanding = false; + + SetShaderParm( SHADERPARM_DIVERSITY, 0 ); // Talon's model should phase out if the player is too close + + +} + +//============================================================================= +// +// hhTalon::FlyTicker +// +//============================================================================= + +void hhTalon::FlyTicker(void) { + idAngles ang; + float distance; + float deltaTime = MS2SEC(gameLocal.msec); + idVec3 oldVel; + float distanceXY; + float deltaZ; + float rotSpeed; + + // If the bird is currently in fly mode, but hasn't finished the takeoff anim, then don't let the bird fly around + if( GetAnimator()->IsAnimPlaying( GetAnimator()->GetAnim( takeOffAnim ) ) ) { + return; + } + + acceleration = talonTargetLoc - GetOrigin(); + + idVec2 vecXY = acceleration.ToVec2(); + distance = acceleration.Normalize(); + + distanceXY = vecXY.Length(); + deltaZ = talonTargetLoc.z - GetOrigin().z; + + float dp = acceleration * GetAxis()[0]; + float side = acceleration * GetAxis()[1]; + ang = GetAxis().ToAngles(); + + // Talon can either rotate towards the target, or fly straight. + if( flyStraightTime > 0 ) { // Continue to fly straight + flyStraightTime -= deltaTime; + + if ( flyStraightTime <= 0 && !bLanding ) { // No longer flying straight + Event_FlyAnim(); + } + + rotSpeed = 0; + } else if( bLanding ) { // About to land, so turn slightly faster than normal + rotSpeed = ROTATION_SPEED_FAST * rotationFactor; + } else { + rotSpeed = ROTATION_SPEED * rotationFactor; + } + + + rotSpeed *= (60.0f * USERCMD_ONE_OVER_HZ); + + // Determine whether Talon should rotate left or right to reach the target + if(dp > 0) { + if(dp >= 0.98f || ( side <= 0.1f && side >= 0.1f )) { // CJR PCF 04/28/06: don't turn if directly facing the target + rotSpeed = 0; + } else if (side < -0.1f) { + rotSpeed *= -1.0f; + } + } else { + if(side < 0) { + rotSpeed *= -1.0f; + } + } + + // Apply rotation + deltaViewAngles.yaw = rotSpeed; + oldVel = velocity; + + float defaultVelocityXY = spawnArgs.GetFloat( "velocityXY", "7.0" ); + float defaultVelocityZ = spawnArgs.GetFloat( "velocityZ", "4" ); + + velocity = viewAxis[0] * defaultVelocityXY * velocityFactor; // xy-velocity + + // Z-change based upon distance + if(abs(deltaZ) > 0 && distanceXY > 0) { + velocity.z = (deltaZ * defaultVelocityXY) / distanceXY; + + // Clamp z velocity + if(velocity.z > defaultVelocityZ) { + velocity.z = defaultVelocityZ; + } + } + + if ( CheckReachedTarget( distance ) ) { + return; + } + + // Apply velocity + SetOrigin( GetOrigin() + velocity * (60.0f * USERCMD_ONE_OVER_HZ) ); + + // Check for colliding with world surfaces (only if not currently trying to land) + if( !bLanding ) { + CheckCollisions(deltaTime); + } + + // Timeout for flying to perch spot... if too much time has passed, then fly straight for a bit + checkFlyTime -= deltaTime; + if( checkFlyTime <= 0 && !bLanding ) { + flyStraightTime = spawnArgs.GetFloat( "flyStraightTime", "0.5" ); + checkFlyTime = spawnArgs.GetFloat( "checkFlyTime", "4.0" ); + + PostEventMS( &EV_GlideAnim, 0 ); + } + + // If no talonTarget (probably flying towards Tommy), then constantly look for a new target + if( !bForcedTarget && !GetAnimator()->IsAnimPlaying( GetAnimator()->GetAnim( prelandAnim ) ) ) { + FindTalonTarget( NULL, NULL, false ); + } +} + +//============================================================================= +// +// hhTalon::EnterTommyState +// +//============================================================================= + +void hhTalon::EnterTommyState(void) { + idVec3 bindOrigin; + idMat3 bindAxis; + + bReturnToTommy = false; + + EndState(); + state = StateTommy; + + Hide(); + + UpdateVisuals(); + + // Bind the bird to Tommy's shoulder + owner->GetJointWorldTransform( "fx_bird", bindOrigin, bindAxis ); + SetOrigin( bindOrigin ); + SetAxis( owner->viewAxis ); + BindToJoint( owner.GetEntity(), "fx_bird", true ); + + // Start checking for enemies + PostEventSec( &EV_CheckForEnemy, spawnArgs.GetFloat("checkEnemyTime", "4.0" ) ); + + // Start checking for nearby targets + PostEventSec( &EV_CheckForTarget, spawnArgs.GetFloat( "checkTargetTime", "1.0" ) ); +} + +//============================================================================= +// +// hhTalon::EndState +// +//============================================================================= + +void hhTalon::EndState(void) { + Unbind(); + + int oldState = state; + state = StateNone; + + if ( fl.hidden ) { // If the bird is hidden, show it when ending the state + Show(); + } + + // Zero out Talon's velocity when initially taking off, so it doesn't inherit any from the bind master + velocity = vec3_zero; + GetPhysics()->SetLinearVelocity( vec3_zero ); + + // Cancel any checks for enemies + CancelEvents( &EV_CheckForEnemy ); + CancelEvents( &EV_CheckForTarget ); + + StopAttackFX(); + + if( oldState == StateTommy ) { + GetPhysics()->SetAxis( mat3_identity ); + + // Show the hawk in the player's view after it takes off + Show(); + } else if( oldState == StatePerch ) { + CancelEvents( &EV_PerchSquawk ); + } else if ( oldState == StateAttack ) { + // Clear the enemy + if ( enemy.IsValid() ) { + (static_cast(enemy.GetEntity()))->Distracted( GetOwner() ); // Set the enemy's enemy back to Tommy + enemy.Clear(); + } + } +} + +//============================================================================= +// +// hhTalon::TommyTicker +// +//============================================================================= + +void hhTalon::TommyTicker(void) { + idVec3 viewOrigin; + idMat3 viewAxis; + idMat3 ownerAxis; + float deltaTime = MS2SEC(gameLocal.msec); + + bLanding = false; + + // Turn towards direction of Tommy + idVec3 toFace = owner->GetAxis()[0]; + toFace.z = 0; + toFace.Normalize(); + float dp = toFace * GetAxis()[0]; + float side = toFace * GetAxis()[1]; + + if(side > 0.05 || dp < 0 ) { + deltaViewAngles.yaw = PERCH_ROTATION * perchRotationFactor; + UpdateVisuals(); + } else if (side < -0.05) { + deltaViewAngles.yaw = -PERCH_ROTATION * perchRotationFactor; + UpdateVisuals(); + } +} + +//============================================================================= +// +// hhTalon::FindEnemy +// +//============================================================================= + +bool hhTalon::FindEnemy( void ) { + idEntity *ent; + hhMonsterAI *actor; + hhMonsterAI *bestEnemy; + float bestDist; + float maxEnemyDist; + float dist; + idVec3 delta; + int enemyCount; + + // Check if Talon already has a valid enemy + if ( enemy.IsValid() && enemy->health > 0 ) { + return true; + } + + bestDist = 0; + maxEnemyDist = 1024.0f * 1024.0f; // sqr + bestEnemy = NULL; + enemyCount = 0; + for ( ent = gameLocal.activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + if ( ent->fl.hidden || ent->fl.isDormant || !ent->IsType( hhMonsterAI::Type ) ) { + continue; + } + + actor = static_cast( ent ); + + // Only attack living enemies, and only attack enemies that are targetting Tommy or Talon + if ( (actor->health <= 0) || ( actor->GetEnemy() != GetOwner() && actor->GetEnemy() != this ) ) { + continue; + } + + if ( !gameLocal.InPlayerPVS( actor ) ) { + continue; + } + + // Check if Talon should avoid this enemy -- these enemies are also not included in the enemy count + if ( !ent->spawnArgs.GetBool( "bTalonAttack", "0" ) ) { + continue; + } + + delta = GetOwner()->GetOrigin() - actor->GetPhysics()->GetOrigin(); // Find the farthest enemy (in the PVS) to Tommy + dist = delta.LengthSqr(); + if ( dist > bestDist && dist < maxEnemyDist && GetOwner()->CanSee( actor, false ) ) { + bestDist = dist; + bestEnemy = actor; + } + + enemyCount++; + } + if ( bestEnemy && enemyCount >= 2 ) { // Only attack if more than one enemy is attacking the player + enemy = bestEnemy; + return true; + } + + enemy.Clear(); + return false; +} + +//============================================================================= +// +// hhTalon::EnterPerchState +// +//============================================================================= + +void hhTalon::EnterPerchState(void) { + EndState(); + state = StatePerch; + + velocity = vec3_zero; + + if ( talonTarget ) { + CalculateTalonTargetLocation(); // Must recalculate the target location, as the target may have moved + SetOrigin( talonTargetLoc ); + + if ( talonTarget->IsBound() ) { // Only bind talon to this target, if it's bound to something + Bind( talonTarget, true ); + } + + GetPhysics()->SetLinearVelocity( vec3_zero ); + } + + if ( talonTarget && talonTarget->bShouldSquawk ) { + float frequency = talonTarget->spawnArgs.GetFloat( "squawkFrequency", "5.0" ); + PostEventSec( &EV_PerchSquawk, frequency ); + } + + SetShaderParm( SHADERPARM_DIVERSITY, 1 ); // Talon's model shouldn't phase out if the player is too close + + // Start checking for nearby enemies + PostEventSec( &EV_CheckForEnemy, spawnArgs.GetFloat("checkEnemyTime", "4.0" ) ); + + // Start checking for nearby targets + PostEventSec( &EV_CheckForTarget, spawnArgs.GetFloat( "checkTargetTime", "1.0" ) ); +} + +//============================================================================= +// +// hhTalon::PerchTicker +// +//============================================================================= + +void hhTalon::PerchTicker(void) { + float deltaTime = MS2SEC(gameLocal.msec); + + bLanding = false; + + // Turn towards direction of perch spot + idVec3 toFace = talonTarget->GetPhysics()->GetAxis()[0]; + toFace.z = 0; + toFace.Normalize(); + float dp = toFace * GetAxis()[0]; + float side = toFace * GetAxis()[1]; + + if ( dp < 0 ) { + if ( side >= 0 ) { + deltaViewAngles.yaw = PERCH_ROTATION * perchRotationFactor; + } else { + deltaViewAngles.yaw = -PERCH_ROTATION * perchRotationFactor; + } + } else if (side > 0.05 ) { + deltaViewAngles.yaw = PERCH_ROTATION * perchRotationFactor; + } else if (side < -0.05) { + deltaViewAngles.yaw = -PERCH_ROTATION * perchRotationFactor; + } + + UpdateVisuals(); +} + +//============================================================================= +// +// hhTalon::Event_PerchChatter +// +// Small chirping noise which is part of Talon's idle animation +//============================================================================= + +void hhTalon::Event_PerchChatter(void) { + if ( state != StateTommy ) { // Don't chatter when on Tommy's shoulder + StartSound( "snd_chatter", SND_CHANNEL_VOICE ); + } +} + +//============================================================================= +// +// hhTalon::Event_PerchSquawk +// +// Louder, squawking noise, alerting the player to this location +//============================================================================= + +void hhTalon::Event_PerchSquawk(void) { + if ( !talonTarget ) { + return; + } + + if ( talonTarget->bShouldSquawk ) { // Always check, because it could have been turned off after Talon perched + + // Check if the player is outside of the squawk distance + float squawkDistSqr = talonTarget->spawnArgs.GetFloat( "squawkDistance", "0" ); + squawkDistSqr *= squawkDistSqr; + + if ( (GetOrigin() - owner->GetOrigin() ).LengthSqr() > squawkDistSqr ) { // Far enough away, so squawk + GetAnimator()->ClearAllAnims( gameLocal.time, TALON_BLEND ); + GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, squawkAnim, gameLocal.time, TALON_BLEND); + PostEventMS( &EV_IdleAnim, GetAnimator()->GetAnim( squawkAnim )->Length() + TALON_BLEND ); + StartSound( "snd_squawk", SND_CHANNEL_VOICE ); + + // Glow + SetShaderParm( SHADERPARM_TIMEOFFSET, MS2SEC( gameLocal.time ) ); + } + + // Post the next squawk attempt + float frequency = talonTarget->spawnArgs.GetFloat( "squawkFrequency", "5.0" ); + PostEventSec( &EV_PerchSquawk, frequency + frequency * gameLocal.random.RandomFloat() ); + } +} + +//============================================================================= +// +// hhTalon::Event_CheckForTarget +// +//============================================================================= + +void hhTalon::Event_CheckForTarget() { + hhTalonTarget *oldTarget = talonTarget; + + // Determine if Talon should take off from his perch + if ( !bForcedTarget ) { // do not check if Talon is forced this this point + FindTalonTarget(NULL, NULL, false); + + if( oldTarget != talonTarget ) { + Event_TakeOffAnim(); + + if( oldTarget ) { // Inform the perch spot that talon is leaving + oldTarget->Left(this); + } + + EnterFlyState(); + return; + } + } + + PostEventSec( &EV_CheckForTarget, spawnArgs.GetFloat( "checkTargetTime", "1.0" ) ); +} + +//============================================================================= +// +// hhTalon::Event_CheckForEnemy +// +//============================================================================= + +void hhTalon::Event_CheckForEnemy() { + if ( !ai_talonAttack.GetBool() ) { + return; + } + if ( !bForcedTarget ) { + if ( FindEnemy() ) { + Event_TakeOffAnim(); + + if( talonTarget ) { // Inform the perch spot that talon is leaving + talonTarget->Left( this ); + } + + EnterAttackState(); + return; + } + } + + PostEventSec( &EV_CheckForEnemy, spawnArgs.GetFloat("checkEnemyTime", "4.0" ) ); +} + +//============================================================================= +// +// hhTalon::EnterAttackState +// +//============================================================================= + +void hhTalon::EnterAttackState(void) { + EndState(); + state = StateAttack; + checkFlyTime = spawnArgs.GetFloat( "attackTime", "8.0f" ); + + StartSound( "snd_attack", SND_CHANNEL_BODY ); + + bClawingAtEnemy = false; + + StartAttackFX(); +} + +//============================================================================= +// +// hhTalon::AttackTicker +// +//============================================================================= + +void hhTalon::AttackTicker(void) { + float deltaTime = MS2SEC(gameLocal.msec); + + if ( !FindEnemy() ) { + ReturnToTommy(); + StopAttackFX(); + return; + } + + idAngles ang; + float distance; + idVec3 oldVel; + bool landing = false; + float distanceXY; + float deltaZ; + float rotSpeed; + float distToEnemy; + + idMat3 junkAxis; + enemy->GetViewPos(talonTargetLoc, junkAxis); + talonTargetLoc -= junkAxis[2] * 10.0f; + + distToEnemy = (talonTargetLoc - GetOrigin()).Length(); + + acceleration = talonTargetLoc - GetOrigin(); + + idVec2 vecXY = acceleration.ToVec2(); + distance = acceleration.Normalize(); + + distanceXY = vecXY.Length(); + deltaZ = talonTargetLoc.z - GetOrigin().z; + + float dp = acceleration * GetAxis()[0]; + float side = acceleration * GetAxis()[1]; + ang = GetAxis().ToAngles(); + + // Talon can either rotate towards the target, or fly straight. + if( flyStraightTime > 0 ) { // Continue to fly straight + flyStraightTime -= deltaTime; + + if ( flyStraightTime <= 0 ) { // Done gliding, play a flap animation + Event_FlyAnim(); + + if ( bClawingAtEnemy ) { // Done attacking + bClawingAtEnemy = false; + + flyStraightTime = 1.0f; + } + } + + rotSpeed = 0; + } else { + rotSpeed = ROTATION_SPEED * rotationFactor; + } + + rotSpeed *= (60.0f * USERCMD_ONE_OVER_HZ); // CJR PCF 04/28/06: Adjust for 30Hz + + // Determine whether Talon should rotate left or right to reach the target + if(dp > 0) { + if(dp >= 0.98f || ( side <= 0.1f && side >= 0.1f )) { // CJR PCF 04/28/06: don't turn if directly facing the target + rotSpeed = 0; + } else if (side < -0.1f) { + rotSpeed *= -1; + } + } else { + if(side < 0) { + rotSpeed *= -1; + } + } + + // Apply rotation + deltaViewAngles.yaw = rotSpeed; + float desiredRoll = -side * 20 * rotSpeed; + + oldVel = velocity; + + float defaultVelocityXY = spawnArgs.GetFloat( "velocityXY_attack", "8.5" ); + float defaultVelocityZ = spawnArgs.GetFloat( "velocityZ", "4" ); + + // Adjust velocity based upon the attack + if ( bClawingAtEnemy ) { + defaultVelocityXY = 0.0f; + + idVec3 tempVec( vecXY.x, vecXY.y, 0.0f ); + viewAxis = tempVec.ToMat3(); + + } else if ( flyStraightTime > 0 ) { // Flying away from the enemy + float adjust = hhMath::ClampFloat( 0.0f, 1.0f, 2.0f - flyStraightTime * 2.0f ); + defaultVelocityXY *= adjust; // Move more slowly when about to attack + } else { // Flying around, check if the bird should slow down when nearing the enemy + if ( distToEnemy < 150.0f && dp > 0 ) { // Close to the enemy and facing it + if( !GetAnimator()->IsAnimPlaying( GetAnimator()->GetAnim( preAttackAnim ) ) ) { + GetAnimator()->ClearAllAnims( gameLocal.time, TALON_BLEND ); + GetAnimator()->CycleAnim(ANIMCHANNEL_ALL, preAttackAnim, gameLocal.time, TALON_BLEND); + CancelEvents( &EV_GlideAnim ); // Could possibly have a glide anim queue + CancelEvents( &EV_TakeOffAnimB ); + } + float adjust = 1.0f - hhMath::ClampFloat( 0.0f, 0.9f, (100 - (distToEnemy-50) ) / 100.0f); // Decelerate Talon + defaultVelocityXY *= adjust; + } + } + + velocity = viewAxis[0] * defaultVelocityXY * velocityFactor; // xy-velocity + + // Z-change based upon distance + if(abs(deltaZ) > 0 && distanceXY > 0) { + velocity.z = (deltaZ * defaultVelocityXY) / distanceXY; + + // Clamp z velocity + if(velocity.z > defaultVelocityZ) { + velocity.z = defaultVelocityZ; + } + } + + // Apply velocity + SetOrigin( GetOrigin() + velocity ); + + UpdateVisuals(); + + // Timeout for flying to perch spot... if too much time has passed, then fly straight for a bit + checkFlyTime -= deltaTime; + if( checkFlyTime <= 0 && !bClawingAtEnemy && flyStraightTime <= 0 ) { + ReturnToTommy(); + StopAttackFX(); + } + + // Check if Talon reached the target and attack! + if ( flyStraightTime <= 0 && ( distToEnemy < spawnArgs.GetFloat( "distanceAttackEpsilon", "60" ) ) ) { + // Damage the enemy + StartSound( "snd_attack", SND_CHANNEL_BODY ); + + // Distract the enemy by setting Talon as its enemy -- random chance that this will succeed + if ( enemy.IsValid() && gameLocal.random.RandomFloat() < spawnArgs.GetFloat( "attackChance", "0.5" ) ) { + (static_cast(enemy.GetEntity()))->Distracted( this ); + owner->TalonAttackComment(); + } + + // Play an attack anim here + GetAnimator()->ClearAllAnims( gameLocal.time, 0 ); + GetAnimator()->CycleAnim(ANIMCHANNEL_ALL, attackAnim, gameLocal.time, 0 ); + + flyStraightTime = MS2SEC( GetAnimator()->GetAnim( attackAnim )->Length() ); + PostEventSec( &EV_FlyAnim, flyStraightTime ); // Play a fly anim once this attack animation is done + + bClawingAtEnemy = true; + } +} + +//============================================================================= +// +// hhTalon::StartAttackFX +// +//============================================================================= + +void hhTalon::StartAttackFX() { + SetShaderParm( SHADERPARM_MODE, 1 ); // Fire glow attack + + if ( trailFx.IsValid() ) { // An effect already exists, turn it on + trailFx->Nozzle( true ); + } else { // Spawn a new effect + const char *defName = spawnArgs.GetString( "fx_attackTrail" ); + if (defName && defName[0]) { + hhFxInfo fxInfo; + + fxInfo.SetNormal( -GetAxis()[0] ); + fxInfo.SetEntity( this ); + fxInfo.RemoveWhenDone( false ); + trailFx = SpawnFxLocal( defName, GetOrigin(), GetAxis(), &fxInfo, gameLocal.isClient ); + if (trailFx.IsValid()) { + trailFx->fl.neverDormant = true; + trailFx->fl.networkSync = false; + trailFx->fl.clientEvents = true; + } + } + } +} + +//============================================================================= +// +// hhTalon::StopAttackFX +// +//============================================================================= + +void hhTalon::StopAttackFX() { + SetShaderParm( SHADERPARM_MODE, 0 ); // Fire glow attack done + + if ( trailFx.IsValid() ) { + trailFx->Nozzle( false ); + } +} + +//============================================================================= +// +// hhTalon::EnterVehicleState +// +//============================================================================= + +void hhTalon::EnterVehicleState(void) { + EndState(); + state = StateVehicle; + Hide(); + + CancelEvents( &EV_GlideAnim ); // Could possibly have a glide anim queue + CancelEvents( &EV_TakeOffAnimB ); +} + +//============================================================================= +// +// hhTalon::ExitVehicleState +// +//============================================================================= + +void hhTalon::ExitVehicleState(void) { + if ( talonTarget ) { + talonTarget->Left( this ); + } +} + +//============================================================================= +// +// hhTalon::VehicleTicker +// +// How Talon acts when the player is in a vehicle: +// - In the interest of speed, Talon warps from target to Target +// - This allows for the giant translation GUIs to instantly translate as the player gets near +//============================================================================= + +void hhTalon::VehicleTicker(void) { + float deltaTime = MS2SEC(gameLocal.msec); + hhTalonTarget *oldEnt = talonTarget; + + if( GetAnimator()->IsAnimPlaying( GetAnimator()->GetAnim( prelandAnim ) ) ) { + return; + } + + FindTalonTarget( NULL, NULL, bReturnToTommy ); + + if ( talonTarget == oldEnt ) { // No change to the target, so do nothing + return; + } + + if ( !talonTarget ) { // No Talon Target, so hide Talon + Hide(); + } else { // have a target, to show the bird + Show(); + } + + // Have a target, so spawn the bird at the target, if it isn't already there + CalculateTalonTargetLocation(); // Must recalculate the target location, as the target may have moved + SetOrigin( talonTargetLoc ); + SetAxis( talonTargetAxis ); + UpdateVisuals(); + + SetSkin( openWingsSkin ); + Event_LandAnim(); + + // Trigger the target + if ( talonTarget ) { + talonTarget->Reached( this ); // Reached the spot, but fly through it + } + + // Inform the last talonTarget that Talon has left + if ( oldEnt ) { + oldEnt->Left( this ); + } +} + +//============================================================================= +// +// hhTalon::UpdateAnimationControllers +// Talon doesn't use any anim controllers +//============================================================================= + +bool hhTalon::UpdateAnimationControllers() { + idVec3 vel; + float speed; + float roll; + + speed = velocity.Length(); + if ( speed < 3.0f || flyStraightTime > 0 ) { + roll = 0.0f; + } else { + roll = acceleration * viewAxis[1] * -fly_roll_scale / fly_speed; + if ( roll > fly_roll_max ) { + roll = fly_roll_max; + } else if ( roll < -fly_roll_max ) { + roll = -fly_roll_max; + } + } + + fly_roll = fly_roll * 0.95f + roll * 0.05f; + + viewAxis = idAngles( 0, current_yaw, fly_roll ).ToMat3(); + + return false; +} + +//============================================================================= +// +// hhTalon::ReturnToTommy +// +//============================================================================= + +void hhTalon::ReturnToTommy( void ) { + if ( bReturnToTommy ) { // already returning + return; + } + + bReturnToTommy = true; + bLanding = false; + + FindTalonTarget(NULL, NULL, true); + + SetSkin( openWingsSkin ); + GetAnimator()->ClearAllAnims( gameLocal.time, TALON_BLEND ); + GetAnimator()->CycleAnim(ANIMCHANNEL_ALL, flyAnim, gameLocal.time, TALON_BLEND); + EnterFlyState(); +} + +// ANIM EVENTS ================================================================ + +//============================================================================= +// +// hhTalon::Event_LandAnim +// +//============================================================================= + +void hhTalon::Event_LandAnim(void) { + GetAnimator()->ClearAllAnims( gameLocal.time, TALON_BLEND ); + GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, landAnim, gameLocal.time, TALON_BLEND); + + if ( talonTarget ) { // Different animation if landing on Tommy or not + PostEventMS( &EV_IdleAnim, GetAnimator()->GetAnim( landAnim )->Length() + TALON_BLEND ); + } else { // No target, so the bird must be landing on Tommy + PostEventMS( &EV_TommyIdleAnim, GetAnimator()->GetAnim( landAnim )->Length() + TALON_BLEND ); + } + + CancelEvents( &EV_GlideAnim ); // Could possibly have a glide anim queue + CancelEvents( &EV_TakeOffAnimB ); +} + +//============================================================================= +// +// hhTalon::Event_PreLandAnim +// +//============================================================================= + +void hhTalon::Event_PreLandAnim(void) { + GetAnimator()->ClearAllAnims( gameLocal.time, TALON_BLEND ); + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, prelandAnim, gameLocal.time, TALON_BLEND ); + + CancelEvents( &EV_GlideAnim ); // Could possibly have a glide anim queue + CancelEvents( &EV_TakeOffAnimB ); +} + +//============================================================================= +// +// hhTalon::Event_IdleAnim +// +//============================================================================= + +void hhTalon::Event_IdleAnim(void) { + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, idleAnim, gameLocal.time, TALON_BLEND); + + StartSound("snd_idle", SND_CHANNEL_BODY); +} + +//============================================================================= +// +// hhTalon::Event_TommyIdleAnim +// +//============================================================================= + +void hhTalon::Event_TommyIdleAnim(void) { + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, tommyIdleAnim, gameLocal.time, TALON_BLEND); +} + +//============================================================================= +// +// hhTalon::Event_FlyAnim +// +//============================================================================= + +void hhTalon::Event_FlyAnim(void) { + GetAnimator()->ClearAllAnims( gameLocal.time, TALON_BLEND ); + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, flyAnim, gameLocal.time, TALON_BLEND); + + StartSound("snd_fly", SND_CHANNEL_BODY); +} + +//============================================================================= +// +// hhTalon::Event_GlideAnim +// +//============================================================================= + +void hhTalon::Event_GlideAnim(void) { + GetAnimator()->ClearAllAnims( gameLocal.time, TALON_BLEND ); + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, glideAnim, gameLocal.time, TALON_BLEND); + + StartSound("snd_glide", SND_CHANNEL_BODY); +} + +//============================================================================= +// +// hhTalon::Event_TakeOffAnim +// +//============================================================================= + +void hhTalon::Event_TakeOffAnim(void) { + GetAnimator()->ClearAllAnims( gameLocal.time, 0 ); // No blend when clearing old anims while taking off + GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, takeOffAnim, gameLocal.time, TALON_BLEND); + + PostEventMS( &EV_TakeOffAnimB, GetAnimator()->GetAnim( takeOffAnim )->Length() + TALON_BLEND ); + + StartSound("snd_takeoff", SND_CHANNEL_ANY); +} + +//============================================================================= +// +// hhTalon::Event_TakeOffAnimB +// +//============================================================================= + +void hhTalon::Event_TakeOffAnimB(void) { + GetAnimator()->ClearAllAnims( gameLocal.time, 0 ); + GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, takeOffAnimB, gameLocal.time, 0); + + PostEventMS( &EV_GlideAnim, GetAnimator()->GetAnim( takeOffAnimB )->Length() + TALON_BLEND ); +} + +//============================================================================= +// +// hhTalon::Show +// +// Special show needed to remove any collision information on Talon +//============================================================================= + +void hhTalon::Show() { + if ( state == StateTommy ) { + return; + } + + hhMonsterAI::Show(); + + // Needed after the show + GetPhysics()->SetContents( 0 ); // No collisions + GetPhysics()->SetClipMask( 0 ); // No collisions +} + +// TALON PERCH SPOT =========================================================== + +//============================================================================= +// +// hhTalonTarget::Spawn +// +//============================================================================= + +void hhTalonTarget::Spawn(void) { + priority = 2.0f - spawnArgs.GetInt("priority"); + if ( priority < 0 ) { + priority = 0; + } else if ( priority > 2) { + priority = 2; + } + + bNotInSpiritWalk = spawnArgs.GetBool( "notInSpiritWalk" ); + bShouldSquawk = spawnArgs.GetBool( "shouldSquawk" ); + bValidForTalon = spawnArgs.GetBool( "valid", "1" ); + + gameLocal.RegisterTalonTarget(this); + + GetPhysics()->SetContents( 0 ); + + Hide(); +} + +void hhTalonTarget::Save(idSaveGame *savefile) const { + savefile->WriteInt( priority ); + savefile->WriteBool( bNotInSpiritWalk ); + savefile->WriteBool( bShouldSquawk ); + savefile->WriteBool( bValidForTalon ); +} + +void hhTalonTarget::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( priority ); + savefile->ReadBool( bNotInSpiritWalk ); + savefile->ReadBool( bShouldSquawk ); + savefile->ReadBool( bValidForTalon ); +} + +//============================================================================= +// +// hhTalonTarget::ShowTargets +// +//============================================================================= + +void hhTalonTarget::ShowTargets( void ) { + int i; + int count; + idEntity *ent; + + count = gameLocal.talonTargets.Num(); + for(i = 0; i < count; i++) { + ent = (hhTalonTarget *)gameLocal.talonTargets[i]; + ent->Show(); + } +} + +//============================================================================= +// +// hhTalonTarget::HideTargets +// +//============================================================================= + +void hhTalonTarget::HideTargets( void ) { + int i; + int count; + idEntity *ent; + + count = gameLocal.talonTargets.Num(); + for(i = 0; i < count; i++) { + ent = (hhTalonTarget *)gameLocal.talonTargets[i]; + ent->Hide(); + } +} + +//============================================================================= +// +// hhTalonTarget::~hhTalonTarget +// +//============================================================================= + +hhTalonTarget::~hhTalonTarget() { + gameLocal.talonTargets.Remove( this ); +} + +//============================================================================= +// +// hhTalonTarget::Reached +// +// Called when Talon reaches the target point +//============================================================================= + +void hhTalonTarget::Reached( hhTalon *talon ) { + if( !talon ) { + return; + } + + // Landing on a perch spot sends an action message to the spot's targets + int num = targets.Num(); + for (int ix=0; ixPostEventMS(&EV_TalonAction, 0, talon, true); + } + } +} + +//============================================================================= +// +// hhTalonTarget::Left +// +// Called when Talon leaves the target point +//============================================================================= + +void hhTalonTarget::Left( hhTalon *talon ) { + if( !talon ) { + return; + } + + // Leaving on a perch spot sends an action message to the spot's targets + int num = targets.Num(); + for (int ix=0; ixPostEventMS(&EV_TalonAction, 0, talon, false); + } + } +} + +//============================================================================= +// +// hhTalonTarget::Event_CallTalon +// +// Calls Talon to this point and forces him to stay on this point unless released +// or called to another point +//============================================================================= + +void hhTalonTarget::Event_CallTalon( float vel, float rot, float perch ) { + hhPlayer *player; + hhTalon *talon; + + // Find Talon + player = static_cast( gameLocal.GetLocalPlayer() ); + if ( !player->talon.IsValid() ) { + return; + } + + talon = player->talon.GetEntity(); + + talon->SetForcedTarget( true ); + + talon->SetForcedPhysicsFactors( vel, rot, perch ); + + talon->FindTalonTarget( NULL, this, false ); // Force this entity as the target + talon->Event_TakeOffAnim(); + + talon->EnterFlyState(); +} + +//============================================================================= +// +// hhTalonTarget::Event_ReleaseTalon +// +// Releases Talon from this perch spot and allows him to continue his normal scripting +//============================================================================= + +void hhTalonTarget::Event_ReleaseTalon() { + hhPlayer *player; + hhTalon *talon; + + // Find Talon + player = static_cast( gameLocal.GetLocalPlayer() ); + if ( !player->talon.IsValid() ) { + return; + } + + talon = player->talon.GetEntity(); + + talon->SetForcedTarget( false ); + talon->SetForcedPhysicsFactors( 1.0f, 1.0f, 1.0f ); +} + +//============================================================================= +// +// hhTalonTarget::Event_SetPerchState +// +// Set the state if Talon can or cannot fly towards this target +//============================================================================= + +void hhTalonTarget::Event_SetPerchState( float newState ) { + bValidForTalon = ( newState > 0 ) ? true : false; +} diff --git a/src/Prey/game_talon.h b/src/Prey/game_talon.h new file mode 100644 index 0000000..2ab5fd8 --- /dev/null +++ b/src/Prey/game_talon.h @@ -0,0 +1,194 @@ + +#ifndef __GAME_TALON_H__ +#define __GAME_TALON_H__ + +#define MAX_TALON_PTS 16 + +typedef enum talontargetType_s { + TTT_NORMAL, + TTT_IMPORTANT, + TTT_BENEFICIAL +} talonTargetType_t; + +extern const idEventDef EV_TalonAction; + +class hhTalonTarget; + +//----------------------------------------------------------------------------- + +class hhTalon : public hhMonsterAI { +public: + CLASS_PROTOTYPE(hhTalon); + + void Spawn(void); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + hhTalon(); + ~hhTalon(); + void SummonTalon(void); + void OwnerEnteredVehicle( void ); + void OwnerExitedVehicle( void ); + + void SetOwner( hhPlayer *newOwner ); + hhPlayer *GetOwner( void ) { return owner.GetEntity(); } + void FindTalonTarget( idEntity *skipEntity, hhTalonTarget *forceTarget, bool bForcePlayer ); + void CalculateTalonTargetLocation(); + + bool CheckReachedTarget(float distance); + void CheckCollisions(float deltaTime); + + void Portalled( idEntity *portal ); + void Show(); + + void SetForcedTarget( bool force ) { bForcedTarget = force; } + void SetForcedPhysicsFactors( float newVel, float newRot, float newPerch ) { velocityFactor = newVel; rotationFactor = newRot; perchRotationFactor = newPerch; } + bool UpdateAnimationControllers(); + + bool FindEnemy( void ); + + void EnterTommyState(); + void EnterFlyState(); + void EnterVehicleState(); + void ExitVehicleState(void); + + void EnterPerchState(); + void EnterAttackState(); + void EndState(); + + void Event_LandAnim(void); + void Event_PreLandAnim(void); + void Event_IdleAnim(void); + void Event_TommyIdleAnim(void); + void Event_FlyAnim(void); + void Event_GlideAnim(void); + void Event_TakeOffAnim(void); + void Event_TakeOffAnimB(void); + + void ReturnToTommy( void ); + + virtual int HasAmmo( ammo_t type, int amount ) { return 0; } + virtual bool UseAmmo( ammo_t type, int amount ) { return false ; } + + bool IsAttacking( void ) { return state == StateAttack; } + +protected: + void Think(); + void FlyMove( void ); + void AdjustFlyingAngles(); + bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + void CrashLand( const idVec3 &oldOrigin, const idVec3 &oldVelocity ); + + void Damage( idEntity *inflictor, idEntity *attack, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + void Event_PerchChatter(void); + void Event_PerchSquawk(void); // Perched on a spot and squawking + + void Event_CheckForTarget(); // Checks for nearby targets to land upon + void Event_CheckForEnemy(); // Checks for nearby enemies + + void PerchTicker(void); // Perched on a spot + void FlyTicker(void); // Flying to a perch spot or to Tommy + void TommyTicker(void); // Perched on Tommy's shoulder + void AttackTicker(void); // Attacking an enemy + void VehicleTicker(void); // Owner is in a vehicle, so warp from talon point to talon point + + void StartAttackFX(); + void StopAttackFX(); + +protected: + idEntityPtr owner; + idVec3 velocity; + idVec3 acceleration; + + hhTalonTarget *talonTarget; + idVec3 talonTargetLoc; + idMat3 talonTargetAxis; + + idVec3 lastCheckOrigin; // Used to check if the bird flew through a wall + + float checkTraceTime; // Used to determine when to trace for world collisions + float checkFlyTime; // Used to timeout and find a new flying spot + float flyStraightTime; // Used to force Talon to fly straight for a short time + + bool bLanding; + bool bReturnToTommy; // Forces the bird to return to Tommy, no matter what + + bool bForcedTarget; // True if talon is forced to stay at particular talon target + + bool bClawingAtEnemy; // True if talon is currently playing his attack anim and clawing at creature's heads + + float velocityFactor; + float rotationFactor; + float perchRotationFactor; + + // Anims + int flyAnim; + int glideAnim; + int prelandAnim; + int landAnim; + int idleAnim; + int tommyIdleAnim; + int squawkAnim; + int stepAnim; + int takeOffAnim; + int takeOffAnimB; + int attackAnim; + int preAttackAnim; + + const idDeclSkin *openWingsSkin; + const idDeclSkin *closedWingsSkin; + + // Task variables + idEntityPtr enemy; // Used for attacking + + idEntityPtr trailFx; + + enum States { + StateNone = 0, + StateTommy, + StateFly, + StateVehicle, + StatePerch, + StateAttack + } state; + const idEventDef *stateEvent; // Ticker event for current state +}; + +//----------------------------------------------------------------------------- + +class hhTalonTarget : public idEntity { + friend class hhTalon; + +public: + CLASS_PROTOTYPE(hhTalonTarget); + + void Spawn(void); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + ~hhTalonTarget(); + + virtual void Reached( hhTalon *talon ); // Called when Talon reached this point + virtual void Left( hhTalon *talon ); // Called when Talon leaves this point + + static void ShowTargets( void ); + static void HideTargets( void ); + + void SetSquawk( bool newSquawk ) { bShouldSquawk = newSquawk; } + +protected: + void Event_CallTalon( float vel, float rot, float perch ); + void Event_SetPerchState( float newState ); + + void Event_ReleaseTalon(); + + int priority; // priority value. Zero is default, higher numbers is a higher priority + // -1 skips the target spot completely + bool bNotInSpiritWalk; // Talon will skip this target if the player is spiritwalking + + bool bShouldSquawk; // if Talon should squawk on this spot or not + bool bValidForTalon; // if Talon is attacted to this spot or not +}; + +#endif /* __GAME_TALON_H__ */ diff --git a/src/Prey/game_targetproxy.cpp b/src/Prey/game_targetproxy.cpp new file mode 100644 index 0000000..f78b06f --- /dev/null +++ b/src/Prey/game_targetproxy.cpp @@ -0,0 +1,282 @@ +// Game_targetproxy.cpp +// + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +//========================================================================== +// +// hhModelProxy +// +// Keeps model/anims in sync with another entity +//========================================================================== +CLASS_DECLARATION(hhAnimatedEntity, hhModelProxy) +END_CLASS + +void hhModelProxy::Spawn() { + renderEntity.noSelfShadow = true; + renderEntity.noShadow = true; + + GetPhysics()->SetContents(0); + + BecomeActive(TH_THINK|TH_TICKER); + + // Required so that models move in place. + GetAnimator()->RemoveOriginOffset( true ); + + original = NULL; +} + +void hhModelProxy::Save(idSaveGame *savefile) const { + original.Save(savefile); + owner.Save(savefile); +} + +void hhModelProxy::Restore( idRestoreGame *savefile ) { + original.Restore(savefile); + owner.Restore(savefile); +} + +void hhModelProxy::WriteToSnapshot( idBitMsgDelta &msg ) const { + GetPhysics()->WriteToSnapshot(msg); + WriteBindToSnapshot(msg); + + msg.WriteBits(original.GetSpawnId(), 32); + msg.WriteBits(owner.GetSpawnId(), 32); + + msg.WriteBits(renderEntity.allowSurfaceInViewID, GENTITYNUM_BITS); +} + +void hhModelProxy::ReadFromSnapshot( const idBitMsgDelta &msg ) { + GetPhysics()->ReadFromSnapshot(msg); + ReadBindFromSnapshot(msg); + + original.SetSpawnId(msg.ReadBits(32)); + owner.SetSpawnId(msg.ReadBits(32)); + + renderEntity.allowSurfaceInViewID = msg.ReadBits(GENTITYNUM_BITS); +} + +void hhModelProxy::ClientPredictionThink( void ) { + Think(); +} + + +/* +=============== +hhModelProxy::SetOriginAndAxis +Note: This was ripped from idEntity::UpdateModelTransform (Was ripped from idEntity) +=============== +*/ +void hhModelProxy::SetOriginAndAxis( idEntity *entity ) { + idVec3 origin; + idMat3 axis; + + + if ( original->GetPhysicsToVisualTransform( origin, axis ) ) { + axis = axis * original->GetPhysics()->GetAxis(); + origin = original->GetPhysics()->GetOrigin() + origin * axis; + } + else { + axis = original->GetPhysics()->GetAxis(); + origin = original->GetPhysics()->GetOrigin(); + } + + SetOrigin( origin ); + SetAxis( axis ); +} + + +void hhModelProxy::SetOriginal(idEntity *other) { + original = other; + if (original.IsValid() && original.GetEntity()) { + SetOriginAndAxis( original.GetEntity() ); + Bind(original.GetEntity(), true); + } +} + +idEntity *hhModelProxy::GetOriginal() { + if (!original.IsValid()) { + return NULL; + } + return original.GetEntity(); +} + +void hhModelProxy::SetOwner(hhPlayer *other) { + owner = other; + renderEntity.allowSurfaceInViewID = owner->entityNumber+1; +} + +hhPlayer *hhModelProxy::GetOwner() { + if (!owner.IsValid()) { + return NULL; + } + return owner.GetEntity(); +} + +void hhModelProxy::Ticker() { + if (!owner.IsValid() || !original.IsValid()) { //rww - added validation for "original" + PostEventMS(&EV_Remove, 0); + return; + } + UpdateVisualState(); +} + +void hhModelProxy::UpdateVisualState() { + + // Update visual state to state of original + if (original.IsValid() && original.GetEntity() && !fl.hidden) { + if (GetRenderEntity()->hModel != original->GetRenderEntity()->hModel) { + const char *modelname; + + if (gameLocal.isMultiplayer && (original->IsType(hhPlayer::Type) || original->IsType(hhSpiritProxy::Type))) { + //rww - players and spirit proxies in mp have variable model names, but keep the original "model" field + //as a fallback. + modelname = original->spawnArgs.GetString( "playerModel" ); + if (!modelname || !modelname[0]) { //empty string, fallback to "model" + modelname = original->spawnArgs.GetString( "model" ); + } + } + else { + modelname = original->spawnArgs.GetString( "model" ); + } + + SetModel(modelname); + } + + // Run our physics to keep our binding to the original up to date. + GetPhysics()->Evaluate( gameLocal.time - gameLocal.previousTime, gameLocal.time ); + + if ( original->GetAnimator() ) { + GetAnimator()->CopyAnimations( *( original->GetAnimator() ) ); + GetAnimator()->CopyPoses( *( original->GetAnimator() ) ); + } + + UpdateVisuals(); + } +} + +void hhModelProxy::ProxyFinished() { + PostEventMS(&EV_Remove, 0); +} + + +//========================================================================== +// +// hhTargetProxy +// +// Special proxy for spirit bow vision that allows sight through walls +//========================================================================== +const idEventDef EV_FadeOutProxy(""); + +CLASS_DECLARATION(hhModelProxy, hhTargetProxy) + EVENT( EV_FadeOutProxy, hhTargetProxy::Event_FadeOut ) +END_CLASS + +void hhTargetProxy::Spawn() { + renderEntity.weaponDepthHack = true; + SetShaderParm(5, 0.5f + ((float)(entityNumber % 8)) / 8.0f); // Unique number [0.5 .. 1.5] for jitter + + owner = NULL; + + //TODO: Need a way to be visible only if owner is local client + + fadeAlpha.Init(gameLocal.time, BOWVISION_FADEIN_DURATION, 0.0f, 1.0f); + StayAlive(); + + fl.networkSync = true; +} + +void hhTargetProxy::Save(idSaveGame *savefile) const { + savefile->WriteFloat( fadeAlpha.GetStartTime() ); // idInterpolate + savefile->WriteFloat( fadeAlpha.GetDuration() ); + savefile->WriteFloat( fadeAlpha.GetStartValue() ); + savefile->WriteFloat( fadeAlpha.GetEndValue() ); +} + +void hhTargetProxy::Restore( idRestoreGame *savefile ) { + float set; + + savefile->ReadFloat( set ); // idInterpolate + fadeAlpha.SetStartTime( set ); + savefile->ReadFloat( set ); + fadeAlpha.SetDuration( set ); + savefile->ReadFloat( set ); + fadeAlpha.SetStartValue(set); + savefile->ReadFloat( set ); + fadeAlpha.SetEndValue( set ); +} + +void hhTargetProxy::WriteToSnapshot( idBitMsgDelta &msg ) const { + hhModelProxy::WriteToSnapshot(msg); + + msg.WriteFloat(fadeAlpha.GetStartTime()); + msg.WriteFloat(fadeAlpha.GetDuration()); + msg.WriteFloat(fadeAlpha.GetStartValue()); + msg.WriteFloat(fadeAlpha.GetEndValue()); +} + +void hhTargetProxy::ReadFromSnapshot( const idBitMsgDelta &msg ) { + hhModelProxy::ReadFromSnapshot(msg); + + fadeAlpha.SetStartTime(msg.ReadFloat()); + fadeAlpha.SetDuration(msg.ReadFloat()); + fadeAlpha.SetStartValue(msg.ReadFloat()); + fadeAlpha.SetEndValue(msg.ReadFloat()); +} + +void hhTargetProxy::ProxyFinished() { + CancelEvents(&EV_FadeOutProxy); + fadeAlpha.Init(gameLocal.time, BOWVISION_FADEOUT_DURATION, 1.0f, 0.0f); + PostEventMS(&EV_Remove, BOWVISION_FADEOUT_DURATION); +} + +void hhTargetProxy::UpdateVisualState() { + //TODO: If owner not in spirit walk, immediately hide and post a remove + // This should handle the case of being in altmode and toggling spiritwalk off + // so that we don't see the targets fade out in non-spiritwalk mode + + //TODO: net play support: snapshot owner->entityNumber. Hide if owner isn't the localclient +// if (owner->entityNumber == gameLocal.localClientNum) { +// } + + + if( !original.IsValid() || !original.GetEntity() || original->IsHidden() ) { // mdl: Don't show hidden entities (keeps actors from showing up in bowvision after gibbing) + //rww - added validity checks for "original" + Hide(); + return; + } + + // Check to see if within players view + idVec3 toProxy = GetPhysics()->GetOrigin() - owner->GetPhysics()->GetOrigin(); + float score = toProxy * (owner->viewAngles.ToMat3()[0]); + if (score < 0.2f) { + Hide(); + } + else { + Show(); + SetShaderParm(3, fadeAlpha.GetCurrentValue(gameLocal.time)); + hhModelProxy::UpdateVisualState(); + } +} + +void hhTargetProxy::StayAlive() { + // Stay alive for BOWVISION_UPDATE_FREQUENCY longer (MS) + a little slop + CancelEvents(&EV_Remove); + CancelEvents(&EV_FadeOutProxy); + PostEventMS(&EV_FadeOutProxy, BOWVISION_UPDATE_FREQUENCY * 2); + + // Fade back in if fading out, otherwise stay faded in + float curvalue = fadeAlpha.GetCurrentValue(gameLocal.time); + fadeAlpha.Init(gameLocal.time, BOWVISION_FADEIN_DURATION, curvalue, 1.0f); + + if ( owner.IsValid() && owner.GetEntity() ) { + UpdateVisualState(); + } +} + +void hhTargetProxy::Event_FadeOut() { + ProxyFinished(); +} diff --git a/src/Prey/game_targetproxy.h b/src/Prey/game_targetproxy.h new file mode 100644 index 0000000..0c8ceb3 --- /dev/null +++ b/src/Prey/game_targetproxy.h @@ -0,0 +1,63 @@ + +#ifndef __GAME_TARGETPROXY_H__ +#define __GAME_TARGETPROXY_H__ + +class hhModelProxy : public hhAnimatedEntity { +public: + CLASS_PROTOTYPE( hhModelProxy ); + + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - net code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + + void SetOriginal(idEntity *other); + idEntity * GetOriginal(); + virtual void ProxyFinished(); + virtual void Ticker(); + virtual void UpdateVisualState(); + virtual void SetOriginAndAxis( idEntity * ); + void SetOwner(hhPlayer *other); + hhPlayer * GetOwner(); + +protected: + //rww - these need to be entityptr's, not raw pointers + idEntityPtr original; + idEntityPtr owner; +}; + + +#define BOWVISION_FADEIN_DURATION 1500 +#define BOWVISION_FADEOUT_DURATION 1500 +#define BOWVISION_UPDATE_FREQUENCY 12*MAX_GENTITIES/BOWVISION_ROVER_STRIDE // Rate at which to search for proxies +#define BOWVISION_ROVER_STRIDE 25 // Number of entities to check each tick + +class hhTargetProxy : public hhModelProxy { +public: + CLASS_PROTOTYPE( hhTargetProxy ); + + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - net code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + void StayAlive(); + virtual void ProxyFinished(); + virtual void UpdateVisualState(); + +protected: + void Event_FadeOut(); + +protected: + idInterpolate fadeAlpha; +}; + + +#endif diff --git a/src/Prey/game_targets.cpp b/src/Prey/game_targets.cpp new file mode 100644 index 0000000..861af6a --- /dev/null +++ b/src/Prey/game_targets.cpp @@ -0,0 +1,849 @@ +/* + game_targets.cpp + + These entities, when targeted, perform an action + +*/ + + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_Autosave( "" ); +const idEventDef EV_FinishedSave( "" ); + +/*********************************************************************** + + hhTarget_SetSkin + + Set all targets to given skin +***********************************************************************/ + +CLASS_DECLARATION( idTarget, hhTarget_SetSkin ) + EVENT( EV_Activate, hhTarget_SetSkin::Event_Trigger ) +END_CLASS + +/* +================ +hhTarget_SetSkin::Spawn +================ +*/ +void hhTarget_SetSkin::Spawn( void ) { + spawnArgs.GetString( "skinName", "", skinName ); +} + + +/* +================ + hhTarget_SetSkin::Event_Trigger +================ +*/ +void hhTarget_SetSkin::Event_Trigger( idEntity *activator ) { + idEntity *ent = NULL; + + // Set all targets to the specified skin + for( int t=0; tSetSkinByName( (ent->GetSkin()) ? NULL : skinName.c_str() ); + } +} + + + +/*********************************************************************** + + hhTarget_Enable + + Enable all targets +***********************************************************************/ + +CLASS_DECLARATION( idTarget, hhTarget_Enable ) + EVENT( EV_Activate, hhTarget_Enable::Event_Trigger ) +END_CLASS + +/* +================ +hhTarget_Enable::Spawn +================ +*/ +void hhTarget_Enable::Spawn( void ) { +} + + +/* +================ + hhTarget_Enable::Event_Trigger +================ +*/ +void hhTarget_Enable::Event_Trigger( idEntity *activator ) { + + // Enable all targets + for (int t=0; tPostEventMS( &EV_Enable, 0 ); + } + } +} + + +/*********************************************************************** + + hhTarget_Disable + + Disable all targets +***********************************************************************/ + +CLASS_DECLARATION( idTarget, hhTarget_Disable ) + EVENT( EV_Activate, hhTarget_Disable::Event_Trigger ) +END_CLASS + +/* +================ +hhTarget_Disable::Spawn +================ +*/ +void hhTarget_Disable::Spawn( void ) { +} + + +/* +================ + hhTarget_Disable::Event_Trigger +================ +*/ +void hhTarget_Disable::Event_Trigger( idEntity *activator ) { + + // Disable all targets + for (int t=0; tPostEventMS( &EV_Disable, 0 ); + } + } +} + + +/*********************************************************************** + + hhTarget_Earthquake + +***********************************************************************/ +const idEventDef EV_TurnOff( "", NULL ); + +CLASS_DECLARATION( idTarget, hhTarget_Earthquake ) + EVENT( EV_Activate, hhTarget_Earthquake::Event_Trigger ) + EVENT( EV_TurnOff, hhTarget_Earthquake::Event_TurnOff ) +END_CLASS + +/* +================ +hhTarget_Earthquake::Spawn +================ +*/ +void hhTarget_Earthquake::Spawn( void ) { + //PDMMERGE: Modify to use the sound for the visual shake exclusively + // and forcefield for the world shake exclusively. + //This duplicates functionality of func_earthquake. Check their effect + //out and see which we want to keep. If keeping, this could then become func_earthquake. + //Could alternatively use a sound player and a func_idforcefield + + shakeTime = SEC2MS(spawnArgs.GetFloat("shake_time")); + shakeAmplitude = spawnArgs.GetFloat("shake_severity"); + + forceField.RandomTorque( shakeAmplitude * 40); + + // Grab clip model to use for checking who's currently encroaching me + cm = new idClipModel( GetPhysics()->GetClipModel() ); + if( !cm ) { + gameLocal.Error( "hhTarget_Earthquake::Spawn: Unable to spawn idClipModel\n" ); + return; + } + forceField.SetClipModel( cm ); + // remove the collision model from the physics object + GetPhysics()->SetClipModel( NULL, 1.0f ); +} + +void hhTarget_Earthquake::Save(idSaveGame *savefile) const { + savefile->WriteFloat( shakeTime ); + savefile->WriteFloat( shakeAmplitude ); + savefile->WriteStaticObject( forceField ); + savefile->WriteClipModel( cm ); +} + +void hhTarget_Earthquake::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( shakeTime ); + savefile->ReadFloat( shakeAmplitude ); + savefile->ReadStaticObject( forceField ); + savefile->ReadClipModel( cm ); +} + +/* +================ + hhTarget_Earthquake::Think +================ +*/ +void hhTarget_Earthquake::Think() { + if (thinkFlags & TH_THINK) { + forceField.Evaluate( gameLocal.time ); + } +} + + +/* +================ +hhTarget_Earthquake::Event_TurnOff +================ +*/ +void hhTarget_Earthquake::Event_TurnOff( void ) { + hhPlayer *player; + BecomeInactive(TH_THINK); + StopSound(SND_CHANNEL_BODY); + + // Turn camera interpolator back on + for (int i = 0; i < gameLocal.numClients; i++ ) { + player = static_cast(gameLocal.entities[ i ]); + if ( player ) { + player->cameraInterpolator.SetInterpolationType( IT_VariableMidPointSinusoidal ); + } + } +} + +/* +================ + hhTarget_Earthquake::Event_Trigger +================ +*/ +void hhTarget_Earthquake::Event_Trigger( idEntity *activator ) { + hhPlayer *player; + + if( !cm ) { // No collision model + return; + } + + StartSound("snd_quake", SND_CHANNEL_BODY); + + idBounds bounds; + bounds.FromTransformedBounds( cm->GetBounds(), cm->GetOrigin(), cm->GetAxis() ); + //gameRenderWorld->DebugBounds(colorRed, bounds, vec3_origin, 5000); + + for (int i = 0; i < gameLocal.numClients; i++ ) { + player = static_cast(gameLocal.entities[ i ]); + if ( !player || ( player->fl.notarget ) ) { + continue; + } + + if( bounds.IntersectsBounds(player->GetPhysics()->GetAbsBounds()) ) { + player->cameraInterpolator.SetInterpolationType(IT_None); + } + } + + // Turn on physics + BecomeActive( TH_THINK ); + PostEventSec( &EV_TurnOff, MS2SEC(shakeTime) ); +} + + +/*********************************************************************** + + hhTarget_SetLightParm + +***********************************************************************/ + +CLASS_DECLARATION( idTarget, hhTarget_SetLightParm ) + EVENT( EV_Activate, hhTarget_SetLightParm::Event_Activate ) +END_CLASS + +/* +================ +hhTarget_SetLightParm::Event_Activate +================ +*/ +void hhTarget_SetLightParm::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->IsType(idLight::Type) ) { + static_cast(ent)->SetLightParm( parmnum, value ); + } + } + if (spawnArgs.GetBool("toggle") && (value == 0 || value == 1)) { + int val = value; + val ^= 1; + value = val; + spawnArgs.SetFloat(va("shaderParm%d", parmnum), value); + } + } + } +} + +/*********************************************************************** + + hhTarget_PlayWeaponAnim + +***********************************************************************/ + +CLASS_DECLARATION( idTarget, hhTarget_PlayWeaponAnim ) + EVENT( EV_Activate, hhTarget_PlayWeaponAnim::Event_Trigger ) +END_CLASS + +/* +================ + hhTarget_PlayWeaponAnim::Event_Trigger +================ +*/ +void hhTarget_PlayWeaponAnim::Event_Trigger( idEntity *activator ) { + hhPlayer* pPlayer = NULL; + + if( activator && activator->IsType(hhPlayer::Type) ) { + pPlayer = static_cast( activator ); + if( pPlayer ) { + pPlayer->ProcessEvent( &EV_PlayWeaponAnim, spawnArgs.GetString("anim", "initialPickup"), spawnArgs.GetInt("numTries") ); + } + } +} + + +//========================================================================== +// +// hhTarget_ControlVehicle +// +//========================================================================== + +CLASS_DECLARATION(idTarget, hhTarget_ControlVehicle) + EVENT( EV_Activate, hhTarget_ControlVehicle::Event_Activate ) +END_CLASS + + +void hhTarget_ControlVehicle::Spawn() { +} + +void hhTarget_ControlVehicle::Event_Activate( idEntity *activator ) { + hhVehicle *vehicle = NULL; + + if (activator->IsType(hhPlayer::Type)) { + hhPlayer *player = static_cast(activator); + + // Search target list to find vehicle + int numTargets = targets.Num(); + for (int ix=0; ixIsType(hhVehicle::Type)) { + vehicle = static_cast(targets[ix].GetEntity()); + } + } + } + + if( vehicle ) { + player->EnterVehicle( vehicle ); + } + } +} + + +//========================================================================== +// +// hhTarget_AttachToRail +// +// OBSOLETE, just target the hhRailRide directly +//========================================================================== + +CLASS_DECLARATION(idTarget, hhTarget_AttachToRail) + EVENT( EV_Activate, hhTarget_AttachToRail::Event_Activate ) +END_CLASS + + +void hhTarget_AttachToRail::Spawn() { +} + +// Bind player to given bone of a targeted hhAnimator +void hhTarget_AttachToRail::Event_Activate( idEntity *activator ) { + hhRailRide *rail = NULL; + + if (activator && activator->IsType(hhPlayer::Type)) { + hhPlayer *player = static_cast(activator); + + // Search target list to find hhRailController + int numTargets = targets.Num(); + for (int ix=0; ixIsType(hhRailRide::Type)) { + rail = static_cast(targets[ix].GetEntity()); + } + } + } + + if (rail) { + rail->Attach(player, false); + } + } +} + +//========================================================================== +// +// hhTarget_EnableReactions +// +//========================================================================== +CLASS_DECLARATION( idTarget, hhTarget_EnableReactions ) + EVENT( EV_Activate, hhTarget_EnableReactions::Event_Activate ) +END_CLASS + +void hhTarget_EnableReactions::Spawn() { +} + +void hhTarget_EnableReactions::Event_Activate(idEntity *activator) { + // Enable reactions on all targets + for (int t=0; tfl.refreshReactions = true; + } + } +} + +//========================================================================== +// +// hhTarget_DisableReactions +// +//========================================================================== +CLASS_DECLARATION( idTarget, hhTarget_DisableReactions ) + EVENT( EV_Activate, hhTarget_DisableReactions::Event_Activate ) +END_CLASS + +void hhTarget_DisableReactions::Spawn() { +} + +void hhTarget_DisableReactions::Event_Activate(idEntity *activator) { + // Disable reactions on all targets + for (int t=0; tfl.refreshReactions = false; + } + } +} + +//========================================================================== +// +// hhTarget_EnablePassageway +// +//========================================================================== +CLASS_DECLARATION( idTarget, hhTarget_EnablePassageway ) + EVENT( EV_Activate, hhTarget_EnablePassageway::Event_Activate ) +END_CLASS + +void hhTarget_EnablePassageway::Spawn() { +} + +void hhTarget_EnablePassageway::Event_Activate(idEntity *activator) { + // Enable reactions on all targets + for (int t=0; tIsType(hhAIPassageway::Type) ) { + static_cast(ent)->SetEnablePassageway(TRUE); + } + } +} + +//========================================================================== +// +// hhTarget_DisablePassageway +// +//========================================================================== +CLASS_DECLARATION( idTarget, hhTarget_DisablePassageway ) + EVENT( EV_Activate, hhTarget_DisablePassageway::Event_Activate ) +END_CLASS + +void hhTarget_DisablePassageway::Spawn() { +} + +void hhTarget_DisablePassageway::Event_Activate(idEntity *activator) { + // Enable reactions on all targets + for (int t=0; tIsType(hhAIPassageway::Type)) { + static_cast(ent)->SetEnablePassageway(FALSE); + } + } +} + + +const idEventDef EV_TriggerTargets( "" ); + +//========================================================================== +// +// hhTarget_PatternRelay +// +// When triggered, relays trigger messages in a pattern +//========================================================================== +CLASS_DECLARATION( idTarget, hhTarget_PatternRelay ) + EVENT( EV_Activate, hhTarget_PatternRelay::Event_Activate ) + EVENT( EV_TriggerTargets, hhTarget_PatternRelay::Event_TriggerTargets ) +END_CLASS + +void hhTarget_PatternRelay::Spawn() { + timeGranularity = spawnArgs.GetFloat("timestep", "1"); +} + +void hhTarget_PatternRelay::Event_Activate(idEntity *activator) { + idStr pattern = spawnArgs.GetString("pattern", "xoxoxo"); + + //TODO: Clear out any from last time around? + + float issueTime = 0.0f; + int length = pattern.Length(); + for (int ix=0; ixhud->SetStateInt("subtitlex", bCentered ? 0 : x); + player->hud->SetStateInt("subtitley", y); + player->hud->SetStateInt("subtitlecentered", bCentered); + player->hud->SetStateString("subtitletext", text); + player->hud->StateChanged(gameLocal.time); + player->hud->HandleNamedEvent("DisplaySubtitle"); + + CancelEvents(&EV_TurnOff); + PostEventSec(&EV_TurnOff, duration); +} + +void hhTarget_Subtitle::Event_FadeOutText() { + idPlayer *player = gameLocal.GetLocalPlayer(); + if (player && player->hud) { + player->hud->HandleNamedEvent("RemoveSubtitle"); + } +} + + +//========================================================================== +// +// hhTarget_EndLevel +// +// When triggered, does a level transition +//========================================================================== +CLASS_DECLARATION( idTarget_EndLevel, hhTarget_EndLevel ) + EVENT( EV_Activate, hhTarget_EndLevel::Event_Activate ) +END_CLASS + +void hhTarget_EndLevel::Event_Activate(idEntity *activator) { + idUserInterface *guiLoading = NULL; + idStr nextMap; + if ( spawnArgs.GetString( "nextMap", "", nextMap ) ) { + + // Session code hasn't yet loaded the gui for next level here, so we preload it based on the same + // logic as it uses in idSessionLocal::LoadLoadingGui(). + idStr stripped = nextMap; + stripped.StripFileExtension(); + stripped.StripPath(); + idStr guiMap = va( "guis/map/%s.gui", stripped.c_str() ); + if (uiManager->CheckGui( guiMap ) ) { + guiLoading = uiManager->FindGui( guiMap, true, false, true ); + } else { + guiLoading = uiManager->FindGui( "guis/map/loading.gui", true, false, true ); + } + guiLoading->SetStateFloat("map_loading", 0.0f); + guiLoading->SetStateBool("showddainfo", true); + + const idDecl *mapDecl = declManager->FindType(DECL_MAPDEF, stripped.c_str(), false ); + if ( mapDecl ) { + const idDeclEntityDef *mapInfo = static_cast(mapDecl); + const char *friendlyName = mapInfo->dict.GetString("name"); + guiLoading->SetStateString("friendlyname", friendlyName); + } + + guiLoading->StateChanged(gameLocal.time); + + // HUMANHEAD CJR: If the player hits this and they are spiritwalking, stop the spiritwalk before loading the next level + if ( activator->IsType( hhPlayer::Type ) ) { + hhPlayer *player = static_cast( activator ); + if ( player ) { + + // Player managed kill themselves at the end of level? Clear deathwalk flags + if (player->IsDeathWalking()) { + player->bDeathWalk = false; + player->SetHealth( 50.0f ); // Don't say I never got you anything + player->SetSpiritPower( 0.0f ); // Clear spirit power + } + + if ( player->IsSpiritWalking() ) { + player->PutawayEtherealWeapon(); // Don't do a full spiritstop, as that teleports the player, too. Just fix the player's weapon + } + } + } + // END CJR + } + + idTarget_EndLevel::Event_Activate(activator); +} + + +//========================================================================== +// +// hhTarget_ConsolidatePlayers +// +// When triggered, consolidates all players into a single view (coop) +//========================================================================== +CLASS_DECLARATION( idTarget, hhTarget_ConsolidatePlayers ) + EVENT( EV_Activate, hhTarget_ConsolidatePlayers::Event_Activate ) +END_CLASS + +void hhTarget_ConsolidatePlayers::Event_Activate(idEntity *activator) { + if (gameLocal.IsCooperative()) { + //TODO: Implement + } +} + + +//========================================================================== +// +// hhTarget_WarpPlayers +// +// When triggered, teleports all players to targeted start spots (coop) +//========================================================================== +CLASS_DECLARATION( idTarget, hhTarget_WarpPlayers ) + EVENT( EV_Activate, hhTarget_WarpPlayers::Event_Activate ) +END_CLASS + +void hhTarget_WarpPlayers::Event_Activate(idEntity *activator) { + if (gameLocal.IsCooperative()) { + //TODO: Implement + } +} + +//========================================================================== +// +// hhTarget_FollowPath +// +// When triggered, make an ai follow a path +//========================================================================== +CLASS_DECLARATION( idTarget, hhTarget_FollowPath ) + EVENT( EV_Activate, hhTarget_FollowPath::Event_Activate ) +END_CLASS + +void hhTarget_FollowPath::Event_Activate(idEntity *activator) { + int i; + idEntity *ent; + const function_t *func; + const char *funcName; + idThread *thread; + hhMonsterAI *entAI; + + 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() ); + } + entAI = static_cast(ent); + if ( entAI && entAI->AI_FOLLOWING_PATH ) { + gameLocal.Warning( "hhTarget_FollowPath %s already following path", ent->name.c_str() ); + continue; + } + ent->spawnArgs.Set( "alt_path", spawnArgs.GetString( "alt_path" ) ); + // create a thread and call the function + thread = new idThread(); + thread->CallFunction( ent, func, true ); + thread->Start(); + } + } +} + +//=============================================================================== +// +// hhTarget_LockDoor +// +//=============================================================================== + +CLASS_DECLARATION( idTarget, hhTarget_LockDoor ) + EVENT( EV_Activate, hhTarget_LockDoor::Event_Activate ) +END_CLASS + +void hhTarget_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(); + if ( ent && ent->IsType( idDoor::Type ) ) { + if ( static_cast( ent )->IsLocked() ) { + static_cast( ent )->Lock( 0 ); + } else { + static_cast( ent )->Lock( lock ); + } + } + if ( ent && ent->IsType( hhModelDoor::Type ) ) { + if ( static_cast( ent )->IsLocked() ) { + static_cast( ent )->Lock( 0 ); + } else { + static_cast( ent )->Lock( lock ); + } + } + if ( ent && ent->IsType( hhProxDoor::Type ) ) { + if ( static_cast( ent )->IsLocked() ) { + static_cast( ent )->Lock( 0 ); + } else { + static_cast( ent )->Lock( lock ); + } + } + } +} + +//========================================================================== +// +// hhTarget_DisplayGui +// +// When triggered, displays full screen gui +//========================================================================== +CLASS_DECLARATION( idTarget, hhTarget_DisplayGui ) + EVENT( EV_Activate, hhTarget_DisplayGui::Event_Activate ) +END_CLASS + +void hhTarget_DisplayGui::Event_Activate(idEntity *activator) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if (player && player->IsType(hhPlayer::Type)) { + static_cast(player)->SetOverlayGui( spawnArgs.GetString("gui_overlay") ); + } +} + + +//========================================================================== +// +// hhTarget_Autosave +// +// When triggered, autosaves +//========================================================================== +CLASS_DECLARATION( idTarget, hhTarget_Autosave ) + EVENT( EV_Activate, hhTarget_Autosave::Event_Activate ) +END_CLASS + +void hhTarget_Autosave::Event_Activate(idEntity *activator) { + const char *desc = spawnArgs.GetString( "text_savename" ); + if ( !desc || desc[0] == 0 ) { + gameLocal.Warning( "hhTarget_Autosave has no text_savename key" ); + desc = ""; + } + + idStr saveName = va( common->GetLanguageDict()->GetString( "#str_00838" ), desc ); + //HUMANHEAD PCF mdl 05/05/06 - Changed ' to " to avoid problems when saves contained ' + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, va( "savegame \"%s\"", saveName.c_str() ) ); +} + + +//========================================================================== +// +// hhTarget_Show +// +//========================================================================== + +CLASS_DECLARATION( idTarget, hhTarget_Show ) + EVENT( EV_Activate, hhTarget_Show::Event_Activate ) +END_CLASS + +void hhTarget_Show::Event_Activate( idEntity *activator ) { + int i; + idEntity *ent; + + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->Show(); + } + } +} + +//========================================================================== +// +// hhTarget_Hide +// +//========================================================================== + +CLASS_DECLARATION( idTarget, hhTarget_Hide ) + EVENT( EV_Activate, hhTarget_Hide::Event_Activate ) +END_CLASS + +void hhTarget_Hide::Event_Activate( idEntity *activator ) { + int i; + idEntity *ent; + + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->Hide(); + } + } +} + diff --git a/src/Prey/game_targets.h b/src/Prey/game_targets.h new file mode 100644 index 0000000..036f5c7 --- /dev/null +++ b/src/Prey/game_targets.h @@ -0,0 +1,235 @@ + +#ifndef __GAME_TARGETS_H__ +#define __GAME_TARGETS_H__ + +class hhTarget_SetSkin : public idTarget { +public: + CLASS_PROTOTYPE(hhTarget_SetSkin); + + void Spawn(void); + void Save( idSaveGame *savefile ) const { savefile->WriteString(skinName); } + void Restore( idRestoreGame *savefile ) { savefile->ReadString(skinName); } + +protected: + void Event_Trigger( idEntity *activator ); + + idStr skinName; +}; + +class hhTarget_Enable : public idTarget { +public: + CLASS_PROTOTYPE(hhTarget_Enable); + + void Spawn(void); + +protected: + void Event_Trigger( idEntity *activator ); +}; + +class hhTarget_Disable : public idTarget { +public: + CLASS_PROTOTYPE(hhTarget_Disable); + + void Spawn(void); + +protected: + void Event_Trigger( idEntity *activator ); +}; + +class hhTarget_Earthquake : public idTarget { +public: + CLASS_PROTOTYPE(hhTarget_Earthquake); + + void Spawn(void); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + virtual void Present(void) { } + virtual void Think(void); + +protected: + void Event_Trigger( idEntity *activator ); + void Event_TurnOff( void ); + +private: + float shakeTime; + float shakeAmplitude; + idForce_Field forceField; + idClipModel *cm; +}; + +class hhTarget_SetLightParm : public idTarget { +public: + CLASS_PROTOTYPE( hhTarget_SetLightParm ); + +protected: + void Event_Activate( idEntity *activator ); +}; + +class hhTarget_PlayWeaponAnim : public idTarget { +public: + CLASS_PROTOTYPE(hhTarget_PlayWeaponAnim); + +protected: + void Event_Trigger( idEntity *activator ); +}; + +class hhTarget_ControlVehicle : public idTarget { + CLASS_PROTOTYPE( hhTarget_ControlVehicle ); + +public: + void Spawn(); + +protected: + void Event_Activate( idEntity *activator ); +}; + +class hhTarget_AttachToRail : public idTarget { + CLASS_PROTOTYPE( hhTarget_AttachToRail ); + +public: + void Spawn(); + +protected: + void Event_Activate( idEntity *activator ); +}; + +class hhTarget_EnableReactions : public idTarget { + CLASS_PROTOTYPE( hhTarget_EnableReactions ); + +public: + void Spawn(); + +protected: + void Event_Activate( idEntity *activator ); +}; + +class hhTarget_DisableReactions : public idTarget { + CLASS_PROTOTYPE( hhTarget_DisableReactions ); + +public: + void Spawn(); + +protected: + void Event_Activate( idEntity *activator ); +}; + +class hhTarget_EnablePassageway : public idTarget { + CLASS_PROTOTYPE( hhTarget_EnablePassageway ); + +public: + void Spawn(); + +protected: + void Event_Activate( idEntity *activator ); +}; + +class hhTarget_DisablePassageway : public idTarget { + CLASS_PROTOTYPE( hhTarget_DisablePassageway ); + +public: + void Spawn(); + +protected: + void Event_Activate( idEntity *activator ); +}; + +class hhTarget_PatternRelay : public idTarget { + CLASS_PROTOTYPE( hhTarget_PatternRelay ); + +public: + void Spawn(); + void Save( idSaveGame *savefile ) const { savefile->WriteFloat(timeGranularity); } + void Restore( idRestoreGame *savefile ) { savefile->ReadFloat(timeGranularity); } + +protected: + void Event_Activate( idEntity *activator ); + void Event_TriggerTargets(); + + float timeGranularity; +}; + +class hhTarget_Subtitle : public idTarget { + CLASS_PROTOTYPE( hhTarget_Subtitle ); + +public: + void Spawn(); + +protected: + void Event_Activate( idEntity *activator ); + void Event_FadeOutText(); +}; + + +class hhTarget_EndLevel : public idTarget_EndLevel { + CLASS_PROTOTYPE( hhTarget_EndLevel ); + +protected: + virtual void Event_Activate(idEntity *activator); +}; + + +class hhTarget_ConsolidatePlayers : public idTarget { + CLASS_PROTOTYPE( hhTarget_ConsolidatePlayers ); + +protected: + void Event_Activate( idEntity *activator ); +}; + + +class hhTarget_WarpPlayers : public idTarget { + CLASS_PROTOTYPE( hhTarget_WarpPlayers ); + +protected: + void Event_Activate( idEntity *activator ); +}; + +class hhTarget_FollowPath : public idTarget { + CLASS_PROTOTYPE( hhTarget_FollowPath ); + +protected: + void Event_Activate( idEntity *activator ); +}; + +class hhTarget_LockDoor: public idTarget { +public: + CLASS_PROTOTYPE( hhTarget_LockDoor ); + +private: + void Event_Activate( idEntity *activator ); +}; + +class hhTarget_DisplayGui : public idTarget { +public: + CLASS_PROTOTYPE(hhTarget_DisplayGui); + +protected: + void Event_Activate( idEntity *activator ); +}; + +class hhTarget_Autosave : public idTarget { +public: + CLASS_PROTOTYPE(hhTarget_Autosave); + +protected: + void Event_Activate( idEntity *activator ); + void Event_Autosave( void ); + void Event_FinishedSave( void ); +}; + +class hhTarget_Show : public idTarget { +public: + CLASS_PROTOTYPE(hhTarget_Show); + +protected: + void Event_Activate( idEntity *activator ); +}; + +class hhTarget_Hide : public idTarget { +public: + CLASS_PROTOTYPE(hhTarget_Hide); + +protected: + void Event_Activate( idEntity *activator ); +}; + +#endif /* __GAME_TARGETS_H__ */ diff --git a/src/Prey/game_trackmover.cpp b/src/Prey/game_trackmover.cpp new file mode 100644 index 0000000..510f212 --- /dev/null +++ b/src/Prey/game_trackmover.cpp @@ -0,0 +1,172 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_StartNextMove( "", NULL ); +const idEventDef EV_Kill( "" ); + +CLASS_DECLARATION(idMover, hhTrackMover) + EVENT( EV_PostSpawn, hhTrackMover::Event_PostSpawn ) + EVENT( EV_StartNextMove, hhTrackMover::Event_StartNextMove ) + EVENT( EV_Activate, hhTrackMover::Event_Activate ) + EVENT( EV_Deactivate, hhTrackMover::Event_Deactivate ) + EVENT( EV_Kill, hhTrackMover::Event_Kill ) +END_CLASS + +hhTrackMover::hhTrackMover() { + currentNode = NULL; +} + +void hhTrackMover::Spawn( void ) { + timeBetweenMoves = spawnArgs.GetInt("moveDelayMS"); + Hide(); + GetPhysics()->SetContents( 0 ); + fl.takedamage = false; + PostEventMS(&EV_PostSpawn, 0); +} + +// Return the next hhTrackNode to move to based on current player position +idEntity *hhTrackMover::GetNextDestination( void ) { + + idEntity *node, *minNode; + float distToPlayerSquared, minDistToPlayerSquared; + + if ( !currentNode ) { + return NULL; + } + + // Get player closest to currentNode + hhPlayer *player = NULL; + float bestDistSqr = idMath::INFINITY; + idVec3 origin = currentNode->GetPhysics()->GetOrigin(); + for ( int i = 0; i < MAX_CLIENTS ; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if (ent && ent->IsType(hhPlayer::Type)) { + idVec3 toEnt = ent->GetPhysics()->GetOrigin() - origin; + float distSqr = toEnt.LengthSqr(); + if (distSqr < bestDistSqr) { + bestDistSqr = distSqr; + player = static_cast(ent); + } + } + } + if ( !player ) { + return NULL; + } + + // Find closest adjacent node to player + minNode = currentNode; + minDistToPlayerSquared = (player->GetPhysics()->GetOrigin() - currentNode->GetPhysics()->GetOrigin()).LengthSqr(); + for (int t=0; ttargets.Num(); t++) { + node = currentNode->targets[t].GetEntity(); + if (node) { + idVec3 toPlayer = player->GetPhysics()->GetOrigin() - node->GetPhysics()->GetOrigin(); + distToPlayerSquared = toPlayer.LengthSqr(); + if (distToPlayerSquared < minDistToPlayerSquared) { + minDistToPlayerSquared = distToPlayerSquared; + minNode = node; + + if(p_mountedGunDebug.GetInteger()) { + gameRenderWorld->DebugLine(colorGreen, currentNode->GetPhysics()->GetOrigin(), node->GetPhysics()->GetOrigin(), 2000, false); + } + } + else { + if(p_mountedGunDebug.GetInteger()) { + gameRenderWorld->DebugLine(colorYellow, currentNode->GetPhysics()->GetOrigin(), node->GetPhysics()->GetOrigin(), 2000, false); + } + } + } + } + + return minNode; +} + +void hhTrackMover::DoneMoving( void ) { + if( state == StateGlobal ) { + idMover::DoneMoving(); + PostEventMS( &EV_StartNextMove, 0); + } +} + +void hhTrackMover::Event_PostSpawn() { + bool activate; + spawnArgs.GetBool("autoactivate", "0", activate); + + if( !targets.Num() || !targets[0].IsValid() ) { + gameLocal.Error("hhTrackMover %s doesn't have a target tracknode\n", name.c_str()); + } + + currentNode = targets[0].GetEntity(); + dest_position = GetLocalCoordinates(currentNode->GetPhysics()->GetOrigin()); + SetOrigin( dest_position ); + + if (activate) { + state = StateGlobal; + PostEventMS( &EV_StartNextMove, 1000 ); + } else { + state = StateIdle; + } +} + +void hhTrackMover::Event_Activate( idEntity *activator ) { + if( state == StateIdle ) { + PostEventMS( &EV_StartNextMove, 0 ); + state = StateGlobal; + } +} + +void hhTrackMover::Event_Deactivate() { + if( state == StateGlobal ) { + state = StateIdle; + + DoneMoving(); + DoneRotating(); + CancelEvents(&EV_StartNextMove); + + } +} + +void hhTrackMover::Event_StartNextMove() { + idAngles Angles; + idVec3 Origin; + idEntity *ent = GetNextDestination(); + + if (ent && ent != currentNode) { + + Origin = GetLocalCoordinates( ent->GetPhysics()->GetOrigin() ); + if( !GetPhysics()->GetOrigin().Compare(Origin) ) { + dest_position = Origin; + BeginMove( NULL ); + } + + Angles = ent->GetPhysics()->GetAxis().ToAngles(); + if( !GetPhysics()->GetAxis().ToAngles().Compare(Angles) ) { + dest_angles = Angles; + BeginRotation( NULL, true ); + } + + currentNode = ent; + } else { + PostEventMS( &EV_StartNextMove, timeBetweenMoves ); + } +} + +void hhTrackMover::Event_Kill() { + idMover::DoneMoving(); + idMover::DoneRotating(); + CancelEvents(&EV_StartNextMove); + state = StateDead; +} + +void hhTrackMover::Save( idSaveGame *savefile ) const { + savefile->WriteInt( state ); + savefile->WriteInt( timeBetweenMoves ); + savefile->WriteObject( currentNode ); +} + +void hhTrackMover::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( reinterpret_cast ( state ) ); + savefile->ReadInt( timeBetweenMoves ); + savefile->ReadObject( reinterpret_cast ( currentNode ) ); +} diff --git a/src/Prey/game_trackmover.h b/src/Prey/game_trackmover.h new file mode 100644 index 0000000..a5eff64 --- /dev/null +++ b/src/Prey/game_trackmover.h @@ -0,0 +1,33 @@ +#ifndef __GAME_TRACKMOVER_H__ +#define __GAME_TRACKMOVER_H__ + + +class hhTrackMover : public idMover { + CLASS_PROTOTYPE( hhTrackMover ); +public: + hhTrackMover(); + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + idEntity * GetNextDestination( void ); + void DoneMoving( void ); + +protected: + enum States { + StateGlobal = 0, + StateIdle, + StateDead + } state; + + int timeBetweenMoves; + idEntity * currentNode; // Current node of track + + void Event_Activate( idEntity *activator ); + void Event_Deactivate(); + void Event_PostSpawn( void ); + void Event_StartNextMove( void ); + void Event_Kill(); +}; + + +#endif /* __GAME_TRACKMOVER_H__ */ diff --git a/src/Prey/game_trigger.cpp b/src/Prey/game_trigger.cpp new file mode 100644 index 0000000..f9a2215 --- /dev/null +++ b/src/Prey/game_trigger.cpp @@ -0,0 +1,1482 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/********************************************************************** + +hhFuncParmAccessor + +**********************************************************************/ + +CLASS_DECLARATION( idClass, hhFuncParmAccessor ) +END_CLASS + +/* +================ +hhFuncParmAccessor::hhFuncParmAccessor +================ +*/ +hhFuncParmAccessor::hhFuncParmAccessor() { + function = NULL; +} + +/* +================ +hhFuncParmAccessor::hhFuncParmAccessor +================ +*/ +hhFuncParmAccessor::hhFuncParmAccessor( const hhFuncParmAccessor* accessor ) { + assert( accessor ); + + SetInfo( accessor->GetReturnKey(), accessor->GetFunction(), accessor->GetParms() ); +} + +/* +================ +hhFuncParmAccessor::hhFuncParmAccessor +================ +*/ +hhFuncParmAccessor::hhFuncParmAccessor( const hhFuncParmAccessor& accessor ) { + SetInfo( accessor.GetReturnKey(), accessor.GetFunction(), accessor.GetParms() ); +} + +/* +================ +hhFuncParmAccessor::SetInfo +================ +*/ +void hhFuncParmAccessor::SetInfo( const char* returnKey, const function_t* func, const idList& parms ) { + SetFunction( func ); + SetParms( parms ); + SetReturnKey( returnKey ); + + Verify(); +} + +void hhFuncParmAccessor::InsertParm_String( const char *text, int index ) { + InsertParm( text, index ); +} + +void hhFuncParmAccessor::InsertParm_Float( float value, int index ) { + InsertParm( va("%.2f", value), index ); +} + +void hhFuncParmAccessor::InsertParm_Int( int value, int index ) { + InsertParm( va("%d", value), index ); +} + +void hhFuncParmAccessor::InsertParm_Vector( const idVec3 &vec, int index ) { + InsertParm( va("'%.2f %.2f %.2f'", vec[0], vec[1], vec[2]), index ); +} + +void hhFuncParmAccessor::InsertParm_Entity( const idEntity *ent, int index ) { + if ( ent ) { + InsertParm( ent->GetName(), index ); + } +} + +void hhFuncParmAccessor::SetParm_Entity( const idEntity *ent, int index ) { + if ( ent ) { + parms.AssureSize(index+1); + parms[index] = ent->GetName(); + } +} + +void hhFuncParmAccessor::SetParm_String( const char *str, int index ) { + if ( str ) { + parms.AssureSize(index+1); + parms[index] = str; + } +} + +void hhFuncParmAccessor::InsertParm( const char *text, int index ) { + parms.Insert( text, index ); +} + +/* +================ +hhFuncParmAccessor::ParseFunctionKeyValue +================ +*/ +void hhFuncParmAccessor::ParseFunctionKeyValue( const char* value ) { + if( !value || !value[0] ) { + return; + } + + hhUtils::SplitString( idStr(value), GetParms(), ' ' ); + ParseFunctionKeyValue( GetParms() ); +} + +/* +================ +hhFuncParmAccessor::ParseFunctionKeyValue +================ +*/ +void hhFuncParmAccessor::ParseFunctionKeyValue( idList& valueList ) { + SetFunction( FindFunction(valueList[0]) ); + if( GetFunction() ) { + valueList.RemoveIndex( 0 );//Remove function name + } else { + gameLocal.Warning("hhFuncParmAccessor: Function %s did not exist", (const char*)valueList[0]); + SetReturnKey( valueList[0] ); + valueList.RemoveIndex( 0 );//Remove return key + SetFunction( FindFunction(valueList[0]) ); + valueList.RemoveIndex( 0 );//Remove function name + } +} + +/* +================ +hhFuncParmAccessor::FindFunction +================ +*/ +function_t* hhFuncParmAccessor::FindFunction( const char* funcname ) { + function_t* func = NULL; + + if( funcname && *funcname ) { + func = gameLocal.program.FindFunction( funcname ); + } + + return func; +} + +/* +================ +hhFuncParmAccessor::CallFunction +================ +*/ +void hhFuncParmAccessor::CallFunction( idDict& returnDict ) { + if( !GetFunction() ) { + return; + } + + hhThread* scriptThread = new hhThread; + if( !scriptThread ) { + return; + } + + scriptThread->ClearStack(); + if( !scriptThread->ParseAndPushArgsOntoStack(GetParms(), GetFunction()) ) { + SAFE_DELETE_PTR( scriptThread ); //This is needed because we won't be removed by Execute + return; + } + + scriptThread->CallFunction( GetFunction(), false ); + + scriptThread->Execute(); + + idTypeDef* returnType = GetReturnType(); + if( !GetReturnKey() || !GetReturnKey()[0] ) { + return; + } + + if( returnType == &type_void ) { + gameLocal.Warning( "Return key assigned for function (%s) that returns void\n", GetFunctionName() ); + return; + } + + returnDict.Set( GetReturnKey(), returnType->GetReturnValueAsString(gameLocal.program) ); +} + +/* +================ +hhFuncParmAccessor::Verify +================ +*/ +void hhFuncParmAccessor::Verify() { + if( !GetFunction() ) { + return; + } + + int numParms = GetFunction()->def->TypeDef()->NumParameters(); + const char* parm = NULL; + idTypeDef* parmType = NULL; + + if( numParms != GetParms().Num() ) { + gameLocal.Warning( "Wrong # of parms passed for function %s", GetFunctionName() ); + return; + } + + if( GetReturnType() == &type_void && GetReturnKey() && GetReturnKey()[0] ) { + gameLocal.Warning( "Used Return key %s for function %s that returns void", GetReturnKey(), GetFunctionName() ); + return; + } + + for( int ix = 0; ix < numParms; ++ix ) { + parmType = GetParmType( ix ); + parm = GetParm( ix ); + if( !parmType->VerifyData(parm) ) { + gameLocal.Warning( "%s is not Type %s", parm, parmType->Name() ); + return; + } + } +} + +/* +================ +hhFuncParmAccessor::GetFunctionName +================ +*/ +const char* hhFuncParmAccessor::GetFunctionName() const { + return GetFunction()->Name(); +} + +/* +================ +hhFuncParmAccessor::GetFunction +================ +*/ +const function_t* hhFuncParmAccessor::GetFunction() const { + return function; +} + +/* +================ +hhFuncParmAccessor::GetReturnKey +================ +*/ +const char* hhFuncParmAccessor::GetReturnKey() const { + return returnKey.c_str(); +} + +/* +================ +hhFuncParmAccessor::GetReturnType +================ +*/ +idTypeDef* hhFuncParmAccessor::GetReturnType() const { + return GetFunction()->def->TypeDef()->ReturnType(); +} + +/* +================ +hhFuncParmAccessor::GetParm +================ +*/ +const char* hhFuncParmAccessor::GetParm( int index ) const { + return parms[ index ]; +} + +/* +================ +hhFuncParmAccessor::GetParmType +================ +*/ +idTypeDef* hhFuncParmAccessor::GetParmType( int index ) const { + return GetFunction()->def->TypeDef()->GetParmType( index ); +} + +/* +================ +hhFuncParmAccessor::GetParms +================ +*/ +const idList& hhFuncParmAccessor::GetParms() const { + return parms; +} + +/* +================ +hhFuncParmAccessor::GetParms +================ +*/ +idList& hhFuncParmAccessor::GetParms() { + return parms; +} + +/* +================ +hhFuncParmAccessor::SetFunction +================ +*/ +void hhFuncParmAccessor::SetFunction( const function_t* func ) { + function = func; +} + +/* +================ +hhFuncParmAccessor::SetParms +================ +*/ +void hhFuncParmAccessor::SetParms( const idList& parms ) { + this->parms = parms; +} + +/* +================ +hhFuncParmAccessor::SetReturnKey +================ +*/ +void hhFuncParmAccessor::SetReturnKey( const char* key ) { + returnKey = key; +} + +/* +================ +hhFuncParmAccessor::Save +================ +*/ +void hhFuncParmAccessor::Save( idSaveGame *savefile ) const { + int num = parms.Num(); + savefile->WriteInt( num ); + for( int i = 0; i < num; i++ ) { + savefile->WriteString( parms[i] ); + } + + savefile->WriteString( returnKey ); + savefile->WriteString( function ? function->Name() : "" ); +} + +/* +================ +hhFuncParmAccessor::Restore +================ +*/ +void hhFuncParmAccessor::Restore( idRestoreGame *savefile ) { + int num; + savefile->ReadInt( num ); + idStr tmp; + parms.Clear(); + for( int i = 0; i < num; i++ ) { + savefile->ReadString( tmp ); + parms.Append( tmp ); + } + + savefile->ReadString( returnKey ); + savefile->ReadString( tmp ); + if( tmp.Length() > 0 ) { + function = FindFunction( tmp ); + HH_ASSERT( function ); + } else { + function = NULL; + } +} + +/********************************************************************** + +hhTrigger + +**********************************************************************/ + +const idEventDef EV_Trigger("", "e"); +const idEventDef EV_Retrigger("", "e"); +const idEventDef EV_UnTrigger("", NULL); +const idEventDef EV_PollForUntouch("", NULL); + +CLASS_DECLARATION( idEntity, hhTrigger ) + EVENT( EV_Enable, hhTrigger::Event_Enable ) + EVENT( EV_Disable, hhTrigger::Event_Disable ) + EVENT( EV_Activate, hhTrigger::Event_Activate) + EVENT( EV_Deactivate, hhTrigger::Event_Deactivate) + EVENT( EV_Touch, hhTrigger::Event_Touch) + EVENT( EV_Trigger, hhTrigger::Event_TriggerAction ) + EVENT( EV_Retrigger, hhTrigger::Event_Retrigger ) + EVENT( EV_UnTrigger, hhTrigger::Event_UnTriggerAction ) + EVENT( EV_PollForUntouch, hhTrigger::Event_PollForUntouch ) + EVENT( EV_PostSpawn, hhTrigger::Event_PostSpawn ) +END_CLASS + +/* +================ +hhTrigger::Activate + +the trigger was just activated by activator +================ +*/ +void hhTrigger::Activate( idEntity *activator ) { + int triggerDelay = 0; + + unTriggerActivator = activator; + + // if not enabled, return + if( !bEnabled ) { + return; + } + + bActive = true; + + 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 ( delay > 0 ) { + // don't allow it to trigger again until our delay has passed + triggerDelay = hhMath::hhMax( 0, SEC2MS(delay + randomDelay * gameLocal.random.CRandomFloat()) ); + nextTriggerTime = gameLocal.time + triggerDelay + 1; + PostEventMS( &EV_Trigger, triggerDelay, activator ); + } else { + //Changed so we check to see if the activator is still with in the trigger volume + ProcessEvent( &EV_Trigger, activator ); + } +} + +/* +================ +hhTrigger::Spawn +================ +*/ +void hhTrigger::Spawn(void) { + + unTriggerActivator=NULL; + bActive=false; + + spawnArgs.GetFloat( "wait", "0.5", wait ); + spawnArgs.GetFloat( "random", "0", random ); + spawnArgs.GetFloat( "delay", "0", delay ); + spawnArgs.GetFloat( "randomeDelay", "0", randomDelay ); + spawnArgs.GetString( "requires", "", requires ); + spawnArgs.GetInt( "removeItem", "0", removeItem ); + triggerBehavior = (triggerBehavior_t)spawnArgs.GetInt( "triggerBehavior" ); + spawnArgs.GetBool( "noTouch", "0", noTouch ); + spawnArgs.GetFloat( "uncalldelay", "0.2", untouchGranularity ); + spawnArgs.GetFloat( "refire", "-1", refire ); + spawnArgs.GetBool( "enabled", "1", initiallyEnabled ); + spawnArgs.GetBool( "untrigger", "0", bUntrigger ); + spawnArgs.GetBool( "always_trigger", "0", alwaysTrigger ); + spawnArgs.GetBool( "isSimpleBox", "1", isSimpleBox ); + spawnArgs.GetBool( "noVehicles", "0", bNoVehicles ); + + GetTriggerClasses( spawnArgs ); + + nextTriggerTime = 0; + + PostEventMS( (initiallyEnabled) ? &EV_Enable : &EV_Disable, 0); + + PostEventMS( &EV_PostSpawn, 0 ); +} + +/* +================ +hhTrigger::Save +================ +*/ +void hhTrigger::Save( idSaveGame *savefile ) const { + savefile->WriteBool( bActive ); + savefile->WriteBool( bEnabled ); + savefile->WriteFloat( untouchGranularity ); + savefile->WriteFloat( wait ); + savefile->WriteFloat( random ); + savefile->WriteFloat( delay ); + savefile->WriteFloat( randomDelay ); + savefile->WriteFloat( refire ); + savefile->WriteInt( nextTriggerTime ); + savefile->WriteBool( alwaysTrigger ); + savefile->WriteBool( isSimpleBox ); + savefile->WriteBool( bNoVehicles ); + savefile->WriteStaticObject( funcInfo ); + savefile->WriteStaticObject( unfuncInfo ); + savefile->WriteStaticObject( funcRefInfo ); + savefile->WriteStaticObject( unfuncRefInfo ); + savefile->WriteStaticObject( funcRefActivatorInfo ); + savefile->WriteStaticObject( unfuncRefActivatorInfo ); + savefile->WriteString( requires ); + savefile->WriteInt( removeItem ); + savefile->WriteBool( noTouch ); + savefile->WriteBool( initiallyEnabled ); + savefile->WriteBool( bUntrigger ); + + unTriggerActivator.Save( savefile ); + + savefile->WriteStringList( TriggerClasses ); + savefile->WriteInt( triggerBehavior ); +} + +/* +================ +hhTrigger::Restore +================ +*/ +void hhTrigger::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( bActive ); + savefile->ReadBool( bEnabled ); + savefile->ReadFloat( untouchGranularity ); + savefile->ReadFloat( wait ); + savefile->ReadFloat( random ); + savefile->ReadFloat( delay ); + savefile->ReadFloat( randomDelay ); + savefile->ReadFloat( refire ); + savefile->ReadInt( nextTriggerTime ); + savefile->ReadBool( alwaysTrigger ); + savefile->ReadBool( isSimpleBox ); + savefile->ReadBool( bNoVehicles ); + savefile->ReadStaticObject( funcInfo ); + savefile->ReadStaticObject( unfuncInfo ); + savefile->ReadStaticObject( funcRefInfo ); + savefile->ReadStaticObject( unfuncRefInfo ); + savefile->ReadStaticObject( funcRefActivatorInfo ); + savefile->ReadStaticObject( unfuncRefActivatorInfo ); + savefile->ReadString( requires ); + savefile->ReadInt( removeItem ); + savefile->ReadBool( noTouch ); + savefile->ReadBool( initiallyEnabled ); + savefile->ReadBool( bUntrigger ); + + unTriggerActivator.Restore( savefile ); + + savefile->ReadStringList( TriggerClasses ); + savefile->ReadInt( reinterpret_cast ( triggerBehavior ) ); +} + +/* +================ +hhTrigger::CallFunctions +================ +*/ +void hhTrigger::CallFunctions( idEntity *activator ) { + funcInfo.CallFunction( spawnArgs ); + funcRefInfo.CallFunction( spawnArgs ); + + funcRefActivatorInfo.InsertParm_Entity( activator, 1 );//Needs to be second parm. After self + funcRefActivatorInfo.CallFunction( spawnArgs ); +} + +/* +================ +hhTrigger::UncallFunctions +================ +*/ +void hhTrigger::UncallFunctions( idEntity *activator ) { + unfuncInfo.CallFunction( spawnArgs ); + unfuncRefInfo.CallFunction( spawnArgs ); + + unfuncRefActivatorInfo.InsertParm_Entity( activator, 1 );//Needs to be second parm. After self + unfuncRefActivatorInfo.CallFunction( spawnArgs ); +} + +/* +================ +hhTrigger::TriggerAction +================ +*/ +void hhTrigger::TriggerAction( idEntity *activator ) { + + // Activate all targets + ActivateTargets( activator ); + + CallFunctions( activator ); + + if ( wait >= 0 ) { + nextTriggerTime = gameLocal.time + SEC2MS( wait + random * gameLocal.random.CRandomFloat() ); + } else { + nextTriggerTime = gameLocal.time + 1; + PostEventMS( &EV_Disable, 0 ); + } + + // Handle refire + if (refire > 0) { + PostEventSec( &EV_Retrigger, refire + random * gameLocal.random.CRandomFloat(), activator); + } +} + +/* +================ +hhTrigger::UnTriggerAction +================ +*/ +void hhTrigger::UnTriggerAction() { + if( bUntrigger ) { + ActivateTargets( unTriggerActivator.GetEntity() ); + } + + UncallFunctions( unTriggerActivator.GetEntity() ); +} + +/* +=============== +hhTrigger::IsEncroaching +=============== +*/ +bool hhTrigger::IsEncroaching( const idEntity* entity ) { + + // nla - Simplified checks to bounds when possible. + // Error was due to contents of an idActivator not hitting anything here. (it has contents 0) + // But since the collide logic has been done elsewhere, we can simplify this to just a is this in this check. + if (isSimpleBox) { + // num = gameLocal.clip.EntitiesTouchingBounds( GetPhysics()->GetAbsBounds(), entity->GetPhysics()->GetContents(), touch, MAX_GENTITIES ); + if ( GetPhysics()->GetAbsBounds().IntersectsBounds( entity->GetPhysics()->GetAbsBounds() ) ) { + return( true ); + } + } + else { + //num = hhUtils::EntitiesTouchingClipmodel( GetPhysics()->GetClipModel(), touch, MAX_GENTITIES, entity->GetPhysics()->GetContents() ); + if ( entity->GetPhysics()->ClipContents( GetPhysics()->GetClipModel() ) ) { + return( true ); + } + } + + return false; +} + +/* +=============== +hhTrigger::IsEncroached +=============== +*/ +bool hhTrigger::IsEncroached() { + idEntity *touch[ MAX_GENTITIES ]; + int num; + + if (isSimpleBox) { + num = gameLocal.clip.EntitiesTouchingBounds( GetPhysics()->GetAbsBounds(), MASK_SHOT_BOUNDINGBOX, touch, MAX_GENTITIES ); + } + else { + num = hhUtils::EntitiesTouchingClipmodel( GetPhysics()->GetClipModel(), touch, MAX_GENTITIES ); + } + + for (int i = 0; i < num; i++ ) { + if( touch[ i ] == this ) { + continue; + } + + if( CheckUnTriggerClass(touch[ i ]) ) { + return true; + } + } + return false; +} + +/* +=============== +hhTrigger::SetTriggerClasses +=============== +*/ +void hhTrigger::SetTriggerClasses( idList& list ) { + TriggerClasses.Clear(); + + TriggerClasses.SetNum(list.Num()); + + for(int iIndex = 0; iIndex < list.Num(); ++iIndex) { + TriggerClasses[iIndex] = list[iIndex]; + } +} + +/* +=============== +hhTrigger::CheckTriggerClass +=============== +*/ +bool hhTrigger::CheckTriggerClass( idEntity* activator ) { + if( !activator ) { + return false; + } + + if(!TriggerClasses.Num() || triggerBehavior == TB_ANY) { + return true; + } + + + for(int iIndex = 0; iIndex < TriggerClasses.Num(); ++iIndex) { + //Look for exact match then try for prefix match + if( !idStr(activator->spawnArgs.GetString("classname")).Icmp(TriggerClasses[iIndex].c_str()) || + !idStr(activator->spawnArgs.GetString("classname")).IcmpPrefix(TriggerClasses[iIndex].c_str()) ) { + + if( activator->IsType(hhPlayer::Type) ) {//Player needs client + hhPlayer* player = static_cast(activator); + if ( player->noclip || !player->ShouldTouchTrigger(this) ) { + return false; + } + if ( bNoVehicles && player->InVehicle() ) { + return false; + } + } + if( activator->IsType(idActor::Type) ) { // Dead monsters shouldn't keep refires going + if (activator->health <= 0) { + return false; + } + } + return true; + } + } + + return false; +} + +/* +=============== +hhTrigger::CheckUnTriggerClass + +Helper function used for overriding +=============== +*/ +bool hhTrigger::CheckUnTriggerClass(idEntity* activator) { + return CheckTriggerClass(activator); +} + +/* +=============== +hhTrigger::GetTriggerClasses +=============== +*/ +void hhTrigger::GetTriggerClasses(idDict& Args) { + const idKeyValue* pKeyValue = NULL; + int iNumKeyValues = Args.GetNumKeyVals(); + + TriggerClasses.Clear(); + + if(triggerBehavior == TB_PLAYER_ONLY) { + TriggerClasses.AddUnique( "player" ); + + } else if(triggerBehavior == TB_FRIENDLIES_ONLY) { + TriggerClasses.AddUnique( "player" ); + TriggerClasses.AddUnique( "character" ); + + } else if(triggerBehavior == TB_MONSTERS_ONLY) { + TriggerClasses.AddUnique( "monster" ); + + } else if(triggerBehavior == TB_PLAYER_MONSTERS_FRIENDLIES) { + TriggerClasses.AddUnique( "player" ); + TriggerClasses.AddUnique( "monster" ); + TriggerClasses.AddUnique( "character" ); + + } else if(triggerBehavior == TB_SPECIFIC_ENTITIES){ + for( int iIndex = 0; iIndex < iNumKeyValues; ++iIndex ) { + pKeyValue = Args.GetKeyVal( iIndex ); + if ( !pKeyValue->GetKey().Cmpn( "trigger_class", 13 ) ) { + TriggerClasses.AddUnique( pKeyValue->GetValue() ); + } + } + } +} + +/* +=============== +hhTrigger::Enable +=============== +*/ +void hhTrigger::Enable() { + GetPhysics()->SetContents( CONTENTS_TRIGGER ); + GetPhysics()->EnableClip(); + + bEnabled = true; +} + +/* +=============== +hhTrigger::Disable +=============== +*/ +void hhTrigger::Disable() { + // we may be relinked if we're bound to another object, so clear the contents as well + GetPhysics()->SetContents( 0 ); + GetPhysics()->DisableClip(); + + CancelEvents( &EV_Retrigger ); + bEnabled = false; +} + +/* +=============== +hhTrigger::Event_PostSpawn +=============== +*/ +void hhTrigger::Event_PostSpawn() { + funcInfo.ParseFunctionKeyValue( spawnArgs.GetString("call") ); + funcInfo.Verify(); + unfuncInfo.ParseFunctionKeyValue( spawnArgs.GetString("uncall") ); + unfuncInfo.Verify(); + + funcRefInfo.ParseFunctionKeyValue( spawnArgs.GetString("callRef") ); + funcRefInfo.InsertParm_Entity( this, 0 ); + funcRefInfo.Verify(); + + unfuncRefInfo.ParseFunctionKeyValue( spawnArgs.GetString("uncallRef") ); + unfuncRefInfo.InsertParm_Entity( this, 0 ); + unfuncRefInfo.Verify(); + + funcRefActivatorInfo.ParseFunctionKeyValue( spawnArgs.GetString("callRefActivator") ); + funcRefActivatorInfo.InsertParm_Entity( this, 0 ); + // NOTE: Activator parm inserted at time of function call, therefore don't verify now + + unfuncRefActivatorInfo.ParseFunctionKeyValue( spawnArgs.GetString("uncallRefActivator") ); + unfuncRefActivatorInfo.InsertParm_Entity( this, 0 ); + // NOTE: Activator parm inserted at time of function call, therefore don't verify now +} + +/* +=============== +hhTrigger::Event_PollForUntouch +=============== +*/ +void hhTrigger::Event_PollForUntouch() { + if (!IsEncroached()) { + CancelEvents(&EV_PollForUntouch); + PostEventMS(&EV_Deactivate, 0); + } + else { + PostEventSec(&EV_PollForUntouch, untouchGranularity); + } +} + +/* +=============== +hhTrigger::Event_Touch +=============== +*/ +void hhTrigger::Event_Touch( idEntity *other, trace_t *trace ) { + + if( noTouch || !CheckTriggerClass(other) ) { + return; + } + + if( !IsActive() ) { + Activate( other ); + + // If this trigger uses any of the unTrigger mechanisms, start polling + if (bUntrigger || refire || unfuncInfo.GetFunction() || unfuncRefInfo.GetFunction() || unfuncRefActivatorInfo.GetFunction()) { + PostEventSec(&EV_PollForUntouch, untouchGranularity); + } + } + // If we have already triggered the first time, but should always trigger + else if ( alwaysTrigger ) { + Activate( other ); + } +} + +/* +================ +hhTrigger::Retrigger +================ +*/ +void hhTrigger::Event_Retrigger( idEntity *activator ) { + //AOB - needed to stop infinite retrigger when triggered from gui + //rww - activator can be null if removed before event is serviced. actually, isn't this a bad idea? maybe it's better to use an index. + if (!activator || (!noTouch && !IsEncroaching(activator)) ) { + CancelEvents( &EV_Retrigger ); + PostEventMS( &EV_Deactivate, 0 ); + return; + } + + // This is seperate event so we can cull them seperately from normal trigger events + + TriggerAction( activator ); +} + +/* +================ +hhTrigger::Event_UnTriggerAction +================ +*/ +void hhTrigger::Event_UnTriggerAction() { + UnTriggerAction(); +} + +/* +================ +hhTrigger::Event_TriggerAction +================ +*/ +void hhTrigger::Event_TriggerAction( idEntity *activator ) { + //HUMANHEAD rww - we are running into a null activator situation when the spirit proxy for the player runs into a trigger, + //and then the player switches back to his body and removes the proxy before the trigger fires (due to a relay/delay/whatever), + //as well as possibly any other situation where a delayed trigger is activated by an entity that gets removed before firing. + //it has been decided that in these situations, the world should be used as the activator. + if (!activator) { + activator = gameLocal.world; + } + //HUMANHEAD END + // Added noTouch && !IsEncroached to fix the issue with retriggered hurt constantly damaging the player, even when they weren't in it. + // nla - Added check to allow 'delayed' triggers to function when you weren't in them. + if (!noTouch && !IsEncroaching(activator) && !delay ) { + CancelEvents( &EV_Retrigger ); + PostEventMS( &EV_Deactivate, 0 ); + return; + } + + TriggerAction( activator ); +} + +/* +================ +hhTrigger::Event_Enable +================ +*/ +void hhTrigger::Event_Enable( void ) { + Enable(); +} + +/* +================ +hhTrigger::Event_Disable +================ +*/ +void hhTrigger::Event_Disable( void ) { + Disable(); +} + +/* +================ +hhTrigger::Event_Activate +================ +*/ +void hhTrigger::Event_Activate( idEntity *activator ) { + Activate( activator ); +} + +/* +================ +hhTrigger::Event_Deactivate +================ +*/ +void hhTrigger::Event_Deactivate() { + CancelEvents( &EV_Retrigger ); + PostEventMS( &EV_UnTrigger, 0 ); + bActive=false; +} + +/*********************************************************************** + +hhDamageTrigger + +***********************************************************************/ + +CLASS_DECLARATION( hhTrigger, hhDamageTrigger ) + EVENT( EV_Enable, hhDamageTrigger::Event_Enable ) + EVENT( EV_Disable, hhDamageTrigger::Event_Disable ) +END_CLASS + + +void hhDamageTrigger::Spawn(void) { + funcRefInfoDamage.ParseFunctionKeyValue( spawnArgs.GetString("damageCallRef") ); + funcRefInfoDamage.InsertParm_Entity( this, 0 ); +} + +void hhDamageTrigger::Save(idSaveGame *savefile) const { + savefile->WriteStaticObject( funcRefInfoDamage ); +} + +void hhDamageTrigger::Restore( idRestoreGame *savefile ) { + savefile->ReadStaticObject( funcRefInfoDamage ); +} + +void hhDamageTrigger::Damage(idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location) { + // Call script if requested with damageDef, used for spherebrain + if (funcRefInfoDamage.GetFunction() != NULL) { + funcRefInfoDamage.InsertParm_Entity( attacker, 1 ); + spawnArgs.Set( "last_damage", damageDefName ); + funcRefInfoDamage.CallFunction( spawnArgs ); + } + if ( spawnArgs.GetBool( "activate_targets" ) ) { + ActivateTargets( attacker ); + } + hhTrigger::Damage(inflictor, attacker, dir, damageDefName, damageScale, location); +} + +void hhDamageTrigger::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if (!CheckTriggerClass(attacker)) { + health += damage; + return; + } + + if ( gameLocal.time < nextTriggerTime ) { + health += damage; + return; + } + + Activate( attacker ); +} + +void hhDamageTrigger::Event_Enable() { + bEnabled = true; + fl.takedamage = true; + GetPhysics()->SetContents( CONTENTS_SHOOTABLE|CONTENTS_SHOOTABLEBYARROW ); +} + +void hhDamageTrigger::Event_Disable() { + GetPhysics()->SetContents( 0 ); + fl.takedamage = false; + bEnabled = false; +} + +/*********************************************************************** + +hhTriggerPain + +***********************************************************************/ + +const idEventDef EV_DamageBox("damageBox", "e"); + +CLASS_DECLARATION( hhTrigger, hhTriggerPain ) + EVENT( EV_Activate, hhTriggerPain::Event_Activate ) + EVENT( EV_DamageBox, hhTriggerPain::Event_DamageBox ) +END_CLASS + +/* +================ +hhTriggerPain + + Damages activator + Can be turned on or off by enable/disable + Fires events, triggers targets +================ +*/ +void hhTriggerPain::Spawn(void) { + spawnArgs.GetBool( "apply_impulse", "0", applyImpulse ); +} + +void hhTriggerPain::Event_DamageBox( idEntity *activator ) { + idEntity *touch[ MAX_GENTITIES ]; + int num; + + if (isSimpleBox) { + num = gameLocal.clip.EntitiesTouchingBounds( GetPhysics()->GetAbsBounds(), MASK_SHOT_BOUNDINGBOX, touch, MAX_GENTITIES ); + } + else { + num = hhUtils::EntitiesTouchingClipmodel( GetPhysics()->GetClipModel(), touch, MAX_GENTITIES ); + } + + for (int i = 0; i < num; i++ ) { + if( touch[ i ] == this ) { + continue; + } + + if( CheckTriggerClass( touch[i] ) ) { + touch[i]->Damage(activator, activator, vec3_origin, spawnArgs.GetString("def_damage"), 1.0f, INVALID_JOINT ); + } + } +} + +/* +================ +hhTriggerPain::TriggerAction +================ +*/ +void hhTriggerPain::TriggerAction(idEntity *activator) { + + if(activator) { + // If a player in a vehicle, apply pain to vehicle + if (activator->IsType(hhPlayer::Type)) { + hhPlayer *player = static_cast(activator); + if (player->InVehicle()) { + activator = player->GetVehicleInterface()->GetVehicle(); + } + } + + idVec3 Velocity = activator->GetPhysics()->GetLinearVelocity(); + + activator->Damage(NULL, NULL, -Velocity, spawnArgs.GetString("def_damage"), 1.0f, INVALID_JOINT ); + + if ( applyImpulse ) { +// activator->ApplyImpulse( this, 0, activator->GetPhysics()->GetOrigin(), -Velocity / 10.0 ); + activator->ApplyImpulse( this, 0, activator->GetPhysics()->GetOrigin(), -Velocity * (activator->GetPhysics()->GetMass()*1.5f) ); + } + + } + + // Handle default trigger behavior + hhTrigger::TriggerAction(activator); +} + +/* +================ +hhTriggerPain::Event_Activate +================ +*/ +void hhTriggerPain::Event_Activate( idEntity *activator ) { + //AOB - allow toggling +// if( IsEnabled() && (IsActive() || !IsEncroached()) ) { + if( IsEnabled() ) { + ProcessEvent( &EV_Disable ); + ProcessEvent( &EV_Deactivate ); + } else { + if( !IsEnabled() ) { + ProcessEvent( &EV_Enable ); + } + Activate( activator ); + } +} + +/* +================ +hhTriggerPain::Save +================ +*/ +void hhTriggerPain::Save( idSaveGame *savefile ) const { + savefile->WriteBool( applyImpulse ); +} + +/* +================ +hhTriggerPain::Restore +================ +*/ +void hhTriggerPain::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( applyImpulse ); +} + +/*********************************************************************** + +hhTriggerEnabler + +***********************************************************************/ + +CLASS_DECLARATION( hhTrigger, hhTriggerEnabler ) +END_CLASS + +/* +================ +hhTriggerEnabler + + When it is entered, target is enabled + When it is exited, target is disabled. +================ +*/ +void hhTriggerEnabler::Spawn(void) { +} + +/* +================ +hhTriggerEnabler::TriggerAction +================ +*/ +void hhTriggerEnabler::TriggerAction(idEntity *activator) { + + // Enable all targets + for (int t=0; tPostEventMS( &EV_Enable, 0 ); + } + } + + // Handle default trigger behavior + hhTrigger::TriggerAction(activator); +} + + +/* +================ +hhTriggerEnabler::UnTriggerAction +================ +*/ +void hhTriggerEnabler::UnTriggerAction() { + + // Disable all targets + for (int t=0; tPostEventMS( &EV_Disable, 0 ); + } + } + + // Handle default trigger behavior + hhTrigger::UnTriggerAction(); +} + + +/*********************************************************************** + +hhTriggerSight + + Activates targets when seen +***********************************************************************/ + +CLASS_DECLARATION( hhTrigger, hhTriggerSight ) + EVENT( EV_Enable, hhTriggerSight::Event_Enable ) + EVENT( EV_Disable, hhTriggerSight::Event_Disable ) +END_CLASS + + +void hhTriggerSight::Spawn(void) { + BecomeActive(TH_THINK); +} + +void hhTriggerSight::Think(void) { + + if (thinkFlags & TH_THINK) { + hhPlayer *player = NULL; + for (int i = 0; i < gameLocal.numClients; i++ ) { + player = static_cast(gameLocal.entities[ i ]); + if ( !player ) { + continue; + } + + // if there is no way we can see this player + if ( !gameLocal.InPlayerPVS(this) ) { + continue; + } + + if (player->CanSee(this, true)) { + + // activate + PostEventMS(&EV_Activate, 0, player); + + // Automatically disables itself, re-enable for repeat + PostEventMS(&EV_Disable, 0); + } + } + } + + Present(); // Need this so it get's removed from active list +} + +void hhTriggerSight::Event_Enable( void ) { + bEnabled = true; + BecomeActive(TH_THINK); +} + +void hhTriggerSight::Event_Disable( void ) { + bEnabled = false; + BecomeInactive(TH_THINK); +} + +/* +================ +hhTriggerSight::Save +================ +*/ +void hhTriggerSight::Save( idSaveGame *savefile ) const { + savefile->WriteInt( pvsArea ); +} + +/* +================ +hhTriggerSight::Restore +================ +*/ +void hhTriggerSight::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( pvsArea ); + Spawn(); +} + +/* +=============================================================================== + + hhTrigger_Count + +=============================================================================== +*/ + +CLASS_DECLARATION( hhTrigger, hhTrigger_Count ) +END_CLASS + +/* +================ +hhTrigger_Count::Save +================ +*/ +void hhTrigger_Count::Save( idSaveGame *savefile ) const { + savefile->WriteInt( goal ); + savefile->WriteInt( count ); + savefile->WriteFloat( delay ); +} + +/* +================ +hhTrigger_Count::Restore +================ +*/ +void hhTrigger_Count::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( goal ); + savefile->ReadInt( count ); + savefile->ReadFloat( delay ); +} + +/* +================ +hhTrigger_Count::Spawn +================ +*/ +void hhTrigger_Count::Spawn( void ) { + spawnArgs.GetInt( "count", "1", goal ); + spawnArgs.GetFloat( "delay", "0", delay ); + count = 0; +} + +/* +================ +hhTrigger_Count::Activate +================ +*/ +void hhTrigger_Count::Activate( idEntity *activator ) { + int triggerDelay = 0; + + unTriggerActivator = activator; + + // if not enabled, return + if (!bEnabled) { + return; + } + + bActive=true; + + 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 ( delay >= 0 ) { + // goal of -1 means trigger has been exhausted + if (goal >= 0) { + count++; + if ( count >= goal ) { + if (spawnArgs.GetBool("repeat")) { + count = 0; + } else { + goal = -1; + } + + // don't allow it to trigger again until our delay has passed + triggerDelay = hhMath::hhMax( 0, SEC2MS(delay + randomDelay * gameLocal.random.CRandomFloat()) ); + nextTriggerTime = gameLocal.time + triggerDelay; + PostEventMS( &EV_Trigger, triggerDelay, activator ); + } + } + } +} + +/* +================ +hhTrigger_Count::TriggerAction +================ +*/ +void hhTrigger_Count::TriggerAction( idEntity *activator ) { + hhTrigger::TriggerAction( activator ); + + if (goal == -1) { + PostEventMS( &EV_Remove, 0 ); + } +} + + +/* +=============================================================================== + + hhTrigger_Event + +=============================================================================== +*/ + +CLASS_DECLARATION( hhTrigger, hhTrigger_Event ) +END_CLASS + +/* +================ +hhTrigger_Event::Spawn +================ +*/ +void hhTrigger_Event::Spawn( void ) { + eventDef = FindEventDef( spawnArgs.GetString("eventDef") ); +} + +/* +================ +hhTrigger_Event::FindEventDef +================ +*/ +const idEventDef* hhTrigger_Event::FindEventDef( const char* eventDefName ) const { + function_t *func = hhTrigger_Event::FindFunction( eventDefName ); + if( !func || !func->eventdef ) { + gameLocal.Error( "Cannot find event %s on trigger %s\n", eventDefName, name.c_str() ); + } + + return func->eventdef; +} + +/* +================ +hhTrigger_Event::FindFunction +================ +*/ +function_t* hhTrigger_Event::FindFunction( const char* funcname ) { + function_t* func = NULL; + + func = gameLocal.program.FindFunction( funcname, gameLocal.program.FindType(funcname) ); + if( !func ) { + gameLocal.Error( "Cannot find function '%s' in hhTrigger", funcname ); + } + + return func; +} + +/* +================ +hhTrigger_Event::ActivateTargets +================ +*/ +void hhTrigger_Event::ActivateTargets( idEntity *activator ) const { + idEntity *ent; + int i; + + HH_ASSERT( eventDef ); + + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( !ent ) { + continue; + } + if ( ent->RespondsTo( *eventDef ) ) { + ent->ProcessEvent( eventDef, activator ); + } + for (int ix=0; ixGetRenderEntity()->gui[ix]->Trigger(gameLocal.time); + } + } + } +} + +/*********************************************************************** + + hhMineTrigger + +***********************************************************************/ + +CLASS_DECLARATION( hhTrigger, hhMineTrigger ) +END_CLASS + +bool hhMineTrigger::CheckTriggerClass( idEntity* activator ) { + if( !activator ) { + return false; + } + + if(!TriggerClasses.Num() || triggerBehavior == TB_ANY) { + return true; + } + + for(int iIndex = 0; iIndex < TriggerClasses.Num(); ++iIndex) { + //Look for exact match then try for prefix match + if( !idStr(activator->spawnArgs.GetString("classname")).Icmp(TriggerClasses[iIndex].c_str()) || + !idStr(activator->spawnArgs.GetString("classname")).IcmpPrefix(TriggerClasses[iIndex].c_str()) ) { + + if( activator->IsType(hhPlayer::Type) ) {//Player needs client + hhPlayer* player = static_cast(activator); + if ( player->noclip || !player->ShouldTouchTrigger(this) ) { + return false; + } + if ( bNoVehicles && player->InVehicle() ) { + return false; + } + } + if( activator->IsType(idActor::Type) ) { // Dead monsters shouldn't keep refires going + if (activator->health <= 0) { + return false; + } + } + + return true; + } + } + + if ( activator->IsType(hhMountedGun::Type) ) { + return true; + } + + //HUMANHEAD jsh PCF 4/28/06 hardcode monsters to trigger mines + if ( !gameLocal.isMultiplayer && activator->IsType( idAI::Type ) && activator->health > 0 ) { + return true; + } + + return false; +} \ No newline at end of file diff --git a/src/Prey/game_trigger.h b/src/Prey/game_trigger.h new file mode 100644 index 0000000..db11b85 --- /dev/null +++ b/src/Prey/game_trigger.h @@ -0,0 +1,258 @@ +#ifndef __PREY_TRIGGER_H +#define __PREY_TRIGGER_H + +class hhFuncParmAccessor : public idClass { + CLASS_PROTOTYPE( hhFuncParmAccessor ); + +public: + hhFuncParmAccessor(); + explicit hhFuncParmAccessor( const hhFuncParmAccessor* accessor ); + explicit hhFuncParmAccessor( const hhFuncParmAccessor& accessor ); + + void SetInfo( const char* returnKey, const function_t* func, const idList& parms ); + + const char* GetFunctionName() const; + const function_t* GetFunction() const; + const char* GetReturnKey() const; + idTypeDef* GetReturnType() const; + const char* GetParm( int index ) const; + idTypeDef* GetParmType( int index ) const; + const idList& GetParms() const; + idList& GetParms(); + + void Verify(); + + static function_t* FindFunction( const char* funcname ); + void CallFunction( idDict& returnDict ); + void ParseFunctionKeyValue( const char* value ); + void ParseFunctionKeyValue( idList& valueList ); + +public: + void SetFunction( const function_t* func ); + void SetParms( const idList& parms ); + void SetReturnKey( const char* key ); + + void SetParm_Entity( const idEntity *ent, int index ); + void SetParm_String( const char *str, int index ); + + void InsertParm_String( const char *text, int index ); + void InsertParm_Float( float value, int index ); + void InsertParm_Int( int value, int index ); + void InsertParm_Vector( const idVec3 &vec, int index ); + void InsertParm_Entity( const idEntity *ent, int index ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +protected: + void InsertParm( const char *text, int index ); + +protected: + idList parms; + idStr returnKey; + const function_t* function; +}; + +enum triggerBehavior_t { + TB_PLAYER_ONLY, + TB_FRIENDLIES_ONLY, + TB_MONSTERS_ONLY, + TB_PLAYER_MONSTERS_FRIENDLIES, + TB_SPECIFIC_ENTITIES, + TB_ANY, + NUM_BEHAVIORS +}; + +class hhTrigger : public idEntity +{ +public: + CLASS_PROTOTYPE( hhTrigger ); + + void Spawn( void ); + + virtual void Activate( idEntity *activator ); + virtual void TriggerAction( idEntity *activator ); + virtual void UnTriggerAction(); + bool IsEncroaching( const idEntity* entity ); + bool IsEncroached(); + + void SetTriggerClasses(idList& list); + virtual bool CheckTriggerClass(idEntity* activator); + virtual bool CheckUnTriggerClass(idEntity* activator); + void GetTriggerClasses(idDict& Args); + + virtual void Enable(); + virtual void Disable(); + + const bool IsActive() const { return bActive; } + const bool IsEnabled() const { return bEnabled; } + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +protected: + void CallFunctions( idEntity *activator ); + void UncallFunctions( idEntity *activator ); + + virtual void Event_Enable(); + virtual void Event_Disable(); + void Event_Activate( idEntity *activator ); + void Event_Deactivate(); + virtual void Event_Touch( idEntity *other, trace_t *trace ); + void Event_TriggerAction( idEntity *activator ); + virtual void Event_Retrigger( idEntity *activator ); + void Event_PollForUntouch(); + virtual void Event_UnTriggerAction(); + void Event_PostSpawn(); + +public: + bool bActive; // Whether the trigger is currently touched + bool bEnabled; // whether the trigger is currently triggerable + float untouchGranularity; // Seconds between untouch polls + float wait; // Seconds before trigger is triggerable again + float random; // Random wait variance + float delay; // Seconds to delay the triggering + float randomDelay; + float refire; // Seconds before refiring trigger + int nextTriggerTime; // Time in ms when the trigger will be triggerable again + bool alwaysTrigger; // Do we want to always trigger, instead of just once + bool isSimpleBox; // Simple boxes can use cheaper bounds test for encroach checks + bool bNoVehicles; + + hhFuncParmAccessor funcInfo; + hhFuncParmAccessor unfuncInfo; + hhFuncParmAccessor funcRefInfo; + hhFuncParmAccessor unfuncRefInfo; + hhFuncParmAccessor funcRefActivatorInfo; + hhFuncParmAccessor unfuncRefActivatorInfo; + + idStr requires; // item that the trigger requires of the player + int removeItem; // whether to remove the required item from players inventory + bool noTouch; // whether to disregard touch as triggering mechanism + bool initiallyEnabled; // whether the trigger is initially triggerable + bool bUntrigger; // whether to resend the trigger message when untriggered + idEntityPtr unTriggerActivator; // Used solely for sending to Activate() when bUntrigger is set + + idList TriggerClasses; + + triggerBehavior_t triggerBehavior; +}; + +class hhDamageTrigger : public hhTrigger +{ +public: + CLASS_PROTOTYPE( hhDamageTrigger ); + + void Spawn( void ); + 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 ); + +protected: + virtual void Event_Enable(); + void Event_Disable(); + + hhFuncParmAccessor funcRefInfoDamage; +}; + + +class hhTriggerPain : public hhTrigger { +public : + CLASS_PROTOTYPE( hhTriggerPain ); + + void Spawn( void ); + virtual void TriggerAction(idEntity *activator); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +protected: + void Event_Activate( idEntity *activator ); + void Event_DamageBox( idEntity *activator ); + +protected: + bool applyImpulse; +}; + + +class hhTriggerEnabler : public hhTrigger { +public : + CLASS_PROTOTYPE( hhTriggerEnabler ); + + void Spawn( void ); + virtual void TriggerAction(idEntity *activator); + virtual void UnTriggerAction(void); +}; + + +class hhTriggerSight : public hhTrigger { +public: + CLASS_PROTOTYPE( hhTriggerSight ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +protected: + virtual void Think( void ); + virtual void Event_Enable( void ); + void Event_Disable( void ); + + int pvsArea; +}; + +/* +=============================================================================== + + Trigger which fires targets after being activated a specific number of times. + +=============================================================================== +*/ +class hhTrigger_Count : public hhTrigger { +public: + CLASS_PROTOTYPE( hhTrigger_Count ); + + // 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 Activate( idEntity *activator ); + virtual void TriggerAction( idEntity *activator ); + +protected: + int goal; + int count; + float delay; +}; + +class hhTrigger_Event : public hhTrigger { +public: + CLASS_PROTOTYPE( hhTrigger_Event ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const { } + void Restore( idRestoreGame *savefile ) { Spawn(); } + +protected: + virtual void ActivateTargets( idEntity *activator ) const; + + const idEventDef* FindEventDef( const char* eventDefName ) const; + static function_t* FindFunction( const char* funcname ); + +protected: + const idEventDef* eventDef; +}; + +class hhMineTrigger : public hhTrigger { + CLASS_PROTOTYPE( hhMineTrigger ); + +public: + virtual bool CheckTriggerClass(idEntity* activator); +}; + +#endif diff --git a/src/Prey/game_tripwire.cpp b/src/Prey/game_tripwire.cpp new file mode 100644 index 0000000..dd41dfb --- /dev/null +++ b/src/Prey/game_tripwire.cpp @@ -0,0 +1,336 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +/********************************************************************** + +hhTriggerTripwire + +**********************************************************************/ +CLASS_DECLARATION( idEntity, hhTriggerTripwire ) + EVENT( EV_Enable, hhTriggerTripwire::Event_Enable ) + EVENT( EV_Disable, hhTriggerTripwire::Event_Disable ) + EVENT( EV_Activate, hhTriggerTripwire::Event_Activate ) + EVENT( EV_Deactivate, hhTriggerTripwire::Event_Deactivate ) +END_CLASS + +/* +================ +hhTriggerTripwire::Spawn +================ +*/ +void hhTriggerTripwire::Spawn( void ) { + m_fMaxBeamDistance = spawnArgs.GetFloat("lengthBeam", "4096"); + + m_CallFunc.ParseFunctionKeyValue( spawnArgs.GetString("call") ); + + m_CallFuncRef.ParseFunctionKeyValue( spawnArgs.GetString("callRef") ); + m_CallFuncRef.InsertParm_Entity( this, 0 ); + + m_CallFuncRefActivator.ParseFunctionKeyValue( spawnArgs.GetString("callRefActivator") ); + m_CallFuncRefActivator.InsertParm_Entity( this, 0 ); + + m_TriggerBehavior = (triggerBehavior_t)spawnArgs.GetInt("triggerBehavior"); + + GetPhysics()->GetContents( 0 ); + GetPhysics()->DisableClip(); + + // Spawn the eyebeam + m_pBeamEntity = hhBeamSystem::SpawnBeam( GetOrigin(), spawnArgs.GetString("beam") ); + if (m_pBeamEntity.IsValid()) { + m_pBeamEntity->SetOrigin( GetOrigin() ); + m_pBeamEntity->SetAxis( GetAxis() ); + m_pBeamEntity->Bind( this, true ); + } + + GetTriggerClasses( spawnArgs ); + + ToggleBeam( true ); + + m_pOwner = NULL; +} + +void hhTriggerTripwire::Save(idSaveGame *savefile) const { + savefile->WriteFloat(m_fMaxBeamDistance); + + savefile->WriteStaticObject(m_CallFunc); + savefile->WriteStaticObject(m_CallFuncRef); + savefile->WriteStaticObject(m_CallFuncRefActivator); + + m_pBeamEntity.Save(savefile); + + savefile->WriteInt(m_TriggerClasses.Num()); // idList + for (int i=0; iWriteString(m_TriggerClasses[i]); + } + + savefile->WriteInt(m_TriggerBehavior); + savefile->WriteObject( m_pOwner ); +} + +void hhTriggerTripwire::Restore( idRestoreGame *savefile ) { + int num; + + savefile->ReadFloat(m_fMaxBeamDistance); + + savefile->ReadStaticObject(m_CallFunc); + savefile->ReadStaticObject(m_CallFuncRef); + savefile->ReadStaticObject(m_CallFuncRefActivator); + + m_pBeamEntity.Restore(savefile); + + m_TriggerClasses.Clear(); + savefile->ReadInt(num); // idList + m_TriggerClasses.SetNum(num); + for (int i=0; iReadString(m_TriggerClasses[i]); + } + + savefile->ReadInt((int&)m_TriggerBehavior); + savefile->ReadObject( reinterpret_cast( m_pOwner ) ); +} + +/* +=============== +hhTriggerTripwire::~hhTriggerTripwire +=============== +*/ + +hhTriggerTripwire::~hhTriggerTripwire() { + SAFE_REMOVE( m_pBeamEntity ); +} + +/* +================ +hhSecurityEyeTripwire::SetOwner +================ +*/ +void hhTriggerTripwire::SetOwner( idEntity* pOwner ) { + m_pOwner = pOwner; +} + +/* +=============== +hhTriggerTripwire::NotifyTargets +=============== +*/ +void hhTriggerTripwire::NotifyTargets( idEntity* pActivator ) { + ActivateTargets( pActivator ); + + if( m_pOwner ) { + m_pOwner->ProcessEvent( &EV_Notify, pActivator ); + } +} + +/* +=============== +hhTriggerTripwire::ToggleBeam +=============== +*/ +void hhTriggerTripwire::ToggleBeam( bool bOn ) { + if( bOn ) { + BecomeActive( TH_TICKER ); + } + else { + BecomeInactive( TH_TICKER ); + } + + if( m_pBeamEntity.IsValid() ) { + m_pBeamEntity->Activate( bOn ); + } +} + +/* +=============== +hhTriggerTripwire::Ticker +=============== +*/ +void hhTriggerTripwire::Ticker() { + trace_t TraceInfo; + idEntity* pTraceTarget = NULL; + idVec3 TraceLength = GetAxis()[0] * m_fMaxBeamDistance; + idVec3 TraceEndPoint = GetOrigin() + TraceLength; + + gameLocal.clip.TracePoint( TraceInfo, GetOrigin(), TraceEndPoint, MASK_VISIBILITY, this ); + + // Update the beam system + if( m_pBeamEntity.IsValid() ) { + m_pBeamEntity->SetTargetLocation( TraceInfo.endpos ); + } + + if( TraceInfo.fraction < 1.0f && TraceInfo.c.entityNum < ENTITYNUM_MAX_NORMAL ) { + pTraceTarget = gameLocal.GetTraceEntity(TraceInfo); + if( CheckTriggerClass(pTraceTarget) ) { + CallFunctions( pTraceTarget ); + + NotifyTargets( pTraceTarget ); + + if( spawnArgs.GetBool("triggerOnce") ) { + PostEventMS( &EV_Disable, 0 ); + } + return; + } + } + + if(p_tripwireDebug.GetBool()) { + gameRenderWorld->DebugLine(colorRed, GetPhysics()->GetOrigin(), GetPhysics()->GetOrigin() + TraceLength * TraceInfo.fraction); + + gameRenderWorld->DebugBox( colorBlue, idBox(GetPhysics()->GetBounds(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis()) ); + } +} + +/* +================ +hhTriggerTripwire::CallFunctions +================ +*/ +void hhTriggerTripwire::CallFunctions( idEntity *activator ) { + m_CallFunc.CallFunction( spawnArgs ); + m_CallFuncRef.CallFunction( spawnArgs ); + + m_CallFuncRefActivator.InsertParm_Entity( activator, 1 );//Needs to be second parm. After self + m_CallFuncRefActivator.CallFunction( spawnArgs ); +} + +/* +=============== +hhTriggerTripwire::SetTriggerClasses +=============== +*/ +void hhTriggerTripwire::SetTriggerClasses( idList& List ) { + m_TriggerClasses.Clear(); + + m_TriggerClasses.SetNum( List.Num() ); + + for(int iIndex = 0; iIndex < List.Num(); ++iIndex) { + m_TriggerClasses[iIndex] = List[iIndex]; + } +} + +/* +=============== +hhTriggerTripwire::CheckTriggerClass +=============== +*/ +bool hhTriggerTripwire::CheckTriggerClass( idEntity* pActivator ) { + if( !pActivator ) { + return false; + } + + if(!m_TriggerClasses.Num() || m_TriggerBehavior == TB_ANY) { + return true; + } + + for(int iIndex = 0; iIndex < m_TriggerClasses.Num(); ++iIndex) { + //Look for exact match then try for prefix match + if( !idStr(pActivator->spawnArgs.GetString("classname")).Icmp(m_TriggerClasses[iIndex].c_str()) || + !idStr(pActivator->spawnArgs.GetString("classname")).IcmpPrefix(m_TriggerClasses[iIndex].c_str()) ) { + if( pActivator && pActivator->IsType(hhPlayer::Type) ) {//Player needs client + hhPlayer* pPlayer = static_cast(pActivator); + if( !pPlayer || pPlayer->noclip || (pPlayer->GetPhysics()->GetClipMask() & MASK_SPIRITPLAYER) == MASK_SPIRITPLAYER ) { + return false; + } + } + // Vehicles are added for trigger behaviors including players, so here we check if the pilot is a player and should touch triggers + // Monsters piloting vehicles currently do not get scanned by tripwires + if( pActivator && pActivator->IsType(hhVehicle::Type) ) { + hhVehicle *pVehicle = static_cast(pActivator); + if( !pVehicle || pVehicle->IsNoClipping() || !pVehicle->GetPilot() || !pVehicle->GetPilot()->IsType(hhPlayer::Type) ) { + return false; + } + } + return true; + } + } + + return false; +} + +/* +=============== +hhTriggerTripwire::GetTriggerClasses +=============== +*/ +void hhTriggerTripwire::GetTriggerClasses( idDict& Args ) { + const idKeyValue* pKeyValue = NULL; + int iNumKeyValues = Args.GetNumKeyVals(); + + m_TriggerClasses.Clear(); + + switch( m_TriggerBehavior ) { + case TB_PLAYER_ONLY: + m_TriggerClasses.AddUnique( "player" ); + m_TriggerClasses.AddUnique( "vehicle" ); + break; + + case TB_FRIENDLIES_ONLY: + m_TriggerClasses.AddUnique( "player" ); + m_TriggerClasses.AddUnique( "vehicle" ); + m_TriggerClasses.AddUnique( "character" ); + break; + + case TB_MONSTERS_ONLY: + m_TriggerClasses.AddUnique( "monster" ); + break; + + case TB_PLAYER_MONSTERS_FRIENDLIES: + m_TriggerClasses.AddUnique( "player" ); + m_TriggerClasses.AddUnique( "vehicle" ); + m_TriggerClasses.AddUnique( "monster" ); + m_TriggerClasses.AddUnique( "character" ); + break; + + case TB_SPECIFIC_ENTITIES: + for( int iIndex = 0; iIndex < iNumKeyValues; ++iIndex ) { + pKeyValue = Args.GetKeyVal( iIndex ); + if ( !pKeyValue->GetKey().Cmpn( "trigger_class", 13 ) ) { + m_TriggerClasses.AddUnique( pKeyValue->GetValue() ); + } + } + break; + + default: + HH_ASSERT(!"Invalid trigger behavior!\n"); + break; + } +} + +/* +================ +hhTriggerTripwire::Event_Enable +================ +*/ +void hhTriggerTripwire::Event_Enable( void ) { + ToggleBeam( true ); +} + +/* +================ +hhTriggerTripwire::Event_Disable +================ +*/ +void hhTriggerTripwire::Event_Disable( void ) { + ToggleBeam( false ); +} + +/* +================ +hhTriggerTripwire::Event_Activate +================ +*/ +void hhTriggerTripwire::Event_Activate( idEntity *activator ) { + ToggleBeam( true ); +} + +/* +================ +hhTriggerTripwire::Event_Deactivate +================ +*/ +void hhTriggerTripwire::Event_Deactivate() { + ToggleBeam( false ); +} + +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build diff --git a/src/Prey/game_tripwire.h b/src/Prey/game_tripwire.h new file mode 100644 index 0000000..9eb17f4 --- /dev/null +++ b/src/Prey/game_tripwire.h @@ -0,0 +1,50 @@ +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +#ifndef __HH_TRIGGER_TRIPWIRE_H +#define __HH_TRIGGER_TRIPWIRE_H + +class hhTriggerTripwire : public idEntity { + CLASS_PROTOTYPE( hhTriggerTripwire ); + +public: + ~hhTriggerTripwire(); + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void ToggleBeam( bool bOn ); + void SetOwner( idEntity* pOwner ); + const idEntity* GetOwner() const { return m_pOwner; } + +protected: + virtual void NotifyTargets( idEntity* pActivator ); + + void SetTriggerClasses( idList& List ); + virtual bool CheckTriggerClass( idEntity* pActivator ); + void GetTriggerClasses( idDict& Args ); + + void CallFunctions( idEntity* activator ); + +protected: + virtual void Ticker(); + void Event_Enable(); + void Event_Disable(); + void Event_Activate( idEntity *activator ); + void Event_Deactivate(); + +public: + float m_fMaxBeamDistance; + +protected: + idEntity* m_pOwner; + hhFuncParmAccessor m_CallFunc; + hhFuncParmAccessor m_CallFuncRef; + hhFuncParmAccessor m_CallFuncRefActivator; + + idEntityPtr m_pBeamEntity; + + idList m_TriggerClasses; + triggerBehavior_t m_TriggerBehavior; +}; + +#endif +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/src/Prey/game_utils.cpp b/src/Prey/game_utils.cpp new file mode 100644 index 0000000..1031d42 --- /dev/null +++ b/src/Prey/game_utils.cpp @@ -0,0 +1,749 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +////////////////////// +// hhMatterEventDefPartner +////////////////////// +hhMatterEventDefPartner::hhMatterEventDefPartner( const char* eventNamePrefix ) { + const char* event = NULL; + const idEventDef* eventDef = NULL; + surfTypes_t type = SURFTYPE_NONE; + + for( int ix = 0; ix < NUM_SURFACE_TYPES; ++ix ) { + type = (surfTypes_t)ix; + event = va( "<%s_%s>", eventNamePrefix, gameLocal.MatterTypeToMatterName(type) ); + eventDef = idEventDef::FindEvent( event ); + AddPartner( eventDef, type ); + } +} + + +////////////////////// +// hhUtils +////////////////////// + + + +int hhUtils::ContentsOfBounds(const idBounds &localBounds, const idVec3 &location, const idMat3 &axis, idEntity *pass) { + idTraceModel trm; + trm.SetupBox(localBounds); + idClipModel *clipModel = new idClipModel(trm); + int contents = gameLocal.clip.Contents(location, clipModel, axis, MASK_ALL, pass); + delete clipModel; + return contents; +} + +/* +==================== +hhUtils::EntityDefWillFit + Check whether a given entity def will fit in a location if spawned, pass the contents that would block it +==================== +*/ +bool hhUtils::EntityDefWillFit( const char *defName, const idVec3 &location, const idMat3 &axis, int contentMask, idEntity *passEntity ) { + idBounds localBounds; + idVec3 size; + + // Calculate a local bounds for object + const idDict *dict = gameLocal.FindEntityDefDict(defName, false); + localBounds.Zero(); + if (dict) { + if ( dict->GetVector( "mins", NULL, localBounds[0] ) && + dict->GetVector( "maxs", NULL, localBounds[1] ) ) { + } else if ( dict->GetVector( "size", NULL, size ) ) { + localBounds[0].Set( size.x * -0.5f, size.y * -0.5f, 0.0f ); + localBounds[1].Set( size.x * 0.5f, size.y * 0.5f, size.z ); + } else { // Default bounds + localBounds.Expand( 1.0f ); + } + } + + // Determine contents of the bounds + idTraceModel trm; + trm.SetupBox(localBounds); + idClipModel *clipModel = new idClipModel(trm); + int contents = gameLocal.clip.Contents(location, clipModel, axis, contentMask, passEntity); + delete clipModel; + +/* if (contents & CONTENTS_SOLID) { + gameRenderWorld->DebugBounds( colorRed, localBounds, location, 5000); + } + else if (contents == 0) { + gameRenderWorld->DebugBounds( colorGreen, localBounds, location, 5000); + } + else { + gameRenderWorld->DebugBounds( colorYellow, localBounds, location, 5000); + }*/ + + return (contents == 0); +} + + +/* +==================== +hhUtils::SpawnDebrisMass +==================== +*/ +void hhUtils::SpawnDebrisMass( const char *debrisMassEntity, + const idVec3 &origin, + const idVec3 *orientation, + const idVec3 *velocity, + const int power, + bool nonsolid, + float *duration, + idEntity *entForBounds ) { //HUMANHEAD rww - added entForBounds + + idDict args; + idVec3 powerVector; + + + if ( !debrisMassEntity || !debrisMassEntity[0] ) { + gameLocal.Warning( "Invalid Debris Entity called (%p)", debrisMassEntity ); + return; + } + + args.SetVector( "origin", origin ); + + // Set the values to the dictionary + if ( orientation ) { + args.SetVector( "orientation", *orientation ); + } + + if ( power >= 0 ) { + powerVector.x = powerVector.y = powerVector.z = power; + args.SetVector( "power", powerVector ); + } + + if ( nonsolid ) { + args.Set( "nonsolid", "1" ); + } + + if ( velocity ) { + args.SetVector( "velocity", *velocity ); + } + + if (entForBounds) { //HUMANHEAD rww + args.SetBool("spawnUsingEntity", true); + args.SetBool("useAFBounds", true); + } + + // Spawn the object + idEntity *ent = gameLocal.SpawnObject( debrisMassEntity, &args ); + + if ( duration && ent->IsType( hhDebrisSpawner::Type ) ) { + hhDebrisSpawner *dspawner = static_cast ( ent ); + + if (entForBounds) { //HUMANHEAD rww + dspawner->Activate(entForBounds); + } + + *duration = dspawner->GetDuration(); + } +} + + +/* +==================== +hhUtils::SpawnDebrisMass +==================== +*/ +void hhUtils::SpawnDebrisMass( const char *debrisMassEntity, + idEntity *sourceEntity, + const int power ) { + + idEntity *mass; + idDict args; + + + if ( !debrisMassEntity || !debrisMassEntity[0] ) { + gameLocal.Warning( "Invalid Debris Entity called (%p).", debrisMassEntity ); + return; + } + + args.SetVector( "origin", sourceEntity->GetOrigin() ); + args.SetVector( "orientation", sourceEntity->GetPhysics()->GetAxis()[0] ); + args.SetVector( "velocity", sourceEntity->GetPhysics()->GetLinearVelocity() ); + + if ( power >= 0 ) { + args.SetInt( "power", power ); + } + + args.SetInt( "spawnUsingEntity" , 1 ); + + + // Spawn the object + mass = gameLocal.SpawnObject( debrisMassEntity, &args ); + if ( mass ) { + if ( mass->IsType( hhDebrisSpawner::Type ) ) { + ( ( hhDebrisSpawner * ) mass )->Activate( sourceEntity ); + } + } + else { + gameLocal.Printf("Error spawning debris mass: %s\n", debrisMassEntity ); + } + +} + + +hhProjectile *hhUtils::LaunchProjectile(idEntity *attacker, + const char *projectile, + const idMat3 &axis, + const idVec3 &origin ) { + + hhProjectile *proj=NULL; + idDict args; + args.Clear(); + args.Set( "classname", projectile ); + args.Set( "origin", origin.ToString() ); + + idEntity *ent=NULL; + if (gameLocal.SpawnEntityDef( args, &ent ) && ent && ent->IsType(hhProjectile::Type)) { + proj = ( hhProjectile * )ent; + proj->Create( attacker, origin, axis ); + proj->Launch( origin, axis, vec3_origin ); + } + + return proj; +} + +void hhUtils::DebugCross( const idVec4 &color, const idVec3 &start, int size, const int lifetime ) { + static idVec3 xaxis(1.0f,0.0f,0.0f); + static idVec3 yaxis(0.0f,1.0f,0.0f); + static idVec3 zaxis(0.0f,0.0f,1.0f); + gameRenderWorld->DebugLine(color, start-xaxis*size, start+xaxis*size, lifetime); + gameRenderWorld->DebugLine(color, start-yaxis*size, start+yaxis*size, lifetime); + gameRenderWorld->DebugLine(color, start-zaxis*size, start+zaxis*size, lifetime); +} + +void hhUtils::DebugAxis( const idVec3 &origin, const idMat3 &axis, int size, const int lifetime ) { + gameRenderWorld->DebugArrow(colorRed, origin, origin+axis[0]*size, 5, lifetime); + gameRenderWorld->DebugArrow(colorGreen, origin, origin+axis[1]*size, 5, lifetime); + gameRenderWorld->DebugArrow(colorBlue, origin, origin+axis[2]*size, 5, lifetime); +} + + +idVec3 hhUtils::RandomVector() { + idVec3 vec; + vec.x = gameLocal.random.CRandomFloat(); + vec.y = gameLocal.random.CRandomFloat(); + vec.z = gameLocal.random.CRandomFloat(); + return vec; +} + +float hhUtils::RandomSign() { + return gameLocal.random.RandomFloat() < 0.5f ? -1.0f : 1.0f; +} + +idVec3 hhUtils::RandomPointInBounds(idBounds &bounds) { + idVec3 point; + point.x = bounds[0].x + (bounds[1].x - bounds[0].x) * gameLocal.random.RandomFloat(); + point.y = bounds[0].y + (bounds[1].y - bounds[0].y) * gameLocal.random.RandomFloat(); + point.z = bounds[0].z + (bounds[1].z - bounds[0].z) * gameLocal.random.RandomFloat(); + return point; +} + +idVec3 hhUtils::RandomPointInShell( const float innerRadius, const float outerRadius ) { + idAngles dir( gameLocal.random.RandomFloat() * 360.0f, gameLocal.random.RandomFloat() * 360.0f, gameLocal.random.RandomFloat() * 360.0f ); + return dir.ToForward() * (innerRadius + (outerRadius - innerRadius) * gameLocal.random.RandomFloat()); +} + +/* +================= +hhUtils::SplitString + Strips out the first element which is usually the original command +================= +*/ +void hhUtils::SplitString( const idCmdArgs& input, idList& pieces ) { + int numParms = input.Argc() - 1; + for( int ix = 0; ix < numParms; ++ix ) { + pieces.Append( input.Argv(ix + 1) ); + } +} + +/* +================= +hhUtils::SplitString + Takes a comma seperated string, and returns the bits between the commas + Spaces on either side of the comma are stripped off + The 'pieces' list is not cleared. +================= +*/ +void hhUtils::SplitString( const idStr& input, idList& pieces, const char delimiter ) { + idStr element; + int endIndex = -1; + const char groupDelimiter = '\''; + char currentChar = '\0'; + + for( int startIndex = 0; startIndex <= input.Length(); ++startIndex ) { + currentChar = input[ startIndex ]; + if (currentChar) { + if( currentChar == groupDelimiter ) { + endIndex = input.Find( currentChar, startIndex + 1 ); + element = input.Mid( startIndex + 1, endIndex - startIndex - 1 ); + + startIndex = endIndex; + + pieces.Append( element ); + element.Clear(); + continue; + } else if( currentChar == delimiter ) { + element += '\0'; + pieces.Append( element ); + element.Clear(); + continue; + } + + element += currentChar; + } + } + + if( element.Length() ) { + pieces.Append( element ); + } +} + + +/* +================= +hhUtils::GetValues +Takes a source dict, and a key base string, and returns all values for keys with that base. ie: + + "base" "b" + "base1" "b1" + "base4" "b4" + "base_bob" "bob" + + is the dict. + +The option 'numericOnly' restricts the keys to be either an exact match, or a numeric addition + + hhUtils::GetValues( dict, "base", strList, true ); + +strList would contain: "b", "b1", "b4" + +while: + + hhUtils::GetValues( dict, "base", strList ); + +strList would contain: "b", "b1", "b4", "bob" + +================= +*/ +void hhUtils::GetValues( idDict &source, const char *keyBase, idList &values, bool numericOnly ) { + idList keys; + + GetKeysAndValues( source, keyBase, keys, values, numericOnly ); +} // hhUtils::GetValues( idDict &, const char *, idList, [bool] ) + + +/* +================= +hhUtils::GetKeys +Takes a source dict, and a key base string, and returns all keys with that base. ie: + + "base" "b" + "base1" "b1" + "base4" "b4" + "base_bob" "bob" + + is the dict. + +The option 'numericOnly' restricts the keys to be either an exact match, or a numeric addition + + hhUtils::GetValues( dict, "base", strList, true ); + +strList would contain: "base", "base1", "base4" + +while: + + hhUtils::GetValues( dict, "base", strList ); + +strList would contain: "base", "base1", "base4", "base_bob" + +================= +*/ +void hhUtils::GetKeys( idDict &source, const char *keyBase, idList &keys, bool numericOnly ) { + idList values; + + GetKeysAndValues( source, keyBase, keys, values, numericOnly ); +} + + +/* +============== +hhUtils::getKeysAndValues +============== +*/ +void hhUtils::GetKeysAndValues( idDict &source, const char *keyBase, idList &keys, idList &values, bool numericOnly ) { + const idKeyValue * kv = NULL; + idStr keyEnd; + + + for ( kv = source.MatchPrefix( keyBase, kv ); + kv && kv->GetValue().Length(); + kv = source.MatchPrefix( keyBase, kv ) ) { + keyEnd = kv->GetKey(); + keyEnd.Strip( keyBase ); + + // Is a valid debris base And it isn't a variation. (ie, contains a .X postfix) + if ( !numericOnly || ( idStr::IsNumeric( keyEnd ) && keyEnd.Find( '.' ) < 0 ) ) { + //gameLocal.Printf( "Adding %s => %s\n", (const char *) kv->GetKey(), (const char *) kv->GetValue() ); + keys.Append( kv->GetKey() ); + values.Append( kv->GetValue() ); + } + + } + +} + +/* +================= +hhUtils::RandomSpreadDir +================= +*/ +idVec3 hhUtils::RandomSpreadDir( const idMat3& baseAxis, const float spread ) { + float ang = hhMath::Sin( spread * gameLocal.TimeBasedRandomFloat() ); + float spin = hhMath::TWO_PI * gameLocal.TimeBasedRandomFloat(); + idVec3 dir = baseAxis[ 0 ] + baseAxis[ 2 ] * ( ang * hhMath::Sin(spin) ) - baseAxis[ 1 ] * ( ang * hhMath::Cos(spin) ); + dir.Normalize(); + + return dir; +} + +//----------------------------------------------------- +// ProjectOntoScreen +// +// Project a world position onto screen +//----------------------------------------------------- +idVec3 hhUtils::ProjectOntoScreen(idVec3 &world, const renderView_t &renderView) { + idVec3 pdc(-1000.0f, -1000.0f, -1.0f); + + // Convert world -> camera + idVec3 view = ( world - renderView.vieworg ) * renderView.viewaxis.Inverse(); + + // Orient from doom coords to camera coords (look down +Z) + idVec3 cam; + cam.x = -view[1]; + cam.y = -view[2]; + cam.z = view[0]; + + if (cam.z > 0.0f) { + // Adjust for differing FOVs + float halfwidth = renderView.width * 0.5f; + float halfheight = renderView.height * 0.5f; + float f = halfwidth / tan( renderView.fov_x * 0.5f * idMath::M_DEG2RAD ); + float g = halfheight / tan( renderView.fov_y * 0.5f * idMath::M_DEG2RAD ); + + // Project onto screen + pdc[0] = (cam.x * f / cam.z) + halfwidth; + pdc[1] = (cam.y * g / cam.z) + halfheight; + pdc[2] = cam.z; + } + + return pdc; // negative Z indicates behind the view +} + + +/* +================= +hhUtils::GetLocalGravity +================= +*/ +idVec3 hhUtils::GetLocalGravity( const idVec3& origin, const idBounds& bounds, const idVec3& defaultGravity ) { + idEntity* entityList[ MAX_GENTITIES ]; + idEntity* entity = NULL; + hhGravityZoneBase* zone = NULL; + + int numEntities = gameLocal.clip.EntitiesTouchingBounds( bounds.Translate(origin), CONTENTS_TRIGGER, entityList, MAX_GENTITIES ); + for( int ix = 0; ix < numEntities; ++ix ) { + entity = entityList[ ix ]; + + if( !entity ) { + continue; + } + + if( !entity->IsType(hhGravityZoneBase::Type) ) { + continue; + } + + zone = static_cast( entity ); + if( !zone->IsActive() || !zone->IsEnabled() ) { + continue; + } + + // More expensive check if non-simple zone + if ( !zone->isSimpleBox ) { + idTraceModel *playerTrm = new idTraceModel(bounds); + idClipModel *playerModel = new idClipModel( *playerTrm ); + playerModel->SetContents(CONTENTS_TRIGGER); + + idClipModel *zoneModel = zone->GetPhysics()->GetClipModel(); + + int contents = gameLocal.clip.ContentsModel( playerModel->GetOrigin(), playerModel, playerModel->GetAxis(), -1, + zoneModel->Handle(), zoneModel->GetOrigin(), zoneModel->GetAxis() ); + + delete playerModel; + delete playerTrm; + if ( !contents ) { + continue; + } + } + + return zone->GetCurrentGravity( origin ); + } + + return defaultGravity; +} + +//============================================================================= +// +// PointToAngle +// +// Point should be about the origin +//============================================================================= +float hhUtils::PointToAngle(float x, float y) +{ + if ( x == 0 && y == 0 ) { + return 0; + } + + if ( x >= 0 ) { // x >= 0 + if (y >= 0 ) { // y >= 0 + if ( x > y ) { + return atan( y / x ); // octant 0 + } else { + return DEG2RAD(90) - atan( x / y ); // octant 1 + } + } + else { // y < 0 + y = -y; + if ( x > y ) { + return -atan( y / x ); // octant 8 + } else { + return DEG2RAD(270) + atan( x / y ); // octant 7 + } + } + } + else { // x < 0 + x = -x; + if ( y >= 0 ) { // y>= 0 + if ( x > y ) { + return DEG2RAD(180) - atan( y / x ); // octant 3 + } + else { + return DEG2RAD(90) + atan( x / y ); // octant 2 + } + } + else { // y < 0 + y = -y; + if ( x > y ) { + return DEG2RAD(180) + atan( y / x ); // octant 4 + } else { + return DEG2RAD(270) - atan( x / y ); // octant 5 + } + } + } + + return 0; +} + +float hhUtils::DetermineFinalFallVelocityMagnitude( const float totalFallDist, const float gravity ) { + if( hhMath::Fabs(gravity) <= VECTOR_EPSILON ) { + return 0.0f; + } + + return gravity * hhMath::Sqrt( 2.0f * totalFallDist / gravity ); +} + +/* +====================== +hhUtils::ChannelName2Num +returns -1 if not found +HUMANHEAD nla +====================== +*/ +int hhUtils::ChannelName2Num( const char *name, const idDict *entityDef ) { + int num; + + entityDef->GetInt( va( "channel2num_%s", name ), "-1", num ); + + return( num ); +} + + +/* +================ +hhUtils::CreateFxDefinition + +//HUMANHEAD: aob - used for our wound system +================ +*/ +void hhUtils::CreateFxDefinition( idStr &definition, const char* smokeName, const float duration ) { + + definition.Clear(); + + if( !smokeName || !smokeName[0] ) { + return; + } + + definition = va( + "fx %s // IMPLICITLY GENERATED\n" + "{ {\n" + "name \"%s\"\n" + "duration %.2f\n" + "delay 0\n" + "restart 0\n" + "particle \"%s.prt\"\n" + "} }\n", smokeName, smokeName, duration, smokeName); +} + +/* +================ +hhUtils::CalculateSoundVolume + Calculate a linear volume from a velocity magnitude and min/max range +================ +*/ +float hhUtils::CalculateSoundVolume( const float value, const float min, const float max ) { + float linear; + float decibels; + + if( min == max ) { + return 0.0f; + } + + linear = hhMath::ClampFloat( 0.0f, 1.0f, (value - min) / (max - min) ); + linear = (linear * 0.95f) + 0.05f; // Make lower end -30dB not -60dB + + decibels = hhMath::Scale2dB(linear); + decibels -= 3; + linear = hhMath::dB2Scale(decibels); + + return linear; +} + +/* +================ +hhUtils::CalculateScale +================ +*/ +float hhUtils::CalculateScale( const float value, const float min, const float max ) { + if( min == max ) { + return 0.0f; + } + + return hhMath::ClampFloat( 0.0f, 1.0f, (value - min) / (max - min) ); +} + +// Find all entities overlapping this clipmodel +// Pass a contents to limit the entities found to those matching that contents +int hhUtils::EntitiesTouchingClipmodel( idClipModel *clipModel, idEntity **entityList, int maxCount, int contents ) { + int i, j, numClipModels, numEntities; + idClipModel * clipModels[ MAX_GENTITIES ]; + idClipModel * cm; + idEntity * ent; + + numClipModels = gameLocal.clip.ClipModelsTouchingBounds( clipModel->GetAbsBounds(), contents, clipModels, MAX_GENTITIES ); + numEntities = 0; + + // For each entity in bounds of our clipModel + // test if entity overlaps the clipModel + for ( i = 0; i < numClipModels; i++ ) { + cm = clipModels[ i ]; + + if (cm == clipModel) { + continue; + } + + ent = cm->GetEntity(); + + if (!ent || !ent->GetPhysics()->GetClipModel()->IsTraceModel()) { + // Can only test versus trace models + continue; + } + + // if the entity is not yet in the list + for ( j = 0; j < numEntities; j++ ) { + if ( entityList[j] == ent ) { + break; + } + } + if ( j < numEntities ) { + continue; + } + + // Test if entity clipmodel overlaps our clipmodel + if ( !ent->GetPhysics()->ClipContents( clipModel ) ) { + continue; + } + + // add entity to the list + if ( numEntities >= maxCount ) { + gameLocal.Warning( "hhUtils::EntitiesTouchingClipmodel: max count" ); + break; + } + entityList[numEntities++] = ent; + + } + + return numEntities; +} + +idBounds hhUtils::ScaleBounds( const idBounds& bounds, float scale ) { + idBounds localBounds; + + if( scale <= 0.0 || scale == 1.0f ) { + return bounds; + } + + //Put bounds at origin so our scale is done correctly + localBounds = bounds.Translate( -bounds.GetCenter() ); + + for( int ix = 0; ix < 2; ++ix ) { + localBounds[ix] *= scale; + } + + return localBounds; +} + +idVec3 hhUtils::DetermineOppositePointOnBounds( const idVec3& start, const idVec3& dir, const idBounds& bounds ) { + float scale = 0.0f; + float radius = bounds.GetRadius(); + + if( !bounds.RayIntersection(start, dir, scale) ) { + return start; + } + + if( !bounds.RayIntersection(start + dir * radius, -dir, scale) ) { + return start; + } + + return start + dir * (radius - scale); +} + +/* +============= +hhUtils::PassArgs + Take any args that begin with prefix, and pass them into dict +============= +*/ + +void hhUtils::PassArgs( const idDict &source, idDict &dest, const char *passPrefix ) { + const idKeyValue * kv = NULL; + idStr indexStr; + + // Loop through looking for the next valid key to pass + for ( kv = source.MatchPrefix( passPrefix, kv ); + kv && kv->GetValue().Length(); + kv = source.MatchPrefix( passPrefix, kv ) ) { + indexStr = kv->GetKey(); + indexStr.Strip( passPrefix ); + +// gameLocal.Printf( "Passing %s => %s\n", +// (const char *) indexStr, +// (const char *) kv->GetValue() ); + + dest.Set( indexStr, kv->GetValue() ); + } +} + diff --git a/src/Prey/game_utils.h b/src/Prey/game_utils.h new file mode 100644 index 0000000..ca97c29 --- /dev/null +++ b/src/Prey/game_utils.h @@ -0,0 +1,318 @@ + +#ifndef __GAME_UTILS_H__ +#define __GAME_UTILS_H__ + +//Helper defines to allow line commenting with certain defines. +#define COMMENT SLASH(/) +#define SLASH(s) /##s + +////////////////////// +// hhMatterPartner +////////////////////// +template< class PartnerType > +class hhMatterPartner { +public: + const PartnerType& GetPartner( const idEntity* ent, const idMaterial* material ) const; + const PartnerType& GetPartner( surfTypes_t type ) const; + +protected: + void AddPartner( PartnerType partner, surfTypes_t type ); + +protected: + // HUMANHEAD mdl: This doesn't need to be saved because it's generated automatically by the constructor. I think. + idList< PartnerType > matterPartnerList; +}; + +template< class PartnerType > +const PartnerType& hhMatterPartner::GetPartner( const idEntity* ent, const idMaterial* material ) const { + return GetPartner( gameLocal.GetMatterType(ent, material) ); +} + +template< class PartnerType > +const PartnerType& hhMatterPartner::GetPartner( surfTypes_t type ) const { + return matterPartnerList[ (int)type ]; +} + +template< class PartnerType > +void hhMatterPartner::AddPartner( PartnerType partner, surfTypes_t type ) { + matterPartnerList.Insert( partner, (int)type ); +} + +////////////////////// +// hhMatterEventDefPartner +////////////////////// +class hhMatterEventDefPartner : public hhMatterPartner { +public: + hhMatterEventDefPartner( const char* eventNamePrefix ); +}; + + +////////////////////// +// hhUtils +////////////////////// +class hhUtils { +public: + static int ContentsOfBounds(const idBounds &localBounds, const idVec3 &location, const idMat3 &axis, idEntity *pass); + static bool EntityDefWillFit( const char *defName, const idVec3 &location, const idMat3 &axis, int contentMask, idEntity *pass ); + static void SpawnDebrisMass( const char *debrisEntity, + const idVec3 &origin, + const idVec3 *orientation = NULL, + const idVec3 *velocity = NULL, + const int power = -1, + bool nonsolid = false, + float *duration = NULL, + idEntity *entForBounds = NULL ); //HUMANHEAD rww - added entForBounds + + static void SpawnDebrisMass( const char *debrisEntity, + idEntity *sourceEntity, + const int power = -1 ); + + static hhProjectile * LaunchProjectile( idEntity *attacker, + const char *projectile, + const idMat3 &axis, + const idVec3 &origin ); + + static void DebugAxis( const idVec3 &origin, const idMat3 &axis, int size, const int lifetime ); + static void DebugCross( const idVec4 &color, + const idVec3 &start, + int size, + const int lifetime = 0 ); + + static idVec3 RandomVector(); + static float RandomSign(); + static idVec3 RandomSpreadDir( const idMat3& baseAxis, const float spread ); + + static idVec3 RandomPointInBounds(idBounds &bounds); + static idVec3 RandomPointInShell( const float innerRadius, const float outerRadius ); + + static void SplitString( const idCmdArgs& input, idList& pieces ); + static void SplitString( const idStr& input, idList& pieces, const char delimiter = ',' ); + + static idVec3 GetLocalGravity( const idVec3& origin, const idBounds& bounds, const idVec3& defaultGravity = gameLocal.GetGravity() ); + + static idVec3 ProjectOntoScreen(idVec3 &world, const renderView_t &renderView); + + static void GetValues( idDict& source, const char *keyBase, idList &values, bool numericOnly = false ); + + static void GetKeys( idDict& source, const char *keyBase, idList &keys, bool numericOnly = false ); + + static void GetKeysAndValues( idDict& source, const char *keyBase, idList &keys, idList &values, bool numericOnly = false ); + + static float DetermineFinalFallVelocityMagnitude( const float totalFallDist, const float gravity ); + + static int ChannelName2Num( const char *name, const idDict *entityDef ); + + static int EntitiesTouchingClipmodel(idClipModel *clipModel, idEntity **entityList, int maxCount, int contents=MASK_SHOT_BOUNDINGBOX ); + + static void PassArgs( const idDict &source, idDict &dest, const char *passPrefix = "pass_" ); + + static float PointToAngle(float x, float y); + + static idMat3 SwapXZ( const idMat3& axis ); + + static void CreateFxDefinition( idStr &definition, const char* smokeName, const float duration ); + + static float CalculateSoundVolume( const float value, const float min, const float max ); + + static float CalculateScale( const float value, const float min, const float max ); + + static idBounds ScaleBounds( const idBounds& bounds, float scale ); + + static idVec3 DetermineOppositePointOnBounds( const idVec3& start, const idVec3& dir, const idBounds& bounds ); + + template< class Type > + static void Swap( Type& Val1, Type& Val2 ); + + template< class listType > static ID_INLINE void RemoveContents( idList& list, bool clear ) { + for( int index = list.Num() - 1; index >= 0; --index ) { + SAFE_REMOVE( list[index] ); + } + if( clear ) { + list.Clear(); + } else { + memset( list.Ptr(), 0, list.MemoryUsed() ); + } + } +}; + +/* +================ +hhUtils::SwapXZ + +HUMANHEAD: aob +================ +*/ +ID_INLINE idMat3 hhUtils::SwapXZ( const idMat3& axis ) { + return idMat3( -axis[2], axis[1], axis[0] ); +} + + +/* +=============== +hhMath::Swap +=============== +*/ +template< class Type > +ID_INLINE void hhUtils::Swap( Type& Val1, Type& Val2 ) { + Type Temp; + + Temp = Val1; + Val1 = Val2; + Val2 = Temp; +} + +template< class Type > +class hhCycleList { + public: + hhCycleList(); + virtual ~hhCycleList(); + + void Clear(); + void Append( Type& obj ); + void AddUnique( Type& obj ); + const Type& Next(); + const Type& Previous(); + const Type& Random(); + const Type& Get() const; + int Num() const; + //rww - needed for networking sync + int GetCurrentIndex(void) { return currentIndex; } + void SetCurrentIndex(int newIndex) { currentIndex = newIndex; } + + const Type & operator[]( int index ) const; + Type & operator[]( int index ); + protected: + int currentIndex; + idList list; +}; + +/* +=============== +hhCycleList::hhCycleList +=============== +*/ +template< class Type > +ID_INLINE hhCycleList::hhCycleList() { + Clear(); +} + +/* +=============== +hhCycleList::~hhCycleList +=============== +*/ +template< class Type > +ID_INLINE hhCycleList::~hhCycleList() { + Clear(); +} + +/* +=============== +hhCycleList::Clear +=============== +*/ +template< class Type > +ID_INLINE void hhCycleList::Clear() { + list.Clear(); + currentIndex = 0; +} + +/* +=============== +hhCycleList::Append +=============== +*/ +template< class Type > +ID_INLINE void hhCycleList::Append( Type& obj ) { + list.Append( obj ); +} + +/* +=============== +hhCycleList::AddUnique +=============== +*/ +template< class Type > +ID_INLINE void hhCycleList::AddUnique( Type& obj ) { + list.AddUnique( obj ); +} + +/* +=============== +hhCycleList::Next +=============== +*/ +template< class Type > +ID_INLINE const Type& hhCycleList::Next() { + assert( list.Num() ); + + currentIndex = (currentIndex + 1) % list.Num(); + return Get(); +} + +/* +=============== +hhCycleList::Previous +=============== +*/ +template< class Type > +ID_INLINE const Type& hhCycleList::Previous() { + --currentIndex; + if( currentIndex < 0 ) { + currentIndex = (list.Num() - 1); + } + return Get(); +} + +/* +=============== +hhCycleList::Random +=============== +*/ +template< class Type > +ID_INLINE const Type& hhCycleList::Random() { + currentIndex = gameLocal.random.RandomInt( list.Num() ); + return Get(); +} + +/* +=============== +hhCycleList::Get +=============== +*/ +template< class Type > +ID_INLINE const Type& hhCycleList::Get() const { + return list[currentIndex]; +} + +/* +=============== +hhCycleList::Num +=============== +*/ +template< class Type > +ID_INLINE int hhCycleList::Num() const { + return list.Num(); +} + +/* +=============== +hhCycleList::operator[] +=============== +*/ +template< class Type > +ID_INLINE const Type & hhCycleList::operator[]( int index ) const { + return list[index]; +} + +/* +=============== +hhCycleList::operator[] +=============== +*/ +template< class Type > +ID_INLINE Type & hhCycleList::operator[]( int index ) { + return list[index]; +} + +#endif /* __GAME_UTILS_H__ */ diff --git a/src/Prey/game_vehicle.cpp b/src/Prey/game_vehicle.cpp new file mode 100644 index 0000000..cfd827e --- /dev/null +++ b/src/Prey/game_vehicle.cpp @@ -0,0 +1,1922 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +//========================================================================== +// +// hhVehicleThruster +// +//========================================================================== +CLASS_DECLARATION( idEntity, hhVehicleThruster ) + EVENT( EV_Broadcast_AssignFx, hhVehicleThruster::Event_AssignFxSmoke ) +END_CLASS + +hhVehicleThruster::hhVehicleThruster() { + fxSmoke = NULL; +} + +void hhVehicleThruster::Spawn() { + owner = NULL; + GetPhysics()->SetContents(0); + soundDistance = spawnArgs.GetFloat("soundDistance"); + bSomeThrusterActive = false; + bSoundMaster = spawnArgs.GetBool("soundmaster"); + localVelocity = localOffset = localDirection = vec3_origin; + fl.networkSync = true; +} + +void hhVehicleThruster::Save(idSaveGame *savefile) const { + owner.Save(savefile); + fxSmoke.Save(savefile); + savefile->WriteVec3(localOffset); + savefile->WriteVec3(localDirection); + savefile->WriteFloat(soundDistance); + savefile->WriteBool(bSomeThrusterActive); + savefile->WriteBool(bSoundMaster); + savefile->WriteVec3(localVelocity); +} + +void hhVehicleThruster::Restore( idRestoreGame *savefile ) { + owner.Restore(savefile); + fxSmoke.Restore(savefile); + savefile->ReadVec3(localOffset); + savefile->ReadVec3(localDirection); + savefile->ReadFloat(soundDistance); + savefile->ReadBool(bSomeThrusterActive); + savefile->ReadBool(bSoundMaster); + savefile->ReadVec3(localVelocity); +} + +void hhVehicleThruster::WriteToSnapshot( idBitMsgDelta &msg ) const { + WriteBindToSnapshot(msg); + GetPhysics()->WriteToSnapshot(msg); + + msg.WriteBits(owner.GetSpawnId(), 32); + msg.WriteBits(fxSmoke.GetSpawnId(), 32); + + msg.WriteFloat(localOffset.x); + msg.WriteFloat(localOffset.y); + msg.WriteFloat(localOffset.z); + + msg.WriteFloat(localDirection.x); + msg.WriteFloat(localDirection.y); + msg.WriteFloat(localDirection.z); + + msg.WriteFloat(soundDistance); + + msg.WriteBits(bSomeThrusterActive, 1); + msg.WriteBits(bSoundMaster, 1); + + msg.WriteFloat(localVelocity.x); + msg.WriteFloat(localVelocity.y); + msg.WriteFloat(localVelocity.z); + + msg.WriteBits(IsHidden(), 1); +} + +void hhVehicleThruster::ReadFromSnapshot( const idBitMsgDelta &msg ) { + ReadBindFromSnapshot(msg); + GetPhysics()->ReadFromSnapshot(msg); + + owner.SetSpawnId(msg.ReadBits(32)); + fxSmoke.SetSpawnId(msg.ReadBits(32)); + + localOffset.x = msg.ReadFloat(); + localOffset.y = msg.ReadFloat(); + localOffset.z = msg.ReadFloat(); + + localDirection.x = msg.ReadFloat(); + localDirection.y = msg.ReadFloat(); + localDirection.z = msg.ReadFloat(); + + soundDistance = msg.ReadFloat(); + + bSomeThrusterActive = !!msg.ReadBits(1); + bSoundMaster = !!msg.ReadBits(1); + + localVelocity.x = msg.ReadFloat(); + localVelocity.y = msg.ReadFloat(); + localVelocity.z = msg.ReadFloat(); + + bool hidden = !!msg.ReadBits(1); + if (hidden != IsHidden()) { + if (hidden) { + Hide(); + } else { + Show(); + } + } + + if (fxSmoke.IsValid() && fxSmoke.GetEntity() && fxSmoke->IsType(idEntityFx::Type)) { + if (fxSmoke->IsHidden() != IsHidden()) { + fxSmoke->BecomeActive( TH_THINK ); + if (IsHidden()) { + fxSmoke->Nozzle(false); + fxSmoke->fl.hidden = true; + } else { + fxSmoke->Nozzle(true); + fxSmoke->fl.hidden = false; + } + } + } +} + +void hhVehicleThruster::ClientPredictionThink( void ) { + Think(); +} + +void hhVehicleThruster::SetSmoker(bool bSmoker, idVec3 &offset, idVec3 &dir) { + localOffset = offset; + localDirection = dir; + if ( bSmoker && (!fxSmoke.IsValid() || !fxSmoke.GetEntity()) ) { + hhFxInfo fxInfo; + const char *smokeName; + smokeName = spawnArgs.GetString( "fx_smoke" ); + if (smokeName && *smokeName) { + fxInfo.SetStart(false); + fxInfo.RemoveWhenDone(false); + fxInfo.SetEntity( this ); + idVec3 loc = owner->GetOrigin() + localOffset * owner->GetAxis(); + idMat3 axis = (localDirection * owner->GetAxis()).ToMat3(); + BroadcastFxInfo( smokeName, loc, axis, &fxInfo, &EV_Broadcast_AssignFx, false ); + } + } + else if (!bSmoker && fxSmoke.IsValid() && fxSmoke.GetEntity()) { + fxSmoke->PostEventMS(&EV_Remove, 0); + fxSmoke = NULL; + } +} + +void hhVehicleThruster::SetThruster(bool on) { + + if (fxSmoke.IsValid() && fxSmoke.GetEntity()) { + fxSmoke->Nozzle( on ); + } + + on ? Show() : Hide(); +} + +void hhVehicleThruster::SetDying(bool bDying) { + if (bSoundMaster) { + SetSmoker(true, localOffset, localDirection); + } + else { + SetSmoker(bDying, localOffset, localDirection); + } +} + +void hhVehicleThruster::Update( const idVec3 &vel ) { + if (bSoundMaster) { + localVelocity = vel; + bool on = localVelocity != vec3_origin; + if (on && !bSomeThrusterActive) { + // Just became active + StartSound("snd_thrust", SND_CHANNEL_THRUSTERS); + bSomeThrusterActive = true; + } + else if (!on) { + // Just became inactive + StopSound(SND_CHANNEL_THRUSTERS); + bSomeThrusterActive = false; + } + } + +} + +bool hhVehicleThruster::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + idVec3 toSound; + idVec3 toPilot; + if (owner.IsValid()) { + axis.Identity(); + toSound = -localVelocity; // Position thrust sounds in the opposite direction as thrust velocity + toSound.Normalize(); + toPilot = (owner->GetOrigin() - GetOrigin()) + (owner->spawnArgs.GetVector("offset_pilot") + idVec3(0,0,1)*owner->spawnArgs.GetFloat("pilot_eyeHeight"))*owner->GetAxis(); + origin = toPilot + (toSound * owner->GetAxis() * soundDistance); + return true; + } + return false; + +} + +void hhVehicleThruster::Event_AssignFxSmoke( hhEntityFx* fx ) { + fxSmoke = fx; +} + +//========================================================================== +// +// hhPilotVehicleInterface +// +//========================================================================== +CLASS_DECLARATION( idClass, hhPilotVehicleInterface ) +END_CLASS + +hhPilotVehicleInterface::hhPilotVehicleInterface() { + UnderScriptControl( false ); +} + +//rww - added to handle when a player is destroyed before a vehicle (only in mp i guess) +hhPilotVehicleInterface::~hhPilotVehicleInterface() { + if (pilot.IsValid() && pilot.GetEntity()) { + pilot->SetVehicleInterface(NULL); + } + if (vehicle.IsValid() && vehicle.GetEntity()) { + vehicle->SetPilotInterface(NULL); + } +} + +void hhPilotVehicleInterface::Save(idSaveGame *savefile) const { + pilot.Save(savefile); + vehicle.Save(savefile); + savefile->WriteBool(underScriptControl); +} + +void hhPilotVehicleInterface::Restore( idRestoreGame *savefile ) { + pilot.Restore(savefile); + vehicle.Restore(savefile); + savefile->ReadBool(underScriptControl); +} + +void hhPilotVehicleInterface::RetrievePilotInput( usercmd_t& cmds, idAngles& viewAngles ) { + if( pilot.IsValid() ) { + pilot->GetPilotInput( cmds, viewAngles ); + } +} + +void hhPilotVehicleInterface::TakeControl( hhVehicle* theVehicle, idActor* thePilot ) { + vehicle = theVehicle; + pilot = thePilot; + + if( theVehicle ) { + theVehicle->AcceptPilot( this ); + } +} + +void hhPilotVehicleInterface::ReleaseControl() { + if (vehicle.IsValid()) { + vehicle->ReleaseControl(); + } + vehicle = NULL; + pilot = NULL; +} + +idVec3 hhPilotVehicleInterface::DeterminePilotOrigin() const { + assert( vehicle.IsValid() ); + + return vehicle->DeterminePilotOrigin(); +} + +idMat3 hhPilotVehicleInterface::DeterminePilotAxis() const { + assert( vehicle.IsValid() ); + + return vehicle->DeterminePilotAxis(); +} + +bool hhPilotVehicleInterface::ControllingVehicle() const { + return vehicle.IsValid() && vehicle->IsVehicle(); +} + +hhVehicle* hhPilotVehicleInterface::GetVehicle() const { + return vehicle.GetEntity(); +} + +idActor* hhPilotVehicleInterface::GetPilot() const { + return pilot.GetEntity(); +} + +bool hhPilotVehicleInterface::InvalidVehicleImpulse( int impulse ) { + switch( impulse ) { + case IMPULSE_0: + case IMPULSE_1: + case IMPULSE_2: + case IMPULSE_3: + case IMPULSE_4: + case IMPULSE_5: + case IMPULSE_6: + case IMPULSE_7: + case IMPULSE_8: + case IMPULSE_9: + case IMPULSE_10: + case IMPULSE_11: + case IMPULSE_12: + case IMPULSE_13: + case IMPULSE_14: + case IMPULSE_15: + case IMPULSE_16: + case IMPULSE_19: + case IMPULSE_25: + case IMPULSE_40: + case IMPULSE_54: + return true; + } + + return false; +} + +//========================================================================== +// +// hhAIVehicleInterface +// +//========================================================================== +CLASS_DECLARATION( hhPilotVehicleInterface, hhAIVehicleInterface ) +END_CLASS + +hhAIVehicleInterface::hhAIVehicleInterface(void) { + stateFiring = false; + stateAltFiring = false; +} + +void hhAIVehicleInterface::Save(idSaveGame *savefile) const { + savefile->WriteUsercmd(bufferedCmds); + savefile->WriteAngles(bufferedViewAngles); + savefile->WriteBool(stateFiring); + savefile->WriteBool(stateAltFiring); + savefile->WriteFloat(stateOrientSpeed); + savefile->WriteFloat(stateThrustSpeed); + savefile->WriteVec3(stateOrientDestination); + savefile->WriteVec3(stateThrustDestination); + +} + +void hhAIVehicleInterface::Restore( idRestoreGame *savefile ) { + savefile->ReadUsercmd(bufferedCmds); + savefile->ReadAngles(bufferedViewAngles); + savefile->ReadBool(stateFiring); + savefile->ReadBool(stateAltFiring); + savefile->ReadFloat(stateOrientSpeed); + savefile->ReadFloat(stateThrustSpeed); + savefile->ReadVec3(stateOrientDestination); + savefile->ReadVec3(stateThrustDestination); +} + +void hhAIVehicleInterface::TakeControl( hhVehicle* vehicle, idActor* pilot ) { + hhPilotVehicleInterface::TakeControl( vehicle, pilot ); + + ClearBufferedCmds(); + bufferedViewAngles = (pilot) ? pilot->GetAxis().ToAngles() : ang_zero; +} + +void hhAIVehicleInterface::Fire(bool on) { + stateFiring = on; +} + +void hhAIVehicleInterface::AltFire(bool on) { + stateAltFiring = on; +} + +void hhAIVehicleInterface::OrientTowards( const idVec3 &loc, float speed ) { + stateOrientDestination = loc; + stateOrientSpeed = idMath::ClampFloat( 0.0f, 1.0f, speed ); +} + +void hhAIVehicleInterface::ThrustTowards( const idVec3 &loc, float speed ) { + stateThrustDestination = loc; + stateThrustSpeed = idMath::ClampFloat( 0.0f, 1.0f, speed ); +} + +void hhAIVehicleInterface::RetrievePilotInput( usercmd_t& cmds, idAngles& viewAngles ) { + + if( !ControllingVehicle() ) { + return; + } + + // First grab any buffered commands + cmds = bufferedCmds; + viewAngles = bufferedViewAngles; + + idVec3 eyePos = GetPilot()->GetEyePosition(); + + // Now, Override the buffered input with any script/ai requests + // Based on current script requests, construct a command packet + cmds.buttons |= (stateFiring ? BUTTON_ATTACK : 0); + cmds.buttons |= (stateAltFiring ? BUTTON_ATTACK_ALT : 0); + + // Handle thrusting + if ( stateThrustSpeed > 0.0f ) { + idVec3 targetDir = stateThrustDestination - eyePos; + + // Use proportional control system to ease into destination + const float proportionalGain = 0.7f; + float error = idMath::ClampFloat(0.0f, 1.0f, targetDir.Normalize() / 512.0f); // error only taken into consideration when within 512 units of destination + if (error < 1.0f) { + error *= proportionalGain; + } + + targetDir *= vehicle->GetAxis().Inverse(); + + cmds.forwardmove = targetDir.x * 127.0f * error * stateThrustSpeed; + cmds.rightmove = -targetDir.y * 127.0f * error * stateThrustSpeed; + cmds.upmove = targetDir.z * 127.0f * error * stateThrustSpeed; + } + + // Handle orienting: Using direction vectors to interpolate our orientation to our moving target orientation + viewAngles.pitch = -idMath::AngleNormalize180( vehicle->GetAxis()[0].ToPitch() ); + viewAngles.yaw = vehicle->GetAxis()[0].ToYaw(); + viewAngles.roll = 0.0f; + + if ( stateOrientSpeed > 0.0f ) { + idVec3 localDir; + idAngles idealViewAngles( ang_zero ); + idVec3 targetDir = stateOrientDestination - eyePos; + targetDir.Normalize(); + float degrees = stateOrientSpeed * 360.0f * MS2SEC(gameLocal.msec); + + vehicle->GetPhysics()->GetAxis().ProjectVector( targetDir, localDir ); + idealViewAngles.yaw = localDir.ToYaw(); + idealViewAngles.pitch = -idMath::AngleNormalize180( localDir.ToPitch() ); + + idAngles deltaViewAngles = (idealViewAngles - viewAngles).Normalize180(); + deltaViewAngles.Clamp( idAngles(-degrees, -degrees, 0.0f), idAngles(degrees, degrees, 0.0f) ); + if( !deltaViewAngles.Compare(ang_zero, VECTOR_EPSILON) ) { + viewAngles += deltaViewAngles; + bufferedViewAngles = viewAngles; // Save for next time + } + } +} + +void hhAIVehicleInterface::BufferPilotCmds( const usercmd_t* cmds, const idAngles* viewAngles ) { + if( cmds ) { + bufferedCmds = *cmds; + } + + if( viewAngles ) { + bufferedViewAngles = *viewAngles; + } +} + +void hhAIVehicleInterface::ClearBufferedCmds() { + memset( &bufferedCmds, 0, sizeof(usercmd_t) ); + stateFiring = false; + stateAltFiring = false; + stateOrientSpeed = 0.0f; + stateThrustSpeed = 0.0f; +} + +bool hhAIVehicleInterface::IsVehicleDocked() const { + return vehicle.IsValid() && vehicle->IsDocked(); +} + +//========================================================================== +// +// hhPlayerVehicleInterface +// +//========================================================================== +CLASS_DECLARATION( hhPilotVehicleInterface, hhPlayerVehicleInterface ) +END_CLASS + +hhPlayerVehicleInterface::hhPlayerVehicleInterface() { + hud = NULL; + uniqueHud = true; + translationAlpha.Init(gameLocal.time, 0, 0.0f, 0.0f); +} + +hhPlayerVehicleInterface::~hhPlayerVehicleInterface() { +} + +void hhPlayerVehicleInterface::Save(idSaveGame *savefile) const { + savefile->WriteStaticObject(weaponHandState); + controlHand.Save(savefile); + savefile->WriteBool(uniqueHud); + savefile->WriteUserInterface(hud, uniqueHud); + + savefile->WriteFloat( translationAlpha.GetStartTime() ); // idInterpolate + savefile->WriteFloat( translationAlpha.GetDuration() ); + savefile->WriteFloat( translationAlpha.GetStartValue() ); + savefile->WriteFloat( translationAlpha.GetEndValue() ); +} + +void hhPlayerVehicleInterface::Restore( idRestoreGame *savefile ) { + float set; + + savefile->ReadStaticObject(weaponHandState); + controlHand.Restore(savefile); + savefile->ReadBool(uniqueHud); + savefile->ReadUserInterface(hud); + + savefile->ReadFloat( set ); // idInterpolate + translationAlpha.SetStartTime( set ); + savefile->ReadFloat( set ); + translationAlpha.SetDuration( set ); + savefile->ReadFloat( set ); + translationAlpha.SetStartValue(set); + savefile->ReadFloat( set ); + translationAlpha.SetEndValue( set ); +} + +void hhPlayerVehicleInterface::UpdateControlHand( const usercmd_t& cmds ) { + if( controlHand.IsValid() ) { + controlHand->UpdateControlDirection( idVec3(Sign(cmds.forwardmove), Sign(cmds.rightmove), Sign(cmds.upmove)) ); + } +} + +void hhPlayerVehicleInterface::CreateControlHand( hhPlayer* pilot, const char* handName ) { + //RemoveHand(); + + if( !handName || !handName[0] ) { + return; + } + + weaponHandState.SetPlayer( pilot ); + weaponHandState.SetWeaponTransition( 1 ); + weaponHandState.Archive( NULL, 0, handName, 1 ); + + if ( pilot->hand.IsValid() && pilot->hand->IsType( hhControlHand::Type ) ) { + controlHand = static_cast( pilot->hand.GetEntity() ); + } + else { + controlHand = NULL; + } + + //controlHand = static_cast( hhControlHand::AddHand(pilot, handName) ); +} + +void hhPlayerVehicleInterface::RemoveHand() { + if( controlHand.IsValid() ) { + //controlHand->RemoveHand(); + controlHand = NULL; + } + weaponHandState.RestoreFromArchive(); +} + +void hhPlayerVehicleInterface::RetrievePilotInput( usercmd_t& cmds, idAngles& viewAngles ) { + if( pilot.IsValid() ) { + hhPilotVehicleInterface::RetrievePilotInput( cmds, viewAngles ); + UpdateControlHand( cmds ); + } +} + +void hhPlayerVehicleInterface::TakeControl( hhVehicle* vehicle, idActor* pilot ) { + hhPilotVehicleInterface::TakeControl( vehicle, pilot ); + + if( vehicle ) { + idStr hudName = vehicle->spawnArgs.GetString( "gui_hud" ); + if( hudName.Length() ) { + uniqueHud = vehicle->spawnArgs.GetBool("uniqueguihud", "1"); + hud = uiManager->FindGui( hudName.c_str(), true, uniqueHud, !uniqueHud ); + + bool isPilotLocal = false; + idPlayer *localPl = gameLocal.GetLocalPlayer(); + if (localPl) { + if (localPl->entityNumber == pilot->entityNumber || (localPl->spectating && localPl->spectator == pilot->entityNumber)) { + isPilotLocal = true; + } + } + if (isPilotLocal) { + translationAlpha.Init( gameLocal.time, 0, 0.0f, 0.0f ); + hud->Activate( true, gameLocal.GetTime() ); + pilot->fl.clientEvents = true; //rww - hack + pilot->PostEventMS( &EV_StartHudTranslation, 1000 ); + pilot->fl.clientEvents = false; //rww - hack + } + } + + if (!gameLocal.isClient) { + CreateControlHand( static_cast(pilot), vehicle->spawnArgs.GetString("def_hand") ); + } + } +} + +void hhPlayerVehicleInterface::ReleaseControl() { + if( vehicle.IsValid() ) { + hud = NULL; + uniqueHud = true; + if (!gameLocal.isClient) { + RemoveHand(); + } else { + if (controlHand.IsValid() && controlHand.GetEntity()) { + controlHand->Hide(); + } + } + } + + hhPilotVehicleInterface::ReleaseControl(); +} + +void hhPlayerVehicleInterface::DrawHUD( idUserInterface* _hud ) { + if( vehicle.IsValid() && vehicle->IsVehicle() && _hud ) { + float alpha = translationAlpha.GetCurrentValue(gameLocal.GetTime()); + _hud->SetStateFloat( "translationAlpha", alpha ); + _hud->SetStateBool( "translationProject", false ); + + vehicle->DrawHUD( _hud ); + } +} + +void hhPlayerVehicleInterface::StartHUDTranslation() { + translationAlpha.Init( gameLocal.time, 1000, 0.0f, 1.0f ); +} + + +//========================================================================== +// +// hhVehicle +// +//========================================================================== + +const idEventDef EV_VehicleExplode("", "efff"); +const idEventDef EV_Vehicle_FireCannon( "fireCannon" ); + +const idEventDef EV_VehicleGetIn("getIn", "e"); // Script Commands +const idEventDef EV_VehicleGetOut("getOut"); +const idEventDef EV_VehicleFire("fire", "d"); +const idEventDef EV_VehicleAltFire("altFire", "d"); +const idEventDef EV_VehicleOrientTowards("orientTowards", "vf"); +const idEventDef EV_VehicleStopOrientingTOwards("stopOrientingTowards"); +const idEventDef EV_VehicleThrustTowards("thrustTowards", "vf"); +const idEventDef EV_VehicleStopThrustingTOwards("stopThrustingTowards"); +const idEventDef EV_VehicleReleaseControl("releaseControl"); +const idEventDef EV_Vehicle_EjectPilot("ejectPilot"); + +ABSTRACT_DECLARATION( hhRenderEntity, hhVehicle ) + EVENT( EV_Vehicle_FireCannon, hhVehicle::Event_FireCannon ) + EVENT( EV_VehicleExplode, hhVehicle::Event_Explode ) + EVENT( EV_VehicleGetIn, hhVehicle::Event_GetIn ) + EVENT( EV_VehicleGetOut, hhVehicle::Event_GetOut ) + EVENT( EV_VehicleFire, hhVehicle::Event_Fire ) + EVENT( EV_VehicleAltFire, hhVehicle::Event_AltFire ) + EVENT( EV_VehicleOrientTowards, hhVehicle::Event_OrientTowards ) + EVENT( EV_VehicleThrustTowards, hhVehicle::Event_ThrustTowards ) + EVENT( EV_VehicleReleaseControl, hhVehicle::Event_ReleaseScriptControl ) + EVENT( EV_Vehicle_EjectPilot, hhVehicle::Event_EjectPilot ) + EVENT( EV_ResetGravity, hhVehicle::Event_ResetGravity ) +END_CLASS + +hhVehicle::~hhVehicle() { + if (GetPilotInterface()) { + idActor *actor = GetPilotInterface()->GetPilot(); + if (actor && actor->IsType(idActor::Type)) { //if it still has a valid pilot, eject them. + EjectPilot(); + } + } +} + +void hhVehicle::Spawn() { + memset( &oldCmds, 0, sizeof(usercmd_t) ); + thrusterCost = spawnArgs.GetInt( "thrusterCost" ); + currentPower = spawnArgs.GetInt( "maxPower" ); + thrustFactor = spawnArgs.GetFloat( "thrustFactor" ); + dockBoostFactor = spawnArgs.GetFloat( "dockBoostFactor" ); + if ( gameLocal.isMultiplayer ) { + noDamage = false; + } else { + noDamage = spawnArgs.GetBool( "noDamage", "0" ); + } + + bDamageSelfOnCollision = spawnArgs.GetBool( "damageSelfOnCollision" ); + bDamageOtherOnCollision = spawnArgs.GetBool( "damageOtherOnCollision" ); + + lastAttackTime = 0; + fl.neverDormant = false; + fl.refreshReactions = true; + fireController = NULL; + pilotInterface = NULL; + bHeadlightOn = false; + validThrustTime = gameLocal.time; + + bDisallowAttackUntilRelease = false; + bDisallowAltAttackUntilRelease = false; + + InitializeAttackFuncs(); + + //rww - i see no reason why the dock should be set upon spawning the vehicle. + //also would cause issues in mp where only one shuttle should be docked at a time. + //startDock is set by the dock who created us + /* + hhDock *dock = (hhDock *)gameLocal.FindEntity(spawnArgs.GetString("startDock")); + SetDock( dock ); + if (dock && dock->IsType(hhShuttleDock::Type)) { + hhShuttleDock *shDock = static_cast(dock); + if (shDock->GetDockedShuttle()) { //if the dock already has a shuttle, do not dock this ship there. + SetDock(NULL); + } + } + */ + + InitPhysics(); + + BecomeConsole(); + + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + + SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + SetPhysics( &physicsObj ); + + if ( spawnArgs.GetInt( "nodrop" ) ) { + physicsObj.PutToRest(); + } + else { + physicsObj.DropToFloor(); + } +} + +void hhVehicle::Save(idSaveGame *savefile) const { + savefile->WriteStaticObject(physicsObj); + savefile->WriteMat3(modelAxis); + + if( fireController ) { + savefile->WriteBool(true); + savefile->WriteStaticObject(*fireController); + } else { + savefile->WriteBool(false); + } + + savefile->WriteUsercmd(oldCmds); + + headlight.Save(savefile); + domelight.Save(savefile); + + savefile->WriteBool(bHeadlightOn); + savefile->WriteInt(currentPower); + savefile->WriteFloat(thrustFactor); + savefile->WriteFloat(thrustMin); + savefile->WriteFloat(thrustMax); + savefile->WriteFloat(thrustAccel); + savefile->WriteFloat(thrustScale); + savefile->WriteFloat(dockBoostFactor); + + dock.Save(savefile); + lastAttacker.Save(savefile); + + savefile->WriteInt(lastAttackTime); + savefile->WriteInt(vehicleClipMask); + savefile->WriteInt(vehicleContents); + savefile->WriteBool(bDamageSelfOnCollision); + savefile->WriteBool(bDamageOtherOnCollision); + savefile->WriteBool(bDisallowAttackUntilRelease); + savefile->WriteBool(bDisallowAltAttackUntilRelease); + savefile->WriteInt(thrusterCost); + savefile->WriteInt(validThrustTime); + + savefile->WriteEventDef(attackFunc); + savefile->WriteEventDef(finishedAttackingFunc); + savefile->WriteEventDef(altAttackFunc); + savefile->WriteEventDef(finishedAltAttackingFunc); + + savefile->WriteBool(noDamage); +} + +void hhVehicle::Restore( idRestoreGame *savefile ) { + savefile->ReadStaticObject(physicsObj); + RestorePhysics(&physicsObj); + + savefile->ReadMat3(modelAxis); + + bool test; + savefile->ReadBool( test ); + if (test) { + fireController = CreateFireController(); + savefile->ReadStaticObject(*fireController); + } else { + SAFE_DELETE_PTR(fireController); + } + + savefile->ReadUsercmd(oldCmds); + + headlight.Restore(savefile); + domelight.Restore(savefile); + + savefile->ReadBool(bHeadlightOn); + savefile->ReadInt(currentPower); + savefile->ReadFloat(thrustFactor); + savefile->ReadFloat(thrustMin); + savefile->ReadFloat(thrustMax); + savefile->ReadFloat(thrustAccel); + savefile->ReadFloat(thrustScale); + savefile->ReadFloat(dockBoostFactor); + + dock.Restore(savefile); + lastAttacker.Restore(savefile); + + savefile->ReadInt(lastAttackTime); + savefile->ReadInt(vehicleClipMask); + savefile->ReadInt(vehicleContents); + savefile->ReadBool(bDamageSelfOnCollision); + savefile->ReadBool(bDamageOtherOnCollision); + savefile->ReadBool(bDisallowAttackUntilRelease); + savefile->ReadBool(bDisallowAltAttackUntilRelease); + savefile->ReadInt(thrusterCost); + savefile->ReadInt(validThrustTime); + + savefile->ReadEventDef(attackFunc); + savefile->ReadEventDef(finishedAttackingFunc); + savefile->ReadEventDef(altAttackFunc); + savefile->ReadEventDef(finishedAltAttackingFunc); + + savefile->ReadBool(noDamage); +} + +const idEventDef* hhVehicle::GetAttackFunc( const char* funcName ) { + function_t* function = NULL; + + if( !funcName || !funcName[0] ) { + return NULL; + } + + idTypeDef *funcType = gameLocal.program.FindType(funcName); + if (!funcType) { + return NULL; + } + + function = gameLocal.program.FindFunction( funcName, funcType ); + if( !function || !function->eventdef ) { + return NULL; + } + + HH_ASSERT( RespondsTo(*function->eventdef) ); + + return function->eventdef; +} + +void hhVehicle::InitializeAttackFuncs() { + idStr funcName; + attackFunc = finishedAttackingFunc = altAttackFunc = finishedAltAttackingFunc = NULL; + + funcName = spawnArgs.GetString("attackFunc"); + if (funcName.Length()) { + attackFunc = GetAttackFunc( funcName.c_str() ); + finishedAttackingFunc = GetAttackFunc( (funcName + "Done").c_str() ); + } + funcName = spawnArgs.GetString("altAttackFunc"); + if (funcName.Length()) { + altAttackFunc = GetAttackFunc( funcName.c_str() ); + finishedAltAttackingFunc = GetAttackFunc( (funcName + "Done").c_str() ); + } +} + +void hhVehicle::Think() { + usercmd_t pilotCmds; + idAngles pilotViewAngles; + + if( IsVehicle() && GetPilotInterface() ) { + GetPilotInterface()->RetrievePilotInput( pilotCmds, pilotViewAngles ); + ProcessPilotInput( &pilotCmds, &pilotViewAngles ); + GetPhysics()->SetAxis( mat3_identity ); + } + + hhRenderEntity::Think(); +} + +void hhVehicle::Present() { + if (fireController) { + fireController->UpdateMuzzleFlash(); + } + + hhRenderEntity::Present(); +} + +void hhVehicle::AcceptPilot( hhPilotVehicleInterface* pilotInterface ) { + //assure that the vehicle is showing upon accepting a new pilot + Show(); + + this->pilotInterface = pilotInterface; + + fl.neverDormant = true; + fl.takedamage = true; + StartSound( "snd_activation", SND_CHANNEL_ANY ); + StartSound( "snd_inuse", SND_CHANNEL_MISC1 ); + + CreateDomeLight(); + CreateHeadLight(); + BecomeActive( TH_TICKER ); + + const idDict* infoDict = NULL; + if ( GetPilot() && GetPilot()->IsType(idAI::Type) ) { + infoDict = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_fireInfoAI"), false ); + } else { + infoDict = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_fireInfo"), false ); + } + fireController = CreateFireController(); + HH_ASSERT( fireController ); + fireController->Init( infoDict, this, GetPilot() ); + + // supress model in player views, but allow it in mirrors and remote views + if (GetPilot()->IsType(hhPlayer::Type)) { + GetRenderEntity()->suppressSurfaceInViewID = GetPilot()->entityNumber + 1; + } + + BecomeVehicle(); +} + +void hhVehicle::RestorePilot( hhPilotVehicleInterface* pilotInterface) { + const idDict *infoDict = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_fireInfo"), false ); + assert( infoDict ); + fireController->SetWeaponDict( infoDict ); + this->pilotInterface = pilotInterface; +} + + +void hhVehicle::EjectPilot() { + assert( IsVehicle() ); + + SAFE_DELETE_PTR( fireController ); + + fl.takedamage = false; + fl.neverDormant = false; + StopSound( SND_CHANNEL_MISC1 ); + StartSound( "snd_deactivation", SND_CHANNEL_ANY ); + + FreeDomeLight(); + FreeHeadLight(); + BecomeInactive( TH_TICKER ); + + + if (GetPilotInterface()->GetPilot()) { //rww - make sure pilot is valid + GetPilotInterface()->GetPilot()->ExitVehicle( this ); + pilotInterface = NULL; + } + + RemoveVehicle(); +} + +idVec3 hhVehicle::DeterminePilotOrigin() const { + return GetOrigin() + spawnArgs.GetVector("offset_pilot") * GetAxis(); +} + +idMat3 hhVehicle::DeterminePilotAxis() const { + return GetAxis(); +} + +void hhVehicle::BecomeVehicle() { + SetVehiclePhysics(); + + SetVehicleModel(); +} + +bool hhVehicle::CanBecomeVehicle(idActor *pilot) { + // Check to see if shuttle will fit + idEntity *touch[ MAX_GENTITIES ]; + idBounds bounds, localBounds; + + idVec3 location = GetOrigin(); + localBounds[0] = spawnArgs.GetVector("mins"); + localBounds[1] = spawnArgs.GetVector("maxs"); + +// idBox box(localBounds, location, GetAxis()); +// gameRenderWorld->DebugBox(colorRed, box, 8000); + + idTraceModel trm; + trm.SetupBox( localBounds ); + idClipModel *clipModel = new idClipModel( trm ); + clipModel->Link(gameLocal.clip, this, 254, location, GetAxis()); + + int pilotClipMask = pilot ? pilot->GetPhysics()->GetClipMask() : 0; + pilotClipMask &= (~CONTENTS_HUNTERCLIP); // Vehicles don't collide with hunterclip so they don't get hung up on dock borders + int num = hhUtils::EntitiesTouchingClipmodel( clipModel, touch, MAX_GENTITIES, CLIPMASK_VEHICLE | pilotClipMask ); + bool blocked = false; + for (int i=0; iGetBindMaster() != pilot && (touch[i]->GetPhysics()->GetContents() & CLIPMASK_VEHICLE)) { + blocked = true; + //gameLocal.Printf("Blocked by %s\n", touch[i]->GetName()); + break; + } + } + + clipModel->Unlink(); + delete clipModel; + + return !blocked; +} + +bool hhVehicle::IsVehicle() const { + return fireController != NULL && GetPilotInterface() && GetPilotInterface()->GetPilot(); +} + +void hhVehicle::BecomeConsole() { + SetConsolePhysics(); + + if( IsDocked() ) { + //Feels like a hack. Shouldn't the dock do this or at least be in SetConsolePhysics + SetAxis( dock->GetAxis() ); + GetPhysics()->SetAxis( dock->GetAxis() ); + SetOrigin( dock->GetOrigin() + dock->spawnArgs.GetVector("offset_console") * dock->GetAxis() ); + + physicsObj.PutToRest(); + } + + SetConsoleModel(); +} + +bool hhVehicle::IsConsole() const { + return !IsVehicle(); +} + +idVec3 hhVehicle::GetPortalPoint() { + idVec3 offset = spawnArgs.GetVector("offset_pilot") + idVec3(0,0,1)*spawnArgs.GetFloat("pilot_eyeHeight"); + return GetOrigin() + offset * GetAxis(); +} + +void hhVehicle::Portalled(idEntity *portal) { + idActor *pilot = GetPilot(); + if (pilot) { + // Update the view angles of the player so next time we get input, it won't reset our angles + if (pilot->IsType(hhPlayer::Type)) { + hhPlayer* player = static_cast( pilot ); + idVec3 origin = DeterminePilotOrigin(); + idVec3 viewDir = GetAxis()[0]; + + // Don't know if all this is necessary, might be overkill, definitely need some of it though for 'shuttle through portal' + player->Unbind(); + player->SetOrientation( origin, mat3_identity, viewDir, viewDir.ToAngles() ); + player->SetUntransformedViewAxis( mat3_identity ); + player->Bind(this, true); + } + } +} + +void hhVehicle::ResetGravity() { + if( IsVehicle() ) { + physicsObj.SetGravity( spawnArgs.GetVector("activeGravity") ); + } + else { + physicsObj.SetGravity( spawnArgs.GetVector("inactiveGravity") ); + } +} + +void hhVehicle::SetAxis( const idMat3& axis ) { + modelAxis = axis; + + UpdateVisuals(); +} + +void hhVehicle::InitPhysics() { + physicsObj.SetSelf( this ); + + physicsObj.SetFriction( + spawnArgs.GetFloat("friction_linear"), + spawnArgs.GetFloat("friction_angular"), + spawnArgs.GetFloat("friction_contact") ); +} + +void hhVehicle::SetConsolePhysics() { + idTraceModel trm; + const char *clipModelName = spawnArgs.GetString( "clipmodel" ); + assert( clipModelName[0] ); + + if ( !collisionModelManager->TrmFromModel( clipModelName, trm ) ) { + gameLocal.Error( "hhVehicle '%s' at (%s): cannot load collision model %s\n", + name.c_str(), GetPhysics()->GetOrigin().ToString(0), clipModelName ); + return; + } + + physicsObj.SetClipModel( new idClipModel(trm), spawnArgs.GetFloat("density") ); + physicsObj.DisableImpact(); + physicsObj.SetBouncyness( 0.0f ); + physicsObj.SetContents( CONTENTS_VEHICLE ); + physicsObj.SetClipMask( CLIPMASK_VEHICLE | CONTENTS_PLAYERCLIP | CONTENTS_MONSTERCLIP ); + physicsObj.SetLinearVelocity( vec3_zero ); + physicsObj.SetAngularVelocity( vec3_zero ); + + //Used as a cache for when noclipping + vehicleClipMask = physicsObj.GetClipMask(); + vehicleContents = physicsObj.GetContents(); + + ResetGravity(); +} + +void hhVehicle::SetVehiclePhysics() { + idBounds bounds( spawnArgs.GetVector("mins"), spawnArgs.GetVector("maxs") ); + + physicsObj.SetClipModel( new idClipModel(idTraceModel(bounds)), spawnArgs.GetFloat("density") ); + physicsObj.SetAxis( mat3_identity ); + physicsObj.EnableImpact(); + physicsObj.SetBouncyness( spawnArgs.GetFloat("bouncyness") ); + physicsObj.SetContents( CONTENTS_VEHICLE ); + + SetThrustBooster( spawnArgs.GetFloat("thrustMin"), spawnArgs.GetFloat("thrustMax"), spawnArgs.GetFloat("thrustAccel") ); + + int pilotClipMask = (GetPilotInterface() && GetPilotInterface()->GetPilot()) ? GetPilotInterface()->GetPilot()->GetPhysics()->GetClipMask() : 0; + pilotClipMask &= (~CONTENTS_HUNTERCLIP); // Vehicles don't collide with hunterclip so they don't get hung up on dock borders + physicsObj.SetClipMask( CLIPMASK_VEHICLE | pilotClipMask ); + + //Used as a cache for when noclipping + vehicleClipMask = physicsObj.GetClipMask(); + vehicleContents = physicsObj.GetContents(); + + ResetGravity(); +} + +void hhVehicle::SetConsoleModel() { + SetModel( spawnArgs.GetString("model") ); +} + +void hhVehicle::SetVehicleModel() { + SetModel( spawnArgs.GetString("model_active") ); +} + +float hhVehicle::CmdScale( const usercmd_t* cmd ) const { + float scale = 0.0f; + + if( !cmd ) { + return scale; + } + + idVec3 abscmd(abs(cmd->forwardmove), abs(cmd->rightmove), abs(cmd->upmove)); + + // Bound the cmd vector to a sphere whose radius is equal to the longest cmd axis + int desiredLength = max(max(abscmd[0], abscmd[1]), abscmd[2]); + if ( desiredLength != 0.0f ) { + float currentLength = abscmd.Length(); + scale = desiredLength / currentLength; + } + + return scale; +} + +void hhVehicle::SetThrustBooster(float minBooster, float maxBooster, float accelBooster) { + thrustMin = minBooster; + thrustMax = maxBooster; + thrustAccel = accelBooster; + + thrustScale = 0.1f; +} + +void hhVehicle::ProcessPilotInput( const usercmd_t* cmds, const idAngles* viewAngles ) { + idVec3 impulse; + + if( viewAngles ) { + SetAxis( viewAngles->ToMat3() ); + } + + if( !cmds ) { + return; + } + + ProcessButtons( *cmds ); + + //Check vehicle here because we could have exited the vehicle in ProcessButtons + if( !IsVehicle() ) { + return; + } + + ProcessImpulses( *cmds ); + + if ( gameLocal.time >= validThrustTime ) { + impulse = GetAxis()[0] * cmds->forwardmove * thrustFactor; + impulse -= GetAxis()[1] * cmds->rightmove * thrustFactor; + impulse += GetAxis()[2] * cmds->upmove * thrustFactor; + } + else { + impulse = vec3_origin; + } + + // Apply booster to allow key taps to be very low thrust + thrustScale = idMath::ClampFloat( thrustMin, thrustMax, thrustScale * thrustAccel ); + if( impulse.LengthSqr() >= VECTOR_EPSILON ) { + FireThrusters( impulse * CmdScale(cmds) * thrustScale * (60.0f * USERCMD_ONE_OVER_HZ) ); + } else { + thrustScale = thrustMin; + } + + memcpy( &oldCmds, cmds, sizeof(usercmd_t) ); +} + +void hhVehicle::ProcessButtons( const usercmd_t& cmds ) { + + //This is needed in case we enter a dock while firing or using the tractor beam + if( (cmds.buttons & BUTTON_ATTACK) && !(oldCmds.buttons & BUTTON_ATTACK) ) { + if (IsDocked() && dock->AllowsExit() && spawnArgs.GetBool("fireToExit")) { + if (!gameLocal.isClient) { + EjectPilot(); + } + return; + } + } else if( (cmds.buttons & BUTTON_ATTACK_ALT) && !(oldCmds.buttons & BUTTON_ATTACK_ALT) ) { + if (IsDocked() && dock->AllowsExit() && spawnArgs.GetBool("altFireToExit")) { + if (!gameLocal.isClient) { + EjectPilot(); + } + return; + } + } + + if (bDisallowAttackUntilRelease) { + if (!(cmds.buttons & BUTTON_ATTACK)) { + bDisallowAttackUntilRelease = false; + } + if( oldCmds.buttons & BUTTON_ATTACK ) { + oldCmds.buttons &= ~BUTTON_ATTACK; + if( finishedAttackingFunc ) { + ProcessEvent( finishedAttackingFunc ); + } + } + } + else { + if( (cmds.buttons & BUTTON_ATTACK) ) { + if( attackFunc ) { + ProcessEvent( attackFunc ); + } + } else if( oldCmds.buttons & BUTTON_ATTACK ) { + if( finishedAttackingFunc ) { + ProcessEvent( finishedAttackingFunc ); + } + } + } + + if (bDisallowAltAttackUntilRelease) { + if (!(cmds.buttons & BUTTON_ATTACK_ALT)) { + bDisallowAltAttackUntilRelease = false; + } + if ( oldCmds.buttons & BUTTON_ATTACK_ALT ) { + oldCmds.buttons &= ~BUTTON_ATTACK_ALT; + if( finishedAltAttackingFunc ) { + ProcessEvent( finishedAltAttackingFunc ); + } + } + } + else { + if( cmds.buttons & BUTTON_ATTACK_ALT ) { + if( altAttackFunc ) { + ProcessEvent( altAttackFunc ); + } + } else if( oldCmds.buttons & BUTTON_ATTACK_ALT ) { + if( finishedAltAttackingFunc ) { + ProcessEvent( finishedAltAttackingFunc ); + } + } + } +} + +void hhVehicle::DoPlayerImpulse(int impulse) { + if ( gameLocal.isClient && GetPilot() && GetPilot()->IsType(hhPlayer::Type) ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + hhPlayer *pl = static_cast(GetPilot()); + + assert( pl->entityNumber == gameLocal.localClientNum ); + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteBits( impulse, 6 ); + pl->ClientSendEvent( hhPlayer::EVENT_IMPULSE, &msg ); + } + + switch (impulse) { + case IMPULSE_16: + if (!gameLocal.isClient) { + Headlight( !bHeadlightOn ); + } + break; + } +} + +void hhVehicle::ProcessImpulses( const usercmd_t& cmds ) { + if( (cmds.flags & UCF_IMPULSE_SEQUENCE) == (oldCmds.flags & UCF_IMPULSE_SEQUENCE) ) { + return; + } + + DoPlayerImpulse(cmds.impulse); //rww - seperated into its own function because of networked impulse events +} + +void hhVehicle::ApplyImpulse( idEntity* ent, int id, const idVec3& point, const idVec3& impulse ) { + hhRenderEntity::ApplyImpulse( ent, id, point, impulse ); +} + +void hhVehicle::ApplyImpulse( const idVec3& impulse ) { + ApplyImpulse( gameLocal.world, 0, GetOrigin() + physicsObj.GetCenterOfMass(), impulse * physicsObj.GetMass() ); +} + +void hhVehicle::UpdateModel( void ) { + idVec3 origin; + idMat3 axis; + + if ( GetPhysicsToVisualTransform(origin, axis) ) { + //HUMANHEAD: aob + GetRenderEntity()->axis = axis * GetAxis(); + GetRenderEntity()->origin = GetOrigin() + origin * GetRenderEntity()->axis; + //HUMANHEAD END + } else { + //HUMANHEAD: aob + GetRenderEntity()->axis = GetAxis(); + GetRenderEntity()->origin = GetOrigin(); + //HUMANHEAD 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 ); +} + +void hhVehicle::CreateDomeLight() { + if (spawnArgs.GetBool("domelight")) { + // This method is more oriented for flexibility + idVec3 light_offset = spawnArgs.GetVector("offset_domelight"); + const char *objName = spawnArgs.GetString("def_domelight"); + + idDict args; + idVec3 lightOrigin = GetPhysics()->GetOrigin() + light_offset * GetPhysics()->GetAxis(); + args.SetVector( "origin", lightOrigin ); + + domelight = (idLight *)gameLocal.SpawnObject(objName, &args); + domelight->Bind( this, true ); + domelight->SetLightParm(SHADERPARM_TIMEOFFSET, -MS2SEC(gameLocal.time)); + } +} + +void hhVehicle::FreeDomeLight() { + SAFE_REMOVE( domelight ); +} + +void hhVehicle::CreateHeadLight() { + if ( spawnArgs.GetBool("headlight") ) { + // This method is more oriented for presenting our own lights + // Depending on the speed cost of having seperate entities for lights, we may + // want to present our own, like the weapons + idStr light_shader = spawnArgs.GetString("mtr_headlight"); + idVec3 light_color = spawnArgs.GetVector("headlight_color"); + idVec3 light_offset = spawnArgs.GetVector("offset_headlight"); + idVec3 light_target = spawnArgs.GetVector("offset_headlighttarget"); + idVec3 light_frustum = spawnArgs.GetVector("headlight_frustum"); + + idDict args; + idVec3 lightOrigin = GetPhysics()->GetOrigin() + light_offset * GetPhysics()->GetAxis(); + light_target.Normalize(); + idMat3 lightAxis = (light_target * GetPhysics()->GetAxis()).hhToMat3(); + + if ( light_shader.Length() ) { + args.Set( "texture", light_shader ); + } + args.SetVector( "origin", lightOrigin ); + args.Set ("angles", lightAxis.ToAngles().ToString()); + args.SetVector( "_color", light_color ); + args.SetVector( "light_target", lightAxis[0] * light_frustum.x ); + args.SetVector( "light_right", lightAxis[1] * light_frustum.y ); + args.SetVector( "light_up", lightAxis[2] * light_frustum.z ); + headlight = ( idLight * )gameLocal.SpawnEntityTypeClient( idLight::Type, &args ); + + //rww - headlight is a pure local entity + assert(headlight.IsValid() && headlight.GetEntity()); + headlight->fl.networkSync = false; + headlight->fl.clientEvents = true; + + headlight->Bind(this, true); + + headlight->SetLightParm( 6, 0.0f ); // fade out + headlight->SetLightParm( SHADERPARM_TIMEOFFSET, 0 ); // Initially faded out already + } +} + +void hhVehicle::FreeHeadLight() { + SAFE_REMOVE( headlight ); +} + +void hhVehicle::Headlight( bool on ) { + bHeadlightOn = on; + float timeOffset = -MS2SEC(gameLocal.time); + + if( headlight.IsValid() ) { + headlight->SetLightParm(SHADERPARM_TIMEOFFSET, timeOffset); + SetShaderParm(7, timeOffset); + + if (bHeadlightOn) { + headlight->SetLightParm(6, 1.0f); // Fade light in + SetShaderParm(6, 1.0f); // Fade light cone in + } + else { + headlight->SetLightParm(6, 0.0f); // Fade out + SetShaderParm(6, 0.0f); // Fade out + } + } +} + +void hhVehicle::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + if (InGodMode() || InDialogMode()) { + return; + } + + if (gameLocal.isMultiplayer) { //rww - don't let shuttles damage themselves or their teammates + if (attacker && attacker->IsType(hhPlayer::Type) && GetPilot() == attacker) { + return; + } + + hhPlayer *playerPilot = (GetPilot() && GetPilot()->IsType( hhPlayer::Type )) ? static_cast(GetPilot()) : NULL; + hhPlayer *player = (attacker && attacker->IsType( hhPlayer::Type )) ? static_cast(attacker) : NULL; + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); + if ( (gameLocal.gameType == GAME_TDM + && playerPilot + && !gameLocal.serverInfo.GetBool( "si_teamDamage" ) + && damageDef + && !damageDef->GetBool( "noTeam" ) + && player + && player->team == playerPilot->team) ) { + return; + } + } else { + if ( noDamage ) { + return; + } + if (attacker && attacker->IsType(idAI::Type) && GetPilot() == attacker) { + return; + } + } + + if (GetPilotInterface() && GetPilotInterface()->GetPilot()) { + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); + + if (gameLocal.isMultiplayer && IsType(hhShuttle::Type)) { //rww - in mp, let's try flickering the tractor beam on and off when hit + hhShuttle *shtlSelf = static_cast(this); + if (shtlSelf->noTractorTime <= gameLocal.time && shtlSelf->TractorIsActive()) { //don't let it stay off for a long time from constant attack + float dmg = ((float)damageDef->GetInt("damage", "100"))*damageScale; + int dmgTime = (int)(dmg*12); + //cap it to something reasonable on both ends + if (dmgTime < 100) { + dmgTime = 100; + } + else if (dmgTime > 1200) { + dmgTime = 1200; + } + shtlSelf->noTractorTime = gameLocal.time + dmgTime; + } + } + + // Let playerview know about the impact direction + if ( !damageDef ) { + gameLocal.Warning( "Unknown damageDef '%s'", damageDefName ); + return; + } + idVec3 damage_from, localDamageVector; + damage_from = dir; + damage_from.Normalize(); + if ( GetPilotInterface()->GetPilot()->IsType(hhPlayer::Type)) { + // Pass this on so we can get directional damage + hhPlayer *playerPilot = static_cast(GetPilotInterface()->GetPilot()); + playerPilot->viewAxis.ProjectVector( damage_from, localDamageVector ); + playerPilot->playerView.DamageImpulse( localDamageVector, damageDef ); + + // Track last attacker for use in displaying HUD hit indicator + if (!gameLocal.isClient) { + playerPilot->ReportAttack(attacker); + } + + if (gameLocal.isMultiplayer) { //rww - in MP, we want to damage the player a little from shuttle damage + float plDmgScale = damageScale*0.2f; + bool pilotWasTakingDamage = playerPilot->fl.takedamage; + playerPilot->fl.takedamage = true; + playerPilot->Damage(inflictor, attacker, dir, damageDefName, plDmgScale, INVALID_JOINT); + playerPilot->fl.takedamage = pilotWasTakingDamage; + if (playerPilot->health == 1) { //if the player died (or almost, kind of a hack) as a result, blow me up too + damageDefName = "damage_suicide"; + } + } + } + } + + // Tell HUD to show impact direction + if( IsVehicle() ) {//AI may want to know who hit them + lastAttacker = attacker; + lastAttackTime = gameLocal.time; + } + idEntity::Damage( inflictor, attacker, dir, damageDefName, 1.0f, location ); +} + +void hhVehicle::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if (gameLocal.isClient) { + return; + } + if( gameLocal.isMultiplayer || health < spawnArgs.GetInt("gibhealth") ) { //rww - always explode in mp + CancelEvents(&EV_VehicleExplode); + PostEventMS(&EV_VehicleExplode, 0, attacker, dir.x, dir.y, dir.z); + } + else if( health + damage > 0 ) { // Only do this the first death + ConsumePower(currentPower); + + if( domelight.IsValid() ) { + domelight->SetLightParm(7, 1.0f); + } + + StopSound( SND_CHANNEL_DYING, true ); + StartSound( "snd_dying", SND_CHANNEL_DYING, 0, true ); + + // control release during the damage pipe was causing a crash when damaged by splash damage + // the clip model is invalidated, then radius damage tries to apply to it. + // Should be okay to switch back if "killed" is made into an event + CancelEvents( &EV_VehicleExplode ); + PostEventSec( &EV_VehicleExplode, spawnArgs.GetFloat("explodedelay"), attacker, dir.x, dir.y, dir.z ); + } +} + +bool hhVehicle::Collide( const trace_t &collision, const idVec3 &velocity ) { + static const float minCollisionVelocity = 40.0f; + static const float maxCollisionVelocity = 650.0f; + + // Velocity in normal direction + float len = velocity * -collision.c.normal; + + if ( len > minCollisionVelocity && collision.c.material && !(collision.c.material->GetSurfaceFlags() & SURF_NOIMPACT) ) { + if (StartSound( "snd_bounce", SND_CHANNEL_BODY3 )) { + // Change volume only after we know the sound played + float volume = hhUtils::CalculateSoundVolume( len, minCollisionVelocity, maxCollisionVelocity ); + HH_SetSoundVolume(volume, SND_CHANNEL_BODY3); + } + + if (len > 200.0f) { + // Damage other + idEntity *other = gameLocal.GetTraceEntity(collision); + if (other && bDamageOtherOnCollision) { + idEntity *collideAttacker = this; + + if (GetPilotInterface() && GetPilotInterface()->GetPilot()) { //use pilot as attacker if we have one + collideAttacker = GetPilotInterface()->GetPilot(); + } + float damageScale = hhUtils::CalculateScale(len, 200, 600); + damageScale = (damageScale * 4.0f) + 1.0f; // damageScale in range [1..5] + damageScale *= other->spawnArgs.GetFloat("vehicledamagescale"); // for fine tuning + + // NOTE: Assuming damage_vehicleCollision is set to 20! + if (20 * damageScale >= other->health) { + other->Damage(this, collideAttacker, vec3_origin, "damage_vehicleCollision_gib", damageScale, 0); + } + else { + other->Damage(this, collideAttacker, vec3_origin, "damage_vehicleCollision", damageScale, 0); + } + } + + // Damage self + if (bDamageSelfOnCollision) { + Damage(other, other, collision.c.normal, "damage_vehicleCollision", 1.0f, 0); + } + } + } + + return false; +} + +bool hhVehicle::IsNoClipping() const { + return GetPilotInterface() && GetPilotInterface()->GetPilot() && GetPilotInterface()->GetPilot()->IsType(hhPlayer::Type) && static_cast(GetPilotInterface()->GetPilot())->noclip; +} + +bool hhVehicle::InGodMode() const { + return GetPilotInterface() && GetPilotInterface()->GetPilot() && GetPilotInterface()->GetPilot()->IsType(hhPlayer::Type) && static_cast(GetPilotInterface()->GetPilot())->godmode; +} + +bool hhVehicle::InDialogMode() const { + return GetPilotInterface() && GetPilotInterface()->GetPilot() && GetPilotInterface()->GetPilot()->IsType(hhPlayer::Type) && static_cast(GetPilotInterface()->GetPilot())->InDialogDamageMode(); +} + +void hhVehicle::GiveHealth( int amount ) { + health = idMath::ClampInt( 0, spawnHealth, health + amount ); +} + +void hhVehicle::GivePower( int amount ) { + currentPower = idMath::ClampInt( 0, spawnArgs.GetInt("maxPower"), currentPower + amount ); +} + +bool hhVehicle::HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ) { + idToken token; + if( !src->ReadToken(&token) || token == ";" ) { + return false; + } + if( !token.Icmp("controlvehicle") ) { + if (IsHidden()) { //rww - was possible to get back in a vehicle once it had been hidden for removal + return true; + } + if( entityGui->IsType(idActor::Type) ) { + static_cast( entityGui )->EnterVehicle( this ); + } + return true; + } + return false; +} + +void hhVehicle::DrawHUD( idUserInterface* _hud ) { + if( _hud ) { + //rww - transfer necessary hud variables from the player hud to the vehicle hud + if (GetPilotInterface() && GetPilotInterface()->GetPilot() && GetPilotInterface()->GetPilot()->IsType(hhPlayer::Type)) { + hhPlayer *plPilot = static_cast(GetPilotInterface()->GetPilot()); + if (plPilot->hud) { + //aiming + _hud->SetStateFloat("aim_R", plPilot->hud->GetStateFloat("aim_R", "")); + _hud->SetStateFloat("aim_G", plPilot->hud->GetStateFloat("aim_G", "")); + _hud->SetStateFloat("aim_B", plPilot->hud->GetStateFloat("aim_B", "")); + _hud->SetStateString("aim_text", plPilot->hud->GetStateString("aim_text", "")); + //set in UpdateHud: + /* + _hud->SetStateString("playername", plPilot->hud->GetStateString("playername", "")); + _hud->SetStateFloat("team_R", plPilot->hud->GetStateFloat("team_R", "")); + _hud->SetStateFloat("team_G", plPilot->hud->GetStateFloat("team_G", "")); + _hud->SetStateFloat("team_B", plPilot->hud->GetStateFloat("team_B", "")); + _hud->SetStateBool("ismultiplayer", plPilot->hud->GetStateBool("ismultiplayer", "")); + */ + + //set in UpdateHud: + /* + //top 4 + for (int i = 0; i < 4; i++) { + _hud->SetStateString( va( "player%i", i+1 ), plPilot->hud->GetStateString( va( "player%i", i+1 ), "")); + _hud->SetStateString( va( "player%i_portrait", i+1 ), plPilot->hud->GetStateString(va( "player%i_portrait", i+1 ), "")); + _hud->SetStateString( va( "player%i_score", i+1 ), plPilot->hud->GetStateString( va( "player%i_score", i+1 ), "")); + _hud->SetStateString( va( "rank%i", i+1 ), plPilot->hud->GetStateString( va( "rank%i", i+1 ), "")); + } + _hud->SetStateString("rank_self", plPilot->hud->GetStateString("rank_self", "")); + */ + } + } + _hud->SetStateBool( "dying", health <= 0 ); + _hud->SetStateFloat( "healthfraction", ((float)health)/(float)spawnHealth ); + _hud->SetStateFloat( "powerfraction", (currentPower)/spawnArgs.GetFloat("maxPower") ); + idAngles angles = GetAxis().ToAngles(); + _hud->SetStateFloat( "pitch", angles.pitch ); + _hud->SetStateFloat( "yaw", angles.yaw ); + _hud->SetStateFloat( "roll", angles.roll ); + _hud->Redraw( gameLocal.realClientTime ); + } +} + +void hhVehicle::PerformDeathAction(int deathAction, idActor *savedPilot, idEntity *attacker, idVec3 &dir) { + switch(deathAction) { + case 0: // Drop pilot + break; + case 1: // Kill pilot + savedPilot->Damage(this, attacker, dir, spawnArgs.GetString("def_killpilotdamage"), 1.0f, 0); + break; + } +} + +void hhVehicle::Explode( idEntity *attacker, idVec3 dir ) { + if (gameLocal.isClient) { + return; + } + if (!GetPilotInterface()) { + return; + } + + idEntityPtr savedPilot = GetPilotInterface()->GetPilot(); + + EjectPilot(); + + if (savedPilot.IsValid()) { + int deathAction = savedPilot->IsType(hhPlayer::Type) ? spawnArgs.GetInt("DeathActionPlayer") : spawnArgs.GetInt("DeathActionAI"); + PerformDeathAction(deathAction, savedPilot.GetEntity(), attacker, dir); + } + + const char *deathExplosionDef = spawnArgs.GetString("def_deathexplosion", ""); + if (deathExplosionDef && deathExplosionDef[0]) { + gameLocal.RadiusDamage(GetOrigin(), this, this, this, this, deathExplosionDef); + } + + // Explode into gibs + idVec3 vel = dir * 300; + hhUtils::SpawnDebrisMass(spawnArgs.GetString("def_debrisspawner"), + GetPhysics()->GetOrigin(), NULL, &vel, 1); + StopSound(SND_CHANNEL_DYING, true); + StartSound("snd_death", SND_CHANNEL_ANY); +} + +bool hhVehicle::HasPower( int amount ) const { + return currentPower >= amount; +} + +bool hhVehicle::ConsumePower( int amount ) { + if( InGodMode() ) { + return true; + } + + if (currentPower >= amount) { + currentPower -= amount; + return true; + } + + currentPower = 0; + return false; +} + +void hhVehicle::RemoveVehicle() { + //rww - stop sounds too + StopSound(SND_CHANNEL_THRUSTERS, true); + StopSound(SND_CHANNEL_MISC1, true); + StopSound(SND_CHANNEL_DYING, true); + + Hide(); + GetPhysics()->SetContents( 0 ); + GetPhysics()->SetClipMask( 0 ); + PostEventMS( &EV_Remove, 3000 ); // Give anything targetting it a chance to retarget +} + +void hhVehicle::WriteToSnapshot( idBitMsgDelta &msg ) const { + physicsObj.WriteToSnapshot( msg ); + assert(currentPower < (1<<20)); + msg.WriteBits(currentPower, 20); + assert(health < (1<<12)); + msg.WriteBits(health, 12); + msg.WriteBits(bHeadlightOn, 1); + + idCQuat modelQuat = modelAxis.ToCQuat(); + msg.WriteFloat(modelQuat.x); + msg.WriteFloat(modelQuat.y); + msg.WriteFloat(modelQuat.z); + + msg.WriteBits(renderEntity.suppressSurfaceInViewID, GENTITYNUM_BITS); + + /* + msg.WriteBits(currentPower, 32); + msg.WriteFloat(thrustFactor); + msg.WriteFloat(thrustMin); + msg.WriteFloat(thrustMax); + msg.WriteFloat(thrustAccel); + msg.WriteFloat(thrustScale); + msg.WriteFloat(dockBoostFactor); + */ + + msg.WriteBits(IsNoClipping() ? 0 : vehicleClipMask, 32); + msg.WriteBits(IsNoClipping() ? 0 : vehicleContents, 32); + msg.WriteBits(IsHidden(), 1); + + msg.WriteBits(dock.GetSpawnId(), 32); + msg.WriteBits(domelight.GetSpawnId(), 32); + WriteBindToSnapshot( msg ); + + if (fireController) { //fire controller can be null at this point. + msg.WriteBits(fireController->barrelOffsets.GetCurrentIndex(), 8); + } + else { + msg.WriteBits(0, 8); + } +} + +void hhVehicle::ReadFromSnapshot( const idBitMsgDelta &msg ) { + physicsObj.ReadFromSnapshot( msg ); + currentPower = msg.ReadBits(20); + health = msg.ReadBits(12); + bool headlightOn = !!msg.ReadBits(1); + if (headlightOn != bHeadlightOn) { + Headlight(headlightOn); + } + + idCQuat modelQuat; + modelQuat.x = msg.ReadFloat(); + modelQuat.y = msg.ReadFloat(); + modelQuat.z = msg.ReadFloat(); + modelAxis = modelQuat.ToMat3(); + + renderEntity.suppressSurfaceInViewID = msg.ReadBits(GENTITYNUM_BITS); + + /* + currentPower = msg.ReadBits(32); + thrustFactor = msg.ReadFloat(); + thrustMin = msg.ReadFloat(); + thrustMax = msg.ReadFloat(); + thrustAccel = msg.ReadFloat(); + thrustScale = msg.ReadFloat(); + dockBoostFactor = msg.ReadFloat(); + */ + + int newVehicleClipMask = msg.ReadBits(32); + if (newVehicleClipMask != vehicleClipMask) { + physicsObj.SetClipMask(newVehicleClipMask); + vehicleClipMask = newVehicleClipMask; + } + int newVehicleContents = msg.ReadBits(32); + if (newVehicleContents != vehicleContents) { + physicsObj.SetContents(newVehicleContents); + vehicleContents = newVehicleContents; + } + + bool hidden = !!msg.ReadBits(1); + if (hidden != IsHidden()) { + if (hidden) { + Hide(); + } else { + Show(); + } + } + + int spawnId; + + spawnId = msg.ReadBits(32); //rwwFIXME why is 0 check needed? something checking the container strangely? + if (!spawnId) { + dock = NULL; + } + else { + dock.SetSpawnId(spawnId); + } + spawnId = msg.ReadBits(32); + if (!spawnId) { + domelight = NULL; + } + else { + if (domelight.SetSpawnId(spawnId)) { + domelight->Bind( this, true ); + domelight->SetLightParm(SHADERPARM_TIMEOFFSET, -MS2SEC(gameLocal.time)); + } + } + ReadBindFromSnapshot( msg ); + + int barrelIndex = msg.ReadBits(8); + if (fireController) { + fireController->barrelOffsets.SetCurrentIndex(barrelIndex); + } +} + +void hhVehicle::ClientPredictionThink( void ) { + Think(); +} + +bool hhVehicle::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + switch ( event ) { + case EVENT_EJECT_PILOT: + EjectPilot(); + return true; + default: + return hhRenderEntity::ClientReceiveEvent( event, time, msg ); + } +} + +void hhVehicle::Event_Explode( idEntity *attacker, float dx, float dy, float dz ) { + if (InDialogMode()) { + // Death not allowed, repost + CancelEvents(&EV_VehicleExplode); + PostEventMS(&EV_VehicleExplode, 1000, attacker, dx, dy, dz); + return; + } + + idVec3 dir(dx, dy, dz); + Explode(attacker, dir); +} + +void hhVehicle::Event_ResetGravity() { + ResetGravity(); +} + +void hhVehicle::Event_FireCannon() { + if( fireController && fireController->LaunchProjectiles(vec3_zero) ) { + StartSound( "snd_cannon", SND_CHANNEL_ANY ); + fireController->MuzzleFlash(); + if (GetPilot() && GetPilot()->IsType(hhPlayer::Type)) { + // Only players get weapon fire feedback, so monster shuttles aren't pushed back into docks + fireController->WeaponFeedback(); + } + } +} + +// Script control interfaces +void hhVehicle::Event_GetIn( idEntity *ent ) { + if (ent->IsType(idActor::Type)) { + static_cast( ent )->EnterVehicle( this ); + GetPilotInterface()->UnderScriptControl( true ); + } +} + +void hhVehicle::Event_GetOut() { + if (GetPilotInterface()) { + GetPilotInterface()->UnderScriptControl( true ); + EjectPilot(); + } +} + +void hhVehicle::Event_Fire( bool start ) { + if( GetPilotInterface() ) { + GetPilotInterface()->UnderScriptControl( true ); + GetPilotInterface()->Fire( start ); + } +} + +void hhVehicle::Event_AltFire( bool start ) { + if( GetPilotInterface() ) { + GetPilotInterface()->UnderScriptControl( true ); + GetPilotInterface()->AltFire( start ); + } +} + +void hhVehicle::Event_OrientTowards( idVec3 &point, float speed ) { + if( GetPilotInterface() ) { + GetPilotInterface()->UnderScriptControl( true ); + GetPilotInterface()->OrientTowards( point, speed ); // passed as percentage of max + } +} + +void hhVehicle::Event_StopOrientingTowards() { + if( GetPilotInterface() ) { + GetPilotInterface()->UnderScriptControl( true ); + GetPilotInterface()->OrientTowards( vec3_origin, 0.0f ); + } +} + +void hhVehicle::Event_ThrustTowards( idVec3 &point, float speed ) { + idVec3 pt = point; + if( GetPilotInterface() ) { + GetPilotInterface()->UnderScriptControl( true ); + GetPilotInterface()->ThrustTowards( pt, speed ); // passed as percentage of max + } +} + +void hhVehicle::Event_StopThrustingTowards() { + if( GetPilotInterface() ) { + GetPilotInterface()->UnderScriptControl( true ); + GetPilotInterface()->ThrustTowards( vec3_origin, 0.0f ); + } +} + +void hhVehicle::Event_ReleaseScriptControl() { + if( GetPilotInterface() ) { + GetPilotInterface()->UnderScriptControl( false ); + GetPilotInterface()->ClearBufferedCmds(); + } +} + +void hhVehicle::Event_EjectPilot() { + EjectPilot(); +} + diff --git a/src/Prey/game_vehicle.h b/src/Prey/game_vehicle.h new file mode 100644 index 0000000..8e32330 --- /dev/null +++ b/src/Prey/game_vehicle.h @@ -0,0 +1,375 @@ +#ifndef __GAME_VEHICLE_H__ +#define __GAME_VEHICLE_H__ + +#define CONTENTS_VEHICLE (CONTENTS_BODY|CONTENTS_MONSTERCLIP|CONTENTS_MOVEABLECLIP) +#define CLIPMASK_VEHICLE (CONTENTS_BODY|CONTENTS_VEHICLECLIP|CONTENTS_SOLID|CONTENTS_FORCEFIELD) + +extern const idEventDef EV_Vehicle_FireCannon; +extern const idEventDef EV_VehicleExplode; + +class idLight; +class hhVehicle; +class hhPlayer; +class hhDock; +class hhShuttleDock; + +enum EFireMode{ + FIREMODE_NOTHING=0, + FIREMODE_CANNON, + FIREMODE_TRACTOR, + FIREMODE_EXIT, + NUM_FIREMODES +}; + +// Support for direction masks, as used for thrusters +enum { + // The order of these is important and is laid out as: + THRUSTER_FRONT=0, // x < 0 + THRUSTER_BACK, // x > 0 + THRUSTER_LEFT, // y < 0 + THRUSTER_RIGHT, // y > 0 + THRUSTER_TOP, // z < 0 + THRUSTER_BOTTOM, // z > 0 + THRUSTER_DIRECTIONS +}; + + + +//========================================================================== +// +// hhVehicleThruster +// +//========================================================================== + +class hhVehicleThruster : public idEntity { + CLASS_PROTOTYPE( hhVehicleThruster ); +public: + hhVehicleThruster(); + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - network code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + + void SetOwner(hhVehicle *v) { owner = v; } + void SetSmoker(bool s, idVec3 &offset, idVec3 &dir); + void SetThruster(bool on); + void Update( const idVec3 &vel ); + void SetDying(bool dying); + virtual bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); + +protected: + void Event_AssignFxSmoke( hhEntityFx* fx ); + +protected: + //rww - changed to idEntityPtr's + idEntityPtr owner; + idEntityPtr fxSmoke; + idVec3 localOffset; + idVec3 localDirection; + float soundDistance; + bool bSomeThrusterActive; + bool bSoundMaster; + idVec3 localVelocity; +}; + + +//========================================================================== +// +// hhPilotVehicleInterface +// +// +//========================================================================== +class hhPilotVehicleInterface : public idClass { + CLASS_PROTOTYPE( hhPilotVehicleInterface ); + + public: + hhPilotVehicleInterface(); + ~hhPilotVehicleInterface(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //Called from vehicle + virtual void RetrievePilotInput( usercmd_t& cmds, idAngles& viewAngles ); + virtual idActor* GetPilot() const; + virtual void UnderScriptControl( bool yes ) { underScriptControl = yes; } + virtual bool UnderScriptControl() const { return underScriptControl; } + virtual void OrientTowards( const idVec3 &loc, float speed ) {} + virtual void ThrustTowards( const idVec3 &loc, float speed ) {} + virtual void Fire(bool on) {} + virtual void AltFire(bool on) {} + virtual void ClearBufferedCmds() {} + + //Called from pilot + virtual bool ControllingVehicle() const; + virtual void ReleaseControl(); + virtual hhVehicle* GetVehicle() const; + virtual void TakeControl( hhVehicle* vehicle, idActor* pilot ); + virtual idVec3 DeterminePilotOrigin() const; + virtual idMat3 DeterminePilotAxis() const; + + virtual bool InvalidVehicleImpulse( int impulse ); + int GetVehicleSpawnId() const { return vehicle.GetSpawnId(); } + bool SetVehicleSpawnId( int id ) { return vehicle.SetSpawnId( id ); } + protected: + idEntityPtr pilot; + idEntityPtr vehicle; + bool underScriptControl; +}; + +class hhAIVehicleInterface : public hhPilotVehicleInterface { + CLASS_PROTOTYPE( hhAIVehicleInterface ); + + public: + hhAIVehicleInterface(void); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //Called from pilot + void BufferPilotCmds( const usercmd_t* cmds, const idAngles* viewAngles ); + virtual void OrientTowards( const idVec3 &loc, float speed ); + virtual void ThrustTowards( const idVec3 &loc, float speed ); + virtual bool IsVehicleDocked() const; + virtual void TakeControl( hhVehicle* vehicle, idActor* pilot ); + virtual void Fire(bool on); + virtual void AltFire(bool on); + + //Called from vehicle + virtual void RetrievePilotInput( usercmd_t& cmds, idAngles& viewAngles ); + + protected: + void ClearBufferedCmds(); + + protected: + usercmd_t bufferedCmds; + idAngles bufferedViewAngles; + bool stateFiring; + bool stateAltFiring; + idVec3 stateOrientDestination; + float stateOrientSpeed; + idVec3 stateThrustDestination; + float stateThrustSpeed; +}; + +class hhPlayerVehicleInterface : public hhPilotVehicleInterface { + CLASS_PROTOTYPE( hhPlayerVehicleInterface ); + + public: + hhPlayerVehicleInterface(); + virtual ~hhPlayerVehicleInterface(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //Called from vehicle + virtual void RetrievePilotInput( usercmd_t& cmds, idAngles& viewAngles ); + + //Called from pilot + virtual void ReleaseControl(); + virtual void TakeControl( hhVehicle* vehicle, idActor* pilot ); + void StartHUDTranslation(); // HUMANHEAD mdl: Moved these into hhPlayer events to prevent loadgame crash + // (events must be on entities, not just idClasses, in order to save properly) + int GetHandSpawnId() const { return controlHand.GetSpawnId(); } + bool SetHandSpawnId( int id ) { return controlHand.SetSpawnId( id ); } + hhControlHand *GetHandEntity() const { return controlHand.GetEntity(); } + const hhWeaponHandState *GetWeaponHandState() const { return &weaponHandState; } + hhWeaponHandState *GetWeaponHandState() { return &weaponHandState; } + + //Called from playerView + virtual idUserInterface* GetHUD() const { return hud; } + virtual void DrawHUD( idUserInterface* _hud ); + + protected: + void UpdateControlHand( const usercmd_t& cmds ); + void CreateControlHand( hhPlayer* pilot, const char* handName ); + void RemoveHand(); + + protected: + hhWeaponHandState weaponHandState; + idEntityPtr controlHand; + + idUserInterface* hud; + bool uniqueHud; //rww + idInterpolate translationAlpha; // interpolator for hud translation +}; + +//========================================================================== +// +// hhVehicle +// +//========================================================================== + +class hhVehicle : public hhRenderEntity { + ABSTRACT_PROTOTYPE( hhVehicle ); + +public: + hhVehicle() : fireController(NULL), pilotInterface(NULL) {} + ~hhVehicle(); + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think(); + virtual void ClientPredictionThink( void ); + virtual void Present(); + enum { + EVENT_EJECT_PILOT = idEntity::EVENT_MAXEVENTS, + EVENT_MAXEVENTS + }; + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + 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 bool HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ); + virtual const idMat3& GetAxis( int id = 0 ) const { return modelAxis; } + virtual void SetAxis( const idMat3& axis ); + virtual void Portalled(idEntity *portal); + virtual idVec3 GetPortalPoint( void ); + virtual idVec3 DeterminePilotOrigin() const; + virtual idMat3 DeterminePilotAxis() const; + + virtual void FireThrusters( const idVec3& impulse ) {} + virtual void ApplyImpulse( idEntity* ent, int id, const idVec3& point, const idVec3& impulse ); + virtual void ApplyImpulse( const idVec3& impulse ); + + virtual void SetDock( const hhDock* dock ) { this->dock = dock; } + virtual void Undock() { dock.Clear(); } + virtual bool IsDocked() const { return dock.IsValid(); } + virtual idActor* GetPilot() const { return (GetPilotInterface()) ? GetPilotInterface()->GetPilot() : NULL; } + virtual hhPilotVehicleInterface* GetPilotInterface() const { return pilotInterface; } + virtual void SetPilotInterface(hhPilotVehicleInterface *pInterface) { pilotInterface = pInterface; } + + bool IsNoClipping() const; + bool InGodMode() const; + bool InDialogMode() const; + + virtual float GetThrustFactor() const { return thrustFactor; } + + void GiveHealth( int amount ); + void GivePower( int amount ); + bool ConsumePower( int amount ); + bool HasPower( int amount ) const; + bool NeedsPower() const { return currentPower < spawnArgs.GetFloat("maxPower"); } + + virtual bool WillAcceptPilot( idActor *act ) = 0; + virtual void AcceptPilot( hhPilotVehicleInterface* pilotInterface ); + void RestorePilot( hhPilotVehicleInterface* pilotInterface); + virtual void ReleaseControl() {} + virtual void EjectPilot(); + virtual bool CanBecomeVehicle(idActor *pilot); + virtual void BecomeVehicle(); + virtual void BecomeConsole(); + virtual bool IsVehicle() const; + virtual bool IsConsole() const; + + virtual idVec3 GetFireOrigin() { return GetOrigin(); } // Called by firecontroller + virtual idMat3 GetFireAxis() { return GetAxis(); } + + virtual void ResetGravity(); + + virtual void ProcessPilotInput( const usercmd_t* cmds, const idAngles* viewAngles ); + + virtual float GetWeaponRecoil() const { return (fireController) ? fireController->GetRecoil() : 0.0f; } + virtual float GetWeaponFireDelay() const { return (fireController) ? fireController->GetFireDelay() : 0.0f; } + + virtual void DrawHUD( idUserInterface* _hud ); + + virtual void DoPlayerImpulse(int impulse); //rww - seperated into its own function because of networked impulse events + +protected: + virtual void UpdateModel(); + virtual const idEventDef* GetAttackFunc( const char* funcName ); + virtual void InitializeAttackFuncs(); + + virtual void InitPhysics(); + virtual void SetConsolePhysics(); + virtual void SetVehiclePhysics(); + + virtual void SetConsoleModel(); + virtual void SetVehicleModel(); + + void ProcessButtons( const usercmd_t& cmds ); + void ProcessImpulses( const usercmd_t& cmds ); + float CmdScale( const usercmd_t* cmd ) const; + void SetThrustBooster( float minBooster, float maxBooster, float accelBooster ); + + hhVehicleFireController * CreateFireController() { return new hhVehicleFireController; } + + virtual void CreateHeadLight(); + virtual void FreeHeadLight(); + virtual void Headlight( bool on ); + virtual void CreateDomeLight(); + virtual void FreeDomeLight(); + + virtual void RemoveVehicle(); + virtual void PerformDeathAction( int deathAction, idActor *savedPilot, idEntity *attacker, idVec3 &dir ); + virtual void Explode( idEntity *attacker, idVec3 dir ); + virtual void Event_FireCannon(); + void Event_Explode( idEntity *attacker, float dx, float dy, float dz ); + void Event_GetIn( idEntity *ent ); + void Event_GetOut(); + void Event_ResetGravity(); + void Event_Fire( bool start ); + void Event_AltFire( bool start ); + void Event_OrientTowards( idVec3 &point, float speed ); + void Event_StopOrientingTowards(); + void Event_ThrustTowards( idVec3 &point, float speed ); + void Event_StopThrustingTowards(); + void Event_ReleaseScriptControl(); + void Event_EjectPilot(); + + +protected: + hhPhysics_Vehicle physicsObj; // physics object for vehicle + idMat3 modelAxis; + + hhPilotVehicleInterface* pilotInterface; + + hhVehicleFireController* fireController; + usercmd_t oldCmds; + + //FIXME: make these renderLight structs + idEntityPtr headlight; // Head light + idEntityPtr domelight; // Dome light + bool bHeadlightOn; + + int currentPower; + float thrustFactor; + float thrustMin; + float thrustMax; + float thrustAccel; + float thrustScale; + float dockBoostFactor; + + idEntityPtr dock; + + idEntityPtr lastAttacker; + int lastAttackTime; + + int vehicleClipMask; // clipmask of vehicle when not noclipping + int vehicleContents; // contents of vehicle when not noclipping + + bool bDamageSelfOnCollision; // Vehicle takes damage when colliding + bool bDamageOtherOnCollision;// Vehicle takes damage when colliding + + bool bDisallowAttackUntilRelease; + bool bDisallowAltAttackUntilRelease; + + int thrusterCost; + int validThrustTime; // Delay thrust capability a couple seconds after entering vehicle + + const idEventDef* attackFunc; + const idEventDef* finishedAttackingFunc; + const idEventDef* altAttackFunc; + const idEventDef* finishedAltAttackingFunc; + + bool noDamage; +}; + +#endif + diff --git a/src/Prey/game_vomiter.cpp b/src/Prey/game_vomiter.cpp new file mode 100644 index 0000000..bfc3d67 --- /dev/null +++ b/src/Prey/game_vomiter.cpp @@ -0,0 +1,333 @@ +// Game_Vomiter.cpp +// +// spews vomit chunks periodically +// triggering will cause an eruption + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_Erupt("erupt", "d"); +const idEventDef EV_UnErupt("unerupt", NULL); +const idEventDef EV_DamagePulse("damagepulse", NULL); +const idEventDef EV_MovablePulse(""); + +const idEventDef EV_Broadcast_AssignFxErupt( "", "e" ); + +CLASS_DECLARATION(hhAnimatedEntity, hhVomiter) + EVENT( EV_Activate, hhVomiter::Event_Trigger ) + EVENT( EV_Erupt, hhVomiter::Event_Erupt ) + EVENT( EV_UnErupt, hhVomiter::Event_UnErupt ) + EVENT( EV_PlayIdle, hhVomiter::Event_PlayIdle ) + EVENT( EV_DamagePulse, hhVomiter::Event_DamagePulse ) + EVENT( EV_MovablePulse, hhVomiter::Event_MovablePulse ) + EVENT( EV_Broadcast_AssignFxErupt, hhVomiter::Event_AssignFxErupt ) +END_CLASS + +hhVomiter::hhVomiter(void) { + // mdl: Moved this here to prevent uninitialized fxErupt from crashing the destructor + fxErupt = NULL; +} + +void hhVomiter::Spawn(void) { + + detectionTrigger = NULL; + + spawnArgs.GetFloat("minDelay", "3", mindelay); + spawnArgs.GetFloat("maxDelay", "7", maxdelay); + secBetweenDamage = spawnArgs.GetFloat("secBetweenDamage"); + secBetweenMovables = spawnArgs.GetFloat("secBetweenMovables"); + minMovableVel = spawnArgs.GetFloat("minMovableVel"); + maxMovableVel = spawnArgs.GetFloat("maxMovableVel"); + removeTime = spawnArgs.GetFloat("movableRemoveTime"); + numMovables = spawnArgs.GetInt("numMovables"); + goAwayChance = spawnArgs.GetFloat("GoAwayChance"); + + idleAnim = GetAnimator()->GetAnim("idle"); + painAnim = GetAnimator()->GetAnim("pain"); + spewAnim = GetAnimator()->GetAnim("spew"); + deathAnim = GetAnimator()->GetAnim("death"); + + bErupting = false; + bIdling = false; + + // setup the clipModel + GetPhysics()->SetContents( CONTENTS_SOLID ); + + // Spawn trigger used for waking it up + SpawnTrigger(); + + //TODO: Spawn this the first time it erupts instead ? + // Spawn FX system + hhFxInfo fxInfo; + fxInfo.SetStart( false ); + fxInfo.SetNormal( GetAxis()[2] ); + fxInfo.RemoveWhenDone( false ); + fxInfo.Triggered( true ); + BroadcastFxInfo(spawnArgs.GetString("fx_vomit"), GetOrigin(), GetAxis(), &fxInfo, &EV_Broadcast_AssignFxErupt ); + + StartIdle(); + GoToSleep(); + fl.takedamage = true; +} + +hhVomiter::~hhVomiter() { + SAFE_REMOVE(fxErupt); +} + +void hhVomiter::Save(idSaveGame *savefile) const { + savefile->WriteBool(bErupting); + savefile->WriteBool(bIdling); + savefile->WriteBool(bAwake); + savefile->WriteFloat(mindelay); + savefile->WriteFloat(maxdelay); + + savefile->WriteFloat(secBetweenDamage); + savefile->WriteFloat(secBetweenMovables); + + savefile->WriteInt(idleAnim); + savefile->WriteInt(painAnim); + savefile->WriteInt(spewAnim); + savefile->WriteInt(deathAnim); + savefile->WriteFloat(minMovableVel); + savefile->WriteFloat(maxMovableVel); + savefile->WriteFloat(removeTime); + + savefile->WriteInt(numMovables); + savefile->WriteFloat(goAwayChance); + + savefile->WriteObject(fxErupt); + + detectionTrigger.Save(savefile); +} + +void hhVomiter::Restore( idRestoreGame *savefile ) { + savefile->ReadBool(bErupting); + savefile->ReadBool(bIdling); + savefile->ReadBool(bAwake); + savefile->ReadFloat(mindelay); + savefile->ReadFloat(maxdelay); + + savefile->ReadFloat(secBetweenDamage); + savefile->ReadFloat(secBetweenMovables); + + savefile->ReadInt(idleAnim); + savefile->ReadInt(painAnim); + savefile->ReadInt(spewAnim); + savefile->ReadInt(deathAnim); + savefile->ReadFloat(minMovableVel); + savefile->ReadFloat(maxMovableVel); + savefile->ReadFloat(removeTime); + + savefile->ReadInt(numMovables); + savefile->ReadFloat(goAwayChance); + + savefile->ReadObject( reinterpret_cast(fxErupt) ); + + detectionTrigger.Restore(savefile); +} + +void hhVomiter::SpawnTrigger() { + idDict Args; + + hhUtils::PassArgs( spawnArgs, Args, "triggerpass_" ); + + Args.Set( "target", name.c_str() ); + Args.Set( "bind", name.c_str() ); + + Args.SetVector( "origin", GetOrigin() + spawnArgs.GetVector("offset_trigger") ); + Args.SetMatrix( "rotation", GetAxis() ); + detectionTrigger = gameLocal.SpawnObject( spawnArgs.GetString("def_trigger"), &Args ); +} + +void hhVomiter::DormantBegin( void ) { +} + +void hhVomiter::DormantEnd( void ) { +} + +void hhVomiter::Erupt(int repeat) { + if (bErupting || health <= 0 || fl.isDormant) { + if (fl.isDormant) { + UnErupt(); // go to sleep, etc., so we can wake up again later + } + return; + } + + bErupting = true; + StartSound( "snd_erupt", SND_CHANNEL_ANY ); + GetAnimator()->ClearAllAnims( gameLocal.time, 0 ); + GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, spewAnim, gameLocal.time, 0); + + int startTime = GetAnimator()->CurrentAnim( ANIMCHANNEL_ALL )->Length(); + PostEventMS( &EV_PlayIdle, startTime ); + + PostEventMS( &EV_MovablePulse, 0 ); // Self repeating moveable spawn + PostEventMS( &EV_DamagePulse, 0 ); // Self repeating damage pulse + + // Activate FX + if (fxErupt) { + fxErupt->PostEventMS( &EV_Activate, 0, this ); + } + + // Stop erupting in a while + PostEventSec( &EV_UnErupt, spawnArgs.GetFloat("vomitTime") ); +} + +void hhVomiter::UnErupt() { + CancelEvents(&EV_Erupt); // Stop any pending events + CancelEvents(&EV_DamagePulse); + CancelEvents(&EV_MovablePulse); + + if (bErupting) { + bErupting = false; + // Deactivate FX + if (fxErupt) { + fxErupt->PostEventMS( &EV_Fx_KillFx, 0 ); + } + } + + GoToSleep(); +} + +void hhVomiter::GoToSleep() { + bAwake = false; +} + +void hhVomiter::WakeUp() { + if (!bAwake && !bErupting && health > 0) { + bAwake = true; + StartSound( "snd_wakeup", SND_CHANNEL_ANY ); + PostEventSec( &EV_Erupt, 0.1+mindelay + gameLocal.random.RandomFloat()*(maxdelay-mindelay), true); + } +} + +bool hhVomiter::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if (painAnim) { + StartSound( "snd_pain", SND_CHANNEL_ANY ); + GetAnimator()->ClearAllAnims( gameLocal.time, 200 ); + GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, painAnim, gameLocal.time, 200); + + int startTime = GetAnimator()->CurrentAnim( ANIMCHANNEL_ALL )->Length(); + PostEventMS( &EV_PlayIdle, startTime ); + } + return( idEntity::Pain(inflictor, attacker, damage, dir, location) ); +} + +void hhVomiter::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + + if (health + damage > 0) { // Only do this the first death + // Clear Event Queue of all eruptions + CancelEvents(&EV_Erupt); + CancelEvents(&EV_DamagePulse); + if (bErupting) { + UnErupt(); + } + + StartSound( "snd_die", SND_CHANNEL_ANY ); + + // Spawn dead effect -- currently an explosion + hhFxInfo fxInfo; + fxInfo.SetNormal( GetAxis()[2] ); + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfoPrefixed( "fx_death", GetOrigin(), -GetAxis(), &fxInfo ); + + ActivateTargets( attacker ); + + StopIdle(); + GetAnimator()->ClearAllAnims( gameLocal.time, 0 ); + GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, deathAnim, gameLocal.time, 0); + } + else { // Post-death pain +// GetAnimator()->ClearAllAnims( gameLocal.time, 200 ); +// GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, painAnim, gameLocal.time, 200); + } +} + +void hhVomiter::StartIdle() { + hhFxInfo fxInfo; + + if (idleAnim && !bIdling && health > 0) { + PostEventMS(&EV_PlayIdle, 0); + StartSound( "snd_idle", SND_CHANNEL_IDLE ); + bIdling = true; + } +} + +void hhVomiter::StopIdle() { + CancelEvents(&EV_PlayIdle); + StopSound( SND_CHANNEL_IDLE ); + bIdling = false; +} + +// +// Events +// + +void hhVomiter::Event_AssignFxErupt( hhEntityFx* fx ) { + fxErupt = fx; +} + +void hhVomiter::Event_PlayIdle() { + GetAnimator()->CycleAnim(ANIMCHANNEL_ALL, idleAnim, gameLocal.time, 100); +} + +void hhVomiter::Event_Trigger( idEntity *activator ) { + WakeUp(); +} + +void hhVomiter::Event_Erupt(int repeat) { + Erupt(repeat); +} + +void hhVomiter::Event_UnErupt() { + UnErupt(); +} + +void hhVomiter::Event_DamagePulse() { + idEntity *touch[ MAX_GENTITIES ]; + int num; + // Damage all entities touching trigger's clipmodel + num = hhUtils::EntitiesTouchingClipmodel( detectionTrigger->GetPhysics()->GetClipModel(), touch, MAX_GENTITIES, MASK_SHOT_BOUNDINGBOX ); + for (int i = 0; i < num; i++ ) { + if( !touch[i] || touch[i] == this ) { + continue; + } + touch[i]->Damage(this, this, GetAxis()[2], spawnArgs.GetString("def_damage"), 1.0f, 0); + } + PostEventSec( &EV_DamagePulse, secBetweenDamage ); +} + +void hhVomiter::Event_MovablePulse() { + // Spawn movables + idDict args; + idEntity *ent; + idVec3 vel, dir; + + const char *movableName = spawnArgs.RandomPrefix("def_movable", gameLocal.random); + if (movableName && *movableName) { + args.Clear(); + args.SetVector("origin", GetOrigin()); + + if (gameLocal.random.RandomFloat() < goAwayChance) { + args.SetBool("removeOnCollision", true); + } + else { + args.SetFloat("removeTime", removeTime); + } + + for (int ix=0; ixGetPhysics()->SetLinearVelocity( vel ); + ent->fl.takedamage = false; // don't let the radius damage affect them + } + } + PostEventSec( &EV_MovablePulse, secBetweenMovables ); // Self repeating event +} diff --git a/src/Prey/game_vomiter.h b/src/Prey/game_vomiter.h new file mode 100644 index 0000000..64f91a8 --- /dev/null +++ b/src/Prey/game_vomiter.h @@ -0,0 +1,61 @@ + +#ifndef __GAME_VOMITER_H__ +#define __GAME_VOMITER_H__ + +class hhVomiter : public hhAnimatedEntity { +public: + CLASS_PROTOTYPE( hhVomiter ); + + hhVomiter(); + virtual ~hhVomiter(); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Erupt(int repeat); + void UnErupt(void); + 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 ); + virtual void DormantBegin( void ); + virtual void DormantEnd( void ); + +protected: + void StartIdle(); + void StopIdle(); + void SpawnTrigger(); + void WakeUp(); + void GoToSleep(); + +protected: + void Event_DamagePulse(void); + void Event_Trigger( idEntity *activator ); + void Event_Erupt(int repeat); + void Event_UnErupt(void); + void Event_PlayIdle( void ); + void Event_MovablePulse(); + void Event_AssignFxErupt( hhEntityFx* fx ); + +private: + bool bErupting; + bool bIdling; + bool bAwake; + float mindelay; // Delays until next vomit + float maxdelay; + float secBetweenDamage; + float secBetweenMovables; + int idleAnim; + int painAnim; + int spewAnim; + int deathAnim; + float minMovableVel; + float maxMovableVel; + float removeTime; + int numMovables; + float goAwayChance; + idEntityFx *fxErupt; + + idEntityPtr detectionTrigger; +}; + +#endif /* __GAME_VOMITER_H__ */ diff --git a/src/Prey/game_weaponHandState.cpp b/src/Prey/game_weaponHandState.cpp new file mode 100644 index 0000000..3c8750d --- /dev/null +++ b/src/Prey/game_weaponHandState.cpp @@ -0,0 +1,282 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/* +TERMS: + Transition: How to transition up and down + 0 - Abruptly. No up/down anims. Just disappears + 1 - With Anims. Play the up/down anims. +*/ + +CLASS_DECLARATION( idClass, hhWeaponHandState ) +END_CLASS + +/* +============ +hhWeaponHandState::hhWeaponHandState +============ +*/ +hhWeaponHandState::hhWeaponHandState( hhPlayer *aPlayer, + bool aTrackWeapon, int weaponTransition, + bool aTrackHand, int handTransition ){ + + player = aPlayer; + + oldHand = NULL; + oldWeaponNum = 0; + + oldHandUp = handTransition; + oldWeaponUp = weaponTransition; + + trackHand = aTrackHand; + trackWeapon = aTrackWeapon; +} + +void hhWeaponHandState::Save(idSaveGame *savefile) const { + player.Save(savefile); + oldHand.Save(savefile); + savefile->WriteInt(oldWeaponNum); + savefile->WriteInt(oldHandUp); + savefile->WriteInt(oldWeaponUp); + savefile->WriteBool(trackHand); + savefile->WriteBool(trackWeapon); +} + +void hhWeaponHandState::Restore( idRestoreGame *savefile ) { + player.Restore(savefile); + oldHand.Restore(savefile); + savefile->ReadInt(oldWeaponNum); + savefile->ReadInt(oldHandUp); + savefile->ReadInt(oldWeaponUp); + savefile->ReadBool(trackHand); + savefile->ReadBool(trackWeapon); +} + +void hhWeaponHandState::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteBits(player.GetSpawnId(), 32); + msg.WriteBits(oldHand.GetSpawnId(), 32); + + msg.WriteBits(oldWeaponNum, 32); + msg.WriteBits(oldHandUp, 32); + msg.WriteBits(oldWeaponUp, 32); + msg.WriteBits(trackHand, 1); + msg.WriteBits(trackWeapon, 1); +} + +void hhWeaponHandState::ReadFromSnapshot( const idBitMsgDelta &msg ) { + player.SetSpawnId(msg.ReadBits(32)); + oldHand.SetSpawnId(msg.ReadBits(32)); + + oldWeaponNum = msg.ReadBits(32); + oldHandUp = msg.ReadBits(32); + oldWeaponUp = msg.ReadBits(32); + trackHand = !!msg.ReadBits(1); + trackWeapon = !!msg.ReadBits(1); +} + + +/* +============ +hhWeaponHandState::SetPlayer +============ +*/ +void hhWeaponHandState::SetPlayer( hhPlayer *aPlayer ) { + player = aPlayer; +} + +/* +============ +hhWeaponHandState::Save +============ +*/ +int hhWeaponHandState::Archive( const char *newWeaponDef, int weaponTransition, + const char *newHandDef, int handTransition ) { + + if ( !player.IsValid() ) { + gameLocal.Warning( "Error in hhWeaponHandState::Archive, no player set" ); + } + + // Do we care about the old hand? + if ( trackHand ) { + // Save the hand, break if no hand. Must save for the restore + oldHand = player->hand; + + if ( oldHand.IsValid() ) { + // If we are going to raise it later put down + if ( oldHandUp ) { + oldHand->PutAway(); + } + + // Make it disapear + oldHand->Hide(); + + // Make sure it doesn't come back (Ready makes it visible again) + oldHand->CancelEvents(&EV_Hand_Ready); + } + + // Clear the hand out + player->hand = NULL; + } + + // Do we care about the old weapon? + if ( trackWeapon ) { + // Save the weapon, break if none. Must save for the restore + oldWeaponNum = player->GetCurrentWeapon(); + if (oldWeaponNum == -1) + { + oldWeaponNum = player->GetIdealWeapon(); + assert(oldWeaponNum != -1); + } + //Calling destructor directly otherwise it doesn't get called until next frame + if( player->weapon.IsValid() ) { + player->weapon->DeconstructScriptObject(); + player->weapon->Hide(); + } + SAFE_REMOVE( player->weapon ); + } + + // See if we have a new weapon to put up + if ( trackWeapon && newWeaponDef && newWeaponDef[0] ) { + player->ForceWeapon( player->GetWeaponNum(newWeaponDef) ); + + // Weapon_Combat() normally takes care of the lowering/raising logic, so force the raise here + player->weapon->Raise(); + player->SetState( "RaiseWeapon" ); + + // Now that we have the weapon, how do we get it there? + if ( weaponTransition ) { + player->weapon->Raise(); + } + else { + player->weapon->ProcessEvent( &EV_Weapon_State, "Idle", 0 ); + } + } + + // Should we put up a new hand? + if ( trackHand && newHandDef && !player->IsDeathWalking() ) { + hhHand *newHand; //! Debug,remove later + + // Create the new hand + newHand = hhHand::AddHand( player.GetEntity(), newHandDef, true ); + + if ( !newHand ) { + gameLocal.Warning( "Error creating new hand for state change" ); + return( 0 ); + } + + // Make sure the weapon is pop'd into place + newHand->HandleWeapon( player.GetEntity(), newHand, 0, true ); + // Now that the hand is up, how do we get it there? + if ( handTransition ) { + player->hand->Raise(); + } + else { + player->hand->Ready(); + } + } + + return( 0 ); +} + + +/* +============ +hhWeaponHandState::RestoreFromArchive +============ +*/ +int hhWeaponHandState::RestoreFromArchive() { + bool setWeapon = false; + + if ( !player.IsValid() ) { + gameLocal.Warning( "Error in hhWeaponHandState::RestoreFromArchive, no player set" ); + return 0; + } + + if ( trackWeapon ) { + //Calling destructor directly otherwise it doesn't get called until next frame + if( player->weapon.IsValid() ) { + player->weapon->DeconstructScriptObject(); + player->weapon->Hide(); + } + SAFE_REMOVE( player->weapon ); + + // Put up the old weapon if there + if ( oldWeaponNum ) { + player->ForceWeapon( oldWeaponNum ); + + // Weapon_Combat() normally takes care of the lowering/raising logic, so force the raise here + player->SetState( "RaiseWeapon" ); + + if ( oldWeaponUp && player->health <= 0 ) { //HUMANHEAD bjk PCF (4-29-06) - fix weapon after shuttle death + // Make sure it is lowered + player->weapon->ProcessEvent( &EV_Weapon_State, "Raise", 0 ); + player->weapon->UpdateScript(); + + player->weapon->Raise(); + } + else { + player->weapon->ProcessEvent( &EV_Weapon_State, "Idle", 0 ); + } + } else { + player->ForceWeapon( -1 ); + } + setWeapon = true; + } + + + if ( trackHand ) { + // Remove the old hand - Need to set owner to NULL so it won't complain + if ( player->hand.IsValid() ) { + player->hand->ForceRemove(); + } + SAFE_REMOVE( player->hand ); + + hhHand *debugHand = oldHand.GetEntity(); + + // Find the first hand that should be there. Note: The 'if' could be made into a 'while' to do multiple levels + if ( oldHand.IsValid() && !oldHand->IsValidFor( player.GetEntity() ) ) { + idEntityPtr delHand; + + //gameLocal.Printf( "%d Removing weapon state hand\n", gameLocal.time ); + + delHand = oldHand; // Save the old hand for deletion + + // Move to the next hand + oldHand = oldHand->GetPreviousHand(); + debugHand = oldHand.GetEntity(); + + // delete the old old hand + delHand->ForceRemove(); + SAFE_REMOVE( delHand ); + } + + player->hand = oldHand; + + // If valid, put it up/have it handle the weapon + if ( oldHand.IsValid() ) { + player->hand->Show(); + + // Make sure the weapon is in the proper state + player->hand->HandleWeapon( player.GetEntity(), + player->hand.GetEntity(), 0, true ); + + // If they want it raised, raise it + if ( oldHandUp ) { + player->hand->Raise(); + } + // Else just pop it in there! + else { + player->hand->Ready(); + } + } + // If no hands in line, then set the weapon straight if we haven't set it already + else if ( !setWeapon ) { + player->weapon->ProcessEvent( &EV_Weapon_State, "Idle", 0 ); + } //. Got a real hand? + } //. We care about the hand + + return( 0 ); +} diff --git a/src/Prey/game_weaponHandState.h b/src/Prey/game_weaponHandState.h new file mode 100644 index 0000000..9d309ea --- /dev/null +++ b/src/Prey/game_weaponHandState.h @@ -0,0 +1,41 @@ + +#ifndef __PREY_GAME_WEAPONHANDSTATE_H__ +#define __PREY_GAME_WEAPONHANDSTATE_H__ + +class hhWeaponHandState : public idClass { + CLASS_PROTOTYPE( hhWeaponHandState ); + + public: + hhWeaponHandState( hhPlayer *player = NULL, + bool trackWeapon = true, int weaponTransition = 0, + bool trackHand = true, int handTransistion = 0 ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - network code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + int Archive( const char *newWeaponDef = NULL, int newWeaponTransition = 0, + const char *newHandDef = NULL, int newHandTransition = 0 ); + + int RestoreFromArchive(); + + void SetPlayer( hhPlayer *player ); + + void SetWeaponTransition( int trans ) { oldWeaponUp = trans; } + + protected: + + idEntityPtr player; + idEntityPtr oldHand; // Previous Hand. NULL if don't care + int oldWeaponNum; + int oldHandUp; + int oldWeaponUp; + + bool trackHand; + bool trackWeapon; +}; + +#endif /* __PREY_GAME_WEAPONHANDSTATE_H__ */ diff --git a/src/Prey/game_woundmanager.cpp b/src/Prey/game_woundmanager.cpp new file mode 100644 index 0000000..9541930 --- /dev/null +++ b/src/Prey/game_woundmanager.cpp @@ -0,0 +1,314 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +// mdl: Only steam this often to keep performance hits when hitting several pipes +#define STEAM_DELAY 1000.0f + + +hhWoundManagerRenderEntity::hhWoundManagerRenderEntity( const idEntity* self ) { + this->self = self; +} + +hhWoundManagerRenderEntity::~hhWoundManagerRenderEntity() { +} + +void hhWoundManagerRenderEntity::DetermineWoundInfo( const trace_t& trace, const idVec3 velocity, jointHandle_t& jointNum, idVec3& origin, idVec3& normal, idVec3& dir ) { + jointNum = CLIPMODEL_ID_TO_JOINT_HANDLE( trace.c.id ); + + origin = trace.c.point; + normal = trace.c.normal; + dir = velocity; + dir.Normalize(); +} + +void hhWoundManagerRenderEntity::AddWounds( const idDeclEntityDef *def, surfTypes_t matterType, jointHandle_t jointNum, const idVec3& origin, const idVec3& normal, const idVec3& dir ) { + PROFILE_SCOPE("AddWounds", PROFMASK_COMBAT); + if( !def ) { + return; + } + + // Do a check so we don't spawn too many steam effects at once + if( matterType == SURFTYPE_PIPE ) { + if ( gameLocal.time > gameLocal.GetSteamTime() ) { + // Mark the current steam time + gameLocal.SetSteamTime( gameLocal.time + STEAM_DELAY ); + } else { + // Don't steam on this hit + matterType = SURFTYPE_METAL; + } + } + + ApplyWound( def->dict, gameLocal.MatterTypeToMatterKey("fx_wound", matterType), jointNum, origin, normal, dir ); //smoke_wound conflicted with smoke + + const char* impactMarkKey = gameLocal.MatterTypeToMatterKey("mtr", matterType); + ApplyMark( def->dict, impactMarkKey, origin, normal, dir ); + ApplySplatExit( def->dict, impactMarkKey, origin, normal, dir ); +} + +void hhWoundManagerRenderEntity::ApplyMark( const idDict& damageDict, const char* impactMarkKey, const idVec3 &origin, const idVec3 &normal, const idVec3 &dir ) { + PROFILE_SCOPE("ApplyMark", PROFMASK_COMBAT); + idList strList; + + const idDict* decalDict = gameLocal.FindEntityDefDict( damageDict.GetString("def_entranceMark_decal"), false ); + if( !decalDict || !self->spawnArgs.GetBool("accepts_decals", "1") ) { + return; + } + + if( !impactMarkKey || !impactMarkKey[0] ) { + return; + } + + idStr impactMark = decalDict->RandomPrefix( impactMarkKey, gameLocal.random ); + if( !impactMark.Length() ) { + return; + } + + if( self->IsType(idBrittleFracture::Type) ) { + static_cast( self.GetEntity() )->ProjectDecal( origin, normal, gameLocal.GetTime(), NULL ); + } else { + hhUtils::SplitString( impactMark, strList ); + + //bjk: uses normal now + float depth = 10.0f; + + for( int ix = strList.Num() - 1; ix >= 0; --ix ) { + gameLocal.ProjectDecal( origin, -normal, depth, true, hhMath::Lerp(decalDict->GetVec2("mark_size"), gameLocal.random.RandomFloat()), strList[ix] ); //HUMANHEAD bjk: using normal + } + } + + //rww - if we were running this code on the server, we could do this: + /* + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + idVec2 markSize; + + msg.Init(msgBuf, sizeof(msgBuf)); + msg.BeginWriting(); + msg.WriteFloat(origin[0]); + msg.WriteFloat(origin[1]); + msg.WriteFloat(origin[2]); + msg.WriteDir(normal, 24); + msg.WriteDir(dir, 24); + + //AGH. rwwFIXME: configstring type system? + msg.WriteString(impactMark); + + if (self->IsType(idBrittleFracture::Type)) + { + msg.WriteBool(true); + msg.WriteShort(self.GetEntity()->entityNumber); + } + else + { + msg.WriteBool(false); + markSize = damageDict->GetVec2("mark_size"); + msg.WriteFloat(markSize[0]); + msg.WriteFloat(markSize[1]); + } + + self.GetEntity()->BroadcastEventReroute(idEntity::EVENT_PROJECT_DECAL, &msg); + */ +} + +void hhWoundManagerRenderEntity::ApplyWound( const idDict& damageDict, const char* woundKey, jointHandle_t jointNum, const idVec3 &origin, const idVec3 &normal, const idVec3 &dir ) { + PROFILE_SCOPE("ApplyWound", PROFMASK_COMBAT); + + if ( !g_bloodEffects.GetBool() ) { + return; + } + + if (! (self->fl.acceptsWounds && woundKey && *woundKey)) { + return; + } + + const idDict* woundDict = gameLocal.FindEntityDefDict( damageDict.GetString("def_entranceWound"), false ); + if( !woundDict ) { + return; + } + + const char* woundName = woundDict->RandomPrefix( woundKey, gameLocal.random ); + if( !woundName || !woundName[0] ) { + return; + } + + // for steam sound + idStr sndKey = "snd_"; + sndKey += woundKey; + const char *sndName = woundDict->GetString(sndKey.c_str()); + bool bHasSound = idStr::Length(sndName) > 0; + + /* // Optimization: works but it could cause problems in some of our non-euclidean spaces if + // a portal destination is less than 1024 units away + if (!bHasSound && !gameLocal.isMultiplayer && gameLocal.GetLocalPlayer()) { + // Don't spawn purely visual wounds when behind player + idVec3 playerOrigin = gameLocal.GetLocalPlayer()->GetOrigin(); + idMat3 playerView = gameLocal.GetLocalPlayer()->GetAxis(); + idVec3 toSpot = origin - playerOrigin; + float dist = toSpot.Normalize(); + + // Only do this optimization when within some distance to avoid problems with the effects being on the + // other side of a portal + if (dist < 1024.0f) { + float dot = toSpot * playerView[0]; + + // If spot is behind me and not right on the border, throw it out + if (dot < 0.0f && dist > 64.0f) { + return; + } + } + } + */ + + hhFxInfo fxInfo; + fxInfo.SetNormal( normal ); + if( !self.GetEntity()->IsType(idStaticEntity::Type) ) { + fxInfo.SetEntity( self.GetEntity() ); + } + fxInfo.RemoveWhenDone( true ); + //correct for server: self->BroadcastFxInfo( woundName, origin, dir.ToMat3(), &fxInfo ); + //however this function should -only- be called on the client. + if (gameLocal.isServer && !gameLocal.GetLocalPlayer()) { + gameLocal.Error("hhWoundManagerRenderEntity::ApplyWound called on non-listen server!"); + } + hhEntityFx *fx = self.GetEntity()->SpawnFxLocal( woundName, origin, normal.ToMat3(), &fxInfo, true ); + if (fx) { + fx->fl.neverDormant = true; + + if (bHasSound) { + const idSoundShader *shader = declManager->FindSound( sndName ); + fx->StartSoundShader(shader, SND_CHANNEL_ANY, 0, true); + } + } +} + +void hhWoundManagerRenderEntity::ApplySplatExit( const idDict& damageDict, const char* impactMarkKey, const idVec3 &origin, const idVec3 &normal, const idVec3 &dir ) { + PROFILE_SCOPE("ApplySplatExit", PROFMASK_COMBAT); + + if ( !g_bloodEffects.GetBool() ) { + return; + } + + if( gameLocal.random.RandomFloat() > 0.8f ) { + return; + } + + const idDict *splatDict = gameLocal.FindEntityDefDict( damageDict.GetString("def_exitSplat"), false ); + if( !splatDict || !self->spawnArgs.GetBool("produces_splats") ) { + return; + } + + if( !impactMarkKey || !impactMarkKey[0] ) { + return; + } + + const char* decal = splatDict->RandomPrefix( impactMarkKey, gameLocal.random ); + if( !decal || !decal[0] ) { + return; + } + + trace_t trace; + if( gameLocal.clip.TracePoint(trace, origin, origin+dir*200.0f, MASK_SHOT_RENDERMODEL, self.GetEntity() ) ) { + + surfTypes_t matterType = gameLocal.GetMatterType( self.GetEntity(), trace.c.material, "ApplySplatExit" ); + + gameLocal.ProjectDecal( trace.endpos, + -trace.c.normal, + splatDict->GetFloat("splat_dist", "10"), + true, + hhMath::Lerp(splatDict->GetVec2("mark_size"), + gameLocal.random.RandomFloat()), decal ); + } +} + +hhWoundManagerAnimatedEntity::hhWoundManagerAnimatedEntity( const idEntity* self ) : hhWoundManagerRenderEntity(self) { +} + +hhWoundManagerAnimatedEntity::~hhWoundManagerAnimatedEntity() { +} + +void hhWoundManagerAnimatedEntity::ApplyMark( const idDict& damageDict, const char* impactMarkKey, const idVec3 &origin, const idVec3 &normal, const idVec3 &dir ) { + idList strList; + + const idDict* overlayDict = gameLocal.FindEntityDefDict( damageDict.GetString("def_entranceMark_overlay"), false ); + if( !overlayDict || !self->spawnArgs.GetBool("accepts_decals", "1") ) { + return; + } + + if( !impactMarkKey && !impactMarkKey[0] ) { + return; + } + + idStr impactMark = overlayDict->RandomPrefix( impactMarkKey, gameLocal.random ); + if( !impactMark.Length() ) { + return; + } + + hhUtils::SplitString( impactMark, strList ); + for( int ix = strList.Num() - 1; ix >= 0; --ix ) { + self->ProjectOverlay( origin, dir, overlayDict->GetFloat("mark_size"), strList[ix] ); + } +} + +void hhWoundManagerAnimatedEntity::ApplyWound( const idDict& damageDict, const char* woundKey, jointHandle_t jointNum, const idVec3 &origin, const idVec3 &normal, const idVec3 &dir ) { + + if (! (self->fl.acceptsWounds && woundKey && *woundKey && jointNum != INVALID_JOINT)) { + return; + } + + const idDict* woundDict = gameLocal.FindEntityDefDict( damageDict.GetString("def_entranceWound"), false ); + if( !woundDict ) { + return; + } + + const char* woundName = woundDict->RandomPrefix( woundKey, gameLocal.random ); + if( !woundName || !woundName[0] ) { + return; + } + +#if !GOLD + //correct for server: self->BroadcastFxInfo( woundName, origin, dir.ToMat3(), &fxInfo ); + //however this function should -only- be called on the client. + if (gameLocal.isServer && !gameLocal.GetLocalPlayer()) { + gameLocal.Error("hhWoundManagerRenderEntity::ApplyWound called on non-listen server!"); + } +#endif + +#if 0 + hhFxInfo fxInfo; + fxInfo.SetNormal( normal ); + fxInfo.SetEntity( self.GetEntity() ); + fxInfo.RemoveWhenDone( true ); + + hhEntityFx* fx = self.GetEntity()->SpawnFxLocal( woundName, origin, normal.ToMat3(), &fxInfo, true ); + if (fx) { + fx->fl.neverDormant = true; + fx->BindToJoint( self.GetEntity(), jointNum, true); + } +#else //rww - do this in some way that is less hideous and slow (specialcased here, cause this function gets a lot of use) + //(avoids binding twice and some other needless logic) + if ( !g_skipFX.GetBool() ) { + idDict fxArgs; + + fxArgs.Set( "fx", woundName ); + fxArgs.SetBool( "start", true ); + fxArgs.SetBool( "removeWhenDone", true ); + fxArgs.SetVector( "origin", origin ); + fxArgs.SetMatrix( "rotation", normal.ToMat3() ); + + hhEntityFx *fx = (hhEntityFx *)gameLocal.SpawnClientObject( "func_fx", &fxArgs ); + if (fx) { + hhFxInfo fxInfo; + fxInfo.SetNormal( normal ); + fxInfo.SetEntity( self.GetEntity() ); + fxInfo.RemoveWhenDone( true ); + fx->SetFxInfo( fxInfo ); + + fx->fl.neverDormant = true; + fx->BindToJoint( self.GetEntity(), jointNum, true ); + fx->Show(); + } + } +#endif +} diff --git a/src/Prey/game_woundmanager.h b/src/Prey/game_woundmanager.h new file mode 100644 index 0000000..e434979 --- /dev/null +++ b/src/Prey/game_woundmanager.h @@ -0,0 +1,34 @@ +#ifndef __HH_WOUND_MANAGER_RENDERENTITY_H +#define __HH_WOUND_MANAGER_RENDERENTITY_H + +class hhWoundManagerRenderEntity { + public: + hhWoundManagerRenderEntity( const idEntity* self ); + virtual ~hhWoundManagerRenderEntity(); + + //Main entry point + void AddWounds( const idDeclEntityDef *def, surfTypes_t matterType, jointHandle_t jointNum, const idVec3& origin, const idVec3& normal, const idVec3& dir ); + virtual void DetermineWoundInfo( const trace_t& trace, const idVec3 velocity, jointHandle_t& jointNum, idVec3& origin, idVec3& normal, idVec3& dir ); + + protected: + virtual void ApplyWound( const idDict& damageDict, const char* woundKey, jointHandle_t jointNum, const idVec3 &origin, const idVec3 &normal, const idVec3 &dir ); + virtual void ApplyMark( const idDict& damageDict, const char* impactMarkKey, const idVec3 &origin, const idVec3 &normal, const idVec3 &dir ); + virtual void ApplySplatExit( const idDict& damageDict, const char* impactMarkKey, const idVec3 &origin, const idVec3 &normal, const idVec3 &dir ); + + protected: + idEntityPtr self; +}; + +class hhWoundManagerAnimatedEntity : public hhWoundManagerRenderEntity { + public: + hhWoundManagerAnimatedEntity( const idEntity* self ); + virtual ~hhWoundManagerAnimatedEntity(); + + protected: + + virtual void ApplyMark( const idDict& damageDict, const char* impactMarkKey, const idVec3 &origin, const idVec3 &normal, const idVec3 &dir ); + virtual void ApplyWound( const idDict& damageDict, const char* woundKey, jointHandle_t jointNum, const idVec3 &origin, const idVec3 &normal, const idVec3 &dir ); + +}; + +#endif \ No newline at end of file diff --git a/src/Prey/game_wraith.cpp b/src/Prey/game_wraith.cpp new file mode 100644 index 0000000..05fa725 --- /dev/null +++ b/src/Prey/game_wraith.cpp @@ -0,0 +1,974 @@ +//***************************************************************************** +//** +//** GAME_WRAITH.CPP +//** +//** Game code for the Wraith creature +//** +//***************************************************************************** + +// HEADER FILES --------------------------------------------------------------- + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +// MACROS --------------------------------------------------------------------- + +#define TWEEN_BANK 1000 + +// TYPES ---------------------------------------------------------------------- + +// CLASS DECLARATIONS --------------------------------------------------------- + +const idEventDef EV_Flee( "", NULL ); +const idEventDef EV_PlayAnimMoveEnd( "", NULL ); +extern const idEventDef EV_SpawnDeathWraith; +extern const idEventDef AI_FindEnemy; + +CLASS_DECLARATION( hhMonsterAI, hhWraith ) + EVENT( EV_Flee, hhWraith::Event_Flee ) + EVENT( EV_PlayAnimMoveEnd, hhWraith::Event_PlayAnimMoveEnd ) + EVENT( AI_FindEnemy, hhWraith::Event_FindEnemy ) + EVENT( EV_Activate, hhWraith::Event_Activate ) +END_CLASS + +//============================================================================= +// +// hhWraith::Spawn +// +//============================================================================= +void hhWraith::Spawn(void) { + // List animations + flyAnim = GetAnimator()->GetAnim( "fly" ); + possessAnim = GetAnimator()->GetAnim( "alert" ); + leftAnim = GetAnimator()->GetAnim( "bankLeft" ); + rightAnim = GetAnimator()->GetAnim( "bankRight" ); + fleeAnim = GetAnimator()->GetAnim( "flee" ); + fleeInAnim = GetAnimator()->GetAnim( "fleeIn" ); + + lastAnim = NULL; + + canPossess = spawnArgs.GetBool( "possess", "1" ); + + // Physics and collision information + fl.takedamage = true; + + minDamageDist = spawnArgs.GetFloat( "minDamageDist", "50" ); + + // Initialize flight logic + straightTicks = 0; + damageTicks = 0; + turnTicks = 0; + + isScaling = false; + + // Initially launch the wraith forward + velocity = GetPhysics()->GetAxis()[0] * 100; + + GetAnimator()->ClearAllAnims( gameLocal.time, 0 ); + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, flyAnim, gameLocal.time, 0 ); + + BecomeActive( TH_THINK ); + fl.neverDormant = true; + + nextCheckTime = gameLocal.time + spawnArgs.GetInt("trace_check_time", "200"); + lastCheckOrigin = GetPhysics()->GetOrigin(); + lastDamageTime = spawnArgs.GetFloat( "damageDelay", "0.5" ); + + if ( spawnArgs.GetBool( "spawnFlyUp", "0" ) ) { + state = WS_SPAWN; + + // Orient the wraith pointing upwards + desiredAxis[0] = idVec3( 0, 0, 1 ); + desiredAxis[1] = idVec3( 1, 0, 0 ); + desiredAxis[2] = idVec3( 0, -1, 0 ); + + // Set the speed of this movement + desiredVelocity = spawnArgs.GetFloat( "initialVelocity", "10" ); // TODO: Make this random + + // Calculate how far the wraith should fly and how long that should take + // NOTE: If we make the velocity a random range, then we can make this spawn time constant + countDownTimer = spawnArgs.GetFloat( "spawnTime", "0.5" ) * 60; // Half a second to raise up + } else { + state = WS_FLY; + } + + // Initialize .def values + velocity_xy = spawnArgs.GetFloat( "velocity_xy", "7" ) * (60.0f * USERCMD_ONE_OVER_HZ); + velocity_z = spawnArgs.GetFloat( "velocity_z", "1" ) * (60.0f * USERCMD_ONE_OVER_HZ); + velocity_z_fast = spawnArgs.GetFloat( "velocity_z_fast", "5" ) * (60.0f * USERCMD_ONE_OVER_HZ); + dist_z_close = spawnArgs.GetFloat( "dist_z_close", "5" ); + dist_z_far = spawnArgs.GetFloat( "dist_z_far", "100" ); + + turn_threshold = DEG2RAD( spawnArgs.GetFloat( "turn_threshold", "5" ) ); // CJR PCF 5/17/06: Removed unnecessary 30Hz compensation + turn_radius_max = DEG2RAD( spawnArgs.GetFloat( "turn_radius_max", "130" ) ); // CJR PCF 5/17/06: Remove unnecessary 30Hz compensation + + straight_ticks = spawnArgs.GetInt( "straight_ticks", "30" ) * (USERCMD_HZ / 60.0f); + damage_ticks = spawnArgs.GetFloat( "damage_ticks", "8" ) * (USERCMD_HZ / 60.0f); + turn_ticks = spawnArgs.GetFloat( "turn_ticks", "200" ) * (USERCMD_HZ / 60.0f); + + flee_speed_z = spawnArgs.GetFloat( "flee_speed_z", "10" ) * (60.0f * USERCMD_ONE_OVER_HZ); + + target_z_threshold = spawnArgs.GetFloat( "target_z_threshold", "20" ); + + // JRM: are we waiting for a trigger? Then no sound. + if(!spawnArgs.GetBool("trigger","0")) { + StartSound( "snd_flyloop", SND_CHANNEL_BODY ); + StartSound( "snd_sight", SND_CHANNEL_VOICE ); + + // If this wraith should flee when spawned, then kill it immediately and get it fleeing + // This is used by the possessed kids to spawn a wraith that flees immediately + if ( spawnArgs.GetBool("flee_at_spawn", "0" ) ) { + // Fleeing + Killed( this, this, 0, vec3_origin, 0 ); + + isScaling = true; + scaleStart = spawnArgs.GetFloat( "scaleStart", "1" ); + scaleEnd = spawnArgs.GetFloat( "scaleEnd", "1" ); + scaleTime = spawnArgs.GetFloat( "scaleTime", "1" ); + + // Set data for dynamically scaling the wraith + SetShaderParm( SHADERPARM_ANY_DEFORM, DEFORMTYPE_SCALE ); // Scale deform + SetShaderParm( SHADERPARM_ANY_DEFORM_PARM1, scaleStart ); // Scale deform + lastScaleTime = MS2SEC( gameLocal.time ) + scaleTime; + } + } + + Event_SetMoveType(MOVETYPE_FLY); // JRM to get the wraiths to move again + + GetPhysics()->SetContents( CONTENTS_SHOOTABLE|CONTENTS_SHOOTABLEBYARROW ); + GetPhysics()->SetClipMask( 0 ); + + bFaceEnemy = false; + nextDrop = 0; + nextChatter = 0; + nextPossessTime = 0; + + minChatter = SEC2MS(spawnArgs.GetFloat( "min_chatter_time", "3" )); + maxChatter = SEC2MS(spawnArgs.GetFloat( "max_chatter_time", "6" )); + + if ( !IsHidden() ) { + Hide(); + PostEventMS( &EV_Activate, 0, this ); + } +} + +//============================================================================= +// +// hhWraith::~hhWraith +// +//============================================================================= +hhWraith::~hhWraith() { + StopSound( SND_CHANNEL_BODY ); +} + +//============================================================================= +// +// hhWraith::Save +// +//============================================================================= +void hhWraith::Save( idSaveGame *savefile ) const { + savefile->WriteInt( lastAnim ); + savefile->WriteBool( canPossess ); + savefile->WriteVec3( velocity ); + savefile->WriteInt( damageTicks ); + savefile->WriteInt( straightTicks ); + savefile->WriteInt( turnTicks ); + savefile->WriteVec3( lastCheckOrigin ); + savefile->WriteFloat( lastDamageTime ); + savefile->WriteInt( state ); + savefile->WriteFloat( velocity_xy ); + savefile->WriteFloat( velocity_z ); + savefile->WriteFloat( velocity_z_fast ); + savefile->WriteFloat( dist_z_close ); + savefile->WriteFloat( dist_z_far ); + savefile->WriteFloat( turn_threshold ); + savefile->WriteFloat( turn_radius_max ); + savefile->WriteInt( straight_ticks ); + savefile->WriteInt( damage_ticks ); + savefile->WriteInt( turn_ticks ); + savefile->WriteFloat( flee_speed_z ); + savefile->WriteFloat( target_z_threshold ); + savefile->WriteFloat( minDamageDist ); + savefile->WriteBool( isScaling ); + savefile->WriteFloat( scaleStart ); + savefile->WriteFloat( scaleEnd ); + savefile->WriteFloat( lastScaleTime ); + savefile->WriteInt( countDownTimer ); + savefile->WriteMat3( desiredAxis ); + savefile->WriteFloat( desiredVelocity ); + savefile->WriteBool( bFaceEnemy ); + fxFly.Save( savefile ); +} + +//============================================================================= +// +// hhWraith::Restore +// +//============================================================================= +void hhWraith::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( lastAnim ); + savefile->ReadBool( canPossess ); + savefile->ReadVec3( velocity ); + savefile->ReadInt( damageTicks ); + savefile->ReadInt( straightTicks ); + savefile->ReadInt( turnTicks ); + savefile->ReadVec3( lastCheckOrigin ); + savefile->ReadFloat( lastDamageTime ); + savefile->ReadInt( (int &)state ); + savefile->ReadFloat( velocity_xy ); + savefile->ReadFloat( velocity_z ); + savefile->ReadFloat( velocity_z_fast ); + savefile->ReadFloat( dist_z_close ); + savefile->ReadFloat( dist_z_far ); + savefile->ReadFloat( turn_threshold ); + savefile->ReadFloat( turn_radius_max ); + savefile->ReadInt( straight_ticks ); + savefile->ReadInt( damage_ticks ); + savefile->ReadInt( turn_ticks ); + savefile->ReadFloat( flee_speed_z ); + savefile->ReadFloat( target_z_threshold ); + savefile->ReadFloat( minDamageDist ); + savefile->ReadBool( isScaling ); + savefile->ReadFloat( scaleStart ); + savefile->ReadFloat( scaleEnd ); + savefile->ReadFloat( lastScaleTime ); + savefile->ReadInt( countDownTimer ); + savefile->ReadMat3( desiredAxis ); + savefile->ReadFloat( desiredVelocity ); + savefile->ReadBool( bFaceEnemy ); + + // List animations + flyAnim = GetAnimator()->GetAnim( "fly" ); + possessAnim = GetAnimator()->GetAnim( "alert" ); + leftAnim = GetAnimator()->GetAnim( "bankLeft" ); + rightAnim = GetAnimator()->GetAnim( "bankRight" ); + fleeAnim = GetAnimator()->GetAnim( "flee" ); + fleeInAnim = GetAnimator()->GetAnim( "fleeIn" ); + + canPossess = spawnArgs.GetBool( "possess", "1" ); + + minDamageDist = spawnArgs.GetFloat( "minDamageDist", "50" ); + + // Initialize .def values + velocity_xy = spawnArgs.GetFloat( "velocity_xy", "7" ); + velocity_z = spawnArgs.GetFloat( "velocity_z", "1" ); + velocity_z_fast = spawnArgs.GetFloat( "velocity_z_fast", "5" ); + dist_z_close = spawnArgs.GetFloat( "dist_z_close", "5" ); + dist_z_far = spawnArgs.GetFloat( "dist_z_far", "100" ); + + turn_threshold = DEG2RAD( spawnArgs.GetFloat( "turn_threshold", "5" ) ); + turn_radius_max = DEG2RAD( spawnArgs.GetFloat( "turn_radius_max", "130" ) ); + + straight_ticks = spawnArgs.GetInt( "straight_ticks", "30" ) * (60.0f * USERCMD_ONE_OVER_HZ); + damage_ticks = spawnArgs.GetFloat( "damage_ticks", "8" ) * (60.0f * USERCMD_ONE_OVER_HZ); + turn_ticks = spawnArgs.GetFloat( "turn_ticks", "200" ) * (60.0f * USERCMD_ONE_OVER_HZ); + + flee_speed_z = spawnArgs.GetFloat( "flee_speed_z", "10" ); + + target_z_threshold = spawnArgs.GetFloat( "target_z_threshold", "20" ); + + nextDrop = 0; + nextChatter = 0; + nextPossessTime = gameLocal.time + 500; // Don't possess for half a second after loading + minChatter = SEC2MS(spawnArgs.GetFloat( "min_chatter_time", "3" )); + maxChatter = SEC2MS(spawnArgs.GetFloat( "max_chatter_time", "6" )); + + fxFly.Restore( savefile ); + + nextCheckTime = gameLocal.time + spawnArgs.GetInt("trace_check_time", "200"); + scaleTime = spawnArgs.GetFloat( "scaleTime", "1" ); +} + +//============================================================================= +// +// hhWraith::Think +// +//============================================================================= +void hhWraith::Think(void) { + if(!IsHidden()) { // JRM - NO TRACES WHEN HIDDEN! + CheckCollisions(); + + if (gameLocal.time > nextDrop) { + hhMonsterAI::Damage(this, this, vec3_origin, "damage_wraithminion", 0.05f, INVALID_JOINT); + nextDrop = gameLocal.time + 1000; + } + + if (gameLocal.time > nextChatter) { + StartSound( "snd_chatter", SND_CHANNEL_VOICE ); + nextChatter = gameLocal.time + (minChatter + gameLocal.random.RandomFloat() * (maxChatter - minChatter)); + } + } + + // Scale the wraith + if ( isScaling ) { + float delta = lastScaleTime - MS2SEC( gameLocal.time ); + if ( delta > 0 ) { + SetShaderParm( SHADERPARM_ANY_DEFORM_PARM1, hhMath::Lerp( scaleEnd, scaleStart, delta / scaleTime ) ); + } + } + + hhMonsterAI::Think(); // JRM: Bypassing hhAI::Think() +} + +//============================================================================= +// +// hhWraith::Event_Activate +// +//============================================================================= +void hhWraith::Event_Activate(idEntity *activator) { + if ( IsHidden() ) { + TeleportIn(activator); + return; + } + if ( spawnArgs.GetBool("flee_at_spawn", "0" ) ) { + // Fleeing + Killed( this, this, 0, vec3_origin, 0 ); + + isScaling = true; + scaleStart = spawnArgs.GetFloat( "scaleStart", "1" ); + scaleEnd = spawnArgs.GetFloat( "scaleEnd", "1" ); + scaleTime = spawnArgs.GetFloat( "scaleTime", "1" ); + + // Set data for dynamically scaling the wraith + SetShaderParm( SHADERPARM_ANY_DEFORM, DEFORMTYPE_SCALE ); // Scale deform + SetShaderParm( SHADERPARM_ANY_DEFORM_PARM1, scaleStart ); // Scale deform + lastScaleTime = MS2SEC( gameLocal.time ) + scaleTime; + } + + hhMonsterAI::Event_Activate(activator); + + // we've been waiting for a trigger + if (spawnArgs.GetBool("trigger")) { + StartSound( "snd_flyloop", SND_CHANNEL_BODY ); + StartSound( "snd_sight", SND_CHANNEL_VOICE ); + } + + + const char *defName = spawnArgs.GetString("fx_fly"); + if (defName && defName[0]) { + hhFxInfo fxInfo; + + fxInfo.SetNormal( -GetAxis()[0] ); + fxInfo.SetEntity( this ); + fxInfo.RemoveWhenDone( false ); + fxFly = SpawnFxLocal( defName, GetOrigin(), GetAxis(), &fxInfo, gameLocal.isClient ); + if (fxFly.IsValid()) { + fxFly->fl.neverDormant = true; + + fxFly->fl.networkSync = false; + fxFly->fl.clientEvents = true; + } + } +} + +//============================================================================= +// +// hhWraith::FindEnemy +// +//============================================================================= +void hhWraith::Event_FindEnemy( int useFOV ) { + int i; + idEntity *ent; + idActor *closest; + idActor *actor; + float closestDist; + float distSquared; + + closest = NULL; + closestDist = 99999999.0f; + + if (team != 0) { // Search players + for ( i = 0; i < gameLocal.numClients ; i++ ) { + ent = gameLocal.entities[ i ]; + + if ( !ent || !ent->IsType( idActor::Type ) ) { + continue; + } + + if ( ent->IsType( hhPlayer::Type ) ) { // Check if the player is really dead or deathwalking + hhPlayer *player = static_cast< hhPlayer * >( ent ); + if ( player->IsDead() || player->IsDeathWalking() ) { + continue; + } + } + + // Find the closest enemy + distSquared = ( GetOrigin() - ent->GetOrigin() ).LengthSqr(); + if ( distSquared < closestDist ) { + closestDist = distSquared; + closest = static_cast( ent ); + } + } + } + else { // Search monsters + int numMonsters = hhMonsterAI::allSimpleMonsters.Num(); + for ( i = 0; i < numMonsters ; i++ ) { + actor = hhMonsterAI::allSimpleMonsters[ i ]; + + if ( !actor || actor==this ) { + continue; + } + + // Ignore dormant entities! + if (actor->IsHidden() || actor->fl.isDormant) { + continue; + } + + if ( (actor->health <= 0) || !(ReactionTo(actor) & ATTACK_ON_SIGHT) ) { + continue; + } + + distSquared = (actor->GetOrigin() - GetOrigin()).LengthSqr(); + if ( distSquared < closestDist ) { + closestDist = distSquared; + closest = actor; + } + } + } + + if (closest) { + SetEnemy( closest ); + } + else { + // FIXME: No enemies found, should we just go away or circle for a while? + Damage(this, this, vec3_origin, "damage_suicide", 1.0f, INVALID_JOINT); + } + + idThread::ReturnEntity( closest ); +} + +//============================================================================= +// +// hhWraith::UpdateEnemyPosition +// +// This is here to override the default UpdateEnemyPosition, which +// runs code that is unnecessary for the wraiths +//============================================================================= +void hhWraith::UpdateEnemyPosition( void ) { + // Do nothing. Wraith's don't need to check if the enemy is no longer visible, since they can go through walls +} + +//============================================================================= +// +// hhWraith::EnemyDead +// +//============================================================================= +void hhWraith::EnemyDead() { + if ( !enemy.IsValid() || !enemy->IsType( hhPlayer::Type ) ) { // Only consider the enemy dead if the enemy is not a player + hhMonsterAI::EnemyDead(); + } else if ( enemy.IsValid() && enemy->IsType( hhPlayer::Type ) ) { // If the player is deathwalking, then the enemy is dead + hhPlayer *player = static_cast< hhPlayer * >( enemy.GetEntity() ); + if ( player->IsDead() || player->IsDeathWalking() ) { + hhMonsterAI::EnemyDead(); + } + } +} + +//============================================================================= +// +// hhWraith::FlyMove +// +//============================================================================= +void hhWraith::FlyMove( void ) { + // The state of the creature determines how it will move + switch ( state ) { + case WS_SPAWN: + FlyUp(); // Flying up right after spawn + break; + case WS_FLY: + FlyToEnemy(); + break; + case WS_FLEE: // Flying away + FlyAway(); + break; +// case WS_POSSESS_CHARGE: +// WraithDamageEnemy(); +// break; + case WS_STILL: // Wraith is not moving at all (playing a specific anim, etc) + return; + } + + // run the physics for this frame + physicsObj.UseFlyMove( true ); + physicsObj.UseVelocityMove( false ); + physicsObj.SetDelta( vec3_zero ); + physicsObj.ForceDeltaMove( disableGravity ); + RunPhysics(); +} + +//============================================================================= +// +// hhWraith::FlyToEnemy +// +//============================================================================= +void hhWraith::FlyToEnemy( void ) { + float delta = 0; + bool dir = false; + int anim; + + if ( !enemy.IsValid() ) { + PostEventMS( &AI_FindEnemy, 1, 0 ); + return; + } + + if (canPossess) { + // Determine which direction the wraith needs to turn to the enemy (if the wraith should turn) + if ( --straightTicks <= 0 ) { + dir = GetFacePosAngle( enemy->GetOrigin(), delta ); + + turnTicks++; // Add in the number of ticks that the wraith has been turning + // If the wraith has been turning for a very long period of time (more than 4 seconds), then force the wraith to go straight + + // Vary turn_ticks by +/- 25 depending on the DDA value + if ( turnTicks > this->turn_ticks - (25 * ((gameLocal.GetDDAValue() - 0.5f) * 2))) { + dir = 0; + delta = 0; + turnTicks = 0; + deltaViewAngles.yaw = 0; + } + } else { + dir = 0; + delta = 0; + deltaViewAngles.yaw = 0; + turnTicks = 0; + } + } + + // Turn the wraith, and set the correct bank animation + anim = flyAnim; + if ( delta > this->turn_threshold ) { // Wraith should turn + if ( dir ) { // Turn to the left + deltaViewAngles.yaw = this->turn_radius_max; + anim = leftAnim; + } else { + deltaViewAngles.yaw = -this->turn_radius_max; + anim = rightAnim; + } + } else if ( straightTicks <= 0 ) { // Straight at the player + deltaViewAngles.yaw = 0; + // Vary straight_ticks by +/- 25 depending on the DDA value + straightTicks = this->straight_ticks - (25 * ((gameLocal.GetDDAValue() - 0.5f) * 2)); // Stay on this path for a short period of time + } + + // Actually set the animation + if ( anim != lastAnim ) { + GetAnimator()->ClearAllAnims( gameLocal.time, TWEEN_BANK ); + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, TWEEN_BANK ); + lastAnim = anim; + } + + velocity = viewAxis[0] * this->velocity_xy; + if ( damageTicks > 0 ) { // Fly slower if damaged + velocity *= 0.85f; + velocity += 10 * idVec3( gameLocal.random.CRandomFloat(), gameLocal.random.CRandomFloat(), gameLocal.random.CRandomFloat() ); + + damageTicks--; + } + + // Equalize the z-height of the wraith relative to its target + idVec3 enemyOrigin = enemy->GetEyePosition(); + idVec3 origin = GetPhysics()->GetOrigin(); + + // Factor in fly_offset + enemyOrigin.z += fly_offset; + + // Adjust z-velocity based upon the delta Z to target + velocity.z = 0; + if( ( origin.z > enemyOrigin.z + this->target_z_threshold ) || ( origin.z < enemyOrigin.z - this->target_z_threshold ) ) { + float newZ = enemyOrigin.z + target_z_threshold * 0.5f; + float deltaZ = newZ - origin.z; + if ( abs( deltaZ ) > this->dist_z_far ) { + velocity.z = this->velocity_z_fast * ( ( deltaZ > 0 ) ? 1 : -1 ); + } else if ( abs( deltaZ ) > this->dist_z_close ) { + velocity.z = this->velocity_z * ( ( deltaZ > 0 ) ? 1 : -1 ); + } + } + + // 1.5x the velocity if DDA is 1.0, half it for DDA = 0.0, leave it the same for normal (DDA = 0.5) + GetPhysics()->SetOrigin( GetPhysics()->GetOrigin() + (velocity * (gameLocal.GetDDAValue() + 0.5f))); + + UpdateVisuals(); +} + +//============================================================================= +// +// hhWraith::FlyAway +// +// Wraiths will automatically fly away from their enemy and up +// The Wraith will eventually be removed from the world in the CheckFleeRemove function +//============================================================================= +void hhWraith::FlyAway( void ) { + + velocity.x = 0; + velocity.y = 0; + velocity.z = this->flee_speed_z; + + GetPhysics()->SetOrigin( GetPhysics()->GetOrigin() + velocity ); + + UpdateVisuals(); + + // Check to see if the wraith has flown out of the world + CheckFleeRemove(); +} + +//============================================================================= +// +// hhWraith::FlyUp +// +// mdl: Wraiths will automatically fly up after being spawned if spawnFlyUp is set to 1 +// NOT related to flying away during death, that's FlyAway() +//============================================================================= +void hhWraith::FlyUp( void ) { + idVec3 velocity; + + // Turn towards desired angle + // TODO: Make this smooth + SetAxis( desiredAxis ); + + // Apply velocity + velocity = desiredAxis[0] * desiredVelocity; + SetOrigin( GetOrigin() + velocity ); + + UpdateVisuals(); + + // If the wraith is near the end of the spawn, then smoothly rotate the wraith to the horizontal + if ( --countDownTimer <= 0 ) { + state = WS_FLY; + } +} + +//============================================================================= +// +// hhWraith::TurnTowardEnemy +// +//============================================================================= +void hhWraith::TurnTowardEnemy() { + float delta; + bool dir; + + if ( !enemy.IsValid() ) { + return; + } + + // Face torward the enemy + dir = GetFacePosAngle( enemy->GetOrigin(), delta ); + + if ( delta > turn_threshold ) { + // Turn based upon delta angles + if ( dir ) { // Turn to the right + deltaViewAngles.yaw = turn_radius_max; + } else { + deltaViewAngles.yaw = -turn_radius_max; + } + } + + UpdateVisuals(); +} + +//============================================================================= +// +// hhWraith::CheckFleeRemove +// +//============================================================================= +void hhWraith::CheckFleeRemove( void ) { + // Check if the Wraith has flown out of the world + if( gameLocal.clip.Contents( GetPhysics()->GetOrigin() + idVec3(0, 0, 48), GetPhysics()->GetClipModel(), viewAxis, CONTENTS_SOLID, this ) ) { + Hide(); + PostEventSec( &EV_Remove, 2.0f ); // Keep the wraith around for a few seconds so the sounds can finish + state = WS_STILL; // No reason to keep moving the wraith + } +} + +//============================================================================= +// +// hhWraith::Damage +// +//============================================================================= +void hhWraith::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + hhMonsterAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + damageTicks = this->damage_ticks; +} + +//============================================================================= +// +// hhWraith::Killed +// +//============================================================================= +void hhWraith::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + hhFxInfo fx; + //fx.SetEntity( this ); + fx.RemoveWhenDone( true ); + const char *deathFX; + + // Stop flyloop and chatter sounds, if any + StopSound( SND_CHANNEL_BODY ); + StopSound( SND_CHANNEL_VOICE ); + + if ( inflictor == this ) { + // If we timed out, don't drop a soul + spawnArgs.Delete( "def_dropSoul" ); + deathFX = spawnArgs.GetString( "fx_death" ); + } else { + // Use alternate death fx if we were killed by the player + deathFX = spawnArgs.GetString( "fx_death2" ); + } + + SpawnFxLocal( deathFX, GetOrigin(), GetAxis(), &fx ); + + SAFE_REMOVE(fxFly); + + hhMonsterAI::Killed( inflictor, attacker, damage, dir, location ); + + StartSound( "snd_death", SND_CHANNEL_VOICE ); + + Hide(); + + // activate targets + //ActivateTargets( this ); + + PostEventSec( &EV_Remove, 4 ); +} + +//============================================================================= +// +// hhWraith::CheckCollisions +// +// Check if the wraith is entering or exiting the world +//============================================================================= +void hhWraith::CheckCollisions( void ) { + + if (gameLocal.time < nextCheckTime) { + return; + } + + hhFxInfo fxInfo; + trace_t tr1; + trace_t tr2; + idVec3 jointPos; + idMat3 jointAxis; + + if (!GetJointWorldTransform( spawnArgs.GetString("joint_collision"), jointPos, jointAxis )) { + jointPos = GetOrigin(); + jointAxis = GetAxis(); + } + + // Trace to determine if the wraith just flew through a wall, or just emerged from a wall + // For speed, this only checks against the world + idVec3 dir, point; + gameLocal.clip.TracePoint( tr1, jointPos, lastCheckOrigin, MASK_SHOT_BOUNDINGBOX, this ); // Check if we entered something solid + gameLocal.clip.TracePoint( tr2, lastCheckOrigin, jointPos, MASK_SHOT_BOUNDINGBOX, this ); // Check if we exited something solid + + idVec3 playerOrigin = gameLocal.GetLocalPlayer()->GetOrigin(); + if ( tr1.fraction < 1.0f ) { + point = tr1.c.point; + dir = tr1.c.normal; + } + else if ( tr2.fraction < 1.0f) { + point = tr2.c.point; + dir = tr2.c.normal; + } + if ( tr1.fraction < 1.0f && tr2.fraction < 1.0f ) { + if ( (tr1.c.point - playerOrigin).LengthSqr() <= (tr2.c.point - playerOrigin).LengthSqr() ) { + point = tr1.c.point; + dir = tr1.c.normal; + } + else { + point = tr2.c.point; + dir = tr2.c.normal; + } + } + + if ( tr1.fraction < 1.0f || tr2.fraction < 1.0f ) { + // Spawn an FX at wall collision + //TODO: IMPORTANT: This currently spawns a system every check while passing through something. This creates enormous + // amounts of particles. Fix so there is a single system spawned on the way in and a single system spawned on the way out. + //TODO: This currently hits players so you get a big blob of particles right in your face and right behind you. + const char *hitWallFxName = spawnArgs.GetString("fx_hitwall"); + if (hitWallFxName && *hitWallFxName) { + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfo(hitWallFxName, point, mat3_identity, &fxInfo); + } + + + // Project a spooge decal on the wall here + const char *decalName = spawnArgs.GetString("mtr_decal", NULL); + if (decalName && *decalName) { + float depth = spawnArgs.GetFloat("decal_trace"); + float size = spawnArgs.GetFloat( "decal_size" ); + +// gameRenderWorld->DebugArrow(colorRed, point, point + dir*128, 5, 10000); + + gameLocal.ProjectDecal(tr1.c.point, -tr1.c.normal, depth, true, size, decalName); + gameLocal.ProjectDecal(tr2.c.point, -tr2.c.normal, depth, true, size, decalName); + } + } + + // Possession check + if ( canPossess && gameLocal.time > nextPossessTime ) { + gameLocal.clip.TraceBounds( tr1, lastCheckOrigin, jointPos, GetPhysics()->GetBounds(), MASK_SHOT_BOUNDINGBOX, this ); + if ( gameLocal.entities[ tr1.c.entityNum ] && gameLocal.entities[ tr1.c.entityNum ]->IsType( idActor::Type ) ) { + // If we hit a possessable actor, possess them + idActor *actor = reinterpret_cast ( gameLocal.entities[ tr1.c.entityNum ] ); + if ( actor != lastActor.GetEntity() ) { + // Play the attack sound + StartSound( "snd_attack", SND_CHANNEL_VOICE ); + if ( actor->IsType( hhPlayer::Type ) ) { + hhPlayer *player = reinterpret_cast ( gameLocal.entities[ tr1.c.entityNum ] ); + int power = player->GetSpiritPower(); + power -= 25; + + if ( power <= 0 ) { // Possess the player + power = 0; + // mdl: Wraiths no longer possess + //if ( player->CanBePossessed() ) { + // Possessable + //WraithPossess( player ); + //} else + if ( enemy.GetEntity() == player && player->IsSpiritWalking() && !player->IsPossessed() ) { + // Send the player back to his body + reinterpret_cast (enemy.GetEntity())->DisableSpiritWalk(5); + // Don't immediately possess if the players body happens to be right there. + nextPossessTime = gameLocal.time + 1000; + } + } else { // Gain health from the player's spirit power + health += 5; + if ( health > spawnHealth ) { + health = spawnHealth; + } + } + player->SetSpiritPower( power ); + + }// else if ( actor->CanBePossessed() ) { + // // Possess the actor + // WraithPossess( actor ); + //} + } + lastActor = actor; + } else { + lastActor = NULL; + } + } + + nextCheckTime = gameLocal.time + spawnArgs.GetInt("trace_check_time", "200"); + lastCheckOrigin = jointPos; +} + +//============================================================================= +// +// hhWraith::Event_Flee +// +// The wraith is about to flee and has already played the flee intro anim +//============================================================================= +void hhWraith::Event_Flee( ) { + GetAnimator()->ClearAllAnims( gameLocal.time, 0 ); + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, fleeAnim, gameLocal.time, 0 ); + state = WS_FLEE; +} + +//============================================================================= +// +// hhWraith::PlayAnimMove +// +//============================================================================= +void hhWraith::PlayAnimMove( int anim, int blendTime ) { + GetAnimator()->ClearAllAnims( gameLocal.time, blendTime ); + GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, blendTime ); + state = WS_STILL; + PostEventMS( &EV_PlayAnimMoveEnd, GetAnimator()->GetAnim( possessAnim )->Length() - blendTime ); +} + +//============================================================================= +// +// hhWraith::PlayAnimMoveEnd +// +//============================================================================= +void hhWraith::PlayAnimMoveEnd() { + idVec3 boneOrigin; + idMat3 boneAxis; + GetJointWorldTransform( "Head", boneOrigin, boneAxis ); + + GetPhysics()->SetOrigin( boneOrigin ); // Move the wraith to the end of the animation + + GetAnimator()->ClearAllAnims( gameLocal.time, 0 ); + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, flyAnim, gameLocal.time, 0 ); + state = WS_FLY; + + damageTicks = 0; +} + +//============================================================================= +// +// hhWraith::WraithPossess +// +// Actually possess an actor +//============================================================================= +void hhWraith::WraithPossess( idActor *actor ) { + hhFxInfo fxInfo; + + actor->Possess( this ); + + // Spawn in a flash + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfoPrefixed( "fx_possessionFlash", GetOrigin(), GetAxis(), &fxInfo ); + + // activate targets + ActivateTargets( actor ); + + PostEventMS( &EV_Remove, 0 ); +} + + +//============================================================================= +// +// hhWraith::Event_PlayAnimMoveEnd +// +//============================================================================= +void hhWraith::Event_PlayAnimMoveEnd( ) { + PlayAnimMoveEnd(); +} + +void hhWraith::Event_EnemyIsSpirit( hhPlayer *player, hhSpiritProxy *proxy ) { + // Only switch to the proxy if the player has no spirit power + //if ( player->GetSpiritPower() == 0 ) { + // hhMonsterAI::Event_EnemyIsSpirit( player, proxy ); + //} +} + +void hhWraith::Event_EnemyIsPhysical( hhPlayer *player, hhSpiritProxy *proxy ) { + // We don't care whether we can see the enemy or not + enemy = player; +} + +void hhWraith::TeleportIn( idEntity *activator ) { + if ( spawnArgs.GetBool( "quickSpawn" ) ) { + PostEventMS( &EV_Show, 0 ); + PostEventMS( &EV_Activate, 50, activator ); + } + else { + hhFxInfo fx; + fx.RemoveWhenDone( true ); + fx.SetEntity( this ); + SpawnFxLocal( spawnArgs.GetString( "fx_spawn" ), GetOrigin(), GetAxis(), &fx ); + PostEventMS( &EV_Show, 1000 ); + PostEventMS( &EV_Activate, 1050, activator ); + } +} + +void hhWraith::Portalled(idEntity *portal) { + hhMonsterAI::Portalled( portal ); + if ( fxFly.IsValid() ) { + hhFxInfo fxInfo; + fxInfo.SetNormal( -GetAxis()[0] ); + fxInfo.SetEntity( this ); + fxInfo.RemoveWhenDone( false ); + + fxFly->SetFxInfo( fxInfo ); + + // Reset the fx system + fxFly->Stop(); + fxFly->Start( gameLocal.time ); + } +} + diff --git a/src/Prey/game_wraith.h b/src/Prey/game_wraith.h new file mode 100644 index 0000000..7a6f657 --- /dev/null +++ b/src/Prey/game_wraith.h @@ -0,0 +1,128 @@ + +#ifndef __GAME_WRAITH_H__ +#define __GAME_WRAITH_H__ + +typedef enum wraithState_s { + WS_SPAWN = 0, + WS_FLY, + WS_FLEE, + WS_POSSESS_CHARGE, + WS_DEATH_CHARGE, + WS_STILL +} wraithState_t; + +class hhWraith : public hhMonsterAI { + public: + CLASS_PROTOTYPE(hhWraith); + + void Spawn(void); + virtual ~hhWraith(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void UpdateEnemyPosition( 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 Think( void ); + + virtual int HasAmmo( ammo_t type, int amount ) { return 0; } + virtual bool UseAmmo( ammo_t type, int amount ) { return false ; } + + protected: + virtual void EnemyDead(); + + // State flight functions + virtual void FlyUp( void ); + virtual void FlyMove( void ); + virtual void FlyToEnemy( void ); + virtual void FlyAway( void ); + + void CheckFleeRemove( void ); + void WraithPossess( idActor *actor ); + void CheckCollisions( void ); + + void PlayAnimMove( int anim, int blendTime ); + void PlayAnimMoveEnd(); + void TurnTowardEnemy(); + + void Event_FindEnemy( int useFOV ); + void Event_TurnTowardEnemy(); + void Event_Flee(); + void Event_PlayAnimMoveEnd( ); + virtual void Event_Activate(idEntity *activator); + + virtual void Event_EnemyIsSpirit( hhPlayer *player, hhSpiritProxy *proxy ); + virtual void Event_EnemyIsPhysical( hhPlayer *player, hhSpiritProxy *proxy ); + + virtual void TeleportIn( idEntity *activator ); + virtual void StartDisposeCountdown() { } // Doesn't apply to wraiths + + virtual void Portalled(idEntity *portal); + + protected: + // Animations + int flyAnim; + int possessAnim; + int leftAnim; + int rightAnim; + int fleeAnim; + int fleeInAnim; + + int lastAnim; // Used in turning flight logic + + // Variables + bool canPossess; + + idVec3 velocity; + + int damageTicks; // # of ticks to act damaged (fly slower, flash red) + int straightTicks; // # of ticks to fly straight + int turnTicks; // # if ticks when turning before the wraith will be forced to fly straight + + int nextCheckTime; + idVec3 lastCheckOrigin; + float lastDamageTime; // used to delay the damage check after applying damage + + wraithState_t state; + + // .def file variables + float velocity_xy; + float velocity_z; + float velocity_z_fast; + float dist_z_close; + float dist_z_far; + float turn_threshold; + float turn_radius_max; + int straight_ticks; // Number of ticks to delay before turning + int damage_ticks; + int turn_ticks; // Max time to spend turning before going straight + float flee_speed_z; + float target_z_threshold; + + float minDamageDist; // Damage distance check + + // Scale variables + bool isScaling; + float scaleStart; + float scaleEnd; + float scaleTime; + float lastScaleTime; + + int countDownTimer; + idMat3 desiredAxis; + float desiredVelocity; + + bool bFaceEnemy; + int nextDrop; + int nextChatter; + int minChatter; + int maxChatter; + + idEntityPtr lastActor; + + int nextPossessTime; + + idEntityPtr fxFly; +}; + +#endif /* __GAME_WRAITH_H__ */ diff --git a/src/Prey/game_zone.cpp b/src/Prey/game_zone.cpp new file mode 100644 index 0000000..7902135 --- /dev/null +++ b/src/Prey/game_zone.cpp @@ -0,0 +1,1182 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_SetGravityVector("setgravity", "v"); +const idEventDef EV_SetGravityFactor("setgravityfactor", "f"); +const idEventDef EV_DeactivateZone("", NULL); + +//----------------------------------------------------------------------- +// +// hhZone +// +// NOTE: You do not get a EntityLeaving() callback for entities that are +// removed. Could possibly use hhSafeEntitys to tell when they are removed +// but what's the point of calling EntityLeaving() with an invalid pointer. +//----------------------------------------------------------------------- + +ABSTRACT_DECLARATION(hhTrigger, hhZone) + EVENT( EV_DeactivateZone, hhZone::Event_TurnOff ) + EVENT( EV_Enable, hhZone::Event_Enable ) + EVENT( EV_Disable, hhZone::Event_Disable ) + EVENT( EV_Touch, hhZone::Event_Touch ) +END_CLASS + +//NOTE: If this works, this entity can cease inheriting from trigger, just need to take the +// tracemodel creation logic, isSimpleBox variable, make our own enable/disable functions +// touch goes away, triggeraction goes away, much simpler interface +#define ZONES_ALWAYS_ACTIVE 1 // testing: want to be able to use dormancy to turn off --pdm + +void hhZone::Spawn(void) { + slop = 0.0f; // Extra slop for bounds check +#if !ZONES_ALWAYS_ACTIVE + fl.neverDormant = true; +#endif + +#if ZONES_ALWAYS_ACTIVE + fl.neverDormant = false; + BecomeActive(TH_THINK); + bActive = true; + bEnabled = true; +#endif +} + +void hhZone::Save(idSaveGame *savefile) const { + savefile->WriteInt(zoneList.Num()); // idList + for (int i=0; iWriteInt(zoneList[i]); + } + + savefile->WriteFloat(slop); +} + +void hhZone::Restore( idRestoreGame *savefile ) { + int num; + + zoneList.Clear(); // idList + savefile->ReadInt(num); + zoneList.SetNum(num); + for (int i=0; iReadInt(zoneList[i]); + } + + savefile->ReadFloat(slop); +} + +bool hhZone::ValidEntity(idEntity *ent) { + return (ent && ent!=this && + ent->GetPhysics() && + !ent->GetPhysics()->IsType(idPhysics_Static::Type) && + ent->GetPhysics()->GetContents() != 0); +} + +void hhZone::Empty() { +} + +bool hhZone::ContainsEntityOfType(const idTypeInfo &t) { + idEntity *touch[ MAX_GENTITIES ]; + idBounds clipBounds; + + clipBounds.FromTransformedBounds( GetPhysics()->GetBounds(), GetOrigin(), GetAxis() ); + int num = gameLocal.clip.EntitiesTouchingBounds( clipBounds.Expand(slop), MASK_SHOT_BOUNDINGBOX, touch, MAX_GENTITIES ); + for (int i=0; iIsType(t)) { + gameLocal.Printf("Contains a %s\n", t.classname); + return true; + } + } + gameLocal.Printf("Doesn't contain a %s\n", t.classname); + return false; +} + +bool PointerInList(idEntity *target, idEntity **list, int num) { + for (int j=0; j < num; j++ ) { + if (list[j] == target) { + return true; + } + } + return false; +} + +void hhZone::ResetZoneList() { + // Call Leaving for anything previously entered + idEntity *previouslyInZone; + for (int i=0; i < zoneList.Num(); i++ ) { + previouslyInZone = gameLocal.entities[zoneList[i]]; + + if (previouslyInZone) { + EntityLeaving(previouslyInZone); + } + } + zoneList.Clear(); +} + +void hhZone::TriggerAction(idEntity *activator) { + CancelEvents(&EV_DeactivateZone); + // Turn on until all encroachers are gone + BecomeActive(TH_THINK); +} + +void hhZone::ApplyToEncroachers() { + idEntity *touch[ MAX_GENTITIES ]; + idEntity *previouslyInZone; + idEntity *encroacher; + int i, num; + + idBounds clipBounds; + clipBounds.FromTransformedBounds( GetPhysics()->GetBounds(), GetOrigin(), GetAxis() ); + + // Find all encroachers + if (isSimpleBox) { + num = gameLocal.clip.EntitiesTouchingBounds( clipBounds.Expand(slop), MASK_SHOT_BOUNDINGBOX | CONTENTS_PROJECTILE | CONTENTS_TRIGGER, touch, MAX_GENTITIES ); // CONTENTS_TRIGGER for walkthrough movables + } + else { + num = hhUtils::EntitiesTouchingClipmodel( GetPhysics()->GetClipModel(), touch, MAX_GENTITIES, MASK_SHOT_BOUNDINGBOX | CONTENTS_TRIGGER ); + } + + // for anything previously applied, but no longer encroaching, call EntityLeaving() + for (i=0; i < zoneList.Num(); i++ ) { + previouslyInZone = gameLocal.entities[zoneList[i]]; + + if (previouslyInZone) { + if (!ValidEntity(previouslyInZone) || !PointerInList(previouslyInZone, touch, num)) { + // We've applied before, but it's no longer encroaching + EntityLeaving(previouslyInZone); + + // NOTE: Rather than removing and dealing with the list shifting, we reconstruct the list + // from the touch list later + } + } + } + + // Check touch list for any newly entered encroachers + for (i = 0; i < num; i++ ) { + encroacher = touch[i]; + if (ValidEntity(encroacher)) { + if (zoneList.FindIndex(encroacher->entityNumber) == -1) { + EntityEntered(encroacher); + } + } + } + + // Call all encroachers and rebuild list + zoneList.Clear(); //fixme: could make a version of clear() that doesn't deallocate the memory + for (i = 0; i < num; i++ ) { + encroacher = touch[i]; + if (ValidEntity(encroacher)) { + zoneList.Append(encroacher->entityNumber); + EntityEncroaching(encroacher); + } + } + + // Deactivate if no encroachers left + if (!zoneList.Num()) { + Empty(); +#if !ZONES_ALWAYS_ACTIVE + PostEventMS(&EV_DeactivateZone, 0); +#endif + } +} + +void hhZone::Think() { + if (thinkFlags & TH_THINK) { + ApplyToEncroachers(); + } +} + +void hhZone::Event_TurnOff() { + BecomeInactive(TH_THINK); + bActive = false; +} + +void hhZone::Event_Enable( void ) { + hhTrigger::Event_Enable(); + TriggerAction(this); +} + +void hhZone::Event_Disable( void ) { + BecomeInactive(TH_THINK); + ResetZoneList(); + hhTrigger::Event_Disable(); +} + +void hhZone::Event_Touch( idEntity *other, trace_t *trace ) { + CancelEvents(&EV_DeactivateZone); + // Turn on until all encroachers are gone + BecomeActive(TH_THINK); + + bActive = true; +} + + +//----------------------------------------------------------------------- +// +// hhTriggerZone +// +// Zone used for precise trigger/untrigger mechanic. Fires trigger once +// upon a valid entity entering, and again when a valid entity leaves. Also, +// optionally calls a function for each entity in the volume each tick. +//----------------------------------------------------------------------- + +CLASS_DECLARATION(hhZone, hhTriggerZone) +END_CLASS + +void hhTriggerZone::Spawn() { + funcRefInfo.ParseFunctionKeyValue( spawnArgs.GetString("inCallRef") ); +} + +void hhTriggerZone::Save(idSaveGame *savefile) const { + savefile->WriteStaticObject( funcRefInfo ); +} + +void hhTriggerZone::Restore( idRestoreGame *savefile ) { + savefile->ReadStaticObject( funcRefInfo ); +} + +bool hhTriggerZone::ValidEntity(idEntity *ent) { + return (hhZone::ValidEntity(ent) && !IsType(hhProjectile::Type)); +} + +void hhTriggerZone::EntityEntered(idEntity *ent) { + ActivateTargets(ent); +} + +void hhTriggerZone::EntityLeaving(idEntity *ent) { + ActivateTargets(ent); +} + +void hhTriggerZone::EntityEncroaching( idEntity *ent ) { + if (funcRefInfo.GetFunction() != NULL) { + funcRefInfo.SetParm_Entity( ent, 0 ); + funcRefInfo.Verify(); + funcRefInfo.CallFunction( spawnArgs ); + } +} + +//----------------------------------------------------------------------- +// +// hhGravityZoneBase +// +//----------------------------------------------------------------------- + +ABSTRACT_DECLARATION(hhZone, hhGravityZoneBase) +END_CLASS + +void hhGravityZoneBase::Spawn(void) { + bReorient = spawnArgs.GetBool("reorient"); + bShowVector = spawnArgs.GetBool("showVector"); + bKillsMonsters = spawnArgs.GetBool("killmonsters"); + + //rww - avoid dictionary lookup post-spawn + gravityOriginOffset = vec3_origin; + if (spawnArgs.GetVector("override_origin", gravityOriginOffset.ToString(), gravityOriginOffset)) { + gravityOriginOffset -= GetOrigin(); + } + + //rww - sync over network + fl.networkSync = true; +} + +void hhGravityZoneBase::Save(idSaveGame *savefile) const { + savefile->WriteBool(bReorient); + savefile->WriteBool(bKillsMonsters); + savefile->WriteBool(bShowVector); + savefile->WriteVec3(gravityOriginOffset); +} + +void hhGravityZoneBase::Restore( idRestoreGame *savefile ) { + savefile->ReadBool(bReorient); + savefile->ReadBool(bKillsMonsters); + savefile->ReadBool(bShowVector); + savefile->ReadVec3(gravityOriginOffset); +} + +//rww - network code +void hhGravityZoneBase::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteBits(bReorient, 1); + msg.WriteFloat(slop); +} + +void hhGravityZoneBase::ReadFromSnapshot( const idBitMsgDelta &msg ) { + bReorient = !!msg.ReadBits(1); + slop = msg.ReadFloat(); +} + +void hhGravityZoneBase::ClientPredictionThink( void ) { + Think(); +} +//rww - end network code + +const idVec3 hhGravityZoneBase::GetGravityOrigin() const { + return GetOrigin()+gravityOriginOffset; +} + +bool hhGravityZoneBase::ValidEntity(idEntity *ent) { + if (!ent) { + return false; + } + if (ent->fl.ignoreGravityZones) { + return false; + } + + if (ent->IsType(hhProjectile::Type) && ent->GetPhysics()->GetGravity() == vec3_origin) { + return false; // Projectiles with zero gravity + } + if (ent->IsType(hhPlayer::Type)) { + hhPlayer *pl = static_cast(ent); + if (pl->noclip) { + return false; // Noclipping players + } + else if (pl->spectating) { + return false; //spectating players + } + else if (gameLocal.isMultiplayer && ent->health <= 0) { + return false; //dead mp players (only the prox ragdoll needs gravity) + } + } + if (ent->IsType(hhVehicle::Type)) { + if (static_cast(ent)->IsNoClipping()) { + return false; // Noclipping vehicles + } + if (ent->IsType(hhShuttle::Type) && static_cast(ent)->IsConsole()) { + return false; // unpiloted shuttles + } + } + if (ent->IsType(hhPortal::Type) ) { + return true; // Portals are always valid entities in zones + } + + if (!hhZone::ValidEntity( ent )) { + return false; + } + + return true; +} + +bool hhGravityZoneBase::TouchingOtherZones(idEntity *ent, bool traceCheck, idVec3 &otherInfluence) { //rww + if (!ent->GetPhysics()) { + return false; + } + + bool hitAny = false; + + otherInfluence.Zero(); + + idBounds clipBounds; + + idEntity *touch[ MAX_GENTITIES ]; + clipBounds.FromTransformedBounds( ent->GetPhysics()->GetBounds(), ent->GetOrigin(), ent->GetAxis() ); + int num = gameLocal.clip.EntitiesTouchingBounds( clipBounds, GetPhysics()->GetContents(), touch, MAX_GENTITIES ); + for (int i = 0; i < num; i++) { + if (touch[i] && touch[i]->entityNumber != entityNumber && touch[i]->IsType(hhGravityZoneBase::Type)) { + //touching the object, isn't me, and seems to be another gravity zone + bool touchValid = false; + + if (traceCheck) { //let's perform a trace from the ent's origin to see which zone is hit first. (this is not an ideal solution, but it works) + trace_t tr; + const int checkContents = GetPhysics()->GetContents(); + const idVec3 &start = ent->GetOrigin(); + const float testLength = 512.0f; + idVec3 end; + + //first trace against the other + end = (touch[i]->GetPhysics()->GetBounds().GetCenter()-start).Normalize()*testLength; + gameLocal.clip.TracePoint(tr, start, end, checkContents, ent); + if (tr.c.entityNum == touch[i]->entityNumber) { //if the trace actually hit the other one + float otherFrac = tr.fraction; + + //now trace against me + end = (GetPhysics()->GetBounds().GetCenter()-start).Normalize()*testLength; + gameLocal.clip.TracePoint(tr, start, GetPhysics()->GetBounds().GetCenter(), checkContents, ent); + if (tr.c.entityNum != entityNumber || tr.fraction >= otherFrac) { //if the impact was further away (or same, don't want fighting), i lose. + touchValid = true; + } + } + } + else { + touchValid = true; + } + + if (touchValid) { + //accumulate force from other zones + hhGravityZoneBase *zone = static_cast(touch[i]); + if (zone->isSimpleBox || ent->GetPhysics()->ClipContents(zone->GetPhysics()->GetClipModel())) { //if not simple box perform a clip check + idVec3 grav = zone->GetCurrentGravity(ent->GetOrigin()); + hitAny = true; + + grav.Normalize(); + otherInfluence += grav; + + otherInfluence.Normalize(); + } + } + } + } + + return hitAny; +} + +void hhGravityZoneBase::EntityEntered( idEntity *ent ) { + if( ent->RespondsTo(EV_ShouldRemainAlignedToAxial) ) { + ent->ProcessEvent( &EV_ShouldRemainAlignedToAxial, (int)false ); + } + if( ent->RespondsTo(EV_OrientToGravity) ) { + ent->ProcessEvent( &EV_OrientToGravity, (int)bReorient ); + } +} + +void hhGravityZoneBase::EntityLeaving( idEntity *ent ) { + if( ent->RespondsTo(EV_ShouldRemainAlignedToAxial) ) { + ent->ProcessEvent( &EV_ShouldRemainAlignedToAxial, (int)true ); + } + + // Instead of reseting gravity here, post a message to do it, so if we are transitioning + // to another gravity zone or wallwalk, there won't be any discontinuities + if (gameLocal.isClient && !ent->fl.clientEvents && !ent->fl.clientEntity && ent->IsType(hhProjectile::Type)) { + ent->fl.clientEvents = true; //hackery to let normal projectiles reset their gravity for prediction + ent->PostEventMS( &EV_ResetGravity, 200 ); + ent->fl.clientEvents = false; + } + else { + ent->PostEventMS( &EV_ResetGravity, 200 ); + } +} + +void hhGravityZoneBase::EntityEncroaching( idEntity *ent ) { + // Cancel any pending gravity resets from other zones + ent->CancelEvents( &EV_ResetGravity ); + + idVec3 curGravity = GetCurrentGravity( ent->GetOrigin() ); + idVec3 otherGravity; + if (TouchingOtherZones(ent, false, otherGravity)) { //factor in gravity for all other zones being touched to avoid back-and-forth behaviour + float l = curGravity.Normalize(); + curGravity += otherGravity; + curGravity *= l*0.5f; + } + if (ent->GetPhysics()->IsAtRest() && ent->GetGravity() != curGravity) { + ent->SetGravity( curGravity ); + ent->GetPhysics()->Activate(); + } + else { + ent->SetGravity( curGravity ); + } + + if (ent->IsType( hhMonsterAI::Type )) { + if (bKillsMonsters && ent->health > 0 && + !static_cast(ent)->OverrideKilledByGravityZones() && + !ent->IsType(hhCrawler::Type) && + (idMath::Fabs(curGravity.x) > 0.01f || idMath::Fabs(curGravity.y) > 0.01f || curGravity.z >= 0.0f) && + static_cast(ent)->IsActive() ) { + + const char *monsterDamageType = spawnArgs.GetString("def_monsterdamage"); + ent->Damage(this, NULL, vec3_origin, monsterDamageType, 1.0f, 0); + } + } +} + + +//----------------------------------------------------------------------- +// +// hhGravityZone +// +//----------------------------------------------------------------------- + +CLASS_DECLARATION(hhGravityZoneBase, hhGravityZone) + EVENT( EV_SetGravityVector, hhGravityZone::Event_SetNewGravity ) +END_CLASS + +void hhGravityZone::Spawn(void) { + zeroGravOnChange = spawnArgs.GetBool("zeroGravOnChange"); + idVec3 startGravity( spawnArgs.GetVector("gravity") ); + interpolationTime = SEC2MS(spawnArgs.GetFloat("interpTime")); + gravityInterpolator.Init( gameLocal.time, 0, startGravity, startGravity ); + + if (startGravity != gameLocal.GetGravity()) { + if (!gameLocal.isMultiplayer) { //don't play sound in mp + StartSound("snd_gravity_loop_on", SND_CHANNEL_MISC1, 0, true); + } + } +} + +void hhGravityZone::Save(idSaveGame *savefile) const { + savefile->WriteFloat( gravityInterpolator.GetStartTime() ); // idInterpolate + savefile->WriteFloat( gravityInterpolator.GetDuration() ); + savefile->WriteVec3( gravityInterpolator.GetStartValue() ); + savefile->WriteVec3( gravityInterpolator.GetEndValue() ); + + savefile->WriteInt(interpolationTime); + savefile->WriteBool(zeroGravOnChange); +} + +void hhGravityZone::Restore( idRestoreGame *savefile ) { + float set; + idVec3 vec; + + savefile->ReadFloat( set ); // idInterpolate + gravityInterpolator.SetStartTime( set ); + savefile->ReadFloat( set ); + gravityInterpolator.SetDuration( set ); + savefile->ReadVec3( vec ); + gravityInterpolator.SetStartValue( vec ); + savefile->ReadVec3( vec ); + gravityInterpolator.SetEndValue( vec ); + + savefile->ReadInt(interpolationTime); + savefile->ReadBool(zeroGravOnChange); +} + +void hhGravityZone::Think() { + hhGravityZoneBase::Think(); + if (thinkFlags & TH_THINK) { + if (bShowVector) { + gameRenderWorld->DebugArrow(colorGreen, renderEntity.origin, renderEntity.origin + GetCurrentGravity(vec3_origin), 10); + } + } +} + +const idVec3 hhGravityZone::GetDestinationGravity() const { + return gravityInterpolator.GetEndValue(); +} + +const idVec3 hhGravityZone::GetCurrentGravity(const idVec3 &location) const { + return gravityInterpolator.GetCurrentValue( gameLocal.time ); +} + +void hhGravityZone::SetGravityOnZone( idVec3 &newGravity ) { + idVec3 startGrav; + + if (!gameLocal.isMultiplayer) { //don't play sound in mp + if ( newGravity.Compare(gameLocal.GetGravity(), VECTOR_EPSILON) ) { + StartSound("snd_gravity_off", SND_CHANNEL_ANY); + StopSound(SND_CHANNEL_MISC1, true); + StartSound("snd_gravity_loop_off", SND_CHANNEL_MISC1, 0, true); + } + else { + StartSound("snd_gravity_on", SND_CHANNEL_ANY); + StopSound(SND_CHANNEL_MISC1, true); + StartSound("snd_gravity_loop_on", SND_CHANNEL_MISC1, 0, true); + } + } + + if ( zeroGravOnChange ) { // nla + startGrav = vec3_origin; + } + else { + startGrav = GetCurrentGravity(vec3_origin); + } + // Interpolate to new gravity + gravityInterpolator.Init( gameLocal.time, interpolationTime, startGrav, newGravity ); +} + +//rww - network code +void hhGravityZone::WriteToSnapshot( idBitMsgDelta &msg ) const { + hhGravityZoneBase::WriteToSnapshot(msg); + + msg.WriteFloat(gravityInterpolator.GetStartTime()); + msg.WriteFloat(gravityInterpolator.GetDuration()); + idVec3 vecStart = gravityInterpolator.GetStartValue(); + msg.WriteFloat(vecStart.x); + msg.WriteFloat(vecStart.y); + msg.WriteFloat(vecStart.z); + idVec3 vecEnd = gravityInterpolator.GetEndValue(); + msg.WriteDeltaFloat(vecStart.x, vecEnd.x); + msg.WriteDeltaFloat(vecStart.y, vecEnd.y); + msg.WriteDeltaFloat(vecStart.z, vecEnd.z); +} + +void hhGravityZone::ReadFromSnapshot( const idBitMsgDelta &msg ) { + hhGravityZoneBase::ReadFromSnapshot(msg); + gravityInterpolator.SetStartTime(msg.ReadFloat()); + gravityInterpolator.SetDuration(msg.ReadFloat()); + idVec3 vecStart; + vecStart.x = msg.ReadFloat(); + vecStart.y = msg.ReadFloat(); + vecStart.z = msg.ReadFloat(); + gravityInterpolator.SetStartValue(vecStart); + idVec3 vecEnd; + vecEnd.x = msg.ReadDeltaFloat(vecStart.x); + vecEnd.y = msg.ReadDeltaFloat(vecStart.y); + vecEnd.z = msg.ReadDeltaFloat(vecStart.z); + gravityInterpolator.SetEndValue(vecEnd); +} + +void hhGravityZone::ClientPredictionThink( void ) { + hhGravityZoneBase::ClientPredictionThink(); +} +//rww - end network code + +void hhGravityZone::Event_SetNewGravity( idVec3 &newGravity ) { + SetGravityOnZone( newGravity ); +} + + +//----------------------------------------------------------------------- +// +// hhGravityZoneInward +// +//----------------------------------------------------------------------- + +CLASS_DECLARATION(hhGravityZoneBase, hhGravityZoneInward) + EVENT( EV_SetGravityFactor, hhGravityZoneInward::Event_SetNewGravityFactor ) +END_CLASS + +void hhGravityZoneInward::Spawn(void) { + float startFactor = spawnArgs.GetFloat("factor", "50000"); + monsterGravityFactor = spawnArgs.GetFloat("monsterGravFactor", "1"); + interpolationTime = SEC2MS(spawnArgs.GetFloat("interpTime")); + factorInterpolator.Init( gameLocal.time, 0, startFactor, startFactor ); +} + +void hhGravityZoneInward::Save(idSaveGame *savefile) const { + savefile->WriteFloat( factorInterpolator.GetStartTime() ); // idInterpolate + savefile->WriteFloat( factorInterpolator.GetDuration() ); + savefile->WriteFloat( factorInterpolator.GetStartValue() ); + savefile->WriteFloat( factorInterpolator.GetEndValue() ); + + savefile->WriteInt(interpolationTime); + savefile->WriteFloat(monsterGravityFactor); +} + +void hhGravityZoneInward::Restore( idRestoreGame *savefile ) { + float set; + + savefile->ReadFloat( set ); // idInterpolate + factorInterpolator.SetStartTime( set ); + savefile->ReadFloat( set ); + factorInterpolator.SetDuration( set ); + savefile->ReadFloat( set ); + factorInterpolator.SetStartValue(set); + savefile->ReadFloat( set ); + factorInterpolator.SetEndValue( set ); + + savefile->ReadInt(interpolationTime); + savefile->ReadFloat(monsterGravityFactor); +} + +void hhGravityZoneInward::EntityEntered(idEntity *ent) { + hhGravityZoneBase::EntityEntered(ent); + if ( ent && ent->IsType( hhMonsterAI::Type ) ) { + static_cast(ent)->GravClipModelAxis( true ); + } + // Disallow slope checking, it makes us stutter when walking on convex surfaces + + // aob - commented this because it allows the player to walk up vertical walls while in inward gravity zone + //Didn't see any studdering when thia was commented out. Do we still need it? + //if (ent->IsType( hhPlayer::Type ) && ent->GetPhysics()->IsType(hhPhysics_Player::Type) ) { + // static_cast(ent->GetPhysics())->SetSlopeCheck(false); + //} + //now done constantly while in a gravity zone + /* + if (ent->IsType( hhPlayer::Type ) && ent->GetPhysics()->IsType(hhPhysics_Player::Type) ) { + static_cast(ent->GetPhysics())->SetInwardGravity(1); + } + */ +} + +void hhGravityZoneInward::EntityLeaving(idEntity *ent) { + hhGravityZoneBase::EntityLeaving(ent); + if ( ent && ent->IsType( hhMonsterAI::Type ) ) { + static_cast(ent)->GravClipModelAxis( false ); + } + // Re-enable slope checking + + // aob - commented this because it allows the player to walk up vertical walls while in inward gravity zone + //Didn't see any studdering when thia was commented out. Do we still need it? + //if (ent->IsType( hhPlayer::Type ) && ent->GetPhysics()->IsType(hhPhysics_Player::Type) ) { + // static_cast(ent->GetPhysics())->SetSlopeCheck(true); + //} + if (ent->IsType( hhPlayer::Type ) && ent->GetPhysics()->IsType(hhPhysics_Player::Type) ) { + static_cast(ent->GetPhysics())->SetInwardGravity(0); + } +} + +// This is actually called each tick if for entities inside +void hhGravityZoneInward::EntityEncroaching( idEntity *ent ) { + + // Cancel any pending gravity resets from other zones + ent->CancelEvents( &EV_ResetGravity ); + + idVec3 curGravity = GetCurrentGravity( ent->GetOrigin() ); + idVec3 otherGravity; + if (TouchingOtherZones(ent, false, otherGravity)) { //factor in gravity for all other zones being touched to avoid back-and-forth behaviour + float l = curGravity.Normalize(); + curGravity += otherGravity; + curGravity *= l*0.5f; + } + if (ent->GetPhysics()->IsAtRest() && ent->GetGravity() != curGravity) { + ent->SetGravity( curGravity ); + ent->GetPhysics()->Activate(); + } + else { + ent->SetGravity( curGravity ); + } + if (ent->IsType( idAI::Type )) { + if (bKillsMonsters && ent->health > 0 && + !ent->IsType(hhCrawler::Type) && + (curGravity.x != 0.0f || curGravity.y != 0.0f || curGravity.z >= 0.0f) && + !static_cast(ent)->OverrideKilledByGravityZones() && + static_cast(ent)->IsActive() ) { + const char *monsterDamageType = spawnArgs.GetString("def_monsterdamage"); + ent->Damage(this, NULL, vec3_origin, monsterDamageType, 1.0f, 0); + } + } + //rww + else if (ent->IsType( hhPlayer::Type ) && ent->GetPhysics()->IsType(hhPhysics_Player::Type) ) { + static_cast(ent->GetPhysics())->SetInwardGravity(1); + } + + + if( ent->IsType(idAI::Type) && ent->health > 0 ) { + ent->GetPhysics()->SetGravity( ent->GetPhysics()->GetGravity() * monsterGravityFactor ); + ent->GetPhysics()->Activate(); + } + + if( bShowVector ) { + hhUtils::DebugCross( colorBlue, GetOrigin(), 100, 10 ); + idVec3 newGrav = GetCurrentGravity( ent->GetOrigin() ); + gameRenderWorld->DebugArrow( colorGreen, ent->GetRenderEntity()->origin, ent->GetRenderEntity()->origin + newGrav, 10 ); + } +} + +const idVec3 hhGravityZoneInward::GetCurrentGravity( const idVec3 &location ) const { + idVec3 grav; + idVec3 origin = GetGravityOrigin(); + float factor = factorInterpolator.GetCurrentValue( gameLocal.GetTime() ); + idVec3 inward = origin - location; + inward.Normalize(); + grav = inward * DEFAULT_GRAVITY * factor; + return grav; +} + +void hhGravityZoneInward::Event_SetNewGravityFactor( float newFactor ) { + // Interpolate to new gravity factor + float curFactor = factorInterpolator.GetCurrentValue( gameLocal.GetTime() ); + factorInterpolator.Init( gameLocal.GetTime(), interpolationTime, curFactor, newFactor ); +} + +//----------------------------------------------------------------------- +// +// hhAIWallwalkZone +// +//----------------------------------------------------------------------- + +CLASS_DECLARATION(hhGravityZone, hhAIWallwalkZone) +END_CLASS + +bool hhAIWallwalkZone::ValidEntity(idEntity *ent) { + // allow AI that isnt dead + return ent->IsType(idAI::Type) && ent->health > 0; +} + +void hhAIWallwalkZone::EntityEncroaching( idEntity *ent ) { + // Cancel any pending gravity resets from other zones + ent->CancelEvents( &EV_ResetGravity ); + + trace_t TraceInfo; + gameLocal.clip.TracePoint(TraceInfo, ent->GetOrigin(), ent->GetOrigin() + (idVec3(0,0,-300)*ent->GetRenderEntity()->axis), ent->GetPhysics()->GetClipMask(), ent); + if( TraceInfo.fraction < 1.0f ) { // && ent->health > 0 ) { + ent->SetGravity( -TraceInfo.c.normal ); + ent->GetPhysics()->Activate(); + } +} + +//----------------------------------------------------------------------- +// +// hhGravityZoneSinkhole +// +//----------------------------------------------------------------------- + +CLASS_DECLARATION(hhGravityZoneInward, hhGravityZoneSinkhole) + EVENT( EV_SetGravityFactor, hhGravityZoneSinkhole::Event_SetNewGravityFactor ) +END_CLASS + +void hhGravityZoneSinkhole::Spawn(void) { + bReorient = false; + maxMagnitude = spawnArgs.GetFloat("maxMagnitude", "10000"); + minMagnitude = spawnArgs.GetFloat("minMagnitude", "0"); +} + +void hhGravityZoneSinkhole::Save(idSaveGame *savefile) const { + savefile->WriteFloat(maxMagnitude); + savefile->WriteFloat(minMagnitude); +} + +void hhGravityZoneSinkhole::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat(maxMagnitude); + savefile->ReadFloat(minMagnitude); +} + +// Still have this in case we want to do something mass based +const idVec3 hhGravityZoneSinkhole::GetCurrentGravityEntity(const idEntity *ent) const { + idVec3 grav = vec3_origin; + if (ent) { + // precalc mass product / G as a constant and expose that as the fudge factor + idVec3 origin = GetGravityOrigin(); + float factor = factorInterpolator.GetCurrentValue( gameLocal.time ); + idVec3 inward = origin - ent->GetOrigin(); + float distance = inward.Normalize(); + float distanceSquared = distance*distance; + + // Some different gravitational fields + // float gravMag = (mass * ent->GetPhysics()->GetMass() * GRAVITATIONAL_CONSTANT) / distanceSquared; + // float gravMag = factor*factor / distanceSquared; // Inverse squared distance + // float gravMag = factor / sqrt(distance); // Inverse sqrt distance + // float gravMag = factor * sqrt(distance); // sqrt distance + float gravMag = factor*factor / 2 + 0.2f * distanceSquared; // Inverse squared distance + //gameLocal.Printf("factor=%.0f gravity magnitude=%.2f\n", factor, gravMag); + + gravMag = hhMath::ClampFloat(minMagnitude, maxMagnitude, gravMag); // This will cut off extremely large forces + grav = inward * gravMag; + } + return grav; +} + +const idVec3 hhGravityZoneSinkhole::GetCurrentGravity(const idVec3 &location) const { + idVec3 grav; + + // precalc mass product / G as a constant and expose that as the fudge factor + idVec3 origin = GetGravityOrigin(); + float factor = factorInterpolator.GetCurrentValue( gameLocal.time ); + idVec3 inward = origin - location; + float distance = inward.Normalize(); + float distanceSquared = distance*distance; + + // Some different gravitational fields + float gravMag = factor*factor / 2 + 0.2f * distanceSquared; // Inverse squared distance + gravMag = hhMath::ClampFloat(minMagnitude, maxMagnitude, gravMag); // This will cut off extremely large forces + grav = inward * gravMag; + return grav; +} + +void hhGravityZoneSinkhole::Event_SetNewGravityFactor( float newFactor ) { + // Interpolate to new gravity factor + float curFactor = factorInterpolator.GetCurrentValue(gameLocal.time); + factorInterpolator.Init( gameLocal.time, interpolationTime, curFactor, newFactor ); +} + + +//----------------------------------------------------------------------- +// +// hhVelocityZone +// +//----------------------------------------------------------------------- + +const idEventDef EV_SetVelocityVector("setvelocity", "v"); + +CLASS_DECLARATION(hhZone, hhVelocityZone) + EVENT( EV_SetVelocityVector, hhVelocityZone::Event_SetNewVelocity ) +END_CLASS + +void hhVelocityZone::Spawn(void) { + bReorient = spawnArgs.GetBool("reorient"); + interpolationTime = SEC2MS(spawnArgs.GetFloat("interpTime")); + idVec3 startVelocity = spawnArgs.GetVector("velocity"); + //slop = 25.0f; // we use a slightly larger bounds to catch things that are rotated by bReorient + bShowVector = spawnArgs.GetBool("showVector"); + bKillsMonsters = spawnArgs.GetBool("killmonsters"); + + velocityInterpolator.Init(gameLocal.time, 0, startVelocity, startVelocity); +} + +void hhVelocityZone::Save(idSaveGame *savefile) const { + savefile->WriteFloat( velocityInterpolator.GetStartTime() ); // idInterpolate + savefile->WriteFloat( velocityInterpolator.GetDuration() ); + savefile->WriteVec3( velocityInterpolator.GetStartValue() ); + savefile->WriteVec3( velocityInterpolator.GetEndValue() ); + + savefile->WriteBool(bKillsMonsters); + savefile->WriteBool(bReorient); + savefile->WriteBool(bShowVector); + savefile->WriteInt(interpolationTime); +} + +void hhVelocityZone::Restore( idRestoreGame *savefile ) { + float set; + idVec3 vec; + + savefile->ReadFloat( set ); // idInterpolate + velocityInterpolator.SetStartTime( set ); + savefile->ReadFloat( set ); + velocityInterpolator.SetDuration( set ); + savefile->ReadVec3( vec ); + velocityInterpolator.SetStartValue( vec ); + savefile->ReadVec3( vec ); + velocityInterpolator.SetEndValue( vec ); + + savefile->ReadBool(bKillsMonsters); + savefile->ReadBool(bReorient); + savefile->ReadBool(bShowVector); + savefile->ReadInt(interpolationTime); +} + +void hhVelocityZone::EntityLeaving(idEntity *ent) { + ent->GetPhysics()->SetLinearVelocity(idVec3(0, 0, 0)); + if( ent->RespondsTo(EV_OrientToGravity) ) { + ent->ProcessEvent( &EV_OrientToGravity, (int)bReorient ); + } +} + +void hhVelocityZone::EntityEncroaching(idEntity *ent) { + idVec3 baseVelocity = velocityInterpolator.GetCurrentValue(gameLocal.time); + idVec3 baseVelocityDirection = baseVelocity; + baseVelocityDirection.Normalize(); + + idVec3 curVelocity; + curVelocity = ent->GetPhysics()->GetLinearVelocity(); + curVelocity.ProjectOntoPlane(baseVelocityDirection); + + ent->GetPhysics()->SetLinearVelocity( curVelocity + baseVelocity ); + if( ent->RespondsTo(EV_OrientToGravity) ) { + ent->ProcessEvent( &EV_OrientToGravity, (int)bReorient ); + } + else if (ent->IsType( idAI::Type )) { + if (bKillsMonsters && ent->health > 0 && + !static_cast(ent)->IsFlying()) { + const char *monsterDamageType = spawnArgs.GetString("def_monsterdamage"); + ent->Damage(this, NULL, vec3_origin, monsterDamageType, 1.0f, 0); + } + } +} + +void hhVelocityZone::Think() { + hhZone::Think(); + if (thinkFlags & TH_THINK) { + if (bShowVector) { + idVec3 baseVelocity = velocityInterpolator.GetCurrentValue(gameLocal.time); + gameRenderWorld->DebugArrow(colorGreen, renderEntity.origin, renderEntity.origin+baseVelocity, 10); + } + } +} + +void hhVelocityZone::Event_SetNewVelocity( idVec3 &newVelocity ) { + // Interpolate to new velocity + idVec3 currentVelocity = velocityInterpolator.GetCurrentValue(gameLocal.time); + velocityInterpolator.Init(gameLocal.time, interpolationTime, currentVelocity, newVelocity); +} + + +//----------------------------------------------------------------------- +// +// hhShuttleRecharge +// +//----------------------------------------------------------------------- + +CLASS_DECLARATION(hhZone, hhShuttleRecharge) +END_CLASS + +void hhShuttleRecharge::Spawn(void) { + amountHealth = spawnArgs.GetInt("amounthealth"); + amountPower = spawnArgs.GetInt("amountpower"); +} + +void hhShuttleRecharge::Save(idSaveGame *savefile) const { + savefile->WriteInt(amountHealth); + savefile->WriteInt(amountPower); +} + +void hhShuttleRecharge::Restore( idRestoreGame *savefile ) { + savefile->ReadInt(amountHealth); + savefile->ReadInt(amountPower); +} + +bool hhShuttleRecharge::ValidEntity(idEntity *ent) { + return ent && ent->IsType(hhShuttle::Type); +} + +void hhShuttleRecharge::EntityEntered(idEntity *ent) { + //static_cast(ent)->SetRecharging(true); +} + +void hhShuttleRecharge::EntityLeaving(idEntity *ent) { + //static_cast(ent)->SetRecharging(false); +} + +void hhShuttleRecharge::EntityEncroaching(idEntity *ent) { + if (ent->IsType(hhVehicle::Type)) { + hhVehicle *vehicle = static_cast(ent); + + //HUMANHEAD bjk PCF (4-27-06) - shuttle recharge was slow + if(USERCMD_HZ == 30) { + vehicle->GiveHealth(2*amountHealth); + vehicle->GivePower(2*amountPower); + } else { + vehicle->GiveHealth(amountHealth); + vehicle->GivePower(amountPower); + } + } +} + + +//----------------------------------------------------------------------- +// +// hhDockingZone +// +//----------------------------------------------------------------------- + +CLASS_DECLARATION(hhZone, hhDockingZone) +END_CLASS + +void hhDockingZone::Spawn(void) { + dock = NULL; + triggerBehavior = TB_PLAYER_MONSTERS_FRIENDLIES; // Allow all actors to trigger it + + fl.networkSync = true; +} + +void hhDockingZone::Save(idSaveGame *savefile) const { + dock.Save(savefile); +} + +void hhDockingZone::Restore( idRestoreGame *savefile ) { + dock.Restore(savefile); +} + +void hhDockingZone::RegisterDock(hhDock *d) { + dock = d; +} + +bool hhDockingZone::ValidEntity(idEntity *ent) { + if (ent) { + if (dock.IsValid() && dock->ValidEntity(ent)) { + return true; + } + if (ent->IsType(idActor::Type)) { //FIXME: Is this causing the shuttleCount to go wrong + return hhShuttle::ValidPilot(static_cast(ent)); + } + } + return false; +} + +void hhDockingZone::EntityEncroaching(idEntity *ent) { + if (dock.IsValid()) { + dock->EntityEncroaching(ent); + } +} + +void hhDockingZone::EntityEntered(idEntity *ent) { + if (dock.IsValid()) { + dock->EntityEntered(ent); + } +} + +void hhDockingZone::EntityLeaving(idEntity *ent) { + if (dock.IsValid()) { + dock->EntityLeaving(ent); + } +} + +void hhDockingZone::WriteToSnapshot( idBitMsgDelta &msg ) const { + GetPhysics()->WriteToSnapshot(msg); + msg.WriteBits(dock.GetSpawnId(), 32); +} + +void hhDockingZone::ReadFromSnapshot( const idBitMsgDelta &msg ) { + GetPhysics()->ReadFromSnapshot(msg); + dock.SetSpawnId(msg.ReadBits(32)); +} + +void hhDockingZone::ClientPredictionThink( void ) { + if (!gameLocal.isNewFrame) { + return; + } + Think(); +} + +//----------------------------------------------------------------------- +// +// hhShuttleDisconnect +// +//----------------------------------------------------------------------- + +CLASS_DECLARATION(hhZone, hhShuttleDisconnect) +END_CLASS + +void hhShuttleDisconnect::Spawn(void) { +} + +bool hhShuttleDisconnect::ValidEntity(idEntity *ent) { + return ent && ent->IsType(hhShuttle::Type); +} + +void hhShuttleDisconnect::EntityEntered(idEntity *ent) { + static_cast(ent)->AllowTractor(false); +} + +void hhShuttleDisconnect::EntityEncroaching(idEntity *ent) { +} + +void hhShuttleDisconnect::EntityLeaving(idEntity *ent) { + static_cast(ent)->AllowTractor(true); +} + + +//----------------------------------------------------------------------- +// +// hhShuttleSlingshot +// +//----------------------------------------------------------------------- + +CLASS_DECLARATION(hhZone, hhShuttleSlingshot) +END_CLASS + +void hhShuttleSlingshot::Spawn(void) { +} + +bool hhShuttleSlingshot::ValidEntity(idEntity *ent) { + return ent && ent->IsType(hhShuttle::Type); +} + +void hhShuttleSlingshot::EntityEntered(idEntity *ent) { +} + +void hhShuttleSlingshot::EntityEncroaching(idEntity *ent) { + float factor = spawnArgs.GetFloat("BoostFactor"); + + hhShuttle *shuttle = static_cast(ent); + shuttle->ApplyBoost( 255.0f * factor); + + // CJR: Alter the player's view when zooming through a slingshot zone + shuttle->GetPilot()->PostEventMS( &EV_SetOverlayMaterial, 0, spawnArgs.GetString( "mtr_speedView" ), -1, false ); + +} + +void hhShuttleSlingshot::EntityLeaving(idEntity *ent) { + // CJR: Reset the player's view after zooming through a slingshot zone + hhShuttle *shuttle = static_cast(ent); + shuttle->GetPilot()->PostEventMS( &EV_SetOverlayMaterial, 0, "", -1, false ); +} + + + +//----------------------------------------------------------------------- +// +// hhRemovalVolume +// +//----------------------------------------------------------------------- + +CLASS_DECLARATION(hhZone, hhRemovalVolume) +END_CLASS + +void hhRemovalVolume::Spawn(void) { +} + +bool hhRemovalVolume::ValidEntity(idEntity *ent) { + return ent && ( + ent->IsType(idMoveable::Type) || + ent->IsType(idItem::Type) || + ent->IsType(hhAFEntity::Type) || + ent->IsType(hhAFEntity_WithAttachedHead::Type) || + (ent->IsType(hhMonsterAI::Type) && ent->health<=0 && !ent->fl.isTractored) ); +} + +void hhRemovalVolume::EntityEntered(idEntity *ent) { + ent->PostEventMS(&EV_Remove, 0); +} + +void hhRemovalVolume::EntityEncroaching(idEntity *ent) { +} + +void hhRemovalVolume::EntityLeaving(idEntity *ent) { +} diff --git a/src/Prey/game_zone.h b/src/Prey/game_zone.h new file mode 100644 index 0000000..aa2e7e3 --- /dev/null +++ b/src/Prey/game_zone.h @@ -0,0 +1,266 @@ + +#ifndef __GAME_GRAVITYZONE_H__ +#define __GAME_GRAVITYZONE_H__ + +class hhDock; + +class hhZone : public hhTrigger { +public: + ABSTRACT_PROTOTYPE( hhZone ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + virtual void Present( void ) { } // HUMANHEAD mdl: Not used by zones + + // hhTrigger interface + void TriggerAction(idEntity *activator); + + // hhZone interface + void ResetZoneList(); + void ApplyToEncroachers(); + bool ContainsEntityOfType(const idTypeInfo &t); + virtual void EntityEntered(idEntity *ent) {} + virtual void EntityLeaving(idEntity *ent) {} + virtual void EntityEncroaching(idEntity *ent) {} + virtual bool ValidEntity(idEntity *ent); + virtual void Empty(); + +protected: + void Event_TurnOff(); + void Event_Enable( void ); + void Event_Disable( void ); + void Event_Touch( idEntity *other, trace_t *trace ); + +protected: + idList zoneList; // List of valid entities in zone last frame + float slop; +}; + +class hhTriggerZone : public hhZone { +public: + CLASS_PROTOTYPE( hhTriggerZone ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool ValidEntity(idEntity *ent); + virtual void EntityEntered(idEntity *ent); + virtual void EntityLeaving(idEntity *ent); + virtual void EntityEncroaching(idEntity *ent); + + hhFuncParmAccessor funcRefInfo; +}; + +class hhGravityZoneBase : public hhZone { +public: + ABSTRACT_PROTOTYPE( hhGravityZoneBase ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool ValidEntity(idEntity *ent); + virtual void EntityEntered(idEntity *ent); + virtual void EntityEncroaching(idEntity *ent); + virtual void EntityLeaving(idEntity *ent); + virtual const idVec3 GetGravityOrigin() const; + virtual const idVec3 GetCurrentGravity(const idVec3 &location) const = 0; + + virtual bool TouchingOtherZones(idEntity *ent, bool traceCheck, idVec3 &otherInfluence); + + //rww - network code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + +protected: + bool bReorient; + bool bKillsMonsters; + bool bShowVector; + idVec3 gravityOriginOffset; //rww - avoid dictionary lookup +}; + +class hhGravityZone : public hhGravityZoneBase { +public: + CLASS_PROTOTYPE( hhGravityZone ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + virtual void Think( void ); + virtual const idVec3 GetDestinationGravity() const; + virtual const idVec3 GetCurrentGravity(const idVec3 &location) const; + virtual void SetGravityOnZone( idVec3 &newGravity ); + + //rww - network code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + +protected: + void Event_SetNewGravity( idVec3 &newgrav ); + +protected: + idInterpolate gravityInterpolator; + int interpolationTime; + bool zeroGravOnChange; +}; + +class hhAIWallwalkZone : public hhGravityZone { +public: + CLASS_PROTOTYPE( hhAIWallwalkZone ); + virtual void EntityEncroaching(idEntity *ent); + virtual bool ValidEntity(idEntity *ent); +}; + +class hhGravityZoneInward : public hhGravityZoneBase { +public: + CLASS_PROTOTYPE( hhGravityZoneInward ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void EntityEntered(idEntity *ent); + virtual void EntityLeaving(idEntity *ent); + virtual void EntityEncroaching(idEntity *ent); + + virtual const idVec3 GetCurrentGravity(const idVec3 &location) const; + +protected: + virtual void Event_SetNewGravityFactor( float newFactor ); + +protected: + idInterpolate factorInterpolator; + int interpolationTime; + float monsterGravityFactor; +}; + + +#define GRAVITATIONAL_CONSTANT 1.03416206832413664e-7f + +class hhGravityZoneSinkhole : public hhGravityZoneInward { +public: + CLASS_PROTOTYPE( hhGravityZoneSinkhole ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual const idVec3 GetCurrentGravity(const idVec3 &location) const; + const idVec3 GetCurrentGravityEntity(const idEntity *ent) const; + +protected: + void Event_SetNewGravityFactor( float newFactor ); + +protected: + float maxMagnitude; + float minMagnitude; +}; + + +class hhVelocityZone : public hhZone { +public: + CLASS_PROTOTYPE( hhVelocityZone ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + virtual void Think( void ); + virtual void EntityEncroaching(idEntity *ent); + virtual void EntityLeaving(idEntity *ent); + +protected: + void Event_SetNewVelocity( idVec3 &newvel ); + +protected: + idInterpolate velocityInterpolator; + bool bKillsMonsters; + bool bReorient; + bool bShowVector; + int interpolationTime; +}; + +class hhShuttleRecharge : public hhZone { +public: + CLASS_PROTOTYPE( hhShuttleRecharge ); + + void Spawn(void); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + virtual bool ValidEntity(idEntity *ent); + virtual void EntityEntered(idEntity *ent); + virtual void EntityLeaving(idEntity *ent); + virtual void EntityEncroaching(idEntity *ent); + +protected: + int amountHealth; + int amountPower; +}; + +class hhDockingZone : public hhZone { +public: + CLASS_PROTOTYPE( hhDockingZone ); + + void Spawn(void); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + virtual bool ValidEntity(idEntity *ent); + virtual void EntityEntered(idEntity *ent); + virtual void EntityEncroaching(idEntity *ent); + virtual void EntityLeaving(idEntity *ent); + void RegisterDock(hhDock *d); + + //rww - network code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + +protected: + idEntityPtr dock; +}; + +class hhShuttleDisconnect : public hhZone { +public: + CLASS_PROTOTYPE( hhShuttleDisconnect ); + + void Spawn(void); + virtual bool ValidEntity(idEntity *ent); + virtual void EntityEntered(idEntity *ent); + virtual void EntityEncroaching(idEntity *ent); + virtual void EntityLeaving(idEntity *ent); + +protected: +}; + +class hhShuttleSlingshot : public hhZone { +public: + CLASS_PROTOTYPE( hhShuttleSlingshot ); + + void Spawn(void); + virtual bool ValidEntity(idEntity *ent); + virtual void EntityEntered(idEntity *ent); + virtual void EntityEncroaching(idEntity *ent); + virtual void EntityLeaving(idEntity *ent); + +protected: +}; + +class hhRemovalVolume : public hhZone { +public: + CLASS_PROTOTYPE( hhRemovalVolume ); + + void Spawn(void); + virtual bool ValidEntity(idEntity *ent); + virtual void EntityEntered(idEntity *ent); + virtual void EntityEncroaching(idEntity *ent); + virtual void EntityLeaving(idEntity *ent); + +protected: +}; + +#endif /* __GAME_GRAVITYZONE_H__ */ diff --git a/src/Prey/particles_particles.cpp b/src/Prey/particles_particles.cpp new file mode 100644 index 0000000..b699f36 --- /dev/null +++ b/src/Prey/particles_particles.cpp @@ -0,0 +1,368 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idVec3 hhSmokeParticles::defaultDir( 0.0f, 0.0f, 1.0f ); + +/* +================ +hhSmokeParticles::hhSmokeParticles +================ +*/ +hhSmokeParticles::hhSmokeParticles() { +} + +#if 0 +/* +================ +hhParticleSystem::RunTrailStage +================ +*/ +void hhParticleSystem::RunTrailStage( const int ev ) { + if (GetParticle()) { + idFXSmokeStage *stage = particleSystem->events[ev]; + float fraction; + //HUMANHEAD: aob + int numParticleToTrigger = 0; + idVec3 gravity; + //HUMANHEAD END + + for( int i=0; ihidden && current && current->state != SMOKE_DEAD ) { + // do we need to insert another particle? + if (current->nozzle && current->triggered != current->numToTrigger) { + //HUMANHEAD: aob - moved logic to helper function + ResetSmokeParticleTrail( ¤t->particles[current->triggered], current, stage ); + //HUMANHEAD END + current->triggered++; + } + // move them all, kill if we need to + bool triggerAlive = false; + for( int j=0; jtriggered; j++ ) { + if ( current->particles[j].age <= stage->timeToFade ) { + smokeParticle *sParticle = ¤t->particles[j]; + idVec3 speed = vec3_origin; // HUMANHEAD JRM - changed to vec3 + if (sParticle->age >= stage->timeToFinalSpeed) { + speed = stage->finalSpeed; + } else { + fraction = sParticle->age * stage->oneOverTimeToFinalSpeed; + speed = stage->initialSpeed + (stage->finalSpeed - stage->initialSpeed) * fraction; + } + //HUMANHEAD: aob + if (sParticle->age >= stage->timeToFinalGravity) { + gravity = stage->finalGravity; + } else { + fraction = sParticle->age * stage->oneOverTimeToFinalGravity; + gravity = stage->initialGravity + (stage->finalGravity - stage->initialGravity) * fraction; + } + //HUMANHEAD END + if (sParticle->age != 0) { + if (stage->slowSpawnSpeedOverLife) { + fraction = 1.0f - ((float)j / current->numToTrigger); + //HUMANHEAD: aob + sParticle->position += DetermineDeltaPos( speed * fraction, sParticle->xyzmove, sParticle->direction ); + //HUMANHEAD END + } else { + //HUMANHEAD: aob + sParticle->position += DetermineDeltaPos( speed, sParticle->xyzmove, sParticle->direction ); + //HUMANHEAD END + } + //HUMANHEAD: aob + sParticle->position += gravity; + //HUMANHEAD END + } + sParticle->age += gameLocal.msec; + triggerAlive = true; + if ( (sParticle->age+current->baseTime) >= stage->timeToFade ) { + if (current->nozzle && stage->continuous ) { + //HUMANHEAD: aob - moved logic to helper function + ResetSmokeParticleTrail( sParticle, current, stage ); + //HUMANHEAD END + } + } + } + } + if (!triggerAlive) { + current->state = SMOKE_DEAD; + } + } + } + } +} + +/* +================ +hhParticleSystem::ResetSmokeParticleTrail +================ +*/ +void hhParticleSystem::ResetSmokeParticleTrail( smokeParticle* particle, smokeGroup* group, idFXSmokeStage* smokeStage ) { + float ang = 0.0f; + float size = 0.0f; + + particle->position = group->groupOrigin; + particle->direction = group->groupDirection; + // HUMANHEAD JRM - need to make spawn direction spherical - NOT box + ang = gameLocal.random.RandomFloat() * hhMath::TWO_PI; + size = smokeStage->xymove; + if( !smokeStage->noXYMoveRandomness ) { + size *= gameLocal.random.RandomFloat(); + } + particle->xyzmove = idVec3( 0.0f, hhMath::Cos(ang) * size, hhMath::Sin(ang) * size ); + particle->xyzmove *= particle->direction.ToMat3(); + // HUMANHEAD JRM - end + particle->age = 0; +} + +/* +================ +hhParticleSystem::RunSmokeStage +================ +*/ +void hhParticleSystem::RunSmokeStage( const int ev ) { + if (GetParticle()) { + idFXSmokeStage *stage = particleSystem->events[ev]; + if (stage->trails) { + return RunTrailStage( ev ); + } + float fraction; + //HUMANHEAD: aob + int numParticleToTrigger = 0; + idVec3 gravity; + //HUMANHEAD END + for( int i=0; ihidden && current->state != SMOKE_DEAD ) { + // do we need to insert another particle? + // HUMANHEAD JRM - made a while and seperated if + if (current->nozzle && current->frameCount >= current->triggerEvery && current->triggered < current->numToTrigger) { + //HUMANHEAD: aob + numParticleToTrigger = current->minToTrigger + gameLocal.random.RandomInt( current->maxToTrigger - current->minToTrigger ); + //HUMANHEAD END + while (numParticleToTrigger && current->triggered < current->numToTrigger) {//HUMANHEAD: aob - changed numToTrigger to numParticlesToTrigger + //HUMANHEAD: aob - moved logic to helper function + ResetSmokeParticle( ¤t->particles[current->triggered], current, stage ); + current->triggered++; + //HUMANHEAD: aob + --numParticleToTrigger; + //HUMANHEAD END + } + current->triggerEvery = stage->triggerEvery - gameLocal.random.RandomInt( stage->triggerEvery - stage->minTriggerEvery ); + current->frameCount = 0; + } + current->frameCount++; + // move them all, kill if we need to + idVec3 mwpVector; + mwpVector.Zero(); + if (stage->moveWithParent && current->groupOrigin != current->oldGroupOrigin) { + mwpVector = current->groupOrigin - current->oldGroupOrigin; + current->oldGroupOrigin = current->groupOrigin; + } + bool triggerAlive = false; + for( int j=0; jtriggered; j++ ) { + if ( current->particles[j].age <= stage->timeToFade ) { + smokeParticle *sParticle = ¤t->particles[j]; + sParticle->age += gameLocal.msec; + sParticle->rotation += (sParticle->rotationSpeed * stage->rotationMul); + idVec3 speed; // HUMANHEAD JRM - changed to vec3 from float + if (sParticle->age >= stage->timeToFinalSpeed) { + speed = stage->finalSpeed; + } else { + fraction = sParticle->age * stage->oneOverTimeToFinalSpeed; + speed = stage->initialSpeed + (stage->finalSpeed - stage->initialSpeed) * fraction; + } + //HUMANHEAD: aob + if (sParticle->age >= stage->timeToFinalGravity) { + gravity = stage->finalGravity; + } else { + fraction = sParticle->age * stage->oneOverTimeToFinalGravity; + gravity = stage->initialGravity + (stage->finalGravity - stage->initialGravity) * fraction; + } + //HUMANHEAD END + if (sParticle->age != 0) { + if (stage->slowSpawnSpeedOverLife) { + fraction = 1.0f - ((float)j / current->numToTrigger); + //HUMANHEAD: aob + sParticle->position += DetermineDeltaPos( speed * fraction, sParticle->xyzmove, sParticle->direction ); + //HUMANHEAD END + } else { + //HUMANHEAD: aob + sParticle->position += DetermineDeltaPos( speed, sParticle->xyzmove, sParticle->direction ); + //HUMANHEAD END + } + //HUMANHEAD: aob + sParticle->position += gravity; + //HUMANHEAD END + } + sParticle->position += mwpVector; + triggerAlive = true; + if ( sParticle->age >= stage->timeToFade ) { + if( current->nozzle && stage->continuous ) { + //HUMANHEAD: aob - moved logic into helper function + ResetSmokeParticle( sParticle, current, stage ); + //HUMANHEAD END + } + } + } + } + if (!triggerAlive) { + current->state = SMOKE_DEAD; + } + } + } + } +} + +/* +================ +hhParticleSystem::ResetSmokeParticle +================ +*/ +void hhParticleSystem::ResetSmokeParticle( smokeParticle* particle, smokeGroup* group, idFXSmokeStage* smokeStage ) { + float ang = 0.0f; + float size = 0.0f; + + particle->position = (smokeStage->moveWithParent) ? group->oldGroupOrigin : group->groupOrigin; + particle->direction = group->groupDirection; + particle->rotation = gameLocal.random.RandomInt(360); + particle->rotationSpeed = gameLocal.random.CRandomFloat() * 3.0f; + // HUMANHEAD JRM - need to make spawn direction spherical - NOT box + ang = gameLocal.random.RandomFloat() * hhMath::TWO_PI; + size = smokeStage->xymove; + if( !smokeStage->noXYMoveRandomness ) { + size *= gameLocal.random.RandomFloat(); + } + particle->xyzmove = idVec3( 0.0f, hhMath::Cos(ang) * size, hhMath::Sin(ang) * size ); + particle->xyzmove *= particle->direction.ToMat3(); + // HUMANHEAD JRM - end + particle->age = 0; +} + +/* +================ +hhParticleSystem::DetermineDeltaPos +================ +*/ +idVec3 hhParticleSystem::DetermineDeltaPos( const idVec3& speed, const idVec3& xyzmove, const idVec3& dir ) { + idVec3 deltaPos; + //Convert to world coords + idVec3 velocity = (dir + xyzmove) * dir.ToMat3().Transpose(); + + for( int ix = 0; ix < 3; ++ix ) { + deltaPos[ix] = speed[ix] * velocity[ix]; + } + + //Convert back to local coords + return deltaPos * dir.ToMat3(); +} + +/* +================ +hhParticleSystem::ReTrigger +================ +*/ +const int hhParticleSystem::ReTrigger( const idVec3& newOrigin, const idVec3& newDirection ) { + + // all triggers need to be checked, but all triggers have the same Num() <-- optimize + for( int i = 0; i < triggers[0].Num(); i++ ) { + if ( StageFinished(i) ) { + ResetStage(i, newOrigin, newDirection ); + return (myIndex<<16)|i; + } + } + + smokeParticle initial; + initial.position = vec3_origin; + initial.rotation = 0.0f; + initial.rotationSpeed = 0.0f; + initial.direction = vec3_origin; + //HUMANHEAD: aob + initial.xyzmove.Zero(); + //HUMANHEAD END + + GetParticle(); + for( int i=0; ievents.Num(); i++ ) { + smokeGroup* newGroup = new smokeGroup; + + newGroup->state = SMOKE_ALIVE; + //HUMANHEAD: aob + newGroup->triggered = 0; + newGroup->minToTrigger = particleSystem->events[i]->minToTrigger; + newGroup->maxToTrigger = particleSystem->events[i]->maxToTrigger; + newGroup->numToTrigger = (particleSystem->events[i]->randomNumToTrigger) ? newGroup->minToTrigger + gameLocal.random.RandomInt(newGroup->maxToTrigger - newGroup->minToTrigger) : particleSystem->events[i]->numToTrigger; + newGroup->particles.AssureSize( newGroup->numToTrigger, initial ); + //HUMANHEAD END + newGroup->triggerEvery = particleSystem->events[i]->triggerEvery - gameLocal.random.RandomInt( particleSystem->events[i]->triggerEvery - particleSystem->events[i]->minTriggerEvery ); + newGroup->frameCount = newGroup->triggerEvery; + //HUMANHEAD: aob + newGroup->groupDirection = (newDirection == vec3_origin) ? defaultDir : newDirection; + //HUMANHEAD END + newGroup->groupOrigin = newOrigin; + newGroup->oldGroupOrigin = newOrigin; + newGroup->nozzle = true; + newGroup->hidden = false; + newGroup->baseTime = 0; + triggers[i].Append( newGroup ); + } + return (myIndex<<16)|(triggers[0].Num()-1); +} + +/* +================ +hhParticleSystem::ResetStage +================ +*/ +void hhParticleSystem::ResetStage( const int st, const idVec3 &start, const idVec3& direction ) { + GetParticle(); + + //HUMANHEAD: aob + smokeParticle initial; + initial.position = vec3_origin; + initial.rotation = 0.0f; + initial.rotationSpeed = 0.0f; + initial.direction = vec3_origin; + initial.xyzmove.Zero(); + //HUMANHEAD END + + for( int i=0; ievents.Num(); i++ ) { + smokeGroup* current = triggers[i][st]; + current->state = SMOKE_ALIVE; + current->triggered = 0; + //HUMANHEAD: aob + current->minToTrigger = particleSystem->events[i]->minToTrigger; + current->maxToTrigger = particleSystem->events[i]->maxToTrigger; + current->numToTrigger = (particleSystem->events[i]->randomNumToTrigger) ? current->minToTrigger + gameLocal.random.RandomInt(current->maxToTrigger - current->minToTrigger) : particleSystem->events[i]->numToTrigger; + current->particles.AssureSize( current->numToTrigger, initial ); + //HUMANHEAD END + current->triggerEvery = particleSystem->events[i]->triggerEvery - gameLocal.random.RandomInt( particleSystem->events[i]->triggerEvery - particleSystem->events[i]->minTriggerEvery ); + current->frameCount = current->triggerEvery; + current->groupOrigin = start; + current->oldGroupOrigin = start; + //HUMANHEAD: aob + current->groupDirection = (direction == vec3_origin) ? defaultDir : direction; + //HUMANHEAD END + current->nozzle = true; + current->hidden = false; + current->baseTime = 0; + } +} + +/* +================ +hhParticleSystem::SetNozzleOrigin +================ +*/ +void hhParticleSystem::SetNozzleDirection( const int handle, const idVec3 &vec ) { + int st = TriggerForHandle( handle ); + GetParticle(); + for( int i=0; ievents.Num(); i++ ) { + smokeGroup* current = triggers[i][st]; + //HUMANHEAD: aob + current->groupDirection = (vec == vec3_origin) ? defaultDir : vec; + //HUMANHEAD END + } +} +#endif \ No newline at end of file diff --git a/src/Prey/particles_particles.h b/src/Prey/particles_particles.h new file mode 100644 index 0000000..fbf100d --- /dev/null +++ b/src/Prey/particles_particles.h @@ -0,0 +1,13 @@ +// COMMENTED OUT +#ifndef __PREY_PARTICLES_PARTICLES__ +#define __PREY_PARTICLES_PARTICLES__ + +class hhSmokeParticles : public idSmokeParticles { + public: + hhSmokeParticles(); + + protected: + static const idVec3 defaultDir; +}; + +#endif \ No newline at end of file diff --git a/src/Prey/physics_delta.cpp b/src/Prey/physics_delta.cpp new file mode 100644 index 0000000..f6e5a89 --- /dev/null +++ b/src/Prey/physics_delta.cpp @@ -0,0 +1,155 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION( idPhysics_Actor, hhPhysics_Delta ) +END_CLASS + +//============ +// hhPhysics_Delta::hhPhysics_Delta +//============ +hhPhysics_Delta::hhPhysics_Delta() { + + delta.Zero(); +} //. hhPhysics_Delta::hhPhysics_Delta() + + +//============ +// hhPhysics_Delta::Evaluate +//============ +bool hhPhysics_Delta::Evaluate( int timeStepMSec, int endTimeMSec ) { + trace_t trace; + idVec3 dest; + idRotation rotation; + float timeStep; + idVec3 velocity; + + + timeStep = MS2SEC( timeStepMSec ); + velocity = delta / timeStep; + + if ( delta == vec3_zero ) { + Rest(); + return( false ); + } + + clipModel->Unlink(); // Taken from monster + + dest = GetOrigin() + delta; + rotation.SetOrigin( GetOrigin() ); + rotation.SetAngle( 0 ); + + //? Maybe model after the idPhysics_Monster::Slide move, where we + // slide along scholng + + // If there was a collision, adjust the dest + if ( gameLocal.clip.Motion( trace, GetOrigin(), dest, rotation, clipModel, + GetAxis(), clipMask, self ) ) { + if ( self->AllowCollision( trace ) ) { + //gameLocal.Printf( "Hit something %.2f\n", trace.fraction ); + + dest = trace.endpos; + + //? Move this elsewhere? + self->Collide( trace, velocity ); + } //. We run into what we hit = ) + + //? Maybe we apply an impulse? + } //. We hit something + + //gameLocal.Printf( "Moving from %s to %s (%s)\n", GetOrigin().ToString(), + // dest.ToString(), delta.ToString() ); + + + //! What about origin and axis? + clipModel->Link( gameLocal.clip, self, 0, dest, GetAxis() ); // Taken from monster + + // We are done with delta, so zero it out + delta.Zero(); + + return( true ); +} //. hhPhysics_Delta::Evaluate( int, int ); + + +//============ +// hhPhysics_Delta::SetDelta +//============ +void hhPhysics_Delta::SetDelta( const idVec3 &d ){ + + delta = d; + + if ( delta != vec3_origin ) { + Activate(); + } +} //. hhPhysics_Delta::SetDelta( const idVec3 & ) + + +//============ +// hhPhysics_Delta::Activate +//============ +void hhPhysics_Delta::Activate(){ + + self->BecomeActive( TH_PHYSICS ); + +} //. hhPhysics_Delta::Activate( const idVec3 & ) + + +//============ +// hhPhysics_Delta::Rest +//============ +void hhPhysics_Delta::Rest(){ + + self->BecomeInactive( TH_PHYSICS ); + +} //. hhPhysics_Delta::Rest( const idVec3 & ) + + + +//================ +//hhPhysics_Delta::SetAxis +//================ +void hhPhysics_Delta::SetAxis( const idMat3 &newAxis, int id ) { + // Ripped from idPhysics_Monster + clipModel->Link( gameLocal.clip, self, 0, clipModel->GetOrigin(), newAxis ); + //? Activate(); +} + + +//================ +//hhPhysics_Delta::SetOrigin +//================ +void hhPhysics_Delta::SetOrigin( const idVec3 &newOrigin, int id ) { + // Ripped from idPhysics_Monster + /* + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localOrigin = newOrigin; + if ( masterEntity ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + newOrigin * masterAxis; + } + else { + current.origin = newOrigin; + } + */ + clipModel->Link( gameLocal.clip, self, 0, newOrigin, clipModel->GetAxis() ); + //? Activate(); +} + +//================ +//hhPhysics_Delta::Save +//================ +void hhPhysics_Delta::Save( idSaveGame *savefile ) const { + savefile->WriteVec3( delta ); +} + +//================ +//hhPhysics_Delta::Restore +//================ +void hhPhysics_Delta::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( delta ); +} + diff --git a/src/Prey/physics_delta.h b/src/Prey/physics_delta.h new file mode 100644 index 0000000..76d1505 --- /dev/null +++ b/src/Prey/physics_delta.h @@ -0,0 +1,34 @@ +#ifndef __PREY_PHYSICS_DELTA_H__ +#define __PREY_PHYSICS_DELTA_H__ + + + +class hhPhysics_Delta : public idPhysics_Actor { + + public: + CLASS_PROTOTYPE( hhPhysics_Delta ); + + hhPhysics_Delta(); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + + + void SetDelta( const idVec3 &d ); + + void Activate(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + protected: + void Rest(); + + idVec3 delta; + +}; + +#endif /* __PREY_PHYSICS_DELTA_H__ */ diff --git a/src/Prey/physics_preyai.cpp b/src/Prey/physics_preyai.cpp new file mode 100644 index 0000000..71546f8 --- /dev/null +++ b/src/Prey/physics_preyai.cpp @@ -0,0 +1,573 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +// HUMANHEAD nla - Class constants +const int hhPhysics_AI::NO_FLY_DIRECTION = 1000; + +CLASS_DECLARATION( idPhysics_Monster, hhPhysics_AI ) +END_CLASS + +/* +===================== +hhPhysics_AI::hhPhysics_AI +===================== +*/ +hhPhysics_AI::hhPhysics_AI() { + // HUMANHEAD nla + flyStepDirection = NO_FLY_DIRECTION; + //HUMANHEAD END + lastMoveTouch = NULL; + useGravity = TRUE; + bGravClipModelAxis = false; +} + +/* +===================== +hhPhysics_AI::SlideMove +===================== +// HUMANHEAD nla - if touched is NULL, the touched entities will be added +// directly. Otherwise they will be added to the list +*/ +monsterMoveResult_t hhPhysics_AI::SlideMove( idVec3 &start, idVec3 &velocity, const idVec3 &delta, idList *touched ) { + trace_t tr; + idVec3 move; + int i; + + blockingEntity = NULL; + move = delta; + for( i = 0; i < 3; i++ ) { + gameLocal.clip.Translation( tr, start, start + move, clipModel, clipModel->GetAxis(), clipMask, self ); + + if ( tr.c.entityNum != ENTITYNUM_WORLD && tr.c.entityNum != ENTITYNUM_NONE && gameLocal.entities[ tr.c.entityNum ] ) + lastMoveTouch = gameLocal.entities[ tr.c.entityNum ]; + + + 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 ); + */ + + // HUMANHEAD nla - Added logic to allow monsters to push + // if we can push other entities and not blocked by the world. Added PushCosine + if ( self->Pushes() && ( tr.c.entityNum != ENTITYNUM_WORLD ) ) { + // Early out if we aren't facing enough towards the object + idVec3 normVelocity( velocity ); + + normVelocity.NormalizeFast(); + /* + gameLocal.Printf( "%d Trying %s * %s = %.2f vs %.2f\n", gameLocal.time, + normVelocity.ToString(), tr.c.normal.ToString(), + idMath::Fabs( normVelocity * tr.c.normal ), self->PushCosine() ); + */ + if ( idMath::Fabs( normVelocity * tr.c.normal ) >= self->PushCosine() ) { + trace_t trPush; + int pushFlags; + float totalMass; + + clipModel->SetPosition( start, 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; + + // clip & push + totalMass = gameLocal.push.ClipTranslationalPush( trPush, self, pushFlags, start + move, move ); + + if ( totalMass > 0.0f ) { + // decrease velocity based on the total mass of the objects being pushed ? + /*? Put back in later? + if ( velocity.LengthSqr() > Square( crouchSpeed * 0.8f ) ) { + velocity *= crouchSpeed * 0.8f / velocity.Length(); + } + pushed = true; + */ + } + + // Added logic to prevent pushing + tr = trPush; + start = tr.endpos; + // time_left -= time_left * trace.fraction; + + // if moved the entire distance + // Changed from orig. + /* + if ( trace.fraction >= 1.0f ) { + break; + } + */ + // Changed to this! = ) + if ( tr.fraction == 1.0f ) { + if ( i > 0 ) { + return MM_SLIDING; + } + return MM_OK; + } + } + } + // HUMANHEAD END + + // clip the movement delta and velocity + move.ProjectOntoPlane( tr.c.normal, OVERCLIP ); + velocity.ProjectOntoPlane( tr.c.normal, OVERCLIP ); + } + +/* NOTE: Underlying code changed some, fit this back in if needed. + //HUMANHEAD nla + if (touched == NULL) { + if (tr.fraction < 1.0f) { + AddTouchEnt(tr.c.entityNum); + } + } + else { + touched->Append(tr.c.entityNum); + } +*/ + return MM_BLOCKED; +} + +// HUMANHEAD nla +/* +===================== +hhPhysics_AI::FlyMove + + move start into the delta direction + the velocity is clipped conform any collisions +===================== +*/ +#define NUM_DIRECTIONS 2 +monsterMoveResult_t hhPhysics_AI::FlyMove( idVec3 &start, idVec3 &velocity, const idVec3 &delta ) { + trace_t tr; + idVec3 up, down, noStepPos, noStepVel, stepPos, stepVel, dirGrav; + monsterMoveResult_t result1, result2; + idVec3 originalStart, originalVelocity, bestStart, bestVelocity; + float noStepDistSq, distSq, bestDistSq; + int bestFlyStepDirection; + monsterMoveResult_t bestReturn; + idList noStepEntities, stepEntities, bestEntities; + + + if ( delta == vec3_origin ) { + return MM_OK; + } + + // Initialize + originalStart = start; + originalVelocity = velocity; + bestFlyStepDirection = NO_FLY_DIRECTION; + + // try to move without stepping up + noStepPos = start; + noStepVel = velocity; + result1 = SlideMove( noStepPos, noStepVel, delta, &noStepEntities ); + if ( result1 == MM_OK ) { + AddTouchEntList( noStepEntities ); + start = noStepPos; + velocity = noStepVel; + return MM_OK; + } + + // Assume no stepping is the best + bestStart = noStepPos; + bestVelocity = noStepVel; + bestReturn = result1; + bestEntities = noStepEntities; + // Add small fudge factor to take into account float issues + noStepDistSq = ((noStepPos - originalStart) * 1.001f).LengthSqr(); + bestDistSq = noStepDistSq; + + // Try to move around the obstacle + for ( int direction = 0; direction < NUM_DIRECTIONS ; direction++ ) { + stepEntities.Clear(); + + //! Maybe make this relative to the clip model? + if (direction == 0) { // Set the direction. Assumes 2x + dirGrav.Set(0, 0, -1); + } + else { + dirGrav *= -1; + } + + // try to step "up" + up = start - dirGrav * maxStepHeight; + gameLocal.clip.Translation( tr, start, up, clipModel, clipModel->GetAxis(), clipMask, self ); + if ( tr.fraction == 0.0f ) { + start = originalStart; // Reset + velocity = originalVelocity; + continue; + } + + // Add the entity touched + if ( tr.fraction < 1.0f ) { + stepEntities.Append( tr.c.entityNum ); + } + + // try to move at the stepped up position + stepPos = tr.endpos; + stepVel = velocity; + result2 = SlideMove( stepPos, stepVel, delta, &stepEntities ); + if ( result2 == MM_BLOCKED ) { // Couldn't move all the way at stepped pos + start = originalStart; // Reset + velocity = originalVelocity; + distSq = (stepPos - tr.endpos).LengthSqr(); // See if we moved further up there. Ignore the step up + if ((distSq > bestDistSq)) { + bestStart = stepPos; + bestVelocity = stepVel; + bestReturn = result2; + bestEntities = stepEntities; + bestDistSq = distSq; + bestFlyStepDirection = direction; + } + continue; + } + + // Stepping is the best move + start = originalStart; // Reset + velocity = originalVelocity; + distSq = ( stepPos - originalStart ).LengthSqr(); // Include the distance up we traveled + if ( ( direction == flyStepDirection ) || // Going the same direction as last time + ( ( ( direction < flyStepDirection ) || // Haven't tested the last direction + ( bestFlyStepDirection != flyStepDirection ) ) && // Have tested the last, but coundn't go that way + ( distSq > bestDistSq ) ) // Closer than the best + ) { + bestStart = stepPos; // Save the no step version + bestVelocity = stepVel; + bestReturn = MM_STEPPED; + bestEntities = stepEntities; + bestDistSq = distSq; + bestFlyStepDirection = direction; + } + + } //. Direction loop + + // Didn't work, use the no step return + start = bestStart; + velocity = bestVelocity; + flyStepDirection = bestFlyStepDirection; + AddTouchEntList( bestEntities ); + + return( bestReturn ); +} + +/* +================ +hhPhysics_AI::Evaluate +================ +*/ +bool hhPhysics_AI::Evaluate( int timeStepMSec, int endTimeMSec ) { + idVec3 masterOrigin, oldOrigin; + idMat3 masterAxis; + float timeStep; + float oldMasterYaw, oldMasterDeltaYaw; // HUMANHEAD pdm + + timeStep = MS2SEC( timeStepMSec ); + + moveResult = MM_OK; + blockingEntity = NULL; + oldOrigin = current.origin; + oldMasterYaw = masterYaw; // HUMANHEAD pdm + oldMasterDeltaYaw = masterDeltaYaw; // HUMANHEAD pdm + + //HUMANHEAD: aob + HadGroundContacts( HasGroundContacts() ); + //HUMANHEAD END + + // if bound to a master + if ( masterEntity ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + current.localOrigin * masterAxis; + clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() ); + //HUMANHEAD rww + if (!timeStep) { + current.velocity = vec3_origin; + } + else { + //HUMANHEAD END + current.velocity = ( current.origin - oldOrigin ) / timeStep; + } + masterDeltaYaw = masterYaw; + masterYaw = masterAxis[0].ToYaw(); + masterDeltaYaw = masterYaw - masterDeltaYaw; + + // HUMANHEAD pdm: If we haven't moved and master is at rest, put me to rest + if (current.origin == oldOrigin && masterYaw == oldMasterYaw && masterDeltaYaw == oldMasterDeltaYaw) { + if (masterEntity->IsAtRest()) { + Rest(); + } + return false; + } + // HUMANHEAD END + return true; + } + + // if the monster is at rest + if ( current.atRest >= 0 ) { + return false; + } + + assert(timeStep != 0.0f); //HUMANHEAD rww + + ActivateContactEntities(); + + // move the monster velocity into the frame of a pusher + current.velocity -= current.pushVelocity; + + clipModel->Unlink(); + + // check if on the ground + //HUMANHEAD: aob - moved ground check to after movement + + // 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 || self->fl.isTractored || ( !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 ) { + //HUMANHEAD: aob - removed scope hardcode + moveResult = SlideMove( current.origin, current.velocity, delta ); + delta.Zero(); + } + + if ( !fly && !self->fl.isTractored && IsGravityEnabled()) { // HUMANHEAD JRM: Gravity toggle + 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 + //HUMANHEAD: aob + current.velocity = delta / timeStep; + //HUMANHEAD END + + //HUMANHEAD: aob - removed scope hardcode + //moveResult = idPhysics_Monster::StepMove( current.origin, current.velocity, delta ); + if( IsGravityEnabled() ) { + moveResult = StepMove( current.origin, current.velocity, delta ); + } else { + moveResult = SlideMove( current.origin, current.velocity, delta ); + } + + delta.Zero(); + } + } + + //HUMANHEAD: aob - for changing gravity zones + idVec3 rotationCheckOrigin = GetOrigin() + GetAxis()[2] * GetBounds()[1].z; + IterativeRotateMove( GetAxis()[2], -GetGravityNormal(), GetOrigin(), rotationCheckOrigin, p_iterRotMoveNumIterations.GetInteger() ); + //HUMANHEAD END + + clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() ); + + // check if on the ground + //HUMANHEAD: aob - removed scope hardcode + CheckGround( current ); + + // get all the ground contacts + EvaluateContacts(); + + // move the monster velocity back into the world frame + current.velocity += current.pushVelocity; + current.pushVelocity.Zero(); + + if ( IsOutsideWorld() ) { + // HUMANHEAD pdm: Allow some things to go outside world without warning + if (!self->IsType(hhWraith::Type) && !self->IsType( hhTalon::Type ) ) { + gameLocal.Warning( "clip model outside world bounds for entity '%s' at (%s)", self->name.c_str(), current.origin.ToString(0) ); + } + Rest(); + } + + return ( current.origin != oldOrigin ); +} + +/* +================== +hhPhysics_AI::ApplyFriction + +Handles both ground friction and water friction + +//HUMANHEAD: aob +================== +*/ +idVec3 hhPhysics_AI::ApplyFriction( const idVec3& vel, const float deltaTime ) { + float speed = 0.0f; + float newSpeed = 0.0f; + float control = 0.0f; + float drop = 0.0f; + idVec3 velocity = vel; + + speed = velocity.Length(); + if( speed <= VECTOR_EPSILON ) { + return vec3_origin; + } + + if( fly || self->fl.isTractored ) { + drop += speed * PM_FLYFRICTION * deltaTime; + } + else if( !current.onGround ) { + drop += speed * PM_AIRFRICTION * deltaTime; + } + else if( current.onGround ) { + if( !(GetGroundSurfaceFlags() & SURF_SLICK) ) { + drop += speed * PM_FRICTION * deltaTime; + } + } + + // scale the velocity + newSpeed = speed - drop; + if( newSpeed < 0.0f ) { + newSpeed = 0.0f; + } + + return vel * ( newSpeed / speed ); +} + +/* +================ +hhPhysics_AI::AddForce + HUMANHEAD pdm: added so that springs will work on monsters +================ +*/ +void hhPhysics_AI::AddForce( const int id, const idVec3 &point, const idVec3 &force ) { + // HUMANHEAD pdm: so we can use forces on them + current.velocity += (force / GetMass()) * USERCMD_ONE_OVER_HZ; + + Activate(); +} + +/* +================ +hhPhysics_AI::SetLinearVelocity +================ +*/ +void hhPhysics_AI::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { + current.velocity = newLinearVelocity; + + Activate(); +} + +/* +================ +hhPhysics_AI::GetLinearVelocity +================ +*/ +const idVec3& hhPhysics_AI::GetLinearVelocity( int id ) const { + return current.velocity; +} + +/* +================ +hhPhysics_AI::ApplyImpulse +================ +*/ +void hhPhysics_AI::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) { + // HUMANHEAD: aob - so we can use forces on them (crane) + current.velocity += impulse / GetMass(); + // HUMANHEAD END + + Activate(); +} + +/* +================ +hhPhysics_AI::SetMaster +overridden to set localAxis +================ +*/ +void hhPhysics_AI::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(); + idAI *entAI = static_cast(self); + if ( entAI ) { + idAngles angles( 0, entAI->spawnArgs.GetFloat( "angle" ), 0 ); + localAxis = angles.ToMat3() * masterAxis.Transpose(); + } + } + ClearContacts(); + } + else { + if ( masterEntity ) { + masterEntity = NULL; + Activate(); + } + } +} + +/* +================ +hhPhysics_AI::SetGravity +overridden to call SetClipModelAxis for asteroid gravity +================ +*/ +void hhPhysics_AI::SetGravity( const idVec3 &newGravity ) { + idPhysics_Monster::SetGravity( newGravity ); + if ( bGravClipModelAxis ) { + SetClipModelAxis(); + } +} + +//================ +//hhPhysics_AI::Save +//================ +void hhPhysics_AI::Save( idSaveGame *savefile ) const { + savefile->WriteInt( flyStepDirection ); + lastMoveTouch.Save( savefile ); + savefile->WriteBool( useGravity ); + savefile->WriteMat3( localAxis ); + savefile->WriteBool( bGravClipModelAxis ); +} + +//================ +//hhPhysics_AI::Restore +//================ +void hhPhysics_AI::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( flyStepDirection ); + lastMoveTouch.Restore( savefile ); + savefile->ReadBool( useGravity ); + savefile->ReadMat3( localAxis ); + savefile->ReadBool( bGravClipModelAxis ); +} + diff --git a/src/Prey/physics_preyai.h b/src/Prey/physics_preyai.h new file mode 100644 index 0000000..1c2f5c7 --- /dev/null +++ b/src/Prey/physics_preyai.h @@ -0,0 +1,45 @@ +#ifndef __HH_PHYSICS_AI +#define __HH_PHYSICS_AI + +class hhPhysics_AI : public idPhysics_Monster { + CLASS_PROTOTYPE( hhPhysics_AI ); + + public: + hhPhysics_AI(); + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + const idVec3& GetLinearVelocity( int id = 0 ) const; + virtual void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + void AddForce( const int id, const idVec3 &point, const idVec3 &force ); + void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ); + idVec3 GetLocalOrigin( void ) { return current.localOrigin; } + + idEntity* GetLastMoveTouch(void) {return lastMoveTouch.GetEntity();} + + void EnableGravity(bool tf = true) {useGravity = tf;} + bool IsGravityEnabled(void) const {return useGravity;} + void SetMaster( idEntity *master, const bool orientated ); + void SetGravity( const idVec3 &newGravity ); + void SetGravClipModelAxis( bool enable ) { bGravClipModelAxis = enable; } + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + protected: + idVec3 ApplyFriction( const idVec3& vel, const float deltaTime ); + virtual monsterMoveResult_t FlyMove( idVec3 &start, idVec3 &velocity, const idVec3 &delta ); + virtual monsterMoveResult_t SlideMove( idVec3 &start, idVec3 &velocity, const idVec3 &delta, idList *touched = NULL ); + + // HUMANHEAD nla - Constants + public: + static const int NO_FLY_DIRECTION; + idMat3 localAxis; + + protected: + int flyStepDirection; // Direction we were last stepping in + idEntityPtr lastMoveTouch; // Entity we touched on last slidemove + bool useGravity; + bool bGravClipModelAxis; // Set clipmodel axis for gravity changes +}; + +#endif \ No newline at end of file diff --git a/src/Prey/physics_preyparametric.cpp b/src/Prey/physics_preyparametric.cpp new file mode 100644 index 0000000..4808a3b --- /dev/null +++ b/src/Prey/physics_preyparametric.cpp @@ -0,0 +1,302 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION( idPhysics_Static, hhPhysics_StaticWeapon ) +END_CLASS + +/* +============ +hhPhysics_StaticWeapon::hhPhysics_StaticWeapon +============ +*/ +hhPhysics_StaticWeapon::hhPhysics_StaticWeapon() { + castSelf = NULL; + selfOwner = NULL; +} + +/* +============ +hhPhysics_StaticWeapon::SetSelfOwner +============ +*/ +void hhPhysics_StaticWeapon::SetSelfOwner( idActor* a ) { + if( self && self->IsType(hhWeapon::Type) ) { + castSelf = static_cast( self ); + } + + if( a && a->IsType(hhPlayer::Type) ) { + selfOwner = static_cast( a ); + } +} + +/* +============ +hhPhysics_StaticWeapon::SetLocalAxis +============ +*/ +void hhPhysics_StaticWeapon::SetLocalAxis( const idMat3& newLocalAxis ) { + current.localAxis = newLocalAxis; +} + +/* +============ +hhPhysics_StaticWeapon::SetLocalOrigin +============ +*/ +void hhPhysics_StaticWeapon::SetLocalOrigin( const idVec3& newLocalOrigin ) { + current.localOrigin = newLocalOrigin; +} + +/* +============ +hhPhysics_StaticWeapon::Evaluate +============ +*/ +bool hhPhysics_StaticWeapon::Evaluate( int timeStepMSec, int endTimeMSec ) { + if( !selfOwner ) { + return false; + } + + idMat3 localAxis; + idVec3 localOrigin( 0.0f, 0.0f, selfOwner->EyeHeight() ); + idAngles pitchAngles( selfOwner->GetUntransformedViewAngles().pitch, 0.0f, 0.0f ); + + if( selfOwner->InVehicle() ) { + localAxis = pitchAngles.ToMat3(); + } else { + localAxis = ( pitchAngles + selfOwner->GunTurningOffset() ).ToMat3(); + localOrigin += selfOwner->GunAcceleratingOffset(); + if ( castSelf ) { + castSelf->MuzzleRise( localOrigin, localAxis ); + } + } + + SetLocalAxis( localAxis ); + SetLocalOrigin( localOrigin ); + + return idPhysics_Static::Evaluate( timeStepMSec, endTimeMSec ); +} + +//================ +//hhPhysics_StaticWeapon::Save +//================ +void hhPhysics_StaticWeapon::Save( idSaveGame *savefile ) const { + savefile->WriteObject( castSelf ); + savefile->WriteObject( selfOwner ); +} + +//================ +//hhPhysics_StaticWeapon::Restore +//================ +void hhPhysics_StaticWeapon::Restore( idRestoreGame *savefile ) { + savefile->ReadObject( reinterpret_cast( castSelf ) ); + savefile->ReadObject( reinterpret_cast( selfOwner ) ); +} + +CLASS_DECLARATION( idPhysics_Static, hhPhysics_StaticForceField ) +END_CLASS + +/* +================ +hhPhysics_StaticForceField::hhPhysics_StaticForceField +================ +*/ +hhPhysics_StaticForceField::hhPhysics_StaticForceField() { +} + +/* +================ +hhPhysics_StaticForceField::Evaluate +================ +*/ +bool hhPhysics_StaticForceField::Evaluate( int timeStepMSec, int endTimeMSec ) { + bool moved = idPhysics_Static::Evaluate( timeStepMSec, endTimeMSec ); + + EvaluateContacts(); + + return moved; +} + +/* +================ +hhPhysics_StaticForceField::AddContactEntitiesForContacts +================ +*/ +void hhPhysics_StaticForceField::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 ); + } + } +} + +/* +================ +hhPhysics_StaticForceField::ActivateContactEntities +================ +*/ +void hhPhysics_StaticForceField::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-- ); + } + } +} + +/* +================ +hhPhysics_StaticForceField::EvaluateContacts +================ +*/ +bool hhPhysics_StaticForceField::EvaluateContacts( void ) { + idVec6 dir; + int num; + + ClearContacts(); + + contacts.SetNum( 10, false ); + + dir.SubVec3(0).Zero(); + dir.SubVec3(1).Zero(); + //dir.SubVec3(0).Normalize(); + //dir.SubVec3(1).Normalize(); + num = gameLocal.clip.Contacts( &contacts[0], 10, clipModel->GetOrigin(), + dir, CONTACT_EPSILON, clipModel, clipModel->GetAxis(), MASK_SOLID, self ); + contacts.SetNum( num, false ); + + AddContactEntitiesForContacts(); + + return ( contacts.Num() != 0 ); +} + +/* +================ +hhPhysics_StaticForceField::GetNumContacts +================ +*/ +int hhPhysics_StaticForceField::GetNumContacts( void ) const { + return contacts.Num(); +} + +/* +================ +hhPhysics_StaticForceField::GetContact +================ +*/ +const contactInfo_t &hhPhysics_StaticForceField::GetContact( int num ) const { + return contacts[num]; +} + +/* +================ +hhPhysics_StaticForceField::ClearContacts +================ +*/ +void hhPhysics_StaticForceField::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 ); +} + +/* +================ +hhPhysics_StaticForceField::AddContactEntity +================ +*/ +void hhPhysics_StaticForceField::AddContactEntity( 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 ) { + return; + } + } + contactEntities.Alloc() = e; +} + +/* +================ +hhPhysics_StaticForceField::RemoveContactEntity +================ +*/ +void hhPhysics_StaticForceField::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; + } + } +} + +//================ +//hhPhysics_StaticForceField::Save +//================ +void hhPhysics_StaticForceField::Save( idSaveGame *savefile ) const { + int i; + 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++ ) { + //HUMANHEAD PCF mdl 04/26/06 - Changed '->' to a '.', as it should have been + contactEntities[i].Save( savefile ); + } +} + +//================ +//hhPhysics_StaticForceField::Restore +//================ +void hhPhysics_StaticForceField::Restore( idRestoreGame *savefile ) { + int i; + int num; + + savefile->ReadInt( num ); + contacts.SetNum( num ); + for ( i = 0; i < num; i++ ) { + savefile->ReadContactInfo( contacts[i] ); + } + + savefile->ReadInt( num ); + contactEntities.SetNum( num ); + for ( i = 0; i < num; i++ ) { + //HUMANHEAD PCF mdl 04/26/06 - Changed '->' to a '.', as it should have been + contactEntities[i].Restore( savefile ); + } +} + diff --git a/src/Prey/physics_preyparametric.h b/src/Prey/physics_preyparametric.h new file mode 100644 index 0000000..6169304 --- /dev/null +++ b/src/Prey/physics_preyparametric.h @@ -0,0 +1,54 @@ + +#ifndef __PREY_PHYSICS_PARAMETRIC_H__ +#define __PREY_PHYSICS_PARAMETRIC_H__ + +class hhWeapon; +class hhPlayer; + +class hhPhysics_StaticWeapon : public idPhysics_Static { + CLASS_PROTOTYPE( hhPhysics_StaticWeapon ); + + public: + hhPhysics_StaticWeapon(); + + virtual void SetSelfOwner( idActor* a ); + virtual bool Evaluate( int timeStepMSec, int endTimeMSec ); + + void SetLocalAxis( const idMat3& newLocalAxis ); + void SetLocalOrigin( const idVec3& newLocalOrigin ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + protected: + hhWeapon* castSelf; + hhPlayer* selfOwner; +}; + +class hhPhysics_StaticForceField : public idPhysics_Static { + CLASS_PROTOTYPE( hhPhysics_StaticForceField ); + + public: + hhPhysics_StaticForceField(); + + virtual bool Evaluate( int timeStepMSec, int endTimeMSec ); + + virtual bool EvaluateContacts( void ); + virtual int GetNumContacts( void ) const; + virtual const contactInfo_t & GetContact( int num ) const; + virtual void ClearContacts( void ); + virtual void AddContactEntitiesForContacts( void ); + virtual void AddContactEntity( idEntity *e ); + virtual void RemoveContactEntity( idEntity *e ); + + virtual void ActivateContactEntities( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + protected: + idList contacts; // contacts + idList contactEntities; +}; + +#endif /* __PREY_PHYSICS_PARAMETRIC_H__ */ diff --git a/src/Prey/physics_simple.cpp b/src/Prey/physics_simple.cpp new file mode 100644 index 0000000..428013b --- /dev/null +++ b/src/Prey/physics_simple.cpp @@ -0,0 +1,50 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION( idPhysics_RigidBody, hhPhysics_RigidBodySimple ) +END_CLASS + +/* +================ +SimpleRigidBodyDerivatives +================ +*/ +void SimpleRigidBodyDerivatives( const float t, const void *clientData, const float *state, float *derivatives ) { + const hhPhysics_RigidBodySimple *p = (hhPhysics_RigidBodySimple *) 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; + + // derivatives + d->linearVelocity = p->inverseMass * s->linearMomentum; + d->angularMatrix.Zero(); + //d->angularMatrix = SkewSymmetric( vec3_zero ) * s->orientation; + d->force = - p->linearFriction * s->linearMomentum + p->current.externalForce; + d->torque.Zero(); +} + +/* +================ +hhPhysics_RigidBodySimple::hhPhysics_RigidBodySimple +================ +*/ +hhPhysics_RigidBodySimple::hhPhysics_RigidBodySimple() { + SAFE_DELETE_PTR( integrator ); + integrator = new idODE_Euler( sizeof(rigidBodyIState_t) / sizeof(float), SimpleRigidBodyDerivatives, this ); +} + +/* +================ +hhPhysics_RigidBodySimple::Integrate +================ +*/ +void hhPhysics_RigidBodySimple::Integrate( const float deltaTime, rigidBodyPState_t &next ) { + idPhysics_RigidBody::Integrate( deltaTime, next ); +} \ No newline at end of file diff --git a/src/Prey/physics_simple.h b/src/Prey/physics_simple.h new file mode 100644 index 0000000..ea26372 --- /dev/null +++ b/src/Prey/physics_simple.h @@ -0,0 +1,15 @@ +#ifndef __HH_PHYSICS_SIMPLE_H__ +#define __HH_PHYSICS_SIMPLE_H__ + +class hhPhysics_RigidBodySimple : public idPhysics_RigidBody { + CLASS_PROTOTYPE( hhPhysics_RigidBodySimple ); + public: + hhPhysics_RigidBodySimple(); + + virtual void Integrate( const float deltaTime, rigidBodyPState_t &next ); + + protected: + friend void SimpleRigidBodyDerivatives( const float t, const void *clientData, const float *state, float *derivatives ); +}; + +#endif \ No newline at end of file diff --git a/src/Prey/physics_vehicle.cpp b/src/Prey/physics_vehicle.cpp new file mode 100644 index 0000000..ccb9b1f --- /dev/null +++ b/src/Prey/physics_vehicle.cpp @@ -0,0 +1,234 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +//#define DEBUG_VEHICLE_PHYSICS 1 +#define VEHICLE_DEBUG if(g_vehicleDebug.GetInteger()) gameLocal.Printf +#if DEBUG_VEHICLE_PHYSICS + #define CheckSolid(a, b, c, d, e, f) DoCheckSolid(a, b, c, d, e, f) +#else + #define CheckSolid(a, b, c, d, e, f) 0 +#endif + +//------------------------------------------------------------- +// +// CheckSolid: utility function for checking for collision errors +// +//------------------------------------------------------------- +bool DoCheckSolid(const char *text, idVec3 &pos, idClipModel *clipModel, int clipMask, idMat3 &axis, idEntity *pass) { + int myContents = gameLocal.clip.Contents(pos, clipModel, axis, clipMask, pass); + if( myContents ) { + VEHICLE_DEBUG("%s\n", text); + VEHICLE_DEBUG(" position: %s\n", pos.ToString()); + VEHICLE_DEBUG(" orientation: %s\n", axis.ToAngles().ToString()); + VEHICLE_DEBUG(" contents: %d\n", myContents); + return true; + } + return false; +} + + +CLASS_DECLARATION( hhPhysics_RigidBodySimple, hhPhysics_Vehicle ) +END_CLASS + +//------------------------------------------------------------- +// +// hhPhysics_Vehicle::VehicleMotion +// +//------------------------------------------------------------- +bool hhPhysics_Vehicle::VehicleMotion(trace_t &collision, const idVec3 &start, const idVec3 &end, const idMat3 &axis) { + trace_t translationalTrace; + idVec3 move = end - start; + idVec3 curPosition = start; + idMat3 curAxis = axis; + + memset(&collision, 0, sizeof(collision)); // sanity + + collision.fraction = 1.0f; // Assume success until proven otherwise + + // + // try to slide move + // + for(int i = 0; i < 3; i++ ) { + + gameLocal.clip.Translation( translationalTrace, curPosition, curPosition + move, clipModel, curAxis, clipMask, self ); + curPosition = translationalTrace.endpos; + + CheckSolid(va("SlideMoved (%d) into something solid during slidemove!", i), curPosition, clipModel, clipMask, curAxis, self); + + // Keep the shortest frac + if (translationalTrace.fraction < collision.fraction) { + collision.fraction = translationalTrace.fraction; + collision.c = translationalTrace.c; + } + + if ( translationalTrace.fraction == 1.0f ) { + break; + } + + // Shorten the move by the amount moved + move *= (1.0f - translationalTrace.fraction); + + // project movement and momentum onto the sliding surface + move.ProjectOntoPlane( translationalTrace.c.normal, OVERCLIP ); + } + + collision.endAxis = curAxis; + collision.endpos = curPosition; + + return collision.fraction < 1.0f; +} + +//------------------------------------------------------------- +// +// hhPhysics_Vehicle::CheckForCollisions +// +// Evaluate the impulse based rigid body physics. +// in the event of a collision, tries to do a slide move +//------------------------------------------------------------- +bool hhPhysics_Vehicle::CheckForCollisions( const float deltaTime, rigidBodyPState_t &next, trace_t &collision ) { + bool collided = false; + + CheckSolid("Before VehicleMotion: in something solid!", current.i.position, clipModel, clipMask, current.i.orientation, self); + + collided = VehicleMotion( collision, current.i.position, next.i.position, current.i.orientation ); + if( collided ) { + // set the next state to the state at the moment of impact + next.i.position = collision.endpos; + next.i.orientation = collision.endAxis; + collided = true; + } + + CheckSolid("After VehicleMotion: in something solid!", next.i.position, clipModel, clipMask, next.i.orientation, self); + + return collided; +} + +/* +================ +hhPhysics_Vehicle::SetFriction +================ +*/ +void hhPhysics_Vehicle::SetFriction( const float linear, const float angular, const float contact ) { + //don't cap the friction for the vehicle + linearFriction = linear; + angularFriction = angular; + contactFriction = contact; +} + +const float VEH_VELOCITY_MAX = 16000; +const int VEH_VELOCITY_TOTAL_BITS = 16; +const int VEH_VELOCITY_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( VEH_VELOCITY_MAX ) ) + 1; +const int VEH_VELOCITY_MANTISSA_BITS = VEH_VELOCITY_TOTAL_BITS - 1 - VEH_VELOCITY_EXPONENT_BITS; +const float VEH_MOMENTUM_MAX = 1e20f; +const int VEH_MOMENTUM_TOTAL_BITS = 16; +const int VEH_MOMENTUM_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( VEH_MOMENTUM_MAX ) ) + 1; +const int VEH_MOMENTUM_MANTISSA_BITS = VEH_MOMENTUM_TOTAL_BITS - 1 - VEH_MOMENTUM_EXPONENT_BITS; +const float VEH_FORCE_MAX = 1e20f; +const int VEH_FORCE_TOTAL_BITS = 16; +const int VEH_FORCE_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( VEH_FORCE_MAX ) ) + 1; +const int VEH_FORCE_MANTISSA_BITS = VEH_FORCE_TOTAL_BITS - 1 - VEH_FORCE_EXPONENT_BITS; + +/* +================ +hhPhysics_Vehicle::WriteToSnapshot +================ +*/ +void hhPhysics_Vehicle::WriteToSnapshot( idBitMsgDelta &msg ) const { + idCQuat quat, localQuat; + + quat = current.i.orientation.ToCQuat(); + localQuat = current.localAxis.ToCQuat(); + + msg.WriteFloat( gravityVector.x ); + msg.WriteFloat( gravityVector.y ); + msg.WriteFloat( gravityVector.z ); + + msg.WriteBits(dropToFloor, 1); + + 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], VEH_MOMENTUM_EXPONENT_BITS, VEH_MOMENTUM_MANTISSA_BITS ); + msg.WriteFloat( current.i.linearMomentum[1], VEH_MOMENTUM_EXPONENT_BITS, VEH_MOMENTUM_MANTISSA_BITS ); + msg.WriteFloat( current.i.linearMomentum[2], VEH_MOMENTUM_EXPONENT_BITS, VEH_MOMENTUM_MANTISSA_BITS ); + msg.WriteFloat( current.i.angularMomentum[0], VEH_MOMENTUM_EXPONENT_BITS, VEH_MOMENTUM_MANTISSA_BITS ); + msg.WriteFloat( current.i.angularMomentum[1], VEH_MOMENTUM_EXPONENT_BITS, VEH_MOMENTUM_MANTISSA_BITS ); + msg.WriteFloat( current.i.angularMomentum[2], VEH_MOMENTUM_EXPONENT_BITS, VEH_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], VEH_VELOCITY_EXPONENT_BITS, VEH_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[1], VEH_VELOCITY_EXPONENT_BITS, VEH_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[2], VEH_VELOCITY_EXPONENT_BITS, VEH_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.externalForce[0], VEH_FORCE_EXPONENT_BITS, VEH_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.externalForce[1], VEH_FORCE_EXPONENT_BITS, VEH_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.externalForce[2], VEH_FORCE_EXPONENT_BITS, VEH_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.externalTorque[0], VEH_FORCE_EXPONENT_BITS, VEH_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.externalTorque[1], VEH_FORCE_EXPONENT_BITS, VEH_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.externalTorque[2], VEH_FORCE_EXPONENT_BITS, VEH_FORCE_MANTISSA_BITS ); +} + +/* +================ +hhPhysics_Vehicle::ReadFromSnapshot +================ +*/ +void hhPhysics_Vehicle::ReadFromSnapshot( const idBitMsgDelta &msg ) { + idCQuat quat, localQuat; + + idVec3 newGrav; + newGrav.x = msg.ReadFloat(); + newGrav.y = msg.ReadFloat(); + newGrav.z = msg.ReadFloat(); + + SetGravity(newGrav); + + dropToFloor = !!msg.ReadBits(1); + + 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( VEH_MOMENTUM_EXPONENT_BITS, VEH_MOMENTUM_MANTISSA_BITS ); + current.i.linearMomentum[1] = msg.ReadFloat( VEH_MOMENTUM_EXPONENT_BITS, VEH_MOMENTUM_MANTISSA_BITS ); + current.i.linearMomentum[2] = msg.ReadFloat( VEH_MOMENTUM_EXPONENT_BITS, VEH_MOMENTUM_MANTISSA_BITS ); + current.i.angularMomentum[0] = msg.ReadFloat( VEH_MOMENTUM_EXPONENT_BITS, VEH_MOMENTUM_MANTISSA_BITS ); + current.i.angularMomentum[1] = msg.ReadFloat( VEH_MOMENTUM_EXPONENT_BITS, VEH_MOMENTUM_MANTISSA_BITS ); + current.i.angularMomentum[2] = msg.ReadFloat( VEH_MOMENTUM_EXPONENT_BITS, VEH_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, VEH_VELOCITY_EXPONENT_BITS, VEH_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[1] = msg.ReadDeltaFloat( 0.0f, VEH_VELOCITY_EXPONENT_BITS, VEH_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[2] = msg.ReadDeltaFloat( 0.0f, VEH_VELOCITY_EXPONENT_BITS, VEH_VELOCITY_MANTISSA_BITS ); + current.externalForce[0] = msg.ReadDeltaFloat( 0.0f, VEH_FORCE_EXPONENT_BITS, VEH_FORCE_MANTISSA_BITS ); + current.externalForce[1] = msg.ReadDeltaFloat( 0.0f, VEH_FORCE_EXPONENT_BITS, VEH_FORCE_MANTISSA_BITS ); + current.externalForce[2] = msg.ReadDeltaFloat( 0.0f, VEH_FORCE_EXPONENT_BITS, VEH_FORCE_MANTISSA_BITS ); + current.externalTorque[0] = msg.ReadDeltaFloat( 0.0f, VEH_FORCE_EXPONENT_BITS, VEH_FORCE_MANTISSA_BITS ); + current.externalTorque[1] = msg.ReadDeltaFloat( 0.0f, VEH_FORCE_EXPONENT_BITS, VEH_FORCE_MANTISSA_BITS ); + current.externalTorque[2] = msg.ReadDeltaFloat( 0.0f, VEH_FORCE_EXPONENT_BITS, VEH_FORCE_MANTISSA_BITS ); + + current.i.orientation = quat.ToMat3(); + current.localAxis = localQuat.ToMat3(); + + if ( clipModel ) { + clipModel->Link( gameLocal.clip, self, clipModel->GetId(), current.i.position, current.i.orientation ); + } +} diff --git a/src/Prey/physics_vehicle.h b/src/Prey/physics_vehicle.h new file mode 100644 index 0000000..b473386 --- /dev/null +++ b/src/Prey/physics_vehicle.h @@ -0,0 +1,37 @@ + +#ifndef __HH_PHYSICS_VEHICLE_H__ +#define __HH_PHYSICS_VEHICLE_H__ + +/* +=================================================================================== + + hhPhysics_Vehicle + + Simulates the motion of a vehicle through the environment. The orientation of + the vehicle is controlled directly by the player, so the physics move the + vehicle so that it will fit at it's current orientation. + +=================================================================================== +*/ + +class hhPhysics_Vehicle : public hhPhysics_RigidBodySimple { + +public: + CLASS_PROTOTYPE( hhPhysics_Vehicle ); + + virtual void SetFriction( const float linear, const float angular, const float contact ); + + const idVec3& GetLinearMomentum() const { return current.i.linearMomentum; } + const idVec3& GetAngularMomentum() const { return current.i.angularMomentum; } + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + +protected: + virtual bool CheckForCollisions( const float deltaTime, rigidBodyPState_t &next, trace_t &collision ); + + bool VehicleMotion( trace_t &collision, const idVec3 &start, const idVec3 &end, const idMat3 &axis ); +}; + + +#endif \ No newline at end of file diff --git a/src/Prey/prey_animator.cpp b/src/Prey/prey_animator.cpp new file mode 100644 index 0000000..b32d452 --- /dev/null +++ b/src/Prey/prey_animator.cpp @@ -0,0 +1,607 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +/* +============ +hhAnimator::hhAnimator +============ +*/ + +hhAnimator::hhAnimator() { + + lastCycleRotate = 0; + +} //. hhAnimator::hhAnimator() + + +/* +===================== +hhAnimator::CycleAnimRandom +===================== +*/ +void hhAnimator::CycleAnim( int channelNum, int anim, int currentTime, int blendTime, const idEventDef *pEvent ) { + + //! NLANOTE - Add ability to skip cycling + idAnimator::CycleAnim( channelNum, anim, currentTime, blendTime ); + + if ( !anim ) { return; } + + + hhAnim *animPtr = (hhAnim *)GetAnim( anim ); + //HUMANHEAD rww - crash here, can't repro. putting extra logic in for non-gold builds to check. +#if !GOLD + if (!animPtr) { + assert(0); + common->Warning("hhAnimator::CycleAnim anim index %i returned NULL for GetAnim.", anim); + return; + } +#endif +//HUMANHEAD END + + // If the anim was an exact match to the requested name, if so, just return + if ( animPtr->exactMatch ) { + return; + } + + // Determine if there are multiple anims to play. If not, then just return. + int numAnimVariants = GetNumAnimVariants( anim ); + + if ( numAnimVariants < 2 ) { + return; + } + + // The CycleAnim could have started at a random spot in the anim, take this into account, and add only the time left in the anim + int lengthLeft; + lengthLeft = animPtr->Length() - channels[ channelNum ][0].AnimTime( currentTime ); + + channels[ channelNum ][0].rotateTime = currentTime + lengthLeft; + channels[ channelNum ][0].rotateEvent = pEvent; + + entity->PostEventMS( &EV_CheckCycleRotate, lengthLeft ); + +} //. BlendCycleRandom( int, idAnim *, float, int, int ) + + +/* +============== +hhAnimator::GetNumAnimVariants +============== +*/ +int hhAnimator::GetNumAnimVariants( int anim ) { + const char *name = GetAnim( anim )->Name(); + int len, numAnims, maxAnims; + + + // nla - Copied from idDeclModelDef::GetAnim( const char * ) + len = strlen( name ); + if ( len && idStr::CharIsNumeric( name[ len - 1 ] ) ) { + // find a specific animation + return( 1 ); + } + + // find all animations with same name + numAnims = 0; + maxAnims = NumAnims(); + + for( int i = 1; i < maxAnims; i++ ) { + if ( !strcmp( GetAnim( i )->Name(), name ) ) { + numAnims++; + } + } + + return( numAnims ); +} //. hhAnimator::GetNumAnimVariants( int ) + + + +/* +===================== +hhAnimator::CheckCycleRotate + Check the blend animations to see if we need to switch any animations +===================== +*/ +void hhAnimator::CheckCycleRotate() { + hhAnimBlend *blend; + int anim; + int i; + const idEventDef *event; + + + if ( gameLocal.time <= lastCycleRotate ) { + return; + } + lastCycleRotate = gameLocal.time; + + // Cycle through the channels + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + // Only cycle out the first/main anim of the channel. The rest ar being phased out and don't matter + blend = channels[ i ]; + if ( !blend->IsDone( gameLocal.time ) ) { + if ( blend->Anim() && ( blend->rotateTime > 0 ) && + ( blend->rotateTime <= gameLocal.time ) ) { // HUMANHEAD JRM + + anim = GetAnim( blend->Anim()->Name() ); + event = blend->rotateEvent; + + // gameLocal.Printf( "%s Cycling Anim %s\n", entity->name.c_str(), GetAnim( anim )->FullName() ); + + // blendWeight.Init( currentTime, blendTime, weight, newweight ); + int remainingBlend = ( blend->blendStartTime + blend->blendDuration ) - + gameLocal.time; + if ( remainingBlend < 0 ) { remainingBlend = 0; } + blend->CycleAnim( modelDef, anim, gameLocal.time, remainingBlend ); + entity->BecomeActive( TH_ANIMATE ); + + blend->rotateTime = gameLocal.time + GetAnim( anim )->Length(); + blend->rotateEvent = event; + + entity->PostEventMS( &EV_CheckCycleRotate, GetAnim( anim )->Length() ); + + // Process any change events + if ( event ) { + entity->ProcessEvent( event ); + } + } + } + } +} //. CheckCycleRotate + + +/* +==================== +hhAnimator::CheckThaw +HUMANHEAD nla +==================== +*/ +void hhAnimator::CheckThaw( ) { + hhAnimBlend *blend; + int i, j; + + + // Cycle through the channels + blend = channels[0]; + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++, blend++ ) { + if ( blend->Anim() ) { + blend->ThawIfTime( gameLocal.time ); + } + } + } + +} //. CheckThaw( ) + +/* +===================== +hhAnimator::CheckTween + Check if the current animation should be tweened to from the previous anim + Inputs: channel - The channel to check the current anim with + Outputs: The time in ms for the tween. 0 if non given. +HUMANHEAD nla +===================== +*/ +int hhAnimator::CheckTween( int channelNum ) { + idStr toAnimName; + idStr fromAnimName = ""; + int timeMS = 0; + int tempInt; + + + // Ensure we have a valid channel number + if ( ( channelNum < 0 ) || ( channelNum >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idAnimator::CheckTween : channel out of range" ); + } + + + // If no valid anim playing, then nothing to tween. :) + if ( channels[ channelNum ][ 0 ].Anim() == NULL ) { + return( 0 ); + } + else { // Set the to anim name + toAnimName = channels[ channelNum ][ 0 ].AnimName(); + } + + // If a valid anim was playing, get its name + if ( channels[ channelNum ][ 1 ].Anim() != NULL ) { + fromAnimName = channels[ channelNum ][ 1 ].AnimName(); + } + + // We aren't really tweening, head out. = ) + if ( toAnimName == fromAnimName ) { + // gameLocal.Printf("Earlying out!"); + return( 0 ); + } + + // See if a default anim tween time is given + if ( entity->spawnArgs.GetInt( va( "tween2%s", + (const char *) toAnimName ), + "0", tempInt ) ) { + timeMS = tempInt; + // JRM - removed + //gameLocal.Printf( "Got tween2%s of %d\n", (const char *) toAnimName, + // timeMS ); + } + + // See if a anim to anim tween time is given + if ( fromAnimName.Length() && + entity->spawnArgs.GetInt( va( "tween%s2%s", + (const char *) fromAnimName, + (const char *) toAnimName ), + "0", tempInt ) ) { + //! Check that they aren't the same anims + + timeMS = tempInt; + // JRM - removed + //gameLocal.Printf( "Got tween%s2%s of %d\n", (const char *) fromAnimName, + // (const char *) toAnimName, + // timeMS ); + } + + // If a valid tween time is valid, use it + if ( timeMS > 0 ) { + // Freeze the current anim + channels[ channelNum ][ 0 ].Freeze( gameLocal.time, GetEntity(), timeMS ); + + // Setup the new blend times + // NLAMERGE 5 - This really used anymore? + /* + channels[ channelNum ][ 0 ].blendWeight.SetDuration( timeMS ); + channels[ channelNum ][ 1 ].blendWeight.SetDuration( timeMS ); + */ + } + + return( timeMS ); +} //. CheckTween( int ) + + +/* +===================== +hhAnimator::IsAnimPlaying +HUMANHEAD nla +===================== +*/ +bool hhAnimator::IsAnimPlaying( int channelNum, const idAnim *anim ) { + idAnimBlend * playingAnim; + + + playingAnim = FindAnim( channelNum, anim ); + if ( playingAnim && ! playingAnim->IsDone( gameLocal.time ) ) { + return( true ); + } + + return( false ); +} //. IsAnimPlaying( idAnim * ) + +/* +===================== +ggAnimator::IsAnimPlaying +HUMANHEAD nla +===================== +*/ +bool hhAnimator::IsAnimPlaying( int channelNum, const char* anim ) { + return IsAnimPlaying( channelNum, GetAnim( GetAnim( anim ) ) ); +} + + +/* +===================== +hhAnimator::IsAnimPlaying +HUMANHEAD nla +===================== +*/ +bool hhAnimator::IsAnimPlaying( const idAnim *anim ) { + + for( int i = 0; i < ANIM_NumAnimChannels; i++ ) { + if ( IsAnimPlaying( i, anim ) ) { + return( true ); + } + } + + return( false ); +} + + +/* +===================== +hhAnimator::IsAnimPlaying +HUMANHEAD nla +===================== +*/ +bool hhAnimator::IsAnimPlaying( const char* anim ) { + return IsAnimPlaying( GetAnim( GetAnim( anim ) ) ); +} + + +/* +===================== +hhAnimator::FindAnim +HUMANHEAD nla +===================== +*/ +idAnimBlend *hhAnimator::FindAnim( int channelNum, const idAnim *anim ) { + int i; + + if ( ( channelNum < 0 ) || ( channelNum >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "hhAnimator::FindAnim : channel out of range" ); + } + + for( i = 0; i < ANIM_MaxAnimsPerChannel; i++ ) { + if ( channels[ channelNum ][ i ].Anim() == anim ) { + return &( channels[ channelNum ][ i ] ); + } + } + + return NULL; +} + + +/* +===================== +hhAnimator::GetBlendAnim +HUMANHEAD nla +===================== +*/ +const idAnimBlend * hhAnimator::GetBlendAnim( int channelNum, int index ) const { + + if ( channelNum < 0 || channelNum >= ANIM_NumAnimChannels ) { + gameLocal.Error("Channel out of range"); + } + + return ( ( index < 0 ) || ( index >= ANIM_MaxAnimsPerChannel ) ) ? NULL : &(channels[channelNum][ index ]); +} + + +/* +===================== +hhAnimator::NumBlendAnims +// HUMANHEAD Copied logic from IsAnimating +HUMANHEAD nla +===================== +*/ +int hhAnimator::NumBlendAnims( int currentTime ) { + int i, j; + const idAnimBlend *blend; + // HUMANHEAD nla + int num; + + num = 0; + // HUMANHEAD END + + if ( !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->IsDone( currentTime ) ) { + // HUMANHEAD nla + num++; + // HUMANHEAD END + } + } + } + + return num; +} + + +/* +==================== +hhAnimator::CopyAnimations +HUMANHEAD nla +==================== +*/ +void hhAnimator::CopyAnimations( hhAnimator &source ) { + + + // Loop through each channel + for ( int chan = 0; chan < ANIM_NumAnimChannels; ++chan ) { + + // NLA - Simplifed to the double loop in the Nov 2003 "Big Merge" (c) + // Copy the channel anim info + for ( int anim = 0; anim < ANIM_MaxAnimsPerChannel; + ++anim ) { + + channels[ chan ][ anim ] = source.channels[ chan ][ anim ]; + + } + } + + +} + + +/* +==================== +hhAnimator::CopyPoses +HUMANHEAD nla +==================== +*/ +void hhAnimator::CopyPoses( hhAnimator &source ) { + int i, num; + + // Copy over the joint Mod info + jointMods.DeleteContents( true ); + num = source.jointMods.Num(); + jointMods.SetNum( num ); + for( i = 0; i < num; i++ ) { + jointMods[ i ] = new jointMod_t; + *jointMods[ i ] = *source.jointMods[ i ]; + } + + // Copy over the frameBounds + frameBounds = source.frameBounds; + + // Copy over AF info + AFPoseBlendWeight = source.AFPoseBlendWeight; + + num = source.AFPoseJoints.Num(); + AFPoseJoints.SetNum( num ); + for ( int i = 0; i < num; i++ ) { + AFPoseJoints[ i ] = source.AFPoseJoints[ i ]; + } + + num = source.AFPoseJointMods.Num(); + AFPoseJointMods.SetNum( num ); + for ( int i = 0; i < num; i++ ) { + AFPoseJointMods[ i ] = source.AFPoseJointMods[ i ]; + } + + num = source.AFPoseJointFrame.Num(); + AFPoseJointFrame.SetNum( num ); + for ( int i = 0; i < num; i++ ) { + AFPoseJointFrame[ i ] = source.AFPoseJointFrame[ i ]; + } + + AFPoseBounds = source.AFPoseBounds; + AFPoseTime = source.AFPoseTime; +} + + +/* +==================== +hhAnimator::Freeze +HUMANHEAD nla +==================== +*/ +bool hhAnimator::Freeze( ) { + hhAnimBlend *blend; + + + blend = &( channels[ 0 ][ 0 ] ); + + return( blend->Freeze( gameLocal.time, GetEntity() ) ); + +} + + +/* +==================== +hhAnimator::Thaw +HUMANHEAD nla +==================== +*/ +bool hhAnimator::Thaw( ) { + hhAnimBlend *blend; + + + blend = &( channels[ 0 ][ 0 ] ); + + return( blend->Thaw( gameLocal.time ) ); + +} + + +/* +===================== +hhAnimator::IsAnimating +===================== +*/ +bool hhAnimator::IsAnimating( int currentTime ) const { + int i, j; + const hhAnimBlend *blend; + // HUMANHEAD nla + int numFrozen = 0; + // HUMANHEAD END + + + 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->IsDone( currentTime ) ) { + return true; + } + // HUMANHEAD nla + if ( blend->IsFrozen() ) { numFrozen++; } + // HUMANHEAD END + } + } + + // HUMANHEAD nla + if ( numFrozen > 1 ) { return( true ); } + // HUMANHEAD END + + return false; +} + +/* +===================== +hhAnimator::FrameHasChanged +===================== +*/ +bool hhAnimator::FrameHasChanged( int currentTime ) const { + int i, j; + const hhAnimBlend *blend; + // HUMANHEAD nla - Used to allow for tween (2 frozen) anims to work + int numFrozen = 0; + // HUMANHEAD END + + + 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; + } + // HUMANHEAD nla + if ( blend->IsFrozen() ) { numFrozen++; } + // HUMANHEAD END + } + } + + // HUMANHEAD nla + if ( numFrozen > 1 ) { return( true ); } + // HUMANHEAD END + + if ( forceUpdate && IsAnimating( currentTime ) ) { + return true; + } + + return false; +} + +//================ +//hhAnimator::Save +//================ +void hhAnimator::Save( idSaveGame *savefile ) const { + // Not a subclass of idClass, so this is necessary -mdl + idAnimator::Save( savefile ); + savefile->WriteInt( lastCycleRotate ); +} + +//================ +//hhAnimator::Restore +//================ +void hhAnimator::Restore( idRestoreGame *savefile ) { + // Not a subclass of idClass, so this is necessary -mdl + idAnimator::Restore( savefile ); + savefile->ReadInt( lastCycleRotate ); +} + diff --git a/src/Prey/prey_animator.h b/src/Prey/prey_animator.h new file mode 100644 index 0000000..6b5b2c7 --- /dev/null +++ b/src/Prey/prey_animator.h @@ -0,0 +1,50 @@ + + +#ifndef __PREY_GAME_ANIMATOR_H__ +#define __PREY_GAME_ANIMATOR_H__ + +class hhAnimator : public idAnimator { + public: + + hhAnimator(); + + // Overridden methods + bool FrameHasChanged( int animtime ) const; + bool IsAnimating( int currentTime ) const; + + // Unique methods + void CycleAnim( int channelNum, int anim, int currenttime, int blendtime, const idEventDef *pEvent = NULL ); + void CheckCycleRotate(); + void CheckThaw(); + int CheckTween( int channelNum ); + + bool IsAnimPlaying( int channelNum, const idAnim *anim ); + //bool IsAnimPlaying( int channelNum, int animNum ) { return( IsAnimPlaying( channelNum, GetAnim( animNum ) ) ); } + bool IsAnimPlaying( int channelNum, const char* anim ); + bool IsAnimPlaying( const idAnim *anim ); + //bool IsAnimPlaying( int animNum ) { return( IsAnimPlaying( GetAnim( animNum ) ) ); } + bool IsAnimPlaying( const char* anim ); + bool IsAnimatedModel() const { return modelDef != NULL; } + + int GetNumAnimVariants( int anim ); + + const idAnimBlend *GetBlendAnim( int channelNum, int index ) const; + // Backwards compat functions + idAnimBlend * FindAnim( int channelNum, const idAnim *anim ); // JRM: removed const - couldn't compile + const idAnimBlend *GetBlendAnim( int index ) const { return( GetBlendAnim( ANIMCHANNEL_ALL, index ) ); }; + int NumBlendAnims( int currentTime ); + + void CopyAnimations( hhAnimator &animator ); + void CopyPoses( hhAnimator &animator ); + bool Freeze(); + bool Thaw(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + protected: + + int lastCycleRotate; +}; + +#endif /* __PREY_GAME_ANIMATOR_H__ */ diff --git a/src/Prey/prey_baseweapons.cpp b/src/Prey/prey_baseweapons.cpp new file mode 100644 index 0000000..df22d35 --- /dev/null +++ b/src/Prey/prey_baseweapons.cpp @@ -0,0 +1,2525 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +#define WEAPON_DEBUG if(g_debugWeapon.GetBool()) gameLocal.Warning + +/*********************************************************************** + + hhWeapon + +***********************************************************************/ +const idEventDef EV_PlayAnimWhenReady( "playAnimWhenReady", "s" ); +const idEventDef EV_Weapon_Aside( "" ); +const idEventDef EV_Weapon_EjectAltBrass( "ejectAltBrass" ); + +const idEventDef EV_Weapon_HasAmmo( "hasAmmo", "", 'd' ); +const idEventDef EV_Weapon_HasAltAmmo( "hasAltAmmo", "", 'd' ); + +const idEventDef EV_Weapon_GetFireDelay( "getFireDelay", "", 'f' ); +const idEventDef EV_Weapon_GetAltFireDelay( "getAltFireDelay", "", 'f' ); +const idEventDef EV_Weapon_GetSpread( "getSpread", "", 'f' ); +const idEventDef EV_Weapon_GetAltSpread( "getAltSpread", "", 'f' ); +const idEventDef EV_Weapon_GetString( "getString", "s", 's' ); +const idEventDef EV_Weapon_GetAltString( "getAltString", "s", 's' ); + +const idEventDef EV_Weapon_AddToAltClip( "addToAltClip", "f" ); +const idEventDef EV_Weapon_AltAmmoInClip( "altAmmoInClip", "", 'f' ); +const idEventDef EV_Weapon_AltAmmoAvailable( "altAmmoAvailable", "", 'f' ); +const idEventDef EV_Weapon_AltClipSize( "altClipSize", "", 'f' ); + +const idEventDef EV_Weapon_FireAltProjectiles( "fireAltProjectiles" ); +const idEventDef EV_Weapon_FireProjectiles( "fireProjectiles" ); + +const idEventDef EV_Weapon_WeaponAside( "weaponAside" ); // nla +const idEventDef EV_Weapon_WeaponPuttingAside( "weaponPuttingAside" ); // nla +const idEventDef EV_Weapon_WeaponUprighting( "weaponUprighting" ); // nla + +const idEventDef EV_Weapon_IsAnimPlaying( "isAnimPlaying", "s", 'd' ); +const idEventDef EV_Weapon_Raise( "" ); // nla - For the hands to post an event to raise the weapons + +const idEventDef EV_Weapon_SetViewAnglesSensitivity( "setViewAnglesSensitivity", "f" ); +const idEventDef EV_Weapon_UseAltAmmo( "useAltAmmo", "d" ); + +const idEventDef EV_Weapon_Hide( "hideWeapon" ); +const idEventDef EV_Weapon_Show( "showWeapon" ); + +CLASS_DECLARATION( hhAnimatedEntity, hhWeapon ) + EVENT( EV_Weapon_FireAltProjectiles, hhWeapon::Event_FireAltProjectiles ) + EVENT( EV_Weapon_FireProjectiles, hhWeapon::Event_FireProjectiles ) + EVENT( EV_PlayAnimWhenReady, hhWeapon::Event_PlayAnimWhenReady ) + EVENT( EV_SpawnFxAlongBone, hhWeapon::Event_SpawnFXAlongBone ) + EVENT( EV_Weapon_EjectAltBrass, hhWeapon::Event_EjectAltBrass ) + EVENT( EV_Weapon_HasAmmo, hhWeapon::Event_HasAmmo ) + EVENT( EV_Weapon_HasAltAmmo, hhWeapon::Event_HasAltAmmo ) + EVENT( EV_Weapon_AddToAltClip, hhWeapon::Event_AddToAltClip ) + EVENT( EV_Weapon_AltAmmoInClip, hhWeapon::Event_AltAmmoInClip ) + EVENT( EV_Weapon_AltAmmoAvailable, hhWeapon::Event_AltAmmoAvailable ) + EVENT( EV_Weapon_AltClipSize, hhWeapon::Event_AltClipSize ) + EVENT( EV_Weapon_GetFireDelay, hhWeapon::Event_GetFireDelay ) + EVENT( EV_Weapon_GetAltFireDelay, hhWeapon::Event_GetAltFireDelay ) + EVENT( EV_Weapon_GetString, hhWeapon::Event_GetString ) + EVENT( EV_Weapon_GetAltString, hhWeapon::Event_GetAltString ) + EVENT( EV_Weapon_Raise, hhWeapon::Event_Raise ) + EVENT( EV_Weapon_WeaponAside, hhWeapon::Event_Weapon_Aside ) + EVENT( EV_Weapon_WeaponPuttingAside, hhWeapon::Event_Weapon_PuttingAside ) + EVENT( EV_Weapon_WeaponUprighting, hhWeapon::Event_Weapon_Uprighting ) + EVENT( EV_Weapon_IsAnimPlaying, hhWeapon::Event_IsAnimPlaying ) + EVENT( EV_Weapon_SetViewAnglesSensitivity, hhWeapon::Event_SetViewAnglesSensitivity ) + EVENT( EV_Weapon_Hide, hhWeapon::Event_HideWeapon ) + EVENT( EV_Weapon_Show, hhWeapon::Event_ShowWeapon ) + + //idWeapon + EVENT( EV_Weapon_State, hhWeapon::Event_WeaponState ) + EVENT( EV_Weapon_WeaponReady, hhWeapon::Event_WeaponReady ) + EVENT( EV_Weapon_WeaponOutOfAmmo, hhWeapon::Event_WeaponOutOfAmmo ) + EVENT( EV_Weapon_WeaponReloading, hhWeapon::Event_WeaponReloading ) + EVENT( EV_Weapon_WeaponHolstered, hhWeapon::Event_WeaponHolstered ) + EVENT( EV_Weapon_WeaponRising, hhWeapon::Event_WeaponRising ) + EVENT( EV_Weapon_WeaponLowering, hhWeapon::Event_WeaponLowering ) + EVENT( EV_Weapon_AddToClip, hhWeapon::Event_AddToClip ) + EVENT( EV_Weapon_AmmoInClip, hhWeapon::Event_AmmoInClip ) + EVENT( EV_Weapon_AmmoAvailable, hhWeapon::Event_AmmoAvailable ) + EVENT( EV_Weapon_ClipSize, hhWeapon::Event_ClipSize ) + EVENT( AI_PlayAnim, hhWeapon::Event_PlayAnim ) + EVENT( AI_PlayCycle, hhWeapon::Event_PlayCycle ) + EVENT( AI_AnimDone, hhWeapon::Event_AnimDone ) + EVENT( EV_Weapon_Next, hhWeapon::Event_Next ) + EVENT( EV_Weapon_Flashlight, hhWeapon::Event_Flashlight ) + EVENT( EV_Weapon_EjectBrass, hhWeapon::Event_EjectBrass ) + EVENT( EV_Weapon_GetOwner, hhWeapon::Event_GetOwner ) + EVENT( EV_Weapon_UseAmmo, hhWeapon::Event_UseAmmo ) + EVENT( EV_Weapon_UseAltAmmo, hhWeapon::Event_UseAltAmmo ) +END_CLASS + +/* +================ +hhWeapon::hhWeapon +================ +*/ +hhWeapon::hhWeapon() { + owner = NULL; + worldModel = NULL; + + thread = NULL; + + //HUMANHEAD: aob + fireController = NULL; + altFireController = NULL; + fl.networkSync = true; + cameraShakeOffset.Zero(); //rww - had to move here to avoid den/nan/blah in spawn + //HUMANHEAD END + + Clear(); +} + +/* +================ +hhWeapon::Spawn +================ +*/ +void hhWeapon::Spawn() { + //idWeapon + if ( !gameLocal.isClient ) + { + worldModel = static_cast< hhAnimatedEntity * >( gameLocal.SpawnEntityType( hhAnimatedEntity::Type, NULL ) ); + worldModel.GetEntity()->fl.networkSync = true; + } + + thread = new idThread(); + thread->ManualDelete(); + thread->ManualControl(); + //idWeapon End + + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( NULL, 1.0f ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + SetPhysics( &physicsObj ); + + fl.solidForTeam = true; + + // HUMANHEAD: aob + memset( &eyeTraceInfo, 0, sizeof(trace_t) ); + eyeTraceInfo.fraction = 1.0f; + BecomeActive( TH_TICKER ); + handedness = hhMath::hhMax( 1, spawnArgs.GetInt("handedness", "1") ); + fl.neverDormant = true; + // HUMANHEAD END +} + +/* +================ +hhWeapon::~hhWeapon() +================ +*/ +hhWeapon::~hhWeapon() { + SAFE_REMOVE( worldModel ); + SAFE_REMOVE( thread ); + + SAFE_DELETE_PTR( fireController ); + SAFE_DELETE_PTR( altFireController ); + + if ( nozzleFx && nozzleGlowHandle != -1 ) { + gameRenderWorld->FreeLightDef( nozzleGlowHandle ); + } +} + +/* +================ +hhWeapon::SetOwner +================ +*/ +void hhWeapon::SetOwner( idPlayer *_owner ) { + if( !_owner || !_owner->IsType(hhPlayer::Type) ) { + owner = NULL; + return; + } + + owner = static_cast( _owner ); + + if( GetPhysics() && GetPhysics()->IsType(hhPhysics_StaticWeapon::Type) ) { + static_cast(GetPhysics())->SetSelfOwner( owner.GetEntity() ); + } +} + +/* +================ +hhWeapon::Save +================ +*/ +void hhWeapon::Save( idSaveGame *savefile ) const { + savefile->WriteInt( status ); + savefile->WriteObject( thread ); + savefile->WriteString( state ); + savefile->WriteString( idealState ); + savefile->WriteInt( animBlendFrames ); + savefile->WriteInt( animDoneTime ); + + owner.Save( savefile ); + worldModel.Save( savefile ); + + savefile->WriteStaticObject( physicsObj ); + + savefile->WriteObject( fireController ); + savefile->WriteObject( altFireController ); + + savefile->WriteTrace( eyeTraceInfo ); + savefile->WriteVec3( cameraShakeOffset ); + savefile->WriteInt( handedness ); + + savefile->WriteVec3( pushVelocity ); + + savefile->WriteString( weaponDef->GetName() ); + + savefile->WriteString( icon ); + + savefile->WriteInt( kick_endtime ); + + savefile->WriteBool( lightOn ); + + savefile->WriteInt( zoomFov ); + + savefile->WriteInt( weaponAngleOffsetAverages ); + savefile->WriteFloat( weaponAngleOffsetScale ); + savefile->WriteFloat( weaponAngleOffsetMax ); + savefile->WriteFloat( weaponOffsetTime ); + savefile->WriteFloat( weaponOffsetScale ); + + savefile->WriteBool( nozzleFx ); + //HUMANHEAD PCF mdl 05/04/06 - Don't save light handles + //savefile->WriteInt( nozzleGlowHandle ); + savefile->WriteJoint( nozzleJointHandle.view ); + savefile->WriteJoint( nozzleJointHandle.world ); + savefile->WriteRenderLight( nozzleGlow ); + savefile->WriteVec3( nozzleGlowColor ); + savefile->WriteMaterial( nozzleGlowShader ); + savefile->WriteFloat( nozzleGlowRadius ); + savefile->WriteVec3( nozzleGlowOffset ); +} + +/* +================ +hhWeapon::Restore +================ +*/ +void hhWeapon::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( (int &)status ); + savefile->ReadObject( reinterpret_cast(thread) ); + savefile->ReadString( state ); + savefile->ReadString( idealState ); + savefile->ReadInt( animBlendFrames ); + savefile->ReadInt( animDoneTime ); + + //Re-link script fields + WEAPON_ATTACK.LinkTo( scriptObject, "WEAPON_ATTACK" ); + WEAPON_RELOAD.LinkTo( scriptObject, "WEAPON_RELOAD" ); + WEAPON_RAISEWEAPON.LinkTo( scriptObject, "WEAPON_RAISEWEAPON" ); + WEAPON_LOWERWEAPON.LinkTo( scriptObject, "WEAPON_LOWERWEAPON" ); + WEAPON_NEXTATTACK.LinkTo( scriptObject, "nextAttack" ); //HUMANHEAD rww + + //HUMANHEAD: aob + WEAPON_ALTATTACK.LinkTo( scriptObject, "WEAPON_ALTATTACK" ); + WEAPON_ASIDEWEAPON.LinkTo( scriptObject, "WEAPON_ASIDEWEAPON" ); + WEAPON_ALTMODE.LinkTo( scriptObject, "WEAPON_ALTMODE" ); + //HUMANHEAD END + + owner.Restore( savefile ); + worldModel.Restore( savefile ); + + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); + + savefile->ReadObject( reinterpret_cast< idClass *&> ( fireController ) ); + savefile->ReadObject( reinterpret_cast< idClass *&> ( altFireController ) ); + + savefile->ReadTrace( eyeTraceInfo ); + savefile->ReadVec3( cameraShakeOffset ); + savefile->ReadInt( handedness ); + + savefile->ReadVec3( pushVelocity ); + + idStr objectname; + savefile->ReadString( objectname ); + weaponDef = gameLocal.FindEntityDef( objectname, false ); + if (!weaponDef) { + gameLocal.Error( "Unknown weaponDef: %s\n", objectname ); + } + dict = &(weaponDef->dict); + + savefile->ReadString( icon ); + + savefile->ReadInt( kick_endtime ); + + savefile->ReadBool( lightOn ); + + savefile->ReadInt( zoomFov ); + + savefile->ReadInt( weaponAngleOffsetAverages ); + savefile->ReadFloat( weaponAngleOffsetScale ); + savefile->ReadFloat( weaponAngleOffsetMax ); + savefile->ReadFloat( weaponOffsetTime ); + savefile->ReadFloat( weaponOffsetScale ); + + savefile->ReadBool( nozzleFx ); + //HUMANHEAD PCF mdl 05/04/06 - Don't save light handles + //savefile->ReadInt( nozzleGlowHandle ); + savefile->ReadJoint( nozzleJointHandle.view ); + savefile->ReadJoint( nozzleJointHandle.world ); + savefile->ReadRenderLight( nozzleGlow ); + savefile->ReadVec3( nozzleGlowColor ); + savefile->ReadMaterial( nozzleGlowShader ); + savefile->ReadFloat( nozzleGlowRadius ); + savefile->ReadVec3( nozzleGlowOffset ); +} + +/* +================ +hhWeapon::Clear +================ +*/ +void hhWeapon::Clear( void ) { + DeconstructScriptObject(); + scriptObject.Free(); + + WEAPON_ATTACK.Unlink(); + WEAPON_RELOAD.Unlink(); + WEAPON_RAISEWEAPON.Unlink(); + WEAPON_LOWERWEAPON.Unlink(); + WEAPON_NEXTATTACK.Unlink(); //HUMANHEAD rww + + //HUMANHEAD: aob + WEAPON_ALTATTACK.Unlink(); + WEAPON_ASIDEWEAPON.Unlink(); + WEAPON_ALTMODE.Unlink(); + + SAFE_DELETE_PTR( fireController ); + SAFE_DELETE_PTR( altFireController ); + //HUMANEAD END + + //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; + + // nozzle fx + nozzleFx = false; + memset( &nozzleGlow, 0, sizeof( nozzleGlow ) ); + nozzleGlowHandle = -1; + nozzleJointHandle.Clear(); + + memset( &refSound, 0, sizeof( refSound_t ) ); + if ( owner.IsValid() ) { + // don't spatialize the weapon sounds + refSound.listenerId = owner->GetListenerId(); + } + + // clear out the sounds from our spawnargs since we'll copy them from the weapon def + const idKeyValue *kv = spawnArgs.MatchPrefix( "snd_" ); + while( kv ) { + spawnArgs.Delete( kv->GetKey() ); + kv = spawnArgs.MatchPrefix( "snd_" ); + } + + weaponDef = NULL; + dict = NULL; + + kick_endtime = 0; + + icon = ""; + + pushVelocity.Zero(); + + status = WP_HOLSTERED; + state = ""; + idealState = ""; + animBlendFrames = 0; + animDoneTime = 0; + + lightOn = false; + + zoomFov = 90; + + weaponAngleOffsetAverages = 10; //initialize this to default number to prevent infinite loops + + FreeModelDef(); +} + +/* +================ +hhWeapon::InitWorldModel +================ +*/ +void hhWeapon::InitWorldModel( const idDict *dict ) { + idEntity *ent; + + ent = worldModel.GetEntity(); + if ( !ent || !dict ) { + return; + } + + const char *model = dict->GetString( "model_world" ); + const char *attach = dict->GetString( "joint_attach" ); + + if (gameLocal.isMultiplayer) { //HUMANHEAD rww - shadow default based on whether the player shadows + ent->GetRenderEntity()->noShadow = MP_PLAYERNOSHADOW_DEFAULT; + } + + if ( model[0] && attach[0] ) { + ent->Show(); + ent->SetModel( model ); + ent->GetPhysics()->SetContents( 0 ); + ent->GetPhysics()->SetClipModel( NULL, 1.0f ); + ent->BindToJoint( owner.GetEntity(), 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 = LIGHTID_VIEW_MUZZLE_FLASH + owner->entityNumber; + } + } else { + ent->SetModel( "" ); + ent->Hide(); + } +} + +/* +================ +hhWeapon::GetWeaponDef + +HUMANHEAD: aob - this should only be called after the weapon has just been created +================ +*/ +void hhWeapon::GetWeaponDef( const char *objectname ) { + //HUMANHEAD: aob - put logic in helper function + ParseDef( objectname ); + //HUMANHEAD END + + if( owner->inventory.weaponRaised[owner->GetWeaponNum(objectname)] || gameLocal.isMultiplayer ) + ProcessEvent( &EV_Weapon_State, "Raise", 0 ); + else { + ProcessEvent( &EV_Weapon_State, "NewRaise", 0 ); + owner->inventory.weaponRaised[owner->GetWeaponNum(objectname)] = true; + } +} + +/* +================ +hhWeapon::ParseDef + +HUMANHEAD: aob +================ +*/ +void hhWeapon::ParseDef( const char* objectname ) { + if( !objectname || !objectname[ 0 ] ) { + return; + } + + if( !owner.IsValid() ) { + gameLocal.Error( "hhWeapon::ParseDef: no owner" ); + } + + weaponDef = gameLocal.FindEntityDef( objectname, false ); + if (!weaponDef) { + gameLocal.Error( "Unknown weaponDef: %s\n", objectname ); + } + dict = &(weaponDef->dict); + + // setup the world model + InitWorldModel( dict ); + + if (!owner.IsValid() || !owner.GetEntity()) { + gameLocal.Error("NULL owner in hhWeapon::ParseDef!"); + } + + //HUMANHEAD: aob + const idDict* infoDict = gameLocal.FindEntityDefDict( dict->GetString("def_fireInfo"), false ); + if( infoDict ) { + fireController = CreateFireController(); + if( fireController ) { + fireController->Init( infoDict, this, owner.GetEntity() ); + } + } + + infoDict = gameLocal.FindEntityDefDict( dict->GetString("def_altFireInfo"), false ); + if( infoDict ) { + altFireController = CreateAltFireController(); + if( altFireController ) { + altFireController->Init( infoDict, this, owner.GetEntity() ); + } + } + //HUMANHEAD END + + icon = dict->GetString( "inv_icon" ); + + // copy the sounds from the weapon view model def into out spawnargs + const idKeyValue *kv = dict->MatchPrefix( "snd_" ); + while( kv ) { + spawnArgs.Set( kv->GetKey(), kv->GetValue() ); + kv = dict->MatchPrefix( "snd_", kv ); + } + + weaponAngleOffsetAverages = dict->GetInt( "weaponAngleOffsetAverages", "10" ); + weaponAngleOffsetScale = dict->GetFloat( "weaponAngleOffsetScale", "0.1" ); + weaponAngleOffsetMax = dict->GetFloat( "weaponAngleOffsetMax", "10" ); + + weaponOffsetTime = dict->GetFloat( "weaponOffsetTime", "400" ); + weaponOffsetScale = dict->GetFloat( "weaponOffsetScale", "0.002" ); + + zoomFov = dict->GetInt( "zoomFov", "70" ); + + InitScriptObject( dict->GetString("scriptobject") ); + + nozzleFx = weaponDef->dict.GetBool( "nozzleFx", "0" ); + nozzleGlowColor = weaponDef->dict.GetVector("nozzleGlowColor", "1 1 1"); + nozzleGlowRadius = weaponDef->dict.GetFloat("nozzleGlowRadius", "10"); + nozzleGlowOffset = weaponDef->dict.GetVector( "nozzleGlowOffset", "0 0 0" ); + nozzleGlowShader = declManager->FindMaterial( weaponDef->dict.GetString( "mtr_nozzleGlowShader", "" ), false ); + GetJointHandle( weaponDef->dict.GetString( "nozzleJoint", "" ), nozzleJointHandle ); + + //HUMANHEAD bjk + int clipAmmo = owner->inventory.clip[owner->GetWeaponNum(objectname)]; + if ( ( clipAmmo < 0 ) || ( clipAmmo > fireController->ClipSize() ) ) { + // first time using this weapon so have it fully loaded to start + clipAmmo = fireController->ClipSize(); + if ( clipAmmo > fireController->AmmoAvailable() ) { + clipAmmo = fireController->AmmoAvailable(); + } + } + fireController->AddToClip(clipAmmo); + + WEAPON_ALTMODE = owner->inventory.altMode[owner->GetWeaponNum(objectname)]; + //HUMANHEAD END + + Show(); +} + +/* +================ +idWeapon::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 hhWeapon::ShouldConstructScriptObjectAtSpawn( void ) const { + return false; +} + +/* +================ +hhWeapon::InitScriptObject +================ +*/ +void hhWeapon::InitScriptObject( const char* objectType ) { + if( !objectType || !objectType[0] ) { + gameLocal.Error( "No scriptobject set on '%s'.", dict->GetString("classname") ); + } + + if( !idStr::Icmp(scriptObject.GetTypeName(), objectType) ) { + //Same script object, don't reload it + return; + } + + // setup script object + if( !scriptObject.SetType(objectType) ) { + gameLocal.Error( "Script object '%s' not found on weapon '%s'.", objectType, dict->GetString("classname") ); + } + + WEAPON_ATTACK.LinkTo( scriptObject, "WEAPON_ATTACK" ); + WEAPON_RELOAD.LinkTo( scriptObject, "WEAPON_RELOAD" ); + WEAPON_RAISEWEAPON.LinkTo( scriptObject, "WEAPON_RAISEWEAPON" ); + WEAPON_LOWERWEAPON.LinkTo( scriptObject, "WEAPON_LOWERWEAPON" ); + WEAPON_NEXTATTACK.LinkTo( scriptObject, "nextAttack" ); //HUMANHEAD rww + + //HUMANHEAD: aob + WEAPON_ALTATTACK.LinkTo( scriptObject, "WEAPON_ALTATTACK" ); + WEAPON_ASIDEWEAPON.LinkTo( scriptObject, "WEAPON_ASIDEWEAPON" ); + WEAPON_ALTMODE.LinkTo( scriptObject, "WEAPON_ALTMODE" ); + //HUMANHEAD END + + // call script object's constructor + ConstructScriptObject(); +} + +/*********************************************************************** + + GUIs + +***********************************************************************/ + +/* +================ +hhWeapon::Icon +================ +*/ +const char *hhWeapon::Icon( void ) const { + return icon; +} + +/* +================ +hhWeapon::UpdateGUI +================ +*/ +void hhWeapon::UpdateGUI() { + if ( !renderEntity.gui[0] ) { + return; + } + + if ( status == WP_HOLSTERED ) { + return; + } + + int ammoamount = AmmoAvailable(); + int altammoamount = AltAmmoAvailable(); + + // show remaining ammo + renderEntity.gui[ 0 ]->SetStateInt( "ammoamount", ammoamount ); + renderEntity.gui[ 0 ]->SetStateInt( "altammoamount", altammoamount ); + renderEntity.gui[ 0 ]->SetStateBool( "ammolow", ammoamount > 0 && ammoamount <= LowAmmo() ); + renderEntity.gui[ 0 ]->SetStateBool( "altammolow", altammoamount > 0 && altammoamount <= LowAltAmmo() ); + renderEntity.gui[ 0 ]->SetStateBool( "ammoempty", ( ammoamount == 0 ) ); + renderEntity.gui[ 0 ]->SetStateBool( "altammoempty", ( altammoamount == 0 ) ); + renderEntity.gui[ 0 ]->SetStateInt( "clipammoAmount", AmmoInClip() ); + renderEntity.gui[ 0 ]->SetStateInt( "clipaltammoAmount", AltAmmoInClip() ); + renderEntity.gui[ 0 ]->StateChanged(gameLocal.time); +} + +/*********************************************************************** + + Model and muzzleflash + +***********************************************************************/ +/* +================ +hhWeapon::GetGlobalJointTransform + +This returns the offset and axis of a weapon bone in world space, suitable for attaching models or lights +================ +*/ +bool hhWeapon::GetJointWorldTransform( const char* jointName, idVec3 &offset, idMat3 &axis ) { + weaponJointHandle_t handle; + + GetJointHandle( jointName, handle ); + return GetJointWorldTransform( handle, offset, axis ); +} + +/* +================ +hhWeapon::GetGlobalJointTransform + +This returns the offset and axis of a weapon bone in world space, suitable for attaching models or lights +================ +*/ +bool hhWeapon::GetJointWorldTransform( const weaponJointHandle_t& handle, idVec3 &offset, idMat3 &axis, bool muzzleOnly ) { + //FIXME: this seems to work but may need revisiting + //FIXME: totally forgot about mirrors and portals. This may not work. + if( (!pm_thirdPerson.GetBool() && owner == gameLocal.GetLocalPlayer()) || (gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->spectating && gameLocal.GetLocalPlayer()->spectator == owner->entityNumber) || (gameLocal.isServer && !muzzleOnly)) { //rww - mp server should always create projectiles from the viewmodel + // view model + if ( hhAnimatedEntity::GetJointWorldTransform(handle.view, offset, axis) ) { + return true; + } + } else { + // world model + if ( worldModel.IsValid() && worldModel.GetEntity()->GetJointWorldTransform(handle.world, offset, axis) ) { + return true; + } + } + offset = GetOrigin(); + axis = GetAxis(); + return false; +} + +/* +================ +hhWeapon::GetJointHandle + +HUMANHEAD: aob +================ +*/ +void hhWeapon::GetJointHandle( const char* jointName, weaponJointHandle_t& handle ) { + if( dict ) { + handle.view = GetAnimator()->GetJointHandle( jointName ); + } + + if( worldModel.IsValid() ) { + handle.world = worldModel->GetAnimator()->GetJointHandle( jointName ); + } +} + +/* +================ +hhWeapon::SetPushVelocity +================ +*/ +void hhWeapon::SetPushVelocity( const idVec3 &pushVelocity ) { + this->pushVelocity = pushVelocity; +} + + +/*********************************************************************** + + State control/player interface + +***********************************************************************/ + +/* +================ +hhWeapon::Raise +================ +*/ +void hhWeapon::Raise( void ) { + WEAPON_RAISEWEAPON = true; + WEAPON_ASIDEWEAPON = false; +} + +/* +================ +hhWeapon::PutAway +================ +*/ +void hhWeapon::PutAway( void ) { + //hasBloodSplat = false; + WEAPON_LOWERWEAPON = true; + WEAPON_RAISEWEAPON = false; //HUMANHEAD bjk PCF 5-4-06 : fix for weapons being up after death + WEAPON_ASIDEWEAPON = false; + WEAPON_ATTACK = false; + WEAPON_ALTATTACK = false; +} + + +/* +================ +hhWeapon::PutAside +================ +*/ +void hhWeapon::PutAside( void ) { + //HUMANHEAD bjk PCF (4-27-06) - no setting unnecessary weapon state + //WEAPON_LOWERWEAPON = false; + //WEAPON_RAISEWEAPON = false; + WEAPON_ASIDEWEAPON = true; + WEAPON_ATTACK = false; + WEAPON_ALTATTACK = false; +} + +/* +================ +hhWeapon::PutUpright +================ +*/ +void hhWeapon::PutUpright( void ) { + WEAPON_ASIDEWEAPON = false; +} + +/* +================ +hhWeapon::SnapDown + +HUMANHEAD: aob +================ +*/ +void hhWeapon::SnapDown() { + ProcessEvent( &EV_Weapon_State, "Down", 0 ); +} + +/* +================ +hhWeapon::SnapUp + +HUMANHEAD: aob +================ +*/ +void hhWeapon::SnapUp() { + ProcessEvent( &EV_Weapon_State, "Up", 0 ); +} + +/* +================ +hhWeapon::Reload +================ +*/ +void hhWeapon::Reload( void ) { + WEAPON_RELOAD = true; +} + +/* +================ +hhWeapon::HideWeapon +================ +*/ +void hhWeapon::HideWeapon( void ) { + Hide(); + if ( worldModel.GetEntity() ) { + worldModel.GetEntity()->Hide(); + } +} + +/* +================ +hhWeapon::ShowWeapon +================ +*/ +void hhWeapon::ShowWeapon( void ) { + Show(); + if ( worldModel.GetEntity() ) { + worldModel.GetEntity()->Show(); + } +} + +/* +================ +hhWeapon::HideWorldModel +================ +*/ +void hhWeapon::HideWorldModel( void ) { + if ( worldModel.GetEntity() ) { + worldModel.GetEntity()->Hide(); + } +} + +/* +================ +hhWeapon::ShowWorldModel +================ +*/ +void hhWeapon::ShowWorldModel( void ) { + if ( worldModel.GetEntity() ) { + worldModel.GetEntity()->Show(); + } +} + +/* +================ +hhWeapon::OwnerDied +================ +*/ +void hhWeapon::OwnerDied( void ) { + Hide(); + if ( worldModel.GetEntity() ) { + worldModel.GetEntity()->Hide(); + } +} + +/* +================ +hhWeapon::BeginAltAttack +================ +*/ +void hhWeapon::BeginAltAttack( void ) { + WEAPON_ALTATTACK = true; + + //if ( status != WP_OUTOFAMMO ) { + // lastAttack = gameLocal.time; + //} +} + +/* +================ +hhWeapon::EndAltAttack +================ +*/ +void hhWeapon::EndAltAttack( void ) { + WEAPON_ALTATTACK = false; + +} + +/* +================ +hhWeapon::BeginAttack +================ +*/ +void hhWeapon::BeginAttack( void ) { + //if ( status != WP_OUTOFAMMO ) { + // lastAttack = gameLocal.time; + //} + + WEAPON_ATTACK = true; +} + +/* +================ +hhWeapon::EndAttack +================ +*/ +void hhWeapon::EndAttack( void ) { + if ( !WEAPON_ATTACK.IsLinked() ) { + return; + } + if ( WEAPON_ATTACK ) { + WEAPON_ATTACK = false; + } +} + +/* +================ +hhWeapon::isReady +================ +*/ +bool hhWeapon::IsReady( void ) const { + return !IsHidden() && ( ( status == WP_READY ) || ( status == WP_OUTOFAMMO ) ); +} + +/* +================ +hhWeapon::IsReloading +================ +*/ +bool hhWeapon::IsReloading( void ) const { + return ( status == WP_RELOAD ); +} + +/* +================ +hhWeapon::IsChangable +================ +*/ +bool hhWeapon::IsChangable( void ) const { + //HUMANHEAD: aob - same as IsReady except w/o IsHidden check + //Allows us to switch weapons while they are hidden + //Hope this doesn't fuck to many things + return ( ( status == WP_READY ) || ( status == WP_OUTOFAMMO ) ); +} + +/* +================ +hhWeapon::IsHolstered +================ +*/ +bool hhWeapon::IsHolstered( void ) const { + return ( status == WP_HOLSTERED ); +} + + +/* +================ +hhWeapon::IsLowered +================ +*/ +bool hhWeapon::IsLowered( void ) const { + return ( status == WP_HOLSTERED ) || ( status == WP_LOWERING ); +} + + +/* +================ +hhWeapon::IsRising +================ +*/ +bool hhWeapon::IsRising( void ) const { + return ( status == WP_RISING ); +} + + +/* +================ +hhWeapon::IsAside +================ +*/ +bool hhWeapon::IsAside( void ) const { + return ( status == WP_ASIDE ); +} + + +/* +================ +hhWeapon::ShowCrosshair +================ +*/ +bool hhWeapon::ShowCrosshair( void ) const { + //HUMANHEAD: aob - added cinematic check + return !( state == idStr( WP_RISING ) || state == idStr( WP_LOWERING ) || state == idStr( WP_HOLSTERED ) ) && !owner->InCinematic(); +} + +/* +===================== +hhWeapon::DropItem +===================== +*/ +idEntity* hhWeapon::DropItem( const idVec3 &velocity, int activateDelay, int removeDelay, bool died ) { + if ( !weaponDef || !worldModel.GetEntity() ) { + return NULL; + } + + const char *classname = weaponDef->dict.GetString( "def_dropItem" ); + if ( !classname[0] ) { + return NULL; + } + StopSound( SND_CHANNEL_BODY, !died ); //HUMANHEAD rww - on death, do not broadcast the stop, because the weapon itself is about to be removed + return idMoveableItem::DropItem( classname, worldModel.GetEntity()->GetPhysics()->GetOrigin(), worldModel.GetEntity()->GetPhysics()->GetAxis(), velocity, activateDelay, removeDelay ); +} + +/* +===================== +hhWeapon::CanDrop +===================== +*/ +bool hhWeapon::CanDrop( void ) const { + if ( !weaponDef || !worldModel.GetEntity() ) { + return false; + } + const char *classname = weaponDef->dict.GetString( "def_dropItem" ); + if ( !classname[ 0 ] ) { + return false; + } + return true; +} + +/*********************************************************************** + + Script state management + +***********************************************************************/ + +/* +===================== +hhWeapon::SetState +===================== +*/ +void hhWeapon::SetState( const char *statename, int blendFrames ) { + const function_t *func; + + func = scriptObject.GetFunction( statename ); + if ( !func ) { + gameLocal.Error( "Can't find function '%s' in object '%s'", statename, scriptObject.GetTypeName() ); + } + + thread->CallFunction( this, func, true ); + state = statename; + + animBlendFrames = blendFrames; + if ( g_debugWeapon.GetBool() ) { + gameLocal.Printf( "%d: weapon state : %s\n", gameLocal.time, statename ); + } + + idealState = ""; +} + +/*********************************************************************** + + Visual presentation + +***********************************************************************/ + +/* +================ +hhWeapon::MuzzleRise + +HUMANHEAD: aob +================ +*/ +void hhWeapon::MuzzleRise( idVec3 &origin, idMat3 &axis ) { + if( fireController ) { + fireController->CalculateMuzzleRise( origin, axis ); + } + + if( altFireController ) { + altFireController->CalculateMuzzleRise( origin, axis ); + } +} + +/* +================ +hhWeapon::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 *hhWeapon::ConstructScriptObject( void ) { + const function_t *constructor; + + //HUMANHEAD: aob + if( !thread ) { + return thread; + } + //HUMANHEAD END + + thread->EndThread(); + + // call script object's constructor + constructor = scriptObject.GetConstructor(); + if ( !constructor ) { + gameLocal.Error( "Missing constructor on '%s' for weapon", scriptObject.GetTypeName() ); + } + + // init the script object's data + scriptObject.ClearObject(); + thread->CallFunction( this, constructor, true ); + thread->Execute(); + + return thread; +} + +/* +================ +hhWeapon::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 hhWeapon::DeconstructScriptObject( void ) { + const function_t *destructor; + + if ( !thread ) { + return; + } + + // don't bother calling the script object's destructor on map shutdown + if ( gameLocal.GameState() == GAMESTATE_SHUTDOWN ) { + return; + } + + thread->EndThread(); + + // call script object's destructor + destructor = scriptObject.GetDestructor(); + if ( destructor ) { + // start a thread that will run immediately and end + thread->CallFunction( this, destructor, true ); + thread->Execute(); + thread->EndThread(); + } + + // clear out the object's memory + scriptObject.ClearObject(); +} + +/* +================ +hhWeapon::UpdateScript +================ +*/ +void hhWeapon::UpdateScript( void ) { + int count; + + if ( !IsLinked() ) { + return; + } + + // only update the script on new frames + if ( !gameLocal.isNewFrame ) { + return; + } + + if ( idealState.Length() ) { + SetState( idealState, animBlendFrames ); + } + + // update script state, which may call Event_LaunchProjectiles, among other things + count = 10; + while( ( thread->Execute() || idealState.Length() ) && count-- ) { + // happens for weapons with no clip (like grenades) + if ( idealState.Length() ) { + SetState( idealState, animBlendFrames ); + } + } + + WEAPON_RELOAD = false; +} + +/* +================ +hhWeapon::PresentWeapon +================ +*/ +void hhWeapon::PresentWeapon( bool showViewModel ) { + UpdateScript(); + + //HUMANHEAD rww - added this gui owner logic here + bool allowGuiUpdate = true; + if ( owner.IsValid() && gameLocal.localClientNum != owner->entityNumber ) { + // if updating the hud for a followed client + if ( gameLocal.localClientNum >= 0 && gameLocal.entities[ gameLocal.localClientNum ] && gameLocal.entities[ gameLocal.localClientNum ]->IsType( idPlayer::Type ) ) { + idPlayer *p = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.localClientNum ] ); + if ( !p->spectating || p->spectator != owner->entityNumber ) { + allowGuiUpdate = false; + } + } else { + allowGuiUpdate = false; + } + } + if (allowGuiUpdate) { + UpdateGUI(); + } + + // update animation + UpdateAnimation(); + + // only show the surface in player view + renderEntity.allowSurfaceInViewID = owner->entityNumber+1; + + // crunch the depth range so it never pokes into walls this breaks the machine gun gui + renderEntity.weaponDepthHack = true; + + // present the model + if ( showViewModel ) { + Present(); + } else { + FreeModelDef(); + } + + if ( worldModel.GetEntity() && worldModel.GetEntity()->GetRenderEntity() ) { + // deal with the third-person visible world model + // don't show shadows of the world model in first person +#ifdef HUMANHEAD + if ( gameLocal.isMultiplayer || (g_showPlayerShadow.GetBool() && pm_modelView.GetInteger() == 1) || pm_thirdPerson.GetBool() + || (pm_modelView.GetInteger() == 2 && owner->health <= 0)) { + worldModel->GetRenderEntity()->suppressShadowInViewID = 0; + } else { + worldModel->GetRenderEntity()->suppressShadowInViewID = owner->entityNumber+1; + worldModel->GetRenderEntity()->suppressShadowInLightID = LIGHTID_VIEW_MUZZLE_FLASH + owner->entityNumber; + } +#else + + // deal with the third-person visible world model + // don't show shadows of the world model in first person + if ( gameLocal.isMultiplayer || g_showPlayerShadow.GetBool() || pm_thirdPerson.GetBool() ) { + worldModel.GetEntity()->GetRenderEntity()->suppressShadowInViewID = 0; + } else { + worldModel.GetEntity()->GetRenderEntity()->suppressShadowInViewID = owner->entityNumber+1; + worldModel.GetEntity()->GetRenderEntity()->suppressShadowInLightID = LIGHTID_VIEW_MUZZLE_FLASH + owner->entityNumber; + } + } +#endif // HUMANHEAD pdm + } + + // update the muzzle flash light, so it moves with the gun + //HUMANHEAD: aob + if( fireController ) { + fireController->UpdateMuzzleFlash(); + } + + if( altFireController ) { + altFireController->UpdateMuzzleFlash(); + } + //HUMANHEAD END + + UpdateNozzleFx(); // expensive + + UpdateSound(); +} + + +/* +================ +hhWeapon::EnterCinematic +================ +*/ +void hhWeapon::EnterCinematic( void ) { + StopSound( SND_CHANNEL_ANY, false ); + + if ( IsLinked() ) { + SetState( "EnterCinematic", 0 ); + thread->Execute(); + + WEAPON_ATTACK = false; + WEAPON_RELOAD = false; +// WEAPON_NETRELOAD = false; +// WEAPON_NETENDRELOAD = false; + WEAPON_RAISEWEAPON = false; + WEAPON_LOWERWEAPON = false; + } + + //disabled = true; + + LowerWeapon(); +} + +/* +================ +hhWeapon::ExitCinematic +================ +*/ +void hhWeapon::ExitCinematic( void ) { + //disabled = false; + + if ( IsLinked() ) { + SetState( "ExitCinematic", 0 ); + thread->Execute(); + } + + RaiseWeapon(); +} + +/* +================ +hhWeapon::NetCatchup +================ +*/ +void hhWeapon::NetCatchup( void ) { + if ( IsLinked() ) { + SetState( "NetCatchup", 0 ); + thread->Execute(); + } +} + +/* +================ +hhWeapon::GetClipBits +================ +*/ +int hhWeapon::GetClipBits(void) const { + return ASYNC_PLAYER_INV_CLIP_BITS; +} + +/* +================ +hhWeapon::GetZoomFov +================ +*/ +int hhWeapon::GetZoomFov( void ) { + return zoomFov; +} + +/* +================ +hhWeapon::GetWeaponAngleOffsets +================ +*/ +void hhWeapon::GetWeaponAngleOffsets( int *average, float *scale, float *max ) { + *average = weaponAngleOffsetAverages; + *scale = weaponAngleOffsetScale; + *max = weaponAngleOffsetMax; +} + +/* +================ +hhWeapon::GetWeaponTimeOffsets +================ +*/ +void hhWeapon::GetWeaponTimeOffsets( float *time, float *scale ) { + *time = weaponOffsetTime; + *scale = weaponOffsetScale; +} + + +/*********************************************************************** + + Ammo + +**********************************************************************/ + +/* +================ +hhWeapon::WriteToSnapshot +================ +*/ +void hhWeapon::WriteToSnapshot( idBitMsgDelta &msg ) const { +#if 0 + //FIXME: need to add altFire stuff too + if( fireController ) { + msg.WriteBits( fireController->AmmoInClip(), GetClipBits() ); + } else { + msg.WriteBits( 0, GetClipBits() ); + } + msg.WriteBits(worldModel.GetSpawnId(), 32); +#else //rww - forget this silliness, let's do this the Right Way. + msg.WriteBits(owner.GetSpawnId(), 32); + msg.WriteBits(worldModel.GetSpawnId(), 32); + + if (fireController) { + fireController->WriteToSnapshot(msg); + } + else { //rwwFIXME this is an ugly hack + msg.WriteBits(0, 32); + msg.WriteBits(0, 32); + msg.WriteBits(0, GetClipBits()); + } + if (altFireController) { + altFireController->WriteToSnapshot(msg); + } + else { //rwwFIXME this is an ugly hack + msg.WriteBits(0, 32); + msg.WriteBits(0, 32); + msg.WriteBits(0, GetClipBits()); + } + + msg.WriteBits(WEAPON_ASIDEWEAPON, 1); + //HUMANHEAD PCF rww 05/09/06 - no need to sync these values + /* + msg.WriteBits(WEAPON_LOWERWEAPON, 1); + msg.WriteBits(WEAPON_RAISEWEAPON, 1); + */ + + WriteBindToSnapshot(msg); +#endif +} + +/* +================ +hhWeapon::ReadFromSnapshot +================ +*/ +void hhWeapon::ReadFromSnapshot( const idBitMsgDelta &msg ) { +#if 0 + //FIXME: need to add altFire stuff too + if( fireController ) { + fireController->AddToClip( msg.ReadBits(GetClipBits()) ); + } else { + msg.ReadBits( GetClipBits() ); + } + + worldModel.SetSpawnId(msg.ReadBits(32)); +#else + bool wmInit = false; + + owner.SetSpawnId(msg.ReadBits(32)); + + if (worldModel.SetSpawnId(msg.ReadBits(32))) { //do this once we finally get the entity in the snapshot + wmInit = true; + } + + //rwwFIXME this is a little hacky, i think. is there a way to do it in ::Spawn? but, the weapon doesn't know its owner at that point. + if (!dict) { + if (!owner.IsValid() || !owner.GetEntity()) { + gameLocal.Error("NULL owner in hhWeapon::ReadFromSnapshot!"); + } + + assert(owner.IsValid()); + + GetWeaponDef(spawnArgs.GetString("classname")); + //owner->ForceWeapon(this); it doesn't like this. + } + assert(fireController); + assert(altFireController); + + if (wmInit) { //need to do this once the ent is valid on the client + InitWorldModel( dict ); + GetJointHandle( weaponDef->dict.GetString( "nozzleJoint", "" ), nozzleJointHandle ); + fireController->UpdateWeaponJoints(); + altFireController->UpdateWeaponJoints(); + } + + fireController->ReadFromSnapshot(msg); + altFireController->ReadFromSnapshot(msg); + + WEAPON_ASIDEWEAPON = !!msg.ReadBits(1); + //HUMANHEAD PCF rww 05/09/06 - no need to sync these values + /* + WEAPON_LOWERWEAPON = !!msg.ReadBits(1); + WEAPON_RAISEWEAPON = !!msg.ReadBits(1); + */ + + ReadBindFromSnapshot(msg); +#endif +} + +/* +================ +hhWeapon::ClientPredictionThink +================ +*/ +void hhWeapon::ClientPredictionThink( void ) { + Think(); +} + +/*********************************************************************** + + Script events + +***********************************************************************/ + +/* +hhWeapon::Event_Raise +*/ +void hhWeapon::Event_Raise() { + Raise(); +} + + +/* +=============== +hhWeapon::Event_WeaponState +=============== +*/ +void hhWeapon::Event_WeaponState( const char *statename, int blendFrames ) { + 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; + animBlendFrames = blendFrames; + thread->DoneProcessing(); +} + +/* +=============== +hhWeapon::Event_WeaponReady +=============== +*/ +void hhWeapon::Event_WeaponReady( void ) { + status = WP_READY; + WEAPON_RAISEWEAPON = false; + + //HUMANHEAD bjk PCF (4-27-06) - no setting unnecessary weapon state + //WEAPON_LOWERWEAPON = false; + //WEAPON_ASIDEWEAPON = false; +} + +/* +=============== +hhWeapon::Event_WeaponOutOfAmmo +=============== +*/ +void hhWeapon::Event_WeaponOutOfAmmo( void ) { + status = WP_OUTOFAMMO; + WEAPON_RAISEWEAPON = false; + + //HUMANHEAD bjk PCF (4-27-06) - no setting unnecessary weapon state + //WEAPON_LOWERWEAPON = false; + //WEAPON_ASIDEWEAPON = false; +} + +/* +=============== +hhWeapon::Event_WeaponReloading +=============== +*/ +void hhWeapon::Event_WeaponReloading( void ) { + status = WP_RELOAD; +} + +/* +=============== +hhWeapon::Event_WeaponHolstered +=============== +*/ +void hhWeapon::Event_WeaponHolstered( void ) { + status = WP_HOLSTERED; + WEAPON_LOWERWEAPON = false; +} + +/* +=============== +hhWeapon::Event_WeaponRising +=============== +*/ +void hhWeapon::Event_WeaponRising( void ) { + status = WP_RISING; + WEAPON_LOWERWEAPON = false; + + //HUMANHEAD bjk PCF (4-27-06) - no setting unnecessary weapon state + //WEAPON_ASIDEWEAPON = false; + + owner->WeaponRisingCallback(); +} + + +/* +=============== +hhWeapon::Event_WeaponAside +=============== +*/ +void hhWeapon::Event_Weapon_Aside( void ) { + status = WP_ASIDE; +} + +/* +=============== +hhWeapon::Event_Weapon_PuttingAside +=============== +*/ +void hhWeapon::Event_Weapon_PuttingAside( void ) { + status = WP_PUTTING_ASIDE; +} + +/* +=============== +hhWeapon::Event_Weapon_Uprighting +=============== +*/ +void hhWeapon::Event_Weapon_Uprighting( void ) { + status = WP_UPRIGHTING; +} + +/* +=============== +hhWeapon::Event_WeaponLowering +=============== +*/ +void hhWeapon::Event_WeaponLowering( void ) { + status = WP_LOWERING; + WEAPON_RAISEWEAPON = false; + + //HUMANHEAD: aob + WEAPON_ASIDEWEAPON = false; + //HUMANHEAD END + + owner->WeaponLoweringCallback(); +} + +/* +=============== +hhWeapon::Event_AddToClip +=============== +*/ +void hhWeapon::Event_AddToClip( int amount ) { + if( fireController ) { + fireController->AddToClip( amount ); + } +} + +/* +=============== +hhWeapon::Event_AmmoInClip +=============== +*/ +void hhWeapon::Event_AmmoInClip( void ) { + int ammo = AmmoInClip(); + idThread::ReturnFloat( ammo ); +} + +/* +=============== +hhWeapon::Event_AmmoAvailable +=============== +*/ +void hhWeapon::Event_AmmoAvailable( void ) { + int ammoAvail = AmmoAvailable(); + idThread::ReturnFloat( ammoAvail ); +} + +/* +=============== +hhWeapon::Event_ClipSize +=============== +*/ +void hhWeapon::Event_ClipSize( void ) { + idThread::ReturnFloat( ClipSize() ); +} + +/* +=============== +hhWeapon::Event_PlayAnim +=============== +*/ +void hhWeapon::Event_PlayAnim( int channel, const char *animname ) { + int anim = 0; + + anim = GetAnimator()->GetAnim( animname ); + if ( !anim ) { + //HUMANHEAD: aob + WEAPON_DEBUG( "missing '%s' animation on '%s'", animname, name.c_str() ); + //HUMANHEAD END + GetAnimator()->Clear( channel, gameLocal.GetTime(), FRAME2MS( animBlendFrames ) ); + animDoneTime = 0; + } else { + //Show(); AOB - removed so we can use hidden weapons Hope this doesn't fuck to many things + GetAnimator()->PlayAnim( channel, anim, gameLocal.GetTime(), FRAME2MS( animBlendFrames ) ); + animDoneTime = GetAnimator()->CurrentAnim( channel )->GetEndTime(); + if ( worldModel.GetEntity() ) { + anim = worldModel.GetEntity()->GetAnimator()->GetAnim( animname ); + if ( anim ) { + worldModel.GetEntity()->GetAnimator()->PlayAnim( channel, anim, gameLocal.GetTime(), FRAME2MS( animBlendFrames ) ); + } + } + } + animBlendFrames = 0; +} + +/* +=============== +hhWeapon::Event_AnimDone +=============== +*/ +void hhWeapon::Event_AnimDone( int channel, int blendFrames ) { + if ( animDoneTime - FRAME2MS( blendFrames ) <= gameLocal.time ) { + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +/* +================ +hhWeapon::Event_Next +================ +*/ +void hhWeapon::Event_Next( void ) { + // change to another weapon if possible + owner->NextBestWeapon(); +} + +/* +================ +hhWeapon::Event_Flashlight +================ +*/ +void hhWeapon::Event_Flashlight( int enable ) { +/* + if ( enable ) { + lightOn = true; + MuzzleFlashLight(); + } else { + lightOn = false; + muzzleFlashEnd = 0; + } +*/ +} +/* +================ +hhWeapon::Event_FireProjectiles + +HUMANHEAD: aob +================ +*/ +void hhWeapon::Event_FireProjectiles() { + //HUMANHEAD: aob - moved logic to this helper function + LaunchProjectiles( fireController ); + //HUMANHEAD END +} + +/* +================ +hhWeapon::Event_GetOwner +================ +*/ +void hhWeapon::Event_GetOwner( void ) { + idThread::ReturnEntity( owner.GetEntity() ); +} + +/* +=============== +hhWeapon::Event_UseAmmo +=============== +*/ +void hhWeapon::Event_UseAmmo( int amount ) { + UseAmmo(); +} + +/* +=============== +hhWeapon::Event_UseAltAmmo +=============== +*/ +void hhWeapon::Event_UseAltAmmo( int amount ) { + UseAltAmmo(); +} + +/* +================ +hhWeapon::RestoreGUI + +HUMANHEAD: aob +================ +*/ +void hhWeapon::RestoreGUI( const char* guiKey, idUserInterface** gui ) { + if( guiKey && guiKey[0] && gui ) { + AddRenderGui( dict->GetString(guiKey), gui, dict ); + } +} + +/* +================ +hhWeapon::Think +================ +*/ +void hhWeapon::Think() { + if( thinkFlags & TH_TICKER ) { + if (owner.IsValid()) { + Ticker(); + } + } +} + +/* +================ +hhWeapon::SpawnWorldModel +================ +*/ +idEntity* hhWeapon::SpawnWorldModel( const char* worldModelDict, idActor* _owner ) { +/* + assert( _owner && worldModelDict ); + + idEntity* pEntity = NULL; + idDict* pArgs = declManager->FindEntityDefDict( worldModelDict, false ); + if( !pArgs ) { return NULL; } + + idStr ownerWeaponBindBone = _owner->spawnArgs.GetString( "bone_weapon_bind" ); + idStr attachBone = pArgs->GetString( "attach" ); + if( attachBone.Length() && ownerWeaponBindBone.Length() ) { + pEntity = gameLocal.SpawnEntityType( idEntity::Type, pArgs ); + HH_ASSERT( pEntity ); + + pEntity->GetPhysics()->SetContents( 0 ); + pEntity->GetPhysics()->DisableClip(); + pEntity->MoveJointToJoint( attachBone.c_str(), _owner, ownerWeaponBindBone.c_str() ); + pEntity->AlignJointToJoint( attachBone.c_str(), _owner, ownerWeaponBindBone.c_str() ); + pEntity->BindToJoint( _owner, ownerWeaponBindBone.c_str(), true ); + + // supress model in player views and in any views on the weapon itself + pEntity->renderEntity.suppressSurfaceInViewID = _owner->entityNumber + 1; + } +*/ + return NULL; +} + +/* +================ +hhWeapon::SetModel +================ +*/ +void hhWeapon::SetModel( const char *modelname ) { + assert( modelname ); + + if ( modelDefHandle >= 0 ) { + gameRenderWorld->RemoveDecals( modelDefHandle ); + } + + hhAnimatedEntity::SetModel( modelname ); + + // hide the model until an animation is played + Hide(); +} + +/* +================ +hhWeapon::FreeModelDef +================ +*/ +void hhWeapon::FreeModelDef() { + hhAnimatedEntity::FreeModelDef(); +} + +/* +================ +hhWeapon::GetPhysicsToVisualTransform +================ +*/ +bool hhWeapon::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + bool bResult = hhAnimatedEntity::GetPhysicsToVisualTransform( origin, axis ); + + assert(!FLOAT_IS_NAN(cameraShakeOffset.x)); + assert(!FLOAT_IS_NAN(cameraShakeOffset.y)); + assert(!FLOAT_IS_NAN(cameraShakeOffset.z)); + + if( !cameraShakeOffset.Compare(vec3_zero, VECTOR_EPSILON) ) { + origin = (bResult) ? cameraShakeOffset + origin : cameraShakeOffset; + origin *= GetPhysics()->GetAxis().Transpose(); + if( !bResult ) { axis = mat3_identity; } + return true; + } + + return bResult; +} + +/* +================ +hhWeapon::GetMasterDefaultPosition +================ +*/ +void hhWeapon::GetMasterDefaultPosition( idVec3 &masterOrigin, idMat3 &masterAxis ) const { + idActor* actor = NULL; + idEntity* master = GetBindMaster(); + + if( master ) { + if( master->IsType(idActor::Type) ) { + actor = static_cast( master ); + actor->DetermineOwnerPosition( masterOrigin, masterAxis ); + + masterOrigin = actor->ApplyLandDeflect( masterOrigin, 1.1f ); + } else { + hhAnimatedEntity::GetMasterDefaultPosition( masterOrigin, masterAxis ); + } + } +} + +/* +================ +hhWeapon::SetShaderParm +================ +*/ +void hhWeapon::SetShaderParm( int parmnum, float value ) { + hhAnimatedEntity::SetShaderParm(parmnum, value); + if ( worldModel.IsValid() ) { + worldModel->SetShaderParm(parmnum, value); + } +} + +/* +================ +hhWeapon::SetSkin +================ +*/ +void hhWeapon::SetSkin( const idDeclSkin *skin ) { + hhAnimatedEntity::SetSkin(skin); + if ( worldModel.IsValid() ) { + worldModel->SetSkin(skin); + } +} + +/* +================ +hhWeapon::PlayCycle +================ +*/ +void hhWeapon::Event_PlayCycle( int channel, const char *animname ) { + int anim; + + anim = GetAnimator()->GetAnim( animname ); + if ( !anim ) { + //HUMANHEAD: aob + WEAPON_DEBUG( "missing '%s' animation on '%s'", animname, name.c_str() ); + //HUMANHEAD END + GetAnimator()->Clear( channel, gameLocal.GetTime(), FRAME2MS( animBlendFrames ) ); + animDoneTime = 0; + } else { + //Show();AOB - removed so we can use hidden weapons Hope this doesn't fuck to many things + + // NLANOTE - Used to be CARandom + GetAnimator()->CycleAnim( channel, anim, gameLocal.GetTime(), FRAME2MS( animBlendFrames ) ); + animDoneTime = GetAnimator()->CurrentAnim( channel )->GetEndTime(); + if ( worldModel.GetEntity() ) { + anim = worldModel.GetEntity()->GetAnimator()->GetAnim( animname ); + // NLANOTE - Used to be CARandom + worldModel.GetEntity()->GetAnimator()->CycleAnim( channel, anim, gameLocal.GetTime(), FRAME2MS( animBlendFrames ) ); + } + } + animBlendFrames = 0; +} + +/* +================ +hhWeapon::Event_IsAnimPlaying +================ +*/ +void hhWeapon::Event_IsAnimPlaying( const char *animname ) { + idThread::ReturnInt( animator.IsAnimPlaying(animname) ); +} + + +/* +================ +hhWeapon::Event_EjectBrass +================ +*/ +void hhWeapon::Event_EjectBrass() { + if( fireController ) { + fireController->EjectBrass(); + } +} + +/* +================ +hhWeapon::Event_EjectAltBrass +================ +*/ +void hhWeapon::Event_EjectAltBrass() { + if( altFireController ) { + altFireController->EjectBrass(); + } +} + +/* +================ +hhWeapon::Event_PlayAnimWhenReady +================ +*/ +void hhWeapon::Event_PlayAnimWhenReady( const char* animName ) { +/* + if( !IsReady() && (!dict || !dict->GetBool("pickupHasRaise") || !IsRaising()) ) { + CancelEvents( &EV_PlayAnimWhenReady ); + PostEventSec( &EV_PlayAnimWhenReady, 0.5f, animName ); + return; + } + + PlayAnim( animName, 0, &EV_Weapon_Ready ); +*/ +} + +/* +================ +hhWeapon::Event_SpawnFXAlongBone +================ +*/ +void hhWeapon::Event_SpawnFXAlongBone( idList* fxParms ) { + if ( !owner->CanShowWeaponViewmodel() || !fxParms ) { + return; + } + + HH_ASSERT( fxParms->Num() == 2 ); + + hhFxInfo fxInfo; + fxInfo.UseWeaponDepthHack( true ); + BroadcastFxInfoAlongBone( dict->GetString((*fxParms)[0].c_str()), (*fxParms)[1].c_str(), &fxInfo, NULL, false ); //rww - default to not broadcast from events +} + +/* +============================== +hhWeapon::Event_FireAltProjectiles +============================== +*/ +void hhWeapon::Event_FireAltProjectiles() { + LaunchProjectiles( altFireController ); +} + +// This is called once per frame to precompute where the weapon is pointing. This is used for determining if crosshairs should display as targetted as +// well as fire controllers to update themselves. This must be called before UpdateCrosshairs(). +void hhWeapon::PrecomputeTraceInfo() { + // This is needed for fireControllers, even if there are no crosshairs + idVec3 eyePos = owner->GetEyePosition(); + idMat3 weaponAxis = GetAxis(); + float traceDist = 1024.0f; // was CM_MAX_TRACE_DIST + + // Perform eye trace + gameLocal.clip.TracePoint( eyeTraceInfo, eyePos, eyePos + weaponAxis[0] * traceDist, + MASK_SHOT_BOUNDINGBOX | CONTENTS_GAME_PORTAL, owner.GetEntity() ); + + // CJR: If the trace hit a portal, then force it to trace the max distance, as if it's tracing through the portal + if ( eyeTraceInfo.fraction < 1.0f ) { + idEntity *ent = gameLocal.GetTraceEntity( eyeTraceInfo ); + if ( ent->IsType( hhPortal::Type ) ) { + eyeTraceInfo.endpos = eyePos + weaponAxis[0] * traceDist; + eyeTraceInfo.fraction = 1.0f; + } + } + +} + +/* +============================== +hhWeapon::UpdateCrosshairs +============================== +*/ +void hhWeapon::UpdateCrosshairs( bool &crosshair, bool &targeting ) { + idEntity* ent = NULL; + trace_t traceInfo; + + crosshair = false; + targeting = false; + if (spawnArgs.GetBool("altModeWeapon")) { + if (WEAPON_ALTMODE) { // Moded weapon in alt-mode + if (altFireController) { + crosshair = altFireController->UsesCrosshair(); + } + } + else { // Moded weapon in normal mode + if (fireController) { + crosshair = fireController->UsesCrosshair(); + } + } + } + else { // Normal non-moded weapon + if (altFireController && altFireController->UsesCrosshair()) { + crosshair = true; + } + if (fireController && fireController->UsesCrosshair()) { + crosshair = true; + } + } + + ent = NULL; + if( crosshair ) { + traceInfo = GetEyeTraceInfo(); + if( traceInfo.fraction < 1.0f ) { + ent = gameLocal.GetTraceEntity(traceInfo); + } + targeting = ( ent && ent->fl.takedamage && !(ent->IsType( hhDoor::Type ) || ent->IsType( hhModelDoor::Type ) || ent->IsType( hhConsole::Type ) ) ); + } +} + +/* +============================== +hhWeapon::GetAmmoType +============================== +*/ +ammo_t hhWeapon::GetAmmoType( void ) const { + return (fireController) ? fireController->GetAmmoType() : 0; +} + +/* +============================== +hhWeapon::AmmoAvailable +============================== +*/ +int hhWeapon::AmmoAvailable( void ) const { + return (fireController) ? fireController->AmmoAvailable() : 0; +} + +/* +============================== +hhWeapon::AmmoInClip +============================== +*/ +int hhWeapon::AmmoInClip( void ) const { + return (fireController) ? fireController->AmmoInClip() : 0; +} + +/* +============================== +hhWeapon::ClipSize +============================== +*/ +int hhWeapon::ClipSize( void ) const { + return (fireController) ? fireController->ClipSize() : 0; +} + +/* +============================== +hhWeapon::AmmoRequired +============================== +*/ +int hhWeapon::AmmoRequired( void ) const { + return (fireController) ? fireController->AmmoRequired() : 0; +} + +/* +============================== +hhWeapon::LowAmmo +============================== +*/ +int hhWeapon::LowAmmo() { + return (fireController) ? fireController->LowAmmo() : 0; +} + +/* +============================== +hhWeapon::GetAltAmmoType +============================== +*/ +ammo_t hhWeapon::GetAltAmmoType( void ) const { + return (altFireController) ? altFireController->GetAmmoType() : 0; +} + +/* +============================== +hhWeapon::AltAmmoAvailable +============================== +*/ +int hhWeapon::AltAmmoAvailable( void ) const { + return (altFireController) ? altFireController->AmmoAvailable() : 0; +} + +/* +============================== +hhWeapon::AltAmmoInClip +============================== +*/ +int hhWeapon::AltAmmoInClip( void ) const { + return (altFireController) ? altFireController->AmmoInClip() : 0; +} + +/* +============================== +hhWeapon::AltClipSize +============================== +*/ +int hhWeapon::AltClipSize( void ) const { + return (altFireController) ? altFireController->ClipSize() : 0; +} + +/* +============================== +hhWeapon::AltAmmoRequired +============================== +*/ +int hhWeapon::AltAmmoRequired( void ) const { + return (altFireController) ? altFireController->AmmoRequired() : 0; +} + +/* +============================== +hhWeapon::LowAltAmmo +============================== +*/ +int hhWeapon::LowAltAmmo() { + return (altFireController) ? altFireController->LowAmmo() : 0; +} + +/* +============================== +hhWeapon::GetAltMode +HUMANHEAD bjk +============================== +*/ +bool hhWeapon::GetAltMode() const { + return WEAPON_ALTMODE != 0; + +} + +/* +============================== +hhWeapon::UseAmmo +============================== +*/ +void hhWeapon::UseAmmo() { + if (fireController) { + fireController->UseAmmo(); + } +} + +/* +============================== +hhWeapon::UseAltAmmo +============================== +*/ +void hhWeapon::UseAltAmmo() { + if (altFireController) { + altFireController->UseAmmo(); + } +} + +/* +============================== +hhWeapon::CheckDeferredProjectiles + +HUMANEAD: rww +============================== +*/ +void hhWeapon::CheckDeferredProjectiles(void) { + if (fireController) { + fireController->CheckDeferredProjectiles(); + } + if (altFireController) { + altFireController->CheckDeferredProjectiles(); + } +} + +/* +============================== +hhWeapon::LaunchProjectiles + +HUMANEAD: aob +============================== +*/ +void hhWeapon::LaunchProjectiles( hhWeaponFireController* controller ) { + if ( IsHidden() ) { + return; + } + + // wake up nearby monsters + if ( !spawnArgs.GetBool( "silent_fire" ) ) { + gameLocal.AlertAI( owner.GetEntity() ); + } + + // set the shader parm to the time of last projectile firing, + // which the gun material shaders can reference for single shot barrel glows, etc + SetShaderParm( SHADERPARM_DIVERSITY, gameLocal.random.CRandomFloat() ); + SetShaderParm( SHADERPARM_TIMEOFFSET, -MS2SEC( gameLocal.realClientTime ) ); + + float low = ((float)controller->AmmoAvailable()*controller->AmmoRequired())/controller->LowAmmo(); + + if( !controller->LaunchProjectiles(pushVelocity) ) { + return; + } + + if (!gameLocal.isClient) { //HUMANHEAD rww - let everyone hear this sound, and broadcast it (so don't try to play it for client-projectile weapons) + if( controller->AmmoAvailable()*controller->AmmoRequired() <= controller->LowAmmo() && low > 1 && spawnArgs.FindKey("snd_lowammo")) { + StartSound( "snd_lowammo", SND_CHANNEL_ANY, 0, true, NULL ); + } + } + + controller->UpdateMuzzleKick(); + + // add the light for the muzzleflash + controller->MuzzleFlash(); + + //HUMANEHAD: aob + controller->WeaponFeedback(); + //HUMANHEAD END +} + +/* +=============== +hhWeapon::SetViewAnglesSensitivity + +HUMANHEAD: aob +=============== +*/ +void hhWeapon::SetViewAnglesSensitivity( float fov ) { + if( owner.IsValid() ) { + owner->SetViewAnglesSensitivity( fov / g_fov.GetFloat() ); + } +} + +/* +=============== +hhWeapon::GetDict + +HUMANHEAD: aob +=============== +*/ +const idDict* hhWeapon::GetDict( const char* objectname ) { + return gameLocal.FindEntityDefDict( objectname, false ); +} + +/* +=============== +hhWeapon::GetFireInfoDict + +HUMANHEAD: aob +=============== +*/ +const idDict* hhWeapon::GetFireInfoDict( const char* objectname ) { + const idDict* dict = GetDict( objectname ); + if( !dict ) { + return NULL; + } + + return gameLocal.FindEntityDefDict( dict->GetString("def_fireInfo"), false ); +} + +/* +=============== +hhWeapon::GetAltFireInfoDict + +HUMANHEAD: aob +=============== +*/ +const idDict* hhWeapon::GetAltFireInfoDict( const char* objectname ) { + const idDict* dict = GetDict( objectname ); + if( !dict ) { + return NULL; + } + + return gameLocal.FindEntityDefDict( dict->GetString("def_altFireInfo"), false ); +} + +void hhWeapon::Event_GetString(const char *key) { + if ( fireController ) { + idThread::ReturnString( fireController->GetString(key) ); + } +} + +void hhWeapon::Event_GetAltString(const char *key) { + if ( altFireController ) { + idThread::ReturnString( altFireController->GetString(key) ); + } +} + +/* +=============== +hhWeapon::Event_AddToAltClip +=============== +*/ +void hhWeapon::Event_AddToAltClip( int amount ) { + HH_ASSERT( altFireController ); + + altFireController->AddToClip( amount ); +} + +/* +=============== +hhWeapon::Event_AmmoInClip +=============== +*/ +void hhWeapon::Event_AltAmmoInClip( void ) { + HH_ASSERT( altFireController ); + + idThread::ReturnFloat( altFireController->AmmoInClip() ); +} + +/* +=============== +hhWeapon::Event_AltAmmoAvailable +=============== +*/ +void hhWeapon::Event_AltAmmoAvailable( void ) { + HH_ASSERT( altFireController ); + + idThread::ReturnFloat( altFireController->AmmoAvailable() ); +} + +/* +=============== +hhWeapon::Event_ClipSize +=============== +*/ +void hhWeapon::Event_AltClipSize( void ) { + HH_ASSERT( altFireController ); + + idThread::ReturnFloat( altFireController->ClipSize() ); +} + +/* +============================== +hhWeapon::Event_GetFireDelay +============================== +*/ +void hhWeapon::Event_GetFireDelay() { + HH_ASSERT( fireController ); + + idThread::ReturnFloat( fireController->GetFireDelay() ); +} + +/* +============================== +hhWeapon::Event_GetAltFireDelay +============================== +*/ +void hhWeapon::Event_GetAltFireDelay() { + HH_ASSERT( altFireController ); + + idThread::ReturnFloat( altFireController->GetFireDelay() ); +} + +/* +============================== +hhWeapon::Event_HasAmmo +============================== +*/ +void hhWeapon::Event_HasAmmo() { + idThread::ReturnInt( fireController->HasAmmo() ); +} + +/* +============================== +hhWeapon::Event_HasAltAmmo +============================== +*/ +void hhWeapon::Event_HasAltAmmo() { + idThread::ReturnInt( altFireController->HasAmmo() ); +} + +/* +=============== +hhWeapon::Event_SetViewAnglesSensitivity + +HUMANHEAD: aob +=============== +*/ +void hhWeapon::Event_SetViewAnglesSensitivity( float fov ) { + SetViewAnglesSensitivity( fov ); +} + +/* +================ +hhWeapon::UpdateNozzleFx +================ +*/ +void hhWeapon::UpdateNozzleFx( void ) { + + if ( !nozzleFx || !g_projectileLights.GetBool()) { + return; + } + + if ( nozzleJointHandle.view == INVALID_JOINT ) { + return; + } + + // + // vent light + // + if ( nozzleGlowHandle == -1 ) { + memset(&nozzleGlow, 0, sizeof(nozzleGlow)); + if ( owner.IsValid() ) { + nozzleGlow.allowLightInViewID = owner->entityNumber+1; + } + nozzleGlow.pointLight = true; + nozzleGlow.noShadows = true; + nozzleGlow.lightRadius.x = nozzleGlowRadius; + nozzleGlow.lightRadius.y = nozzleGlowRadius; + nozzleGlow.lightRadius.z = nozzleGlowRadius; + nozzleGlow.shader = nozzleGlowShader; + GetJointWorldTransform( nozzleJointHandle, nozzleGlow.origin, nozzleGlow.axis ); + + nozzleGlow.origin += nozzleGlowOffset * nozzleGlow.axis; + nozzleGlowHandle = gameRenderWorld->AddLightDef(&nozzleGlow); + } + + GetJointWorldTransform(nozzleJointHandle, nozzleGlow.origin, nozzleGlow.axis ); + nozzleGlow.origin += nozzleGlowOffset * nozzleGlow.axis; + + nozzleGlow.shaderParms[ SHADERPARM_RED ] = nozzleGlowColor.x; + nozzleGlow.shaderParms[ SHADERPARM_GREEN ] = nozzleGlowColor.y; + nozzleGlow.shaderParms[ SHADERPARM_BLUE ] = nozzleGlowColor.z; + + // Copy parms from the weapon into this light + for( int i = 4; i < 8; i++ ) { + nozzleGlow.shaderParms[ i ] = GetRenderEntity()->shaderParms[ i ]; + } + + gameRenderWorld->UpdateLightDef(nozzleGlowHandle, &nozzleGlow); +} + +void hhWeapon::Event_HideWeapon(void) { + HideWeapon(); +} + +void hhWeapon::Event_ShowWeapon(void) { + ShowWeapon(); +} + +/* +=============== +hhWeapon::FillDebugVars +=============== +*/ +void hhWeapon::FillDebugVars(idDict *args, int page) { + idStr text; + + switch(page) { + case 1: + args->SetBool("WEAPON_LOWERWEAPON", WEAPON_LOWERWEAPON != 0); + args->SetBool("WEAPON_RAISEWEAPON", WEAPON_RAISEWEAPON != 0); + args->SetBool("WEAPON_ASIDEWEAPON", WEAPON_ASIDEWEAPON != 0); + args->SetBool("WEAPON_ATTACK", WEAPON_ATTACK != 0); + args->SetBool("WEAPON_ALTATTACK", WEAPON_ALTATTACK != 0); + args->SetBool("WEAPON_ALTMODE", WEAPON_ALTMODE != 0); + args->SetBool("WEAPON_RELOAD", WEAPON_RELOAD != 0); + break; + } +} diff --git a/src/Prey/prey_baseweapons.h b/src/Prey/prey_baseweapons.h new file mode 100644 index 0000000..e0e96d0 --- /dev/null +++ b/src/Prey/prey_baseweapons.h @@ -0,0 +1,365 @@ +#ifndef __HH_PREY_BASE_WEAPONS_H +#define __HH_PREY_BASE_WEAPONS_H + +class hhPlayer; + +extern const idEventDef EV_Weapon_Aside; +extern const idEventDef EV_PlayAnimWhenReady; +extern const idEventDef EV_Weapon_Feedback; +extern const idEventDef EV_PlayCycle; + +//HUMANHEAD: aob +extern const idEventDef EV_Weapon_FireAltProjectiles; +extern const idEventDef EV_Weapon_FireProjectiles; +//HUMANHEAD END + +/*********************************************************************** + + hhWeapon + + Base class for common weapon data and methods +***********************************************************************/ +class hhWeapon: public hhAnimatedEntity { + CLASS_PROTOTYPE( hhWeapon ); + + //HUMANHEAD: aob - used to allow physics object to have access to hhWeaponBases' private methods and vars + friend hhPhysics_StaticWeapon; + //HUMANHEAD END + + public: + hhWeapon(); + virtual ~hhWeapon(); + void Spawn(); + virtual void Think(); + + virtual void Clear(); + virtual void GetWeaponDef( const char *objectname, int ammoinclip ) { GetWeaponDef(objectname); } + virtual void GetWeaponDef( const char *objectname ); + virtual void SetOwner( idPlayer *_owner ); + + virtual void RestoreGUI( const char* guiKey, idUserInterface** gui ); + + virtual void EnterCinematic(); + virtual void ExitCinematic(); + void NetCatchup(); + + virtual int GetClipBits(void) const; + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); + + // GUIs + const char * Icon( void ) const; + virtual void UpdateGUI(); + + virtual void SetModel( const char *modelname ); + virtual void FreeModelDef(); + void GetJointHandle( const char* jointName, weaponJointHandle_t& handle ); + bool GetJointWorldTransform( const char* jointName, idVec3 &offset, idMat3 &axis ); + bool GetJointWorldTransform( const weaponJointHandle_t& handle, idVec3 &offset, idMat3 &axis, bool muzzleOnly = false ); //rww - added muzzleOnly parameter for mp + void SetPushVelocity( const idVec3 &pushVelocity ); + virtual void SetSkin( const idDeclSkin *skin ); + virtual void SetShaderParm( int parmnum, float value ); + + bool IsLinked( void ); + bool IsWorldModelReady( void ); + + // State control/player interface + virtual void Raise( void ); + virtual void PutAway( void ); + virtual void Reload( void ); + //HUMANHEAD: aob - put bodies here + void LowerWeapon( void ) {} + void RaiseWeapon( void ) {} + //HUMANHEAD END + virtual void HideWeapon( void ); + void ShowWeapon( void ); + void HideWorldModel( void ); + void ShowWorldModel( void ); + void OwnerDied( void ); + void BeginAttack( void ); + virtual void EndAttack( void ); + virtual bool IsReady( void ) const; + virtual bool IsReloading( void ) const; + bool IsChangable( void ) const; + virtual bool IsHolstered( void ) const; + bool IsLowered( void ) const; // nla + bool IsRising( void ) const; // nla + bool IsAside( void ) const; // nla + void PutAside( void ); // nla + void PutUpright( void ); // nla + virtual int GetAnimDoneTime( void ) const { return( animDoneTime ); } // nla + + bool ShowCrosshair( void ) const; + idEntity* DropItem( const idVec3 &velocity, int activateDelay, int removeDelay, bool died ); + bool CanDrop( void ) const; + void WeaponStolen( void ) {} + + // Script state management + virtual bool ShouldConstructScriptObjectAtSpawn( void ) const; + virtual idThread *ConstructScriptObject( void ); + virtual void DeconstructScriptObject( void ); + void SetState( const char *statename, int blendFrames ); + void UpdateScript( void ); + + // Visual presentation + virtual // HUMANHEAD: made virtual + void PresentWeapon( bool showViewModel ); + int GetZoomFov(); + void GetWeaponAngleOffsets( int *average, float *scale, float *max ); + void GetWeaponTimeOffsets( float *time, float *scale ); + + //Ammo + ammo_t GetAmmoType( void ) const; + int AmmoAvailable( void ) const; + int AmmoInClip( void ) const; + void ResetAmmoClip( void ) {} + int ClipSize( void ) const; + int AmmoRequired( void ) const; + void UseAmmo(); + int LowAmmo(); + + + //HUMANHEAD: altAmmo + ammo_t GetAltAmmoType( void ) const; + int AltAmmoAvailable( void ) const; + int AltAmmoInClip( void ) const; + int AltClipSize( void ) const; + int LowAltAmmo() const; + int AltAmmoRequired( void ) const; + void UseAltAmmo(); + int LowAltAmmo(); + //HUMANHEAD END + + bool GetAltMode() const; + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + + // Visual presentation + void InitWorldModel( const idDict *dict ); + void MuzzleRise( idVec3 &origin, idMat3 &axis ); + + //HUMANHEAD: aob + virtual void ParseDef( const char* objectname ); + virtual void InitScriptObject( const char* objectType ); + static idEntity* SpawnWorldModel( const char* worldModelDict, idActor* _owner ); + virtual void GetMasterDefaultPosition( idVec3 &masterOrigin, idMat3 &masterAxis ) const; + virtual hhPlayer* GetOwner() const { return owner.GetEntity(); } + virtual void UpdateCrosshairs( bool &crosshair, bool &targeting ); + + virtual int GetHandedness() const { return( handedness ); } + + virtual void BeginAltAttack( void ); + void EndAltAttack( void ); + + int GetStatus() { return( status ); }; + const idDict * GetDict() { return( dict ); }; + + static const idDict* GetDict( const char* objectname ); + static const idDict* GetFireInfoDict( const char* objectname ); + static const idDict* GetAltFireInfoDict( const char* objectname ); + + int GetKickEndTime() const { return kick_endtime; } + void SetKickEndTime( int endTime ) { kick_endtime = endTime; } + idVec3 GetMuzzlePosition() const { return fireController->GetMuzzlePosition(); } + idVec3 GetAltMuzzlePosition() const { return altFireController->GetMuzzlePosition(); } + + void SnapDown(); + void SnapUp(); + + void PrecomputeTraceInfo(); + const trace_t& GetEyeTraceInfo() const { return eyeTraceInfo; } + + void SetViewAnglesSensitivity( float fov ); + + void UpdateNozzleFx( void ); + //HUMANEHAD END + + void CheckDeferredProjectiles(void); //HUMANHEAD rww + + virtual void FillDebugVars( idDict *args, int page ); //HUMANHEAD bjk + + protected: + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + + void LaunchProjectiles( hhWeaponFireController* controller ); + + //needed for overridding + //FIXME: would like to make these templates just in case we change types. + ID_INLINE virtual hhWeaponFireController* CreateFireController(); + ID_INLINE virtual hhWeaponFireController* CreateAltFireController(); + //HUMANHEAD END + + protected: + //HUMANHEAD: aob + void Event_PlayAnimWhenReady( const char* animName ); + void Event_Raise(); // nla + void Event_Weapon_Aside(); + void Event_Weapon_PuttingAside(); + void Event_Weapon_Uprighting(); + + //Called from frame command + void Event_SpawnFXAlongBone( idList* fxParms ); + + void Event_WeaponOutOfAltAmmo(); + void Event_AddToAltClip( int amount ); + void Event_AltAmmoInClip(); + void Event_AltAmmoAvailable(); + void Event_AltClipSize(); + void Event_HasAltAmmo(); + + void Event_HasAmmo(); + + virtual void Event_FireAltProjectiles(); + virtual void Event_FireProjectiles(); + + void Event_EjectAltBrass( void ); + + void Event_IsAnimPlaying( const char *animname ); // CJR + + void Event_GetFireDelay(); + void Event_GetAltFireDelay(); + void Event_GetSpread(); + void Event_GetAltSpread(); + void Event_GetString(const char *key); + void Event_GetAltString(const char *key); + + void Event_SetViewAnglesSensitivity( float fov); + void Event_GetOwner( void ); + void Event_UseAmmo( int amount ); + void Event_UseAltAmmo( int amount ); + //HUMANHEAD END + + //idWeapon events + // script events + void Event_WeaponState( const char *statename, int blendFrames ); + void Event_SetWeaponStatus( float newStatus ); + void Event_WeaponReady( void ); + void Event_WeaponOutOfAmmo( void ); + void Event_WeaponReloading( void ); + void Event_WeaponHolstered( void ); + void Event_WeaponRising( void ); + void Event_WeaponLowering( void ); + void Event_AddToClip( int amount ); + void Event_AmmoInClip( void ); + void Event_AmmoAvailable( void ); + void Event_ClipSize( void ); + void Event_PlayAnim( int channel, const char *animname ); + void Event_PlayCycle( int channel, const char *animname ); + void Event_AnimDone( int channel, int blendFrames ); + void Event_WaitFrame( void ); + void Event_Next( void ); + void Event_SetSkin( const char *skinname ); + void Event_Flashlight( int enable ); + void Event_EjectBrass( void ); + void Event_HideWeapon( void ); + void Event_ShowWeapon( void ); + + protected: + hhWeaponFireController* fireController; + hhWeaponFireController* altFireController; + trace_t eyeTraceInfo; + + idVec3 cameraShakeOffset; + hhPhysics_StaticWeapon physicsObj; + + // 0 - no hands, 1 - right, 2 - left, 3 - both + int handedness; // nla - For determining which hands can be up with which weapons + + idScriptBool WEAPON_ALTATTACK; + idScriptBool WEAPON_ASIDEWEAPON; // nla + idScriptBool WEAPON_ALTMODE; // for moded weapons like rifle, whether in alt mode + + //idWeapon vars + // script control + idScriptBool WEAPON_ATTACK; + idScriptBool WEAPON_RELOAD; + idScriptBool WEAPON_RAISEWEAPON; + idScriptBool WEAPON_LOWERWEAPON; + idScriptFloat WEAPON_NEXTATTACK; //rww + weaponStatus_t status; + idThread * thread; + idStr state; + idStr idealState; + int animBlendFrames; + int animDoneTime; + + //HUMANHEAD: aob - made hhPlayer + idEntityPtr owner; + idEntityPtr worldModel; + + // weapon kick + int kick_endtime; + + idVec3 pushVelocity; + + // weapon definition + const idDeclEntityDef * weaponDef; + const idDict * dict; + + idStr icon; + + bool lightOn; + + // zoom + int zoomFov; // variable zoom fov per weapon + + // weighting for viewmodel angles + int weaponAngleOffsetAverages; + float weaponAngleOffsetScale; + float weaponAngleOffsetMax; + float weaponOffsetTime; + float weaponOffsetScale; + + // Nozzle FX + bool nozzleFx; + weaponJointHandle_t nozzleJointHandle; + renderLight_t nozzleGlow; // nozzle light + int nozzleGlowHandle; // handle for nozzle light + idVec3 nozzleGlowColor; // color of the nozzle glow + const idMaterial * nozzleGlowShader; // shader for glow light + float nozzleGlowRadius; // radius of glow light + idVec3 nozzleGlowOffset; // offset from bound bone + +}; + +/* +================ +hhWeapon::IsLinked +================ +*/ +ID_INLINE bool hhWeapon::IsLinked( void ) { + return scriptObject.HasObject(); +} + +/* +================ +hhWeapon::IsWorldModelReady +================ +*/ +ID_INLINE bool hhWeapon::IsWorldModelReady( void ) { + return worldModel.IsValid(); +} + +/* +================ +hhWeapon::CreateFireController +================ +*/ +ID_INLINE hhWeaponFireController* hhWeapon::CreateFireController() { + return new hhWeaponFireController; +} + +/* +================ +hhWeapon::CreateAltFireController +================ +*/ +ID_INLINE hhWeaponFireController* hhWeapon::CreateAltFireController() { + return CreateFireController(); +} + +#endif diff --git a/src/Prey/prey_beam.cpp b/src/Prey/prey_beam.cpp new file mode 100644 index 0000000..652bbff --- /dev/null +++ b/src/Prey/prey_beam.cpp @@ -0,0 +1,1150 @@ +//************************************************************************** +//** +//** PREY_BEAM.CPP +//** +//** Code for Prey-specific beams +//** +//************************************************************************** + +// HEADER FILES ------------------------------------------------------------ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +// Game code includes +#include "prey_local.h" + +#define DEBUG_BEAMS 0 + +static idVec3 CurveExtractSpline(idVec3 *cPoints, int cPointCount, float alpha, bool bThruCtrlPnts); + +const idEventDef EV_ToggleBeamLength("", "d"); +const idEventDef EV_SetBeamPhaseScale( "setBeamPhaseScale", "f" ); // bg +const idEventDef EV_SetBeamOffsetScale( "setBeamOffsetScale", "f" ); // bg + +CLASS_DECLARATION(idEntity, hhBeamSystem) + EVENT( EV_Activate, hhBeamSystem::Event_Activate ) + EVENT( EV_SetBeamPhaseScale, hhBeamSystem::Event_SetBeamPhaseScale) // bg + EVENT( EV_SetBeamOffsetScale, hhBeamSystem::Event_SetBeamOffsetScale) // bg +END_CLASS + + +hhBeamSystem::hhBeamSystem() { + beamList = NULL; + offsetScale = 1.0f; + phaseScale = 1.0f; + bActive = false; + + targetLocation = vec3_origin; + + decl = NULL; +} + +hhBeamSystem::~hhBeamSystem() { + if(renderEntity.beamNodes) { + Mem_Free( renderEntity.beamNodes ); + renderEntity.beamNodes = NULL; + } + + if(beamList) { + delete [] beamList; + beamList = NULL; + } +} + +void hhBeamSystem::Spawn(void) { + beamList = NULL; + + if (!gameLocal.isClient || fl.clientEntity) { //rww - for client in mp this gets done after the first snapshot is read... + InitSystem( spawnArgs.GetString("model") ); + } + + beamLength = spawnArgs.GetFloat( "lengthBeam" ); + bRigidBeamLength = spawnArgs.GetBool( "rigidBeamLength" ); + + targetEntity = NULL; + targetEntityId = 0; + targetEntityOffset.Zero(); + + SetArcVector( vec3_origin ); + + bActive = spawnArgs.GetBool( "start_off" ); + Activate( !bActive ); +} + +void hhBeamSystem::Activate( const bool bActivate ) { + if( bActive != bActivate ) { + bActive = bActivate; + + if( bActive ) { + BecomeActive( TH_UPDATEPARTICLES ); + Show(); + }else { + BecomeInactive( TH_UPDATEPARTICLES ); + Hide(); + } + } +} + +hhBeamSystem* hhBeamSystem::SpawnBeam( const idVec3& start, const char* modelName, const idMat3& axis, bool pureLocal ) { + idDict Args; + hhBeamSystem* beamSystem = NULL; + idStr modelString; + +#if !GOLD + //rww - checking for creation of beams at nan location + assert(!FLOAT_IS_NAN(start.x)); + assert(!FLOAT_IS_NAN(start.y)); + assert(!FLOAT_IS_NAN(start.z)); + assert(!FLOAT_IS_NAN(axis[0].x)); + assert(!FLOAT_IS_NAN(axis[0].y)); + assert(!FLOAT_IS_NAN(axis[0].z)); + assert(!FLOAT_IS_NAN(axis[1].x)); + assert(!FLOAT_IS_NAN(axis[1].y)); + assert(!FLOAT_IS_NAN(axis[1].z)); + assert(!FLOAT_IS_NAN(axis[2].x)); + assert(!FLOAT_IS_NAN(axis[2].y)); + assert(!FLOAT_IS_NAN(axis[2].z)); +#endif + + if ( !modelName || !modelName[0] ) { + return NULL; + } + + // Ensure that the modelname ends with a valid .beam extention + modelString = modelName; + modelString.DefaultFileExtension( ".beam" ); + + Args.Set( "spawnclass", "hhBeamSystem" ); + Args.Set( "model", modelString.c_str() ); + Args.SetVector( "origin", start ); + Args.SetMatrix( "rotation", axis ); + + HH_ASSERT(!gameLocal.isClient || pureLocal); + + beamSystem = (hhBeamSystem *)gameLocal.SpawnEntityTypeClient( hhBeamSystem::Type, &Args ); + if (beamSystem) { //make sure it's sync'd + beamSystem->fl.networkSync = !pureLocal; + } + HH_ASSERT( beamSystem ); + + beamSystem->SetTargetEntity( NULL ); + beamSystem->SetTargetLocation( start ); // Necessary in case the beam starts out hidden + beamSystem->beamAxis = mat3_identity; + + return beamSystem; +} + +//========================================================================== +// +// hhBeamSystem::InitSystem +// +// Reads beam system information from the entity definition +// +// Commands: +// beamNum - total number of beams in the system +// beamNodes - total number of nodes per beam +// +// Per beam (end number specifies the beam): +// +// beamThickness0 - Thickness +// beamTaperEndPoints0 - If the beam should have tapered end points +// beamShader0 - Shader name +// beamSpline0 - if this beam system uses splines +//========================================================================== + +void hhBeamSystem::InitSystem( const char* modelName ) { + int i; + + HH_ASSERT( modelName ); + + if(renderEntity.beamNodes) { // Delete the previous version before initializing + Mem_Free(renderEntity.beamNodes); + renderEntity.beamNodes = NULL; + } + + // Initialize beam data from the beam file + declName = modelName; + decl = declManager->FindBeam( modelName ); + renderEntity.declBeam = decl; + + // Get new model loaded + if( !renderEntity.hModel || idStr::Icmp( renderEntity.hModel->Name(), modelName ) ) { + renderEntity.hModel = renderModelManager->FindModel( modelName ); + } + + // Allocate the beams + if( beamList ) { + delete[] beamList; + } + beamList = new hhBeam[decl->numBeams]; + + // Allocate the number of beam infos (used for rendering) + renderEntity.beamNodes = (hhBeamNodes_t *)Mem_ClearedAlloc(sizeof(hhBeamNodes_t) * decl->numBeams); + + beamAxis = GetPhysics()->GetAxis(); + + // Initialize each beam with specific info + for(i = 0; i < decl->numBeams; i++) { + beamList[i].Init( this, &renderEntity.beamNodes[i] ); + } + + random.SetSeed( gameLocal.time ); + beamTime = gameLocal.time + random.RandomInt( 5000 ); // Don't let any beams stay in sync. +} + +//========================================================================== +// +// hhBeamSystem::SetTargetLocation +// +//========================================================================== + +void hhBeamSystem::SetTargetLocation(idVec3 newLoc) { + targetLocation = newLoc; + + beamLength = (targetLocation - GetPhysics()->GetOrigin()).Length(); +} + +//========================================================================== +// +// hhBeamSystem::SetTargetEntity +// +// Sets the entity to target from this beam. If joint is INVALID_JOINT, +// then the origin of the entity will be used. +// +// Passing in a NULL entity will clear the target entity, and set the targetLocation to offset +// +// NOTE: The offset passed in should be in world coordinates relative to the model. +// NOTE: The offset can be used as an offset from the joint as well. +//========================================================================== + +void hhBeamSystem::SetTargetEntity( idEntity *ent, int traceId, idVec3 &offset ) { + idVec3 origin; + idMat3 axis; + + targetEntity = ent; + + if ( ent == NULL ) { + targetEntityId = 0; + targetEntityOffset.Zero(); + SetTargetLocation( offset ); + return; + } + + targetEntityId = traceId; + targetEntityOffset = offset; + + // Update the targetLocation + GetTargetLocation(); + + // Calculate the beam length from the new target location + beamLength = (targetLocation - GetPhysics()->GetOrigin()).Length(); +} + +//========================================================================== +// +// hhBeamSystem::SetTargetEntity +// +// Bone name version +//========================================================================== + +void hhBeamSystem::SetTargetEntity( idEntity *ent, const char *boneName, idVec3 &offset ) { + idVec3 origin; + idMat3 axis; + + targetEntity = ent; + + if ( ent == NULL || boneName == NULL ) { + targetEntityId = 0; + targetEntityOffset.Zero(); + SetTargetLocation( offset ); + return; + } + + targetEntityId = ent->GetAnimator()->GetJointHandle( boneName ); + targetEntityOffset = offset; + + // Update the targetLocation + GetTargetLocation(); + + // Calculate the beam length from the new target location + beamLength = (targetLocation - GetPhysics()->GetOrigin()).Length(); +} + +//========================================================================== +// +// hhBeamSystem::GetTargetLocation +// +//========================================================================== + +idVec3 hhBeamSystem::GetTargetLocation( void ) { + idVec3 origin; + idMat3 axis; + idVec3 boneOrigin; + idMat3 boneAxis; + jointHandle_t joint; + idAFEntity_Base *af = NULL; + + if ( targetEntity.IsValid() ) { // Has a target entity, so compute the offset + if ( targetEntityId > 0 ) { // This is a specific joint: + joint = (jointHandle_t)targetEntityId; + } else { // This is a collision handle + joint = CLIPMODEL_ID_TO_JOINT_HANDLE( targetEntityId ); + } + + if ( targetEntity->IsType( idAFEntity_Base::Type ) ) { + af = static_cast( targetEntity.GetEntity() ); + } + + axis = targetEntity->GetAxis(); + origin = targetEntity->GetOrigin(); + + // Calculate the targetLocation, based upon the type of entity + if ( af && af->IsActiveAF() ) { // AF, use the clip model id for the origin + int body = af->BodyForClipModelId( targetEntityId ); + origin = af->GetPhysics()->GetOrigin( body ); + targetLocation = origin + axis * targetEntityOffset; + //rww - added check for targetEntity->GetAnimator() not being null, in case targetEntity is not animated + } else if ( joint != INVALID_JOINT && targetEntity->GetAnimator() && targetEntity->GetAnimator()->GetJointTransform( joint, gameLocal.time, boneOrigin, boneAxis ) ) { // Joint-based + targetLocation = origin + axis * boneOrigin; + } else { // Default - use origin of the entity + targetLocation = origin + axis * targetEntityOffset; + } + } + + return targetLocation; +} + +//========================================================================== +// +// hhBeamSystem::SetAxis +// +//========================================================================== +void hhBeamSystem::SetAxis( const idMat3 &axis ) { + if (!GetPhysics()->GetAxis().Compare(axis)) { + idEntity::SetAxis(axis); + } +} + +//========================================================================== +// +// hhBeamSystem::SetBeamLength +// +//========================================================================== +void hhBeamSystem::SetBeamLength( const float length ) { + beamLength = length; + + if( bRigidBeamLength && beamLength > VECTOR_EPSILON ) { + SetTargetLocation( GetPhysics()->GetOrigin() + GetPhysics()->GetAxis()[0] * beamLength ); + } +} + +/* +================= +hhBeamSystem::WriteToSnapshot +rww - write applicable beam values to snapshot +================= +*/ +void hhBeamSystem::WriteToSnapshot( idBitMsgDelta &msg ) const { +#if !GOLD + gameLocal.Warning("Beam %i being sync'd by server!\n", entityNumber); +#endif + GetPhysics()->WriteToSnapshot( msg ); + + msg.WriteFloat(beamTime); + + //handle this. + //hhBeam *beamList; + + //this is the common method, but doing WriteDirs might be more efficient? + idCQuat quat = beamAxis.ToCQuat(); + msg.WriteFloat(quat.x); + msg.WriteFloat(quat.y); + msg.WriteFloat(quat.z); + + msg.WriteFloat(arcVector[0]); + msg.WriteFloat(arcVector[1]); + msg.WriteFloat(arcVector[2]); + + msg.WriteFloat(targetLocation[0]); + msg.WriteFloat(targetLocation[1]); + msg.WriteFloat(targetLocation[2]); + + if (targetEntity.IsValid()) { + msg.WriteBits(1, 1); + msg.WriteBits(targetEntity->entityNumber, GENTITYNUM_BITS); + } + else { + msg.WriteBits(0, 1); + } + + msg.WriteFloat(targetEntityOffset[0]); + msg.WriteFloat(targetEntityOffset[1]); + msg.WriteFloat(targetEntityOffset[2]); + + //do i care about this? + //int targetEntityId; + + msg.WriteFloat(beamLength); + msg.WriteBits(bRigidBeamLength, 1); + + msg.WriteFloat(phaseScale); + msg.WriteFloat(offsetScale); + + msg.WriteBits(bActive, 1); + + //rwwFIXME this is horrible. oh so very horrible. all those calls to SpawnBeam in code need to be removed, + //and they must all use seperate entity defs, or there is no alternative to sending a real string. =| + msg.WriteString(spawnArgs.GetString("model")); +} + +/* +================= +hhBeamSystem::ReadFromSnapshot +rww - read applicable beam values from snapshot +================= +*/ +void hhBeamSystem::ReadFromSnapshot( const idBitMsgDelta &msg ) { + GetPhysics()->ReadFromSnapshot( msg ); + + beamTime = msg.ReadFloat(); + + idCQuat quat; + quat.x = msg.ReadFloat(); + quat.y = msg.ReadFloat(); + quat.z = msg.ReadFloat(); + beamAxis = quat.ToMat3(); + + arcVector[0] = msg.ReadFloat(); + arcVector[1] = msg.ReadFloat(); + arcVector[2] = msg.ReadFloat(); + + targetLocation[0] = msg.ReadFloat(); + targetLocation[1] = msg.ReadFloat(); + targetLocation[2] = msg.ReadFloat(); + + int hasEnt = msg.ReadBits(1); + if (hasEnt) { + int entNum = msg.ReadBits(GENTITYNUM_BITS); + targetEntity = gameLocal.entities[entNum]; + } + else { + targetEntity = NULL; + } + + targetEntityOffset[0] = msg.ReadFloat(); + targetEntityOffset[1] = msg.ReadFloat(); + targetEntityOffset[2] = msg.ReadFloat(); + + beamLength = msg.ReadFloat(); + bRigidBeamLength = !!msg.ReadBits(1); + + phaseScale = msg.ReadFloat(); + offsetScale = msg.ReadFloat(); + + bool active = !!msg.ReadBits(1); + if (active != bActive) { + Activate(active); + } + + char modelName[128]; + msg.ReadString(modelName, 128); + if (!beamList) { + //then init the system. + InitSystem(modelName); + } +} + +/* +================= +hhBeamSystem::ClientPredictionThink +rww - minimal think on client +================= +*/ +void hhBeamSystem::ClientPredictionThink( void ) { + if (fl.clientEntity && snapshotOwner.IsValid()) { + if (!bActive || !gameLocal.EntInClientSnapshot(snapshotOwner->entityNumber)) { //if the snapshot entity i'm associated with is not in the snapshot, i hide + Hide(); + } + else { + Show(); + } + } + + if (gameLocal.isNewFrame) { + Think(); + } +} + +//========================================================================== +// +// hhBeamSystem::Think +// +// NOTE: Beams will not think AT ALL if they are hidden or not visible +//========================================================================== + +void hhBeamSystem::Think( void ) { + + RunPhysics(); + + if (thinkFlags & TH_UPDATEPARTICLES) { + + if( targets.Num() > 0 ) { + SetTargetEntity( targets[0].GetEntity(), NULL ); + } + + // Update the beamAxis to correctly reflect the target + if( !bRigidBeamLength ) { + idVec3 vec = (GetTargetLocation() - GetPhysics()->GetOrigin()); + beamLength = vec.Normalize(); + + if ( beamLength <= 0 ) { // Ignore beams with no length + return; + } + + if( GetBindMaster() && fl.bindOrientated ) { + beamAxis = GetAxis(); + } else { + beamAxis = vec.ToMat3(); + + if ( targets.Num() > 0 ) { + SetAxis( beamAxis ); // Target beams should update renderEntity information + } else { + GetPhysics()->SetAxis( beamAxis ); // Normal beams don't need to update render entity information + } + } + } else { + SetTargetLocation( GetPhysics()->GetOrigin() + GetPhysics()->GetAxis()[0] * beamLength ); + beamAxis = GetPhysics()->GetAxis(); + } + + assert(decl); + for( int i = 0; i < decl->numBeams; i++) { + ExecuteBeam( i, &beamList[i] ); + beamList[i].TransformNodes(); + } + } + + Present(); +} + +//========================================================================== +// +// hhBeamSystem::Event_Activate +// +// Triggering a beam will toggle its visibility +//========================================================================== + +void hhBeamSystem::Event_Activate( idEntity *activator ) { + Activate( !IsActivated() ); +} + +//========================================================================== +// +// hhBeamSystem::Event_SetBeamPhaseScale +// +//========================================================================== + +void hhBeamSystem::Event_SetBeamPhaseScale( float scale ) { + SetBeamPhaseScale( scale ); +} + +//========================================================================== +// +// hhBeamSystem::Event_SetBeamOffsetScale +// +//========================================================================== + +void hhBeamSystem::Event_SetBeamOffsetScale( float scale ) { + SetBeamOffsetScale( scale ); +} + +//========================================================================== +// +// hhBeamSystem::UpdateModel +// +//========================================================================== + +void hhBeamSystem::UpdateModel( void ) { + + if( renderEntity.hModel && !this->fl.hidden ) { // Only calculate the bounds if the model is valid and the model is visible + renderEntity.bounds = renderEntity.hModel->Bounds( &renderEntity ); + } else { + renderEntity.bounds.Zero(); + renderEntity.bounds.AddPoint(idVec3(-16,-16,-16)); + renderEntity.bounds.AddPoint(idVec3( 16, 16, 16)); + } + + idEntity::UpdateModel(); +} + + +// BEAM CODE --------------------------------------------------------------------- + +//========================================================================== +// +// hhBeam::hhBeam +// +//========================================================================== + +hhBeam::hhBeam() { + nodeList = NULL; +} + +//========================================================================== +// +// hhBeam::~hhBeam +// +//========================================================================== + +hhBeam::~hhBeam() { + nodeList = NULL; +} + +//========================================================================== +// +// hhBeam::Init +// +//========================================================================== + +void hhBeam::Init( hhBeamSystem *newSystem, hhBeamNodes_t *newInfo ) { + int i; + + system = newSystem; + nodeList = newInfo->nodes; + + for(i = 0; i < MAX_BEAM_SPLINE_CONTROLS + EXTRA_SPLINE_CONTROLS; i++) { + splineList[i] = idVec3(0, 0, 0); + } +} + +//========================================================================== +// +// hhBeam::VerifyNodeIndex +// +//========================================================================== + +void hhBeam::VerifyNodeIndex(int index, const char *functionName) { +#if DEBUG_BEAMS + if(index < 0 || index >= system->decl->numNodes) { + gameLocal.Error("%s: %d\n", functionName, index); + } +#endif +} + +//========================================================================== +// +// hhBeam::VerifySplineIndex +// +//========================================================================== + +void hhBeam::VerifySplineIndex(int index, const char *functionName) { +#if DEBUG_BEAMS + if(index < 0 || index >= MAX_BEAM_SPLINE_CONTROLS) { + gameLocal.Error("%s: %d\n", functionName, index); + } +#endif +} + +//========================================================================== +// +// hhBeam::NodeGet +// +// Returns the node location in world space +//========================================================================== + +idVec3 hhBeam::NodeGet(int index) { +#if DEBUG_BEAMS + VerifyNodeIndex(index, "hhBeam::NodeGet"); +#endif + + return( nodeList[index] ); +} + +//========================================================================== +// +// hhBeam::NodeSet +// +//========================================================================== + +void hhBeam::NodeSet(int index, idVec3 value) { +#if DEBUG_BEAMS + VerifyNodeIndex(index, "hhBeam::NodeSet"); +#endif + + nodeList[index] = value; +} + +//========================================================================== +// +// hhBeam::SplineGet +// +//========================================================================== + +idVec3 hhBeam::SplineGet(int index) { +#if DEBUG_BEAMS + VerifySplineIndex(index, "hhBeam::SplineSet"); +#endif + + return(splineList[index]); +} + +//========================================================================== +// +// hhBeam::SplineSet +// +//========================================================================== + +void hhBeam::SplineSet(int index, idVec3 value) { +#if DEBUG_BEAMS + VerifySplineIndex(index, "hhBeam::SplineSet"); +#endif + + splineList[index] = value; +} + +//========================================================================== +// +// hhBeam::SplineLinear +// +//========================================================================== + +void hhBeam::SplineLinear(idVec3 start, idVec3 end) { + int i; + idVec3 loc; + idVec3 delta; + + loc = start; + delta = (end - start) * BEAM_SPLINE_CONTROL_STEP; + + for(i = 0; i < MAX_BEAM_SPLINE_CONTROLS; i++) { + splineList[i] = loc; + loc += delta; + } +} + +//========================================================================== +// +// hhBeam::SplineLinearToTarget +// +//========================================================================== + +void hhBeam::SplineLinearToTarget( void ) { + SplineLinear( system->GetRenderEntity()->origin, system->GetTargetLocation() ); +} + +//========================================================================== +// +// hhBeam::SplineArc +// +//========================================================================== + +void hhBeam::SplineArc( idVec3 start, idVec3 end, idVec3 startVec ) { + int i; + idMat3 axis; + idVec3 loc; + idVec3 delta; + + idVec3 temp = startVec; + temp.Normalize(); + + // Calculate the translation matrix + axis[0] = ( end - start ); + float dist = axis[0].Normalize(); + axis[2] = temp.Cross( axis[0] ); + axis[1] = axis[2].Cross( axis[0] ); + + float dp = temp * axis[0]; + + loc = start; + delta = (end - start) * BEAM_SPLINE_CONTROL_STEP; + + float x = 0; + for( i = 0; i < MAX_BEAM_SPLINE_CONTROLS; i++, x += BEAM_SPLINE_CONTROL_STEP ) { + float temp = -dist * dp * x * ( 1 - x ); + splineList[i] = loc + axis[1] * temp; + loc += delta; + } +} + +//========================================================================== +// +// hhBeam::SplineArcToTarget +// +//========================================================================== + +void hhBeam::SplineArcToTarget( void ) { + SplineArc( system->GetRenderEntity()->origin, system->GetTargetLocation(), system->GetArcVector() ); +} + +//========================================================================== +// +// hhBeam::SplineAddSin +// +//========================================================================== + +void hhBeam::SplineAddSin(int index, float phaseX, float phaseY, float phaseZ, float offsetX, float offsetY, float offsetZ) { + idVec3 sinOffset; + +#if DEBUG_BEAMS + VerifySplineIndex(index, "hhBeam::SplineAddSin"); +#endif + + sinOffset.x = offsetX * idMath::Sin(phaseX); + sinOffset.y = offsetY * idMath::Sin(phaseY); + sinOffset.z = offsetZ * idMath::Sin(phaseZ); + + splineList[index] += sinOffset * system->GetBeamAxis(); +} + +//========================================================================== +// +// hhBeam::SplineAddSinTime +// +// Multiplies the sin value by the current game time +//========================================================================== + +void hhBeam::SplineAddSinTime(int index, float phaseX, float phaseY, float phaseZ, float offsetX, float offsetY, float offsetZ) { + float t; + + t = (gameLocal.time - system->GetBeamTime()) * 0.01f; + SplineAddSin( index, phaseX * t, phaseY * t, phaseZ * t, offsetX, offsetY, offsetZ ); +} + +//========================================================================== +// +// hhBeam::SplineAddSinTimeScaled +// +// Multiplies the sin value by the current game time +//========================================================================== + +void hhBeam::SplineAddSinTimeScaled(int index, float phaseX, float phaseY, float phaseZ, float offsetX, float offsetY, float offsetZ) { + float t; + float offsetScale = system->GetBeamOffsetScale(); + + t = system->GetBeamPhaseScale() * (gameLocal.time - system->GetBeamTime()) * 0.01f; + SplineAddSin( index, phaseX * t, phaseY * t, phaseZ * t, + offsetX * offsetScale, offsetY * offsetScale, offsetZ * offsetScale ); +} + +//========================================================================== +// +// hhBeam::SplineAdd +// +//========================================================================== + +void hhBeam::SplineAdd(int index, idVec3 offset) { +#if DEBUG_BEAMS + VerifySplineIndex(index, "hhBeam::SplineAdd"); +#endif + + float offsetScale = system->GetBeamOffsetScale(); + splineList[index] += offset * offsetScale; +} + +//========================================================================== +// +// hhBeam::ConvertSplineToNodes +// +//========================================================================== + +void hhBeam::ConvertSplineToNodes(void) { + int i; + + // Copy the last spline point into the extra "slop" points + // The extra points are necessary to calculate the final spline segment + for(i = 0; i < EXTRA_SPLINE_CONTROLS; i++) { + splineList[MAX_BEAM_SPLINE_CONTROLS + i] = splineList[MAX_BEAM_SPLINE_CONTROLS - 1]; + } + + // Construct the beam nodes from the spline points + float inc = (float)(MAX_BEAM_SPLINE_CONTROLS) / system->decl->numNodes; + float alpha = 0; + + for(i = 0; i < system->decl->numNodes; i++) { + nodeList[i] = CurveExtractSpline(splineList, MAX_BEAM_SPLINE_CONTROLS + EXTRA_SPLINE_CONTROLS, alpha, true); + alpha += inc; + } +} + +//========================================================================== +// +// hhBeam::NodeLinear +// +//========================================================================== + +void hhBeam::NodeLinear(idVec3 start, idVec3 end) { + int i; + + idVec3 sLoc = start; + idVec3 sDelta = (end - start) / (system->decl->numNodes - 1); + + for(i = 0; i < system->decl->numNodes; i++) { + nodeList[i] = sLoc; + sLoc += sDelta; + } +} + +//========================================================================== +// +// hhBeam::NodeLinearToTarget +// +//========================================================================== + +void hhBeam::NodeLinearToTarget( void ) { + NodeLinear( system->GetRenderEntity()->origin, system->GetTargetLocation() ); +} + +//========================================================================== +// +// hhBeam::NodeAdd +// +//========================================================================== + +void hhBeam::NodeAdd(int index, idVec3 offset) { +#if DEBUG_BEAMS + VerifyNodeIndex(index,"hhBeam::NodeAdd"); +#endif + + nodeList[index] += offset; +} + +//========================================================================== +// +// hhBeam::NodeElectric +// +//========================================================================== + +void hhBeam::NodeElectric(float x, float y, float z, bool bNotEnds) { + int i; + int start = 0; + int end = system->decl->numNodes; + + if(bNotEnds) { + start = 1; + end = system->decl->numNodes - 1; + } + + for(i = start; i < end; i++) { + nodeList[i].x += system->random.CRandomFloat() * x; + nodeList[i].y += system->random.CRandomFloat() * y; + nodeList[i].z += system->random.CRandomFloat() * z; + } +} + +//========================================================================== +// +// hhBeam::TransformNodes +// +// Transform the worldspace nodes into model local space +//========================================================================== + +void hhBeam::TransformNodes(void) { + idVec3 origin = system->GetRenderEntity()->origin; + idMat3 axis = system->GetRenderEntity()->axis.Transpose(); + for(int i = 0; i < system->decl->numNodes; i++) { + nodeList[i] = ( nodeList[i] - origin ) * axis; + } +} + +//========================================================================== +// +// hhBeam::GetBounds +// +// Get local bounds of beam +//========================================================================== +idBounds hhBeam::GetBounds() { + idBounds bounds; + + if( !nodeList ) { + return bounds_zero; + } + + for( int ix = 0; ix < system->decl->numNodes; ++ix ) { + bounds.AddPoint( nodeList[ix] ); + } + + return bounds; +} + +//================ +//hhBeam::Save +//================ +void hhBeam::Save( idSaveGame *savefile ) const { + for( int i = 0; i < MAX_BEAM_SPLINE_CONTROLS + EXTRA_SPLINE_CONTROLS; i++ ) { + savefile->WriteVec3( splineList[i] ); + } +} + +//================ +//hhBeam::Restore +//================ +void hhBeam::Restore( idRestoreGame *savefile, hhBeamSystem *newSystem, hhBeamNodes_t *newInfo) { + system = newSystem; + nodeList = newInfo->nodes; + + for( int i = 0; i < MAX_BEAM_SPLINE_CONTROLS + EXTRA_SPLINE_CONTROLS; i++ ) { + savefile->ReadVec3( splineList[i] ); + } +} + +//============================================================================= +// +// CurveExtractSpline +// +//============================================================================= + +static idVec3 CurveExtractSpline(idVec3 *cPoints, int cPointCount, float alpha, bool bThruCtrlPnts ) { + float t, t2, t3, w1, w2, w3, w4; + int p1, p2, p3, p4, intAlpha; + idVec3 result; + + intAlpha = alpha; + p2 = intAlpha; + p1 = (p2 == 0) ? p2 : p2-1; + p3 = p2+1; + p4 = (p3 == cPointCount-1) ? p3 : p3+1; + t = alpha-intAlpha; + t2 = t * t; + t3 = t2 * t; + + if(!bThruCtrlPnts) { + w1 = (1-t) * (1-t) * (1-t); + w2 = 3.0f * t3 - 6.0f * t2 + 4; + w3 = -3.0f * t3 + 3.0f * t2 + 3.0f * t + 1; + w4 = t3; + return + ( w1 * cPoints[p1] + + w2 * cPoints[p2] + + w3 * cPoints[p3] + + w4 * cPoints[p4]) * (1.0f/6.0f); + } + + // Uses Catmull-Rom to pass thru the control points. + + if((cPoints[p3] - cPoints[p2]).Length() <= 4.0f) // was 16 + { // If points are close enough, just linearly interpolate + result = cPoints[p2] + t * (cPoints[p3] - cPoints[p2]); + } + else { + result = 0.5f * ((-cPoints[p1] + 3 * cPoints[p2] - 3 * cPoints[p3] + cPoints[p4]) * t3 + + (2 * cPoints[p1] - 5 * cPoints[p2]+ 4 * cPoints[p3] - cPoints[p4]) * t2 + + (-cPoints[p1] + cPoints[p3]) * t + 2 * cPoints[p2]); + } + + return(result); +} + +//============================================================================= +// +// hhBeamSystem::ExecuteBeam +// +// Applies the commands to the specified beam +//============================================================================= + +void hhBeamSystem::ExecuteBeam( int index, hhBeam *beam ) { + int i; + const beamCmd_t *cmd; + + for(i = 0; i < decl->cmds[index].Num(); i++) { + cmd = &decl->cmds[index][i]; + + switch( cmd->type ) { + case BEAMCMD_SplineLinearToTarget: + beam->SplineLinearToTarget(); + break; + case BEAMCMD_SplineArcToTarget: + beam->SplineArcToTarget(); + break; + case BEAMCMD_SplineAdd: + beam->SplineAdd( cmd->index, cmd->offset ); + break; + case BEAMCMD_SplineAddSin: + beam->SplineAddSin( cmd->index, cmd->phase.x, cmd->phase.y, cmd->phase.z, cmd->offset.x, cmd->offset.y, cmd->offset.z ); + break; + case BEAMCMD_SplineAddSinTim: + beam->SplineAddSinTime( cmd->index, cmd->phase.x, cmd->phase.y, cmd->phase.z, cmd->offset.x, cmd->offset.y, cmd->offset.z ); + break; + case BEAMCMD_SplineAddSinTimeScaled: + beam->SplineAddSinTimeScaled( cmd->index, cmd->phase.x, cmd->phase.y, cmd->phase.z, cmd->offset.x, cmd->offset.y, cmd->offset.z ); + break; + case BEAMCMD_ConvertSplineToNodes: + beam->ConvertSplineToNodes(); + break; + case BEAMCMD_NodeLinearToTarget: + beam->NodeLinearToTarget(); + break; + case BEAMCMD_NodeElectric: + beam->NodeElectric( cmd->offset.x, cmd->offset.y, cmd->offset.z, true ); + break; + } + } +} + +//================ +//hhBeamSystem::Save +//================ +void hhBeamSystem::Save( idSaveGame *savefile ) const { + savefile->WriteString( declName ); + savefile->WriteInt( random.GetSeed() ); + savefile->WriteFloat( beamTime ); + + for( int i = 0; i < decl->numBeams; i++) { + beamList[i].Save( savefile ); + } + + savefile->WriteMat3( beamAxis ); + savefile->WriteVec3( arcVector ); + savefile->WriteVec3( targetLocation ); + targetEntity.Save( savefile ); + savefile->WriteVec3( targetEntityOffset ); + savefile->WriteInt( targetEntityId ); + savefile->WriteFloat( beamLength ); + savefile->WriteBool( bRigidBeamLength ); + savefile->WriteFloat( phaseScale ); + savefile->WriteFloat( offsetScale ); + savefile->WriteBool( bActive ); +} + +//================ +//hhBeamSystem::Restore +//================ +void hhBeamSystem::Restore( idRestoreGame *savefile ) { + savefile->ReadString( declName ); + + // Initialize beam data from the beam file + decl = declManager->FindBeam( declName ); + HH_ASSERT( renderEntity.declBeam == decl ); + + // Initialize the random number generator + int seed; + savefile->ReadInt( seed ); + random.SetSeed( seed ); + + savefile->ReadFloat( beamTime ); + + // Allocate the beams + if( beamList ) { + delete[] beamList; + } + beamList = new hhBeam[decl->numBeams]; + + for( int i = 0; i < decl->numBeams; i++) { + beamList[i].Restore( savefile, this, &renderEntity.beamNodes[i]); + } + + savefile->ReadMat3( beamAxis ); + savefile->ReadVec3( arcVector ); + savefile->ReadVec3( targetLocation ); + targetEntity.Restore( savefile ); + savefile->ReadVec3( targetEntityOffset ); + savefile->ReadInt( targetEntityId ); + savefile->ReadFloat( beamLength ); + savefile->ReadBool( bRigidBeamLength ); + savefile->ReadFloat( phaseScale ); + savefile->ReadFloat( offsetScale ); + savefile->ReadBool( bActive ); + + Activate(bActive); +} + diff --git a/src/Prey/prey_beam.h b/src/Prey/prey_beam.h new file mode 100644 index 0000000..191cb4e --- /dev/null +++ b/src/Prey/prey_beam.h @@ -0,0 +1,206 @@ + +#ifndef __PREY_BEAM_H__ +#define __PREY_BEAM_H__ + +#define MAX_BEAM_SPLINE_CONTROLS 6 +#define BEAM_SPLINE_CONTROL_STEP ( 1.0f / ( MAX_BEAM_SPLINE_CONTROLS - 1 ) ) +#define EXTRA_SPLINE_CONTROLS 2 + +#define BEAM_HZ 30 +#define BEAM_MSEC (1000 / BEAM_HZ) + +extern const idEventDef EV_ToggleBeamLength; + +class hhBeamSystem; +class hhBeam; +class hhDeclBeam; + +/* +class beamCmd { +public: + virtual void Execute( hhBeam *beam ); +}; + +// BEAM COMMANDS +class beamCmd_SplineLinearToTarget : public beamCmd { +public: + void Execute( hhBeam *beam ); +}; + +class beamCmd_SplineArcToTarget : public beamCmd { +public: + void Execute( hhBeam *beam ); +}; + +class beamCmd_SplineAdd : public beamCmd { +public: + void Execute( hhBeam *beam ); + + int index; + idVec3 offset; +}; + +class beamCmd_SplineAddSin : public beamCmd_SplineAdd { +public: + void Execute( hhBeam *beam ); + + idVec3 phase; +}; + +class beamCmd_SplineAddSinTime : public beamCmd_SplineAddSin { +public: + void Execute( hhBeam *beam ); +}; + +class beamCmd_SplineAddSinTimeScaled : public beamCmd_SplineAddSin { +public: + void Execute( hhBeam *beam ); +}; + +class beamCmd_ConvertSplineToNodes : public beamCmd { +public: + void Execute( hhBeam *beam ); +}; + +class beamCmd_NodeLinearToTarget : public beamCmd { +public: + void Execute( hhBeam *beam ); +}; + +class beamCmd_NodeElectric : public beamCmd { +public: + void Execute( hhBeam *beam ); + + idVec3 offset; + bool bNotEnds; +}; +// END BEAM COMMANDS +*/ + +class hhBeam { +public: + hhBeam(); + ~hhBeam(); + void Init( hhBeamSystem *newSystem, hhBeamNodes_t *newInfo ); + + idBounds GetBounds(); + + void VerifyNodeIndex(int index, const char *functionName); + void VerifySplineIndex(int index, const char *functionName); + idVec3 NodeGet(int index); + void NodeSet(int index, idVec3 value); + idVec3 SplineGet(int index); + void SplineSet(int index, idVec3 value); + + void SplineLinear(idVec3 start, idVec3 end); + void SplineLinearToTarget( void ); + void SplineArc( idVec3 start, idVec3 end, idVec3 startVec ); + void SplineArcToTarget( void ); + + void SplineAddSin(int index, float phaseX, float phaseY, float phaseZ, float offsetX, float offsetY, float offsetZ); + void SplineAddSinTime(int index, float phaseX, float phaseY, float phaseZ, float offsetX, float offsetY, float offsetZ); + void SplineAddSinTimeScaled(int index, float phaseX, float phaseY, float phaseZ, float offsetX, float offsetY, float offsetZ); + void SplineAdd(int index, idVec3 offset); + void ConvertSplineToNodes(void); + + void NodeLinear(idVec3 start, idVec3 end); + void NodeLinearToTarget( void ); + void NodeAdd(int index, idVec3 offset); + void NodeElectric(float x, float y, float z, bool bNotEnds); + + void TransformNodes(void); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile, hhBeamSystem *newSystem, hhBeamNodes_t *newInfo ); + +protected: + idVec3 *nodeList; // index into renderEntity nodes + hhBeamSystem *system; + idVec3 splineList[MAX_BEAM_SPLINE_CONTROLS + EXTRA_SPLINE_CONTROLS]; +}; + + +class hhBeamSystem : public idEntity { +public: + CLASS_PROTOTYPE( hhBeamSystem ); + + void Spawn(void); + hhBeamSystem(); + ~hhBeamSystem(); + + virtual void Think(void); + virtual void UpdateModel( void ); + + //rww - network friendliness + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + + virtual void InitSystem( const char* modelName ); + virtual float GetBeamTime( void ) { return beamTime; } + virtual void SetTargetLocation(idVec3 newLoc); + virtual void SetTargetEntity( idEntity *ent, int traceId=0, idVec3 &offset=vec3_origin ); + virtual void SetTargetEntity( idEntity *ent, const char *boneName, idVec3 &offset=vec3_origin ); + + virtual bool IsActivated() const { return bActive; } + virtual void Activate( const bool bActivate ); + + virtual idVec3 GetTargetLocation( void ); + virtual idMat3 GetBeamAxis( void ) { return( beamAxis ); } + + virtual void SetArcVector( idVec3 vec ) { arcVector = vec; } + virtual idVec3 GetArcVector( void ) { return( arcVector ); } + + virtual float GetBeamPhaseScale( void ) { return phaseScale; } + virtual void SetBeamPhaseScale( float scale ) { phaseScale = scale; } + virtual float GetBeamOffsetScale( void ) { return offsetScale; } + virtual void SetBeamOffsetScale( float scale ) { offsetScale = scale; } + + //AOB + virtual void SetAxis( const idMat3 &axis ); + virtual float GetBeamLength( void ) { return( beamLength ); } + virtual void SetBeamLength( const float length ); + virtual void ToggleBeamLength( bool rigid ) { bRigidBeamLength = rigid; } + + virtual void ExecuteBeam( int index, hhBeam *beam ); + + static hhBeamSystem* SpawnBeam( const idVec3& start, const char* modelName, const idMat3& axis = mat3_identity, bool pureLocal = false ); + + idRandom random; + idStr declName; + const hhDeclBeam *decl; + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - for functionality regarding client entity beams. + idEntityPtr snapshotOwner; +protected: + virtual void Event_Activate( idEntity *activator ); + void Event_SetBeamPhaseScale( float scale ); // bg + void Event_SetBeamOffsetScale( float scale ); // bg + + float beamTime; + hhBeam *beamList; + + idMat3 beamAxis; + idVec3 arcVector; // For SplineArc + + idVec3 targetLocation; + idEntityPtr targetEntity; + idVec3 targetEntityOffset; + int targetEntityId; + + //AOB + float beamLength; + bool bRigidBeamLength; + + // Used to scale the phase/offset of the beam in code + float phaseScale; + float offsetScale; + +protected: + bool bActive; +}; + +#endif /* __PREY_BEAM_H__ */ diff --git a/src/Prey/prey_bonecontroller.cpp b/src/Prey/prey_bonecontroller.cpp new file mode 100644 index 0000000..86bb669 --- /dev/null +++ b/src/Prey/prey_bonecontroller.cpp @@ -0,0 +1,247 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/********************************************************************** + +hhBoneController + +**********************************************************************/ + +/* +================ +hhBoneController::hhBoneController +================ +*/ +hhBoneController::hhBoneController() { + m_JointHandle = INVALID_JOINT; + m_pOwner = NULL; + + m_Factor.Set(1.0f, 1.0f, 1.0f); + m_TurnRate.Set(0.0f, 0.0f, 0.0f); + + m_IdealAng.Zero(); + m_CurrentAng.Zero(); + m_MinAngles.Zero(); + m_MaxAngles.Zero(); + m_Fraction.Zero(); + m_MaxDelta.Zero(); +} + +/* +================ +hhBoneController::Setup +================ +*/ +void hhBoneController::Setup( idEntity *pOwner, const char *pJointname, idAngles &MinAngles, idAngles &MaxAngles, idAngles& Rate, idAngles& Factor ) { + jointHandle_t Joint = ( pOwner ) ? pOwner->GetAnimator()->GetJointHandle( pJointname ) : INVALID_JOINT; + + Setup( pOwner, Joint, MinAngles, MaxAngles, Rate, Factor ); +} + +/* +================ +hhBoneController::Setup +================ +*/ +void hhBoneController::Setup( idEntity *pOwner, jointHandle_t Joint, idAngles &MinAngles, idAngles &MaxAngles, idAngles& Rate, idAngles& Factor ) { + m_IdealAng.Zero(); + m_CurrentAng.Zero(); + m_Fraction.Zero(); + + m_JointHandle = Joint; + + m_MinAngles = MinAngles; + m_MaxAngles = MaxAngles; + m_Factor = Factor; + m_TurnRate = Rate; + m_pOwner = pOwner; +} + +/* +================ +hhBoneController::Update +================ +*/ +void hhBoneController::Update( int iCurrentTime ) { + idAngles Delta; + int iIndex; + idAngles Turn; + idAngles Angle; + + if ( !m_pOwner || + ( m_JointHandle == INVALID_JOINT ) ) { + return; + } + + Turn = m_TurnRate * MS2SEC(gameLocal.msec) * gameLocal.GetTimeScale(); + + Delta = m_IdealAng - m_CurrentAng; + for( iIndex = 0; iIndex < 3; ++iIndex ) { + if ( Delta[ iIndex ] > Turn[iIndex] ) { + Delta[ iIndex ] = Turn[iIndex]; + } + if ( Delta[ iIndex ] < -Turn[iIndex] ) { + Delta[ iIndex ] = -Turn[iIndex]; + } + } + + m_CurrentAng += Delta; + for(iIndex = 0; iIndex < 3; ++iIndex) { + Angle[iIndex] = m_CurrentAng[iIndex] * m_Factor[iIndex]; + } + + m_pOwner->GetAnimator()->SetJointAxis( m_JointHandle, JOINTMOD_WORLD, Angle.ToMat3() ); + + m_Fraction.pitch = 1.0f - ( (m_MaxDelta.pitch > VECTOR_EPSILON) ? idMath::Fabs((m_IdealAng.pitch - m_CurrentAng.pitch)) / m_MaxDelta.pitch : 0.0f ); + m_Fraction.yaw = 1.0f - ( (m_MaxDelta.yaw > VECTOR_EPSILON) ? idMath::Fabs((m_IdealAng.yaw - m_CurrentAng.yaw)) / m_MaxDelta.yaw : 0.0f ); + m_Fraction.roll = 1.0f - ( (m_MaxDelta.roll > VECTOR_EPSILON) ? idMath::Fabs((m_IdealAng.roll - m_CurrentAng.roll)) / m_MaxDelta.roll : 0.0f ); +} + +/* +================ +hhBoneController::TurnTo +================ +*/ +bool hhBoneController::TurnTo( idAngles &Target ) { + m_IdealAng = Target; + bool bClampAngles = !ClampAngles(); + + m_MaxDelta.pitch = idMath::Fabs(m_IdealAng.pitch - m_CurrentAng.pitch); + m_MaxDelta.yaw = idMath::Fabs(m_IdealAng.yaw - m_CurrentAng.yaw); + m_MaxDelta.roll = idMath::Fabs(m_IdealAng.roll - m_CurrentAng.roll); + + return bClampAngles; +} + +/* +================ +hhBoneController::AimAt +================ +*/ +bool hhBoneController::AimAt( idVec3 &Target ) { + idVec3 dir; + idVec3 localDir; + + if ( !m_pOwner ) { + return false; + } + + dir = Target - m_pOwner->GetOrigin(); + dir.Normalize(); + + m_pOwner->GetAxis().ProjectVector( dir, localDir ); + + m_IdealAng.yaw = idMath::AngleNormalize180( localDir.ToYaw() ); + m_IdealAng.pitch = -idMath::AngleNormalize180( localDir.ToPitch() ); + + bool bClampAngles = !ClampAngles(); + + m_MaxDelta.pitch = idMath::Fabs(m_IdealAng.pitch - m_CurrentAng.pitch); + m_MaxDelta.yaw = idMath::Fabs(m_IdealAng.yaw - m_CurrentAng.yaw); + + return bClampAngles; +} + +/* +===================== +hhBoneController::ClampAngles +===================== +*/ +bool hhBoneController::ClampAngles( void ) { + int i; + bool clamp; + + clamp = false; + for( i = 0; i < 3; i++ ) { + if ( m_IdealAng[ i ] > m_MaxAngles[ i ] ) { + m_IdealAng[ i ] = m_MaxAngles[ i ]; + clamp = true; + } + if ( m_IdealAng[ i ] < m_MinAngles[ i ] ) { + m_IdealAng[ i ] = m_MinAngles[ i ]; + clamp = true; + } + } + + return clamp; +} + +/* +===================== +hhBoneController::SetRotationFactor +===================== +*/ +void hhBoneController::SetRotationFactor(idAngles& RotationFactor) { + m_Factor = RotationFactor; +} + +/* +===================== +hhBoneController::IsFinishedMoving +===================== +*/ +bool hhBoneController::IsFinishedMoving(int iAxis) { + return (m_IdealAng[iAxis] == m_CurrentAng[iAxis]); +} + +/* +===================== +hhBoneController::IsFinishedMoving +===================== +*/ +bool hhBoneController::IsFinishedMoving() { + return (m_IdealAng == m_CurrentAng); +} + +/* +===================== +hhBoneController::AdjustScanRateToLinearizeBonePath +===================== +*/ +void hhBoneController::AdjustScanRateToLinearizeBonePath(float fLinearScanRate) { + float fDeltaPitch = idMath::Fabs( (m_IdealAng.pitch - m_CurrentAng.pitch) * m_Factor.pitch ); + float fDeltaYaw = idMath::Fabs( (m_IdealAng.yaw - m_CurrentAng.yaw) * m_Factor.yaw ); + + float fHypotenuse = idMath::Sqrt(fDeltaPitch * fDeltaPitch + fDeltaYaw * fDeltaYaw); + if(fHypotenuse) { + float fFrac = fLinearScanRate / fHypotenuse; + + m_TurnRate.pitch = fDeltaPitch * fFrac; + m_TurnRate.yaw = fDeltaYaw * fFrac; + } +} + +//================ +//hhBoneController::Save +//================ +void hhBoneController::Save( idSaveGame *savefile ) const { + savefile->WriteAngles( m_Fraction ); + savefile->WriteAngles( m_MaxDelta ); + savefile->WriteInt( m_JointHandle ); + savefile->WriteAngles( m_IdealAng ); + savefile->WriteAngles( m_CurrentAng ); + savefile->WriteAngles( m_MinAngles ); + savefile->WriteAngles( m_MaxAngles ); + savefile->WriteAngles( m_Factor ); + savefile->WriteAngles( m_TurnRate ); + savefile->WriteObject( m_pOwner ); +} + +//================ +//hhBoneController::Restore +//================ +void hhBoneController::Restore( idRestoreGame *savefile ) { + savefile->ReadAngles( m_Fraction ); + savefile->ReadAngles( m_MaxDelta ); + savefile->ReadInt( reinterpret_cast ( m_JointHandle ) ); + savefile->ReadAngles( m_IdealAng ); + savefile->ReadAngles( m_CurrentAng ); + savefile->ReadAngles( m_MinAngles ); + savefile->ReadAngles( m_MaxAngles ); + savefile->ReadAngles( m_Factor ); + savefile->ReadAngles( m_TurnRate ); + savefile->ReadObject( reinterpret_cast ( m_pOwner ) ); +} + diff --git a/src/Prey/prey_bonecontroller.h b/src/Prey/prey_bonecontroller.h new file mode 100644 index 0000000..4bfffe2 --- /dev/null +++ b/src/Prey/prey_bonecontroller.h @@ -0,0 +1,57 @@ +#ifndef __HH_BONE_CONTROLLER_H +#define __HH_BONE_CONTROLLER_H + +/*********************************************************************** + + hhBoneController + +***********************************************************************/ + +class hhBoneController { + public: + hhBoneController(); + + void SetTurnRate( idAngles& Rate ) { m_TurnRate = Rate; } + const idAngles& GetTurnRate() const { return m_TurnRate; } + + void SetRotationFactor( idAngles& RotationFactor ); + const idAngles& GetRotationFactor() const { return m_Factor; } + + void Setup( idEntity *pOwner, const char *pJointname, idAngles &MinAngles, idAngles &MaxAngles, idAngles& Rate, idAngles& Factor ); + void Setup( idEntity *pOwner, jointHandle_t Joint, idAngles &MinAngles, idAngles &MaxAngles, idAngles& Rate, idAngles& Factor ); + void Update( int iCurrentTime ); + bool TurnTo( idAngles &Target ); + bool AimAt( idVec3 &Target ); + bool IsFinishedMoving( int iAxis ); + bool IsFinishedMoving(); + + void AdjustScanRateToLinearizeBonePath( float fLinearScanRate ); + + bool Add( idAngles &Ang ) { return TurnTo( Ang + m_IdealAng ); }; + void Clear( void ) { m_IdealAng.Zero(); }; + const idAngles &CurrentAngles() const { return m_CurrentAng; }; + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + protected: + bool ClampAngles( void ); + + public: + idAngles m_Fraction; + + protected: + idAngles m_MaxDelta; + + jointHandle_t m_JointHandle; + idAngles m_IdealAng; + idAngles m_CurrentAng; + idAngles m_MinAngles; + idAngles m_MaxAngles; + + idAngles m_Factor; + idAngles m_TurnRate; + idEntity *m_pOwner; +}; + +#endif \ No newline at end of file diff --git a/src/Prey/prey_camerainterpolator.cpp b/src/Prey/prey_camerainterpolator.cpp new file mode 100644 index 0000000..c3ff174 --- /dev/null +++ b/src/Prey/prey_camerainterpolator.cpp @@ -0,0 +1,672 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +#define CAMERA_INTERP_LERP_DEBUG if( p_camInterpDebug.GetInteger() == 1 ) gameLocal.Printf +#define CAMERA_INTERP_SLERP_DEBUG if( p_camInterpDebug.GetInteger() == 2 ) gameLocal.Printf + +/********************************************************************** + +hhCameraInterpolator + +**********************************************************************/ + +/* +================ +NoInterpEvaluate +================ +*/ +float NoInterpEvaluate( float& interpVal, float deltaVal ) { + return 1.0f; +} + +/* +================ +VariableMidPointSinusoidalEvaluate +================ +*/ +float VariableMidPointSinusoidalEvaluate( float& interpVal, float deltaVal ) { + interpVal = hhMath::ClampFloat( 0.0f, 1.0f, interpVal + deltaVal ); + + if( interpVal <= 0.0f || interpVal >= 1.0f ) { + return interpVal; + } + + float debug = hhMath::Sin( DEG2RAD(hhMath::MidPointLerp(0.0f, 60.0f, 90.0f, interpVal)) ); + return debug; +} + +/* +================ +LinearEvaluate +================ +*/ +float LinearEvaluate( float& interpVal, float deltaVal ) { + interpVal = hhMath::ClampFloat( 0.0f, 1.0f, interpVal + deltaVal ); + + if( interpVal <= 0.0f || interpVal >= 1.0f ) { + return interpVal; + } + + return interpVal; +} + +/* +================ +InverseEvaluate +================ +*/ +float InverseEvaluate( float& interpVal, float deltaVal ) { + interpVal = hhMath::ClampFloat( 0.0f, 1.0f, interpVal + deltaVal ); + + if( interpVal <= 0.0f || interpVal >= 1.0f ) { + return interpVal; + } + + const float minLerpVal = deltaVal; + if( deltaVal <= 0.0f ) { + return interpVal; + } + + const float scale = (1.0f / minLerpVal) - 1.0f; + float debug = ((1.0f / interpVal) - 1.0f) / scale; + return debug; +} + +/* +================ +hhCameraInterpolator::hhCameraInterpolator +================ +*/ +hhCameraInterpolator::hhCameraInterpolator() : + clipBounds( idBounds(idVec3(-1, -1, 10), idVec3(1, 1, 12)) ) { + + ClearFuncList(); + RegisterFunc( NoInterpEvaluate, IT_None ); + RegisterFunc( VariableMidPointSinusoidalEvaluate, IT_VariableMidPointSinusoidal ); + RegisterFunc( LinearEvaluate, IT_Linear ); + RegisterFunc( InverseEvaluate, IT_Inverse ); + + SetSelf( NULL ); + Setup( 0.0f, IT_None ); + Reset( vec3_origin, mat3_identity[2], 0.0f ); +} + +/* +================ +hhCameraInterpolator::SetSelf +================ +*/ +void hhCameraInterpolator::SetSelf( hhPlayer* self ) { + this->self = self; + clipBounds.SetOwner( self ); +} + +/* +================ +hhCameraInterpolator::GetCurrentEyeHeight +================ +*/ +float hhCameraInterpolator::GetCurrentEyeHeight() const { + return eyeOffsetInfo.current; +} + +/* +================ +hhCameraInterpolator::GetIdealEyeHeight +================ +*/ +float hhCameraInterpolator::GetIdealEyeHeight() const { + return eyeOffsetInfo.end; +} + +/* +================ +hhCameraInterpolator::GetCurrentEyeOffset +================ +*/ +idVec3 hhCameraInterpolator::GetCurrentEyeOffset() const { + return GetCurrentUpVector() * GetCurrentEyeHeight(); +} + +/* +================ +hhCameraInterpolator::GetEyePosition +================ +*/ +idVec3 hhCameraInterpolator::GetEyePosition() const { + return GetCurrentPosition() + GetCurrentEyeOffset(); +} + +/* +================ +hhCameraInterpolator::GetCurrentPosition +================ +*/ +idVec3 hhCameraInterpolator::GetCurrentPosition() const { + return positionInfo.current; +} + +/* +================ +hhCameraInterpolator::GetIdealPosition +================ +*/ +idVec3 hhCameraInterpolator::GetIdealPosition() const { + return positionInfo.end; +} + +/* +================ +hhCameraInterpolator::GetCurrentUpVector +================ +*/ +idVec3 hhCameraInterpolator::GetCurrentUpVector() const { + return GetCurrentAxis()[2]; +} + +/* +================ +hhCameraInterpolator::GetCurrentAxis +================ +*/ +idMat3 hhCameraInterpolator::GetCurrentAxis() const { + return GetCurrentRotation().ToMat3(); +} + +/* +================ +hhCameraInterpolator::GetCurrentAngles +================ +*/ +idAngles hhCameraInterpolator::GetCurrentAngles() const { + return GetCurrentRotation().ToAngles(); +} + +/* +================ +hhCameraInterpolator::GetCurrentRotation +================ +*/ +idQuat hhCameraInterpolator::GetCurrentRotation() const { + return rotationInfo.current; +} + +/* +================ +hhCameraInterpolator::GetIdealUpVector +================ +*/ +idVec3 hhCameraInterpolator::GetIdealUpVector() const { + return GetIdealAxis()[2]; +} + +/* +================ +hhCameraInterpolator::GetIdealAxis +================ +*/ +idMat3 hhCameraInterpolator::GetIdealAxis() const { + return GetIdealRotation().ToMat3(); +} + +/* +================ +hhCameraInterpolator::GetIdealAngles +================ +*/ +idAngles hhCameraInterpolator::GetIdealAngles() const { + return GetIdealRotation().ToAngles(); +} + +/* +================ +hhCameraInterpolator::GetIdealRotation +================ +*/ +idQuat hhCameraInterpolator::GetIdealRotation() const { + return rotationInfo.end; +} + +/* +================ +hhCameraInterpolator::UpdateViewAngles +================ +*/ +idAngles hhCameraInterpolator::UpdateViewAngles( const idAngles& viewAngles ) { + return (viewAngles.ToMat3() * GetCurrentAxis()).ToAngles(); +} + +/* +================ +hhCameraInterpolator::UpdateTarget +================ +*/ +void hhCameraInterpolator::UpdateTarget( const idVec3& idealPos, const idMat3& idealAxis, float eyeOffset, int interpFlags ) { + if( !positionInfo.end.Compare(idealPos, VECTOR_EPSILON) ) { + SetTargetPosition( idealPos, interpFlags ); + } + + if( !idealAxis[2].Compare(GetIdealUpVector(), VECTOR_EPSILON) ) { + SetTargetAxis( idealAxis, interpFlags ); + } + + if( hhMath::Fabs(eyeOffset - hhMath::Fabs(eyeOffsetInfo.end)) >= VECTOR_EPSILON ) { + SetTargetEyeOffset( eyeOffset, interpFlags ); + } +} + +/* +================ +hhCameraInterpolator::SetTargetPosition +================ +*/ +void hhCameraInterpolator::SetTargetPosition( const idVec3& idealPos, int interpFlags ) { + if( interpFlags & INTERPOLATE_POSITION ) { + positionInfo.Set( idealPos ); + } else { + positionInfo.start = idealPos - (positionInfo.end - positionInfo.start); + positionInfo.current = idealPos - (positionInfo.end - positionInfo.current); + positionInfo.end = idealPos; + } +} + +/* +================ +hhCameraInterpolator::SetTargetAxis +================ +*/ +void hhCameraInterpolator::SetTargetAxis( const idMat3& idealAxis, int interpFlags ) { + idQuat cachedRotation; + idQuat idealRotation = DetermineIdealRotation( idealAxis[2] ); + if( interpFlags & INTERPOLATE_ROTATION ) { + rotationInfo.Set( idealRotation ); + } else { + cachedRotation = idealRotation * rotationInfo.end.Inverse(); + rotationInfo.start = cachedRotation * rotationInfo.start; + rotationInfo.current = cachedRotation * rotationInfo.current; + rotationInfo.end = idealRotation; + } +} + +/* +================ +hhCameraInterpolator::SetTargetEyeOffset +================ +*/ +void hhCameraInterpolator::SetTargetEyeOffset( float idealEyeOffset, int interpFlags ) { + if( interpFlags & INTERPOLATE_EYEOFFSET ) { + eyeOffsetInfo.Set( idealEyeOffset ); + } +} + +/* +================ +hhCameraInterpolator::SetInterpolationType +================ +*/ +InterpolationType hhCameraInterpolator::SetInterpolationType( InterpolationType type ) { + InterpolationType cachedType = interpType; + + interpType = type; + + return cachedType; +} + +/* +================ +hhCameraInterpolator::Setup +================ +*/ +void hhCameraInterpolator::Setup( const float lerpScale, const InterpolationType type ) { + this->lerpScale = hhMath::hhMax( 0.01f, lerpScale ); + SetInterpolationType( type ); +} + +/* +================ +hhCameraInterpolator::Reset +================ +*/ +void hhCameraInterpolator::Reset( const idVec3& position, const idVec3& idealUpVector, float eyeOffset ) { + positionInfo.Reset( position ); + rotationInfo.Reset( DetermineIdealRotation(idealUpVector) ); + eyeOffsetInfo.Reset( eyeOffset ); +} + +/* +================ +hhCameraInterpolator::DetermineIdealRotation +================ +*/ +idQuat hhCameraInterpolator::DetermineIdealRotation( const idVec3& idealUpVector, const idVec3& viewDir, const idMat3& untransformedViewAxis ) { + idMat3 mat; + idVec3 newViewVector( viewDir ); + + newViewVector.ProjectOntoPlane( idealUpVector ); + if( newViewVector.LengthSqr() < VECTOR_EPSILON ) { + newViewVector = -Sign( newViewVector * idealUpVector ); + } + + newViewVector.Normalize(); + mat[0] = newViewVector; + mat[1] = idealUpVector.Cross( newViewVector ); + mat[2] = idealUpVector; + + mat = untransformedViewAxis.Transpose() * mat; + return mat.ToQuat(); +} + +/* +================ +hhCameraInterpolator::DetermineIdealRotation +================ +*/ +idQuat hhCameraInterpolator::DetermineIdealRotation( const idVec3& idealUpVector ) { + if( !self ) { + return mat3_identity.ToQuat(); + } + + return DetermineIdealRotation( idealUpVector, self->GetAxis()[0], self->GetUntransformedViewAxis() ); +} + +/* +================ +hhCameraInterpolator::ClearFuncList +================ +*/ +void hhCameraInterpolator::ClearFuncList() { + funcList.SetNum( IT_NumTypes ); + + for( int ix = 0; ix < funcList.Num(); ++ix ) { + funcList[ix] = NULL; + } +} + +/* +================ +hhCameraInterpolator::RegisterFunc +================ +*/ +void hhCameraInterpolator::RegisterFunc( InterpFunc func, InterpolationType type ) { + int index = (int)type; + + if( funcList[index] ) { + gameLocal.Warning( "Function already registered for interpolation type %d", type ); + } + + funcList[index] = func; +} + +/* +================ +hhCameraInterpolator::DetermineFunc +================ +*/ +InterpFunc hhCameraInterpolator::DetermineFunc( InterpolationType type ) { + int index = (int)( p_disableCamInterp.GetBool() ? IT_None : type ); + return funcList[ index ]; +} + +/* +================ +hhCameraInterpolator::Evaluate +================ +*/ +void hhCameraInterpolator::Evaluate( float deltaTime ) { + float baseTime = deltaTime * gameLocal.GetTimeScale(); + float scaledDeltaTime = baseTime * lerpScale; + float lenFactor = (positionInfo.current-positionInfo.end).Length()*0.2f; + if (lenFactor < 1.0f) { + lenFactor = 1.0f; + } + float scaledPosDeltaTime = baseTime * (lerpScale*lenFactor); + float scaledEyeDeltaTime = baseTime * (lerpScale*4.0f); + + InterpFunc func = DetermineFunc( interpType ); + if( !func ) { + return; + } + + EvaluatePosition( func(positionInfo.interpVal, scaledPosDeltaTime) ); + EvaluateRotation( func(rotationInfo.interpVal, scaledDeltaTime) ); + EvaluateEyeOffset( func(eyeOffsetInfo.interpVal, scaledEyeDeltaTime) ); + + VerifyEyeOffset( eyeOffsetInfo.current ); +} + +/* +================ +hhCameraInterpolator::EvaluatePosition +================ +*/ +void hhCameraInterpolator::EvaluatePosition( float interpVal ) { + if( interpVal >= 1.0f ) { + positionInfo.Reset( positionInfo.end ); + return; + } + + positionInfo.current.Lerp( positionInfo.start, positionInfo.end, interpVal ); +} + +/* +================ +hhCameraInterpolator::EvaluateRotation +================ +*/ +void hhCameraInterpolator::EvaluateRotation( float interpVal ) { + if( interpVal >= 1.0f ) { + rotationInfo.Reset( rotationInfo.end ); + return; + } + + rotationInfo.current.Slerp( rotationInfo.start, rotationInfo.end, interpVal ); +} + +/* +================ +hhCameraInterpolator::EvaluateEyeOffset +================ +*/ +void hhCameraInterpolator::EvaluateEyeOffset( float interpVal ) { + if( interpVal >= 1.0f ) { + eyeOffsetInfo.Reset( eyeOffsetInfo.end ); + return; + } + + eyeOffsetInfo.current = hhMath::Lerp( eyeOffsetInfo.start, eyeOffsetInfo.end, interpVal ); +} + +/* +================ +hhCameraInterpolator::VerifyEyeOffset +================ +*/ +void hhCameraInterpolator::VerifyEyeOffset( float& eyeOffset ) { + idPhysics* selfPhysics = NULL; + + if( !self ) { + return; + } + + selfPhysics = self->GetPhysics(); + if( !selfPhysics ) { + return; + } + + if( clipBounds.GetBounds().Translate(GetCurrentUpVector() * eyeOffset).IntersectsBounds(selfPhysics->GetBounds()) ) { + return; + } + + trace_t trace; + gameLocal.clip.Translation( trace, GetCurrentPosition(), GetCurrentPosition() + GetCurrentUpVector() * eyeOffset, &clipBounds, GetCurrentAxis(), selfPhysics->GetClipMask(), NULL ); + eyeOffset *= trace.fraction; +} + +//================ +//hhCameraInterpolator::Save +//================ +void hhCameraInterpolator::Save( idSaveGame *savefile ) const { + savefile->WriteQuat( rotationInfo.start ); + savefile->WriteQuat( rotationInfo.end ); + savefile->WriteQuat( rotationInfo.current ); + savefile->WriteFloat( rotationInfo.interpVal ); + + savefile->WriteVec3( positionInfo.start ); + savefile->WriteVec3( positionInfo.end ); + savefile->WriteVec3( positionInfo.current ); + savefile->WriteFloat( positionInfo.interpVal ); + + savefile->WriteFloat( eyeOffsetInfo.start ); + savefile->WriteFloat( eyeOffsetInfo.end ); + savefile->WriteFloat( eyeOffsetInfo.current ); + savefile->WriteFloat( eyeOffsetInfo.interpVal ); + + savefile->WriteInt( reinterpret_cast ( interpType ) ); + savefile->WriteFloat( lerpScale ); + savefile->WriteObject( self ); + + clipBounds.Save( savefile ); +} + +//================ +//hhCameraInterpolator::Restore +//================ +void hhCameraInterpolator::Restore( idRestoreGame *savefile ) { + savefile->ReadQuat( rotationInfo.start ); + savefile->ReadQuat( rotationInfo.end ); + savefile->ReadQuat( rotationInfo.current ); + savefile->ReadFloat( rotationInfo.interpVal ); + + savefile->ReadVec3( positionInfo.start ); + savefile->ReadVec3( positionInfo.end ); + savefile->ReadVec3( positionInfo.current ); + savefile->ReadFloat( positionInfo.interpVal ); + + savefile->ReadFloat( eyeOffsetInfo.start ); + savefile->ReadFloat( eyeOffsetInfo.end ); + savefile->ReadFloat( eyeOffsetInfo.current ); + savefile->ReadFloat( eyeOffsetInfo.interpVal ); + + savefile->ReadInt( reinterpret_cast ( interpType ) ); + savefile->ReadFloat( lerpScale ); + savefile->ReadObject( reinterpret_cast ( self ) ); + + clipBounds.Restore( savefile ); +} + +//================ +//hhCameraInterpolator::WriteToSnapshot +//================ +void hhCameraInterpolator::WriteToSnapshot( idBitMsgDelta &msg, const hhPlayer *pl ) const +{ + idCQuat sq, q; + + idVec3 plPos = ((idPlayer *)pl)->GetPlayerPhysics()->GetOrigin(); +#if 1 + sq = rotationInfo.start.ToCQuat(); + msg.WriteFloat(sq.x); + msg.WriteFloat(sq.y); + msg.WriteFloat(sq.z); + q = rotationInfo.current.ToCQuat(); + msg.WriteDeltaFloat(sq.x, q.x); + msg.WriteDeltaFloat(sq.y, q.y); + msg.WriteDeltaFloat(sq.z, q.z); + q = rotationInfo.end.ToCQuat(); + msg.WriteDeltaFloat(sq.x, q.x); + msg.WriteDeltaFloat(sq.y, q.y); + msg.WriteDeltaFloat(sq.z, q.z); + msg.WriteFloat(rotationInfo.interpVal, 4, 4); + + msg.WriteDeltaFloat(plPos[0], positionInfo.start[0]); + msg.WriteDeltaFloat(plPos[1], positionInfo.start[1]); + msg.WriteDeltaFloat(plPos[2], positionInfo.start[2]); + msg.WriteDeltaFloat(positionInfo.start[0], positionInfo.current[0]); + msg.WriteDeltaFloat(positionInfo.start[1], positionInfo.current[1]); + msg.WriteDeltaFloat(positionInfo.start[2], positionInfo.current[2]); + msg.WriteDeltaFloat(positionInfo.start[0], positionInfo.end[0]); + msg.WriteDeltaFloat(positionInfo.start[1], positionInfo.end[1]); + msg.WriteDeltaFloat(positionInfo.start[2], positionInfo.end[2]); + msg.WriteFloat(positionInfo.interpVal, 4, 4); + + msg.WriteFloat(eyeOffsetInfo.start); + msg.WriteDeltaFloat(eyeOffsetInfo.start, eyeOffsetInfo.current); + msg.WriteDeltaFloat(eyeOffsetInfo.start, eyeOffsetInfo.end); + msg.WriteFloat(eyeOffsetInfo.interpVal, 4, 4); +#else + q = rotationInfo.current.ToCQuat(); + msg.WriteFloat(q.x); + msg.WriteFloat(q.y); + msg.WriteFloat(q.z); + + msg.WriteDeltaFloat(plPos[0], positionInfo.current[0]); + msg.WriteDeltaFloat(plPos[1], positionInfo.current[1]); + msg.WriteDeltaFloat(plPos[2], positionInfo.current[2]); + + msg.WriteFloat(eyeOffsetInfo.current); +#endif + + //msg.WriteFloat(lerpScale); +} + +//================ +//hhCameraInterpolator::ReadFromSnapshot +//================ +void hhCameraInterpolator::ReadFromSnapshot( const idBitMsgDelta &msg, hhPlayer *pl ) +{ + idCQuat sq, q; + + idVec3 plPos = pl->GetPlayerPhysics()->GetOrigin(); + +#if 1 + sq.x = msg.ReadFloat(); + sq.y = msg.ReadFloat(); + sq.z = msg.ReadFloat(); + rotationInfo.start = sq.ToQuat(); + q.x = msg.ReadDeltaFloat(sq.x); + q.y = msg.ReadDeltaFloat(sq.y); + q.z = msg.ReadDeltaFloat(sq.z); + rotationInfo.current = q.ToQuat(); + q.x = msg.ReadDeltaFloat(sq.x); + q.y = msg.ReadDeltaFloat(sq.y); + q.z = msg.ReadDeltaFloat(sq.z); + rotationInfo.end = q.ToQuat(); + rotationInfo.interpVal = msg.ReadFloat(4, 4); + + positionInfo.start[0] = msg.ReadDeltaFloat(plPos[0]); + positionInfo.start[1] = msg.ReadDeltaFloat(plPos[1]); + positionInfo.start[2] = msg.ReadDeltaFloat(plPos[2]); + positionInfo.current[0] = msg.ReadDeltaFloat(positionInfo.start[0]); + positionInfo.current[1] = msg.ReadDeltaFloat(positionInfo.start[1]); + positionInfo.current[2] = msg.ReadDeltaFloat(positionInfo.start[2]); + positionInfo.end[0] = msg.ReadDeltaFloat(positionInfo.start[0]); + positionInfo.end[1] = msg.ReadDeltaFloat(positionInfo.start[1]); + positionInfo.end[2] = msg.ReadDeltaFloat(positionInfo.start[2]); + positionInfo.interpVal = msg.ReadFloat(4, 4); + + eyeOffsetInfo.start = msg.ReadFloat(); + eyeOffsetInfo.current = msg.ReadDeltaFloat(eyeOffsetInfo.start); + eyeOffsetInfo.end = msg.ReadDeltaFloat(eyeOffsetInfo.start); + eyeOffsetInfo.interpVal = msg.ReadFloat(4, 4); +#else + q.x = msg.ReadFloat(); + q.y = msg.ReadFloat(); + q.z = msg.ReadFloat(); + rotationInfo.current = q.ToQuat(); + + positionInfo.current[0] = msg.ReadDeltaFloat(plPos[0]); + positionInfo.current[1] = msg.ReadDeltaFloat(plPos[1]); + positionInfo.current[2] = msg.ReadDeltaFloat(plPos[2]); + + eyeOffsetInfo.current = msg.ReadFloat(); + + rotationInfo.start = rotationInfo.current; + rotationInfo.end = rotationInfo.current; + positionInfo.start = positionInfo.current; + positionInfo.end = positionInfo.current; + eyeOffsetInfo.start = eyeOffsetInfo.current; + eyeOffsetInfo.end = eyeOffsetInfo.current; +#endif + //lerpScale = msg.ReadFloat(); +} diff --git a/src/Prey/prey_camerainterpolator.h b/src/Prey/prey_camerainterpolator.h new file mode 100644 index 0000000..c5ad582 --- /dev/null +++ b/src/Prey/prey_camerainterpolator.h @@ -0,0 +1,134 @@ +#ifndef __HH_CAMERA_INTERPOLATOR_H +#define __HH_CAMERA_INTERPOLATOR_H + +/********************************************************************** + +hhCameraInterpolator + +**********************************************************************/ +class hhCameraInterpolator; + +enum InterpolationType { + IT_None, + IT_VariableMidPointSinusoidal, + IT_Linear, + IT_Inverse, + IT_NumTypes +}; + +const int INTERPOLATE_NONE = 0; +const int INTERPOLATE_POSITION = BIT( 1 ); +const int INTERPOLATE_ROTATION = BIT( 2 ); +const int INTERPOLATE_EYEOFFSET = BIT( 3 ); + +const int INTERPMASK_ALL = -1; +const int INTERPMASK_WALLWALK = INTERPOLATE_POSITION | INTERPOLATE_ROTATION; + + +typedef float (*InterpFunc)( float&, float ); + +class hhPlayer; + +class hhCameraInterpolator { + public: + hhCameraInterpolator(); + + void SetSelf( hhPlayer* self ); + + float GetCurrentEyeHeight() const; + float GetIdealEyeHeight() const; + idVec3 GetCurrentEyeOffset() const; + idVec3 GetEyePosition() const; + idVec3 GetCurrentPosition() const; + idVec3 GetIdealPosition() const; + + idVec3 GetCurrentUpVector() const; + idMat3 GetCurrentAxis() const; + idAngles GetCurrentAngles() const; + idQuat GetCurrentRotation() const; + + idVec3 GetIdealUpVector() const; + idMat3 GetIdealAxis() const; + idAngles GetIdealAngles() const; + idQuat GetIdealRotation() const; + + void UpdateEyeOffset(); + + void UpdateTarget( const idVec3& idealPosition, const idMat3& idealAxis, const float eyeOffset, int interpFlags ); + void SetTargetPosition( const idVec3& idealPos, int interpFlags ); + void SetTargetAxis( const idMat3& idealAxis, int interpFlags ); + void SetTargetEyeOffset( float idealEyeOffset, int interpFlags ); + void SetIdealRotation( const idQuat& idealRotation ) { rotationInfo.end = idealRotation; } + + void Setup( const float lerpScale, const InterpolationType type ); + + void Reset( const idVec3& position, const idVec3& idealUpVector, float eyeOffset ); + + idAngles UpdateViewAngles( const idAngles& viewAngles ); + + InterpolationType SetInterpolationType( InterpolationType type ); + void Evaluate( float frameTime ); + + idQuat DetermineIdealRotation( const idVec3& idealUpVector, const idVec3& viewDir, const idMat3& untransformedViewAxis ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - send over net + void WriteToSnapshot( idBitMsgDelta &msg, const hhPlayer *pl ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg, hhPlayer *pl ); + + protected: + void ClearFuncList(); + void RegisterFunc( InterpFunc func, InterpolationType type ); + InterpFunc DetermineFunc( InterpolationType type ); + + idQuat DetermineIdealRotation( const idVec3& idealUpVector ); + + void EvaluatePosition( float interpVal ); + void EvaluateRotation( float interpVal ); + void EvaluateEyeOffset( float interpVal ); + + void VerifyEyeOffset( float& eyeOffset ); + + protected: + template + struct LerpInfo_t { + Type start; + Type end; + Type current; + float interpVal; + + void Set( Type endVal ) { + end = endVal; + start = current; + interpVal = 0.0f; + } + + void Reset( Type endVal ) { + end = endVal; + start = end; + current = start; + interpVal = 1.0f; + } + + void IsDone() const { + return interpVal >= 1.0f; + } + }; + + LerpInfo_t rotationInfo; + LerpInfo_t positionInfo; + LerpInfo_t eyeOffsetInfo; + + InterpolationType interpType; + + float lerpScale; + + hhPlayer* self; + idList funcList; + + idClipModel clipBounds; +}; + +#endif \ No newline at end of file diff --git a/src/Prey/prey_firecontroller.cpp b/src/Prey/prey_firecontroller.cpp new file mode 100644 index 0000000..cdeb71f --- /dev/null +++ b/src/Prey/prey_firecontroller.cpp @@ -0,0 +1,548 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +ABSTRACT_DECLARATION( idClass, hhFireController ) +END_CLASS + +/* +================ +hhFireController::hhFireController +================ +*/ +hhFireController::hhFireController() : muzzleFlashHandle(-1) { + // Register us so we can be saved -mdl + gameLocal.RegisterUniqueObject( this ); +} + +/* +================ +hhFireController::~hhFireController +================ +*/ +hhFireController::~hhFireController() { + gameLocal.UnregisterUniqueObject( this ); + SAFE_FREELIGHT( muzzleFlashHandle ); + Clear(); +} + +/* +================ +hhFireController::Init +================ +*/ +void hhFireController::Init( const idDict* viewDict ) { + const char *shader = NULL; + + Clear(); + + dict = viewDict; + + if( !dict ) { + return; + } + + ammoRequired = dict->GetInt( "ammoRequired" ); + + // set up muzzleflash render light + const idMaterial*flashShader; + idVec3 flashColor; + idVec3 flashTarget; + idVec3 flashUp; + idVec3 flashRight; + //HUMANHEAD: aob - changed from float to idVec3 + idVec3 flashRadius; + //HUMANHEAD END + bool flashPointLight; + + // get the projectile + SetProjectileDict( dict->GetString("def_projectile") ); + + dict->GetString( "mtr_flashShader", "muzzleflash", &shader ); + flashShader = declManager->FindMaterial( shader, false ); + flashPointLight = dict->GetBool( "flashPointLight", "1" ); + dict->GetVector( "flashColor", "0 0 0", flashColor ); + flashTime = SEC2MS( dict->GetFloat( "flashTime", "0.08" ) ); + flashTarget = dict->GetVector( "flashTarget" ); + flashUp = dict->GetVector( "flashUp" ); + flashRight = dict->GetVector( "flashRight" ); + + memset( &muzzleFlash, 0, sizeof( muzzleFlash ) ); + + muzzleFlash.pointLight = flashPointLight; + muzzleFlash.shader = flashShader; + muzzleFlash.shaderParms[ SHADERPARM_RED ] = flashColor[0]; + muzzleFlash.shaderParms[ SHADERPARM_GREEN ] = flashColor[1]; + muzzleFlash.shaderParms[ SHADERPARM_BLUE ] = flashColor[2]; + muzzleFlash.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; + + //HUMANHEAD: aob + if( dict->GetFloat("flashRadius", "0", flashRadius[0]) ) { // if 0, no light will spawn + flashRadius[2] = flashRadius[1] = flashRadius[0]; + } else { + flashRadius = dict->GetVector( "flashSize" ); + //muzzleFlash.lightCenter.Set( flashRadius[0] * 0.5f, 0.0f, 0.0f ); + } + muzzleFlash.lightRadius = flashRadius; + //HUMANHEAD END + + muzzleFlash.noShadows = true; //HUMANHEAD bjk + + if ( !flashPointLight ) { + muzzleFlash.target = flashTarget; + muzzleFlash.up = flashUp; + muzzleFlash.right = flashRight; + muzzleFlash.end = flashTarget; + } + + //HUMANHEAD: aob + fireDelay = dict->GetFloat( "fireRate" ); + spread = DEG2RAD( dict->GetFloat("spread") ); + + yawSpread = dict->GetFloat("yawSpread"); + + bCrosshair = dict->GetBool("crosshair"); + + numProjectiles = dict->GetInt( "numProjectiles" ); + //HUMANHEAD END + + deferProjNum = 0; //HUMANHEAD rww + deferProjTime = 0; //HUMANHEAD rww +} + +/* +================ +hhFireController::Clear +================ +*/ +void hhFireController::Clear() { + dict = NULL; + + muzzleOrigin.Zero(); + muzzleAxis.Identity(); + + // weapon definition + numProjectiles = 0; + projectile = NULL; + projDictName = ""; + + memset( &muzzleFlash, 0, sizeof( muzzleFlash ) ); + SAFE_FREELIGHT( muzzleFlashHandle ); + + muzzleFlashEnd = 0;; + flashTime = 0; + + // ammo management + ammoRequired = 0; // amount of ammo to use each shot. 0 means weapon doesn't need ammo. + + fireDelay = 0.0f; + spread = 0.0f; + + yawSpread = 0.0f; + + bCrosshair = false; + + projectileMaxHalfDimension = 0.0f; +} + + +/* +================ +hhFireController::SetProjectileDict +================ +*/ +void hhFireController::SetProjectileDict( const char* name ) { + idVec3 mins; + idVec3 maxs; + projectileMaxHalfDimension = 1.0f; + + projDictName = name; + + if ( name[0] ) { + projectile = gameLocal.FindEntityDefDict( name, false ); + if ( !projectile ) { + gameLocal.Warning( "Unknown projectile '%s'", name ); + } else { + const char *spawnclass = projectile->GetString( "spawnclass" ); + idTypeInfo *cls = idClass::GetClass( spawnclass ); + if ( !cls || !cls->IsType( idProjectile::Type ) ) { + gameLocal.Error( "Invalid spawnclass '%s' on projectile '%s'", spawnclass, name ); + } + + mins = projectile->GetVector( "mins" ); + maxs = projectile->GetVector( "maxs" ); + projectileMaxHalfDimension = hhMath::hhMax( (maxs.y - mins.y) * 0.5f, (maxs.z - mins.z) * 0.5f ); + } + } else { + projectile = NULL; + } +} + +/* +================ +hhFireController::DetermineProjectileAxis +================ +*/ +idMat3 hhFireController::DetermineProjectileAxis( const idMat3& axis ) { + idVec3 dir = hhUtils::RandomSpreadDir( axis, spread ); + idAngles projectileAngles( dir.ToAngles() ); + + projectileAngles[YAW] += yawSpread*hhMath::Sin(gameLocal.random.RandomFloat()*hhMath::TWO_PI); //rww - seperate optional yaw spread + + projectileAngles[2] = axis.ToAngles()[2]; + + return projectileAngles.ToMat3(); +} + +/* +================ +hhFireController::LaunchProjectiles +================ +*/ +bool hhFireController::LaunchProjectiles( const idVec3& pushVelocity ) { + if( !GetProjectileOwner() ) { + return false; + } + + // check if we're out of ammo or the clip is empty + if( !HasAmmo() ) { + return false; + } + + UseAmmo(); + + // calculate the muzzle position + CalculateMuzzlePosition( muzzleOrigin, muzzleAxis ); + + if ( !gameLocal.isClient || dict->GetBool("net_clientProjectiles", "1") ) { //HUMANHEAD rww - clientside projectiles, because our weapons make the god of bandwidth weep. + //HUMANHEAD: aob + idVec3 adjustedOrigin = AssureInsideCollisionBBox( muzzleOrigin, GetSelf()->GetAxis(), GetCollisionBBox(), projectileMaxHalfDimension ); + idMat3 aimAxis = DetermineAimAxis( adjustedOrigin, GetSelf()->GetAxis() ); + //HUMANHEAD END + + LaunchProjectiles( adjustedOrigin, aimAxis, pushVelocity, GetProjectileOwner() ); + + //rww - remove this from here and only do it on the client, we need to create the effect locally and with knowledge + //of if we should get the viewmodel bone or the worldmodel one + //CreateMuzzleFx( muzzleOrigin, muzzleAxis ); + } + + if (gameLocal.GetLocalPlayer()) + { //rww - create muzzle fx on client + idVec3 localMuzzleOrigin = muzzleOrigin; + idMat3 localMuzzleAxis = muzzleAxis; + if (gameLocal.isMultiplayer) { //rww - check if we should display the actual muzzle flash from a point different than the projectile launch location + idVec3 newOrigin; + idMat3 newAxis; + if (CheckThirdPersonMuzzle(newOrigin, newAxis)) { + localMuzzleOrigin = newOrigin; + localMuzzleAxis = newAxis; + } + } + CreateMuzzleFx( localMuzzleOrigin, localMuzzleAxis ); + } + + return true; +} + +/* +================ +hhFireController::CheckDeferredProjectiles +================ +*/ +void hhFireController::CheckDeferredProjectiles(void) { //rww + assert(!gameLocal.isClient); + if (deferProjTime > gameLocal.time) { + return; + } + + //FIXME compensate for intermediate time? + + deferProjTime = gameLocal.time + 50; //time is rather arbitrary. + + int i = 0; + while (deferProjNum > 0 && i < MAX_NET_PROJECTILES) { + if (!deferProjOwner.IsValid()) { + return; + } + + hhProjectile *projectile = SpawnProjectile(); + + projectile->Create(deferProjOwner.GetEntity(), deferProjLaunchOrigin, deferProjLaunchAxis); + projectile->spawnArgs.Set( "weapontype", GetSelf()->spawnArgs.GetString("ddaname", "") ); + projectile->Launch(deferProjLaunchOrigin, DetermineProjectileAxis(deferProjLaunchAxis), deferProjPushVelocity, 0.0f, 1.0f ); + + deferProjNum--; + i++; + } +} + +/* +================ +hhFireController::LaunchProjectiles +================ +*/ +void hhFireController::LaunchProjectiles( const idVec3& launchOrigin, const idMat3& aimAxis, const idVec3& pushVelocity, idEntity* projOwner ) { + if (gameLocal.isMultiplayer && numProjectiles > MAX_NET_PROJECTILES && !dict->GetBool("net_noProjectileDefer", "0") && + !dict->GetBool("net_clientProjectiles", "1") && !gameLocal.isClient) { + //HUMANHEAD rww - in mp our gigantic single-snapshot projectile spawns tend to be very destructive toward bandwidth. + //and so, projectile deferring. + deferProjNum = numProjectiles; + deferProjTime = 0; + deferProjLaunchOrigin = launchOrigin; + deferProjLaunchAxis = aimAxis; + deferProjPushVelocity = pushVelocity; + deferProjOwner = projOwner; + } + else { + if (gameLocal.isClient && !gameLocal.isNewFrame) { + return; + } + + hhProjectile* projectile = NULL; + bool clientProjectiles = dict->GetBool("net_clientProjectiles", "1"); + for( int ix = 0; ix < numProjectiles; ++ix ) { + if (clientProjectiles) { //HUMANHEAD rww - clientside projectiles! + projectile = hhProjectile::SpawnClientProjectile( GetProjectileDict() ); + } + else { + projectile = SpawnProjectile(); + } + + projectile->Create( projOwner, launchOrigin, aimAxis ); + + projectile->spawnArgs.Set( "weapontype", GetSelf()->spawnArgs.GetString("ddaname", "") ); + + projectile->Launch( launchOrigin, DetermineProjectileAxis(aimAxis), pushVelocity, 0.0f, 1.0f ); + } + } +} + +/* +================ +hhFireController::AssureInsideCollisionBBox +================ +*/ +idVec3 hhFireController::AssureInsideCollisionBBox( const idVec3& origin, const idMat3& axis, const idBounds& ownerAbsBounds, float projMaxHalfDim ) const { + float distance = 0.0f; + if( !ownerAbsBounds.RayIntersection(origin, -axis[0], distance) ) { + distance = 0.0f; + } + + // HUMANHEAD CJR: If the player is touching a portal, then set the projectile inside the player so it has a chance to collide with the portal + idEntity *owner = GetProjectileOwner(); + if ( owner && owner->IsType( hhPlayer::Type ) ) { + hhPlayer *player = static_cast(owner); + if ( player->IsPortalColliding() ) { // Player is touching a portal, so force it inside the player bounds + distance = 2.0f * idMath::Fabs( owner->GetPhysics()->GetBounds()[1].y ); // Push back by the player's size + } + } // HUMANHEAD END + + //Need to come back half the size of the projectiles bbox to + //guarentee that the whole projectile is in the owners bbox + return origin + (distance + projMaxHalfDim) * -axis[0]; +} + +/* +================ +hhFireController::CalculateMuzzlePosition +================ +*/ +void hhFireController::CalculateMuzzlePosition( idVec3& origin, idMat3& axis ) { + origin = GetMuzzlePosition(); + axis = GetSelf()->GetAxis(); + + if( g_showProjectileLaunchPoint.GetBool() ) { + idVec3 v = (origin - GetSelf()->GetOrigin()) * GetSelf()->GetAxis().Transpose(); + gameLocal.Printf( "Launching from: (relative to weapon): %s\n", v.ToString() ); + hhUtils::DebugCross( colorGreen, origin, 5, 5000 ); + gameRenderWorld->DebugLine( colorBlue, GetSelf()->GetOrigin(), GetSelf()->GetOrigin() + (v * GetSelf()->GetAxis()), 5000 ); + } +} + +/* +================ +hhFireController::UpdateMuzzleFlashPosition +================ +*/ +void hhFireController::UpdateMuzzleFlashPosition() { + muzzleFlash.axis = GetSelf()->GetAxis(); + muzzleFlash.origin = AssureInsideCollisionBBox( muzzleOrigin, muzzleFlash.axis, GetProjectileOwner()->GetPhysics()->GetAbsBounds(), projectileMaxHalfDimension ); + + //TEST + /*trace_t trace; + idVec3 flashSize = dict->GetVector( "flashSize" ); + if( gameLocal.clip.TracePoint(trace, muzzleFlash.origin, muzzleFlash.origin + muzzleFlash.axis[0] * flashSize[0], MASK_VISIBILITY, GetSelf()) ) { + flashSize[0] *= trace.fraction; + } + muzzleFlash.lightRadius = flashSize; + muzzleFlash.origin += muzzleFlash.axis[0] * flashSize[0] * 0.5f; + muzzleFlash.lightCenter.Set( flashSize[0] * -0.5f, 0.0f, 0.0f ); + + hhUtils::Swap( muzzleFlash.lightRadius[0], muzzleFlash.lightRadius[2] ); + hhUtils::Swap( muzzleFlash.lightCenter[0], muzzleFlash.lightCenter[2] ); + muzzleFlash.axis = hhUtils::SwapXZ( muzzleFlash.axis );*/ + //TEST + + // put the world muzzle flash on the end of the joint, no matter what + //GetGlobalJointTransform( false, flashJointWorld, muzzleOrigin, muzzleFlash.axis ); +} + +/* +================ +hhFireController::MuzzleFlash +================ +*/ +void hhFireController::MuzzleFlash() { + if (!g_muzzleFlash.GetBool()) { + return; + } + + UpdateMuzzleFlashPosition(); + + if( muzzleFlash.lightRadius[0] < VECTOR_EPSILON || muzzleFlash.lightRadius[1] < VECTOR_EPSILON || muzzleFlash.lightRadius[2] < VECTOR_EPSILON ) { + return; + } + + // these will be different each fire + muzzleFlash.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.GetTime() ); + muzzleFlash.shaderParms[ SHADERPARM_DIVERSITY ] = gameLocal.random.RandomFloat(); + + //info.worldMuzzleFlash.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.GetTime() ); + //info.worldMuzzleFlash.shaderParms[ SHADERPARM_DIVERSITY ] = renderEntity.shaderParms[ SHADERPARM_DIVERSITY ]; + + // the light will be removed at this time + muzzleFlashEnd = gameLocal.GetTime() + flashTime; + + if ( muzzleFlashHandle != -1 ) { + gameRenderWorld->UpdateLightDef( muzzleFlashHandle, &muzzleFlash ); + //gameRenderWorld->UpdateLightDef( info.worldMuzzleFlashHandle, &info.worldMuzzleFlash ); + } else { + muzzleFlashHandle = gameRenderWorld->AddLightDef( &muzzleFlash ); + //info.worldMuzzleFlashHandle = gameRenderWorld->AddLightDef( &info.worldMuzzleFlash ); + } +} + +/* +================ +hhFireController::UpdateMuzzleFlash +================ +*/ +void hhFireController::UpdateMuzzleFlash() { + AttemptToRemoveMuzzleFlash(); + + if( muzzleFlashHandle != -1 ) { + UpdateMuzzleFlashPosition(); + if( muzzleFlash.lightRadius[0] < VECTOR_EPSILON || muzzleFlash.lightRadius[1] < VECTOR_EPSILON || muzzleFlash.lightRadius[2] < VECTOR_EPSILON ) { //rww - added to mimic the behaviour of hhFireController::MuzzleFlash + return; + } + gameRenderWorld->UpdateLightDef( muzzleFlashHandle, &muzzleFlash ); + //gameRenderWorld->UpdateLightDef( worldMuzzleFlashHandle, &worldMuzzleFlash ); + } +} + +/* +================ +hhFireController::CreateMuzzleFx +================ +*/ +void hhFireController::CreateMuzzleFx( const idVec3& pos, const idMat3& axis ) { + hhFxInfo fxInfo; + + if (!gameLocal.GetLocalPlayer()) { //rww - not at all necessary for ded server + return; + } + if( GetSelf()->IsHidden() || !GetSelf()->GetRenderEntity()->hModel ) { + return; + } + + fxInfo.SetNormal( axis[0] ); + fxInfo.RemoveWhenDone( true ); + fxInfo.SetEntity( GetSelf() ); + + + //GetSelf()->BroadcastFxInfo( dict->GetString("fx_muzzleFlash"), pos, axis, &fxInfo ); + //rww - this is now client-only. + GetSelf()->SpawnFxLocal( dict->GetString("fx_muzzleFlash"), pos, axis, &fxInfo, true ); +} + +/* +================ +hhFireController::Save +================ +*/ +void hhFireController::Save( idSaveGame *savefile ) const { + savefile->WriteDict( dict ); + savefile->WriteFloat( yawSpread ); + savefile->WriteString( projDictName ); + savefile->WriteVec3( muzzleOrigin ); + savefile->WriteMat3( muzzleAxis ); + savefile->WriteRenderLight( muzzleFlash ); + //HUMANHEAD PCF mdl 05/04/06 - Don't save light handles + //savefile->WriteInt( muzzleFlashHandle ); + savefile->WriteInt( muzzleFlashEnd ); + savefile->WriteInt( flashTime ); + savefile->WriteInt( ammoRequired ); + savefile->WriteFloat( fireDelay ); + savefile->WriteFloat( spread ); + savefile->WriteBool( bCrosshair ); + savefile->WriteFloat( projectileMaxHalfDimension ); + savefile->WriteInt( numProjectiles ); + //HUMANHEAD PCF mdl 05/04/06 - Save whether the light is active + savefile->WriteBool( muzzleFlashHandle != -1 ); +} + +/* +================ +hhFireController::Restore +================ +*/ +void hhFireController::Restore( idRestoreGame *savefile ) { + savefile->ReadDict( &restoredDict ); + dict = &restoredDict; + savefile->ReadFloat( yawSpread ); + savefile->ReadString( projDictName ); + savefile->ReadVec3( muzzleOrigin ); + savefile->ReadMat3( muzzleAxis ); + savefile->ReadRenderLight( muzzleFlash ); + //HUMANHEAD PCF mdl 05/04/06 - Don't save light handles + //savefile->ReadInt( muzzleFlashHandle ); + savefile->ReadInt( muzzleFlashEnd ); + savefile->ReadInt( flashTime ); + savefile->ReadInt( ammoRequired ); + savefile->ReadFloat( fireDelay ); + savefile->ReadFloat( spread ); + savefile->ReadBool( bCrosshair ); + savefile->ReadFloat( projectileMaxHalfDimension ); + savefile->ReadInt( numProjectiles ); + + //HUMANHEAD PCF mdl 05/04/06 - Restore the light if necessary + bool bLight; + savefile->ReadBool( bLight ); + if ( bLight ) { + muzzleFlashHandle = gameRenderWorld->AddLightDef( &muzzleFlash ); + } + + SetProjectileDict( projDictName ); +} + +/* +============================== +hhFireController::GetMuzzlePosition +============================== +*/ +idVec3 hhFireController::GetMuzzlePosition() const { + idVec3 muzzle( dict->GetVector("muzzleOffset", "2 0 0") ); + return GetSelfConst()->GetOrigin() + ( muzzle * GetSelfConst()->GetAxis() ); +} + +/* +============================== +hhFireController::GetMuzzlePosition +============================== +*/ +bool hhFireController::CheckThirdPersonMuzzle(idVec3 &origin, idMat3 &axis) { //rww + return false; +} diff --git a/src/Prey/prey_firecontroller.h b/src/Prey/prey_firecontroller.h new file mode 100644 index 0000000..878b4a4 --- /dev/null +++ b/src/Prey/prey_firecontroller.h @@ -0,0 +1,160 @@ +#ifndef __HH_FIRE_CONTROLLER_H +#define __HH_FIRE_CONTROLLER_H + +#include "gamesys/Class.h" + +#define MAX_NET_PROJECTILES 3 //rww + +class hhFireController : public idClass { + ABSTRACT_PROTOTYPE(hhFireController) + +public: + hhFireController(); + virtual ~hhFireController(); + + virtual void Clear(); + virtual void Init( const idDict* viewDict ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool UsesCrosshair() const = 0; + virtual idVec3 GetMuzzlePosition() const; + ID_INLINE virtual bool HasAmmo() const; + virtual void UseAmmo() = 0; + virtual int AmmoAvailable() const = 0; + ID_INLINE virtual int AmmoRequired() const; + ID_INLINE float GetFireDelay() const; + + void UpdateMuzzleFlash(); + virtual void CreateMuzzleFx( const idVec3& pos, const idMat3& axis ); + + ID_INLINE virtual idMat3 DetermineProjectileAxis( const idMat3& axis ); + virtual bool LaunchProjectiles( const idVec3& pushVelocity ); + + void MuzzleFlash(); + virtual void WeaponFeedback() = 0; + + virtual //HUMANHEAD bjk + const idDict* GetProjectileDict() const { return projectile; } + + void SetWeaponDict( const idDict *wdict ) { dict = wdict; yawSpread = dict->GetFloat("yawSpread"); } + + void CheckDeferredProjectiles(void); //rww + + virtual bool CheckThirdPersonMuzzle(idVec3 &origin, idMat3 &axis); //rww +protected: + void SetProjectileDict( const char* name ); + + virtual void LaunchProjectiles( const idVec3& launchOrigin, const idMat3& aimAxis, const idVec3& pushVelocity, idEntity* projOwner ); + virtual idEntity* GetProjectileOwner() const = 0; + ID_INLINE virtual hhProjectile* SpawnProjectile(); + + virtual const idBounds& GetCollisionBBox() = 0; + virtual void CalculateMuzzlePosition( idVec3& origin, idMat3& axis ); + + ID_INLINE void AttemptToRemoveMuzzleFlash(); + void UpdateMuzzleFlashPosition(); + + idVec3 AssureInsideCollisionBBox( const idVec3& origin, const idMat3& axis, const idBounds& ownerAbsBounds, float projMaxHalfDim ) const; + virtual idMat3 DetermineAimAxis( const idVec3& muzzlePos, const idMat3& weaponAxis ); + + virtual hhRenderEntity *GetSelf() = 0; + virtual const hhRenderEntity *GetSelfConst() const = 0; + +protected: + idDict restoredDict; + const idDict* dict; + + const idDict * projectile; + idStr projDictName; + + idVec3 muzzleOrigin; + idMat3 muzzleAxis; + + // muzzle flash + renderLight_t muzzleFlash; // positioned on view weapon bone + int muzzleFlashHandle; + + int muzzleFlashEnd; + int flashTime; + + // ammo management + int ammoRequired; // amount of ammo to use each shot. 0 means weapon doesn't need ammo. + + float fireDelay; + float spread; + + float yawSpread; //rww - extra spread on yaw + + bool bCrosshair; + + float projectileMaxHalfDimension; + + int numProjectiles; + + //rww - projectile deferring + int deferProjNum; + int deferProjTime; + idVec3 deferProjLaunchOrigin; + idMat3 deferProjLaunchAxis; + idVec3 deferProjPushVelocity; + idEntityPtr deferProjOwner; +}; + +/* +================= +hhFireController::DetermineAimAxis +================= +*/ +ID_INLINE idMat3 hhFireController::DetermineAimAxis( const idVec3& muzzlePos, const idMat3& weaponAxis ) { + return weaponAxis; +} + +/* +================ +hhFireController::HasAmmo +================ +*/ +ID_INLINE bool hhFireController::HasAmmo() const { + return AmmoAvailable() != 0; +} + +/* +================ +hhFireController::SpawnProjectile +================ +*/ +ID_INLINE hhProjectile* hhFireController::SpawnProjectile() { + return hhProjectile::SpawnProjectile( GetProjectileDict() ); +} + +/* +================ +hhFireController::AttemptToRemoveMuzzleFlash +================ +*/ +ID_INLINE void hhFireController::AttemptToRemoveMuzzleFlash() { + if( gameLocal.GetTime() >= muzzleFlashEnd ) { + SAFE_FREELIGHT( muzzleFlashHandle ); + } +} + +/* +================ +hhFireController::AmmoRequired +================ +*/ +ID_INLINE int hhFireController::AmmoRequired() const { + return ammoRequired; +} + +/* +================ +hhFireController::GetFireDelay +================ +*/ +ID_INLINE float hhFireController::GetFireDelay() const { + return fireDelay; +} + +#endif diff --git a/src/Prey/prey_game.cpp b/src/Prey/prey_game.cpp new file mode 100644 index 0000000..2e0a981 --- /dev/null +++ b/src/Prey/prey_game.cpp @@ -0,0 +1,1438 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" +//#include "../prey/win32/fxdlg.h" + +extern idCVar com_forceGenericSIMD; + +//HUMANHEAD: aob - needed for networking to send the least amount of bits +const int DECL_MAX_TYPES_NUM_BITS = hhMath::BitsForInteger( DECL_MAX_TYPES ); +//HUMANHEAD END + +//============================================================================= +// Overridden functions +//============================================================================= + +//--------------------------------------------------- +// +// hhGameLocal::Init +// +//--------------------------------------------------- +void hhGameLocal::Init( void ) { + + //HUMANHEAD rww - moved into idGameLocal::Init, this should be done after the managed heap is initialized. + //ddaManager = new hhDDAManager; //must be before calling idGameLocal::Init(), otherwise if the map can't load, will cause a crash. + //HUMANHEAD END + + idGameLocal::Init(); + + dwWorldClipModel = NULL; +#if _HH_INLINED_PROC_CLIPMODELS + inlinedProcClipModels.Clear(); //HUMANHEAD rww +#endif + + sunCorona = NULL; // CJR + lastAIAlertRadius = 0; + + P_InitConsoleCommands(); + + //HUMANHEAD rww - check lglcd validity and reset values + logitechLCDEnabled = sys->LGLCD_Valid(); + logitechLCDDisplayAlt = false; + logitechLCDButtonsLast = 0; + logitechLCDUpdateTime = 0; + //HUMANHEAD END +} + +//--------------------------------------------------- +// +// hhGameLocal::Shutdown +// +//--------------------------------------------------- +void hhGameLocal::Shutdown( void ) { + +#if INGAME_DEBUGGER_ENABLED + debugger.Shutdown(); // HUMANHEAD pdm: Shut down the debugger before idDict gets shut down +#endif + + idGameLocal::Shutdown(); + + //HUMANHEAD rww - this must be done before managed heap destruction to correspond + //delete ddaManager; + //ddaManager = NULL; + //HUMANHEAD END +} + + +//--------------------------------------------------- +// +// hhGameLocal::UnregisterEntity +// +//--------------------------------------------------- +void hhGameLocal::UnregisterEntity( idEntity *ent ) { + assert( ent ); + + if ( talonTargets.Find( ent ) ) { + talonTargets.Remove( ent ); + } + + idGameLocal::UnregisterEntity( ent ); +} + +//--------------------------------------------------- +// +// hhGameLocal::MapShutdown +// +//--------------------------------------------------- +void hhGameLocal::MapShutdown( void ) { + //HUMANHEAD: mdc - added support for automatically dumping stats on map switch/game exit + if( !isMultiplayer && gamestate != GAMESTATE_NOMAP && g_dumpDDA.GetBool() ) { //make sure we actually have a valid map to export + GetDDA()->Export(NULL); //export the stats + } + //Always clear our tracking stats and create a default node + GetDDA()->ClearTracking(); //Clear Tracking statistics +// GetDDA()->CreateDefaultSectionNode(); //Setup our default section-node + + idGameLocal::MapShutdown(); + + sunCorona = NULL; + + talonTargets.Clear(); +} + +//--------------------------------------------------- +// +// hhGameLocal::InitFromNewMap +// +//--------------------------------------------------- +void hhGameLocal::InitFromNewMap( const char *mapName, idRenderWorld *renderWorld, idSoundWorld *soundWorld, bool isServer, bool isClient, int randseed ) { + + //HUMANHEAD rww - throw up the prey logo if using the logitech lcd screen + if (logitechLCDEnabled) { + sys->LGLCD_UploadImage(NULL, -1, -1, false, true); + } + //HUMANHEAD END + + talonTargets.Clear(); // CJR: Must be before idGameLocal::InitFromNewMap() + +#if INGAME_DEBUGGER_ENABLED + debugger.Reset(); +#endif + + hands.Clear(); + + idGameLocal::InitFromNewMap(mapName, renderWorld, soundWorld, isServer, isClient, randseed); + + // CJR: Determine if this map is a LOTA map by looking for a special entity on the map + bIsLOTA = false; + if ( FindEntity( "LOTA_ThisMapIsLOTA" ) ) { + bIsLOTA = true; + } +} + +//============================================================================= +// +// hhGameLocal::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 +// +// HUMANHEAD pdm: Override for doing our own precaching +//============================================================================= +void hhGameLocal::CacheDictionaryMedia( const idDict *dict ) { +/* HUMANHEAD mdc - fix so com_makingBuild stuff will work correctly... (this was causing too early of an out) + if ( dict == NULL ) { + return; + } +*/ + const idKeyValue *kv = NULL; + const idDecl *decl = NULL; + idFile *file = NULL; + idStr fxName; + idStr buffer; + + idGameLocal::CacheDictionaryMedia( dict ); + + if( dict == NULL ) { //mdc - added early out + return; + } +//HUMANHEAD mdc - skip caching if we are in development and not making a build + if( !g_precache.GetBool() && !sys_forceCache.GetBool() && !cvarSystem->GetCVarBool("com_makingBuild") ) { + return; + } +//HUMANHEAD END + + + // TEMP: precache clip models until they become model_clip + kv = dict->MatchPrefix( "clipmodel", NULL ); + while( kv ) { + if ( kv->GetValue().Length() ) { + declManager->MediaPrint( "Precaching clipmodel: %s\n", kv->GetValue().c_str() ); + renderModelManager->FindModel( kv->GetValue() ); + } + kv = dict->MatchPrefix( "clipmodel", kv ); + } + + //HUMANHEAD bjk: removed smoke_wound_. now using fx_wound and is cached in fx_. no seperate handling needed + + kv = dict->MatchPrefix( "beam", NULL ); + while( kv ) { + if ( kv->GetValue().Length() ) { + if (kv->GetValue().Find(".beam") >= 0) { + declManager->MediaPrint( "Precaching beam %s\n", kv->GetValue().c_str() ); + declManager->FindBeam( kv->GetValue() ); // Cache the beam decl + renderModelManager->FindModel(kv->GetValue()); // Ensure the beam model is cached as well, since beams are a combination of a decl and a rendermodel + } + else { // Must be a reference to a beam entity + declManager->MediaPrint( "Precaching beam entityDef %s\n", kv->GetValue().c_str() ); + FindEntityDef( kv->GetValue().c_str(), false ); + } + } + kv = dict->MatchPrefix( "beam", kv ); + } +} + + +//============================================================================= +// New functionality +//============================================================================= + +//============================================================================= +// +// hhGameLocal::SpawnAppendedMapEntities +// +// HUMANHEAD pdm: Support for level appending +//============================================================================= +#if DEATHWALK_AUTOLOAD +void hhGameLocal::SpawnAppendedMapEntities() { + if (additionalMapFile) { + // Swap mapFile and additionalMapFile + idMapFile *mainMapFile = mapFile; + mapFile = additionalMapFile; + + // This routine based largely on idGameLocal::SpawnMapEntities + + // parse the key/value pairs and spawn entities for dw + // From SpawnMapEntities(); + idMapEntity *mapEnt; + int numEntities; + idDict args; + + numEntities = mapFile->GetNumEntities(); + if ( numEntities == 0 ) { + Error( "...no entities" ); + } + + for ( int i = 1 ; i < numEntities ; i++ ) { // skip worldspawn + mapEnt = mapFile->GetEntity( i ); + args = mapEnt->epairs; + + if ( !InhibitEntitySpawn( args ) ) { + // precache any media specified in the map entity + gameLocal.CacheDictionaryMedia( &args ); + + //HUMANHEAD rww - ok on client + SpawnEntityDef( args, NULL, true, false, true ); + } + } + + // Restore main mapfile + delete additionalMapFile; + additionalMapFile = NULL; + mapFile = mainMapFile; + + + // Since the world collision is always collision model 0, link our model in like an entity would + dwWorldClipModel = new idClipModel("dw_worldmap"); + dwWorldClipModel->SetContents(CONTENTS_SOLID); + dwWorldClipModel->SetEntity(world); + dwWorldClipModel->Link(clip); + } +} +#endif + +//============================================================================= +// +// hhGameLocal::RegisterTalonTarget +// +//============================================================================= +void hhGameLocal::RegisterTalonTarget( idEntity *ent ) { + talonTargets.Append(ent); +} + +//============================================================================= +// +// hhGameLocal::SpawnClientObject +// rww - meant for spawning client ents only. +//============================================================================= +idEntity* hhGameLocal::SpawnClientObject( const char* objectName, idDict* additionalArgs ) { + idDict localArgs; + idDict* args = NULL; + idEntity* object = NULL; + bool clientEnt = true; + + if (!gameLocal.isClient) { //if it's the server spawning a client entity we want to spawn it for listen servers but keep it local + clientEnt = false; + } + + if( !objectName || !objectName[0]) { + Error( "hhGameLocal::SpawnObject: Invalid object name\n" ); + } + + args = ( additionalArgs != NULL ) ? additionalArgs : &localArgs; + + args->Set( "classname", objectName ); + if( !SpawnEntityDef( *args, &object, true, clientEnt ) ) { + Error( "hhGameLocal::SpawnObject: Failed to spawn %s\n", objectName ); + } + + if (object) { + object->fl.clientEntity = clientEnt; + object->fl.networkSync = false; + } + + return object; +} + +//============================================================================= +// +// hhGameLocal::SpawnObject +// +//============================================================================= +idEntity* hhGameLocal::SpawnObject( const char* objectName, idDict* additionalArgs ) { + idDict localArgs; + idDict* args = NULL; + idEntity* object = NULL; + + //rww - are you hitting this? then the code it's coming from is doing something it shouldn't be. + //the client should never, ever call SpawnObject. + assert(!gameLocal.isClient); + + if( !objectName || !objectName[0]) { + Error( "hhGameLocal::SpawnObject: Invalid object name\n" ); + } + + args = ( additionalArgs != NULL ) ? additionalArgs : &localArgs; + + args->Set( "classname", objectName ); + if( !SpawnEntityDef( *args, &object ) ) { + Error( "hhGameLocal::SpawnObject: Failed to spawn %s\n", objectName ); + } + + return object; +} + + +// +// hhGameLocal::RadiusDamage() +// PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +// +void hhGameLocal::RadiusDamage( const idVec3 &origin, idEntity *inflictor, idEntity *attacker, idEntity *ignoreDamage, idEntity *ignorePush, const char *damageDefName, float dmgPower ) { + float distSquared, radiusSquared, damageScale, attackerDamageScale, attackerPushScale; + idEntity * ent; + idEntity * entityList[ MAX_GENTITIES ]; + int numListedEntities; + idBounds bounds; + idVec3 v, damagePoint, dir; + int i, e, damage, radius, push; + // HUMANHEAD AOB + int pushRadius; + trace_t result; + // HUMANHEAD END + + const idDict *damageDef = FindEntityDefDict( damageDefName, false ); + if ( !damageDef ) { + Warning( "Unknown damageDef '%s'", damageDefName ); + return; + } + + damageDef->GetInt( "damage", "0", damage ); // HUMANHEAD JRM - changed to ZERO not 20 default + damageDef->GetInt( "radius", "50", radius ); + damageDef->GetInt( "push", va( "%d", damage * 100 ), push ); + damageDef->GetFloat( "attackerDamageScale", "0.5", attackerDamageScale ); + damageDef->GetFloat( "attackerPushScale", "0", attackerPushScale ); + + // HUMANHEAD aob + pushRadius = Max( 1.0f, damageDef->GetFloat("push_radius", va("%d", radius)) ); + if ( damageDef->GetBool( "nopush" ) ) { + push = 0; + } + // HUMANHEAD END + + if ( radius < 1 ) { + radius = 1; + } + radiusSquared = radius*radius; + + bounds = idBounds( origin ).Expand( radius ); + + // get all entities touching the bounds + numListedEntities = clip.EntitiesTouchingBounds( bounds, -1, entityList, MAX_GENTITIES ); + + if ( inflictor && inflictor->IsType( idAFAttachment::Type ) ) { + inflictor = static_cast(inflictor)->GetBody(); + } + if ( attacker && attacker->IsType( idAFAttachment::Type ) ) { + attacker = static_cast(attacker)->GetBody(); + } + if ( ignoreDamage && ignoreDamage->IsType( idAFAttachment::Type ) ) { + ignoreDamage = static_cast(ignoreDamage)->GetBody(); + } + + // apply damage to the entities + if( damage > 0 ) { // HUMANHEAD JRM - only do damage if WE HAVE damage + for ( e = 0; e < numListedEntities; e++ ) { + ent = entityList[ e ]; + assert( ent ); + + if ( !ent->fl.takedamage ) { + continue; + } + + if ( ent == inflictor /*|| ( ent->IsType( idAFAttachment::Type ) && static_cast(ent)->GetBody() == inflictor )*/ ) { + continue; + } + + if ( ent == ignoreDamage /*|| ( ent->IsType( idAFAttachment::Type ) && static_cast(ent)->GetBody() == ignoreDamage )*/ ) { + continue; + } + + if ( ent->IsType( idAFAttachment::Type ) ) { // bjk: no double splash damage from heads + continue; + } + + // don't damage a dead player + if ( isMultiplayer && ent->entityNumber < MAX_CLIENTS && ent->IsType( idPlayer::Type ) && static_cast< idPlayer * >( ent )->health < 0 ) { + continue; + } + + // find the distance from the edge of the bounding box + for ( i = 0; i < 3; i++ ) { + if ( origin[ i ] < ent->GetPhysics()->GetAbsBounds()[0][ i ] ) { + v[ i ] = ent->GetPhysics()->GetAbsBounds()[0][ i ] - origin[ i ]; + } else if ( origin[ i ] > ent->GetPhysics()->GetAbsBounds()[1][ i ] ) { + v[ i ] = origin[ i ] - ent->GetPhysics()->GetAbsBounds()[1][ i ]; + } else { + v[ i ] = 0; + } + } + + distSquared = v.LengthSqr(); + if ( distSquared >= radiusSquared ) { + continue; + } + + //HUMANHEAD: aob - see if radius damage is blocked. CanDamage is used by more than just RadiusDamage + clip.TracePoint( result, origin, (ent->GetPhysics()->GetAbsBounds()[0] + ent->GetPhysics()->GetAbsBounds()[1]) * 0.5f, CONTENTS_BLOCK_RADIUSDAMAGE, ignoreDamage ); + if( result.fraction < 1.0f && result.c.entityNum != ent->entityNumber ) { + continue; + } + //HUMANHEAD END + + bool canDamage = true; + if ( GERMAN_VERSION || g_nogore.GetBool() ) { + if ( ent->IsType(idActor::Type) ) { + idActor *actor = reinterpret_cast< idActor * > ( ent ); + if ( actor->IsActiveAF() && !actor->spawnArgs.GetBool( "not_gory", "0" ) ) { + canDamage = false; + } + } + } + + + if ( canDamage && ent->CanDamage( origin, damagePoint ) ) { + // push the center of mass higher than the origin so players + // get knocked into the air more + dir = ent->GetPhysics()->GetOrigin() - origin; + dir[ 2 ] += 24; + + // get the damage scale + damageScale = dmgPower * ( 1.0f - (distSquared / radiusSquared) ); + if ( ent == attacker || ( ent->IsType( idAFAttachment::Type ) && static_cast(ent)->GetBody() == attacker ) ) { + damageScale *= attackerDamageScale; + } + + ent->Damage( inflictor, attacker, dir, damageDefName, damageScale, INVALID_JOINT ); + } + } + } // HUMANHEAD END + + // push physics objects + if ( push ) { + //HUMANHEAD: aob - changed radius to pushRadius + RadiusPush( origin, pushRadius, push * dmgPower, attacker, ignorePush, attackerPushScale, false ); + } +} + +/* +============== +hhGameLocal::RadiusPush + PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +============== +*/ +void hhGameLocal::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; + trace_t trace; //HUMANHEAD: aob + + dir.Set( 0.0f, 0.0f, 1.0f ); + + bounds = idBounds( origin ).Expand( radius ); + + // get all clip models touching the bounds + numListedClipModels = clip.ClipModelsTouchingBounds( bounds, -1, clipModelList, MAX_GENTITIES ); + + if ( inflictor && inflictor->IsType( idAFAttachment::Type ) ) { + inflictor = static_cast(inflictor)->GetBody(); + } + if ( ignore && ignore->IsType( idAFAttachment::Type ) ) { + ignore = static_cast(ignore)->GetBody(); + } + + // 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(); + + // never push projectiles + if ( ent->IsType( idProjectile::Type ) ) { + continue; + } + + // players use "knockback" in idPlayer::Damage + if ( ent->IsType( idPlayer::Type ) && !quake ) { + continue; + } + + // don't push the ignore entity + if ( ent == ignore || ( ent->IsType( idAFAttachment::Type ) && static_cast(ent)->GetBody() == ignore ) ) { + continue; + } + + if ( gameRenderWorld->FastWorldTrace( result, origin, clipModel->GetAbsBounds().GetCenter() ) ) { + continue; + } + + // Don't affect ragdolls in non-gore mode + if ( GERMAN_VERSION || g_nogore.GetBool() ) { + if ( ent->IsType(idActor::Type) ) { + idActor *actor = reinterpret_cast< idActor * > ( ent ); + if ( actor->IsActiveAF() && !actor->spawnArgs.GetBool( "not_gory", "0" ) ) { + continue; + } + } + } + + // scale the push for the inflictor + if ( ent == inflictor || ( ent->IsType( idAFAttachment::Type ) && static_cast(ent)->GetBody() == inflictor ) ) { + scale = inflictorScale; + } else if ( ent->IsType(hhMoveable::Type) ) { + scale = ent->spawnArgs.GetFloat("radiusPush", "4.0"); + } else if( !ent->IsType(idActor::Type) ) { + scale = 4.0f; + } else { + scale = 1.0f; + } + + //HUMANHEAD: aob - see if radius damage is blocked + clip.TracePoint( trace, origin, clipModel->GetEntity()->GetOrigin(), CONTENTS_BLOCK_RADIUSDAMAGE, ignore ); + if( trace.fraction < 1.0f && trace.c.entityNum != clipModel->GetEntity()->entityNumber ) { + continue; + } + //HUMANHEAD END + + if ( quake ) { + clipModel->GetEntity()->ApplyImpulse( world, clipModel->GetId(), clipModel->GetOrigin(), scale * push * dir ); + } else { + //RadiusPushClipModel( origin, scale * push, clipModel ); + + //HUMANHEAD bjk + idVec3 impulse = clipModel->GetAbsBounds().GetCenter() - origin; + float dist = impulse.Normalize() / radius; + impulse.z += 1.0f; + dist = 0.6f - 0.3f*dist; + impulse *= push * dist * scale; + clipModel->GetEntity()->ApplyImpulse( world, clipModel->GetId(), clipModel->GetOrigin(), impulse ); + //HUMANHEAD END + } + } +} + +//HUMANHEAD rww +void hhGameLocal::LogitechLCDUpdate(void) { + hhPlayer *pl = (hhPlayer *)GetLocalPlayer(); + if (!pl) { + return; + } + + DWORD buttons; + sys->LGLCD_ReadSoftButtons(&buttons); + if (logitechLCDButtonsLast != buttons && buttons) { + logitechLCDDisplayAlt = !logitechLCDDisplayAlt; + } + logitechLCDButtonsLast = buttons; + + if (logitechLCDUpdateTime >= gameLocal.time) { //only update the screen at 20fps + return; + } + logitechLCDUpdateTime = gameLocal.time + 50; + + sys->LGLCD_DrawBegin(); + if (logitechLCDDisplayAlt) { //primary/secondary ammo + char *ammoStr = va("%s%s", common->GetLanguageDict()->GetString("#str_41150"), common->GetLanguageDict()->GetString("#str_41152")); //"Ammo: N/A"; + char *ammoAltStr = va("%s%s", common->GetLanguageDict()->GetString("#str_41151"), common->GetLanguageDict()->GetString("#str_41152")); //"Alt. Ammo: N/A"; + if (pl->weapon.IsValid()) { + int a = pl->weapon->AmmoAvailable(); + if (a >= 0) { + if (a > 999) { + a = 999; + } + ammoStr = va("%s%i", common->GetLanguageDict()->GetString("#str_41150"), a); //"Ammo: %i" + } + if (pl->weapon->GetAmmoType() != pl->weapon->GetAltAmmoType()) { + a = pl->weapon->AltAmmoAvailable(); + if (a >= 0) { + if (a > 999) { + a = 999; + } + ammoAltStr = va("%s%i", common->GetLanguageDict()->GetString("#str_41151"), a); //"Alt. Ammo: %i" + } + } + } + + sys->LGLCD_DrawText(ammoStr, 46, 6, true); + sys->LGLCD_DrawText(ammoAltStr, 46, 22, true); + + //draw the weapon icon (disabled for now, since it isn't very..recognizable) + //sys->LGLCD_DrawShape(3, 54, 0, 0, 0, 0, true); + } + else { //health/spirit + int v; + v = pl->health; + if (v > 999) { + v = 999; + } + else if (v < 0) { + v = 0; + } + sys->LGLCD_DrawText(va("%s%i", common->GetLanguageDict()->GetString("#str_41153"), v), 70, 6, true); //"Health: %i" + v = pl->GetSpiritPower(); + if (v > 999) { + v = 999; + } + else if (v < 0) { + v = 0; + } + sys->LGLCD_DrawText(va("%s%i", common->GetLanguageDict()->GetString("#str_41154"), v), 70, 22, true); //"Spirit: %i" + + //draw the tommy and talon + float eyePitch; + if (pl->InVehicle()) { + eyePitch = 0.0f; + } + else { + eyePitch = pl->GetEyeAxis()[2].ToAngles().pitch+90.0f; + if (eyePitch < 1.0f && eyePitch > -1.0f) { + eyePitch = 0.0f; + } + } + sys->LGLCD_DrawShape(2, 42, 0, 0, 0, (int)eyePitch, true); + } + + //draw the compass + sys->LGLCD_DrawShape(0, 0, 0, 0, 0, 0, false); //base + sys->LGLCD_DrawShape(1, 21, 21, 13, 1, (int)-pl->GetViewAngles().yaw, true); //line + sys->LGLCD_DrawFinish(false); +} +//HUMANHEAD END + +// +// RunFrame() +// +// PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +gameReturn_t hhGameLocal::RunFrame( const usercmd_t *clientCmds ) { + idEntity * ent; + int num; + float ms; + idTimer timer_think, timer_events, timer_singlethink; + gameReturn_t ret; + idPlayer *player; + const renderView_t *view; + // HUMANHEAD pdm + idTimer timer_singledormant; + bool dormant; + const float thinkalpha = 0.98f; // filter with historical timings + // HUMANHEAD END + + ret.sessionCommand[0] = 0; + +#ifdef _DEBUG + if ( isMultiplayer ) { + assert( !isClient ); + } +#endif + + player = GetLocalPlayer(); + + if ( !isMultiplayer && g_stopTime.GetBool() ) { + // clear any debug lines from a previous frame + gameRenderWorld->DebugClearLines( time + 1 ); + + // set the user commands for this frame + memcpy( usercmds, clientCmds, numClients * sizeof( usercmds[ 0 ] ) ); + + // Fake these categories so it still displays any that are in the history when time is stopped + { PROFILE_SCOPE("Misc_Think", PROFMASK_NORMAL); } + { PROFILE_SCOPE("Dormant Tests", PROFMASK_NORMAL); } + { PROFILE_SCOPE("Sound", PROFMASK_NORMAL); } + { PROFILE_SCOPE("Animation", PROFMASK_NORMAL); } + { PROFILE_SCOPE("Scripting", PROFMASK_NORMAL); } + + if ( player ) { + player->Think(); + if ( player->InVehicle() && player->GetVehicleInterface() ) { + if ( player->GetVehicleInterface()->GetVehicle() ) { + player->GetVehicleInterface()->GetVehicle()->Think(); + } + } + } + } else do { + // update the game time + framenum++; + previousTime = time; + time += msec; + realClientTime = time; + timeRandom = time; //HUMANHEAD rww + +#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 ); + } + } + + // clear any debug lines from a previous frame + gameRenderWorld->DebugClearLines( time ); + + // clear any debug polygons from a previous frame + gameRenderWorld->DebugClearPolygons( time ); + + // set the user commands for this frame + memcpy( usercmds, clientCmds, numClients * sizeof( usercmds[ 0 ] ) ); + + // free old smoke particles + smokeParticles->FreeSmokes(); + + // process events on the server + ServerProcessEntityNetworkEventQueue(); + + // update our gravity vector if needed. + UpdateGravity(); + + // create a merged pvs for all players + SetupPlayerPVS(); + + // sort the active entity list + SortActiveEntityList(); + + timer_think.Clear(); + timer_think.Start(); + PROFILE_START("Misc_Think", PROFMASK_NORMAL); // HUMANHEAD pdm + + // HUMANHEAD pdm: This loop reworked to support debugger and dormant timings + // let entities think + if ( g_timeentities.GetFloat() || g_debugger.GetInteger() || g_dormanttests.GetBool()) { + num = 0; + for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + if ( g_cinematic.GetBool() && inCinematic && !ent->cinematic ) { + ent->GetPhysics()->UpdateTime( time ); + continue; + } + dormant = false; + timer_singledormant.Clear(); + if (!ent->fl.neverDormant) { + timer_singledormant.Start(); + dormant = ent->CheckDormant(); + timer_singledormant.Stop(); + } + + timer_singlethink.Clear(); + if( !dormant ) { + timer_singlethink.Start(); + ent->Think(); + timer_singlethink.Stop(); + } + + ms = timer_singlethink.Milliseconds(); + if ( g_timeentities.GetFloat() && ms >= g_timeentities.GetFloat() ) { + gameLocal.Printf( "%d: entity '%s': %.1f ms\n", time, ent->name.c_str(), ms ); + } + if (g_debugger.GetInteger() || g_dormanttests.GetBool()) { + ent->thinkMS = ent->thinkMS*thinkalpha + (1.0-thinkalpha)*ms; + } + if (g_dormanttests.GetBool() && !ent->fl.neverDormant) { + float msDormant = timer_singledormant.Milliseconds(); + ent->dormantMS = ent->dormantMS*thinkalpha + (1.0f-thinkalpha)*msDormant; + + if (ent->dormantMS > ent->thinkMS && !dormant) { + // If we're spending more time on dormant checks than on actually thinking, + // turn dormant checks off on that class. + Printf("%30s Dms=%6.4f Tms=%6.4f\n", ent->GetClassname(), ent->dormantMS, ent->thinkMS); + } + } + 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; + } + // HUMANHEAD JRM + if( !ent->CheckDormant() ) { + ent->Think(); + } + num++; + } + } else { + num = 0; + for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + // HUMANHEAD JRM + if( !ent->CheckDormant() ) { + ent->Think(); + } + 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; + } + + PROFILE_STOP("Misc_Think", PROFMASK_NORMAL); + + timer_think.Stop(); + timer_events.Clear(); + timer_events.Start(); + + // service any pending events + idEvent::ServiceEvents(); + + timer_events.Stop(); + + // free the player pvs + FreePlayerPVS(); + + // do multiplayer related stuff + if ( isMultiplayer ) { + mpGame.Run(); + } + + // 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; //player->heartRate; // HUMANHEAD pdm: not used + ret.stamina = idMath::FtoiFast( player->stamina ); + // 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 ) ); + 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; + } + } while( ( inCinematic || ( time < cinematicStopTime ) ) && skipCinematic ); + + ret.syncNextGameFrame = skipCinematic; + if ( skipCinematic ) { + soundSystem->SetMute( false ); + skipCinematic = false; + } + + // show any debug info for this frame + RunDebugInfo(); + D_DrawDebugLines(); + + //HUMANHEAD rww + if (logitechLCDEnabled) { + PROFILE_START("LogitechLCDUpdate", PROFMASK_NORMAL); + LogitechLCDUpdate(); + PROFILE_STOP("LogitechLCDUpdate", PROFMASK_NORMAL); + } + //HUMANHEAD END + + return ret; +} + +// PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +bool hhGameLocal::Draw( int clientNum ) { + //HUMANHEAD rww - dedicated server update + if (clientNum == -1) { + gameSoundWorld->PlaceListener( vec3_origin, mat3_identity, -1, gameLocal.time, "Undefined" ); + return false; + } + //HUMANHEAD END + + if ( isMultiplayer ) { + return mpGame.Draw( clientNum ); + } + + //HUMANHEAD: aob - changed idPlayer to hhPlayer + hhPlayer *player = static_cast(entities[ clientNum ]); + + if ( !player ) { + return false; + } + + // render the scene + // HUMANHEAD pdm: added case of vehicle determining the player hud + if (player->InVehicle()) { + player->playerView.RenderPlayerView( player->GetVehicleInterfaceLocal()->GetHUD() ); + } + else { + player->playerView.RenderPlayerView( player->hud ); + } + // HUMANHEAD END + +#if INGAME_DEBUGGER_ENABLED // HUMANHEAD pdm + debugger.UpdateDebugger(); +#endif + + return true; +} + + +float hhGameLocal::GetTimeScale() const { + return hhMath::ClampFloat( VECTOR_EPSILON, 10.0f, cvarSystem->GetCVarFloat("timescale") ); +} + +// +// SimpleMonstersWithinRadius() +// TODO: Use this exclusively and remove MonstersWithinRadius when old AI is removed +int hhGameLocal::SimpleMonstersWithinRadius( const idVec3 org, float radius, idAI **monstList, int maxCount ) const { + idBounds bo( org ); + int entCount = 0; + + bo.ExpandSelf( radius ); + for(int i=0;iGetPhysics()->GetAbsBounds().IntersectsBounds( bo ) ) { + monstList[entCount++] = hhMonsterAI::allSimpleMonsters[i]; + } + } + + return entCount; +} + +// +// SpawnMapEntities() +// +void hhGameLocal::SpawnMapEntities( void ) { + if ( reactionHandler ) { + delete reactionHandler; + } + reactionHandler = new hhReactionHandler; + idGameLocal::SpawnMapEntities(); +} + +/* +============== +hhGameLocal::SendMessageAI +============== +*/ +void hhGameLocal::SendMessageAI( const idEntity* entity, const idVec3& origin, float radius, const idEventDef& message ) { + idAI *simplemonsters[ MAX_GENTITIES ]; + + if( radius < VECTOR_EPSILON || isClient ) { + return; + } + for( int i = gameLocal.SimpleMonstersWithinRadius(origin, radius, simplemonsters) - 1; i >= 0; --i ) { + if( simplemonsters[i]->GetHealth() > 0 && simplemonsters[i]->IsActive() && !simplemonsters[i]->IsHidden() ) { + simplemonsters[i]->ProcessEvent( &message, entity ); + } + } +} + +/* +============== +hhGameLocal::MatterTypeToMatterName +============== +*/ +const char* hhGameLocal::MatterTypeToMatterName( surfTypes_t type ) const { + return sufaceTypeNames[ type ]; +} + +/* +============== +hhGameLocal::MatterTypeToMatterKey +============== +*/ +const char* hhGameLocal::MatterTypeToMatterKey( const char* prefix, surfTypes_t type ) const { + return va( "%s_%s", prefix, MatterTypeToMatterName(type) ); +} + +/* +============== +hhGameLocal::MatterNameToMatterType +============== +*/ +surfTypes_t hhGameLocal::MatterNameToMatterType( const char* name ) const { + surfTypes_t type = SURFTYPE_NONE; + + for( int ix = 0; ix < MAX_SURFACE_TYPES; ++ix ) { + type = (surfTypes_t)ix; + if( idStr::Icmp(name, MatterTypeToMatterName(type)) ) { + continue; + } + + return type; + } + + return SURFTYPE_NONE; +} + +/* +============== +hhGameLocal::GetMatterType +============== +*/ +surfTypes_t hhGameLocal::GetMatterType( const trace_t& trace, const char* descriptor ) const { + return GetMatterType( gameLocal.GetTraceEntity(trace), trace.c.material, descriptor ); +} + +/* +============== +hhGameLocal::GetMatterType +============== +*/ +surfTypes_t hhGameLocal::GetMatterType( const idEntity *ent, const idMaterial *material, const char* descriptor ) const { + surfTypes_t type = SURFTYPE_NONE; + const char *matterName = NULL; + const idMaterial *remapped = material; + + // If the entityDef has a matter specified, always use it + // Otherwise, use the materials matter. If none, default to metal. + if( ent && ent->spawnArgs.GetString("matter", NULL, &matterName ) ) { + type = MatterNameToMatterType( matterName ); + } + else { + if (ent && ent->GetSkin()) { + remapped = ent->GetSkin()->RemapShaderBySkin(material); + } + type = (remapped != NULL) ? remapped->GetSurfaceType() : SURFTYPE_METAL; + } + + // OBS: Will never happen + if( !type ) { +#if 0 + Warning("No matter for hit surface"); + Warning(" Entity: %s", ent ? ent->name.c_str() : "none"); + Warning(" Class: %s", ent ? ent->GetClassname() : "none"); + Warning(" Material: %s", material ? material->GetName() : "none"); +#endif + type = SURFTYPE_METAL; + } + + if( g_debugMatter.GetInteger() > 0 && descriptor && descriptor[0] ) { + Printf("%s: [%s] ent=[%s] mat=[%s]\n", + descriptor, + MatterTypeToMatterName(type), + ent ? ent->GetName() : "none", + material ? material->GetName() : "none"); + } + + return type; +} + +void hhGameLocal::AlertAI( idEntity *ent ) { + if ( ent && ent->IsType( idActor::Type ) ) { + // alert them for the next frame + lastAIAlertTime = time + msec; + lastAIAlertEntity = static_cast( ent ); + lastAIAlertRadius = 0; //no radius required by default + } +} + +void hhGameLocal::AlertAI( idEntity *ent, float radius ) { + if ( ent && ent->IsType( idActor::Type ) ) { + // alert them for the next frame + lastAIAlertTime = time + msec; + lastAIAlertEntity = static_cast( ent ); + lastAIAlertRadius = radius; //radius of effect + } +} + +//================ +//hhGameLocal::Save +//================ +void hhGameLocal::Save( idSaveGame *savefile ) const { + int i, num = talonTargets.Num(); + savefile->WriteInt( num ); + for( i = 0; i < num; i++ ) { + savefile->WriteObject( talonTargets[i] ); + } + + reactionHandler->Save( savefile ); + savefile->WriteClipModel( dwWorldClipModel ); + //HUMANHEAD rww +#if _HH_INLINED_PROC_CLIPMODELS + savefile->WriteInt(inlinedProcClipModels.Num()); + for (i = 0; i < inlinedProcClipModels.Num(); i++) { + savefile->WriteClipModel(inlinedProcClipModels[i]); + } +#endif + //HUMANHEAD END + savefile->WriteVec3( gravityNormal ); + savefile->WriteObject( sunCorona ); + ddaManager->Save( savefile ); + savefile->WriteBool( bIsLOTA ); + savefile->WriteFloat( lastAIAlertRadius ); + + num = staticClipModels.Num(); + savefile->WriteInt( num ); + for( i = 0; i < num; i++ ) { + savefile->WriteClipModel( staticClipModels[i] ); + } + + num = staticRenderEntities.Num(); + savefile->WriteInt( num ); + for( i = 0; i < num; i++ ) { + savefile->WriteRenderEntity( *staticRenderEntities[i] ); + } + + savefile->WriteInt( hands.Num() ); + for ( i = 0; i < hands.Num(); i++ ) { + hands[i].Save( savefile ); + } +} + +//================ +//hhGameLocal::Restore +//================ +void hhGameLocal::Restore( idRestoreGame *savefile ) { + idEntity *ent; + int i, num; + idClipModel *model; + renderEntity_t *renderEnt; + savefile->ReadInt( num ); + talonTargets.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadObject( reinterpret_cast ( ent ) ); + talonTargets[i] = ent; + } + + reactionHandler = new hhReactionHandler; + reactionHandler->Restore( savefile ); + savefile->ReadClipModel( dwWorldClipModel ); + //HUMANHEAD rww +#if _HH_INLINED_PROC_CLIPMODELS + int numInlinedProcClipModels; + savefile->ReadInt(numInlinedProcClipModels); + for (i = 0; i < numInlinedProcClipModels; i++) { + savefile->ReadClipModel(inlinedProcClipModels[i]); + } +#endif + //HUMANHEAD END + savefile->ReadVec3( gravityNormal ); + savefile->ReadObject( reinterpret_cast ( sunCorona ) ); + ddaManager->Restore ( savefile ); + savefile->ReadBool( bIsLOTA ); + savefile->ReadFloat( lastAIAlertRadius ); + + savefile->ReadInt( num ); + staticClipModels.DeleteContents( false ); + staticClipModels.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadClipModel( model ); + staticClipModels[i] = model; + } + + savefile->ReadInt( num ); + staticRenderEntities.DeleteContents( false ); + staticRenderEntities.SetNum( num ); + for( i = 0; i < num; i++ ) { + renderEnt = new renderEntity_t; + savefile->ReadRenderEntity( *renderEnt ); + gameRenderWorld->AddEntityDef( renderEnt ); + staticRenderEntities[i] = renderEnt; + } + + savefile->ReadInt( num ); + hands.SetNum( num ); + for ( i = 0; i < num; i++ ) { + hands[i].Restore( savefile ); + } +} + +bool hhGameLocal::InhibitEntitySpawn( idDict &spawnArgs ) { + const char *modelName = spawnArgs.GetString( "model" ); + int inlineEnt = spawnArgs.GetInt( "inline" ); + + if( idStr::Icmp( spawnArgs.GetString( "classname", NULL ), "func_static" ) == 0 && // Only deal with func_static objects + !spawnArgs.GetBool( "neverInline", "0" ) && // and we're not flagged neverInline + ( inlineEnt != 0 || // and we're flagged explicit inline + world->spawnArgs.GetBool( "inlineAllStatics" ) ) ) { // or we're inlining all statics + +#if _HH_INLINED_PROC_CLIPMODELS + if (inlineEnt != 3) { //HUMANHEAD rww +#endif + // Handle explicit clip models + idClipModel *model = NULL; + const char *temp = spawnArgs.GetString( "clipmodel", NULL ); + + if( temp ) { + if ( idClipModel::CheckModel( temp ) ) { + model = new idClipModel( temp ); + } + } + + if( model || !spawnArgs.GetBool( "noclipmodel", "0" ) ) { + // Get the origin and axis of the object + idMat3 axis; + // 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 ) ) { + float angle = spawnArgs.GetFloat( "angle" ); + if ( angle != 0.0f ) { + axis = idAngles( 0.0f, angle, 0.0f ).ToMat3(); + } else { + axis.Identity(); + } + } + + idVec3 origin = spawnArgs.GetVector( "origin" ); + + // Create a clip model for the static object and position it correctly + if( !model ) { + model = new idClipModel( spawnArgs.GetString( "model" ) ); + } + if( spawnArgs.GetBool( "bulletsOnly", "0" ) ) { + model->SetContents( CONTENTS_SHOOTABLE|CONTENTS_SHOOTABLEBYARROW ); + } else if( spawnArgs.GetBool( "solid", "1" ) ) { + model->SetContents( CONTENTS_SOLID ); + } else { + model->SetContents( 0 ); + } + model->SetPosition( origin, axis ); + model->SetEntity( world ); + model->Link( clip ); + staticClipModels.Append( model ); + } +#if _HH_INLINED_PROC_CLIPMODELS + } +#endif + + if ( inlineEnt == 1 ) { // This means we must hand the model, too + renderEntity_t *renderEnt = new renderEntity_t; + gameEdit->ParseSpawnArgsToRenderEntity( &spawnArgs, renderEnt ); + HH_ASSERT( renderEnt->hModel && !renderEnt->callback && renderEnt->shaderParms[ SHADERPARM_ANY_DEFORM ] == DEFORMTYPE_NONE ); // Inlined statics can't have a callback + renderEnt->entityNum = 0; // WorldSpawn + gameRenderWorld->AddEntityDef( renderEnt ); + staticRenderEntities.Append( renderEnt ); + } + + // Don't spawn an entity for an inlined static + return true; + } else { + return idGameLocal::InhibitEntitySpawn( spawnArgs ); + } +} + +//HUMANHEAD rww +#if _HH_INLINED_PROC_CLIPMODELS +void hhGameLocal::CreateInlinedProcClip(idEntity *clipOwner) { + assert(world); + assert(inlinedProcClipModels.Num() == 0); //should never have existing models when this is called + int n = collisionModelManager->GetNumInlinedProcClipModels(); + if (n <= 0) { //nothing to work with, leave + return; + } + + for (int i = 0; i < n; i++) { + idClipModel *chunk = new idClipModel(va("%s%i", PROC_CLIPMODEL_STRING_PRFX, i)); + chunk->SetContents(CONTENTS_SOLID); + chunk->SetEntity(clipOwner); + chunk->Link(clip); + inlinedProcClipModels.Append(chunk); + } +} +#endif +//HUMANHEAD END + +// Finds entities matching a specified type. If last is NULL it starts at the beginning of the list. +// If last is valid, starts looking at that entities position in the list +idEntity *hhGameLocal::FindEntityOfType(const idTypeInfo &type, idEntity *last) { + idEntity *ent; + if (last) { + ent = last->activeNode.Next(); + } + else { + ent = activeEntities.Next(); + } + + for( ; ent != NULL; ent = ent->activeNode.Next() ) { + if (ent->IsType(type)) { + return ent; + } + } + return NULL; +} + +float hhGameLocal::GetDDAValue( void ) { + if ( !ddaManager ) { + return 0.5; + } else { + return ddaManager->GetDifficulty(); + } +} + + +void hhGameLocal::ClearStaticData( void ) { + delete dwWorldClipModel; //rww - this needs to be done as well, dw clipmodel expects world as owner. + dwWorldClipModel = NULL; + +#if _HH_INLINED_PROC_CLIPMODELS + inlinedProcClipModels.DeleteContents(true); //HUMANHEAD rww +#endif + + staticClipModels.DeleteContents( true ); // Clear any inlined static clip models + staticRenderEntities.DeleteContents( true ); // Clear any inlined static render entities +} + +float hhGameLocal::TimeBasedRandomFloat(void) { + if (gameLocal.isMultiplayer) { //rand based on time step + timeRandom = (1103515245 * timeRandom + 12345)%(1<<31); + return (timeRandom)/( float )( (1<<31) + 1 ); + } + else { //give sp complete randomness + return random.RandomFloat(); + } +} + +bool hhGameLocal::PlayerIsDeathwalking(void) { + HH_ASSERT( !isMultiplayer ); + hhPlayer *player = static_cast (GetLocalPlayer()); + HH_ASSERT( player ); + return player->IsDeathWalking(); +} + + +// Abstraction to hide the ugly code for text exchange between engine and game +void hhGameLocal::GetTip(const char *binding, idStr &keyMaterialString, idStr &keyString, bool &isWide) { + + char keyMaterial[256]; // No passing idStr between game and engine + char key[256]; // No passing idStr between game and engine + keyMaterial[0] = '\0'; + key[0] = '\0'; + if (binding) { + common->MaterialKeyForBinding(binding, keyMaterial, key, isWide); + keyMaterialString = keyMaterial; + keyString = key; + } +} + +/* + hhGameLocal::SetTip + + gui gui to display tip + binding key binding to display associated key + tip textual tip to display + top optional top image + mtr optional material, overrides binding image + prefix optional prefix for gui state keys +*/ +bool hhGameLocal::SetTip(idUserInterface* gui, const char *binding, const char *tip, const char *topMaterial, const char *overrideMaterial, const char *prefix) { + assert(gui); + + idStr keyMaterial, key; + bool keywide = false; + if ( !spawnArgs.GetString("mtr_override", "", keyMaterial) ) { + if (binding) { + GetTip(binding, keyMaterial, key, keywide); + } + } + + const char *translated = common->GetLanguageDict()->GetString(tip); + + if (prefix != NULL) { + gui->SetStateBool( va("%s_keywide", prefix), keywide ); + gui->SetStateString( va("%s_tip", prefix), translated ? translated : "" ); + gui->SetStateString( va("%s_key", prefix), key.c_str() ); + gui->SetStateString( va("%s_keyMaterial", prefix), keyMaterial.c_str() ); + gui->SetStateString( va("%s_topMaterial", prefix), topMaterial ? topMaterial : "" ); + } + else { + gui->SetStateBool( "keywide", keywide ); + gui->SetStateString( "tip", translated ? translated : "" ); + gui->SetStateString( "key", key.c_str() ); + gui->SetStateString( "keyMaterial", keyMaterial.c_str() ); + gui->SetStateString( "topMaterial", topMaterial ? topMaterial : "" ); + } + + return (keyMaterial.Length() > 0); +} + diff --git a/src/Prey/prey_game.h b/src/Prey/prey_game.h new file mode 100644 index 0000000..99610cc --- /dev/null +++ b/src/Prey/prey_game.h @@ -0,0 +1,116 @@ +#ifndef __PREY_GAME_H +#define __PREY_GAME_H + +class hhReactionHandler; +class hhSunCorona; // CJR +class hhHand; +class hhAIInspector; + +#ifdef GAME_DLL +extern idCVar com_forceGenericSIMD; +#endif + +//HUMANHEAD: aob - needed for networking to send the least amount of bits +extern const int DECL_MAX_TYPES_NUM_BITS; +//HUMANHEAD END + +class hhGameLocal : public idGameLocal { +public: + virtual void Init( void ); + virtual void Shutdown( void ); + void UnregisterEntity( idEntity *ent ); + + virtual void MapShutdown( void ); + virtual void InitFromNewMap( const char *mapName, idRenderWorld *renderWorld, idSoundWorld *soundWorld, bool isServer, bool isClient, int randseed ); + virtual void RadiusDamage( const idVec3 &origin, idEntity *inflictor, idEntity *attacker, idEntity *ignoreDamage, idEntity *ignorePush, const char *damageDefName, float dmgPower = 1.0f );// jrm + virtual void RadiusPush( const idVec3 &origin, const float radius, const float push, const idEntity *inflictor, const idEntity *ignore, float inflictorScale, const bool quake ); + //HUMANHEAD rww + virtual void LogitechLCDUpdate(void); + //HUMANHEAD END + virtual gameReturn_t RunFrame( const usercmd_t *clientCmds ); + virtual void CacheDictionaryMedia( const idDict *dict ); + virtual bool Draw( int clientNum ); + + // added functionality functions + void RegisterTalonTarget( idEntity *ent ); + idEntity* SpawnClientObject( const char* objectName, idDict* additionalArgs ); //rww + idEntity* SpawnObject( const char* objectName, idDict* additionalArgs = NULL ); + + void GetTip(const char *binding, idStr &keyMaterialString, idStr &keyString, bool &isWide); + bool SetTip(idUserInterface* gui, const char *binding, const char *tip, const char *topMaterial = NULL, const char *overrideMaterial = NULL, const char *prefix = NULL); + void SetSunCorona( hhSunCorona *ent ) { sunCorona = ent; } // CJR + hhSunCorona * GetSunCorona( void ) { return sunCorona; } // CJR + idEntity * FindEntityOfType(const idTypeInfo &type, idEntity *last); + + // nla + idDict & GetSpawnArgs() { return( spawnArgs ); }; + hhReactionHandler* GetReactionHandler() { return reactionHandler; } + float GetTimeScale() const; + const idVec3& GetGravityNormal() { gravityNormal = gravity; gravityNormal.Normalize(); return gravityNormal; } + int SimpleMonstersWithinRadius( const idVec3 org, float radius, idAI **monstList, int maxCount = MAX_GENTITIES ) const; + + void SendMessageAI( const idEntity* entity, const idVec3& origin, float radius, const idEventDef& message ); + + const char* MatterTypeToMatterName( surfTypes_t type ) const; + const char* MatterTypeToMatterKey( const char* prefix, surfTypes_t type ) const; + surfTypes_t MatterNameToMatterType( const char* name ) const; + surfTypes_t GetMatterType( const trace_t& trace, const char* descriptor = "impact" ) const; + surfTypes_t GetMatterType( const idEntity *ent, const idMaterial *material, const char* descriptor = "impact" ) const; + + class hhDDAManager* GetDDA() { return ddaManager; } + + bool IsLOTA() { return bIsLOTA; } + void AlertAI( idEntity *ent ); + void AlertAI( idEntity *ent, float radius ); + + // Special case, these must be virtual -mdl + virtual void Save( idSaveGame *savefile ) const; + virtual void Restore( idRestoreGame *savefile ); + + float GetDDAValue( void ); + + void ClearStaticData( void ); + + float TimeBasedRandomFloat(void); //HUMANHEAD rww + +#if _HH_INLINED_PROC_CLIPMODELS + virtual void CreateInlinedProcClip(idEntity *clipOwner); //HUMANHEAD rww +#endif + + virtual bool PlayerIsDeathwalking( void ); + + idList talonTargets; + float lastAIAlertRadius; + + idList< idEntityPtr< hhHand > > hands; + + idEntityPtr inspector; + +protected: +#if DEATHWALK_AUTOLOAD + virtual void SpawnAppendedMapEntities(); +#endif + virtual bool InhibitEntitySpawn( idDict &spawnArgs ); // HUMANHEAD mdl + +protected: + virtual void SpawnMapEntities( void ); // JRM + idClipModel * dwWorldClipModel; +#if _HH_INLINED_PROC_CLIPMODELS + idList inlinedProcClipModels; //HUMANHEAD rww - chopped geometry grabbed from proc and turned into clipmodel data +#endif + + idVec3 gravityNormal; + + hhSunCorona *sunCorona; // CJR + + //HUMANHEAD rww - moved this down, it is using mixed memory inside and outside the managed heap as is, very messy. + //class hhDDAManager* ddaManager; + //HUMANHEAD END + + bool bIsLOTA; // CJR: If true, this map is a LOTA map + + idList staticClipModels; // HUMANHEAD mdl: For inlined static clip models + idList staticRenderEntities; // HUMANHEAD mdl: For inlined static models +}; + +#endif diff --git a/src/Prey/prey_items.cpp b/src/Prey/prey_items.cpp new file mode 100644 index 0000000..6f3641d --- /dev/null +++ b/src/Prey/prey_items.cpp @@ -0,0 +1,639 @@ +//************************************************************************** +//** +//** PREY_ITEMS.CPP +//** +//** Game code for Prey-specific items +//** +//************************************************************************** + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +//========================================================================== +// hhItem +//========================================================================== +idEventDef EV_SetPickupState( "", "dd" ); + +CLASS_DECLARATION( idItem, hhItem ) + EVENT( EV_SetPickupState, hhItem::Event_SetPickupState ) + EVENT( EV_RespawnItem, hhItem::Event_Respawn ) +END_CLASS + +/* +================ +hhItem::Spawn +================ +*/ +void hhItem::Spawn() { + // Logic to allow item cabinets to deny pickups until desired + if( spawnArgs.GetBool("enablePickup", "1") ) { + EnablePickup(); + } else { + DisablePickup(); + } + + // Diversity for the blinking highlight shells + SetShaderParm(SHADERPARM_DIVERSITY, gameLocal.random.RandomFloat()); +} + +/* +================ +hhItem::EnablePickup +================ +*/ +void hhItem::EnablePickup() { + GetPhysics()->EnableClip(); + GetPhysics()->SetContents( CONTENTS_TRIGGER ); + canPickUp = !spawnArgs.GetBool("triggerFirst"); +} + +/* +================ +hhItem::DisablePickup +================ +*/ +void hhItem::DisablePickup() { + GetPhysics()->DisableClip(); + GetPhysics()->SetContents( 0 ); + canPickUp = false; +} + +/* +================ +hhItem::Pickup +================ +*/ +bool hhItem::Pickup( idPlayer *player ) { + if( gameLocal.isMultiplayer ) { + if( gameLocal.IsCooperative() ) { + CoopPickup( player ); + } else { + if (MultiplayerPickup( player )) { + //HUMANHEAD rww - see if the weapon has a dropped energy type on it + const char *droppedEnergy = spawnArgs.GetString("def_droppedEnergyType"); + if (droppedEnergy && droppedEnergy[0]) { //if it does, copy it to the player's inventory + const idDeclEntityDef *energyDecl = gameLocal.FindEntityDef(droppedEnergy, false); + if (energyDecl) { + const idDeclEntityDef *fireDecl = gameLocal.FindEntityDef(energyDecl->dict.GetString("def_fireInfo"), false); + if (fireDecl) { + hhPlayer *hhPl = static_cast(player); + int num = hhPl->GetWeaponNum("weaponobj_soulstripper"); + assert(num); + hhPl->inventory.energyType = droppedEnergy; + hhPl->weaponInfo[ num ].ammoMax = fireDecl->dict.GetInt("ammoAmount"); + hhPl->spawnArgs.SetInt( "max_ammo_energy", fireDecl->dict.GetInt("ammoAmount") ); + hhPl->inventory.ammo[hhPl->inventory.AmmoIndexForAmmoClass("ammo_energy")]=0; + hhPl->Give( "ammo_energy", fireDecl->dict.GetString("ammoAmount") ); + } + } + } + //HUMANHEAD END + } + } + } else { + SinglePlayerPickup( player ); + } + return true; +} + +/* +================ +hhItem::ShouldRespawn +================ +*/ +bool hhItem::ShouldRespawn( float* respawnDelay ) const { + float respawn = spawnArgs.GetFloat( "respawn", "5.0" ); + if (gameLocal.isMultiplayer) { //rww - override default in mp + respawn *= 2.0f; + } + + if( respawnDelay && respawn > 0.0f ) { + *respawnDelay = respawn; + } + + return (respawn > 0.0f) && !spawnArgs.GetBool( "dropped" ) && gameLocal.isMultiplayer; +} + +/* +================ +hhItem::PostRespawn +================ +*/ +void hhItem::PostRespawn( float delay ) { + const char *sfx = spawnArgs.GetString( "fxRespawn" ); + if ( sfx && *sfx ) { + PostEventSec( &EV_RespawnFx, delay - 0.5f ); + } + PostEventSec( &EV_RespawnItem, delay ); +} + +/* +================ +hhItem::DetermineRemoveOrRespawn +================ +*/ +void hhItem::DetermineRemoveOrRespawn( int removeDelay ) { + bool keepThinking = false; + + // clear our contents so the object isn't picked up twice + SetPickupState( 0, false ); + + if (gameLocal.isMultiplayer && !IsType(idMoveableItem::Type)) { //check for a respawning skin in mp + idStr respawningSkin; + if (spawnArgs.GetString("skin_itemRespawning", "", respawningSkin)) { + SetSkin(declManager->FindSkin(respawningSkin.c_str())); + keepThinking = true; + } + } + + if (!keepThinking) { + // hide the model + Hide(); + } + + // add the highlight shell + if ( itemShellHandle != -1 ) { + gameRenderWorld->FreeEntityDef( itemShellHandle ); + itemShellHandle = -1; + } + + float respawnDelay = 0.0f; + if ( ShouldRespawn(&respawnDelay) ) { + PostRespawn( respawnDelay ); + } else { + // give some time for the pickup sound to play + // FIXME: Play on the owner + PostEventMS( &EV_Remove, removeDelay ); + } + + if (!keepThinking) { + BecomeInactive( TH_THINK ); + } +} + +/* +================ +hhItem::AnnouncePickup +================ +*/ +int hhItem::AnnouncePickup( idPlayer* player ) { + ServerSendEvent( EVENT_PICKUP, NULL, false, -1 ); + + // play pickup sound + int soundLength = 0; + StartSound( "snd_acquire", SND_CHANNEL_ITEM, 0, false, &soundLength ); + + // trigger our targets + ActivateTargets( player ); + + return soundLength; +} + +/* +================ +hhItem::SinglePlayerPickup +================ +*/ +void hhItem::SinglePlayerPickup( idPlayer *player ) { + if ( !GiveToPlayer( player ) ) { + return; + } + + DetermineRemoveOrRespawn( AnnouncePickup(player) ); +} + +/* +================ +hhItem::CoopPickup +================ +*/ +void hhItem::CoopPickup( idPlayer* player ) { + const char* weaponDef = spawnArgs.GetString( "def_weapon" ); + if( weaponDef && weaponDef[0] ) { + CoopWeaponPickup( player ); + } else { + CoopItemPickup( player ); + } +} + +/* +================ +hhItem::CoopWeaponPickup +================ +*/ +void hhItem::CoopWeaponPickup( idPlayer *player ) { + float delay = 0.0f; + + if( !GiveToPlayer(player) ) { + return; + } + + AnnouncePickup( player ); + + ShouldRespawn( &delay ); + SetPickupState( 0, false ); + PostEventSec( &EV_SetPickupState, delay, CONTENTS_TRIGGER, true ); +} + +/* +================ +hhItem::CoopItemPickup +================ +*/ +void hhItem::CoopItemPickup( idPlayer *player ) { + idEntity* entity = NULL; + hhPlayer* castPlayer = NULL; + + for( int ix = 0; ix < gameLocal.numClients; ++ix ) { + entity = gameLocal.entities[ix]; + if( !entity || !entity->IsType(hhPlayer::Type) ) { + continue; + } + + castPlayer = static_cast( entity ); + castPlayer->GiveItem( this ); + } + + DetermineRemoveOrRespawn( AnnouncePickup(player) ); +} + +/* +================ +hhItem::MultiplayerPickup +================ +*/ +bool hhItem::MultiplayerPickup( idPlayer *player ) { + if( !GiveToPlayer(player) ) { + return false; + } + + DetermineRemoveOrRespawn( AnnouncePickup(player) ); + return true; +} + +/* +================ +hhItem::SetPickupState +================ +*/ +void hhItem::SetPickupState( int contents, bool allowPickup ) { + GetPhysics()->SetContents( contents ); + canPickUp = allowPickup; +} + +/* +================ +hhItem::Event_SetPickupState +================ +*/ +void hhItem::Event_SetPickupState( int contents, bool allowPickup ) { + SetPickupState( contents, allowPickup ); +} + +/* +================ +hhItem::Event_Respawn +================ +*/ +void hhItem::Event_Respawn( void ) { + if ( !gameLocal.isClient ) { + ServerSendEvent( EVENT_RESPAWN, NULL, false, -1 ); + } + BecomeActive( TH_THINK ); + if (gameLocal.isMultiplayer && renderEntity.customSkin != GetNonRespawnSkin()) { + SetSkin(GetNonRespawnSkin()); //restore skin + } + Show(); + inViewTime = -1000; + lastCycle = -1; + + //HUMANHEAD: aob + SetPickupState( CONTENTS_TRIGGER, true ); + //HUMANHEAD END + + SetOrigin( orgOrigin ); + StartSound( "snd_respawn", SND_CHANNEL_ITEM ); +} + + +//============================================================================= +// hhItemSoul +//============================================================================= + +idEventDef EV_PlaySpiritSound( "playSpiritSound", NULL ); +idEventDef EV_Broadcast_AssignFx_Spirit( "", "e" ); +idEventDef EV_Broadcast_AssignFx_Physical( "", "e" ); + +CLASS_DECLARATION( hhItem, hhItemSoul ) + EVENT( EV_TalonAction, hhItemSoul::Event_TalonAction ) + EVENT( EV_PostSpawn, hhItemSoul::Event_PostSpawn ) + EVENT( EV_PlaySpiritSound, hhItemSoul::Event_PlaySpiritSound ) + EVENT( EV_Broadcast_AssignFx_Spirit, hhItemSoul::Event_AssignFxSoul_Spirit ) + EVENT( EV_Broadcast_AssignFx_Physical, hhItemSoul::Event_AssignFxSoul_Physical ) +END_CLASS + +//-------------------------------------------------------------------------- +// +// hhItemSoul::Spawn +// +//-------------------------------------------------------------------------- +void hhItemSoul::Spawn() { + idDict args; + idVec3 offset; + + fl.networkSync = true; //rww + fl.clientEvents = true; //rww + + spawnArgs.SetBool("dropped", true); //rww - don't respawn + + bFollowTriggered = false; + + // Only allow this item to be spawned if we can spirit walk + if (!gameLocal.isMultiplayer) { + hhPlayer *player = static_cast( gameLocal.GetLocalPlayer() ); + if ( player ) { + if ( !player->inventory.requirements.bCanSpiritWalk ) { + Hide(); + PostEventMS( &EV_Remove, 0 ); // Remove this soul before it can be spawned + return; + } + } + } + + BecomeActive( TH_THINK | TH_PHYSICS ); + + // Remove the lifeforce after 60 seconds if it hasn't been picked up + if (gameLocal.isMultiplayer) { //rww + PostEventSec( &EV_Remove, spawnArgs.GetFloat( "lifeTime_mp", "30" ) ); + } + else { + PostEventSec( &EV_Remove, spawnArgs.GetFloat( "lifeTime", "60" ) ); + } + + // Play the spirit sound, after a 10 second delay + PostEventSec( &EV_PlaySpiritSound, spawnArgs.GetFloat( "spiritSoundDelay", "10" ) ); + + renderEntity.onlyVisibleInSpirit = true; + + velocity = vec3_origin; + acceleration = vec3_origin; + surge = 0.0f; + parentMonster = NULL; + + PostEventMS( &EV_PostSpawn, 0 ); +} + +//-------------------------------------------------------------------------- +// +// hhItemSoul::Event_PostSpawn +// +//-------------------------------------------------------------------------- + +void hhItemSoul::Event_PostSpawn() { + hhFxInfo fxInfo; + + if (!gameLocal.isMultiplayer) { + const char *monsterName = spawnArgs.GetString("monsterSpawnedBy", NULL); + if (monsterName) { + idEntity *ent = gameLocal.FindEntity(monsterName); + if (ent && ent->IsType(idAI::Type)) { + parentMonster = ent; + } + } + } + + // Spawn the spirit-only fx (this fx is only seen when spiritwalking) + fxInfo.SetNormal( GetAxis()[2] ); + fxInfo.RemoveWhenDone( false ); + fxInfo.OnlyVisibleInSpirit( true ); + BroadcastFxInfo( spawnArgs.GetString("fx_lifeforce"), GetOrigin(), GetAxis(), &fxInfo, &EV_Broadcast_AssignFx_Spirit, false ); //rww - local + + // Spawn the physical realm fx + fxInfo.SetNormal( GetAxis()[2] ); + fxInfo.RemoveWhenDone( false ); + fxInfo.OnlyVisibleInSpirit( false ); + BroadcastFxInfo( spawnArgs.GetString("fx_lifeforcePhysical"), GetOrigin(), GetAxis(), &fxInfo, &EV_Broadcast_AssignFx_Physical, false ); //rww - local +} + +//-------------------------------------------------------------------------- +// +// hhItemSoul::Event_AssignFxSoul +// +//-------------------------------------------------------------------------- + +void hhItemSoul::Event_AssignFxSoul_Spirit( hhEntityFx* fx ) { + soulFx = fx; +} + +void hhItemSoul::Event_AssignFxSoul_Physical( hhEntityFx* fx ) { + physicalSoulFx = fx; +} + +//-------------------------------------------------------------------------- +// +// hhItemSoul::Event_PlaySpiritSound +// +//-------------------------------------------------------------------------- + +void hhItemSoul::Event_PlaySpiritSound() { + StartSound( "snd_spiritItemNear", SND_CHANNEL_SPIRITWALK, 0, true ); + + // Randomly start the sound over again (random delay between 12 and 22 seconds) + PostEventSec( &EV_PlaySpiritSound, 12.0f + spawnArgs.GetFloat( "spiritSoundDelay", "10.0" ) * gameLocal.random.RandomFloat() ); +} + +//-------------------------------------------------------------------------- +// +// hhItemSoul::~hhItemSoul +// +//-------------------------------------------------------------------------- + +hhItemSoul::~hhItemSoul() { + StopSound( SND_CHANNEL_SPIRITWALK, false ); //stop now, do not broadcast on purpose + SAFE_REMOVE( soulFx ); + SAFE_REMOVE( physicalSoulFx ); +} + +//-------------------------------------------------------------------------- +// +// hhItemSoul::Ticker +// +//-------------------------------------------------------------------------- + +void hhItemSoul::Think() { + idVec3 playerOrigin; + idMat3 playerAxis; + // Move the soul towards the nearest spirit player + //COOP FIXME: Get the NEAREST spirit player for this to support multiplayer and coop. + + hhPlayer *player = NULL; + if (!gameLocal.isMultiplayer) { + player = static_cast( gameLocal.GetLocalPlayer() ); + } + else { + orgOrigin = GetOrigin(); + spin = false; + } + float followSpeed = spawnArgs.GetFloat( "followSpeed", "20" ); + + if ( !player || player->noclip ) { + velocity = vec3_origin; // mdl: If player is noclip, don't move an inch + } else { + player->GetViewPos( playerOrigin, playerAxis ); + + if ( !bFollowTriggered && player && player->IsSpiritWalking() ) { + acceleration = ( playerOrigin - playerAxis[2] * 20 ) - GetOrigin(); + float dot = playerAxis[0] * -acceleration; + + if ( dot > 0.95f ) { // Accelerate only if the player is looking at the soul + bFollowTriggered = true; + lastPlayerOrigin = playerOrigin; + } + } + + if ( bFollowTriggered ) { + //HUMANHEAD PCF mdl 04/28/06 - Changed factor from 0.01 to 0.05 for stationary players + float factor = 0.05f; // Keep acceleration slow enough for the player to see + if ( ( lastPlayerOrigin - playerOrigin ).LengthSqr() > 75.0f ) { + velocity = vec3_origin; // Player has moved, so clear the old velocity + lastPlayerOrigin = playerOrigin; + //HUMANHEAD PCF mdl 04/28/06 - Changed factor from 5 to 3.5 for moving players + factor = 3.5f; // Give acceleration a little boost so the soul doesn't seem slow when the player is moving around. + } + + acceleration = ( playerOrigin - playerAxis[2] * 20 ) - GetOrigin(); + velocity += (factor * acceleration * ( spawnArgs.GetFloat( "acceleration", "0.5" ) * spawnArgs.GetFloat( "surge", "0.01" ) ) * (60.0f * USERCMD_ONE_OVER_HZ)); + + SetOrigin( GetOrigin() + velocity ); + UpdateVisuals(); + + if ( soulFx.IsValid() ) { + soulFx->SetOrigin( GetOrigin() ); + soulFx->UpdateVisuals(); + } + if ( physicalSoulFx.IsValid() ) { + physicalSoulFx->SetOrigin( GetOrigin() ); + physicalSoulFx->UpdateVisuals(); + } + } + } + + // Must be done after this movement for proper relinking + hhItem::Think(); +} + +//rww - netcode +void hhItemSoul::WriteToSnapshot( idBitMsgDelta &msg ) const { + GetPhysics()->WriteToSnapshot(msg); + msg.WriteBits(IsHidden() || !soulFx.IsValid() || !soulFx->IsActive(TH_THINK), 1); +} + +void hhItemSoul::ReadFromSnapshot( const idBitMsgDelta &msg ) { + GetPhysics()->ReadFromSnapshot(msg); + bool hidden = !!msg.ReadBits(1); + if (hidden != IsHidden()) { + if (hidden) { + if( soulFx.IsValid() ) { + soulFx->Nozzle( false ); + } + if( physicalSoulFx.IsValid() ) { + physicalSoulFx->Nozzle( false ); + } + Hide(); + } + else { + Show(); + } + } +} + +void hhItemSoul::ClientPredictionThink( void ) { + Think(); +} + + +//-------------------------------------------------------------------------- +// +// hhItemSoul::Pickup +// +//-------------------------------------------------------------------------- + +bool hhItemSoul::Pickup( idPlayer *player ) { + + CancelEvents( &EV_Remove ); // Remove the 60-second cancel event from when this item was spawned + CancelEvents(&EV_PlaySpiritSound); //rww - also cancel pending spirit sound events + + StopSound( SND_CHANNEL_SPIRITWALK, true ); + + bool result = hhItem::Pickup( player ); + if( soulFx.IsValid() ) { + soulFx->Nozzle( false ); + } + if( physicalSoulFx.IsValid() ) { + physicalSoulFx->Nozzle( false ); + } + + return result; +} + +//-------------------------------------------------------------------------- +// +// hhItemSoul::Event_TalonAction +// +//-------------------------------------------------------------------------- + +void hhItemSoul::Event_TalonAction( idEntity *talon, bool landed ) { + idPlayer *player; + + if( !talon ) { + return; + } + + player = (idPlayer *)(((hhTalon *)talon)->GetOwner()); + Pickup( player ); +} + +//-------------------------------------------------------------------------- +// +// hhItemSoul::Event_Remove +// +//-------------------------------------------------------------------------- +void hhItemSoul::Event_Remove() { + // Now that spirit is gone, start the disposal countdown + if (parentMonster.IsValid()) { + parentMonster->StartDisposeCountdown(); + } + + hhItem::Event_Remove(); +} + +//================ +//hhItemSoul::Save +//================ +void hhItemSoul::Save( idSaveGame *savefile ) const { + soulFx.Save( savefile ); + physicalSoulFx.Save( savefile ); + parentMonster.Save( savefile ); + savefile->WriteVec3( velocity ); + savefile->WriteVec3( acceleration ); + savefile->WriteFloat( surge ); + savefile->WriteBool( bFollowTriggered ); + savefile->WriteVec3( lastPlayerOrigin ); +} + +//================ +//hhItemSoul::Restore +//================ +void hhItemSoul::Restore( idRestoreGame *savefile ) { + soulFx.Restore( savefile ); + physicalSoulFx.Restore( savefile ); + parentMonster.Restore( savefile ); + savefile->ReadVec3( velocity ); + savefile->ReadVec3( acceleration ); + savefile->ReadFloat( surge ); + savefile->ReadBool( bFollowTriggered ); + savefile->ReadVec3( lastPlayerOrigin ); +} + diff --git a/src/Prey/prey_items.h b/src/Prey/prey_items.h new file mode 100644 index 0000000..2c41b0c --- /dev/null +++ b/src/Prey/prey_items.h @@ -0,0 +1,79 @@ +#ifndef __PREY_ITEMS_H__ +#define __PREY_ITEMS_H__ + +// These defined in item.cpp +extern const idEventDef EV_RespawnItem; +extern const idEventDef EV_HideObjective; +extern const idEventDef EV_HideItem; + +class hhItem: public idItem { + CLASS_PROTOTYPE( hhItem ); + + public: + void Spawn(); + + virtual bool Pickup( idPlayer *player ); + + void EnablePickup(); + void DisablePickup(); + + protected: + bool ShouldRespawn( float* respawnDelay = NULL ) const; + void PostRespawn( float delay ); + void DetermineRemoveOrRespawn( int removeDelay ); + int AnnouncePickup( idPlayer* player ); + + void SinglePlayerPickup( idPlayer *player ); + + void CoopPickup( idPlayer* player ); + void CoopWeaponPickup( idPlayer *player ); + void CoopItemPickup( idPlayer *player ); + + bool MultiplayerPickup( idPlayer *player ); + + void SetPickupState( int contents, bool allowPickup ); + + protected: + void Event_SetPickupState( int contents, bool allowPickup ); + void Event_Respawn( void ); +}; + +class hhItemSoul : public hhItem { + CLASS_PROTOTYPE(hhItemSoul); + + public: + void Spawn(); + virtual ~hhItemSoul(); + + virtual void Think(); + + //rww - netcode + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + + virtual bool Pickup( idPlayer *player ); + void Event_TalonAction( idEntity *talon, bool landed ); + void Event_PostSpawn(); + void Event_PlaySpiritSound(); + void Event_AssignFxSoul_Spirit( hhEntityFx* fx ); + void Event_AssignFxSoul_Physical( hhEntityFx* fx ); + + virtual void Event_Remove(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + protected: + idEntityPtr soulFx; + idEntityPtr physicalSoulFx; + idEntityPtr parentMonster; + idVec3 velocity; + idVec3 acceleration; + float surge; + bool bFollowTriggered; + idVec3 lastPlayerOrigin; +}; + + +#endif /* __PREY_ITEMS_H__ */ diff --git a/src/Prey/prey_liquid.cpp b/src/Prey/prey_liquid.cpp new file mode 100644 index 0000000..8e55d4b --- /dev/null +++ b/src/Prey/prey_liquid.cpp @@ -0,0 +1,105 @@ +//************************************************************************** +//** +//** PREY_LIQUID.CPP +//** +//** Game code for Prey-specific dynamic liquid +//** +//** Currently using id's liquid code for the rendering +//************************************************************************** + +// HEADER FILES ------------------------------------------------------------ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +//========================================================================== +// +// hhLiquid +// +//========================================================================== +const idEventDef EV_Disturb("disturb", "vff"); + +CLASS_DECLARATION( hhRenderEntity, hhLiquid ) + EVENT( EV_Touch, hhLiquid::Event_Touch ) + EVENT( EV_Disturb, hhLiquid::Event_Disturb ) +END_CLASS + +void hhLiquid::Spawn(void) { + if (renderEntity.hModel) { + renderEntity.hModel->Reset(); + } + + fl.takedamage = true; + GetPhysics()->SetContents( CONTENTS_WATER | CONTENTS_TRIGGER | CONTENTS_RENDERMODEL ); + + factor_movement = spawnArgs.GetFloat("factor_movement"); + factor_collide = spawnArgs.GetFloat("factor_collide"); +} + +void hhLiquid::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( factor_movement ); + savefile->WriteFloat( factor_collide ); +} + +void hhLiquid::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( factor_movement ); + savefile->ReadFloat( factor_collide ); + + if (renderEntity.hModel) { + renderEntity.hModel->Reset(); + } +} + +void hhLiquid::Disturb( const idVec3 &point, const idBounds &bounds, const float magnitude ) { + if (renderEntity.hModel) { + idVec3 relativeToModel = point - GetPhysics()->GetOrigin(); + //FIXME: Take rotation of 'this' into account + +#if 0 + hhUtils::DebugCross(colorRed, point, 5, 1000); + gameLocal.Printf("Disturbance: Bounds={%.0f,%.0f,%.0f)(%.0f,%.0f,%.0f) mag=%f\n", + bounds[0].x, bounds[0].y, bounds[0].z, + bounds[1].x, bounds[1].y, bounds[1].z, + magnitude); +#endif + + // Pass in bounds in model coords + renderEntity.hModel->IntersectBounds( bounds.Translate(relativeToModel), magnitude ); + } +} + +void hhLiquid::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + idBounds bounds(idVec3(-1,-1,-1), idVec3(1,1,1)); + if (inflictor) { + bounds = inflictor->GetPhysics()->GetBounds(); + } + + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); + if (damageDef) { + float disturbance = damageDef->GetFloat("liquid_disturbance"); + float boundsfactor = damageDef->GetFloat("liquid_bounds_expand"); + Disturb(inflictor->GetPhysics()->GetOrigin(), bounds.Expand(boundsfactor), disturbance); + } +} + +void hhLiquid::Event_Touch(idEntity *other, trace_t *trace) { + + idVec3 vel = other->GetPhysics()->GetLinearVelocity(); + idVec3 pos = other->GetPhysics()->GetOrigin(); + bool bMoving = vel.LengthSqr() > 1.0f; + if (bMoving) { + //TODO: Make scale with velocity + + // Since deforming completely to the bounds is a little too much, average the + // z position of the actor with the surface + pos.z = (pos.z + GetOrigin().z) * 0.5f; + Disturb(pos, other->GetPhysics()->GetBounds(), factor_movement); + } +} + +void hhLiquid::Event_Disturb(const idVec3 &position, float size, float magnitude) { + idBounds bounds(idVec3(-0.5f,-0.5f,-0.5f), idVec3(0.5f,0.5f,0.5f)); + Disturb(position, bounds.Expand(size), magnitude); +} diff --git a/src/Prey/prey_liquid.h b/src/Prey/prey_liquid.h new file mode 100644 index 0000000..051e5d2 --- /dev/null +++ b/src/Prey/prey_liquid.h @@ -0,0 +1,26 @@ + +#ifndef __PREY_SLUDGE_H__ +#define __PREY_SLUDGE_H__ + +class hhLiquid : public hhRenderEntity { +public: + CLASS_PROTOTYPE( hhLiquid ); + + void Spawn(void); + 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 ); + + void Disturb( const idVec3 &point, const idBounds &bounds, const float magnitude ); + +protected: + virtual void Event_Touch(idEntity *other, trace_t *trace); + void Event_Disturb(const idVec3 &position, float size, float magnitude); + +protected: + float factor_movement; + float factor_collide; +}; + + +#endif /* __PREY_SLUDGE_H__ */ diff --git a/src/Prey/prey_local.cpp b/src/Prey/prey_local.cpp new file mode 100644 index 0000000..6ad9cae --- /dev/null +++ b/src/Prey/prey_local.cpp @@ -0,0 +1,3 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop +#include "prey_local.h" \ No newline at end of file diff --git a/src/Prey/prey_local.h b/src/Prey/prey_local.h new file mode 100644 index 0000000..c900bc8 --- /dev/null +++ b/src/Prey/prey_local.h @@ -0,0 +1,115 @@ +#ifndef __PREY_LOCAL_H +#define __PREY_LOCAL_H + +#include "../Game/game_local.h" +#include "prey_game.h" +#include "sys_preycmds.h" + +#include "force_converge.h" +#include "game_targetproxy.h" +#include "sys_debugger.h" +#include "game_console.h" +#include "game_spring.h" +#include "game_misc.h" +#include "game_moveable.h" +#include "game_barrel.h" +#include "physics_vehicle.h" +#include "game_vehicle.h" +#include "game_shuttle.h" +#include "game_railshuttle.h" +#include "game_dockedgun.h" +#include "game_player.h" +#include "prey_beam.h" +#include "game_trigger.h" +#include "game_tripwire.h" +#include "prey_bonecontroller.h" +#include "game_securityeye.h" +#include "game_cards.h" +#include "game_arcadegame.h" +#include "game_jukebox.h" +#include "game_blackjack.h" +#include "game_poker.h" +#include "game_zone.h" +#include "game_safeDeathVolume.h" +#include "game_energynode.h" +#include "prey_weaponrifle.h" +#include "prey_weaponspiritbow.h" +#include "prey_weaponautocannon.h" +#include "prey_weaponcrawlergrenade.h" +#include "prey_weaponrocketlauncher.h" +#include "prey_weaponsoulstripper.h" +#include "prey_weaponhider.h" +#include "game_jumpzone.h" +#include "game_note.h" +#include "game_portal.h" +#include "game_skybox.h" +#include "game_trackmover.h" +#include "game_sphere.h" +#include "prey_items.h" +#include "game_modeltoggle.h" +#include "game_animator.h" +#include "game_targets.h" +#include "game_slots.h" +#include "game_vomiter.h" +#include "game_modeldoor.h" +#include "game_door.h" +#include "game_cilia.h" +#include "game_animatedgui.h" +#include "game_gibbable.h" +//#include "game_pathNode.h" +#include "game_organtrigger.h" +#include "game_gravityswitch.h" + +#include "ai_passageway.h" +#include "ai_speech.h" +#include "ai_reaction.h" +#include "game_monster_ai.h" +#include "game_entityspawner.h" +#include "ai_centurion.h" +#include "ai_droid.h" +#include "ai_mutilatedhuman.h" +#include "ai_mutate.h" +#include "ai_hunter_simple.h" +#include "ai_creaturex.h" +#include "ai_harvester_simple.h" +#include "ai_jetpack_harvester_simple.h" +#include "ai_keeper_simple.h" +#include "ai_gasbag_simple.h" +#include "ai_spawncase.h" +#include "ai_inspector.h" +#include "ai_possessedTommy.h" +#include "ai_sphereboss.h" +#include "ai_crawler.h" + +#include "game_itemcabinet.h" +#include "game_itemautomatic.h" +#include "game_healthspore.h" +#include "game_healthbasin.h" +#include "game_portalframe.h" +#include "game_afs.h" +#include "game_lightfixture.h" +#include "game_mover.h" +#include "game_gun.h" +#include "game_wraith.h" +#include "game_deathwraith.h" +#include "game_talon.h" +#include "game_debrisspawner.h" +#include "game_bindController.h" +#include "game_rail.h" +#include "game_mountedgun.h" +#include "game_fixedpod.h" +#include "game_mine.h" +#include "game_pod.h" +#include "game_podspawner.h" +#include "game_shuttledock.h" +#include "game_shuttletransport.h" +#include "prey_spiritbridge.h" +#include "prey_spiritsecret.h" +#include "game_forcefield.h" +#include "prey_liquid.h" +#include "game_alarm.h" +#include "game_sunCorona.h" +#include "game_eggspawner.h" +#include "game_proxdoor.h" + +#endif diff --git a/src/Prey/prey_projectile.cpp b/src/Prey/prey_projectile.cpp new file mode 100644 index 0000000..de400c1 --- /dev/null +++ b/src/Prey/prey_projectile.cpp @@ -0,0 +1,1547 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_SpawnDriverLocal( "", "s" ); +const idEventDef EV_SpawnFxFlyLocal( "", "s" ); + +const idEventDef EV_Collision_Flesh( "", "tv", 'd' ); +const idEventDef EV_Collision_Metal( "", "tv", 'd' ); +const idEventDef EV_Collision_AltMetal( "", "tv", 'd' ); +const idEventDef EV_Collision_Wood( "", "tv", 'd' ); +const idEventDef EV_Collision_Stone( "", "tv", 'd' ); +const idEventDef EV_Collision_Glass( "", "tv", 'd' ); +const idEventDef EV_Collision_Liquid( "", "tv", 'd' ); +const idEventDef EV_Collision_Spirit( "", "tv", 'd' ); +const idEventDef EV_Collision_Remove( "", "tv", 'd' ); +const idEventDef EV_Collision_CardBoard( "", "tv", 'd' ); +const idEventDef EV_Collision_Tile( "", "tv", 'd' ); +const idEventDef EV_Collision_Forcefield( "", "tv", 'd' ); +const idEventDef EV_Collision_Chaff( "", "tv", 'd' ); +const idEventDef EV_Collision_Wallwalk( "", "tv", 'd' ); +const idEventDef EV_Collision_Pipe( "", "tv", 'd' ); + + +const idEventDef EV_AllowCollision_Flesh( "", "t", 'd' ); +const idEventDef EV_AllowCollision_Metal( "", "t", 'd' ); +const idEventDef EV_AllowCollision_AltMetal( "", "t", 'd' ); +const idEventDef EV_AllowCollision_Wood( "", "t", 'd' ); +const idEventDef EV_AllowCollision_Stone( "", "t", 'd' ); +const idEventDef EV_AllowCollision_Glass( "", "t", 'd' ); +const idEventDef EV_AllowCollision_Liquid( "", "t", 'd' ); +const idEventDef EV_AllowCollision_Spirit( "", "t", 'd' ); +const idEventDef EV_AllowCollision_CardBoard( "", "t", 'd' ); +const idEventDef EV_AllowCollision_Tile( "", "t", 'd' ); +const idEventDef EV_AllowCollision_Forcefield( "", "t", 'd' ); +const idEventDef EV_AllowCollision_Chaff( "", "t", 'd' ); +const idEventDef EV_AllowCollision_Wallwalk( "", "t", 'd' ); +const idEventDef EV_AllowCollision_Pipe( "", "t", 'd' ); + +hhMatterEventDefPartner matterEventsCollision( "collision" ); +hhMatterEventDefPartner matterEventsAllowCollision( "collision_allow" ); + +CLASS_DECLARATION( idProjectile, hhProjectile ) + EVENT( EV_Explode, hhProjectile::Event_Fuse_Explode ) + EVENT( EV_SpawnDriverLocal, hhProjectile::Event_SpawnDriverLocal ) + EVENT( EV_SpawnFxFlyLocal, hhProjectile::Event_SpawnFxFlyLocal ) + + EVENT( EV_Collision_Flesh, hhProjectile::Event_Collision_Impact ) + EVENT( EV_Collision_Metal, hhProjectile::Event_Collision_Impact ) + EVENT( EV_Collision_AltMetal, hhProjectile::Event_Collision_Impact ) + EVENT( EV_Collision_Wood, hhProjectile::Event_Collision_Impact ) + EVENT( EV_Collision_Stone, hhProjectile::Event_Collision_Impact ) + EVENT( EV_Collision_Glass, hhProjectile::Event_Collision_Impact ) + EVENT( EV_Collision_Liquid, hhProjectile::Event_Collision_DisturbLiquid ) + EVENT( EV_Collision_CardBoard, hhProjectile::Event_Collision_Impact ) + EVENT( EV_Collision_Tile, hhProjectile::Event_Collision_Impact ) + EVENT( EV_Collision_Forcefield, hhProjectile::Event_Collision_Impact ) + EVENT( EV_Collision_Pipe, hhProjectile::Event_Collision_Impact ) + EVENT( EV_Collision_Wallwalk, hhProjectile::Event_Collision_Impact ) + EVENT( EV_Collision_Chaff, hhProjectile::Event_Collision_Impact ) + + EVENT( EV_Collision_Remove, hhProjectile::Event_Collision_Remove ) + + EVENT( EV_AllowCollision_Flesh, hhProjectile::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_Metal, hhProjectile::Event_AllowCollision_CollideNoProj ) + EVENT( EV_AllowCollision_AltMetal, hhProjectile::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_Wood, hhProjectile::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_Stone, hhProjectile::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_Glass, hhProjectile::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_Liquid, hhProjectile::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_CardBoard, hhProjectile::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_Tile, hhProjectile::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_Forcefield, hhProjectile::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_Pipe, hhProjectile::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_Wallwalk, hhProjectile::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_Spirit, hhProjectile::Event_AllowCollision_PassThru ) + EVENT( EV_AllowCollision_Chaff, hhProjectile::Event_AllowCollision_Collide ) //bjk: shield blocks all +END_CLASS + + +/* +================ +hhProjectile::Spawn +================ +*/ +void hhProjectile::Spawn() { + bDDACounted = false; + parentProjectile = NULL; + launchTimestamp = 0; + + collidedPortal = NULL; // cjr + collideLocation = vec3_origin; // cjr + collideVelocity = vec3_origin; // cjr + + weaponNum = -1; // cjr - default the weapon index to -1, to denote non-player weapons + + netSyncPhysics = spawnArgs.GetBool( "net_fullphysics" ); //HUMANHEAD rww + bNoCollideWithCrawlers = spawnArgs.GetBool( "noCollideWithCrawlers", "0" ); + bProjCollide = spawnArgs.GetBool( "proj_collision", "0" ); + flyBySoundDistSq = spawnArgs.GetFloat( "flyby_dist", "0" ); + flyBySoundDistSq *= flyBySoundDistSq; + if ( flyBySoundDistSq > 0 ) { + bPlayFlyBySound = true; + } + BecomeActive( TH_TICKER ); +} + +/* +================ +hhProjectile::~hhProjectile +================ +*/ +hhProjectile::~hhProjectile() { + SAFE_REMOVE(fxFly); //HUMANHEAD rww +} + +void hhProjectile::SetParentProjectile( hhProjectile* in_parent ) { + parentProjectile.Assign( in_parent ); +} + +void hhProjectile::SetCollidedPortal( hhPortal *newPortal, idVec3 newLocation, idVec3 newVelocity ) { + collidedPortal.Assign( newPortal ); + collideLocation = newLocation; + collideVelocity = newVelocity; + BecomeActive(TH_MISC1); + + physicsObj.PutToRest(); +} + +hhProjectile* hhProjectile::GetParentProjectile( void ) { + if( parentProjectile.IsValid() ) { + return parentProjectile.GetEntity(); + } + return NULL; +} + + +/* +================ +hhProjectile::SetOrigin +================ +*/ +void hhProjectile::SetOrigin( const idVec3& origin ) { + idVec3 masterOrigin; + idMat3 masterAxis; + idVec3 localOrigin( origin ); + + if( driver.IsValid() && IsBoundTo(driver.GetEntity()) ) { + GetMasterPosition( masterOrigin, masterAxis ); + localOrigin = (localOrigin - masterOrigin) * masterAxis.Transpose(); + } + + idProjectile::SetOrigin( localOrigin ); +} + +/* +================ +hhProjectile::SetAxis +================ +*/ +void hhProjectile::SetAxis( const idMat3& axis ) { + idVec3 masterOrigin; + idMat3 masterAxis; + idMat3 localAxis( axis ); + + if( driver.IsValid() && IsBoundTo(driver.GetEntity()) ) { + GetMasterPosition( masterOrigin, masterAxis ); + localAxis *= masterAxis.Transpose(); + } + + idProjectile::SetAxis( localAxis ); +} + +/* +================ +hhProjectile::Think +================ +*/ +void hhProjectile::Think( void ) { + + // HUMANHEAD: cjr - if this projectile recently struck a portal, then attempt to portal it + if ( (thinkFlags & TH_MISC1) && collidedPortal.IsValid() ) { + GetPhysics()->SetLinearVelocity( collideVelocity ); + collidedPortal->PortalProjectile( this, collideLocation, collideLocation + collideVelocity ); + collidedPortal = NULL; + collideLocation = vec3_origin; + collideVelocity = vec3_origin; + BecomeInactive(TH_MISC1); + } + // HUMANHEAD END + + // run physics + RunPhysics(); + + if( thinkFlags & TH_THINK ) { + //HUMANHEAD: aob - added thrust_start check + if( thrust && (thrust_start <= gameLocal.GetTime() && gameLocal.GetTime() < thrust_end) ) { + // evaluate force + //HUMANHEAD rww - get rid of the thruster, needless projectile constructor overhead. + //thruster.SetForce( GetAxis()[ 0 ] * thrust ); + //thruster.Evaluate( gameLocal.GetTime() ); + //replaced the logic for the thing here. + idVec3 force = GetAxis()[ 0 ] * thrust; + idVec3 point = physicsObj.GetCenterOfMass(); + idVec3 p = GetPhysics()->GetOrigin() + point * physics->GetAxis(); + GetPhysics()->AddForce( 0, p, force ); + } + } + + //HUMANHEAD: aob + if( thinkFlags & TH_TICKER ) { + Ticker(); + } + //HUMANHEAD END + + Present(); + + if ( thinkFlags & TH_MISC2 ) { + UpdateLight(); + } + + //HUMANHEAD jsh flyby sounds + if( !gameLocal.isMultiplayer && bPlayFlyBySound && gameLocal.GetLocalPlayer() ) { + if ( !owner.IsEqualTo( gameLocal.GetLocalPlayer() ) ) { + if ( (GetOrigin() - gameLocal.GetLocalPlayer()->GetOrigin()).LengthSqr() < flyBySoundDistSq ) { + BroadcastFxPrefixedRandom( "fx_flyby", EV_SpawnFxFlyLocal ); + StartSound( "snd_flyby", SND_CHANNEL_BODY ); + bPlayFlyBySound = false; + } + } + } +} + +/* +===================== +hhProjectile::PlayImpactSound + +custom for projectiles. this needs to broadcast, unless we get predicted projectiles operational. +however predicted projectiles are not reliable since the client will not always collide them before +the server does, and so they will get removed with no fx. not a serious issue but somewhat bothersome +nonetheless. +===================== +*/ +int hhProjectile::PlayImpactSound( const idDict* dict, const idVec3 &origin, surfTypes_t type ) { + const char *snd = gameLocal.MatterTypeToMatterKey( "snd_impact", type ); + if( !snd || !snd[0] || !dict ) { + return -1; + } + + int length = 0; + StartSoundShader( declManager->FindSound(dict->GetString(snd), false), SND_CHANNEL_BODY, 0, true, &length ); + return length; +} + +/* +============== +hhProjectile::UpdateLight +============== +*/ +void hhProjectile::UpdateLight() { + // Attempt to remove light + if( lightStartTime != lightEndTime && gameLocal.GetTime() >= lightEndTime ) { + FreeLightDef(); + BecomeInactive(TH_MISC2); + } + + if( lightDefHandle != -1 ) { + UpdateLightPosition(); + UpdateLightFade(); + gameRenderWorld->UpdateLightDef( lightDefHandle, &renderLight ); + } +} + +/* +============== +hhProjectile::UpdateLightPosition +============== +*/ +void hhProjectile::UpdateLightPosition() { + renderLight.origin = GetOrigin() + GetAxis() * lightOffset; + renderLight.axis = GetAxis(); +} + +/* +============== +hhProjectile::UpdateLightFade +============== +*/ +void hhProjectile::UpdateLightFade() { + idVec3 color( vec3_zero ); + float frac = 0.0f; + + int time = gameLocal.GetTime(); + if( lightStartTime != lightEndTime && time < lightEndTime ) { + frac = MS2SEC(gameLocal.GetTime() - lightStartTime) / MS2SEC(lightEndTime - lightStartTime); + color.Lerp( lightColor, color, frac ); + + for( int ix = 0; ix < 3; ++ix ) { + renderLight.shaderParms[SHADERPARM_RED + ix] = color[ix]; + } + } +} + +/* +============== +hhProjectile::CreateLight +============== +*/ +int hhProjectile::CreateLight( const char* shaderName, const idVec3& size, const idVec3& color, const idVec3& offset, float fadeTime ) { + int fadeDuration = 0; + + if ( size.x <= 0.0f || !g_projectileLights.GetBool() ) { + return 0; + } + + if( size.Compare(vec3_zero, VECTOR_EPSILON) ) { + return 0; + } + + SIMDProcessor->Memset( &renderLight, 0, sizeof(renderLight) ); + FreeLightDef(); + + if( !shaderName || !shaderName[0] ) { + return 0; + } + + UpdateLightPosition(); + + renderLight.shader = declManager->FindMaterial( shaderName, false ); + renderLight.pointLight = true; + renderLight.lightRadius = size; + renderLight.shaderParms[SHADERPARM_RED] = color[0]; + renderLight.shaderParms[SHADERPARM_GREEN] = color[1]; + renderLight.shaderParms[SHADERPARM_BLUE] = color[2]; + renderLight.shaderParms[SHADERPARM_ALPHA] = 1.0f; + renderLight.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.GetTime() ); + renderLight.noShadows = true; //HUMANHEAD bjk: cheaper + + fadeDuration = SEC2MS( fadeTime ); + + lightOffset = offset; + lightColor = color; + lightStartTime = gameLocal.GetTime(); + lightEndTime = lightStartTime + fadeDuration; + + BecomeActive(TH_MISC2); + + if( lightDefHandle != -1 ) { + gameRenderWorld->UpdateLightDef( lightDefHandle, &renderLight ); + } else { + lightDefHandle = gameRenderWorld->AddLightDef( &renderLight ); + } + + return fadeDuration; +} + +/* +============== +hhProjectile::BounceSplat +============== +*/ +void hhProjectile::BounceSplat( const idVec3& origin, const idVec3& dir ) { + float size = hhMath::Lerp( spawnArgs.GetVec2("decal_bounce_size", "1.8 2.2"), gameLocal.random.RandomFloat() ); + const char* decal = spawnArgs.RandomPrefix( "mtr_bounce_shader", gameLocal.random ); + if( decal && *decal ) { + gameLocal.ProjectDecal( origin, dir, 16.0f, true, size, decal ); + } +} + + +/* +================ +hhProjectile::Create + +HUMANHEAD: AOBMERGE - Overridden +================ +*/ +void hhProjectile::Create( idEntity *owner, const idVec3 &start, const idVec3 &dir ) { + Create( owner, start, dir.ToMat3() ); +} + +/* +================ +hhProjectile::Launch + +HUMANHEAD: AOBMERGE - Overridden +================ +*/ +void hhProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, const float dmgPower ) { + Launch( start, dir.ToMat3(), pushVelocity, timeSinceFire, launchPower, dmgPower ); + + launchTimestamp = gameLocal.time; //HUMANHEAD: mdc - record launch time +} + +/* +================ +hhProjectile::Create + +HUMANHEAD: AOBMERGE - Overridden +================ +*/ +void hhProjectile::Create( idEntity *owner, const idVec3 &start, const idMat3 &axis ) { + Unbind(); + + SetOrigin( start ); + SetAxis( axis ); + + this->owner = owner; + + CreateLight( spawnArgs.GetString("mtr_light_shader"), spawnArgs.GetVector("light_size"), spawnArgs.GetVector("light_color", "1 1 1") * spawnArgs.GetFloat("light_intensity", "1.0"), spawnArgs.GetVector("light_offset"), spawnArgs.GetFloat("light_fadetime") ); + + GetPhysics()->SetContents( 0 ); + + state = CREATED; +} + +/* +================= +hhProjectile::Launch + +HUMANHEAD: aob - made second parameter idMat3& +================= +*/ +void hhProjectile::Launch( const idVec3 &start, const idMat3 &axis, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, const float dmgPower ) { + float fuse; + idVec3 velocity; + idAngles angular_velocity; + float linear_friction; + float angular_friction; + float contact_friction; + float bounce; + float mass; + float gravity; + idVec3 gravVec; + + spawnArgs.GetVector( "velocity", "0 0 0", velocity ); + spawnArgs.GetAngles( "angular_velocity", "0 0 0", angular_velocity ); + + 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" ); + + 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.isLarge = spawnArgs.GetBool( "largeProjectile" ); // HUMANHEAD bjk + + if ( mass <= 0 ) { + gameLocal.Error( "Invalid mass on '%s'\n", GetClassname() ); + } + + thrust = mass * spawnArgs.GetFloat( "thrust" ); + thrust_start = SEC2MS( spawnArgs.GetFloat("thrust_start") ) + gameLocal.GetTime(); + thrust_end = SEC2MS( spawnArgs.GetFloat("thrust_duration") ) + thrust_start; + //HUMANHEAD: aob - if thrust is set then set TH_THINK + if( hhMath::Fabs(thrust) >= VECTOR_EPSILON ) { + BecomeActive( TH_THINK ); + } + //HUMANHEAD END + + if ( health ) { + fl.takedamage = true; + } + + gravVec = gameLocal.GetGravity(); + gravVec.NormalizeFast(); + + Unbind(); + + int contents = DetermineContents(); + int clipMask = DetermineClipmask(); + + //HUMANHEAD rww + launchQuat = axis.ToCQuat(); //save off launch orientation + launchPos = start; //save off launch pos + //HUMANHEAD END + + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + //HUMANHEAD: aob - added DetermineClipModelOwner so some projectiles can decide to collide with there owners + physicsObj.GetClipModel()->SetOwner( DetermineClipModelOwner() ); + //HUMANHEAD END + physicsObj.SetMass( mass ); + physicsObj.SetFriction( linear_friction, angular_friction, contact_friction ); + if ( contact_friction == 0.0f ) { + physicsObj.NoContact(); + } + physicsObj.SetBouncyness( bounce ); + physicsObj.SetGravity( gravVec * gravity ); + physicsObj.SetContents( contents ); + physicsObj.SetClipMask( clipMask ); + physicsObj.SetLinearVelocity( axis[ 0 ] * velocity[ 0 ] + axis[ 1 ] * velocity[ 1 ] + axis[ 2 ] * velocity[ 2 ] + pushVelocity ); + /* + if (gameLocal.isClient) { + gameLocal.Printf("C: (%f %f %f) (%f %f %f) (%f %f %f, %f %f %f, %f %f %f)\n", velocity[0], velocity[1], velocity[2], + pushVelocity[0], pushVelocity[1], pushVelocity[2], axis[0][0], axis[0][1], axis[0][2], axis[1][0], axis[1][1], + axis[1][2], axis[2][0], axis[2][1], axis[2][2]); + } + else { + gameLocal.Printf("S: (%f %f %f) (%f %f %f) (%f %f %f, %f %f %f, %f %f %f)\n", velocity[0], velocity[1], velocity[2], + pushVelocity[0], pushVelocity[1], pushVelocity[2], axis[0][0], axis[0][1], axis[0][2], axis[1][0], axis[1][1], + axis[1][2], axis[2][0], axis[2][1], axis[2][2]); + } + */ + + physicsObj.SetAngularVelocity( angular_velocity.ToAngularVelocity() * axis ); + physicsObj.SetOrigin( start ); + physicsObj.SetAxis( axis ); + SetPhysics( &physicsObj ); + + //HUMANHEAD rww - get rid of the thruster, needless projectile constructor overhead + //thruster.SetPosition( &physicsObj, 0, physicsObj.GetCenterOfMass() );//idVec3( GetPhysics()->GetBounds()[ 0 ].x, 0, 0 ) ); + + //HUMANHEAD rww - debug projectile position and axis (for client side projectiles) + /* + extern void Debug_ClearDebugLines(void); + extern void Debug_AddDebugLine(idVec3 &start, idVec3 &end, int color); + + idVec3 p = start; + idVec3 prj; + Debug_ClearDebugLines(); + prj = p + (axis[0]*32.0f); + Debug_AddDebugLine(p, prj, 1); + prj = p + (axis[1]*32.0f); + Debug_AddDebugLine(p, prj, 2); + prj = p + (axis[2]*32.0f); + Debug_AddDebugLine(p, prj, 3); + */ + + if ( !gameLocal.isClient || fl.clientEntity ) //HUMANHEAD rww - if clientEntity + { + 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 ); + } + } + + StartSound( "snd_fly", SND_CHANNEL_BODY, 0, true ); + + //HUMANHEAD: aob - replaces id's smoke_fly code + // CJR: Changed so that we can randomly choose between multiple fx_fly systems on a single projectile + BroadcastFxPrefixedRandom( "fx_fly", EV_SpawnFxFlyLocal ); + //HUMANHEAD END + + // 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; + } + + // HUMANHEAD CJR: if launched by a player, set the projectile's weapon index appropriately + if ( owner.IsValid() && owner->IsType( hhPlayer::Type ) ) { + hhPlayer *player = static_cast( owner.GetEntity() ); + weaponNum = player->GetCurrentWeapon(); + } else { // Otherwise, default the weaponNum to denote a non-player weapon + weaponNum = -1; + }// HUMANHEAD END + + state = LAUNCHED; + + if (gameLocal.isClient) { //HUMANHEAD rww + launchTimestamp = gameLocal.time; //HUMANHEAD: mdc - record launch time + return; + } + + // Notify the AI about this launch + gameLocal.SendMessageAI( this, GetOrigin(), spawnArgs.GetFloat("ai_notify_launch", "0"), MA_OnProjectileLaunch ); + BroadcastEntityDef( spawnArgs.GetString("def_driver"), EV_SpawnDriverLocal ); + + launchTimestamp = gameLocal.time; //HUMANHEAD: mdc - record launch time +} + +/* +================ +hhProjectile::DetermineClipModelOwner + +HUMANHEAD: aob +================ +*/ +idEntity* hhProjectile::DetermineClipModelOwner() { + return (spawnArgs.GetBool("collideWithOwner")) ? this : owner.GetEntity(); +} + +/* +================ +hhProjectile::Collide +================ +*/ +extern const int RB_VELOCITY_EXPONENT_BITS; //HUMANHEAD rww +extern const int RB_VELOCITY_MANTISSA_BITS; //HUMANHEAD rww +bool hhProjectile::Collide( const trace_t& collision, const idVec3& velocity ) { + if ( state == EXPLODED || state == FIZZLED || state == COLLIDED ) { //HUMANHEAD bjk + return false; + } + + // HUMANHEAD CJR: Don't allow the collision if the projectile has recently hit a portal + if ( collidedPortal.IsValid() ) { + return false; + } // HUMANHEAD END + + if ( gameLocal.isClient ) { + //HUMANHEAD rww - our projectile stuff is pretty different from id's at this point, so i'm just making some new prediction code. + if (state == EXPLODED || state == FIZZLED || state == COLLIDED) { //HUMANHEAD bjk + ProcessCollision(&collision, velocity); + return false; + } + //HUMANHEAD END + } + + //HUMANHEAD rww - commented out to allow projectiles to collide with owner after portalling + /* + if (gameLocal.isClient) { //HUMANHEAD rww + idEntity* entHit = gameLocal.GetTraceEntity(collision); + if ( entHit == owner.GetEntity() ) { + return true; + } + } + else { + // get the entity the projectile collided with + idEntity* entHit = gameLocal.GetTraceEntity(collision); + if ( entHit == owner.GetEntity() ) { + assert( 0 ); + return false; + } + } + */ + + // remove projectile when a 'noimpact' surface is hit + if ( collision.c.material && ( collision.c.material->GetSurfaceFlags() & SURF_NOIMPACT ) ) { + common->DPrintf("removing projectile that hit noimpact surface: mat=[%s]\n", collision.c.material->GetName() ); + RemoveProjectile( 0 ); + return false; + } + + //HUMANHEAD rww - send events for collisions on the server for pseudo-sync'd projectiles + if (gameLocal.isServer && fl.networkSync && !netSyncPhysics) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + + msg.WriteBits(collision.c.contents, 32); + msg.WriteFloat(collision.c.dist); + msg.WriteBits(collision.c.entityNum, GENTITYNUM_BITS); + msg.WriteShort(collision.c.id); + if (!collision.c.material) { + msg.WriteShort(-1); + } + else { + msg.WriteShort(collision.c.material->Index()); + } + msg.WriteBits(collision.c.modelFeature, 32); + //ensure it is normalized properly first + idVec3 normal = collision.c.normal; + normal.Normalize(); + msg.WriteDir(normal, 24); + msg.WriteFloat(collision.c.point.x); + msg.WriteFloat(collision.c.point.y); + msg.WriteFloat(collision.c.point.z); + msg.WriteBits(collision.c.trmFeature, 32); + msg.WriteBits(collision.c.type, 4); + msg.WriteFloat(collision.fraction); + + //unfortunately, this is needed for proper decal projections + msg.WriteFloat(velocity.x, RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS); + msg.WriteFloat(velocity.y, RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS); + msg.WriteFloat(velocity.z, RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS); + + msg.WriteDir(collision.endAxis[0], 24); + + ServerSendPVSEvent(EVENT_PROJECTILE_EXPLOSION, &msg, collision.endpos); + } + //HUMANHEAD END + + //HUMANHEAD rww - our projectile stuff is pretty different from id's at this point, so i'm just making some new prediction code. + if (gameLocal.isClient) { + //ProcessCollision(&collision, velocity); + if (fl.networkSync && !netSyncPhysics) { //if this projectile is psuedo-sync'd, we will be expecting impact results from the server. + ClientHideProjectile(); + return 0; + } + return ProcessCollisionEvent( &collision, velocity ); + } + //HUMANHEAD END + + if ( !gameLocal.isMultiplayer && spawnArgs.GetString( "hit_notify" ) && owner.IsValid() && owner->IsType( hhMonsterAI::Type ) ) { + gameLocal.SendMessageAI( this, GetOrigin(), spawnArgs.GetFloat("ai_notify_yes", "0"), MA_OnProjectileHit ); + } + + return ProcessCollisionEvent( &collision, velocity ); +} + +/* +================ +hhProjectile::ProcessCollisionEvent +================ +*/ +bool hhProjectile::ProcessCollisionEvent( const trace_t* collision, const idVec3& velocity ) { + assert( collision ); + + idEntity* ent = gameLocal.entities[ collision->c.entityNum ]; + const idEventDef* eventDef = matterEventsCollision.GetPartner( ent, collision->c.material ); + assert( eventDef ); + + ProcessEvent( eventDef, collision, velocity ); + return gameLocal.program.GetReturnedBool(); +} + +/* +================ +hhProjectile::ProcessCollision + +HUMANHEAD: aob +================ +*/ +int hhProjectile::ProcessCollision( const trace_t* collision, const idVec3& velocity ) { + PROFILE_SCOPE("ProjectileCollision", PROFMASK_PHYSICS|PROFMASK_COMBAT); + idEntity* entHit = gameLocal.entities[ collision->c.entityNum ]; + + SetOrigin( collision->endpos ); + SetAxis( collision->endAxis ); + + if( fxFly.IsValid() ) { + fxFly->Stop(); + } + SAFE_REMOVE( fxFly ); + FreeLightDef(); + CancelEvents( &EV_Fizzle ); + + if (entHit) { //rww - may be null on client. + if (!gameLocal.isClient || (fl.networkSync && !netSyncPhysics)) { //don't do this on the client, unless this is a sync'd projectile + DamageEntityHit( collision, velocity, entHit ); + } + } + + fl.takedamage = false; + physicsObj.SetContents( 0 ); + physicsObj.PutToRest(); + + surfTypes_t matterType = gameLocal.GetMatterType( entHit, collision->c.material, "hhProjectile::ProcessCollision" ); + return PlayImpactSound( gameLocal.FindEntityDefDict(spawnArgs.GetString("def_damage")), collision->endpos, matterType ); +} + +/* +================ +hhProjectile::DamageEntityHit + +HUMANHEAD: aob +================ +*/ +void hhProjectile::DamageEntityHit( const trace_t* collision, const idVec3& velocity, idEntity* entHit ) { + PROFILE_SCOPE("DamageEntityHit", PROFMASK_COMBAT); + if (GERMAN_VERSION || g_nogore.GetBool()) { + if (entHit->IsType(idActor::Type)) { + idActor *actor = reinterpret_cast (entHit); + if ( !actor->fl.takedamage || ( actor->IsActiveAF() && !actor->spawnArgs.GetBool( "not_gory", "0" ) ) ) { + // Don't process hits on ragdolls + return; + } + } + } + + float push = 0.0f; + float damageScale = 1.0f; + const char *damage = NULL; + if (gameLocal.isMultiplayer) { //rww - check for special mp damage def + damage = spawnArgs.GetString( "def_damage_mp" ); + } + if (!damage || !damage[0]) { + damage = spawnArgs.GetString( "def_damage" ); + } + hhPlayer* playerHit = (entHit->IsType(hhPlayer::Type)) ? static_cast(entHit) : NULL; + idAFEntity_Base* afHit = (entHit->IsType(idAFEntity_Base::Type)) ? static_cast(entHit) : NULL; + + idVec3 dir = velocity.ToNormal(); + + // non-radius damage defs can also apply an additional impulse to the rigid body physics impulse + const idDeclEntityDef *def = gameLocal.FindEntityDef( damage, false ); + if ( def ) { + if (entHit->IsType(hhProjectile::Type)) { + push = 0.0f; // mdl: Don't let projectiles push each other + } else if (afHit && afHit->IsActiveAF() ) { + push = def->dict.GetFloat( "push_ragdoll" ); + } else { + push = def->dict.GetFloat( "push" ); + } + } + + if (!gameLocal.isClient) { //rww + if( playerHit ) { + // pdm: save collision location in case we want to project a blob there + playerHit->playerView.SetDamageLoc( collision->endpos ); + } + + if( DamageIsValid(collision, damageScale) && entHit->fl.takedamage ) { + UpdateBalanceInfo( collision, entHit ); + + if( damage && damage[0] ) { + idEntity *killer = owner.GetEntity(); + if (killer && killer->IsType(hhVehicle::Type)) { //rww - handle vehicle projectiles killing people + hhVehicle *veh = static_cast(killer); + if (veh->GetPilot()) { + killer = veh->GetPilot(); + } + } + + entHit->Damage( this, killer, dir, damage, damageScale, CLIPMODEL_ID_TO_JOINT_HANDLE(collision->c.id) ); + + if ( playerHit && def->dict.GetInt( "freeze_duration" ) > 0 ) { + playerHit->Freeze( def->dict.GetInt( "freeze_duration" ) ); + } + } + } + + // HUMANHEAD bjk: moved to after damage so impulse can be applied to ragdoll + if ( push > 0.0f ) { + if (g_debugImpulse.GetBool()) { + gameRenderWorld->DebugArrow(colorYellow, collision->c.point, collision->c.point + (push*dir), 25, 2000); + } + + entHit->ApplyImpulse( this, collision->c.id, collision->c.point, push * dir ); + } + } + + if ( entHit->fl.applyDamageEffects ) { + ApplyDamageEffect( entHit, collision, velocity, damage ); + } +} + +/* +================ +hhProjectile::DamageIsValid + +HUMANHEAD: aob +================ +*/ +bool hhProjectile::DamageIsValid( const trace_t* collision, float& damageScale ) { + damageScale = DetermineDamageScale( collision ); + + return true; +} + +/* +================ +hhProjectile::ApplyDamageEffect + +HUMANHEAD: aob +================ +*/ +void hhProjectile::ApplyDamageEffect( idEntity* hitEnt, const trace_t* collision, const idVec3& velocity, const char* damageDefName ) { + if( hitEnt ) { + hitEnt->AddDamageEffect( *collision, velocity, damageDefName, (!fl.networkSync || netSyncPhysics) ); + } +} + +int hhProjectile::DetermineContents() { + return (spawnArgs.GetBool("proj_collision")) ? CONTENTS_PROJECTILE | CONTENTS_SHOOTABLE : CONTENTS_PROJECTILE; +} + +int hhProjectile::DetermineClipmask() { + return MASK_SHOT_RENDERMODEL; +} + +/* +================ +hhProjectile::UpdateBalanceInfo + +HUMANHEAD: aob +================ +*/ +void hhProjectile::UpdateBalanceInfo( const trace_t* collision, const idEntity* hitEnt ) { + hhPlayer* player = (owner.IsValid() && owner->IsType(hhPlayer::Type)) ? static_cast(owner.GetEntity()) : NULL; + + if( hitEnt->IsType(idActor::Type) && player ) { + player->SetLastHitTime( gameLocal.GetTime() ); + player->AddProjectileHits( 1 ); + } +} + +/* +================ +hhProjectile::SpawnProjectile + +HUMANHEAD: aob +================ +*/ +hhProjectile* hhProjectile::SpawnProjectile( const idDict* args ) {//FIXME: Broadcast + assert( args ); + assert(!gameLocal.isClient); + + idEntity* ent = NULL; + + gameLocal.SpawnEntityDef( *args, &ent ); + HH_ASSERT( ent && ent->IsType(hhProjectile::Type) ); + + return static_cast(ent); +} + +/* +================ +hhProjectile::SpawnClientProjectile + +HUMANHEAD: rww +================ +*/ +hhProjectile* hhProjectile::SpawnClientProjectile( const idDict* args ) {//FIXME: Broadcast + assert( args ); + + idEntity* ent = NULL; + + gameLocal.SpawnEntityDef( *args, &ent, true, gameLocal.isClient ); + HH_ASSERT( ent && ent->IsType(hhProjectile::Type) ); + + ent->fl.networkSync = false; + ent->fl.clientEvents = true; + + return static_cast(ent); +} + +/* +================ +hhProjectile::Killed +================ +*/ +void hhProjectile::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + //HUMANHEAD: aob - added collision so we can get explosion to show up when killed + trace_t collision; + + if ( spawnArgs.GetBool( "detonate_on_death" ) ) { + memset( &collision, 0, sizeof( trace_t ) ); + collision.fraction = 0.0f; + collision.endpos = GetOrigin(); + collision.endAxis = GetAxis(); + collision.c.entityNum = attacker->entityNumber; + collision.c.normal = inflictor->GetOrigin() - GetOrigin(); + collision.c.normal.Normalize(); + Explode( &collision, GetPhysics()->GetLinearVelocity(), 0 ); + } else { + Fizzle(); + } +} + +/* +================ +hhProjectile::Fizzle +================ +*/ +void hhProjectile::Fizzle( void ) { + if ( state == EXPLODED || state == FIZZLED || state == COLLIDED ) { //HUMANHEAD bjk + return; + } + + int removeTime = StartSound( "snd_fizzle", SND_CHANNEL_BODY, 0, true ); + + if (!gameLocal.isClient) + { + // fizzle FX + hhFxInfo fxInfo; + fxInfo.SetNormal( -GetAxis()[0] ); + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfoPrefixed( "fx_fuse", GetOrigin(), GetAxis(), &fxInfo ); + } + + SAFE_REMOVE( fxFly ); + + fl.takedamage = false; + physicsObj.SetContents( 0 ); + physicsObj.GetClipModel()->Unlink(); + physicsObj.PutToRest(); + + FreeLightDef(); + + state = FIZZLED; + + RemoveProjectile( removeTime ); +} + +/* +================= +hhProjectile::RemoveProjectile +================= +*/ +void hhProjectile::RemoveProjectile( const int removeDelay ) { + Hide(); + BecomeInactive( TH_TICKER|TH_THINK ); + RemoveBinds();//Remove any fx we have because they aren't hidden + PostEventMS( &EV_Remove, removeDelay ); +} + +/* +================ +hhProjectile::Explode +================ +*/ +void hhProjectile::Explode( const trace_t* collision, const idVec3& velocity, int removeDelay ) { + const char *fxname = NULL; + int length = 0; + + if ( state == EXPLODED || state == FIZZLED || state == COLLIDED ) { //HUMANHEAD bjk + return; + } + + if( !collision ) { + return; + } + + //HUMANHEAD: aob + StartSound( "snd_explode", SND_CHANNEL_BODY, 0, true, &length ); + removeDelay = hhMath::hhMax( length, removeDelay ); + + // explosion light + //FIXME: may need to be Broadcast + removeDelay = hhMath::hhMax( removeDelay, CreateLight(spawnArgs.GetString("mtr_explode_light_shader"), spawnArgs.GetVector("explode_light_size"), spawnArgs.GetVector("explode_light_color", "1 1 1") * spawnArgs.GetFloat("explode_light_intensity", "1.0"), spawnArgs.GetVector("explode_light_offset"), spawnArgs.GetFloat("explode_light_fadetime")) ); + + //HUMANHEAD: aob - moved logic to helper function + SpawnExplosionFx( collision ); + + SpawnDebris( collision->c.normal, velocity.ToNormal() ); + //HUMANHEAD END + + state = EXPLODED; + + //HUMANHEAD rww + if (!gameLocal.isClient) { + idEntity *killer = owner.GetEntity(); + if (killer && killer->IsType(hhVehicle::Type)) { //rww - handle vehicle projectiles killing people + hhVehicle *veh = static_cast(killer); + if (veh->GetPilot()) { + killer = veh->GetPilot(); + } + } + + // splash damage + if (!gameLocal.isClient) { + //SplashDamage( collision->endpos, killer, this, this, spawnArgs.GetString("def_splash_damage") ); + SplashDamage( GetOrigin(), killer, this, this, spawnArgs.GetString("def_splash_damage") ); + } + } + //HUMANHEAD END + + //HUMANHEAD: aob - moved logic to helper function + RemoveProjectile( removeDelay ); + //HUMANHEAD END +} + +/* +================ +hhProjectile::SplashDamage + +HUMANHEAD: aob +================ +*/ +void hhProjectile::SplashDamage( const idVec3& origin, idEntity* attacker, idEntity* ignoreDamage, idEntity* ignorePush, const char* splashDamageDefName ) { + if( splashDamageDefName && splashDamageDefName[0] ) { + gameLocal.RadiusDamage( origin, this, attacker, ignoreDamage, ignorePush, splashDamageDefName ); + } +} + +/* +================ +hhProjectile::SpawnExplosionFx + +HUMANHEAD: aob +================ +*/ +void hhProjectile::SpawnExplosionFx( const trace_t* collision ) { + idEntity* hitEnt = NULL; + hhFxInfo fxInfo; + const idDict* dict = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_detonateFx"), false ); + + if( !dict || !collision || collision->fraction >= 1.0f ) { + return; + } + + hitEnt = gameLocal.entities[ collision->c.entityNum ]; + + fxInfo.SetNormal( collision->c.normal ); + fxInfo.SetIncomingVector( GetAxis()[0] ); + fxInfo.SetBounceVector( hhProjectile::GetBounceDirection( physicsObj.GetLinearVelocity(), + collision->c.normal, + this, hitEnt + ) ); + fxInfo.RemoveWhenDone( true ); + surfTypes_t matterType = gameLocal.GetMatterType( hitEnt, collision->c.material, "hhProjectile::SpawnExplosionFx" ); + const char* fxKey = gameLocal.MatterTypeToMatterKey( "fx", matterType ); + BroadcastFxInfo( dict->RandomPrefix(fxKey, gameLocal.random), GetOrigin(), GetAxis(), &fxInfo, NULL, false ); //rww - no broadcast, only locally. + //FIXME: Once BroadcastFxInfo() is gone and we are spawning the entityfx directly, mark it as fl.neverdormant here +} + +/* +================ +hhProjectile::SpawnDebris + +HUMANHEAD: aob +================ +*/ +void hhProjectile::SpawnDebris( const idVec3& collisionNormal, const idVec3& collisionDir ) { + static char debrisKey[] = "def_debris"; + static char countKey[] = "debris_count"; + static char spreadKey[] = "debris_spread"; + + idDebris *debris = NULL; + idEntity *ent = NULL; + idStr indexStr; + int amount = 0; + const idDict *dict = NULL; + const idKeyValue* defKV = NULL; + for( defKV = spawnArgs.MatchPrefix(debrisKey, NULL); defKV; defKV = spawnArgs.MatchPrefix(debrisKey, defKV) ) { + + if( !defKV->GetValue().Length() ) { + continue; + } + + dict = gameLocal.FindEntityDefDict( defKV->GetValue().c_str(), false ); + if( !dict ) { + continue; + } + + indexStr = defKV->GetKey(); + indexStr.Strip( debrisKey ); + + if (gameLocal.isMultiplayer) { //rww - for decreasing the count for things in mp on a design basis + amount = hhMath::Lerp( spawnArgs.GetVec2(va("%s_mp%s", countKey, indexStr.c_str())), gameLocal.random.RandomFloat() ); + if (!amount) { + amount = hhMath::Lerp( spawnArgs.GetVec2(va("%s%s", countKey, indexStr.c_str())), gameLocal.random.RandomFloat() ); + } + } + else { + amount = hhMath::Lerp( spawnArgs.GetVec2(va("%s%s", countKey, indexStr.c_str())), gameLocal.random.RandomFloat() ); + } + for ( int i = 0; i < amount; i++ ) { + //HUMANHEAD: aob + idVec3 dir = hhUtils::RandomSpreadDir( collisionNormal.ToMat3(), DEG2RAD(spawnArgs.GetFloat(va("%s%s", spreadKey, indexStr.c_str()))) ); + //HUMAMHEAD END + + gameLocal.SpawnEntityDef( *dict, &ent, true, gameLocal.isClient ); //HUMANHEAD rww - make them local non-broadcast entities. + if ( !ent || !ent->IsType( idDebris::Type ) ) { + gameLocal.Error( "hhProjectile: 'projectile_debris' is not an idDebris" ); + } + + debris = static_cast(ent); + debris->Create( owner.GetEntity(), GetOrigin() + collisionNormal*10, dir.ToMat3() ); // HUMANHEAD bjk: displace out of surface + debris->Launch(); + debris->fl.networkSync = false; //HUMANHEAD rww + debris->fl.clientEvents = true; //HUMANHEAD rww + } + } +} + +/* +================ +hhProjectile::SetGravity +================ +*/ +void hhProjectile::SetGravity( const idVec3 &newGravity ) { + float relativeMagnitude = spawnArgs.GetFloat( "gravity" ); + idVec3 newGravityVector( vec3_zero ); + + if( GetGravity().Compare(newGravity, VECTOR_EPSILON) ) { + return; + } + + if( relativeMagnitude > 0.0f ) { + newGravityVector = newGravity; + relativeMagnitude *= newGravityVector.Normalize() / gameLocal.GetGravity().Length(); + newGravityVector *= relativeMagnitude; + } + + GetPhysics()->SetGravity( newGravityVector ); +} + +/* +================ +hhProjectile::ProcessAllowCollisionEvent +================ +*/ +bool hhProjectile::ProcessAllowCollisionEvent( const trace_t* collision ) { + assert( collision ); + + idEntity* ent = gameLocal.entities[ collision->c.entityNum ]; + const idEventDef* eventDef = matterEventsAllowCollision.GetPartner( ent, collision->c.material ); + assert( eventDef ); + + ProcessEvent( eventDef, collision ); + return gameLocal.program.GetReturnedBool(); +} + +//============================================================================= +// +// hhProjectile::Portalled +// +// The projectile was just portalled. Update the fx info to the bound fly fx system +// HUMANHEAD CJR +//============================================================================= + +void hhProjectile::Portalled(idEntity *portal) { + if ( fxFly.IsValid() ) { + hhFxInfo fxInfo; + fxInfo.SetNormal( -GetAxis()[0] ); + fxInfo.SetEntity( this ); + fxInfo.RemoveWhenDone( false ); + + fxFly->SetFxInfo( fxInfo ); + + // Reset the fx system + fxFly->Stop(); + fxFly->Start( gameLocal.time ); + } + if (physicsObj.GetClipModel()) { //HUMANHEAD rww - allow projectiles to collide with owner after portalling + physicsObj.GetClipModel()->SetOwner(this); + } +} // END HUMANHEAD + +//============================================================================= +// +// hhProjectile::AllowCollision +// +// Determines if the projectile can strike a given entity. Here, all +// projectiles will pass-through Wraiths (other than arrows, which is handled +// in the arrow code) +//============================================================================= +bool hhProjectile::AllowCollision( const trace_t& collision ) { + return ProcessAllowCollisionEvent( &collision ); +} + +/* +================ +hhProjectile::Event_Fuse_Explode +================ +*/ +void hhProjectile::Event_Fuse_Explode() { + trace_t collision; + + SIMDProcessor->Memset( &collision, 0, sizeof(trace_t) ); + collision.endpos = GetOrigin(); + collision.endAxis = GetAxis(); + collision.c.normal = -gameLocal.GetGravityNormal(); + Explode( &collision, GetPhysics()->GetLinearVelocity(), 0 ); +} + +/* +================ +hhProjectile::Event_Collision_Explode +================ +*/ +void hhProjectile::Event_Collision_Explode( const trace_t* collision, const idVec3& velocity ) { + Explode( collision, velocity, ProcessCollision(collision, velocity) ); + idThread::ReturnInt( 1 ); +} + +/* +================ +hhProjectile::Event_Collision_Impact +================ +*/ +void hhProjectile::Event_Collision_Impact( const trace_t* collision, const idVec3& velocity ) { + CancelEvents( &EV_Explode ); + RemoveProjectile( ProcessCollision(collision, velocity) ); + state = COLLIDED; + idThread::ReturnInt( 1 ); +} + +/* +================ +hhProjectile::Event_Collision_DisturbLiquid +================ +*/ +void hhProjectile::Event_Collision_DisturbLiquid( const trace_t* collision, const idVec3& velocity ) { + CancelEvents( &EV_Explode ); + RemoveProjectile( ProcessCollision(collision, velocity) ); + idThread::ReturnInt( 1 ); +} + +/* +================ +hhProjectile::Event_Collision_Remove +================ +*/ +void hhProjectile::Event_Collision_Remove( const trace_t* collision, const idVec3& velocity ) { + RemoveProjectile( 0 ); + idThread::ReturnInt( 1 ); +} + +/* +================ +hhProjectile::Event_AllowCollision_CollideNoProj +================ +*/ +void hhProjectile::Event_AllowCollision_CollideNoProj( const trace_t* collision ) { + idEntity* ent = gameLocal.entities[ collision->c.entityNum ]; + if( ent->IsType( hhProjectile::Type ) ) { + if ( static_cast(ent)->ProjCollide() ) { + idThread::ReturnInt( 1 ); + return; + } + } + + if( ent->IsType( hhProjectile::Type ) && // If we're colliding with another projectile + ( !bNoCollideWithCrawlers || // AND we're not set to collide with crawlers + !( ent->IsType( hhProjectileRocketLauncher::Type ) && // OR we're not a rocket launcher projectile + ent->IsType( hhProjectileCrawlerGrenade::Type ) ) ) ) { // AND we're not a crawler projectile + idThread::ReturnInt( 0 ); // Pass through + return; + } + + hhProjectile::Event_AllowCollision_Collide( collision ); +} + +/* +================ +hhProjectile::Event_AllowCollision_Collide +================ +*/ +void hhProjectile::Event_AllowCollision_Collide( const trace_t* collision ) { + idThread::ReturnInt( 1 ); +} + +/* +================ +hhProjectile::Event_AllowCollision_PassThru +================ +*/ +void hhProjectile::Event_AllowCollision_PassThru( const trace_t* collision ) { + idThread::ReturnInt( 0 ); +} + +/* +================ +hhProjectile::Event_SpawnDriverLocal + +HUMANHEAD: aob +================ +*/ +void hhProjectile::Event_SpawnDriverLocal( const char* defName ) { + if( !defName || !defName[ 0 ] ) { + return; + } + + driver = static_cast( gameLocal.SpawnObject(defName) ); + driver->SetPassenger( this ); + + //Not sure if this should be pulled out of the def_driver dict or not + float roll = hhMath::Lerp( spawnArgs.GetVec2("driver_rollRange"), gameLocal.random.RandomFloat() ); + driver->SetAxis( idAngles( 0.0f, 0.0f, roll ).ToMat3() * driver->GetAxis() ); +} + +/* +================ +hhProjectile::Event_SpawnFxFlyLocal + +HUMANHEAD: aob +================ +*/ +void hhProjectile::Event_SpawnFxFlyLocal( const char* defName ) { + if( !defName || !defName[0] ) { + return; + } + + SAFE_REMOVE(fxFly); + hhFxInfo fxInfo; + + fxInfo.SetNormal( -GetAxis()[0] ); + fxInfo.SetEntity( this ); + fxInfo.RemoveWhenDone( false ); + fxFly = SpawnFxLocal( defName, GetOrigin(), GetAxis(), &fxInfo, true ); //rww - client (local) entity. + if (fxFly.IsValid()) { + fxFly->fl.neverDormant = true; + + //rww + fxFly->fl.networkSync = false; + fxFly->fl.clientEvents = true; + } +} + +//================ +//hhProjectile::Save +//================ +void hhProjectile::Save( idSaveGame *savefile ) const { + driver.Save( savefile ); + fxFly.Save( savefile ); + savefile->WriteInt( thrust_start ); + savefile->WriteBool( bDDACounted ); + parentProjectile.Save( savefile ); + savefile->WriteInt( launchTimestamp ); + savefile->WriteInt( weaponNum ); + savefile->WriteBool( bPlayFlyBySound ); + savefile->WriteFloat( flyBySoundDistSq ); + + collidedPortal.Save( savefile ); + savefile->WriteVec3( collideLocation ); + savefile->WriteVec3( collideVelocity ); +} + +//================ +//hhProjectile::Restore +//================ +void hhProjectile::Restore( idRestoreGame *savefile ) { + driver.Restore( savefile ); + fxFly.Restore( savefile ); + savefile->ReadInt( thrust_start ); + savefile->ReadBool( bDDACounted ); + parentProjectile.Restore( savefile ); + savefile->ReadInt( launchTimestamp ); + savefile->ReadInt( weaponNum ); + savefile->ReadBool( bPlayFlyBySound ); + savefile->ReadFloat( flyBySoundDistSq ); + + bNoCollideWithCrawlers = spawnArgs.GetBool( "noCollideWithCrawlers", "0" ); + bProjCollide = spawnArgs.GetBool( "proj_collision", "0" ); + + collidedPortal.Restore( savefile ); + savefile->ReadVec3( collideLocation ); + savefile->ReadVec3( collideVelocity ); +} + +/* +================ +hhProjectile::WriteToSnapshot +================ +*/ +void hhProjectile::WriteToSnapshot( idBitMsgDelta &msg ) const { + //rww - we capture the launch orientation/pos for predicting the projectile launch, and (usually) don't sync physics at all + if (fabsf(launchQuat.ToAngles().roll) > 0.001f) { //is it going to translate to a direction happily, or do we need a real orientation? + msg.WriteBits(1, 1); + msg.WriteFloat(launchQuat.x); + msg.WriteFloat(launchQuat.y); + msg.WriteFloat(launchQuat.z); + } + else { + msg.WriteBits(0, 1); + msg.WriteDir(launchQuat.ToMat3()[0], 24); + } + msg.WriteFloat(launchPos.x); + msg.WriteFloat(launchPos.y); + msg.WriteFloat(launchPos.z); + + idProjectile::WriteToSnapshot(msg); +} + +/* +================ +hhProjectile::ReadFromSnapshot +================ +*/ +void hhProjectile::ReadFromSnapshot( const idBitMsgDelta &msg ) { + //rww - we capture the launch orientation for predicting the projectile launch, and (usually) don't sync physics at all + bool fullOrientation = !!msg.ReadBits(1); + if (fullOrientation) { + launchQuat.x = msg.ReadFloat(); + launchQuat.y = msg.ReadFloat(); + launchQuat.z = msg.ReadFloat(); + } + else { + idVec3 dir = msg.ReadDir(24); + launchQuat = dir.ToMat3().ToCQuat(); + } + launchPos.x = msg.ReadFloat(); + launchPos.y = msg.ReadFloat(); + launchPos.z = msg.ReadFloat(); + + idProjectile::ReadFromSnapshot(msg); +} + +/* +================ +hhProjectile::ClientHideProjectile +================ +*/ +void hhProjectile::ClientHideProjectile(void) { + Hide(); + FreeLightDef(); + BecomeInactive(TH_THINK|TH_PHYSICS); + + GetPhysics()->PutToRest(); + + if (fxFly.IsValid()) { + fxFly->Stop(); + SAFE_REMOVE( fxFly ); + } +} + +/* +=============== +hhProjectile::GetBounceDirection +=============== +*/ + +idVec3 hhProjectile::GetBounceDirection( const idVec3 &incoming, + const idVec3 &surface_normal, + const idEntity *incoming_entity, + const idEntity *surface_entity ) { + idVec3 bounceDir; + idVec3 tanget; + idVec3 normal; + float dot = 0.0f; + float eProjectile = 1.0f; // Elasticity constants + float eTarget = 1.0f; + float fProjectile = 0.0f; // Friction constants + float fTarget = 0.0f; + + if ( incoming_entity ) { + eProjectile = incoming_entity->spawnArgs.GetFloat("bounce", "1"); + fProjectile = incoming_entity->spawnArgs.GetFloat("contact_friction", "0"); + } + + if ( surface_entity ) { + eTarget = surface_entity->spawnArgs.GetFloat("bounce", "1"); + fTarget = surface_entity->spawnArgs.GetFloat("contact_friction", "0"); + } + + dot = incoming * surface_normal; + normal = surface_normal * dot; + tanget = incoming - normal; + + bounceDir = tanget * (1.0f - fProjectile) * (1.0f - fTarget) - + normal * (eProjectile * eTarget); + + //? Should this all be seperated out? Ie, what if they don't want it normalized? + if ( bounceDir.Length() < .0001f ) { + bounceDir = surface_normal; + } + + //HUMANHEAD: nla/aob - removed normalize to give return value more dependence on inputs. + + return( bounceDir ); +} + diff --git a/src/Prey/prey_projectile.h b/src/Prey/prey_projectile.h new file mode 100644 index 0000000..a5b0421 --- /dev/null +++ b/src/Prey/prey_projectile.h @@ -0,0 +1,176 @@ +#ifndef __PREY_PROJECTILE_H__ +#define __PREY_PROJECTILE_H__ + +class hhBeamSystem; + +extern const idEventDef EV_SpawnDriverLocal; +extern const idEventDef EV_SpawnFxFlyLocal; + +extern const idEventDef EV_Collision_Flesh; +extern const idEventDef EV_Collision_Metal; +extern const idEventDef EV_Collision_AltMetal; +extern const idEventDef EV_Collision_Wood; +extern const idEventDef EV_Collision_Stone; +extern const idEventDef EV_Collision_Glass; +extern const idEventDef EV_Collision_Liquid; +extern const idEventDef EV_Collision_Spirit; +extern const idEventDef EV_Collision_Remove; +extern const idEventDef EV_Collision_CardBoard; +extern const idEventDef EV_Collision_Tile; +extern const idEventDef EV_Collision_Forcefield; +extern const idEventDef EV_Collision_Wallwalk; +extern const idEventDef EV_Collision_Chaff; +extern const idEventDef EV_Collision_Pipe; + + +extern const idEventDef EV_AllowCollision_Flesh; +extern const idEventDef EV_AllowCollision_Metal; +extern const idEventDef EV_AllowCollision_AltMetal; +extern const idEventDef EV_AllowCollision_Wood; +extern const idEventDef EV_AllowCollision_Stone; +extern const idEventDef EV_AllowCollision_Glass; +extern const idEventDef EV_AllowCollision_Liquid; +extern const idEventDef EV_AllowCollision_Spirit; +extern const idEventDef EV_AllowCollision_CardBoard; +extern const idEventDef EV_AllowCollision_Tile; +extern const idEventDef EV_AllowCollision_Forcefield; +extern const idEventDef EV_AllowCollision_Wallwalk; +extern const idEventDef EV_AllowCollision_Chaff; +extern const idEventDef EV_AllowCollision_Pipe; + + +/*********************************************************************** + + hhProjectile + +***********************************************************************/ +class hhPortal; // cjr + +class hhProjectile : public idProjectile { + CLASS_PROTOTYPE( hhProjectile ); + + public: + void Spawn(); + virtual ~hhProjectile(); + virtual void Create( idEntity *owner, const idVec3 &start, const idVec3 &dir ); + virtual void Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire = 0.0f, const float launchPower = 1.0f, const float dmgPower = 1.0f ); + + virtual void Create( idEntity *owner, const idVec3 &start, const idMat3 &axis ); + virtual void Launch( const idVec3 &start, const idMat3 &axis, const idVec3 &pushVelocity, const float timeSinceFire = 0.0f, const float launchPower = 1.0f, const float dmgPower = 1.0f ); + + virtual void SetOrigin( const idVec3& origin ); + virtual void SetAxis( const idMat3& axis ); + + virtual void Think(); + + virtual void RemoveProjectile( const int removeDelay ); + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual bool ProcessCollisionEvent( const trace_t* collision, const idVec3& velocity ); + virtual void Explode( const trace_t* collision, const idVec3& velocity, int removeDelay ); + virtual void SplashDamage( const idVec3& origin, idEntity* attacker, idEntity* ignoreDamage, idEntity* ignorePush, const char* splashDamageDefName ); + virtual void BounceSplat( const idVec3& origin, const idVec3& dir ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void Fizzle( void ); + + virtual void SetGravity( const idVec3 &newGravity ); + virtual const idVec3& GetGravity() const { return idEntity::GetGravity(); } + + virtual bool GetDDACounted() const { return bDDACounted; } + virtual void SetDDACounted() { bDDACounted = true; } + virtual void SetParentProjectile( hhProjectile* in_parent ); + virtual hhProjectile* GetParentProjectile( void ); + virtual int GetLaunchTimestamp() const { return launchTimestamp; } + + virtual void Portalled(idEntity *portal); + virtual bool AllowCollision( const trace_t &collision ); + + virtual void UpdateBalanceInfo( const trace_t* collision, const idEntity* hitEnt ); + + static hhProjectile* SpawnProjectile( const idDict* args ); + static hhProjectile* SpawnClientProjectile( const idDict* args ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientHideProjectile(void); + + virtual const int GetWeaponNum( void ) const { return weaponNum; } // HUMANHEAD CJR: weapon-specific index on each projectile + bool ProjCollide() { return bProjCollide; } + + virtual int ProcessCollision( const trace_t* collision, const idVec3& velocity ); //rww - made public + + void SetCollidedPortal( hhPortal *newPortal, idVec3 newLocation, idVec3 newVelocity ); // cjr + + static idVec3 GetBounceDirection( const idVec3 &incoming_vector, + const idVec3 &surface_normal, + const idEntity *incoming_entity = NULL, + const idEntity *surface_entity = NULL ); + + idCQuat launchQuat; //rww - for saving off launch orientation + idVec3 launchPos; //rww - for saving off launch pos + + protected: + virtual float DetermineDamageScale( const trace_t* collision ) const { return 1.0f; } + virtual bool DamageIsValid( const trace_t* collision, float& damageScale ); + virtual int DetermineContents(); + virtual int DetermineClipmask(); + + virtual idEntity* DetermineClipModelOwner(); + + virtual int PlayImpactSound( const idDict* dict, const idVec3 &origin, surfTypes_t type ); + + virtual void UpdateLight(); + virtual void UpdateLightPosition(); + virtual void UpdateLightFade(); + virtual int CreateLight( const char* shaderName, const idVec3& size, const idVec3& color, const idVec3& offset, float fadeTime ); + + virtual void ApplyDamageEffect( idEntity* entHit, const trace_t* collision, const idVec3& velocity, const char* damageDefName ); + + virtual void SpawnExplosionFx( const trace_t* collision ); + virtual void SpawnDebris( const idVec3& collisionNormal, const idVec3& collisionDir ); + + virtual void DamageEntityHit( const trace_t* collision, const idVec3& velocity, idEntity* entHit ); + + virtual bool ProcessAllowCollisionEvent( const trace_t* collision ); + + protected: + void Event_Collision_Explode( const trace_t* collision, const idVec3& velocity ); + void Event_Collision_Impact( const trace_t* collision, const idVec3& velocity ); + void Event_Collision_DisturbLiquid( const trace_t* collision, const idVec3& velocity ); + void Event_Collision_Remove( const trace_t* collision, const idVec3& velocity ); + void Event_AllowCollision_CollideNoProj( const trace_t* collision ); + + void Event_AllowCollision_Collide( const trace_t* collision ); + void Event_AllowCollision_PassThru( const trace_t* collision ); + + void Event_Fuse_Explode(); + + void Event_SpawnDriverLocal( const char* defName ); + void Event_SpawnFxFlyLocal( const char* defName ); + + + protected: + idEntityPtr driver; + idEntityPtr fxFly; + int thrust_start; + bool bDDACounted; //already counted by dda as hitting something + idEntityPtr parentProjectile; //projectile that spawned me + int launchTimestamp; + + int weaponNum; // cjr - weapon index that spawned this projectile (-1) for non-player weapons + + bool bNoCollideWithCrawlers; // mdl: Defines whether or not we collide with crawlers/rockets + bool bProjCollide; + + // jsh flyby sounds + float flyBySoundDistSq; + bool bPlayFlyBySound; + + idEntityPtr collidedPortal; // cjr: This projectile struck a portal, so it should get portalled before thinking + idVec3 collideLocation; // cjr: This projectile struck a portal, so it should get portalled before thinking + idVec3 collideVelocity; // cjr: This projectile struck a portal, so it should get portalled before thinking +}; + +#endif \ No newline at end of file diff --git a/src/Prey/prey_projectileautocannon.cpp b/src/Prey/prey_projectileautocannon.cpp new file mode 100644 index 0000000..0ab2401 --- /dev/null +++ b/src/Prey/prey_projectileautocannon.cpp @@ -0,0 +1,21 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION( hhProjectile, hhProjectileAutoCannonGrenade ) + EVENT( EV_Collision_Flesh, hhProjectileAutoCannonGrenade::Event_Collision_Explode ) + EVENT( EV_Collision_Metal, hhProjectileAutoCannonGrenade::Event_Collision_Explode ) + EVENT( EV_Collision_AltMetal, hhProjectileAutoCannonGrenade::Event_Collision_Explode ) + EVENT( EV_Collision_Wood, hhProjectileAutoCannonGrenade::Event_Collision_Explode ) + EVENT( EV_Collision_Stone, hhProjectileAutoCannonGrenade::Event_Collision_Explode ) + EVENT( EV_Collision_Glass, hhProjectileAutoCannonGrenade::Event_Collision_Explode ) + EVENT( EV_Collision_Liquid, hhProjectileAutoCannonGrenade::Event_Collision_Explode ) + EVENT( EV_Collision_CardBoard, hhProjectileAutoCannonGrenade::Event_Collision_Explode ) + EVENT( EV_Collision_Tile, hhProjectileAutoCannonGrenade::Event_Collision_Explode ) + EVENT( EV_Collision_Forcefield, hhProjectileAutoCannonGrenade::Event_Collision_Explode ) + EVENT( EV_Collision_Pipe, hhProjectileAutoCannonGrenade::Event_Collision_Explode ) + EVENT( EV_Collision_Wallwalk, hhProjectileAutoCannonGrenade::Event_Collision_Explode ) + + EVENT( EV_AllowCollision_Chaff, hhProjectileAutoCannonGrenade::Event_AllowCollision_Collide ) +END_CLASS \ No newline at end of file diff --git a/src/Prey/prey_projectileautocannon.h b/src/Prey/prey_projectileautocannon.h new file mode 100644 index 0000000..026a362 --- /dev/null +++ b/src/Prey/prey_projectileautocannon.h @@ -0,0 +1,8 @@ +#ifndef __HH_PROJECTILE_AUTOCANNON_GRENADE_H +#define __HH_PROJECTILE_AUTOCANNON_GRENADE_H + +class hhProjectileAutoCannonGrenade : public hhProjectile { + CLASS_PROTOTYPE( hhProjectileAutoCannonGrenade ) +}; + +#endif \ No newline at end of file diff --git a/src/Prey/prey_projectilebounce.cpp b/src/Prey/prey_projectilebounce.cpp new file mode 100644 index 0000000..82f7e84 --- /dev/null +++ b/src/Prey/prey_projectilebounce.cpp @@ -0,0 +1,34 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +#include "prey_local.h" + +CLASS_DECLARATION( hhProjectile, hhProjectileBounce ) + EVENT( EV_Collision_Flesh, hhProjectileBounce::Event_Collision_Bounce ) + EVENT( EV_Collision_Metal, hhProjectileBounce::Event_Collision_Bounce ) + EVENT( EV_Collision_AltMetal, hhProjectileBounce::Event_Collision_Bounce ) + EVENT( EV_Collision_Wood, hhProjectileBounce::Event_Collision_Bounce ) + EVENT( EV_Collision_Stone, hhProjectileBounce::Event_Collision_Bounce ) + EVENT( EV_Collision_Glass, hhProjectileBounce::Event_Collision_Bounce ) + EVENT( EV_Collision_CardBoard, hhProjectileBounce::Event_Collision_Bounce ) + EVENT( EV_Collision_Tile, hhProjectileBounce::Event_Collision_Bounce ) + EVENT( EV_Collision_Forcefield, hhProjectileBounce::Event_Collision_Bounce ) + EVENT( EV_Collision_Pipe, hhProjectileBounce::Event_Collision_Bounce ) + EVENT( EV_Collision_Wallwalk, hhProjectileBounce::Event_Collision_Bounce ) + +END_CLASS + +void hhProjectileBounce::Event_Collision_Bounce( const trace_t* collision, const idVec3 &velocity ) { + if( !collision || collision->fraction == 1.0f ) { + return; + } + StartSound( "snd_bounce", SND_CHANNEL_BODY, 0, true, NULL ); + float dot = velocity * collision->c.normal; + idVec3 normal = collision->c.normal * dot; + idVec3 tangent = (velocity - normal).ToNormal() - normal.ToNormal(); + idVec3 newVelocity = tangent.ToNormal() * velocity.Length(); + physicsObj.SetLinearVelocity( newVelocity ); + idThread::ReturnInt( 0 ); +} +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/src/Prey/prey_projectilebounce.h b/src/Prey/prey_projectilebounce.h new file mode 100644 index 0000000..191c186 --- /dev/null +++ b/src/Prey/prey_projectilebounce.h @@ -0,0 +1,11 @@ +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +#ifndef __HH_PROJECTILE_BOUNCE_H +#define __HH_PROJECTILE_BOUNCE_H + +class hhProjectileBounce : public hhProjectile { + CLASS_PROTOTYPE( hhProjectileBounce ); + void Event_Collision_Bounce( const trace_t* collision, const idVec3 &velocity ); +}; + +#endif +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/src/Prey/prey_projectilebug.cpp b/src/Prey/prey_projectilebug.cpp new file mode 100644 index 0000000..a94c4d3 --- /dev/null +++ b/src/Prey/prey_projectilebug.cpp @@ -0,0 +1,135 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +#include "prey_local.h" + +const idEventDef EV_Guide( "" ); + +CLASS_DECLARATION( hhProjectileTracking, hhProjectileBug ) + EVENT( EV_Collision_Flesh, hhProjectileBug::Event_Collision_Explode ) + EVENT( EV_Collision_Metal, hhProjectileBug::Event_Collision_Bounce ) + EVENT( EV_Collision_AltMetal, hhProjectileBug::Event_Collision_Bounce ) + EVENT( EV_Collision_Wood, hhProjectileBug::Event_Collision_Bounce ) + EVENT( EV_Collision_Stone, hhProjectileBug::Event_Collision_Bounce ) + EVENT( EV_Collision_Glass, hhProjectileBug::Event_Collision_Bounce ) + EVENT( EV_Collision_Liquid, hhProjectileBug::Event_Collision_Bounce ) + EVENT( EV_Collision_CardBoard, hhProjectileBug::Event_Collision_Bounce ) + EVENT( EV_Collision_Tile, hhProjectileBug::Event_Collision_Bounce ) + EVENT( EV_Collision_Forcefield, hhProjectileBug::Event_Collision_Bounce ) + EVENT( EV_Collision_Chaff, hhProjectileBug::Event_Collision_Bounce ) + EVENT( EV_Collision_Wallwalk, hhProjectileBug::Event_Collision_Bounce ) + EVENT( EV_Collision_Pipe, hhProjectileBug::Event_Collision_Bounce ) + + EVENT( EV_Guide, hhProjectileBug::Event_TrackTarget ) +END_CLASS + +void hhProjectileBug::Spawn() { + enemyRadius = spawnArgs.GetFloat( "enemy_radius", "200" ); +} + +idEntity* hhProjectileBug::DetermineEnemy() { + idEntity * entityList[ MAX_GENTITIES ]; + idEntity* possibleEnemy = NULL; + float currentEnemyDot = 0.0f; + float currentEnemyDist = CM_MAX_TRACE_DIST; + idEntity* localEnemy = NULL; + + //find player if within certain radius + for( int i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] ) { + possibleEnemy = gameLocal.entities[i]; + if ( !possibleEnemy || (possibleEnemy->GetOrigin() - GetOrigin()).Length() > enemyRadius ) { + continue; + } + localEnemy = WhosClosest( possibleEnemy, localEnemy, currentEnemyDot, currentEnemyDist ); + } + } + if ( localEnemy ) { + return localEnemy; + } + + //otherwise look for bug triggers + float bestDist = 9999999; + int bestIndex = -1; + idBounds bounds = idBounds( GetOrigin() ).Expand( spawnArgs.GetFloat( "enemy_check_radius" )); + int numListedEntities = gameLocal.clip.EntitiesTouchingBounds( bounds, -1, entityList, MAX_GENTITIES ); + for ( int i=0; ispawnArgs.GetInt( "bug_trigger", "0" ) ) { + continue; + } + float dist = ( GetOrigin() - possibleEnemy->GetOrigin() ).Length(); + if ( dist < bestDist ) { + bestIndex = i; + bestDist = dist; + } + } + + if ( bestIndex >= 0 ) { + if ( entityList[bestIndex]->IsType( idActor::Type ) ) { + physicsObj.SetContents( 0 ); + } else { + physicsObj.SetContents( CONTENTS_PROJECTILE ); + } + return entityList[bestIndex]; + } + + return NULL; +} + +idVec3 hhProjectileBug::DetermineEnemyPosition( const idEntity* ent ) const { + float randomOffset = spawnArgs.GetFloat( "offset_max", "70" ) * gameLocal.random.RandomFloat(); + if ( ent && ent->IsType( idActor::Type ) ) { + const idActor *entActor = static_cast(ent); + return entActor->GetEyePosition() + idVec3(0,0,randomOffset); + } + + return ent->GetOrigin() + idVec3(0,0,randomOffset); +} + +void hhProjectileBug::Event_TrackTarget() { + idEntity *newEnemy = DetermineEnemy(); + if( !newEnemy ) { + physicsObj.SetLinearVelocity( GetAxis()[ 0 ] * velocity[ 0 ] + GetAxis()[ 1 ] * velocity[ 1 ] + GetAxis()[ 2 ] * velocity[ 2 ] ); + physicsObj.SetAngularVelocity( angularVelocity.ToAngularVelocity() * GetAxis() ); + return; + } + enemy = newEnemy; + + idVec3 enemyDir = DetermineEnemyDir( enemy.GetEntity() ); + idVec3 currentDir = GetAxis()[0]; + idVec3 newDir = currentDir*(1-turnFactor) + enemyDir*turnFactor; + newDir.Normalize(); + if ( driver.IsValid() ) { + driver->SetAxis(newDir.ToMat3()); + } else { + SetAxis(newDir.ToMat3()); + } + + physicsObj.SetLinearVelocity( GetAxis()[ 0 ] * velocity[ 0 ] + GetAxis()[ 1 ] * velocity[ 1 ] + GetAxis()[ 2 ] * velocity[ 2 ] ); + physicsObj.SetAngularVelocity( angularVelocity.ToAngularVelocity() * GetAxis() ); + + PostEventMS( &EV_Guide, updateRate ); +} + +void hhProjectileBug::Event_Collision_Bounce( const trace_t* collision, const idVec3 &velocity ) { + physicsObj.SetLinearVelocity( hhProjectile::GetBounceDirection( physicsObj.GetLinearVelocity(), collision->c.normal ) ); + idThread::ReturnInt( 0 ); +} + +//================ +//hhProjectileBug::Save +//================ +void hhProjectileBug::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( enemyRadius ); +} + +//================ +//hhProjectileBug::Restore +//================ +void hhProjectileBug::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( enemyRadius ); +} + +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/src/Prey/prey_projectilebug.h b/src/Prey/prey_projectilebug.h new file mode 100644 index 0000000..5696804 --- /dev/null +++ b/src/Prey/prey_projectilebug.h @@ -0,0 +1,20 @@ +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +#ifndef __HH_PROJECTILE_BUG_H +#define __HH_PROJECTILE_BUG_H + +class hhProjectileBug: public hhProjectileTracking { +public: + CLASS_PROTOTYPE( hhProjectileBug ) + idEntity* DetermineEnemy(); + void Event_TrackTarget(); + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + idVec3 DetermineEnemyPosition( const idEntity* ent ) const; + void Event_Collision_Bounce( const trace_t* collision, const idVec3 &velocity ); +protected: + float enemyRadius; +}; + +#endif +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/src/Prey/prey_projectilebugtrigger.cpp b/src/Prey/prey_projectilebugtrigger.cpp new file mode 100644 index 0000000..50df589 --- /dev/null +++ b/src/Prey/prey_projectilebugtrigger.cpp @@ -0,0 +1,40 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +#include "prey_local.h" + +CLASS_DECLARATION( hhProjectile, hhProjectileBugTrigger ) + EVENT( EV_Collision_Flesh, hhProjectileBugTrigger::Event_Collision_Impact ) + EVENT( EV_Collision_Metal, hhProjectileBugTrigger::Event_Collision_Impact ) + EVENT( EV_Collision_AltMetal, hhProjectileBugTrigger::Event_Collision_Impact ) + EVENT( EV_Collision_Wood, hhProjectileBugTrigger::Event_Collision_Impact ) + EVENT( EV_Collision_Stone, hhProjectileBugTrigger::Event_Collision_Impact ) + EVENT( EV_Collision_Glass, hhProjectileBugTrigger::Event_Collision_Impact ) + EVENT( EV_Collision_Liquid, hhProjectileBugTrigger::Event_Collision_Impact ) + EVENT( EV_Collision_CardBoard, hhProjectileBugTrigger::Event_Collision_Impact ) + EVENT( EV_Collision_Tile, hhProjectileBugTrigger::Event_Collision_Impact ) + EVENT( EV_Collision_Forcefield, hhProjectileBugTrigger::Event_Collision_Impact ) + EVENT( EV_Collision_Chaff, hhProjectileBugTrigger::Event_Collision_Impact ) + EVENT( EV_Collision_Pipe, hhProjectileBugTrigger::Event_Collision_Impact ) + EVENT( EV_Collision_Wallwalk, hhProjectileBugTrigger::Event_Collision_Impact ) + + EVENT( EV_AllowCollision_Chaff, hhProjectileBugTrigger::Event_AllowCollision_Collide ) + EVENT( EV_Touch, hhProjectileBugTrigger::Event_Touch ) +END_CLASS + +void hhProjectileBugTrigger::Event_Collision_Impact( const trace_t* collision, const idVec3& velocity ) { + ProcessCollision(collision, velocity); + RemoveProjectile( spawnArgs.GetInt( "remove_time", "1500" ) ); + GetPhysics()->SetContents( CONTENTS_TRIGGER ); + idThread::ReturnInt( 1 ); +} + +void hhProjectileBugTrigger::Event_Touch( idEntity *other, trace_t *trace ) { + idAI *ownerAI = static_cast(owner.GetEntity()); + if ( other && ownerAI && ownerAI->GetEnemy() == other ) { + other->Damage( this, owner.GetEntity(), idVec3( 0,0,1 ), spawnArgs.GetString( "def_damage" ), 1.0, 0 ); + GetPhysics()->SetContents( 0 ); + } +} +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/src/Prey/prey_projectilebugtrigger.h b/src/Prey/prey_projectilebugtrigger.h new file mode 100644 index 0000000..9f8b334 --- /dev/null +++ b/src/Prey/prey_projectilebugtrigger.h @@ -0,0 +1,13 @@ +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +#ifndef __HH_PROJECTILE_BUGTRIGGER_H +#define __HH_PROJECTILE_BUGTRIGGER_H + +class hhProjectileBugTrigger: public hhProjectile { +public: + CLASS_PROTOTYPE( hhProjectileBugTrigger ) + void Event_Collision_Impact( const trace_t* collision, const idVec3& velocity ); + void Event_Touch( idEntity *other, trace_t *trace ); +}; + +#endif +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/src/Prey/prey_projectilecocoon.cpp b/src/Prey/prey_projectilecocoon.cpp new file mode 100644 index 0000000..48ea2b5 --- /dev/null +++ b/src/Prey/prey_projectilecocoon.cpp @@ -0,0 +1,105 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +#include "prey_local.h" + +const idEventDef EV_Guide( "" ); + +CLASS_DECLARATION( hhProjectileTracking, hhProjectileCocoon ) + EVENT( EV_Collision_Flesh, hhProjectileCocoon::Event_Collision_Explode ) + EVENT( EV_Collision_Metal, hhProjectileCocoon::Event_Collision_Proj ) + EVENT( EV_Collision_AltMetal, hhProjectileCocoon::Event_Collision_Bounce ) + EVENT( EV_Collision_Wood, hhProjectileCocoon::Event_Collision_Bounce ) + EVENT( EV_Collision_Stone, hhProjectileCocoon::Event_Collision_Bounce ) + EVENT( EV_Collision_Glass, hhProjectileCocoon::Event_Collision_Bounce ) + EVENT( EV_Collision_Liquid, hhProjectileCocoon::Event_Collision_Bounce ) + EVENT( EV_Collision_CardBoard, hhProjectileCocoon::Event_Collision_Bounce ) + EVENT( EV_Collision_Tile, hhProjectileCocoon::Event_Collision_Bounce ) + EVENT( EV_Collision_Forcefield, hhProjectileCocoon::Event_Collision_Bounce ) + EVENT( EV_Collision_Chaff, hhProjectileCocoon::Event_Collision_Bounce ) + EVENT( EV_Collision_Wallwalk, hhProjectileCocoon::Event_Collision_Bounce ) + EVENT( EV_Collision_Pipe, hhProjectileCocoon::Event_Collision_Bounce ) + + EVENT( EV_Guide, hhProjectileCocoon::Event_TrackTarget ) + EVENT( EV_AllowCollision_Flesh, hhProjectileCocoon::Event_AllowCollision ) + EVENT( EV_AllowCollision_Metal, hhProjectileCocoon::Event_AllowCollision ) + EVENT( EV_AllowCollision_AltMetal, hhProjectileCocoon::Event_AllowCollision ) + EVENT( EV_AllowCollision_Wood, hhProjectileCocoon::Event_AllowCollision ) + EVENT( EV_AllowCollision_Stone, hhProjectileCocoon::Event_AllowCollision ) + EVENT( EV_AllowCollision_Glass, hhProjectileCocoon::Event_AllowCollision ) + EVENT( EV_AllowCollision_Liquid, hhProjectileCocoon::Event_AllowCollision ) + EVENT( EV_AllowCollision_CardBoard, hhProjectileCocoon::Event_AllowCollision ) + EVENT( EV_AllowCollision_Tile, hhProjectileCocoon::Event_AllowCollision ) + EVENT( EV_AllowCollision_Forcefield, hhProjectileCocoon::Event_AllowCollision ) + EVENT( EV_AllowCollision_Pipe, hhProjectileCocoon::Event_AllowCollision ) + EVENT( EV_AllowCollision_Wallwalk, hhProjectileCocoon::Event_AllowCollision ) + EVENT( EV_AllowCollision_Spirit, hhProjectileCocoon::Event_AllowCollision_PassThru ) + EVENT( EV_AllowCollision_Chaff, hhProjectileCocoon::Event_AllowCollision_Collide ) + +END_CLASS + +void hhProjectileCocoon::Spawn() { + nextBounceTime = 0; +} + +void hhProjectileCocoon::Event_TrackTarget() { + idVec3 newVelocity; + if( !enemy.IsValid() ) { + physicsObj.SetLinearVelocity( GetAxis()[ 0 ] * velocity[ 0 ] + GetAxis()[ 1 ] * velocity[ 1 ] + GetAxis()[ 2 ] * velocity[ 2 ] ); + physicsObj.SetAngularVelocity( angularVelocity.ToAngularVelocity() * GetAxis() ); + return; + } + + idVec3 enemyDir = DetermineEnemyDir( enemy.GetEntity() ); + idVec3 currentDir = GetAxis()[0]; + idVec3 newDir = currentDir*(1-turnFactor) + enemyDir*turnFactor; + newDir.Normalize(); + if ( driver.IsValid() ) { + driver->SetAxis(newDir.ToMat3()); + } else { + SetAxis(newDir.ToMat3()); + } + newVelocity = GetAxis()[ 0 ] * velocity[ 0 ] + GetAxis()[ 1 ] * velocity[ 1 ] + GetAxis()[ 2 ] * velocity[ 2 ]; + newVelocity *= spawnArgs.GetFloat( "bounce", "1.0" ); + physicsObj.SetLinearVelocity( newVelocity ); + physicsObj.SetAngularVelocity( angularVelocity.ToAngularVelocity() * GetAxis() ); +} + +void hhProjectileCocoon::Event_Collision_Bounce( const trace_t* collision, const idVec3 &velocity ) { + if ( gameLocal.time >= nextBounceTime ) { + nextBounceTime = gameLocal.time + spawnArgs.GetFloat( "bounce_freq", "200" ); + + StopSound( SND_CHANNEL_BODY, true ); + StartSound( "snd_bounce", SND_CHANNEL_BODY, 0, true, NULL ); + idEntity *ent = gameLocal.entities[ collision->c.entityNum ]; + Event_TrackTarget(); + BounceSplat( GetOrigin(), -collision->c.normal ); + } + idThread::ReturnInt( 0 ); +} + +void hhProjectileCocoon::Event_Collision_Proj( const trace_t* collision, const idVec3 &velocity ) { + idEntity *ent = gameLocal.entities[ collision->c.entityNum ]; + if ( ent && ent->IsType( idProjectile::Type ) ) { + Explode( collision, velocity, 0 ); + } else { + Event_Collision_Bounce( collision, velocity ); + } + + idThread::ReturnInt( 0 ); +} + +void hhProjectileCocoon::Event_AllowCollision( const trace_t* collision ) { + idEntity *ent = gameLocal.entities[ collision->c.entityNum ]; + if ( ent && ent->IsType( idProjectile::Type ) ) { + int foo = 0; + } + + idThread::ReturnInt( 1 ); +} + +void hhProjectileCocoon::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + hhProjectileTracking::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/src/Prey/prey_projectilecocoon.h b/src/Prey/prey_projectilecocoon.h new file mode 100644 index 0000000..2dc1d88 --- /dev/null +++ b/src/Prey/prey_projectilecocoon.h @@ -0,0 +1,19 @@ +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +#ifndef __HH_PROJECTILE_COCOON_H +#define __HH_PROJECTILE_COCOON_H + +class hhProjectileCocoon: public hhProjectileTracking { +public: + CLASS_PROTOTYPE( hhProjectileCocoon ) + void Spawn(); + void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); +protected: + void Event_Collision_Proj( const trace_t* collision, const idVec3 &velocity ); + void Event_TrackTarget(); + void Event_Collision_Bounce( const trace_t* collision, const idVec3 &velocity ); + void Event_AllowCollision( const trace_t* collision ); + int nextBounceTime; +}; + +#endif +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/src/Prey/prey_projectilecrawlergrenade.cpp b/src/Prey/prey_projectilecrawlergrenade.cpp new file mode 100644 index 0000000..5161fee --- /dev/null +++ b/src/Prey/prey_projectilecrawlergrenade.cpp @@ -0,0 +1,557 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/*********************************************************************** + + hhProjectileCrawlerGrenade + +***********************************************************************/ +const idEventDef EV_ApplyExpandWound( "" ); + +const idEventDef EV_DyingState( "" ); +const idEventDef EV_DeadState( "" ); + +CLASS_DECLARATION( hhProjectile, hhProjectileCrawlerGrenade ) + EVENT( EV_ApplyExpandWound, hhProjectileCrawlerGrenade::Event_ApplyExpandWound ) + EVENT( EV_DyingState, hhProjectileCrawlerGrenade::EnterDyingState ) + EVENT( EV_DeadState, hhProjectileCrawlerGrenade::EnterDeadState ) + + EVENT( EV_Collision_Flesh, hhProjectileCrawlerGrenade::Event_Collision_Bounce ) + EVENT( EV_Collision_Metal, hhProjectileCrawlerGrenade::Event_Collision_Bounce ) + EVENT( EV_Collision_AltMetal, hhProjectileCrawlerGrenade::Event_Collision_Bounce ) + EVENT( EV_Collision_Wood, hhProjectileCrawlerGrenade::Event_Collision_Bounce ) + EVENT( EV_Collision_Stone, hhProjectileCrawlerGrenade::Event_Collision_Bounce ) + EVENT( EV_Collision_Glass, hhProjectileCrawlerGrenade::Event_Collision_Bounce ) + EVENT( EV_Collision_CardBoard, hhProjectileCrawlerGrenade::Event_Collision_Bounce ) + EVENT( EV_Collision_Tile, hhProjectileCrawlerGrenade::Event_Collision_Bounce ) + EVENT( EV_Collision_Forcefield, hhProjectileCrawlerGrenade::Event_Collision_Bounce ) + EVENT( EV_Collision_Pipe, hhProjectileCrawlerGrenade::Event_Collision_Bounce ) + EVENT( EV_Collision_Wallwalk, hhProjectileCrawlerGrenade::Event_Collision_Bounce ) + //EVENT( EV_Collision_Chaff, hhProjectileCrawlerGrenade::Event_Collision_Explode ) + EVENT( EV_Collision_Liquid, hhProjectileCrawlerGrenade::Event_Collision_DisturbLiquid ) + + EVENT( EV_AllowCollision_Chaff, hhProjectileCrawlerGrenade::Event_AllowCollision_Collide ) +END_CLASS + +/* +================= +hhProjectileCrawlerGrenade::Spawn +================= +*/ +void hhProjectileCrawlerGrenade::Spawn() { + modelScale.Init( gameLocal.time, 0, 1.0f, 1.0f ); + modelProxy = NULL; + + InitCollisionInfo(); + + //doesn't matter for single player, only for network logic -rww + modelProxyCopyDone = false; + + if( !gameLocal.isClient ) { + SpawnModelProxy(); + } + + BecomeActive( TH_TICKER ); + + if (gameLocal.isClient) + { //rww - do this right away on the client + //Get rid of our model. The modelProxy is our model now. + SetModel( "" ); + } + + //rww - allow events on client + fl.clientEvents = true; +} + +/* +================= +hhProjectileCrawlerGrenade::~hhProjectileCrawlerGrenade +================= +*/ +hhProjectileCrawlerGrenade::~hhProjectileCrawlerGrenade() { + SAFE_REMOVE( modelProxy ); +} + +/* +================= +hhProjectileCrawlerGrenade::InitCollisionInfo +================= +*/ +void hhProjectileCrawlerGrenade::InitCollisionInfo() { + memset( &collisionInfo, 0, sizeof(trace_t) ); + collisionInfo.fraction = 1.0f; +} + +/* +================= +hhProjectileCrawlerGrenade::CopyToModelProxy +================= +*/ +void hhProjectileCrawlerGrenade::CopyToModelProxy() +{ + //rww - do this the right way with an inheriting entityDef + //idDict args = spawnArgs; + //args.Delete( "spawnclass" ); + //args.Delete( "name" ); + + idDict args; + idDict *setArgs; + idStr str; + + if (gameLocal.isClient) + { + setArgs = &modelProxy->spawnArgs; + } + else + { + args.Clear(); + setArgs = &args; + } + + setArgs->Set( "owner", GetName() ); + setArgs->SetVector( "origin", GetOrigin() ); + setArgs->SetMatrix( "rotation", GetAxis() ); + + //copy the model over + if (spawnArgs.GetString("model", "", str)) + { + setArgs->Set("model", str.c_str()); + if (gameLocal.isClient && modelProxy.IsValid()) + { + modelProxy->SetModel(str.c_str()); + } + } + + //these are now taken care of in the ent def. + /* + setArgs->SetBool( "useCombatModel", true ); + setArgs->SetBool( "transferDamage", false ); + setArgs->SetBool( "solid", false ); + */ + + if (!gameLocal.isClient) + { + modelProxy = gameLocal.SpawnObject("projectile_crawler_proxy", &args); + } + + if( modelProxy.IsValid() ) { + modelProxy->Bind( this, true ); + modelProxy->CycleAnim( "idle", ANIMCHANNEL_ALL ); + } + + //for debugging + //spawnArgs.CompareArgs(modelProxy->spawnArgs); + + modelProxyCopyDone = true; +} + +/* +================= +hhProjectileCrawlerGrenade::SpawnModelProxy +================= +*/ +void hhProjectileCrawlerGrenade::SpawnModelProxy() { + if (!gameLocal.isClient) + { + CopyToModelProxy(); + } + + //Get rid of our model. The modelProxy is our model now. + SetModel( "" ); +} + +/* +================= +hhProjectileCrawlerGrenade::Ticker +================= +*/ +void hhProjectileCrawlerGrenade::Ticker() { + if( state == StateDying ) { + if( modelProxy.IsValid() ) { + modelProxy->SetDeformation(DEFORMTYPE_SCALE, modelScale.GetCurrentValue(gameLocal.time)); + } + } +} + +/* +================ +hhProjectileCrawlerGrenade::Launch +================ +*/ +void hhProjectileCrawlerGrenade::Launch( const idVec3 &start, const idMat3 &axis, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, const float dmgPower ) { + hhProjectile::Launch( start, axis, pushVelocity, timeSinceFire, launchPower, dmgPower ); + + if( modelProxy.IsValid() ) { + modelProxy->CycleAnim( "flight", ANIMCHANNEL_ALL ); + } + + float delayBeforeDying = spawnArgs.GetFloat( "delayBeforeDying" ); + float fuse = spawnArgs.GetFloat( "fuse" ); + + inflateDuration = SEC2MS( hhMath::ClampFloat(0.0f, fuse, fuse - delayBeforeDying) ); + + PostEventSec( &EV_DyingState, delayBeforeDying ); + PostEventSec( &EV_DeadState, fuse ); +} + +/* +================ +hhProjectileCrawlerGrenade::SpawnFlyFx +================ +*/ +void hhProjectileCrawlerGrenade::SpawnFlyFx() { + if( modelProxy.IsValid() ) { + modelProxy->BroadcastFxInfoAlongBonePrefix( &spawnArgs, "fx_fly", "joint_legStub" ); + } +} + +/* +================= +hhProjectileCrawlerGrenade::Hide +================= +*/ +void hhProjectileCrawlerGrenade::Hide() { + hhProjectile::Hide(); + + if( modelProxy.IsValid() ) { + modelProxy->Hide(); + } +} + +/* +================= +hhProjectileCrawlerGrenade::Show +================= +*/ +void hhProjectileCrawlerGrenade::Show() { + hhProjectile::Show(); + + if( modelProxy.IsValid() ) { + modelProxy->Show(); + } +} + +/* +================= +hhProjectileCrawlerGrenade::RemoveProjectile +================= +*/ +void hhProjectileCrawlerGrenade::RemoveProjectile( const int removeDelay ) { + hhProjectile::RemoveProjectile( removeDelay ); + + if( modelProxy.IsValid() ) { + modelProxy->PostEventMS( &EV_Remove, removeDelay ); + } +} + +/* +================= +hhProjectileCrawlerGrenade::Event_ApplyExpandWound +================= +*/ +void hhProjectileCrawlerGrenade::Event_ApplyExpandWound() { + trace_t trace; + + if( !modelProxy.IsValid() || !modelProxy->GetCombatModel() ) { + return; + } + + idBounds clipBounds( modelProxy->GetRenderEntity()->bounds ); + idVec3 traceEnd = GetOrigin(); + idVec3 traceStart = traceEnd + hhUtils::RandomPointInShell( clipBounds.Expand(1.0f).GetRadius(), clipBounds.Expand(2.0f).GetRadius() ); + idVec3 jointOrigin, localOrigin, localNormal; + idMat3 jointAxis, axisTranspose; + jointHandle_t jointHandle = INVALID_JOINT; + + CancelEvents( &EV_ApplyExpandWound ); + PostEventSec( &EV_ApplyExpandWound, spawnArgs.GetFloat("expandWoundDelay") ); + + if( !gameLocal.clip.TracePoint(trace, traceStart, traceEnd, modelProxy->GetCombatModel()->GetContents(), NULL) ) { + return; + } + + if( trace.c.entityNum != entityNumber ) {//Make sure we hit ourselves + return; + } + + modelProxy->AddDamageEffect( trace, vec3_zero, spawnArgs.GetString("def_expandDamage"), (!fl.networkSync || netSyncPhysics) ); +} + +/* +================= +hhProjectileCrawlerGrenade::Event_Collision_Bounce +================= +*/ +void hhProjectileCrawlerGrenade::Event_Collision_Bounce( const trace_t* collision, const idVec3 &velocity ) { + static const float minCollisionVelocity = 20.0f; + static const float maxCollisionVelocity = 90.0f; + + StopSound( SND_CHANNEL_BODY, true ); + + // Velocity in normal direction + float len = velocity * -collision->c.normal; + + if( collision->fraction < VECTOR_EPSILON || len < minCollisionVelocity ) { + idThread::ReturnInt( 0 ); + return; + } + + StartSound( "snd_bounce", SND_CHANNEL_BODY, 0, true, NULL ); + float volume = hhUtils::CalculateSoundVolume( len, minCollisionVelocity, maxCollisionVelocity ); + HH_SetSoundVolume( volume, SND_CHANNEL_BODY ); + + BounceSplat( GetOrigin(), -collision->c.normal ); + + SIMDProcessor->Memcpy( &collisionInfo, collision, sizeof(trace_t) ); + collisionInfo.fraction = 0.0f;//Sometimes fraction == 1.0f + + physicsObj.SetAngularVelocity( 0.5f*physicsObj.GetAngularVelocity() ); + + idThread::ReturnInt( 0 ); +} + +/* +================= +hhProjectileCrawlerGrenade::Event_Collision_DisturbLiquid +================= +*/ +void hhProjectileCrawlerGrenade::Event_Collision_DisturbLiquid( const trace_t* collision, const idVec3 &velocity ) { + CancelActivates(); + EnterDeadState(); + + hhProjectile::Event_Collision_DisturbLiquid( collision, velocity ); +} + +//================ +//hhProjectileCrawlerGrenade::Save +//================ +void hhProjectileCrawlerGrenade::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( modelScale.GetStartTime() ); // idInterpolate + savefile->WriteFloat( modelScale.GetDuration() ); + savefile->WriteFloat( modelScale.GetStartValue() ); + savefile->WriteFloat( modelScale.GetEndValue() ); + savefile->WriteInt( inflateDuration ); + savefile->WriteInt( state ); + savefile->WriteTrace( collisionInfo ); + modelProxy.Save( savefile ); +} + +//================ +//hhProjectileCrawlerGrenade::Restore +//================ +void hhProjectileCrawlerGrenade::Restore( idRestoreGame *savefile ) { + float set; + savefile->ReadFloat( set ); // idInterpolate + modelScale.SetStartTime( set ); + savefile->ReadFloat( set ); + modelScale.SetDuration( set ); + savefile->ReadFloat( set ); + modelScale.SetStartValue( set ); + savefile->ReadFloat( set ); + modelScale.SetEndValue( set ); + + savefile->ReadInt( inflateDuration ); + savefile->ReadInt( reinterpret_cast ( state ) ); + + savefile->ReadTrace( collisionInfo ); + modelProxy.Restore( savefile ); +} + +/* +================= +hhProjectileCrawlerGrenade::WriteToSnapshot +================= +*/ +void hhProjectileCrawlerGrenade::WriteToSnapshot( idBitMsgDelta &msg ) const +{ + msg.WriteBits(modelProxyCopyDone, 1); + msg.WriteBits(modelProxy.GetSpawnId(), 32); + + hhProjectile::WriteToSnapshot(msg); +} + +/* +================= +hhProjectileCrawlerGrenade::ReadFromSnapshot +================= +*/ +void hhProjectileCrawlerGrenade::ReadFromSnapshot( const idBitMsgDelta &msg ) +{ + bool newModelProxyCopyDone = !!msg.ReadBits(1); + if (modelProxy.SetSpawnId(msg.ReadBits(32))) + { + if (modelProxyCopyDone != newModelProxyCopyDone && + modelProxy.IsValid() && + modelProxy->IsType(hhGenericAnimatedPart::Type)) + { + modelProxyCopyDone = newModelProxyCopyDone; + CopyToModelProxy(); + } + } + + hhProjectile::ReadFromSnapshot(msg); +} + +/* +================= +hhProjectileCrawlerGrenade::EnterDyingState +================= +*/ +void hhProjectileCrawlerGrenade::EnterDyingState() { + state = StateDying; + modelScale.Init( gameLocal.GetTime(), inflateDuration, modelScale.GetCurrentValue(gameLocal.GetTime()), spawnArgs.GetFloat("inflateScale") ); + + StartSound( "snd_expand_screech", SND_CHANNEL_VOICE, 0, true, NULL ); + ProcessEvent( &EV_ApplyExpandWound ); +} + +/* +================= +hhProjectileCrawlerGrenade::EnterDeadState +================= +*/ +void hhProjectileCrawlerGrenade::EnterDeadState() { + state = StateDead; + CancelEvents( &EV_ApplyExpandWound ); + StopSound( SND_CHANNEL_VOICE, true ); +} + +/* +================= +hhProjectileCrawlerGrenade::CancelActivates +================= +*/ +void hhProjectileCrawlerGrenade::CancelActivates() { + CancelEvents( &EV_DyingState ); + CancelEvents( &EV_DeadState ); +} + +/*********************************************************************** + + hhProjectileStickyCrawlerGrenade + +***********************************************************************/ + +CLASS_DECLARATION( hhProjectileCrawlerGrenade, hhProjectileStickyCrawlerGrenade ) + EVENT( EV_Collision_Flesh, hhProjectileStickyCrawlerGrenade::Event_Collision_Stick ) + EVENT( EV_Collision_Metal, hhProjectileStickyCrawlerGrenade::Event_Collision_Stick ) + EVENT( EV_Collision_AltMetal, hhProjectileStickyCrawlerGrenade::Event_Collision_Stick ) + EVENT( EV_Collision_Wood, hhProjectileStickyCrawlerGrenade::Event_Collision_Stick ) + EVENT( EV_Collision_Stone, hhProjectileStickyCrawlerGrenade::Event_Collision_Stick ) + EVENT( EV_Collision_Glass, hhProjectileStickyCrawlerGrenade::Event_Collision_Stick ) + EVENT( EV_Collision_CardBoard, hhProjectileStickyCrawlerGrenade::Event_Collision_Stick ) + EVENT( EV_Collision_Tile, hhProjectileStickyCrawlerGrenade::Event_Collision_Stick ) + EVENT( EV_Collision_Forcefield, hhProjectileStickyCrawlerGrenade::Event_Collision_Stick ) + EVENT( EV_Collision_Pipe, hhProjectileStickyCrawlerGrenade::Event_Collision_Stick ) + EVENT( EV_Collision_Wallwalk, hhProjectileStickyCrawlerGrenade::Event_Collision_Stick ) + + EVENT( EV_Activate, hhProjectileStickyCrawlerGrenade::Event_Activate ) +END_CLASS + +int hhProjectileStickyCrawlerGrenade::ProcessCollision( const trace_t* collision, const idVec3& velocity ) { + idEntity* entHit = gameLocal.entities[ collision->c.entityNum ]; + + //SAFE_REMOVE( fxFly ); + FreeLightDef(); + CancelEvents( &EV_Fizzle ); + + //physicsObj.SetContents( 0 ); + physicsObj.PutToRest(); + + surfTypes_t matterType = gameLocal.GetMatterType( entHit, collision->c.material, "hhProjectile::ProcessCollision" ); + return PlayImpactSound( gameLocal.FindEntityDefDict(spawnArgs.GetString("def_damage")), collision->endpos, matterType ); +} + +idMat3 hhProjectileStickyCrawlerGrenade::DetermineCollisionAxis( const idMat3& collisionAxis ) { + return collisionAxis; +} + +void hhProjectileStickyCrawlerGrenade::BindToCollisionObject( const trace_t* collision ) { + if( !collision || collision->fraction > 1.0f ) { + return; + } + + //HUMANHEAD PCF rww 05/18/06 - wait until we receive bind info from the server + if (gameLocal.isClient) { + return; + } + //HUMANHEAD END + + idEntity* pEntity = gameLocal.entities[collision->c.entityNum]; + HH_ASSERT( pEntity ); + + // HUMANHEAD PCF pdm 05-20-06: Check for some degenerate cases to combat the server hangs happening + if (pEntity == this || this->IsBound()) { + assert(0); // Report any of these + return; + } + // HUMANHEAD END + + jointHandle_t jointHandle = CLIPMODEL_ID_TO_JOINT_HANDLE( collision->c.id ); + if ( jointHandle != INVALID_JOINT ) { + SetOrigin( collision->endpos ); + SetAxis( DetermineCollisionAxis( (-collision->c.normal).ToMat3()) ); + BindToJoint( pEntity, jointHandle, true ); + } else { + SetOrigin( collision->endpos ); + SetAxis( DetermineCollisionAxis( (-collision->c.normal).ToMat3()) ); + Bind( pEntity, true ); + } +} + +void hhProjectileStickyCrawlerGrenade::Event_Collision_Stick( const trace_t* collision, const idVec3 &velocity ) { + if (proximityDetonateTrigger.GetEntity()) { //rww - don't allow this to be called more than once in a crawler grenade's lifetime + return; + } + ProcessCollision( collision, velocity ); + + BindToCollisionObject( collision ); + + fl.ignoreGravityZones = true; + SetGravity( idVec3(0.f, 0.f, 0.f) ); + spawnArgs.SetVector("gravity", idVec3(0.f, 0.f, 0.f) ); + + BounceSplat( GetOrigin(), -collision->c.normal ); + + idDict dict; + + dict.SetVector( "origin", GetOrigin() ); + //dict.SetMatrix( "rotation", GetAxis() ); + dict.Set( "target", name.c_str() ); + + dict.SetVector( "mins", spawnArgs.GetVector("detonationMins", "-10 -10 -10") ); + dict.SetVector( "maxs", spawnArgs.GetVector("detonationMaxs", "10 10 10") ); + if (!gameLocal.isClient) { + proximityDetonateTrigger = gameLocal.SpawnObject( spawnArgs.GetString("def_trigger"), &dict ); + proximityDetonateTrigger->Bind( this, true ); + if ( proximityDetonateTrigger->IsType( hhTrigger::Type ) ) { + hhTrigger *trigger = static_cast(proximityDetonateTrigger.GetEntity()); + if ( trigger && trigger->IsEncroached() ) { + proximityDetonateTrigger->PostEventMS( &EV_Activate, 0, this ); + } + } + } + + if( modelProxy.IsValid() ) { + modelProxy->CycleAnim( "idle", ANIMCHANNEL_ALL ); + } + + // CJR: Added this from the normal crawler collision bounce code + SIMDProcessor->Memcpy( &collisionInfo, collision, sizeof(trace_t) ); + collisionInfo.fraction = 0.0f;//Sometimes fraction == 1.0f + + idThread::ReturnInt( 1 ); +} + +void hhProjectileStickyCrawlerGrenade::Event_Activate( idEntity *pActivator ) { + StartSound( "snd_expand_screech", SND_CHANNEL_VOICE, 0, true, NULL ); + PostEventSec( &EV_Explode, spawnArgs.GetFloat( "explodeDelay", "1.0" ) ); +} + +void hhProjectileStickyCrawlerGrenade::Explode( const trace_t* collision, const idVec3& velocity, int removeDelay ) { + SAFE_REMOVE( fxFly ); + hhProjectile::Explode( &collisionInfo, velocity, removeDelay ); +} + diff --git a/src/Prey/prey_projectilecrawlergrenade.h b/src/Prey/prey_projectilecrawlergrenade.h new file mode 100644 index 0000000..2903b31 --- /dev/null +++ b/src/Prey/prey_projectilecrawlergrenade.h @@ -0,0 +1,86 @@ +#ifndef __HH_PROJECTILE_CRAWLER_GRENADE_H +#define __HH_PROJECTILE_CRAWLER_GRENADE_H + +extern const idEventDef EV_ApplyExpandWound; + +/*********************************************************************** + + hhProjectileCrawlerGrenade + +***********************************************************************/ +class hhProjectileCrawlerGrenade : public hhProjectile { + CLASS_PROTOTYPE( hhProjectileCrawlerGrenade ); + + public: + void Spawn(); + virtual ~hhProjectileCrawlerGrenade(); + + virtual void Launch( const idVec3 &start, const idMat3 &axis, const idVec3 &pushVelocity, const float timeSinceFire = 0.0f, const float launchPower = 1.0f, const float dmgPower = 1.0f ); + + virtual void Hide(); + virtual void Show(); + virtual void RemoveProjectile( const int removeDelay ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - networking + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + protected: + virtual void Ticker(); + + virtual void SpawnFlyFx(); + virtual void CopyToModelProxy(); + virtual void SpawnModelProxy(); + virtual void InitCollisionInfo(); + + protected: + void Event_ApplyExpandWound(); + + void Event_Collision_Bounce( const trace_t* collision, const idVec3 &velocity ); + void Event_Collision_DisturbLiquid( const trace_t* collision, const idVec3 &velocity ); + void EnterDyingState(); + void EnterDeadState(); + void CancelActivates(); + + protected: + enum States { + StateAlive = 0, + StateDying, + StateDead + } state; + + idInterpolate modelScale; + int inflateDuration; + + trace_t collisionInfo; + + idEntityPtr modelProxy; + + bool modelProxyCopyDone; +}; + +/*********************************************************************** + + hhProjectileStickyCrawlerGrenade + +***********************************************************************/ +class hhProjectileStickyCrawlerGrenade : public hhProjectileCrawlerGrenade { + CLASS_PROTOTYPE( hhProjectileStickyCrawlerGrenade ); + + public: + int ProcessCollision( const trace_t* collision, const idVec3& velocity ); + idMat3 DetermineCollisionAxis( const idMat3& collisionAxis ); + void Event_Activate( idEntity *pActivator ); + + protected: + virtual void BindToCollisionObject( const trace_t* collision ); + void Event_Collision_Stick( const trace_t* collision, const idVec3 &velocity ); + virtual void Explode( const trace_t* collision, const idVec3& velocity, int removeDelay ); + + idEntityPtr proximityDetonateTrigger; +}; + +#endif \ No newline at end of file diff --git a/src/Prey/prey_projectilefreezer.cpp b/src/Prey/prey_projectilefreezer.cpp new file mode 100644 index 0000000..eb98835 --- /dev/null +++ b/src/Prey/prey_projectilefreezer.cpp @@ -0,0 +1,115 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/*********************************************************************** + + hhProjectileFreezer + +***********************************************************************/ +CLASS_DECLARATION( hhProjectile, hhProjectileFreezer ) + //EVENT( EV_Touch, hhProjectileFreezer::Event_Touch ) + + EVENT( EV_Collision_Flesh, hhProjectileFreezer::Event_Collision_Bounce ) + EVENT( EV_Collision_Metal, hhProjectileFreezer::Event_Collision_Bounce ) + EVENT( EV_Collision_AltMetal, hhProjectileFreezer::Event_Collision_Bounce ) + EVENT( EV_Collision_Wood, hhProjectileFreezer::Event_Collision_Bounce ) + EVENT( EV_Collision_Stone, hhProjectileFreezer::Event_Collision_Bounce ) + EVENT( EV_Collision_Glass, hhProjectileFreezer::Event_Collision_Bounce ) + EVENT( EV_Collision_CardBoard, hhProjectileFreezer::Event_Collision_Bounce ) + EVENT( EV_Collision_Tile, hhProjectileFreezer::Event_Collision_Bounce ) + EVENT( EV_Collision_Forcefield, hhProjectileFreezer::Event_Collision_Bounce ) + EVENT( EV_Collision_Pipe, hhProjectileFreezer::Event_Collision_Bounce ) + EVENT( EV_Collision_Wallwalk, hhProjectileFreezer::Event_Collision_Bounce ) +END_CLASS + +void hhProjectileFreezer::Spawn() { + decelStart = SEC2MS( spawnArgs.GetFloat("decelStart") ) + gameLocal.GetTime(); + decelEnd = SEC2MS( spawnArgs.GetFloat("decelDuration") ) + decelStart; + + collided=false; + + BecomeActive( TH_TICKER ); +} + +void hhProjectileFreezer::Launch( const idVec3 &start, const idMat3 &axis, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, const float dmgPower ) { + hhProjectile::Launch( start, axis, pushVelocity, timeSinceFire, launchPower, dmgPower ); + + cachedVelocity = GetPhysics()->GetLinearVelocity(); + + //fl.takedamage = false; + //physicsObj.DisableImpact(); +} + +void hhProjectileFreezer::Event_Collision_Bounce( const trace_t* collision, const idVec3 &velocity ) { + idEntity *entityHit = gameLocal.entities[ collision->c.entityNum ]; + if ( entityHit->IsType(idAI::Type) || entityHit->IsType(idAFEntity_Base::Type) ) { + Event_Collision_Explode(collision, velocity); + return; + } + + ProcessCollision(collision, velocity); + collided=true; + idThread::ReturnInt( 1 ); +} + +bool hhProjectileFreezer::Collide( const trace_t& collision, const idVec3& velocity ) { + if(!collided) + return hhProjectile::Collide( collision, velocity ); + else + return false; +} + + +int hhProjectileFreezer::ProcessCollision( const trace_t* collision, const idVec3& velocity ) { + idEntity* entHit = gameLocal.entities[ collision->c.entityNum ]; + + SetOrigin( collision->endpos ); + SetAxis( collision->endAxis ); + + if (entHit) { //rww - may be null on client. + DamageEntityHit( collision, velocity, entHit ); + } + + fl.takedamage = false; + physicsObj.SetContents( 0 ); + physicsObj.PutToRest(); + + surfTypes_t matterType = gameLocal.GetMatterType( entHit, collision->c.material, "hhProjectile::ProcessCollision" ); + return PlayImpactSound( gameLocal.FindEntityDefDict(spawnArgs.GetString("def_damage")), collision->endpos, matterType ); +} + +void hhProjectileFreezer::Ticker() { + float scale = 0.0f; + if( gameLocal.GetTime() > decelStart && gameLocal.GetTime() < decelEnd ) { + scale = hhMath::Sin( DEG2RAD(hhMath::MidPointLerp( 0.0f, 30.0f, 90.0f, 1.0f - hhUtils::CalculateScale(gameLocal.GetTime(), decelStart, decelEnd))) ); + + GetPhysics()->SetLinearVelocity( cachedVelocity * hhMath::ClampFloat(0.05f, 1.0f, scale) ); + } +} + +void hhProjectileFreezer::Event_Touch( idEntity *other, trace_t *trace ) { + //Supposed to be empty + +} + +void hhProjectileFreezer::Save( idSaveGame *savefile ) const { + savefile->WriteInt( decelStart ); + savefile->WriteInt( decelEnd ); + savefile->WriteVec3( cachedVelocity ); + savefile->WriteBool( collided ); +} + +void hhProjectileFreezer::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( decelStart ); + savefile->ReadInt( decelEnd ); + savefile->ReadVec3( cachedVelocity ); + savefile->ReadBool( collided ); +} + +void hhProjectileFreezer::ApplyDamageEffect( idEntity* hitEnt, const trace_t* collision, const idVec3& velocity, const char* damageDefName ) { + if( hitEnt && gameLocal.random.RandomFloat() > 0.6f ) { + hitEnt->AddDamageEffect( *collision, velocity, damageDefName, (!fl.networkSync || netSyncPhysics) ); + } +} \ No newline at end of file diff --git a/src/Prey/prey_projectilefreezer.h b/src/Prey/prey_projectilefreezer.h new file mode 100644 index 0000000..b0aa427 --- /dev/null +++ b/src/Prey/prey_projectilefreezer.h @@ -0,0 +1,35 @@ +#ifndef __HH_PROJECTILE_FREEZER_H +#define __HH_PROJECTILE_FREEZER_H + +class hhProjectileFreezer : public hhProjectile { + CLASS_PROTOTYPE( hhProjectileFreezer ); + + public: + void Spawn(); + + virtual void Launch( const idVec3 &start, const idMat3 &axis, const idVec3 &pushVelocity, const float timeSinceFire = 0.0f, const float launchPower = 1.0f, const float dmgPower = 1.0f ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool Collide( const trace_t& collision, const idVec3& velocity ); + virtual int ProcessCollision( const trace_t* collision, const idVec3& velocity ); + + protected: + virtual void Ticker(); + + protected: + void Event_Touch( idEntity *other, trace_t *trace ); + void Event_Collision_Bounce( const trace_t* collision, const idVec3 &velocity ); + + void ApplyDamageEffect( idEntity* hitEnt, const trace_t* collision, const idVec3& velocity, const char* damageDefName ); + + protected: + int decelStart; + int decelEnd; + + idVec3 cachedVelocity; + bool collided; +}; + +#endif \ No newline at end of file diff --git a/src/Prey/prey_projectilegasbagpod.cpp b/src/Prey/prey_projectilegasbagpod.cpp new file mode 100644 index 0000000..d635ade --- /dev/null +++ b/src/Prey/prey_projectilegasbagpod.cpp @@ -0,0 +1,89 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION(hhProjectile, hhProjectileGasbagPod) + EVENT(EV_Collision_Flesh, hhProjectileGasbagPod::Event_Collision_SpawnPod) + EVENT(EV_Collision_Metal, hhProjectileGasbagPod::Event_Collision_Proj) + EVENT(EV_Collision_AltMetal, hhProjectileGasbagPod::Event_Collision_SpawnPod) + EVENT(EV_Collision_Wood, hhProjectileGasbagPod::Event_Collision_SpawnPod) + EVENT(EV_Collision_Stone, hhProjectileGasbagPod::Event_Collision_SpawnPod) + EVENT(EV_Collision_Glass, hhProjectileGasbagPod::Event_Collision_SpawnPod) + EVENT(EV_Collision_Liquid, hhProjectileGasbagPod::Event_Collision_SpawnPod) + EVENT(EV_Collision_CardBoard, hhProjectileGasbagPod::Event_Collision_SpawnPod) + EVENT(EV_Collision_Tile, hhProjectileGasbagPod::Event_Collision_SpawnPod) + EVENT(EV_Collision_Forcefield, hhProjectileGasbagPod::Event_Collision_SpawnPod) + EVENT(EV_Collision_Chaff, hhProjectileGasbagPod::Event_Collision_SpawnPod) + EVENT(EV_Collision_Wallwalk, hhProjectileGasbagPod::Event_Collision_SpawnPod) + EVENT(EV_Collision_Pipe, hhProjectileGasbagPod::Event_Collision_SpawnPod) + + EVENT(EV_AllowCollision_Flesh, hhProjectileGasbagPod::Event_AllowCollision_Collide) + EVENT(EV_AllowCollision_Metal, hhProjectileGasbagPod::Event_AllowCollision_Collide) + EVENT(EV_AllowCollision_AltMetal, hhProjectileGasbagPod::Event_AllowCollision_Collide) + EVENT(EV_AllowCollision_Wood, hhProjectileGasbagPod::Event_AllowCollision_Collide) + EVENT(EV_AllowCollision_Stone, hhProjectileGasbagPod::Event_AllowCollision_Collide) + EVENT(EV_AllowCollision_Glass, hhProjectileGasbagPod::Event_AllowCollision_Collide) + EVENT(EV_AllowCollision_Liquid, hhProjectileGasbagPod::Event_AllowCollision_Collide) + EVENT(EV_AllowCollision_CardBoard, hhProjectileGasbagPod::Event_AllowCollision_Collide) + EVENT(EV_AllowCollision_Tile, hhProjectileGasbagPod::Event_AllowCollision_Collide) + EVENT(EV_AllowCollision_Forcefield, hhProjectileGasbagPod::Event_AllowCollision_Collide) + EVENT(EV_AllowCollision_Pipe, hhProjectileGasbagPod::Event_AllowCollision_Collide) + EVENT(EV_AllowCollision_Wallwalk, hhProjectileGasbagPod::Event_AllowCollision_Collide) + EVENT(EV_AllowCollision_Spirit, hhProjectileGasbagPod::Event_AllowCollision_PassThru) + EVENT(EV_AllowCollision_Chaff, hhProjectileGasbagPod::Event_AllowCollision_Collide) //bjk: shield blocks all +END_CLASS + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +void hhProjectileGasbagPod::Spawn(void) { + BecomeActive(TH_TICKER); +} + +void hhProjectileGasbagPod::Ticker(void) { + renderEntity.shaderParms[SHADERPARM_ANY_DEFORM_PARM2] += .1f; + if (renderEntity.shaderParms[SHADERPARM_ANY_DEFORM_PARM2] > 1.0f) { + renderEntity.shaderParms[SHADERPARM_ANY_DEFORM_PARM2] = 1.0f; + BecomeInactive(TH_TICKER); + } +} + +void hhProjectileGasbagPod::Event_Collision_SpawnPod(const trace_t* collision, const idVec3 &velocity) { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + idVec3 avel = GetPhysics()->GetAngularVelocity(); + + physicsObj.PutToRest(); + physicsObj.SetContents(0); + PostEventMS(&EV_Remove, 0); + + idDict args; + args.Clear(); + args.Set("origin", (GetPhysics()->GetOrigin()).ToString()); + args.Set("axis", (GetPhysics()->GetAxis()).ToString()); + args.Set("nodrop", "1"); + + idEntity *ent = gameLocal.SpawnObject("object_pod", &args); + + if (ent) { + ent->GetPhysics()->SetLinearVelocity(vel); + ent->GetPhysics()->SetAngularVelocity(avel); + + if (owner.IsValid()) { + owner->PostEventMS(&EV_NewPod, 0, ent); + } + } + + idThread::ReturnInt(1); +} + +void hhProjectileGasbagPod::Event_Collision_Proj(const trace_t* collision, const idVec3 &velocity) { + idEntity *ent = gameLocal.entities[collision->c.entityNum]; + if (ent && ent->IsType(idProjectile::Type)) { + Explode(collision, velocity, 0); + } else { + Event_Collision_SpawnPod(collision, velocity); + } + + idThread::ReturnInt(0); +} + +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/src/Prey/prey_projectilegasbagpod.h b/src/Prey/prey_projectilegasbagpod.h new file mode 100644 index 0000000..83fffbb --- /dev/null +++ b/src/Prey/prey_projectilegasbagpod.h @@ -0,0 +1,20 @@ +#ifndef __HH_PROJECTILE_GASBAGPOD_H +#define __HH_PROJECTILE_GASBAGPOD_H + +class hhProjectileGasbagPod : public hhProjectile { + CLASS_PROTOTYPE(hhProjectileGasbagPod); +#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + void Event_Collision_SpawnPod(const trace_t* collision, const idVec3 &velocity) {}; + void Event_Collision_Proj(const trace_t* collision, const idVec3 &velocity) {}; +#else +public: + void Spawn(void); + virtual void Ticker(void); + +protected: + void Event_Collision_SpawnPod(const trace_t* collision, const idVec3 &velocity); + void Event_Collision_Proj(const trace_t* collision, const idVec3 &velocity); +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +}; + +#endif diff --git a/src/Prey/prey_projectilehiderweapon.cpp b/src/Prey/prey_projectilehiderweapon.cpp new file mode 100644 index 0000000..24df8e3 --- /dev/null +++ b/src/Prey/prey_projectilehiderweapon.cpp @@ -0,0 +1,136 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/*********************************************************************** + + hhProjectileHider + +***********************************************************************/ +CLASS_DECLARATION( hhProjectile, hhProjectileHider ) +END_CLASS + +void hhProjectileHider::ApplyDamageEffect( idEntity* hitEnt, const trace_t& collision, const idVec3& velocity, const char* damageDefName ) { + //This is used to allow the hider weapon shots to streak when colliding on the ground + if( hitEnt ) { + hitEnt->AddDamageEffect( collision, velocity.ToNormal(), damageDefName, (!fl.networkSync || netSyncPhysics) ); + } +} + +/*********************************************************************** + + hhProjectileHiderCanister + +***********************************************************************/ +const idEventDef EV_CollidedWithChaff( "", "tv", 'd' ); +CLASS_DECLARATION( hhProjectileHider, hhProjectileHiderCanister ) + EVENT( EV_Collision_Flesh, hhProjectileHiderCanister::Event_Collision_Explode ) + EVENT( EV_Collision_Metal, hhProjectileHiderCanister::Event_Collision_Explode ) + EVENT( EV_Collision_AltMetal, hhProjectileHiderCanister::Event_Collision_Explode ) + EVENT( EV_Collision_Wood, hhProjectileHiderCanister::Event_Collision_Explode ) + EVENT( EV_Collision_Stone, hhProjectileHiderCanister::Event_Collision_Explode ) + EVENT( EV_Collision_Glass, hhProjectileHiderCanister::Event_Collision_Explode ) + EVENT( EV_Collision_CardBoard, hhProjectileHiderCanister::Event_Collision_Explode ) + EVENT( EV_Collision_Forcefield, hhProjectileHiderCanister::Event_Collision_Explode ) + EVENT( EV_Collision_Pipe, hhProjectileHiderCanister::Event_Collision_Explode ) + EVENT( EV_Collision_Wallwalk, hhProjectileHiderCanister::Event_Collision_Explode ) + EVENT( EV_Collision_Tile, hhProjectileHiderCanister::Event_Collision_Explode ) + EVENT( EV_CollidedWithChaff, hhProjectileHiderCanister::Event_Collision_ExplodeChaff ) +END_CLASS + +void hhProjectileHiderCanister::Spawn() { + numSubProjectiles = spawnArgs.GetInt( "numSubProjectiles" ); + const idDict *dict = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_subProjectile"), false ); + if (!dict) { + gameLocal.Error( "No def_subProjectile defined for entity '%s'.\n", GetName() ); + } + subProjectileDict = *dict; + subSpread = DEG2RAD( spawnArgs.GetFloat("spread") ); + subBounce = spawnArgs.GetFloat( "subBounce", "1" ); + bScatter = spawnArgs.GetBool( "subScatter", "0" ); +} + +void hhProjectileHiderCanister::SpawnRicochetSpray( const idVec3& bounceVector ) { + hhProjectile* projectile = NULL; + idVec3 dir; + idMat3 projAxis; + idAngles projAngle; + idMat3 bounceAxis; + + if( !bScatter ) { + bounceAxis = bounceVector.ToNormal().ToMat3(); + subProjectileDict.SetVector( "velocity", idVec3(bounceVector.Length() * subBounce, 0.0f, 0.0f) ); + } + + for( int iIndex = 0; iIndex < numSubProjectiles; ++iIndex ) { + if( bScatter ) { + bounceAxis = hhUtils::RandomVector().ToNormal().ToMat3(); + subProjectileDict.SetVector( "velocity", idVec3(bounceVector.Length() * subBounce, 0.0f, 0.0f) ); + } + dir = hhUtils::RandomSpreadDir( bounceAxis, subSpread ); + projAngle = dir.ToAngles(); + projAngle[2] = GetAxis().ToAngles()[2]; + projAxis = projAngle.ToMat3(); + + //HUMANHEAD rww - now local + //projectile = hhProjectile::SpawnProjectile( &subProjectileDict ); + projectile = hhProjectile::SpawnClientProjectile( &subProjectileDict ); + projectile->spawnArgs.Set( "weapontype", spawnArgs.GetString("weapontype", "NONE1") ); + projectile->Create( owner.GetEntity(), GetOrigin(), bounceAxis ); + projectile->Launch( GetOrigin(), projAxis, vec3_zero ); + projectile->SetParentProjectile( this ); + } +} + +void hhProjectileHiderCanister::SpawnDebris( const idVec3& collisionNormal, const idVec3& collisionDir ) { + //Spawn debris along ground with respect to our current direction + idVec3 dir = collisionDir; + dir.ProjectOntoPlane( -GetPhysics()->GetGravityNormal() ); + dir.Normalize(); + hhProjectileHider::SpawnDebris( dir, collisionDir ); +} + +void hhProjectileHiderCanister::Event_Collision_Explode( const trace_t* collision, const idVec3& velocity ) { + hhProjectile::Event_Collision_Explode( collision, velocity ); + + idEntity *entityHit = gameLocal.entities[ collision->c.entityNum ]; + if (entityHit->IsType(idAI::Type) || entityHit->IsType(idPlayer::Type)) + numSubProjectiles = 0; + + SetOrigin(collision->endpos+collision->c.normal*8.0f); + + //rww - these are purely local now, because they cause bandwidth murder. + SpawnRicochetSpray( hhProjectile::GetBounceDirection(velocity, collision->c.normal, this, NULL) ); + + idThread::ReturnInt( 1 ); +} + +void hhProjectileHiderCanister::Event_Collision_ExplodeChaff( const trace_t* collision, const idVec3& velocity ) { + fl.takedamage = false; + hhProjectile::Event_Collision_Explode( collision, velocity ); + idThread::ReturnInt( 1 ); +} + + +void hhProjectileHiderCanister::Save( idSaveGame *savefile ) const { + savefile->WriteInt( numSubProjectiles ); + savefile->WriteDict( &subProjectileDict ); + savefile->WriteFloat( subSpread ); + savefile->WriteBool( bScatter ); +} + +void hhProjectileHiderCanister::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( numSubProjectiles ); + savefile->ReadDict( &subProjectileDict ); + savefile->ReadFloat( subSpread ); + savefile->ReadBool( bScatter ); + + subBounce = spawnArgs.GetFloat( "subBounce", "1" ); +} + +void hhProjectileHiderCanister::Killed( idEntity *inflicter, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + hhProjectileHider::Killed( inflicter, attacker, damage, dir, location ); + //bScatter = true; + SpawnRicochetSpray( dir * (damage * 10.0f) ); +} diff --git a/src/Prey/prey_projectilehiderweapon.h b/src/Prey/prey_projectilehiderweapon.h new file mode 100644 index 0000000..b9751c1 --- /dev/null +++ b/src/Prey/prey_projectilehiderweapon.h @@ -0,0 +1,45 @@ +#ifndef __HH_PROJECTILE_HIDER_WEAPON_H +#define __HH_PROJECTILE_HIDER_WEAPON_H + +/*********************************************************************** + + hhProjectileHider + +***********************************************************************/ +class hhProjectileHider : public hhProjectile { + CLASS_PROTOTYPE( hhProjectileHider ); + + protected: + virtual void ApplyDamageEffect( idEntity* entHit, const trace_t& collision, const idVec3& velocity, const char* damageDefName ); +}; + +/*********************************************************************** + + hhProjectileHiderCanister + +***********************************************************************/ +class hhProjectileHiderCanister : public hhProjectileHider { + CLASS_PROTOTYPE( hhProjectileHiderCanister ); + + public: + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + protected: + virtual void SpawnRicochetSpray( const idVec3& bounceVelocity ); + virtual void SpawnDebris( const idVec3& collisionNormal, const idVec3& collisionDir ); + virtual void Killed( idEntity *inflicter, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + void Event_Collision_Explode( const trace_t* collision, const idVec3& velocity ); + void Event_Collision_ExplodeChaff( const trace_t* collision, const idVec3& velocity ); + + protected: + int numSubProjectiles; + idDict subProjectileDict; + float subSpread; + float subBounce; + bool bScatter; +}; + +#endif diff --git a/src/Prey/prey_projectilemine.cpp b/src/Prey/prey_projectilemine.cpp new file mode 100644 index 0000000..ff88a5a --- /dev/null +++ b/src/Prey/prey_projectilemine.cpp @@ -0,0 +1,505 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/*********************************************************************** + + hhHarvesterMine + +***********************************************************************/ +const idEventDef EV_ApplyAttractionTowards( "applyAttractionTowards", "e" ); + +CLASS_DECLARATION( hhRenderEntity, hhHarvesterMine ) + EVENT( EV_Activate, hhHarvesterMine::Event_Detonate ) + EVENT( EV_ApplyAttractionTowards, hhHarvesterMine::Event_ApplyAttractionTowards ) + EVENT( EV_Explode, hhHarvesterMine::Event_Explode ) +END_CLASS + +/* +================ +hhHarvesterMine::hhHarvesterMine +================ +*/ +hhHarvesterMine::hhHarvesterMine() { + proximityDetonateTrigger = NULL; + proximityAttractionTrigger = NULL; +} + +/* +================ +hhHarvesterMine::~hhHarvesterMine +================ +*/ +hhHarvesterMine::~hhHarvesterMine() { + SAFE_REMOVE( proximityDetonateTrigger ); + SAFE_REMOVE( proximityAttractionTrigger ); +} + +/* +================ +hhHarvesterMine::Spawn +================ +*/ +void hhHarvesterMine::Spawn() { + SpawnTriggers(); +} + +/* +================ +hhHarvesterMine::InitPhysics +================ +*/ +void hhHarvesterMine::InitPhysics( const idVec3& start, const idMat3& axis, const idVec3& pushVelocity ) { + float speed; + float linear_friction; + float contact_friction; + float bounce; + float mass; + float gravity; + idVec3 gravVec; + + speed = spawnArgs.GetVector( "velocity", "0 0 0" ).Length(); + + linear_friction = spawnArgs.GetFloat( "linear_friction" ); + contact_friction = spawnArgs.GetFloat( "contact_friction" ); + bounce = spawnArgs.GetFloat( "bounce" ); + mass = spawnArgs.GetFloat( "mass" ); + gravity = spawnArgs.GetFloat( "gravity" ); + + if ( mass <= 0 ) { + gameLocal.Error( "Invalid mass on '%s'\n", GetClassname() ); + } + + gravVec = gameLocal.GetGravity(); + gravVec.NormalizeFast(); + + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + physicsObj.GetClipModel()->SetOwner( DetermineClipModelOwner() ); + physicsObj.SetMass( mass ); + physicsObj.SetFriction( linear_friction, 1.0f, contact_friction ); + + physicsObj.SetBouncyness( bounce ); + physicsObj.SetGravity( gravVec * gravity ); + physicsObj.SetContents( DetermineContents() ); + + physicsObj.SetClipMask( MASK_SHOT_RENDERMODEL ); + physicsObj.SetLinearVelocity( axis[ 0 ] * speed + pushVelocity ); + + physicsObj.SetOrigin( start ); + physicsObj.SetAxis( axis ); + SetPhysics( &physicsObj ); +} + +/* +================ +hhHarvesterMine::SpawnTriggers +================ +*/ +void hhHarvesterMine::SpawnTriggers() { + idDict dict; + + dict.SetVector( "origin", GetOrigin() ); + dict.SetMatrix( "rotation", GetAxis() ); + dict.Set( "target", name.c_str() ); + dict.SetInt( "triggerBehavior", TB_FRIENDLIES_ONLY ); + + dict.SetVector( "mins", spawnArgs.GetVector("detonationMins", "-10 -10 -10") ); + dict.SetVector( "maxs", spawnArgs.GetVector("detonationMaxs", "10 10 10") ); + proximityDetonateTrigger = gameLocal.SpawnObject( spawnArgs.GetString("def_detonateTrigger"), &dict ); + proximityDetonateTrigger->Bind( this, true ); + + dict.SetVector( "mins", spawnArgs.GetVector("attractionMins", "-20 -20 -20") ); + dict.SetVector( "maxs", spawnArgs.GetVector("attractionMaxs", "20 20 20") ); + dict.SetFloat( "refire", spawnArgs.GetFloat("attractionUpdateFrequency") ); + dict.Set( "eventDef", "applyAttractionTowards" ); + proximityAttractionTrigger = gameLocal.SpawnObject( spawnArgs.GetString("def_attractionTrigger"), &dict ); + proximityAttractionTrigger->Bind( this, true ); +} + +/* +================ +hhHarvesterMine::Create +================ +*/ +void hhHarvesterMine::Create( idEntity *owner, const idVec3 &start, const idMat3 &axis ) { + idStr shaderName; + idVec3 light_color; + idVec3 light_offset; + + Unbind(); + + SetOrigin( start ); + SetAxis( axis ); + + this->owner = owner; + + SIMDProcessor->Memset( &renderLight, 0, sizeof( renderLight ) ); + shaderName = spawnArgs.GetString( "mtr_light_shader" ); + if ( *shaderName ) { + renderLight.shader = declManager->FindMaterial( shaderName, false ); + renderLight.pointLight = true; + renderLight.lightRadius = spawnArgs.GetVector( "light_size" ); + 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; + } + + spawnArgs.GetVector( "light_offset", "0 0 0", lightOffset ); + + GetPhysics()->SetContents( 0 ); + + state = CREATED; +} + +/* +================= +hhHarvesterMine::Launch +================= +*/ +void hhHarvesterMine::Launch( const idVec3 &start, const idMat3& axis, const idVec3 &pushVelocity ) { + int anim = 0; + + if ( health ) { + fl.takedamage = true; + } + + Unbind(); + + //HUMANHEAD: aob - moved logic to helper function + InitPhysics( start, axis, pushVelocity ); + //HUMANHEAD END + + if ( !gameLocal.isClient ) { + PostEventSec( &EV_Explode, hhMath::hhMax(0.0f, spawnArgs.GetFloat("fuse")) ); + } + + StartSound( "snd_fly", SND_CHANNEL_BODY, 0, true, NULL ); + + state = LAUNCHED; +} + +/* +================ +hhHarvesterMine::Killed +================ +*/ +void hhHarvesterMine::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + //HUMANHEAD: aob - added collision so we can get explosion to show up when killed + trace_t collision; + + collision.fraction = 0.0f; + collision.endpos = GetOrigin(); + collision.endAxis = GetAxis(); + collision.c.entityNum = attacker->entityNumber; + collision.c.normal = inflictor->GetOrigin() - GetOrigin(); + collision.c.normal.Normalize(); + Explode( &collision ); +} + +/* +================ +hhHarvesterMine::Collide +================ +*/ +bool hhHarvesterMine::Collide( const trace_t& collision, const idVec3& velocity ) { + return false;//Never stop because of collision, just bounce +} + +/* +================ +hhHarvesterMine::DetermineContents +================ +*/ +int hhHarvesterMine::DetermineContents() { + return CONTENTS_SOLID; +} + +/* +================ +hhHarvesterMine::DetermineClipModelOwner +================ +*/ +idEntity* hhHarvesterMine::DetermineClipModelOwner() { + return (spawnArgs.GetBool("collideWithOwner")) ? this : owner.GetEntity(); +} + +/* +================= +hhHarvesterMine::RemoveProjectile +================= +*/ +void hhHarvesterMine::RemoveProjectile( const int removeDelay ) { + Hide(); + RemoveBinds();//Remove any fx we have because they aren't hidden + PostEventMS( &EV_Remove, removeDelay ); +} + +/* +================ +hhHarvesterMine::Explode +================ +*/ +void hhHarvesterMine::Explode( const trace_t *collision ) { + const char *light_shader; + float light_fadetime; + int removeDelay; + trace_t collisionInfo; + + if ( state == EXPLODED || state == FIZZLED ) { + return; + } + + //HUMANHEAD: aob + if( collision && collision->fraction < 1.0f ) { + memcpy( &collisionInfo, collision, sizeof(trace_t) ); + } else { + collisionInfo.fraction = 0.0f; + collisionInfo.endpos = GetOrigin(); + collisionInfo.endAxis = GetAxis(); + collisionInfo.c.entityNum = gameLocal.world->entityNumber; + collisionInfo.c.normal = idVec3(0.0f,0.0f,1.0f); + } + + removeDelay = spawnArgs.GetFloat( "remove_time", "200" ); + //HUMANHEAD END + + // play sound + //HUMANHEAD: aob - in case the sound length is longer than removeTime + int length = 0; + StartSound( "snd_explode", SND_CHANNEL_BODY, 0, true, &length ); + removeDelay = hhMath::hhMax( length, removeDelay ); + //HUMANHEAD END + + FreeLightDef(); + + // explosion light + light_shader = spawnArgs.GetString( "mtr_explode_light_shader" ); + if ( *light_shader ) { + renderLight.shader = declManager->FindMaterial( light_shader, false ); + renderLight.pointLight = true; + renderLight.lightRadius = spawnArgs.GetVector( "explode_light_size" ); + spawnArgs.GetVector( "explode_light_color", "1 1 1", lightColor ); + renderLight.shaderParms[SHADERPARM_RED] = lightColor[0]; + renderLight.shaderParms[SHADERPARM_GREEN] = lightColor[1]; + renderLight.shaderParms[SHADERPARM_BLUE] = lightColor[2]; + renderLight.shaderParms[SHADERPARM_ALPHA] = 1.0f; + renderLight.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time ); + light_fadetime = spawnArgs.GetFloat( "explode_light_fadetime" ); + lightStartTime = gameLocal.time; + lightEndTime = gameLocal.time + SEC2MS( light_fadetime ); + BecomeActive( TH_THINK ); + } + + if( !gameLocal.isClient ) { + SpawnCollisionFX( &collisionInfo, "fx_detonate" ); + SpawnDebris( collisionInfo.c.normal, physicsObj.GetLinearVelocity().ToMat3()[0] ); + } + + fl.takedamage = false; + physicsObj.SetContents( 0 ); + physicsObj.PutToRest(); + + state = EXPLODED; + + if ( gameLocal.isClient ) { + return; + } + + // splash damage + idStr splash_damage = spawnArgs.GetString( "def_splash_damage" ); + if ( splash_damage.Length() ) { + gameLocal.RadiusDamage( collisionInfo.endpos, this, owner.GetEntity(), this, this, splash_damage ); + } + + //HUMANHEAD: aob - moved logic to helper function + RemoveProjectile( removeDelay ); + //HUMANHEAD END +} + +/* +================ +hhHarvesterMine::SpawnCollisionFX +================ +*/ +void hhHarvesterMine::SpawnCollisionFX( const trace_t* collision, const char* fxKey ) { + hhFxInfo fxInfo; + + if( !collision || collision->fraction >= 1.0f ) { + return; + } + + fxInfo.SetNormal( collision->c.normal ); + fxInfo.RemoveWhenDone( true ); + + BroadcastFxInfoPrefixedRandom( fxKey, GetOrigin(), GetAxis(), &fxInfo ); +} + +/* +================ +hhHarvesterMine::SpawnDebris +================ +*/ +void hhHarvesterMine::SpawnDebris( const idVec3& collisionNormal, const idVec3& collisionDir ) { + int fxdebris = spawnArgs.GetInt( "debris_count" ); + if( !fxdebris ) { + return; + } + + idDebris *debris = NULL; + idEntity *ent = NULL; + int amount = 0; + const idDict *dict = NULL; + for( const idKeyValue* kv = spawnArgs.MatchPrefix("def_debris", NULL); kv; kv = spawnArgs.MatchPrefix("def_debris", kv) ) { + if( !kv->GetValue().Length() ) { + continue; + } + + dict = gameLocal.FindEntityDefDict( kv->GetValue().c_str(), false ); + if( !dict ) { + continue; + } + + amount = gameLocal.random.RandomInt( fxdebris ); + for ( int i = 0; i < amount; i++ ) { + //HUMANHEAD: aob + idVec3 dir = hhUtils::RandomSpreadDir( collisionNormal.ToMat3(), DEG2RAD(spawnArgs.GetFloat("spread_debris", "10")) ); + //HUMAMHEAD END + + gameLocal.SpawnEntityDef( *dict, &ent ); + if ( !ent || !ent->IsType( idDebris::Type ) ) { + gameLocal.Error( "hhProjectile: 'projectile_debris' is not an idDebris" ); + } + + debris = static_cast(ent); + debris->Create( owner.GetEntity(), GetOrigin(), dir.ToMat3() ); + debris->Launch(); + } + } +} + +/* +================ +hhHarvesterMine::SetGravity +================ +*/ +void hhHarvesterMine::SetGravity( const idVec3 &newGravity ) { + float relativeMagnitude = spawnArgs.GetFloat( "gravity" ); + idVec3 newGravityVector( vec3_zero ); + + if( GetGravity().Compare(newGravity, VECTOR_EPSILON) ) { + return; + } + + if( relativeMagnitude > 0.0f ) { + newGravityVector = newGravity; + relativeMagnitude *= newGravityVector.Normalize() / gameLocal.GetGravity().Length(); + newGravityVector *= relativeMagnitude; + } + + GetPhysics()->SetGravity( newGravityVector ); +} + +/* +================ +hhHarvesterMine::Event_Explode +================ +*/ +void hhHarvesterMine::Event_Explode( void ) { + trace_t collision; + + SIMDProcessor->Memset( &collision, 0, sizeof(trace_t) ); + collision.endpos = GetOrigin(); + collision.endAxis = GetAxis(); + collision.c.entityNum = ENTITYNUM_WORLD; + collision.c.normal = idVec3(0.0f, 0.0f, 1.0f); + Explode( &collision ); +} + +/* +================ +hhHarvesterMine::Event_Detonate +================ +*/ +void hhHarvesterMine::Event_Detonate( idEntity *activator ) { + trace_t collision; + + //Monsters and other harvesters are culled out by the trigger behavior + if( owner != activator ) { + SIMDProcessor->Memset( &collision, 0, sizeof(trace_t) ); + collision.endpos = GetOrigin(); + collision.endAxis = GetAxis(); + if(!activator) { + collision.c.entityNum = ENTITYNUM_WORLD; + collision.c.normal = idVec3(0.0f, 0.0f, 1.0f); + } else { + collision.c.entityNum = activator->entityNumber; + collision.c.normal = GetOrigin() - activator->GetOrigin(); + collision.c.normal.Normalize(); + } + + Explode( &collision ); + } +} + +/* +================ +hhHarvesterMine::Event_ApplyAttractionTowards +================ +*/ +void hhHarvesterMine::Event_ApplyAttractionTowards( idEntity *activator ) { + if( !activator || owner == activator ) { + return; + } + + idVec3 dirToTarget = ((activator->IsType(idActor::Type)) ? static_cast(activator)->GetEyePosition() : activator->GetOrigin()) - GetOrigin(); + dirToTarget.Normalize(); + + ApplyImpulse( this, 0, GetOrigin(), dirToTarget * spawnArgs.GetFloat("attractionMagnitude") ); +} + +/* +================ +hhHarvesterMine::Save +================ +*/ +void hhHarvesterMine::Save( idSaveGame *savefile ) const { + proximityDetonateTrigger.Save( savefile ); + proximityAttractionTrigger.Save( savefile ); + + savefile->WriteStaticObject( physicsObj ); + + owner.Save( savefile ); + + savefile->WriteRenderLight( renderLight ); + savefile->WriteVec3( lightOffset ); + savefile->WriteInt( lightStartTime ); + savefile->WriteInt( lightEndTime ); + savefile->WriteVec3( lightColor ); + savefile->WriteInt( state ); +} + +/* +================ +hhHarvesterMine::Restore +================ +*/ +void hhHarvesterMine::Restore( idRestoreGame *savefile ) { + proximityDetonateTrigger.Restore( savefile ); + proximityAttractionTrigger.Restore( savefile ); + + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); + + owner.Restore( savefile ); + + savefile->ReadRenderLight( renderLight ); + savefile->ReadVec3( lightOffset ); + savefile->ReadInt( lightStartTime ); + savefile->ReadInt( lightEndTime ); + savefile->ReadVec3( lightColor ); + savefile->ReadInt( reinterpret_cast ( state ) ); +} diff --git a/src/Prey/prey_projectilemine.h b/src/Prey/prey_projectilemine.h new file mode 100644 index 0000000..690a87c --- /dev/null +++ b/src/Prey/prey_projectilemine.h @@ -0,0 +1,70 @@ +#ifndef __HH_PROJECTILE_MINE_H +#define __HH_PROJECTILE_MINE_H + +/*********************************************************************** + + hhProjectileMine + +***********************************************************************/ +class hhHarvesterMine : public hhRenderEntity { + CLASS_PROTOTYPE( hhHarvesterMine ); + + public: + hhHarvesterMine(); + virtual ~hhHarvesterMine(); + void Spawn(); + + virtual void Create( idEntity *owner, const idVec3 &start, const idMat3 &axis ); + virtual void Launch( const idVec3 &start, const idMat3 &axis, const idVec3 &pushVelocity ); + virtual bool Collide( const trace_t& collision, const idVec3& velocity ); + + virtual void SetGravity( const idVec3 &newGravity ); + + protected: + virtual void SpawnDebris( const idVec3& collisionNormal, const idVec3& collisionDir ); + virtual void RemoveProjectile( const int removeDelay ); + virtual idEntity* DetermineClipModelOwner(); + virtual void SpawnCollisionFX( const trace_t* collision, const char* fxKey ); + + virtual void Explode( const trace_t *collision ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + virtual int DetermineContents(); + virtual void InitPhysics( const idVec3& start, const idMat3& axis, const idVec3& pushVelocity ); + void SpawnTriggers(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + protected: + void Event_Detonate( idEntity *activator ); + void Event_ApplyAttractionTowards( idEntity *activator ); + void Event_Explode(); + + protected: + idEntityPtr proximityDetonateTrigger; + idEntityPtr proximityAttractionTrigger; + + hhPhysics_RigidBodySimple physicsObj; + + idEntityPtr owner; + + renderLight_t renderLight; + //qhandle_t lightDefHandle; // handle to renderer light def + idVec3 lightOffset; + int lightStartTime; + int lightEndTime; + idVec3 lightColor; + + typedef enum { + SPAWNED = 0, + CREATED = 1, + LAUNCHED = 2, + FIZZLED = 3, + EXPLODED = 4 + } mineState_t; + + mineState_t state; +}; + +#endif \ No newline at end of file diff --git a/src/Prey/prey_projectilerifle.cpp b/src/Prey/prey_projectilerifle.cpp new file mode 100644 index 0000000..69f63a0 --- /dev/null +++ b/src/Prey/prey_projectilerifle.cpp @@ -0,0 +1,226 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/*********************************************************************** + + hhProjectileRifleSniper + +***********************************************************************/ +const idEventDef EV_AttemptFinalExitEventDef( "" ); + +CLASS_DECLARATION( hhProjectile, hhProjectileRifleSniper ) + EVENT( EV_AttemptFinalExitEventDef, hhProjectileRifleSniper::Event_AttemptFinalExitEventDef ) + EVENT( EV_AllowCollision_Flesh, hhProjectileRifleSniper::Event_AllowCollision_PassThru ) + EVENT( EV_AllowCollision_Wood, hhProjectileRifleSniper::Event_AllowCollision_PassThru ) + EVENT( EV_AllowCollision_Glass, hhProjectileRifleSniper::Event_AllowCollision_PassThru ) + EVENT( EV_AllowCollision_Pipe, hhProjectileRifleSniper::Event_AllowCollision_PassThru ) + EVENT( EV_AllowCollision_Metal, hhProjectileRifleSniper::Event_AllowCollision_PassThru ) + EVENT( EV_AllowCollision_AltMetal, hhProjectileRifleSniper::Event_AllowCollision_PassThru ) + EVENT( EV_AllowCollision_Tile, hhProjectileRifleSniper::Event_AllowCollision_PassThru ) + EVENT( EV_AllowCollision_CardBoard, hhProjectileRifleSniper::Event_AllowCollision_PassThru ) +END_CLASS + +/* +================= +hhProjectileRifleSniper::Spawn +================= +*/ +void hhProjectileRifleSniper::Spawn() { + numPassThroughs = 0.0f; + maxPassThroughs = hhMath::hhMax( 1.0f, spawnArgs.GetFloat("maxPassThroughs") ); +} + +/* +================= +hhProjectileRifleSniper::Event_AllowCollision_PassThru +================= +*/ +void hhProjectileRifleSniper::Event_AllowCollision_PassThru( const trace_t* collision ) { + assert( collision ); + + idEntity *entityHit = gameLocal.entities[ collision->c.entityNum ]; + + if (!entityHit->IsType(idAI::Type)) { + //gameLocal.Printf("STOP: [%s]\n", entityHit->GetName()); + hhProjectile::Event_AllowCollision_Collide( collision ); + return; + } + + if( lastDamagedEntity != entityHit ) { + lastDamagedEntity = entityHit; + + if( numPassThroughs < maxPassThroughs ) { + idVec3 myVel = GetPhysics()->GetLinearVelocity(); + idVec3 otherVel = entityHit->GetPhysics()->GetLinearVelocity(); + idVec3 vel = myVel - otherVel; + DamageEntityHit( collision, vel, entityHit ); + + numPassThroughs = hhMath::hhMin( numPassThroughs + 1.0f, maxPassThroughs ); + if( numPassThroughs >= maxPassThroughs ) { + ProcessEvent( &EV_AttemptFinalExitEventDef ); + } + } + } + + //gameLocal.Printf("PASS: [%s]\n", entityHit->GetName()); + hhProjectile::Event_AllowCollision_PassThru( collision ); +} + +/* +================= +hhProjectileRifleSniper::DetermineDamageScale +================= +*/ +float hhProjectileRifleSniper::DetermineDamageScale( const trace_t* collision ) const { + float scale = 1.0f - (numPassThroughs / maxPassThroughs); + + return scale; +} + +/* +================= +hhProjectileRifleSniper::DamageIsValid +================= +*/ +bool hhProjectileRifleSniper::DamageIsValid( const trace_t* collision, float& damageScale ) { + if( numPassThroughs < maxPassThroughs && hhProjectile::DamageIsValid(collision, damageScale) ) { + return true; + } + + RemoveProjectile( 0 ); + return false; +} + +void hhProjectileRifleSniper::Think() { + hhProjectile::Think(); + + // Still check for projectile outside of world, since sometimes that can make it + // through, at which point they stop simulating, but still play sound, have fx_fly, etc. + if( !gameLocal.clip.GetWorldBounds().ContainsPoint(GetOrigin()) ) { + RemoveProjectile( 0 ); + BecomeInactive( TH_ALL ); + } +} + +/* +================= +hhProjectileRifleSniper::Event_AttemptFinalExitEventDef +================= +*/ +void hhProjectileRifleSniper::Event_AttemptFinalExitEventDef() { + //Allow the projectile to do something behind final object hit + + CancelEvents( &EV_AttemptFinalExitEventDef ); + if( lastDamagedEntity.IsValid() && !lastDamagedEntity->GetPhysics()->GetAbsBounds().IntersectsBounds(GetPhysics()->GetAbsBounds()) ) { + CancelEvents( &EV_Remove ); + PostEventMS( GetInvalidDamageEventDef(), 0 ); + return; + } + + PostEventMS( &EV_AttemptFinalExitEventDef, USERCMD_MSEC ); +} + +/* +================= +hhProjectileRifleSniper::DamageEntityHit +================= +*/ +void hhProjectileRifleSniper::DamageEntityHit( const trace_t* collision, const idVec3& velocity, idEntity* entHit ) { + float push = 0.0f; + float damageScale = 1.0f; + const char *damage = spawnArgs.GetString( "def_damage" ); + hhPlayer* playerHit = (entHit->IsType(hhPlayer::Type)) ? static_cast(entHit) : NULL; + idAFEntity_Base* afHit = (entHit->IsType(idAFEntity_Base::Type)) ? static_cast(entHit) : NULL; + + idVec3 dir = velocity.ToNormal(); + + // non-radius damage defs can also apply an additional impulse to the rigid body physics impulse + const idDeclEntityDef *def = gameLocal.FindEntityDef( damage, false ); + if ( def ) { + if (entHit->IsType(hhProjectile::Type)) { + push = 0.0f; // mdl: Don't let projectiles push each other + } else if (afHit && afHit->IsActiveAF() ) { + push = def->dict.GetFloat( "push_ragdoll" ); + } else { + push = def->dict.GetFloat( "push" ); + } + } + + if (!gameLocal.isClient) { //rww + if( playerHit ) { + // pdm: save collision location in case we want to project a blob there + playerHit->playerView.SetDamageLoc( collision->endpos ); + } + + if ( entHit && entHit->IsType( idAI::Type ) ) { + idAI *aiHit = static_cast(entHit); + if ( aiHit && aiHit->InVehicle() ) { + idEntity *killer = owner.GetEntity(); + hhVehicle *vehicle = aiHit->GetVehicleInterface()->GetVehicle(); + if ( vehicle ) { + //use a different damagedef, since we cant affect the damage amount from here directly + damage = spawnArgs.GetString( "def_pilotdamage" ); + vehicle->Damage( this, killer, dir, damage, damageScale, CLIPMODEL_ID_TO_JOINT_HANDLE(collision->c.id) ); + } + } + } + + if( DamageIsValid(collision, damageScale) && entHit->fl.takedamage ) { + UpdateBalanceInfo( collision, entHit ); + + if( damage && damage[0] ) { + idEntity *killer = owner.GetEntity(); + if (killer && killer->IsType(hhVehicle::Type)) { //rww - handle vehicle projectiles killing people + hhVehicle *veh = static_cast(killer); + if (veh->GetPilot()) { + killer = veh->GetPilot(); + } + } + + entHit->Damage( this, killer, dir, damage, damageScale, CLIPMODEL_ID_TO_JOINT_HANDLE(collision->c.id) ); + + if ( playerHit && def->dict.GetInt( "freeze_duration" ) > 0 ) { + playerHit->Freeze( def->dict.GetInt( "freeze_duration" ) ); + } + } + } + + // HUMANHEAD bjk: moved to after damage so impulse can be applied to ragdoll + if ( push > 0.0f ) { + if (g_debugImpulse.GetBool()) { + gameRenderWorld->DebugArrow(colorYellow, collision->c.point, collision->c.point + (push*dir), 25, 2000); + } + + entHit->ApplyImpulse( this, collision->c.id, collision->c.point, push * dir ); + } + } + + if ( entHit->fl.applyDamageEffects ) { + ApplyDamageEffect( entHit, collision, velocity, damage ); + } +} + +/* +================ +hhProjectileRifleSniper::Save +================ +*/ +void hhProjectileRifleSniper::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( numPassThroughs ); + savefile->WriteFloat( maxPassThroughs ); + lastDamagedEntity.Save( savefile ); +} + +/* +================ +hhProjectileRifleSniper::Restore +================ +*/ +void hhProjectileRifleSniper::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( numPassThroughs ); + savefile->ReadFloat( maxPassThroughs ); + lastDamagedEntity.Restore( savefile ); +} + diff --git a/src/Prey/prey_projectilerifle.h b/src/Prey/prey_projectilerifle.h new file mode 100644 index 0000000..ad924a9 --- /dev/null +++ b/src/Prey/prey_projectilerifle.h @@ -0,0 +1,41 @@ +#ifndef __HH_PROJECTILE_RIFLE_SNIPER_H +#define __HH_PROJECTILE_RIFLE_SNIPER_H + +/*********************************************************************** + + hhProjectileRifleSniper + +***********************************************************************/ +extern const idEventDef EV_ApplyExitWound; +extern const idEventDef EV_AttemptFinalExitEventDef; + +class hhProjectileRifleSniper : public hhProjectile { + CLASS_PROTOTYPE( hhProjectileRifleSniper ); + + public: + void Spawn(); + virtual void Think(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + protected: + virtual float DetermineDamageScale( const trace_t* collision ) const; + virtual bool DamageIsValid( const trace_t* collision, float& damageScale ); + + virtual const idEventDef* GetInvalidDamageEventDef() const { return &EV_Fizzle; } + void DamageEntityHit( const trace_t* collision, const idVec3& velocity, idEntity* entHit ); + + protected: + void Event_AttemptFinalExitEventDef(); + + void Event_AllowCollision_PassThru( const trace_t* collision ); + + protected: + //Used floats so we can divide without casting + float numPassThroughs; + float maxPassThroughs; + idEntityPtr lastDamagedEntity; +}; + +#endif \ No newline at end of file diff --git a/src/Prey/prey_projectilerocketlauncher.cpp b/src/Prey/prey_projectilerocketlauncher.cpp new file mode 100644 index 0000000..4cf4e80 --- /dev/null +++ b/src/Prey/prey_projectilerocketlauncher.cpp @@ -0,0 +1,323 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/*********************************************************************** + + hhProjectileRocketLauncher + +***********************************************************************/ +const idEventDef EV_SpawnModelProxyLocal( "" ); + +CLASS_DECLARATION( hhProjectile, hhProjectileRocketLauncher ) + EVENT( EV_SpawnModelProxyLocal, hhProjectileRocketLauncher::Event_SpawnModelProxyLocal ) + EVENT( EV_SpawnFxFlyLocal, hhProjectileRocketLauncher::Event_SpawnFxFlyLocal ) + + EVENT( EV_Collision_Flesh, hhProjectileRocketLauncher::Event_Collision_Explode ) + EVENT( EV_Collision_Metal, hhProjectileRocketLauncher::Event_Collision_Explode ) + EVENT( EV_Collision_AltMetal, hhProjectileRocketLauncher::Event_Collision_Explode ) + EVENT( EV_Collision_Wood, hhProjectileRocketLauncher::Event_Collision_Explode ) + EVENT( EV_Collision_Stone, hhProjectileRocketLauncher::Event_Collision_Explode ) + EVENT( EV_Collision_Glass, hhProjectileRocketLauncher::Event_Collision_Explode ) + EVENT( EV_Collision_Liquid, hhProjectileRocketLauncher::Event_Collision_Explode ) + EVENT( EV_Collision_CardBoard, hhProjectileRocketLauncher::Event_Collision_Explode ) + EVENT( EV_Collision_Tile, hhProjectileRocketLauncher::Event_Collision_Explode ) + EVENT( EV_Collision_Forcefield, hhProjectileRocketLauncher::Event_Collision_Explode ) + //EVENT( EV_Collision_Chaff, hhProjectileRocketLauncher::Event_Collision_Explode ) + EVENT( EV_Collision_Pipe, hhProjectileRocketLauncher::Event_Collision_Explode ) + EVENT( EV_Collision_Wallwalk, hhProjectileRocketLauncher::Event_Collision_Explode ) + + EVENT( EV_AllowCollision_Chaff, hhProjectileRocketLauncher::Event_AllowCollision_Collide ) +END_CLASS + +/* +================ +hhProjectileRocketLauncher::Spawn +================ +*/ +void hhProjectileRocketLauncher::Spawn() { +} + +/* +================ +hhProjectileRocketLauncher::~hhProjectileRocketLauncher +================ +*/ +hhProjectileRocketLauncher::~hhProjectileRocketLauncher() { + SAFE_REMOVE( modelProxy ); +} + +/* +================= +hhProjectileRocketLauncher::Hide +================= +*/ +void hhProjectileRocketLauncher::Hide() { + hhProjectile::Hide(); + + if( modelProxy.IsValid() ) { + modelProxy->Hide(); + } +} + +/* +================= +hhProjectileRocketLauncher::Show +================= +*/ +void hhProjectileRocketLauncher::Show() { + hhProjectile::Show(); + + if( modelProxy.IsValid() ) { + modelProxy->Show(); + } +} + +/* +================= +hhProjectileRocketLauncher::RemoveProjectile +================= +*/ +void hhProjectileRocketLauncher::RemoveProjectile( const int removeDelay ) { + hhProjectile::RemoveProjectile( removeDelay ); + + if( modelProxy.IsValid() ) { + modelProxy->PostEventMS( &EV_Remove, removeDelay ); + } +} + +/* +================ +hhProjectileRocketLauncher::Launch +================ +*/ +void hhProjectileRocketLauncher::Launch( const idVec3 &start, const idMat3 &axis, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, const float dmgPower ) { + hhFxInfo fxInfo; + + ProcessEvent( &EV_SpawnModelProxyLocal ); //rww - was using BroadcastEventDef + + hhProjectile::Launch( start, axis, pushVelocity, timeSinceFire, launchPower, dmgPower ); + + if( modelProxy.IsValid() ) { + fxInfo.SetEntity( this ); + modelProxy->BroadcastFxInfoAlongBonePrefix( &spawnArgs, "fx_blood", "joint_bloodFx", false ); //rww - don't broadcast + } +} + +/* +================ +hhProjectileRocketLauncher::Event_SpawnFxFlyLocal +================ +*/ +void hhProjectileRocketLauncher::Event_SpawnFxFlyLocal( const char* defName ) { + hhFxInfo fxInfo; + + if( modelProxy.IsValid() ) { + fxInfo.SetEntity( this ); + modelProxy->SpawnFxAlongBonePrefixLocal( &spawnArgs, "fx_fly", "joint_flyFx", &fxInfo ); + } +} + +/* +================= +hhProjectileRocketLauncher::Event_SpawnModelProxyLocal +================= +*/ +void hhProjectileRocketLauncher::Event_SpawnModelProxyLocal() { + idDict args = spawnArgs; + + static const idMat3 pitchedOverAxis( idAngles(-90.0f, 0.0f, 0.0f).ToMat3() ); + + args.Delete( "spawnclass" ); + args.Delete( "name" ); + args.Delete( "spawn_entnum" ); //HUMANHEAD rww - yeah, might not be smart to try to spawn in the same entity slot. + + args.Set( "owner", GetName() ); + args.SetVector( "origin", GetOrigin() ); + args.SetMatrix( "rotation", pitchedOverAxis * GetAxis() ); + args.SetBool( "transferDamage", false ); + args.SetBool( "solid", false ); + if (!modelProxy.IsValid()) { + modelProxy = gameLocal.SpawnEntityTypeClient( hhGenericAnimatedPart::Type, &args ); //rww - proxy now localized + if( modelProxy.IsValid() ) { + modelProxy->fl.networkSync = false; + modelProxy->Bind( this, true ); + modelProxy->CycleAnim( "idle", ANIMCHANNEL_ALL ); + } + } + + //Get rid of our model. The modelProxy is our model now. + if (!gameLocal.isClient) { + SetModel( "" ); + } +} + +/* +================ +hhProjectileRocketLauncher::Save +================ +*/ +void hhProjectileRocketLauncher::Save( idSaveGame *savefile ) const { + modelProxy.Save( savefile ); +} + +/* +================ +hhProjectileRocketLauncher::Restore +================ +*/ +void hhProjectileRocketLauncher::Restore( idRestoreGame *savefile ) { + modelProxy.Restore( savefile ); +} + +void hhProjectileRocketLauncher::ClientPredictionThink( void ) { + if (!gameLocal.isNewFrame) { //HUMANHEAD rww + return; + } + + //rww - this code is duplicated here since the rocket projectile on the client is a little special-cased + // HUMANHEAD: cjr - if this projectile recently struck a portal, then attempt to portal it + if ( (thinkFlags & TH_MISC1) && collidedPortal.IsValid() ) { + GetPhysics()->SetLinearVelocity( collideVelocity ); + collidedPortal->PortalProjectile( this, collideLocation, collideLocation + collideVelocity ); + collidedPortal = NULL; + collideLocation = vec3_origin; + collideVelocity = vec3_origin; + BecomeInactive(TH_MISC1); + } + // HUMANHEAD END + + RunPhysics(); + + if ( thinkFlags & TH_MISC2 ) { + UpdateLight(); + } +} + +/* +================ +hhProjectileRocketLauncher::Event_AllowCollision_Collide +================ +*/ +void hhProjectileRocketLauncher::Event_AllowCollision_Collide( const trace_t* collision ) { + idThread::ReturnInt( 1 ); +} + +/*********************************************************************** + + hhProjectileChaff + +***********************************************************************/ +const idEventDef EV_CollidedWithChaff( "", "tv", 'd' ); +CLASS_DECLARATION( hhProjectile, hhProjectileChaff ) + EVENT( EV_Touch, hhProjectileChaff::Event_Touch ) + //FIXME: null out all of the events +END_CLASS + +/* +================ +hhProjectileChaff::Spawn +================ +*/ +void hhProjectileChaff::Spawn() { + decelStart = SEC2MS( spawnArgs.GetFloat("decelStart") ) + gameLocal.GetTime(); + decelEnd = SEC2MS( spawnArgs.GetFloat("decelDuration") ) + decelStart; + + BecomeActive( TH_TICKER ); +} + +/* +================ +hhProjectileChaff::Launch +================ +*/ +void hhProjectileChaff::Launch( const idVec3 &start, const idMat3 &axis, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, const float dmgPower ) { + hhProjectile::Launch( start, axis, pushVelocity, timeSinceFire, launchPower, dmgPower ); + + cachedVelocity = GetPhysics()->GetLinearVelocity(); + + fl.takedamage = false; + physicsObj.DisableImpact(); +} + +/* +================ +hhProjectileChaff::Collide +================ +*/ +bool hhProjectileChaff::Collide( const trace_t& collision, const idVec3& velocity ) { + // Let the target know + idEntity *ent = gameLocal.GetTraceEntity( collision ); + if ( ent && ent->RespondsTo( EV_CollidedWithChaff ) ) { + ent->PostEventMS( &EV_CollidedWithChaff, 0, &collision, velocity ); + } + + return true;//Always stop after collision +} + +/* +================ +hhProjectileChaff::DetermineContents +================ +*/ +int hhProjectileChaff::DetermineContents() { + // Removed PROJECTILE + return CONTENTS_BLOCK_RADIUSDAMAGE | CONTENTS_OWNER_TO_OWNER | CONTENTS_SHOOTABLE; +} + +/* +================ +hhProjectileChaff::DetermineClipmask +================ +*/ +int hhProjectileChaff::DetermineClipmask() { + // Removed SHOOTABLE, added PROJECTILE + return CONTENTS_PROJECTILE|CONTENTS_SOLID|CONTENTS_RENDERMODEL|CONTENTS_CORPSE|CONTENTS_WATER|CONTENTS_FORCEFIELD; +} + + +/* +================ +hhProjectileChaff::Ticker +================ +*/ +void hhProjectileChaff::Ticker() { + float scale = 0.0f; + if( gameLocal.GetTime() > decelStart && gameLocal.GetTime() < decelEnd ) { + scale = hhMath::Sin( DEG2RAD(hhMath::MidPointLerp( 0.0f, 30.0f, 90.0f, 1.0f - hhUtils::CalculateScale(gameLocal.GetTime(), decelStart, decelEnd))) ); + + GetPhysics()->SetLinearVelocity( cachedVelocity * hhMath::ClampFloat(0.05f, 1.0f, scale) ); + } +} + +/* +================ +hhProjectileChaff::Event_Touch +================ +*/ +void hhProjectileChaff::Event_Touch( idEntity *other, trace_t *trace ) { + //Supposed to be empty +} + +/* +================ +hhProjectileChaff::Save +================ +*/ +void hhProjectileChaff::Save( idSaveGame *savefile ) const { + savefile->WriteInt( decelStart ); + savefile->WriteInt( decelEnd ); + savefile->WriteVec3( cachedVelocity ); +} + +/* +================ +hhProjectileChaff::Restore +================ +*/ +void hhProjectileChaff::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( decelStart ); + savefile->ReadInt( decelEnd ); + savefile->ReadVec3( cachedVelocity ); +} diff --git a/src/Prey/prey_projectilerocketlauncher.h b/src/Prey/prey_projectilerocketlauncher.h new file mode 100644 index 0000000..70a6a9b --- /dev/null +++ b/src/Prey/prey_projectilerocketlauncher.h @@ -0,0 +1,70 @@ +#ifndef __HH_PROJECTILE_ROCKET_LAUNCHER_H +#define __HH_PROJECTILE_ROCKET_LAUNCHER_H + +/*********************************************************************** + + hhProjectileRocketLauncher + +***********************************************************************/ +class hhProjectileRocketLauncher : public hhProjectile { + CLASS_PROTOTYPE( hhProjectileRocketLauncher ); + + public: + void Spawn(); + virtual ~hhProjectileRocketLauncher(); + + virtual void Launch( const idVec3 &start, const idMat3 &axis, const idVec3 &pushVelocity, const float timeSinceFire = 0.0f, const float launchPower = 1.0f, const float dmgPower = 1.0f ); + + virtual void Hide(); + virtual void Show(); + virtual void RemoveProjectile( const int removeDelay ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void ClientPredictionThink( void ); + + protected: + void Event_SpawnModelProxyLocal(); + void Event_SpawnFxFlyLocal( const char* defName ); + + void Event_AllowCollision_Collide( const trace_t* collision ); + + protected: + idEntityPtr modelProxy; +}; + +/*********************************************************************** + + hhProjectileChaff + +***********************************************************************/ +class hhProjectileChaff : public hhProjectile { + CLASS_PROTOTYPE( hhProjectileChaff ); + + public: + void Spawn(); + + virtual void Launch( const idVec3 &start, const idMat3 &axis, const idVec3 &pushVelocity, const float timeSinceFire = 0.0f, const float launchPower = 1.0f, const float dmgPower = 1.0f ); + virtual bool Collide( const trace_t& collision, const idVec3& velocity ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + protected: + virtual void Ticker(); + + virtual int DetermineContents(); + virtual int DetermineClipmask(); + + protected: + void Event_Touch( idEntity *other, trace_t *trace ); + + protected: + int decelStart; + int decelEnd; + + idVec3 cachedVelocity; +}; + +#endif \ No newline at end of file diff --git a/src/Prey/prey_projectileshuttle.cpp b/src/Prey/prey_projectileshuttle.cpp new file mode 100644 index 0000000..3e51272 --- /dev/null +++ b/src/Prey/prey_projectileshuttle.cpp @@ -0,0 +1,22 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION( hhProjectile, hhProjectileShuttle ) + EVENT( EV_Collision_Flesh, hhProjectileShuttle::Event_Collision_Explode ) + EVENT( EV_Collision_Metal, hhProjectileShuttle::Event_Collision_Explode ) + EVENT( EV_Collision_AltMetal, hhProjectileShuttle::Event_Collision_Explode ) + EVENT( EV_Collision_Wood, hhProjectileShuttle::Event_Collision_Explode ) + EVENT( EV_Collision_Stone, hhProjectileShuttle::Event_Collision_Explode ) + EVENT( EV_Collision_Glass, hhProjectileShuttle::Event_Collision_Explode ) + EVENT( EV_Collision_Liquid, hhProjectileShuttle::Event_Collision_Explode ) + EVENT( EV_Collision_CardBoard, hhProjectileShuttle::Event_Collision_Explode ) + EVENT( EV_Collision_Tile, hhProjectileShuttle::Event_Collision_Explode ) + EVENT( EV_Collision_Forcefield, hhProjectileShuttle::Event_Collision_Explode ) + EVENT( EV_Collision_Chaff, hhProjectileShuttle::Event_Collision_Explode ) + EVENT( EV_Collision_Pipe, hhProjectileShuttle::Event_Collision_Explode ) + EVENT( EV_Collision_Wallwalk, hhProjectileShuttle::Event_Collision_Explode ) + + EVENT( EV_AllowCollision_Chaff, hhProjectileShuttle::Event_AllowCollision_Collide ) +END_CLASS \ No newline at end of file diff --git a/src/Prey/prey_projectileshuttle.h b/src/Prey/prey_projectileshuttle.h new file mode 100644 index 0000000..4165fad --- /dev/null +++ b/src/Prey/prey_projectileshuttle.h @@ -0,0 +1,8 @@ +#ifndef __HH_PROJECTILE_SHUTTLE_H +#define __HH_PROJECTILE_SHUTTLE_H + +class hhProjectileShuttle : public hhProjectile { + CLASS_PROTOTYPE( hhProjectileShuttle ) +}; + +#endif \ No newline at end of file diff --git a/src/Prey/prey_projectilesoulcannon.cpp b/src/Prey/prey_projectilesoulcannon.cpp new file mode 100644 index 0000000..5f96598 --- /dev/null +++ b/src/Prey/prey_projectilesoulcannon.cpp @@ -0,0 +1,143 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +#include "prey_local.h" + +/*********************************************************************** + + hhProjectileSoulCannon + +***********************************************************************/ + +const idEventDef EV_FindSoulEnemy( "", NULL ); + +CLASS_DECLARATION( hhProjectile, hhProjectileSoulCannon ) + EVENT( EV_FindSoulEnemy, hhProjectileSoulCannon::Event_FindEnemy ) +END_CLASS + +//============================================================================= +// +// hhProjectileSoulCannon::Spawn +// +//============================================================================= + +void hhProjectileSoulCannon::Spawn() { + hhProjectile::Spawn(); + + BecomeActive( TH_THINK ); + + PostEventSec( &EV_FindSoulEnemy, 0.5f ); // Find an enemy shortly after being launched + + maxVelocity = spawnArgs.GetFloat( "maxVelocity", "400" ); + maxEnemyDist = spawnArgs.GetFloat( "maxEnemyDist", "4096" ); + + thrustDir = vec3_origin; +} + +//============================================================================= +// +// hhProjectileSoulCannon::Think +// +//============================================================================= + +void hhProjectileSoulCannon::Think( void ) { + // run physics + RunPhysics(); + + // Thrust toward enemy + if ( thinkFlags & TH_THINK && thrustDir != vec3_origin ) { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += thrustDir * spawnArgs.GetFloat( "soulThrust", "5.0" ); + + if ( vel.Length() > maxVelocity ) { // Cap the velocity + vel.Normalize(); + vel *= maxVelocity; + } + + GetPhysics()->SetLinearVelocity( vel ); + GetPhysics()->SetAxis( vel.ToMat3() ); + } + + //HUMANHEAD: aob + if (thinkFlags & TH_TICKER) { + Ticker(); + } + //HUMANHEAD + + Present(); +} + +//============================================================================= +// +// hhProjectileSoulCannon::Event_FindEnemy +// +// Finds a new enemy every second +//============================================================================= + +void hhProjectileSoulCannon::Event_FindEnemy( void ) { + int i; + idEntity *ent; + idActor *actor; + trace_t tr; + + PostEventSec( &EV_FindSoulEnemy, 1.0f ); + + for ( i = 0; i < gameLocal.num_entities; i++ ) { + ent = gameLocal.entities[ i ]; + + if ( !ent || !ent->IsType( idActor::Type ) ) { + continue; + } + + if ( ent == this || ent == this->GetOwner() ) { + continue; + } + + // Ignore dormant entities! + if( ent->IsHidden() || ent->fl.isDormant ) { // HUMANHEAD JRM - changed to fl.isDormant + continue; + } + + actor = static_cast( ent ); + if ( ( actor->health <= 0 ) ) { + continue; + } + + idVec3 center = actor->GetPhysics()->GetAbsBounds().GetCenter(); + + // Cannot see this enemy because he is too far away + if ( maxEnemyDist > 0.0f && ( center - GetOrigin() ).LengthSqr() > maxEnemyDist * maxEnemyDist ) + continue; + + // Quick trace to the center of the potential enemy + if ( !gameLocal.clip.TracePoint( tr, GetOrigin(), center, 1.0f, this ) ) { + thrustDir = center - GetOrigin(); + thrustDir.Normalize(); + break; + } + } +} + +/* +================ +hhProjectileSoulCannon::Save +================ +*/ +void hhProjectileSoulCannon::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( maxVelocity ); + savefile->WriteFloat( maxEnemyDist ); + savefile->WriteVec3( thrustDir ); +} + +/* +================ +hhProjectileSoulCannon::Restore +================ +*/ +void hhProjectileSoulCannon::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( maxVelocity ); + savefile->ReadFloat( maxEnemyDist ); + savefile->ReadVec3( thrustDir ); +} +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/src/Prey/prey_projectilesoulcannon.h b/src/Prey/prey_projectilesoulcannon.h new file mode 100644 index 0000000..f52e2be --- /dev/null +++ b/src/Prey/prey_projectilesoulcannon.h @@ -0,0 +1,30 @@ +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +#ifndef __HH_PROJECTILE_SOUL_CANNON_H +#define __HH_PROJECTILE_SOUL_CANNON_H + +/*********************************************************************** + + hhProjectileSoulCannon + +***********************************************************************/ + +class hhProjectileSoulCannon : public hhProjectile { + CLASS_PROTOTYPE( hhProjectileSoulCannon ); + + public: + void Spawn(); + void Think( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + protected: + float maxVelocity; + float maxEnemyDist; + idVec3 thrustDir; + + void hhProjectileSoulCannon::Event_FindEnemy( void ); +}; + +#endif +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/src/Prey/prey_projectilespiritarrow.cpp b/src/Prey/prey_projectilespiritarrow.cpp new file mode 100644 index 0000000..7ad78b0 --- /dev/null +++ b/src/Prey/prey_projectilespiritarrow.cpp @@ -0,0 +1,105 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/*********************************************************************** + + hhProjectileSpiritArrow + +***********************************************************************/ +CLASS_DECLARATION( hhProjectile, hhProjectileSpiritArrow ) + EVENT( EV_AllowCollision_Spirit, hhProjectile::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_Chaff, hhProjectile::Event_AllowCollision_PassThru ) + + EVENT( EV_AllowCollision_Flesh, hhProjectileSpiritArrow::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_Metal, hhProjectileSpiritArrow::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_AltMetal, hhProjectileSpiritArrow::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_Wood, hhProjectileSpiritArrow::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_Stone, hhProjectileSpiritArrow::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_Glass, hhProjectileSpiritArrow::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_Pipe, hhProjectileSpiritArrow::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_Tile, hhProjectileSpiritArrow::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_CardBoard, hhProjectileSpiritArrow::Event_AllowCollision_Collide ) + + EVENT( EV_Collision_Flesh, hhProjectileSpiritArrow::Event_Collision_Stick ) + EVENT( EV_Collision_Metal, hhProjectileSpiritArrow::Event_Collision_Stick ) + EVENT( EV_Collision_AltMetal, hhProjectileSpiritArrow::Event_Collision_Stick ) + EVENT( EV_Collision_Wood, hhProjectileSpiritArrow::Event_Collision_Stick ) + EVENT( EV_Collision_Stone, hhProjectileSpiritArrow::Event_Collision_Stick ) + EVENT( EV_Collision_Glass, hhProjectileSpiritArrow::Event_Collision_Stick ) + EVENT( EV_Collision_Wallwalk, hhProjectileSpiritArrow::Event_Collision_Stick ) + EVENT( EV_Collision_Pipe, hhProjectileSpiritArrow::Event_Collision_Stick ) + EVENT( EV_Collision_CardBoard, hhProjectileSpiritArrow::Event_Collision_Stick ) + EVENT( EV_Collision_Spirit, hhProjectileSpiritArrow::Event_Collision_Impact ) + EVENT( EV_Collision_Chaff, hhProjectileSpiritArrow::Event_Collision_Impact ) +END_CLASS + + +int hhProjectileSpiritArrow::DetermineClipmask() { + return MASK_SPIRITARROW; +} + +/* +================= +hhProjectileSpiritArrow::BindToCollisionObject +================= +*/ +void hhProjectileSpiritArrow::BindToCollisionObject( const trace_t* collision ) { + if( !collision || collision->fraction == 1.0f ) { + return; + } + + idEntity* pEntity = gameLocal.entities[collision->c.entityNum]; + HH_ASSERT( pEntity ); + + if( !pEntity->fl.applyDamageEffects ) { + PostEventMS( &EV_Fizzle, 0 ); + return; + } + + if ( pEntity->spawnArgs.GetBool( "no_arrow_stick" ) ) { + RemoveProjectile( 0 ); + return; + } + + jointHandle_t jointHandle = CLIPMODEL_ID_TO_JOINT_HANDLE( collision->c.id ); + idVec3 penetrationVector( collision->endAxis[0] * spawnArgs.GetFloat("penetrationDepth", "10") ); // Push the arrow into the hit object a bit + if ( jointHandle != INVALID_JOINT ) { + SetOrigin( collision->endpos + penetrationVector ); + BindToJoint( pEntity, jointHandle, true ); + } else { + SetOrigin( collision->endpos + penetrationVector ); + Bind( pEntity, true ); + } +} + +/* +================= +hhProjectileSpiritArrow::Event_Collision_Stick +================= +*/ +void hhProjectileSpiritArrow::Event_Collision_Stick( const trace_t* collision, const idVec3 &velocity ) { + + ProcessCollision( collision, velocity );//Assuming that EV_Fizzle is canceled in ProcessCollision + + if (gameLocal.isMultiplayer) { //rww - in mp we don't stick to players. + idEntity *pEntity = gameLocal.entities[collision->c.entityNum]; + if (pEntity && pEntity->IsType(hhPlayer::Type)) { + PostEventMS( &EV_Fizzle, 0 ); + + idThread::ReturnInt( 1 ); + return; + } + } + + PostEventMS( &EV_Fizzle, spawnArgs.GetInt("remove_time") ); + + BindToCollisionObject( collision ); + + fl.ignoreGravityZones = true; + SetGravity( idVec3(0.f, 0.f, 0.f) ); + spawnArgs.SetVector("gravity", idVec3(0.f, 0.f, 0.f) ); + + idThread::ReturnInt( 1 ); +} \ No newline at end of file diff --git a/src/Prey/prey_projectilespiritarrow.h b/src/Prey/prey_projectilespiritarrow.h new file mode 100644 index 0000000..5de2b73 --- /dev/null +++ b/src/Prey/prey_projectilespiritarrow.h @@ -0,0 +1,20 @@ +#ifndef __HH_PROJECTILE_SPIRITARROW_H +#define __HH_PROJECTILE_SPIRITARROW_H + +/*********************************************************************** + + hhProjectileSpiritArrow + +***********************************************************************/ +class hhProjectileSpiritArrow: public hhProjectile { + CLASS_PROTOTYPE( hhProjectileSpiritArrow ); + + protected: + virtual int DetermineClipmask(); + void BindToCollisionObject( const trace_t* collision ); + + protected: + void Event_Collision_Stick( const trace_t* collision, const idVec3 &velocity ); +}; + +#endif \ No newline at end of file diff --git a/src/Prey/prey_projectiletracking.cpp b/src/Prey/prey_projectiletracking.cpp new file mode 100644 index 0000000..cdbcaca --- /dev/null +++ b/src/Prey/prey_projectiletracking.cpp @@ -0,0 +1,330 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/*********************************************************************** + + hhProjectileTracking + +***********************************************************************/ +const idEventDef EV_Guide( "" ); +const idEventDef EV_Hover( "" ); +const idEventDef EV_StartTracking( "" ); +const idEventDef EV_StopTracking( "" ); + +CLASS_DECLARATION( hhProjectile, hhProjectileTracking ) + EVENT( EV_Guide, hhProjectileTracking::Event_TrackTarget ) + EVENT( EV_Hover, hhProjectileTracking::Event_Hover ) + EVENT( EV_StartTracking, hhProjectileTracking::Event_StartTracking ) + EVENT( EV_StopTracking, hhProjectileTracking::Event_StopTracking ) + + EVENT( EV_Collision_Flesh, hhProjectileTracking::Event_Collision_Explode ) + EVENT( EV_Collision_Metal, hhProjectileTracking::Event_Collision_Explode ) + EVENT( EV_Collision_AltMetal, hhProjectileTracking::Event_Collision_Explode ) + EVENT( EV_Collision_Wood, hhProjectileTracking::Event_Collision_Explode ) + EVENT( EV_Collision_Stone, hhProjectileTracking::Event_Collision_Explode ) + EVENT( EV_Collision_Glass, hhProjectileTracking::Event_Collision_Explode ) + EVENT( EV_Collision_Liquid, hhProjectileTracking::Event_Collision_Explode ) + EVENT( EV_Collision_CardBoard, hhProjectileTracking::Event_Collision_Explode ) + EVENT( EV_Collision_Tile, hhProjectileTracking::Event_Collision_Explode ) + EVENT( EV_Collision_Forcefield, hhProjectileTracking::Event_Collision_Explode ) + EVENT( EV_Collision_Chaff, hhProjectileTracking::Event_Collision_Explode ) + EVENT( EV_Collision_Wallwalk, hhProjectileTracking::Event_Collision_Explode ) + EVENT( EV_Collision_Pipe, hhProjectileTracking::Event_Collision_Explode ) + + EVENT( EV_AllowCollision_Chaff, hhProjectileTracking::Event_AllowCollision_Collide ) +END_CLASS + + + +hhProjectileTracking::hhProjectileTracking() { + spinAngle = 0.0f; // Needs to be initialized before spawn since GetPhysicsToVisualTransform() is called before spawn +} + +/* +================ +hhProjectileTracking::Spawn +================ +*/ +void hhProjectileTracking::Spawn() { + angularVelocity.Zero(); + velocity.Zero(); + + cachedFovCos = idMath::Cos( DEG2RAD(spawnArgs.GetFloat("fov", "90")) ); + turnFactor = spawnArgs.GetFloat("turnfactor"); + updateRate = spawnArgs.GetInt("trackingUpdateRate"); + turnFactorAcc = spawnArgs.GetFloat( "turn_factor_accel", "1.1" ); + spinDelta = spawnArgs.GetFloat( "spin_delta" ); + + //rww - some things are hhProjectileTracking when they shouldn't be or don't have proper properties. if this is so, complain. + if (!updateRate) { + gameLocal.Warning("hhProjectileTracking with an updateRate of 0 (possible infinite event queue)."); + } +} + +/* +================ +hhProjectileTracking::IsEnemyValid +================ +*/ +bool hhProjectileTracking::EnemyIsValid( idEntity* ent ) const { + if ( ent && ent->IsType( idActor::Type ) ) { + idActor *entActor = static_cast(ent); + return ( entActor->GetHealth() > 0 && entActor->team!= 0 && !entActor->IsHidden() && gameLocal.InPlayerPVS(entActor) ); + } + return ( ent && ent->GetHealth() > 0 && !ent->IsHidden() && gameLocal.InPlayerPVS(ent) ); +} + +/* +================ +hhProjectileTracking::WhosClosest +================ +*/ +idEntity* hhProjectileTracking::WhosClosest( idEntity* possibleEnemy, idEntity* currentEnemy, float& currentEnemyDot, float& currentEnemyDist ) const { + if( !possibleEnemy ) { + return currentEnemy; + } + + idVec3 enemyDir = DetermineEnemyPosition( possibleEnemy ) - GetOrigin(); + float cachedDist = enemyDir.Normalize(); + float cachedDot = enemyDir * GetAxis()[0]; + + //AOB: Think about putting in tolerances for deciding which is better, + if( cachedDot > cachedFovCos && cachedDot > currentEnemyDot && cachedDist <= currentEnemyDist ) { + currentEnemyDot = cachedDot; + currentEnemyDist = cachedDist; + return possibleEnemy; + } + + return currentEnemy; +} + +/* +================ +hhProjectileTracking::DetermineEnemy +================ +*/ +idEntity* hhProjectileTracking::DetermineEnemy() { + idEntity* possibleEnemy = NULL; + float currentEnemyDot = 0.0f; + float currentEnemyDist = CM_MAX_TRACE_DIST; + idEntity* localEnemy = NULL; + + if ( spawnArgs.GetInt( "trackPlayersOnly", "0" ) ) { + for( int i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] ) { + possibleEnemy = gameLocal.entities[i]; + if ( possibleEnemy && possibleEnemy->IsType( hhPlayer::Type ) ) { + hhPlayer *player = static_cast(possibleEnemy); + if ( player && player->IsSpiritOrDeathwalking() ) { + possibleEnemy = player->GetSpiritProxy(); + } + } + localEnemy = WhosClosest( possibleEnemy, localEnemy, currentEnemyDot, currentEnemyDist ); + } + } + + return localEnemy; + } + int num = hhMonsterAI::allSimpleMonsters.Num(); + for( int index = 0; index < num; ++index ) { + possibleEnemy = hhMonsterAI::allSimpleMonsters[ index ]; + if( EnemyIsValid(possibleEnemy) ) { + localEnemy = WhosClosest( possibleEnemy, localEnemy, currentEnemyDot, currentEnemyDist ); + } + } + + return localEnemy; +} + +/* +================ +hhProjectileTracking::Launch +================ +*/ +void hhProjectileTracking::Launch( const idVec3 &start, const idMat3 &axis, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, const float dmgPower ) { + hhProjectile::Launch( start, axis, pushVelocity, timeSinceFire, launchPower, dmgPower ); + + enemy = DetermineEnemy(); + + float randomStartSpread = spawnArgs.GetFloat( "randomStartSpread", "0" ); + if ( randomStartSpread > 0.0 ) { + velocity = spawnArgs.GetVector( "velocity" ).Length() * hhUtils::RandomSpreadDir( GetPhysics()->GetAxis(), 1.0 ); + } else { + velocity = spawnArgs.GetVector( "velocity" ); + } + angularVelocity = spawnArgs.GetAngles( "angular_velocity" ); + float trackDelay = spawnArgs.GetFloat( "trackDelay", "0" ); + if( trackDelay > 0.0 ) { + PostEventSec( &EV_Hover, spawnArgs.GetFloat( "trackStop", "0" ) ); + PostEventSec( &EV_StartTracking, trackDelay ); + } else if ( enemy.IsValid() ) { + PostEventMS( &EV_Guide, updateRate ); + } + float trackDuration = spawnArgs.GetFloat( "trackDuration", "0" ); + if ( trackDuration > 0.0 ) { + PostEventSec( &EV_StopTracking, trackDuration ); + } +} + +void hhProjectileTracking::Event_Hover() { + physicsObj.SetLinearVelocity( velocity * spawnArgs.GetFloat( "hoverScale", "0.15" ) ); +} + +void hhProjectileTracking::Event_StartTracking() { + float randomStartSpread = spawnArgs.GetFloat( "randomStartSpread", "0" ); + if ( randomStartSpread > 0.0 ) { + velocity = spawnArgs.GetVector( "velocity" ); + velocity.y = gameLocal.random.CRandomFloat() * randomStartSpread; + velocity.z = gameLocal.random.CRandomFloat() * randomStartSpread; + } + PostEventMS( &EV_Guide, updateRate ); +} + +void hhProjectileTracking::Event_StopTracking() { + CancelEvents(&EV_Guide); +} + +/* +================ +hhProjectileTracking::DetermineEnemyPosition +================ +*/ +idVec3 hhProjectileTracking::DetermineEnemyPosition( const idEntity* ent ) const { + if ( ent && ent->IsType( idActor::Type ) ) { + const idActor *entActor = static_cast(ent); + return entActor->GetEyePosition(); + } + + return ent->GetOrigin(); +} + +/* +================ +hhProjectileTracking::DetermineEnemyDir +================ +*/ +idVec3 hhProjectileTracking::DetermineEnemyDir( const idEntity* actor ) const { + idVec3 enemyPos = DetermineEnemyPosition( actor ); + idVec3 enemyDir = enemyPos - GetOrigin(); + enemyDir.Normalize(); + + return enemyDir; +} + +/* +================ +hhProjectileTracking::Explode +================ +*/ +void hhProjectileTracking::Explode( const trace_t *collision, const idVec3& velocity, int removeDelay ) { + hhProjectile::Explode( collision, velocity, removeDelay ); + CancelEvents(&EV_Guide); +} + +/* +================ +hhProjectileTracking::GetPhysicsToVisualTransform +================ +*/ +bool hhProjectileTracking::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + if ( spinDelta != 0.0f ) { + axis = idAngles(0,0,spinAngle).ToMat3(); + spinAngle += spinDelta; + if ( spinAngle > 360.0f ) { + spinAngle = 0.0f; + } + return true; + } + + return hhProjectile::GetPhysicsToVisualTransform( origin, axis ); +} + +/* +================ +hhProjectileTracking::Event_TrackTarget +================ +*/ +void hhProjectileTracking::Event_TrackTarget() { + if ( !spawnArgs.GetInt( "constantEnemy", "1" ) ) { + enemy = DetermineEnemy(); + } + + if( !enemy.IsValid() ) { + physicsObj.SetLinearVelocity( GetAxis()[ 0 ] * velocity[ 0 ] + GetAxis()[ 1 ] * velocity[ 1 ] + GetAxis()[ 2 ] * velocity[ 2 ] ); + physicsObj.SetAngularVelocity( angularVelocity.ToAngularVelocity() * GetAxis() ); + return; + } + + if( !enemy->GetHealth() ) { + //if enemy is dead, just explode + trace_t collision; + memset( &collision, 0, sizeof( collision ) ); + collision.endAxis = GetPhysics()->GetAxis(); + collision.endpos = GetPhysics()->GetOrigin(); + collision.c.point = GetPhysics()->GetOrigin(); + collision.c.normal.Set( 0, 0, 1 ); + Explode( &collision, idVec3(0,0,0), 3 ); + } + + if ( turnFactor < 1.0 && turnFactorAcc > 1.0 ) { + turnFactor *= turnFactorAcc; // Accelerate the turn as time goes on so we don't get stuck in any orbits + } + idVec3 enemyDir = DetermineEnemyDir( enemy.GetEntity() ); + idVec3 currentDir = GetAxis()[0]; + idVec3 newDir = currentDir*(1-turnFactor) + enemyDir*turnFactor; + newDir.Normalize(); + if ( driver.IsValid() ) { + driver->SetAxis(newDir.ToMat3()); + } else { + SetAxis(newDir.ToMat3()); + } + + physicsObj.SetLinearVelocity( GetAxis()[ 0 ] * velocity[ 0 ] + GetAxis()[ 1 ] * velocity[ 1 ] + GetAxis()[ 2 ] * velocity[ 2 ] ); + physicsObj.SetAngularVelocity( angularVelocity.ToAngularVelocity() * GetAxis() ); + + PostEventMS( &EV_Guide, updateRate ); +} + +/* +================ +hhProjectileTracking::Save +================ +*/ +void hhProjectileTracking::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( turnFactor ); + savefile->WriteInt( updateRate ); + + enemy.Save( savefile ); + + savefile->WriteAngles( angularVelocity ); + savefile->WriteVec3( velocity ); + savefile->WriteFloat( cachedFovCos ); + savefile->WriteFloat( spinAngle ); + savefile->WriteFloat( turnFactorAcc ); + savefile->WriteFloat( spinDelta ); +} + +/* +================ +hhProjectileTracking::Restore +================ +*/ +void hhProjectileTracking::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( turnFactor ); + savefile->ReadInt( updateRate ); + + enemy.Restore( savefile ); + + savefile->ReadAngles( angularVelocity ); + savefile->ReadVec3( velocity ); + savefile->ReadFloat( cachedFovCos ); + savefile->ReadFloat( spinAngle ); + savefile->ReadFloat( turnFactorAcc ); + savefile->ReadFloat( spinDelta ); +} + +void hhProjectileTracking::StartTracking() { + Event_StartTracking(); +} \ No newline at end of file diff --git a/src/Prey/prey_projectiletracking.h b/src/Prey/prey_projectiletracking.h new file mode 100644 index 0000000..79a6a4a --- /dev/null +++ b/src/Prey/prey_projectiletracking.h @@ -0,0 +1,48 @@ +#ifndef __HH_PROJECTILE_TRACKING_H +#define __HH_PROJECTILE_TRACKING_H + +/*********************************************************************** + + hhProjectileTracking + +***********************************************************************/ +class hhProjectileTracking: public hhProjectile { + CLASS_PROTOTYPE( hhProjectileTracking ); + + public: + hhProjectileTracking(); + void Spawn(); + virtual void Launch( const idVec3 &start, const idMat3 &axis, const idVec3 &pushVelocity, const float timeSinceFire = 0.0f, const float launchPower = 1.0f, const float dmgPower = 1.0f ); + virtual void Explode( const trace_t *collision, const idVec3& velocity, int removeDelay ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void StartTracking(); + void SetEnemy( idEntity* newEnemy ) { enemy = newEnemy; } + protected: + virtual idEntity* DetermineEnemy(); + bool EnemyIsValid( idEntity* actor ) const; + idEntity* WhosClosest( idEntity* possibleEnemy, idEntity* currentEnemy, float& currentEnemyDot, float& currentEnemyDist ) const; + idVec3 DetermineEnemyPosition( const idEntity* enemy ) const; + idVec3 DetermineEnemyDir( const idEntity* enemy ) const; + bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + protected: + void Event_Hover(); + virtual void Event_TrackTarget(); + void Event_StartTracking(); + void Event_StopTracking(); + + protected: + float turnFactor; + int updateRate; + + idEntityPtr enemy; + idAngles angularVelocity; + idVec3 velocity; + float cachedFovCos; + float spinAngle; + float turnFactorAcc; + float spinDelta; +}; + +#endif \ No newline at end of file diff --git a/src/Prey/prey_projectiletrigger.cpp b/src/Prey/prey_projectiletrigger.cpp new file mode 100644 index 0000000..88ab547 --- /dev/null +++ b/src/Prey/prey_projectiletrigger.cpp @@ -0,0 +1,56 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION( hhProjectile, hhProjectileTrigger ) + EVENT( EV_Collision_Flesh, hhProjectileTrigger::Event_Collision_Explode ) + EVENT( EV_Collision_Metal, hhProjectileTrigger::Event_Collision_Explode ) + EVENT( EV_Collision_AltMetal, hhProjectileTrigger::Event_Collision_Explode ) + EVENT( EV_Collision_Wood, hhProjectileTrigger::Event_Collision_Explode ) + EVENT( EV_Collision_Stone, hhProjectileTrigger::Event_Collision_Explode ) + EVENT( EV_Collision_Glass, hhProjectileTrigger::Event_Collision_Explode ) + EVENT( EV_Collision_Liquid, hhProjectileTrigger::Event_Collision_Explode ) + EVENT( EV_Collision_CardBoard, hhProjectileTrigger::Event_Collision_Explode ) + EVENT( EV_Collision_Tile, hhProjectileTrigger::Event_Collision_Explode ) + EVENT( EV_Collision_Forcefield, hhProjectileTrigger::Event_Collision_Explode ) + EVENT( EV_Collision_Chaff, hhProjectileTrigger::Event_Collision_Explode ) + EVENT( EV_Collision_Pipe, hhProjectileTrigger::Event_Collision_Explode ) + EVENT( EV_Collision_Wallwalk, hhProjectileTrigger::Event_Collision_Explode ) + + EVENT( EV_AllowCollision_Chaff, hhProjectileTrigger::Event_AllowCollision_Collide ) +END_CLASS + +void hhProjectileTrigger::Event_Collision_Explode( const trace_t* collision, const idVec3& velocity ) { + ProcessCollision(collision, velocity); + if ( state == EXPLODED || state == FIZZLED || state == COLLIDED ) { + return; + } + if( !collision ) { + return; + } + int length = 0; + idEntity *trigger; + idDict Args; + + Args.Set( "def_damage", spawnArgs.GetString("trigger_damage") ); + Args.Set( "mins", spawnArgs.GetString("trigger_min") ); + Args.Set( "maxs", spawnArgs.GetString("trigger_max") ); + Args.Set( "snd_loop", spawnArgs.GetString("snd_loop") ); + Args.Set( "snd_explode", spawnArgs.GetString("snd_loop") ); + Args.SetVector( "origin", GetOrigin() ); + Args.SetMatrix( "rotation", GetAxis() ); + trigger = gameLocal.SpawnObject( spawnArgs.GetString("def_trigger"), &Args ); + if ( trigger ) { + trigger->PostEventSec( &EV_Remove, spawnArgs.GetFloat( "remove_time", "5" ) ); + trigger->PostEventSec( &EV_StopSound, spawnArgs.GetFloat( "remove_time", "5" ) - 1, SND_CHANNEL_ANY, 0 ); + trigger->StartSound( "snd_explode", SND_CHANNEL_VOICE, 0, true, &length ); + trigger->StartSound( "snd_loop", SND_CHANNEL_BODY, 0, true, &length ); + } + SpawnExplosionFx( collision ); + SpawnDebris( collision->c.normal, velocity.ToNormal() ); + state = EXPLODED; + RemoveProjectile( spawnArgs.GetFloat( "remove_time", "5" ) ); + ProcessCollision(collision, velocity); + idThread::ReturnInt( 1 ); +} diff --git a/src/Prey/prey_projectiletrigger.h b/src/Prey/prey_projectiletrigger.h new file mode 100644 index 0000000..90cfd48 --- /dev/null +++ b/src/Prey/prey_projectiletrigger.h @@ -0,0 +1,10 @@ +#ifndef __HH_PROJECTILE_TRIGGER_H +#define __HH_PROJECTILE_TRIGGER_H + +class hhProjectileTrigger: public hhProjectile { +public: + CLASS_PROTOTYPE( hhProjectileTrigger ) + void Event_Collision_Explode( const trace_t* collision, const idVec3& velocity ); +}; + +#endif \ No newline at end of file diff --git a/src/Prey/prey_projectilewrench.cpp b/src/Prey/prey_projectilewrench.cpp new file mode 100644 index 0000000..a2fbeec --- /dev/null +++ b/src/Prey/prey_projectilewrench.cpp @@ -0,0 +1,43 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION( hhProjectile, hhProjectileWrench ) + EVENT( EV_Collision_Flesh, hhProjectileWrench::Event_Collision_Impact ) + EVENT( EV_Collision_Metal, hhProjectileWrench::Event_Collision_Impact_AlertAI ) + EVENT( EV_Collision_AltMetal, hhProjectileWrench::Event_Collision_Impact_AlertAI ) + EVENT( EV_Collision_Wood, hhProjectileWrench::Event_Collision_Impact_AlertAI ) + EVENT( EV_Collision_Stone, hhProjectileWrench::Event_Collision_Impact_AlertAI ) + EVENT( EV_Collision_Glass, hhProjectileWrench::Event_Collision_Impact_AlertAI ) + EVENT( EV_Collision_Liquid, hhProjectileWrench::Event_Collision_DisturbLiquid ) + EVENT( EV_Collision_CardBoard, hhProjectileWrench::Event_Collision_Impact_AlertAI ) + EVENT( EV_Collision_Tile, hhProjectileWrench::Event_Collision_Impact_AlertAI ) + EVENT( EV_Collision_Forcefield, hhProjectileWrench::Event_Collision_Impact_AlertAI ) + EVENT( EV_Collision_Pipe, hhProjectileWrench::Event_Collision_Impact_AlertAI ) + EVENT( EV_Collision_Wallwalk, hhProjectileWrench::Event_Collision_Impact_AlertAI ) + EVENT( EV_Collision_Chaff, hhProjectileWrench::Event_Collision_Impact_AlertAI ) + +END_CLASS + +void hhProjectileWrench::Event_Collision_Impact_AlertAI( const trace_t* collision, const idVec3& velocity ) { + gameLocal.AlertAI( owner.GetEntity() ); + + CancelEvents( &EV_Explode ); + RemoveProjectile( ProcessCollision(collision, velocity) ); + state = COLLIDED; + idThread::ReturnInt( 1 ); +} + +void hhProjectileWrench::DamageEntityHit( const trace_t* collision, const idVec3& velocity, idEntity* entHit ) { + if (!gameLocal.isMultiplayer) { + hhPlayer* pOwner = (owner->IsType(hhPlayer::Type)) ? static_cast(owner.GetEntity()) : NULL; + + if ( entHit && entHit->IsType( idActor::Type ) ) { + //pOwner->weapon->SetSkinByName( "skins/weapons/wrench_bloody" ); + pOwner->weapon->SetShaderParm( 7, -MS2SEC(gameLocal.time) ); + } + } + hhProjectile::DamageEntityHit( collision, velocity, entHit ); +} + diff --git a/src/Prey/prey_projectilewrench.h b/src/Prey/prey_projectilewrench.h new file mode 100644 index 0000000..128463d --- /dev/null +++ b/src/Prey/prey_projectilewrench.h @@ -0,0 +1,11 @@ +#ifndef __HH_PROJECTILE_WRENCH_H +#define __HH_PROJECTILE_WRENCH_H + +class hhProjectileWrench : public hhProjectile { + CLASS_PROTOTYPE( hhProjectileWrench ); + void Event_Collision_Impact_AlertAI( const trace_t* collision, const idVec3& velocity ); + protected: + void DamageEntityHit( const trace_t* collision, const idVec3& velocity, idEntity* entHit ); +}; + +#endif \ No newline at end of file diff --git a/src/Prey/prey_script_thread.cpp b/src/Prey/prey_script_thread.cpp new file mode 100644 index 0000000..114936f --- /dev/null +++ b/src/Prey/prey_script_thread.cpp @@ -0,0 +1,114 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION( idThread, hhThread ) +END_CLASS + +hhThread::hhThread() : idThread() {} +hhThread::hhThread( idEntity *self, const function_t *func ) : idThread( self, func ) {} +hhThread::hhThread( const function_t *func ) : idThread( func ) {} +hhThread::hhThread( idInterpreter *source, const function_t *func, int args ) : idThread( source, func, args ) {} + +/* +================ +hhThread::PushParm +================ +*/ +void hhThread::PushParm( int value ) { + interpreter.Push( value ); +} + +/* +================ +hhThread::PushString +================ +*/ +void hhThread::PushString( const char *text ) { + PushParm( (int)new idStr(text) ); +} + +/* +================ +hhThread::PushFloat +================ +*/ +void hhThread::PushFloat( float value ) { + PushParm( *(int*)&value ); +} + +/* +================ +hhThread::PushInt +================ +*/ +void hhThread::PushInt( int value ) { + PushParm( value ); +} + +/* +================ +hhThread::PushVector +================ +*/ +void hhThread::PushVector( const idVec3 &vec ) { + float val = 0.0f; + for( int ix = 0; ix < vec.GetDimension(); ++ix ) { + val = vec[ ix ]; + PushParm( *(int*)&val ); + } +} + +/* +================ +hhThread::PushEntity +================ +*/ +void hhThread::PushEntity( const idEntity *ent ) { + HH_ASSERT( ent ); + + PushParm( ent->entityNumber + 1 ); +} + +/* +================ +hhThread::ClearStack +================ +*/ +void hhThread::ClearStack() { + interpreter.Reset(); +} + +/* +================ +hhThread::ParseAndPushArgsOntoStack +================ +*/ +bool hhThread::ParseAndPushArgsOntoStack( const idCmdArgs &args, const function_t* function ) { + idList parmList; + + hhUtils::SplitString( args, parmList ); + + return ParseAndPushArgsOntoStack( parmList, function ); +} + +/* +================ +hhThread::ParseAndPushArgsOntoStack +================ +*/ +bool hhThread::ParseAndPushArgsOntoStack( const idList& args, const function_t* function ) { + int numParms = function->def->TypeDef()->NumParameters(); + idTypeDef* parmType = NULL; + const char* parm = NULL; + + for( int ix = 0; ix < numParms; ++ix ) { + parmType = function->def->TypeDef()->GetParmType( ix ); + parm = args[ ix ].c_str(); + + parmType->PushOntoStack( parm, this ); + } + + return true; +} \ No newline at end of file diff --git a/src/Prey/prey_script_thread.h b/src/Prey/prey_script_thread.h new file mode 100644 index 0000000..33c97dc --- /dev/null +++ b/src/Prey/prey_script_thread.h @@ -0,0 +1,27 @@ +#ifndef __HH_SCRIPT_THREAD_H__ +#define __HH_SCRIPT_THREAD_H__ + +class hhThread : public idThread { + CLASS_PROTOTYPE( hhThread ); + + public: + hhThread(); + hhThread( idEntity *self, const function_t *func ); + hhThread( const function_t *func ); + hhThread( idInterpreter *source, const function_t *func, int args ); + + void PushString( const char *text ); + void PushFloat( float value ); + void PushInt( int value ); + void PushVector( const idVec3 &vec ); + void PushEntity( const idEntity *ent ); + void ClearStack(); + + bool ParseAndPushArgsOntoStack( const idCmdArgs& args, const function_t* function ); + bool ParseAndPushArgsOntoStack( const idList& args, const function_t* function ); + + protected: + void PushParm( int value ); +}; + +#endif \ No newline at end of file diff --git a/src/Prey/prey_sound.cpp b/src/Prey/prey_sound.cpp new file mode 100644 index 0000000..30dc262 --- /dev/null +++ b/src/Prey/prey_sound.cpp @@ -0,0 +1,258 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_StartDelayedSoundShader( "", "sddd" ); +const idEventDef EV_ResetTargetHandles( "resetTargetHandles" ); +const idEventDef EV_SubtitleOff( "" ); + +CLASS_DECLARATION( idSound, hhSound ) + EVENT( EV_PostSpawn, hhSound::Event_SetTargetHandles ) + EVENT( EV_ResetTargetHandles, hhSound::Event_SetTargetHandles ) + EVENT( EV_SubtitleOff, hhSound::Event_SubtitleOff ) +END_CLASS + + +/* +================ +hhSound::hhSound +================ +*/ +hhSound::hhSound( void ) { + positionOffset.Zero(); +} + +/* +================ +hhSound::Spawn +================ +*/ +void hhSound::Spawn() { + PostEventMS( &EV_PostSpawn, 0 ); +} + +/* +================ +hhSound::StartDelayedSoundShader +================ +*/ +void hhSound::StartDelayedSoundShader( const idSoundShader *shader, const s_channelType channel, int soundShaderFlags, bool broadcast ) { + CancelEvents( &EV_StartDelayedSoundShader ); + PostEventSec( &EV_StartDelayedSoundShader, RandomRange(spawnArgs.GetFloat("s_minDelay"), spawnArgs.GetFloat("s_maxDelay")), (const char *)shader->GetName(), (int)channel, (int)soundShaderFlags, (int)broadcast ); +} + +/* +================ +hhSound::StopDelayedSound +================ +*/ +void hhSound::StopDelayedSound( const s_channelType channel, bool broadcast ) { + CancelEvents( &EV_StartDelayedSoundShader ); + idSound::StopSound( channel, broadcast ); +} + +/* +================ +hhSound::StartSoundShader +================ +*/ +bool hhSound::StartSoundShader( const idSoundShader *shader, const s_channelType channel, int soundShaderFlags, bool broadcast, int* length ) { + bool result = true; + float volume = 0.0f; + + positionOffset = DeterminePositionOffset(); + if( !spawnArgs.GetBool("s_useRandomDelay") ) { + StopDelayedSound( channel, broadcast ); + + result = idSound::StartSoundShader( shader, channel, soundShaderFlags, broadcast, length ); + + const char *subtitleText = spawnArgs.GetString( "subtitle", NULL ); + if ( result && subtitleText ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( length > 0 && player && player->hud ) { + player->hud->SetStateInt("subtitlefadetime", 0); + player->hud->SetStateInt("subtitlex", 0 ); + player->hud->SetStateInt("subtitley", 400 ); + player->hud->SetStateInt("subtitlecentered", true); + player->hud->SetStateString("subtitletext", common->GetLanguageDict()->GetString(subtitleText)); + player->hud->StateChanged(gameLocal.time); + player->hud->HandleNamedEvent("DisplaySubtitle"); + PostEventMS( &EV_SubtitleOff, *length ); + } + } + + if( DetermineVolume(volume) ) { + HH_SetSoundVolume( volume, channel ); + } + } else { + StartDelayedSoundShader( shader, channel, soundShaderFlags, broadcast ); + } + + //trigger targets when sound ends + if ( length && targets.Num() && spawnArgs.GetInt("trigger_targets","0") ) { + float delay = *length * 0.001 + spawnArgs.GetFloat("target_delay", "0"); + if ( delay < 0 ) { + delay = 0; + } + PostEventSec( &EV_ActivateTargets, delay , this ); + } + + return result; +} + +/* +================ +hhSound::StopSound +================ +*/ +void hhSound::StopSound( const s_channelType channel, bool broadcast ) { + StopDelayedSound( channel, broadcast ); +} + +/* +================ +hhSound::RandomRange +================ +*/ +float hhSound::RandomRange( const float min, const float max ) { + return hhMath::Lerp( min, max, gameLocal.random.RandomFloat() ); +} + +/* +=============== +hhSound::DetermineVolume +=============== +*/ +bool hhSound::DetermineVolume( float& volume ) { + if( !spawnArgs.GetBool("s_useRandomVolume") ) { + return false; + } + + volume = hhMath::dB2Scale( RandomRange(spawnArgs.GetInt("s_minVolume"), spawnArgs.GetInt("s_maxVolume")) ); + return true; +} + +/* +=============== +hhSound::DeterminePositionOffset +=============== +*/ +idVec3 hhSound::DeterminePositionOffset() { + if( !spawnArgs.GetBool("s_useRandomPosition") ) { + return vec3_origin; + } + + float radius = RandomRange( spawnArgs.GetFloat("s_minRadius"), spawnArgs.GetFloat("s_maxRadius") ); + return hhUtils::RandomVector() * radius; +} + +/* +================ +hhSound::GetCurrentAmplitude +================ +*/ +float hhSound::GetCurrentAmplitude(const s_channelType channel) { + if (refSound.referenceSound && refSound.referenceSound->CurrentlyPlaying()) { + return refSound.referenceSound->CurrentAmplitude(channel); + } + return 0.0f; +} + +/* +================ +hhSound::GetPhysicsToSoundTransform +================ +*/ +bool hhSound::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + origin = positionOffset; + axis.Identity(); + return true; +} + +/* +================ +hhSound::Event_StartDelayedSoundShader +================ +*/ +void hhSound::Event_StartDelayedSoundShader( const char *shaderName, const s_channelType channel, int soundShaderFlags, bool broadcast ) { + int soundLength = 0; + float volume = 0.0f; + + const idSoundShader *shader = declManager->FindSound( shaderName ); + assert( shader ); + if( !shader ) { + return; + } + + if( !GetSoundEmitter() || !GetSoundEmitter()->CurrentlyPlaying() ) { + soundLength = idSound::StartSoundShader( shader, channel, soundShaderFlags, broadcast );//Not sure if we should broadcast + + positionOffset = DeterminePositionOffset(); + if( DetermineVolume(volume) ) { + HH_SetSoundVolume( volume, channel ); + } + } + + CancelEvents( &EV_StartDelayedSoundShader ); + PostEventSec( &EV_StartDelayedSoundShader, MS2SEC(soundLength) + RandomRange(spawnArgs.GetFloat("s_minDelay"), spawnArgs.GetFloat("s_maxDelay")), shaderName, (int)channel, (int)soundShaderFlags, (int)broadcast ); +} + +void hhSound::Event_SubtitleOff( void ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player && player->hud ) { + player->hud->HandleNamedEvent("RemoveSubtitleInstant"); + } +} + +/* +================ +hhSound::Event_SetTargetHandles + +Copied from hhLight +================ +*/ +void hhSound::Event_SetTargetHandles( void ) { + int i; + idEntity *targetEnt = NULL; + + if ( !refSound.referenceSound ) { + refSound.referenceSound = gameSoundWorld->AllocSoundEmitter(); + } + + for( i = 0; i < targets.Num(); i++ ) { + targetEnt = targets[ i ].GetEntity(); + if ( targetEnt ) { + if( targetEnt->IsType(idLight::Type) ) { + static_cast(targetEnt)->SetLightParent( this ); + } + + targetEnt->FreeSoundEmitter( true ); + + // manually set the refSound to this light's refSound + targetEnt->GetRenderEntity()->referenceSound = refSound.referenceSound; + + // update the renderEntity to the renderer + targetEnt->UpdateVisuals(); + } + } +} + +/* +================ +hhSound::Save +================ +*/ +void hhSound::Save( idSaveGame *savefile ) const { + savefile->WriteVec3( positionOffset ); +} + +/* +================ +hhSound::Restore +================ +*/ +void hhSound::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( positionOffset ); +} + diff --git a/src/Prey/prey_sound.h b/src/Prey/prey_sound.h new file mode 100644 index 0000000..b262d60 --- /dev/null +++ b/src/Prey/prey_sound.h @@ -0,0 +1,38 @@ +#ifndef __HH_SOUND_H +#define __HH_SOUND_H + +extern const idEventDef EV_Sound; +extern const idEventDef EV_ResetTargetHandles; + +class hhSound : public idSound { + CLASS_PROTOTYPE( hhSound ); + + public: + hhSound(); + void Spawn(); + virtual bool StartSoundShader( const idSoundShader *shader, const s_channelType channel, int soundShaderFlags, bool broadcast, int* length ); + virtual void StopSound( const s_channelType channel, bool broadcast ); + void StartDelayedSoundShader( const idSoundShader *shader, const s_channelType channel, int soundShaderFlags, bool broadcast ); + void StopDelayedSound( const s_channelType channel, bool broadcast = false ); + float GetCurrentAmplitude(const s_channelType channel); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + protected: + virtual bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); + + float RandomRange( const float min, const float max ); + + bool DetermineVolume( float& volume ); + idVec3 DeterminePositionOffset(); + + protected: + void Event_SetTargetHandles(); + void Event_StartDelayedSoundShader( const char *shader, const s_channelType channel, int soundShaderFlags = 0, bool broadcast = false ); + void Event_SubtitleOff( void ); + protected: + idVec3 positionOffset; +}; + +#endif \ No newline at end of file diff --git a/src/Prey/prey_soundleadincontroller.cpp b/src/Prey/prey_soundleadincontroller.cpp new file mode 100644 index 0000000..ff97b0c --- /dev/null +++ b/src/Prey/prey_soundleadincontroller.cpp @@ -0,0 +1,264 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION( idClass, hhSoundLeadInController ) +END_CLASS + +/* +================ +hhSoundLeadInController::hhSoundLeadInController + +HUMANHEAD: aob +================ +*/ +hhSoundLeadInController::hhSoundLeadInController() { + leadInShader = NULL; + loopShader = NULL; + leadOutShader = NULL; + + startTime = 0; + endTime = 0; + + owner = NULL; + + //rww - networking-related variables + lastLeadChannel = SND_CHANNEL_ANY; + lastLoopChannel = SND_CHANNEL_ANY; + bPlaying = false; + iLoopOnlyOnLocal = -1; +} + +/* +================ +hhSoundLeadInController::SetOwner + +HUMANHEAD: aob +================ +*/ +void hhSoundLeadInController::SetOwner( idEntity* ent ) { + owner = ent; +} + +/* +================ +hhSoundLeadInController::WriteToSnapshot + +HUMANHEAD: rww +================ +*/ +void hhSoundLeadInController::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteBits(lastLeadChannel, 8); + msg.WriteBits(lastLoopChannel, 8); + + msg.WriteBits(owner.GetSpawnId(), 32); + + msg.WriteBits(bPlaying, 1); +} + +/* +================ +hhSoundLeadInController::ReadFromSnapshot + +HUMANHEAD: rww +note that the methods used here are not failsafe given the range of functionality +within this class, and this logic may need to be adjusted on a per-case basis if +more instances are to be sync'd over the net using this method. +================ +*/ +void hhSoundLeadInController::ReadFromSnapshot( const idBitMsgDelta &msg ) { + lastLeadChannel = msg.ReadBits(8); + lastLoopChannel = msg.ReadBits(8); + + owner.SetSpawnId(msg.ReadBits(32)); + + bool nowPlaying = !!msg.ReadBits(1); + if (nowPlaying != bPlaying) { + if (nowPlaying) { + StartSound(lastLeadChannel, lastLoopChannel); + } + else { + StopSound(lastLeadChannel, lastLoopChannel); + } + } +} + +/* +================ +hhSoundLeadInController::SetLeadIn + +HUMANHEAD: aob +================ +*/ +void hhSoundLeadInController::SetLeadIn( const char* soundname ) { + leadInShader = declManager->FindSound( soundname, false ); +} + +/* +================ +hhSoundLeadInController::SetLoop + +HUMANHEAD: aob +================ +*/ +void hhSoundLeadInController::SetLoop( const char* soundname ) { + loopShader = declManager->FindSound( soundname, false ); +} + +/* +================ +hhSoundLeadInController::SetLeadOut + +HUMANHEAD: aob +================ +*/ +void hhSoundLeadInController::SetLeadOut( const char* soundname ) { + leadOutShader = declManager->FindSound( soundname, false ); +} + +/* +================ +hhSoundLeadInController::StartSound + +HUMANHEAD: aob +================ +*/ +int hhSoundLeadInController::StartSound( const s_channelType leadChannel, const s_channelType loopChannel, int soundShaderFlags, bool broadcast ) { + int length = 0; + + if( !owner.IsValid() ) { + return 0; + } + + if( loopShader ) { + owner->StopSound( loopChannel, broadcast ); + } + + if( leadOutShader ) { + owner->StopSound( leadChannel, broadcast ); + } + + if( leadInShader ) { + owner->StartSoundShader( leadInShader, leadChannel, 0, broadcast, &length ); + StartFade( leadInShader, leadChannel, endTime, startTime, leadInShader->GetVolume(), length ); + + startTime = gameLocal.GetTime(); + endTime = startTime + length; + } + + if (iLoopOnlyOnLocal == -1 || iLoopOnlyOnLocal == gameLocal.localClientNum) { //rww - for spirit music and whatever else needs it + owner->StartSoundShader( loopShader, loopChannel, 0, broadcast, NULL ); + StartFade( loopShader, loopChannel, startTime, endTime, loopShader->GetVolume(), length ); + } + + //rww - for networking + lastLeadChannel = leadChannel; + lastLoopChannel = loopChannel; + bPlaying = true; + + return length; +} + +/* +================ +hhSoundLeadInController::StopSound + +HUMANHEAD: aob +================ +*/ +void hhSoundLeadInController::StopSound( const s_channelType leadChannel, const s_channelType loopChannel, bool broadcast ) { + int length = 0; + + if( !owner.IsValid() ) { + return; + } + + if( leadInShader ) { + owner->StopSound( leadChannel, broadcast ); + } + + if( loopShader ) { + owner->StopSound( loopChannel, broadcast ); + } + + if( leadOutShader ) { + owner->StartSoundShader( leadOutShader, leadChannel, 0, broadcast, &length ); + StartFade( leadOutShader, leadChannel, startTime, endTime, hhMath::Scale2dB(0.0f), length ); + + startTime = gameLocal.GetTime(); + endTime = startTime + length; + } + + //rww - for networking + lastLeadChannel = leadChannel; + lastLoopChannel = loopChannel; + bPlaying = false; +} + +/* +================ +hhSoundLeadInController::CalculateScale + +HUMANHEAD: aob +================ +*/ +float hhSoundLeadInController::CalculateScale( const float value, const float min, const float max ) { + return hhUtils::CalculateScale( value, min, max ); +} + +/* +================ +hhSoundLeadInController::StartFade + +HUMANHEAD: aob +================ +*/ +void hhSoundLeadInController::StartFade( const idSoundShader* shader, const s_channelType channel, int start, int end, int finaldBVolume, int duration ) { +/* + float scale = CalculateScale( gameLocal.GetTime(), start, end ); + + scale *= hhMath::dB2Scale( shader->GetVolume() ); + owner->HH_SetSoundVolume( scale, channel ); + owner->FadeSoundShader( finaldBVolume, duration, channel ); +*/ +} + +/* +================ +hhSoundLeadInController::Save +================ +*/ +void hhSoundLeadInController::Save( idSaveGame *savefile ) const { + savefile->WriteSoundShader( leadInShader ); + savefile->WriteSoundShader( loopShader ); + savefile->WriteSoundShader( leadOutShader ); + savefile->WriteInt( startTime ); + savefile->WriteInt( endTime ); + + owner.Save( savefile ); +} + +/* +================ +hhSoundLeadInController::Restore +================ +*/ +void hhSoundLeadInController::Restore( idRestoreGame *savefile ) { + savefile->ReadSoundShader( leadInShader ); + savefile->ReadSoundShader( loopShader ); + savefile->ReadSoundShader( leadOutShader ); + savefile->ReadInt( startTime ); + savefile->ReadInt( endTime ); + + owner.Restore( savefile ); +} + +/* +================ +hhSoundLeadInController::SetLoopOnlyOnLocal +================ +*/ +void hhSoundLeadInController::SetLoopOnlyOnLocal(int loopOnlyOnLocal) { + iLoopOnlyOnLocal = loopOnlyOnLocal; +} diff --git a/src/Prey/prey_soundleadincontroller.h b/src/Prey/prey_soundleadincontroller.h new file mode 100644 index 0000000..91a4917 --- /dev/null +++ b/src/Prey/prey_soundleadincontroller.h @@ -0,0 +1,53 @@ +#ifndef __HH_SOUND_LEADIN_CONTROLLER_H +#define __HH_SOUND_LEADIN_CONTROLLER_H + +class hhSoundLeadInController : public idClass { + CLASS_PROTOTYPE( hhSoundLeadInController ); + + public: + hhSoundLeadInController(); + + void SetOwner( idEntity* ent ); + + //rww - network code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + void SetLeadIn( const char* soundname ); + void SetLoop( const char* soundname ); + void SetLeadOut( const char* soundname ); + + int StartSound( const s_channelType leadChannel, const s_channelType loopChannel, int soundShaderFlags = 0, bool broadcast = false ); + void StopSound( const s_channelType leadChannel, const s_channelType loopChannel, bool broadcast = false ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void SetLoopOnlyOnLocal(int loopOnlyOnLocal); + + protected: + float CalculateScale( const float value, const float min, const float max ); + void StartFade( const idSoundShader* shader, const s_channelType channel, int start, int end, int finaldBVolume, int duration ); + + protected: + void Event_StartSoundShaderEx( idSoundShader* shader, const s_channelType channel, int soundShaderFlags, bool broadcast ); + + protected: + const idSoundShader* leadInShader; + const idSoundShader* loopShader; + const idSoundShader* leadOutShader; + + int startTime; + int endTime; + + idEntityPtr owner; + + //rww - everything below here is for networking, no need to save/restore + s_channelType lastLeadChannel; + s_channelType lastLoopChannel; + public: + bool bPlaying; + int iLoopOnlyOnLocal; +}; + +#endif \ No newline at end of file diff --git a/src/Prey/prey_spiritbridge.cpp b/src/Prey/prey_spiritbridge.cpp new file mode 100644 index 0000000..4cd1710 --- /dev/null +++ b/src/Prey/prey_spiritbridge.cpp @@ -0,0 +1,70 @@ +//************************************************************************** +//** +//** PREY_SPIRITBRIDGE.CPP +//** +//** Game code for the spirit bridge +//************************************************************************** + +// HEADER FILES ------------------------------------------------------------ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +// MACROS ------------------------------------------------------------------ + +// TYPES ------------------------------------------------------------------- + +// CLASS DECLARATIONS ------------------------------------------------------ + +CLASS_DECLARATION( idEntity, hhSpiritBridge ) + EVENT( EV_Activate, hhSpiritBridge::Event_Activate ) +END_CLASS + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// hhSpiritBridge::Spawn +// +//========================================================================== + +void hhSpiritBridge::Spawn(void) { + fl.takedamage = false; + + if ( !spawnArgs.GetInt( "start_off" ) ) { + GetPhysics()->SetContents( CONTENTS_SPIRITBRIDGE ); + } else { // Start the bridge off + GetPhysics()->SetContents( 0 ); + Hide(); + } + + SetShaderParm( SHADERPARM_MODE, gameLocal.random.CRandomFloat() ); +} + +//========================================================================== +// +// hhSpiritBridge::Event_Activate +// +//========================================================================== + +void hhSpiritBridge::Event_Activate( idEntity *activator ) { + if ( IsHidden() ) { + Show(); + GetPhysics()->SetContents( CONTENTS_SPIRITBRIDGE ); + } else { + Hide(); + GetPhysics()->SetContents( 0 ); + } +} diff --git a/src/Prey/prey_spiritbridge.h b/src/Prey/prey_spiritbridge.h new file mode 100644 index 0000000..f75ea9e --- /dev/null +++ b/src/Prey/prey_spiritbridge.h @@ -0,0 +1,17 @@ + +#ifndef __PREY_SPIRITBRIDGE_H__ +#define __PREY_SPIRITBRIDGE_H__ + +//class hhPlayer; + +class hhSpiritBridge : public idEntity { +public: + CLASS_PROTOTYPE( hhSpiritBridge ); + + void Spawn( void ); + +protected: + void Event_Activate( idEntity *activator ); +}; + +#endif /* __PREY_SPIRITBRIDGE_H__ */ diff --git a/src/Prey/prey_spiritproxy.cpp b/src/Prey/prey_spiritproxy.cpp new file mode 100644 index 0000000..4d95d37 --- /dev/null +++ b/src/Prey/prey_spiritproxy.cpp @@ -0,0 +1,1094 @@ +//************************************************************************** +//** +//** PREY_SPIRITPROXY.CPP +//** +//** Game code for the player proxy dropped when the player spirit walks +//** +//************************************************************************** + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +#define MIN_ACTIVATION_TIME SEC2MS( 1.0f ) + +const idEventDef EV_SpawnEffect( "", NULL ); +const idEventDef EV_OrientToGravity( "orientToGravity", "d" ); + +//========================================================================== +// hhSpiritProxy +//========================================================================== + +CLASS_DECLARATION( idActor, hhSpiritProxy ) + EVENT( EV_SpawnEffect, hhSpiritProxy::Event_SpawnEffect ) + EVENT( EV_OrientToGravity, hhSpiritProxy::Event_OrientToGravity ) + EVENT( EV_ResetGravity, hhSpiritProxy::Event_ResetGravity ) + EVENT( EV_ShouldRemainAlignedToAxial, hhSpiritProxy::Event_ShouldRemainAlignedToAxial ) + EVENT( EV_Broadcast_AssignFx, hhSpiritProxy::Event_AssignSpiritFx ) +END_CLASS + +hhSpiritProxy::hhSpiritProxy() { + fl.networkSync = true; + playerModelNum = 0; +} + +void hhSpiritProxy::Spawn(void) { + fl.takedamage = true; + spiritFx = NULL; + + clientAnimated = false; + netAnimType = 0; + + if (gameLocal.isMultiplayer) { + SetModel("model_multiplayer_tommy"); + SetSkinByName("skins/characters/tommy_mp_spirit"); + playerModelNum = 0; + } + + // Required so that models move in place. + GetAnimator()->RemoveOriginOffset( true ); + + if (gameLocal.isMultiplayer && !IsType(hhDeathProxy::Type)) { //rww - ambient sound for mp + StartSound( "snd_spiritSound", SND_CHANNEL_VOICE, 0, false, NULL ); + } +} + +//========================================================================== +// +// hhSpiritProxy::SetModel +// +//========================================================================== +void hhSpiritProxy::SetModel( const char *modelname ) { + spawnArgs.Set("playerModel", modelname); + idActor::SetModel(modelname); +} + +//========================================================================== +// +// hhSpiritProxy::UpdateModelForPlayer +// +//========================================================================== +void hhSpiritProxy::UpdateModelForPlayer(void) { + int modelNum = 0; + player->GetUserInfo()->GetInt("ui_modelNum", "0", modelNum); + + if (modelNum != playerModelNum) { //time to change models then. + idStr customModel; + if (!IsType(hhMPDeathProxy::Type)) { //don't do this for death prox, since it uses the spirit fadeaway skin + SetSkin(NULL); //destroy custom skin on the spirit proxy so that the appropriate player skin is used. + } + playerModelNum = modelNum; + if (player->spawnArgs.GetString(va("model_mp%i", modelNum), "", customModel)) { + SetModel(customModel.c_str()); + } + else { + SetModel("model_multiplayer_tommy"); + } + if (!IsType(hhMPDeathProxy::Type)) { + //new: check for a custom spirit proxy skin for the given model + if (!player->spawnArgs.GetString(va("skin_mpspirit%i", modelNum), "", customModel)) { + customModel = "skins/characters/tommy_mp_spirit"; + } + SetSkinByName(customModel); + + StartAnimation(); //restart animation on new model (unless you're a corpse) + } + } +} + +//========================================================================== +// +// hhSpiritProxy::Think +// +//========================================================================== +void hhSpiritProxy::Think() { + idVec3 oldOrigin = physicsObj.GetOrigin(); + idVec3 oldVelocity = physicsObj.GetLinearVelocity(); + + if (gameLocal.isMultiplayer) { + DrawPlayerIcons(); + + if (player.IsValid() && player.GetEntity() && player->IsType(hhPlayer::Type) && !IsType(hhMPDeathProxy::Type)) { + UpdateModelForPlayer(); + } + } + idActor::Think(); + + if (player.IsValid() && player->IsType(hhPlayer::Type) && (!IsType(hhMPDeathProxy::Type) || !IsType(hhMPDeathProxy::Type))) { + CrashLand(oldOrigin, oldVelocity); + } +} + +//========================================================================== +// +// hhSpiritProxy::CreateProxy +// +// Spawns a proxy object and then activates it +//========================================================================== + +hhSpiritProxy *hhSpiritProxy::CreateProxy( const char *name, hhPlayer *owner, const idVec3& origin, const idMat3& bboxAxis, const idMat3& newViewAxis, const idAngles& newViewAngles, const idMat3& newEyeAxis ) { + hhSpiritProxy *proxy; + + proxy = (hhSpiritProxy *)gameLocal.SpawnObject( name ); + if( !proxy ) { + gameLocal.Error("hhSpiritProxy::CreateProxy: Could not spawn the player proxy\n"); + } + + proxy->ActivateProxy( owner, origin, bboxAxis, newViewAxis, newViewAngles, newEyeAxis ); + + return proxy; +} + + +//========================================================================== +// +// hhSpiritProxy::Event_SpawnEffect +// +//========================================================================== + +void hhSpiritProxy::Event_SpawnEffect() { + hhFxInfo fxInfo; + idVec3 boneOffset; + idMat3 boneAxis; + + GetJointWorldTransform( spawnArgs.GetString( "bone_spiritFx" ), boneOffset, boneAxis ); + + fxInfo.RemoveWhenDone( false ); + fxInfo.SetNormal( boneAxis[1] ); + fxInfo.SetEntity( this ); + BroadcastFxInfo( spawnArgs.GetString( "fx_spirit" ), boneOffset, GetAxis(), &fxInfo, &EV_Broadcast_AssignFx ); +} + +//========================================================================== +// +// hhSpiritProxy::ActivateProxy +// +//========================================================================== + +void hhSpiritProxy::ActivateProxy( hhPlayer *owner, const idVec3& origin, const idMat3 &bboxAxis, const idMat3& newViewAxis, const idAngles& newViewAngles, const idMat3& newEyeAxis ) { + assert( owner ); + + player = owner; + + viewAxis = newViewAxis; + viewAngles = newViewAngles; + eyeAxis = newEyeAxis; + + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel(owner->GetPhysics()->GetClipModel()), 1.0f ); + physicsObj.SetOrigin( origin ); + physicsObj.SetAxis( bboxAxis ); + physicsObj.SetClipMask( CONTENTS_SOLID | CONTENTS_PLAYERCLIP | CONTENTS_FORCEFIELD ); // HUMANHEAD mdl: MASK_PLAYERSOLID - CONTENTS_BODY + physicsObj.SetContents( CONTENTS_CORPSE | CONTENTS_MONSTERCLIP | CONTENTS_RENDERMODEL ); + physicsObj.CheckWallWalk( true ); + if( player->IsCrouching() ) { + physicsObj.ForceCrouching(); + } + SetPhysics( &physicsObj ); + + if (owner->GetPhysics()->IsType(hhPhysics_Player::Type)) { //rww - don't stay half-oriented if player goes into spirit while gravity-flipping + hhPhysics_Player *plPhys = static_cast(owner->GetPhysics()); + OrientToGravity(plPhys->OrientToGravity()); + SetGravity(plPhys->GetGravity()); + } + + // Save the time when activated, to keep the player from being bounced back into the proxy too quickly + activationTime = gameLocal.time; + + StartAnimation(); + + UpdateVisuals(); + + // Set eye height + eyeOffset = GetPhysics()->GetBounds()[ 1 ].z - 6; + + // TODO: AIMSG_REMOVE: Tell the AI that spirit walk has started + + // Spawn spirit effect + PostEventMS( &EV_SpawnEffect, spawnArgs.GetInt( "spiritBlendTime", "400" ) ); + + if (!IsType(hhDeathProxy::Type) && !IsType(hhDeathWalkProxy::Type)) { + idVec3 vel = player->GetPhysics()->GetLinearVelocity(); + GetPhysics()->SetLinearVelocity( player->GetPhysics()->GetLinearVelocity() ); + GetPhysics()->SetAngularVelocity( player->GetPhysics()->GetAngularVelocity() ); + } + + // Make sure spiritproxy is affected by wallwalkmovers + BecomeActive( TH_PHYSICS ); +} + +//========================================================================== +// +// hhSpiritProxy::DeactivateProxy +// +//========================================================================== + +void hhSpiritProxy::DeactivateProxy(void) { + if( !player.IsValid() ) { + return; + } + + // TODO: AIMSG_REMOVED Tell the AI that spirit walk has ended + + // TODO: AIMSG_REMOVED Tell monsters they 'heard' a sound where the player spirit was removed from + // This makes it so monsters will 'investigate' where a player just disappeared from. Could be cool game dynamic? Maybe not needed? + + if (!IsType(hhDeathProxy::Type)) { //rww - for deathwalk, the player will manage values + RestorePlayerLocation( GetOrigin(), GetPhysics()->GetAxis(), viewAxis[0], viewAngles ); + idVec3 vel = GetPhysics()->GetLinearVelocity(); + player->GetPhysics()->SetLinearVelocity( GetPhysics()->GetLinearVelocity() ); + player->GetPhysics()->SetAngularVelocity( GetPhysics()->GetAngularVelocity() ); + } + + //HUMANHEAD rww - in multiplayer, telefrag other players who are in my body + if (gameLocal.isMultiplayer && !gameLocal.isClient) { + idBounds testBounds = player->GetPhysics()->GetAbsBounds(); + if (testBounds != bounds_zero) { + idEntity *touch[ MAX_GENTITIES ]; + + testBounds.ExpandSelf(-4.0f); //don't do anything if they are right on the edge, to avoid exploiting this by going up to someone and spiriting to kill them + int num = gameLocal.clip.EntitiesTouchingBounds(testBounds, player->GetPhysics()->GetClipMask(), touch, MAX_GENTITIES); + for (int i = 0; i < num; i++) { + if (touch[i] && touch[i]->IsType(hhPlayer::Type) && touch[i] != player.GetEntity() && touch[i]->fl.takedamage) { + touch[i]->Damage(player.GetEntity(), player.GetEntity(), vec3_origin, "damage_telefrag", 1.0f, INVALID_JOINT); + } + } + } + } + //HUMANHEAD END + + Hide(); // JRM: Hide this so it doesn't stick around while waiting to be removed + GetPhysics()->SetContents( 0 ); + PostEventMS( &EV_Remove, 1000 ); // Keep the proxy around for a few frames to deal with removal issues - JRM: made bigger + player = NULL; // Completely disconnect the proxy from the owner + + if ( spiritFx.IsValid() ) { // Remove spirit effect + spiritFx->Nozzle( false ); + SAFE_REMOVE( spiritFx ); + } + + fl.refreshReactions = FALSE; + CancelEvents( &EV_SpawnEffect ); +} + +//========================================================================== +// +// hhSpiritProxy::RestorePlayerLocation +// +//========================================================================== +void hhSpiritProxy::RestorePlayerLocation( const idVec3& origin, const idMat3& bboxAxis, const idVec3& viewDir, const idAngles& angles ) { + if( IsCrouching() ) { + player->ForceCrouching(); + } + + player->TeleportNoKillBox( origin, bboxAxis, viewDir, (angles.ToMat3() * eyeAxis.Transpose()).ToAngles() ); +} + +//========================================================================== +// +// hhSpiritProxy::StartAnimation +// +// Starts the animation on the proxy +//========================================================================== +void hhSpiritProxy::StartAnimation() { + int spiritBlendTime = spawnArgs.GetInt( "spiritBlendTime", "400" ); + // Copy the current player animation to blend into the spirit anim + GetAnimator()->CopyAnimations( *( player->GetAnimator() ) ); + + // Play the initial animation + int anim; + + if (gameLocal.isClient) { //rww + switch (netAnimType) { + case 1: + anim = GetAnimator()->GetAnim("spiritleave"); + break; + case 2: + anim = GetAnimator()->GetAnim("crouch"); + break; + default: + assert(!"hhSpiritProxy has invalid netAnimType"); + anim = GetAnimator()->GetAnim("spiritleave"); + break; + } + } + else { + if ( IsCrouching() ) { + // Determine contents of the bounds if the player is crouching + idBounds bounds; + idTraceModel trm; + + 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() ); + + trm.SetupBox( bounds ); + idClipModel *clipModel = new idClipModel(trm); + int contents = gameLocal.clip.Contents( player->GetOrigin(), clipModel, player->GetAxis(), CONTENTS_SOLID, player.GetEntity() ); + delete clipModel; + + if ( contents & CONTENTS_SOLID ) { // Standing upright collides with geometry, so play the crouch anim when spiritwalking + anim = GetAnimator()->GetAnim("crouch"); + netAnimType = 2; + } else { + anim = GetAnimator()->GetAnim("spiritleave"); + netAnimType = 1; + } + } else { + anim = GetAnimator()->GetAnim("spiritleave"); + netAnimType = 1; + } + } + GetAnimator()->ClearAllAnims( gameLocal.time, spiritBlendTime ); + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, spiritBlendTime ); +} + +//========================================================================== +// +// hhSpiritProxy::Damage +// +// When damaged, the proxy forces the player to automatically return +//========================================================================== + +void hhSpiritProxy::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + + if( !player.IsValid() || !player->IsSpiritOrDeathwalking() ) { + return; + } + if (gameLocal.isClient) { //rww - don't predict damage on the spirit proxy + return; + } + + if (!gameLocal.isMultiplayer) { //rww - not in mp + // Don't allow the player to be bounced back into the proxy immediately if damaged + if ( gameLocal.time - activationTime < MIN_ACTIVATION_TIME ) { + return; + } + } + + // Save the player, since it is NULLed on this proxy when removed from spiritwalk + hhPlayer *currentPlayer = player.GetEntity(); + + // Disable spiritwalk + // rww - let's defer this to the next frame so other projectiles and things that would impact this frame still hit + if (idEvent::NumQueuedEvents(currentPlayer, &EV_StopSpiritWalk) <= 0) { + currentPlayer->PostEventMS(&EV_StopSpiritWalk, 0); + } + //currentPlayer->StopSpiritWalk(); + + if ( attacker && attacker == player.GetEntity() ) { // If the attacker is the player itself, then just snap back from spirit mode, without doing any damage + return; + } + + // Apply the damage to the player itself + // rww - post this as an event too so we don't take the damage until after we're out of spirit form + float localDmgScale = damageScale; + if (gameLocal.isMultiplayer) { //rww - scale up the damage against bodies in mp, to increase punishment when your body is found + localDmgScale *= 2.0f; + } + currentPlayer->PostEventMS(&EV_DamagePlayer, 1, inflictor, attacker, dir, damageDefName, localDmgScale, location); + //currentPlayer->Damage( inflictor, attacker, dir, damageDefName, localDmgScale, location ); +} + +/* +=============== +hhSpiritProxy::OrientToGravity +=============== +*/ +void hhSpiritProxy::OrientToGravity( bool orient ) { + physicsObj.OrientToGravity( orient ); +} + +/* +=============== +hhSpiritProxy::Event_OrientToGravity +=============== +*/ +void hhSpiritProxy::Event_OrientToGravity( bool orient ) { + OrientToGravity( orient ); +} + +/* +=============== +hhSpiritProxy::Event_AssignSpiritFx +=============== +*/ +void hhSpiritProxy::Event_AssignSpiritFx( hhEntityFx* fx ) { + spiritFx = fx; +} + +/* +=============== +hhSpiritProxy::Event_ResetGravity + +HUMANHEAD: pdm: Posted when entity is leaving a gravity zone +=============== +*/ +void hhSpiritProxy::Event_ResetGravity() { + if( IsWallWalking() ) { + return; // Don't reset if wallwalking + } + + idActor::Event_ResetGravity(); + + OrientToGravity( true ); // let it reset orientation +} + +//========================================================================== +// +// hhSpiritProxy::ShouldRemainAlignedToAxial +// +//========================================================================== +void hhSpiritProxy::ShouldRemainAlignedToAxial( bool remainAligned ) {//HUMANHEAD + physicsObj.ShouldRemainAlignedToAxial( remainAligned ); +} + +//========================================================================== +// +// hhSpiritProxy::Event_ShouldRemainAlignedToAxial +// +//========================================================================== +void hhSpiritProxy::Event_ShouldRemainAlignedToAxial( bool remainAligned ) { + ShouldRemainAlignedToAxial( remainAligned ); +} + +/* +================ +hhSpiritProxy::UpdateModelTransform +mdl: Based on hhPlayer::UpdateModelTransform +================ +*/ +void hhSpiritProxy::UpdateModelTransform( void ) { + if( af.IsActive() ) { + return idActor::UpdateModelTransform(); + } + + idVec3 origin; + idMat3 axis; + + if( GetPhysicsToVisualTransform(origin, axis) ) { + GetRenderEntity()->axis = axis; + GetRenderEntity()->origin = GetPhysics()->GetOrigin() + origin * renderEntity.axis; + } else { + GetRenderEntity()->axis = GetAxis(); + GetRenderEntity()->origin = GetOrigin(); + } +} + +bool hhSpiritProxy::AllowCollision(const trace_t &collision) { + if (collision.fraction < 1.0f && collision.c.entityNum < MAX_CLIENTS && collision.c.entityNum >= 0 && gameLocal.entities[collision.c.entityNum]) { + if (player.GetEntity() == gameLocal.entities[collision.c.entityNum]) { + return false; //do not collide with the owner of this spirit proxy + } + } + return true; +} + +void hhSpiritProxy::Save( idSaveGame *savefile ) const { + savefile->WriteStaticObject( physicsObj ); + + player.Save( savefile ); + + savefile->WriteAngles( viewAngles ); + savefile->WriteMat3( eyeAxis ); + + spiritFx.Save( savefile ); + + savefile->WriteInt( cachedCurrentWeapon ); + savefile->WriteInt( activationTime ); +} + +void hhSpiritProxy::Restore( idRestoreGame *savefile ) { + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); + + player.Restore( savefile ); + + savefile->ReadAngles( viewAngles ); + savefile->ReadMat3( eyeAxis ); + + spiritFx.Restore( savefile ); + + savefile->ReadInt( cachedCurrentWeapon ); + savefile->ReadInt( activationTime ); +} + +#define _CHEAP_PROX_SYNC + +void hhSpiritProxy::WriteToSnapshot( idBitMsgDelta &msg ) const { +#ifdef _CHEAP_PROX_SYNC + //we don't want to deal with physics stuff for this proxy on the client i suppose. + //write the origin/axis directly and don't worry about varying physics types. + const idVec3 &origin = renderEntity.origin; + msg.WriteFloat(origin.x); + msg.WriteFloat(origin.y); + msg.WriteFloat(origin.z); +#endif + + idQuat q = renderEntity.axis.ToQuat(); + msg.WriteFloat(q.w); + msg.WriteFloat(q.x); + msg.WriteFloat(q.y); + msg.WriteFloat(q.z); + + msg.WriteBits(fl.hidden, 1); + + msg.WriteBits(player.GetSpawnId(), 32); + + msg.WriteBits(netAnimType, 2); + +#ifndef _CHEAP_PROX_SYNC + msg.WriteBits(physicsObj.GetClipMask(), 32); + msg.WriteBits(physicsObj.GetContents(), 32); + physicsObj.WriteToSnapshot(msg, false); +#endif +} + +void hhSpiritProxy::ReadFromSnapshot( const idBitMsgDelta &msg ) { +#ifdef _CHEAP_PROX_SYNC + idVec3 origin; + origin.x = msg.ReadFloat(); + origin.y = msg.ReadFloat(); + origin.z = msg.ReadFloat(); +#endif + + idQuat q; + q.w = msg.ReadFloat(); + q.x = msg.ReadFloat(); + q.y = msg.ReadFloat(); + q.z = msg.ReadFloat(); + +#ifdef _CHEAP_PROX_SYNC + GetPhysics()->SetOrigin(origin); + + idMat3 axis = q.ToMat3(); + GetPhysics()->SetAxis(axis); + viewAxis = axis; +#else + viewAxis = q.ToMat3(); +#endif + + bool hidden = !!msg.ReadBits(1); + if (hidden != fl.hidden) { + if (hidden) { + Hide(); + } + else { + Show(); + } + } + + player.SetSpawnId(msg.ReadBits(32)); + + netAnimType = msg.ReadBits(2); + + //if we haven't started the animation on the client, then start it + if (!clientAnimated && netAnimType && player.IsValid() && player.GetEntity() && player->IsType(hhPlayer::Type) && player->GetAnimator()) { //verify the owner is still valid + StartAnimation(); + clientAnimated = true; + } + +#ifndef _CHEAP_PROX_SYNC + if (player.IsValid() && GetPhysics() != &physicsObj) { + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel(player->GetPhysics()->GetClipModel()), 1.0f ); + physicsObj.CheckWallWalk( true ); + if( player->IsCrouching() ) { + physicsObj.ForceCrouching(); + } + SetPhysics( &physicsObj ); + } + physicsObj.SetClipMask(msg.ReadBits(32)); + physicsObj.SetContents(msg.ReadBits(32)); + physicsObj.ReadFromSnapshot(msg, false); +#endif +} + +void hhSpiritProxy::ClientPredictionThink( void ) { + Think(); +} + +//proxy needs to clear its icons as well when exiting the snapshot +void hhSpiritProxy::NetZombify(void) { + HidePlayerIcons(); + idActor::NetZombify(); +} + +// Derived from hhMonsterAI::CrashLand() and hhPlayer::CrashLand() -mdl +void hhSpiritProxy::CrashLand( const idVec3 &oldOrigin, const idVec3 &oldVelocity ) { + const trace_t& trace = physicsObj.GetGroundTrace(); + if ( af.IsActive() || (!physicsObj.HasGroundContacts() || trace.fraction == 1.0f) && !IsBound() ) { + return; + } + + //aob - only check when we land on the ground + //If we get here we can assume we currently have ground contacts + if( physicsObj.HadGroundContacts() ) { + return; + } + + // if the monster wasn't going down + if ( ( oldVelocity * -physicsObj.GetGravityNormal() ) >= 0.0f ) { + return; + } + + waterLevel_t waterLevel = physicsObj.GetWaterLevel(); + + // never take falling damage if completely underwater + if ( waterLevel == WATERLEVEL_HEAD ) { + return; + } + + // no falling damage if touching a nodamage surface + bool 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; + } + } + + idVec3 deltaVelocity = DetermineDeltaCollisionVelocity( oldVelocity, trace ); + float delta = (IsBound()) ? deltaVelocity.Length() : deltaVelocity * physicsObj.GetGravityNormal(); + + // reduce falling damage if there is standing water + if ( waterLevel == WATERLEVEL_WAIST ) { + delta *= 0.25f; + } + if ( waterLevel == WATERLEVEL_FEET ) { + delta *= 0.5f; + } + + if ( delta < player->crashlandSpeed_jump ) { + return; // Early out + } + + if( trace.fraction == 1.0f ) { + return; + } + + // Determine damage to what you're landing on + idVec3 fallDir = oldVelocity; + fallDir.Normalize(); + float damageScale = hhUtils::CalculateScale( delta, player->crashlandSpeed_soft, player->crashlandSpeed_fatal ); + idVec3 reverseContactNormal = -physicsObj.GetGroundContactNormal(); + idEntity *entity = gameLocal.GetTraceEntity( trace ); + if( entity && trace.c.entityNum != ENTITYNUM_WORLD ) { + entity->ApplyImpulse( this, 0, trace.c.point, (oldVelocity * reverseContactNormal) * reverseContactNormal );//Not sure if this impulse is large enough + + const char* entityDamageName = spawnArgs.GetString( "def_damageFellOnto" ); + if( *entityDamageName && damageScale > 0.0f) { + entity->Damage( this, this, fallDir, entityDamageName, damageScale, INVALID_JOINT ); + } + } + + // Calculate damage to self + const char* selfDamageName = NULL; + if ( delta < player->crashlandSpeed_soft ) { // Soft Fall + //AI_SOFTLANDING = true; + selfDamageName = player->spawnArgs.GetString( "def_damageSoftFall" ); + } + else if ( delta < player->crashlandSpeed_fatal ) { // Hard Fall + //AI_HARDLANDING = true; + selfDamageName = player->spawnArgs.GetString( "def_damageHardFall" ); + } + else { // Fatal Fall + //AI_HARDLANDING = true; + selfDamageName = player->spawnArgs.GetString( "def_damageFatalFall" ); + } + + if( *selfDamageName && damageScale > 0.0f && !noDamage ) { + pain_debounce_time = gameLocal.time + pain_delay + 1; // ignore pain since we'll play our landing anim + hhPlayer *tmp = player.GetEntity(); + player->StopSpiritWalk(true); // Invalidates player + HH_ASSERT(!player.IsValid()); // If it's still valid, we failed to stop spirit walking + tmp->Damage( NULL, NULL, fallDir, selfDamageName, damageScale, INVALID_JOINT ); + } +} + +//========================================================================== +// hhDeathWalkProxy +//========================================================================== + +CLASS_DECLARATION( hhSpiritProxy, hhDeathWalkProxy ) +END_CLASS + +hhDeathProxy::~hhDeathProxy(void) { + HH_ASSERT( gameLocal.isMultiplayer || !player.IsValid() || player->IsDeathWalking() ); +} + +/* +================ +hhDeathWalkProxy::Spawn +================ +*/ +void hhDeathWalkProxy::Spawn() { + initialPos = GetOrigin(); + spawnArgs.GetFloat("bodyMoveScale", "512", bodyMoveScale); + fl.takedamage = false; + timeSinceStage2Started = 0; +} + +void hhDeathWalkProxy::Save(idSaveGame *savefile) const { + savefile->WriteVec3(initialPos); + savefile->WriteFloat(bodyMoveScale); + savefile->WriteInt(timeSinceStage2Started); +} + +void hhDeathWalkProxy::Restore(idRestoreGame *savefile) { + savefile->ReadVec3(initialPos); + savefile->ReadFloat(bodyMoveScale); + savefile->ReadInt(timeSinceStage2Started); +} + +void hhDeathWalkProxy::Think() { + float lerpTime = 0.05f; + + if (!player.IsValid() || !player.GetEntity() || !player->IsType(hhPlayer::Type) || !player->IsDeathWalking()) { + //if player has become invalid or is no longer deathwalking, i wish to be removed. + PostEventMS(&EV_Remove, 0); + return; + } + + hhSpiritProxy::Think(); + + float spiritPercent = player->GetDeathWalkPower() / player->spawnArgs.GetFloat( "deathWalkPowerMax", "1000" ); + + idVec3 newPos = initialPos; + if (player->DeathWalkStage2()) { + timeSinceStage2Started += gameLocal.msec; + if (timeSinceStage2Started > player->spawnArgs.GetInt("deathwalkBodyDropDelayMS")) { + newPos.z = initialPos.z - player->spawnArgs.GetFloat("deathwalkOffsetBelowPortal"); + lerpTime = 0.02f; + } + } + else { + timeSinceStage2Started = 0; + newPos.z = initialPos.z + bodyMoveScale - spiritPercent*bodyMoveScale; + } + + //lerp into that position + idVec3 lerped; + lerped.SLerp(GetOrigin(), newPos, lerpTime ); + SetOrigin(lerped); +} + +void hhDeathWalkProxy::ActivateProxy( hhPlayer *owner, const idVec3& origin, const idMat3 &bboxAxis, const idMat3& newViewAxis, const idAngles& newViewAngles, const idMat3& newEyeAxis ) { + hhSpiritProxy::ActivateProxy(owner, origin, bboxAxis, newViewAxis, newViewAngles, newEyeAxis); + + physicsObj.SetContents(0); //not solid + physicsObj.SetGravity(idVec3(0, 0, 0)); //no gravity + + SetInitialPos(origin); //this is our initial position to move vertically from +} + +void hhDeathWalkProxy::SetInitialPos(const idVec3 &pos) { + initialPos = pos; +} + +void hhDeathWalkProxy::StartAnimation() { + int spiritBlendTime = spawnArgs.GetInt( "spiritBlendTime", "400" ); + // Copy the current player animation to blend into the spirit anim + GetAnimator()->CopyAnimations( *( player->GetAnimator() ) ); + + // Play the initial animation + int anim = GetAnimator()->GetAnim("deathfloat"); + GetAnimator()->ClearAllAnims( gameLocal.time, spiritBlendTime ); + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, spiritBlendTime ); + netAnimType = 3; //rww +} + + + +//========================================================================== +// hhDeathProxy +//========================================================================== + +CLASS_DECLARATION( hhSpiritProxy, hhDeathProxy ) + EVENT( EV_SpawnEffect, hhDeathProxy::Event_SpawnEffect ) + EVENT( EV_Activate, hhDeathProxy::Event_Activate ) +END_CLASS + + +void hhDeathProxy::Spawn() { + lastPhysicalLocation.Zero(); + lastPhysicalAxis.Identity(); +} + +void hhDeathProxy::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + //idActor::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} + +void hhDeathProxy::Event_SpawnEffect() { +} + +void hhDeathProxy::ActivateProxy( hhPlayer *owner, const idVec3& origin, const idMat3 &bboxAxis, const idMat3& newViewAxis, const idAngles& newViewAngles, const idMat3& newEyeAxis ) { + hhSpiritProxy::ActivateProxy( owner, origin, bboxAxis, newViewAxis, newViewAngles, newEyeAxis ); + + // Save "return to" location + lastPhysicalLocation = origin; + lastPhysicalAxis = bboxAxis; + + // Ragdoll the proxy + StartRagdoll(); +} + +void hhDeathProxy::StartAnimation() { + if (gameLocal.isMultiplayer) { //rww + UpdateModelForPlayer(); //update proxy model for whatever the player is using. + } + // Set the start of the death proxy to the current animation in the player + GetAnimator()->CopyAnimations( *( player->GetAnimator() ) ); + GetAnimator()->CopyPoses( *( player->GetAnimator() ) ); + netAnimType = 3; //rww +} + +// mdl: Handy for debugging the death proxy +void hhDeathProxy::Event_Activate() { + hhPlayer *player = reinterpret_cast (gameLocal.GetLocalPlayer()); + ActivateProxy(player, GetPhysics()->GetOrigin(), GetPhysics()->GetAxis(), viewAxis, viewAngles, player->GetEyeAxis()); +} + + + +//========================================================================== +// hhMPDeathProxy +//========================================================================== + +idCVar g_mpPlayerRagdollLife( "g_mpPlayerRagdollLife", "1500", CVAR_GAME | CVAR_INTEGER, "player ragdoll life" ); +idCVar g_mpSyncPlayerRagdoll( "g_mpSyncPlayerRagdoll", "0", CVAR_GAME | CVAR_BOOL, "whether to sync mp ragdolls" ); + +const idEventDef EV_CorpseRemove( "", NULL ); + +CLASS_DECLARATION( hhDeathProxy, hhMPDeathProxy ) + EVENT( EV_CorpseRemove, hhMPDeathProxy::Event_CorpseRemove ) +END_CLASS + + +/* +================ +hhMPDeathProxy::Spawn +================ +*/ +void hhMPDeathProxy::Spawn() { + hasInitial = false; + didFling = false; + + int delay = g_mpPlayerRagdollLife.GetInteger(); + fl.clientEvents = true; + PostEventMS(&EV_CorpseRemove, delay); + + SetShaderParm(SHADERPARM_TIME_OF_DEATH, MS2SEC(gameLocal.time+1000)); + SetSkinByName(spawnArgs.GetString("skin_death")); + //GetPhysics()->SetGravity(idVec3(0,0,512)); cheesy spirit floating upward effect (doesn't work inside grav zones) +} + +/* +================ +hhMPDeathProxy::ActivateProxy +================ +*/ +void hhMPDeathProxy::ActivateProxy( hhPlayer *owner, const idVec3& origin, const idMat3 &bboxAxis, const idMat3& newViewAxis, const idAngles& newViewAngles, const idMat3& newEyeAxis ) { + hhDeathProxy::ActivateProxy(owner, origin, bboxAxis, newViewAxis, newViewAngles, newEyeAxis); + + hasInitial = true; + initialPos = origin; + initialRot = bboxAxis.ToCQuat(); + + GetPhysics()->SetOrigin(initialPos); + GetPhysics()->SetAxis(initialRot.ToMat3()); +} + +/* +================ +hhMPDeathProxy::Event_CorpseRemove +================ +*/ +void hhMPDeathProxy::Event_CorpseRemove(void) { + Hide(); + GetPhysics()->SetContents(0); + if (!gameLocal.isClient) { + PostEventMS(&EV_Remove, 1000); + } +} + +/* +================ +hhMPDeathProxy::ActivateProxy +================ +*/ +void hhMPDeathProxy::SetFling(const idVec3 &point, const idVec3 &force) { + //initialFlingPoint = point; + initialFlingForce = force; +} + +/* +================ +hhMPDeathProxy::WriteToSnapshot +================ +*/ +void hhMPDeathProxy::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteBits(player.GetSpawnId(), 32); + + msg.WriteBits(IsActiveAF(), 1); + + bool physSync = g_mpSyncPlayerRagdoll.GetBool(); //the server can decide to do this. + msg.WriteBits(physSync, 1); + if (physSync) { + GetPhysics()->WriteToSnapshot(msg); + } + else { //if we are not properly sync'ing then use just an initial orientation + msg.WriteFloat(initialPos.x); + msg.WriteFloat(initialPos.y); + msg.WriteFloat(initialPos.z); + + msg.WriteFloat(initialRot.x); + msg.WriteFloat(initialRot.y); + msg.WriteFloat(initialRot.z); + + //FIXME get some of this from the server? may be necessary if a more complex method of determining the fling direction is devised. + /* + msg.WriteFloat(initialFlingPoint.x); + msg.WriteFloat(initialFlingPoint.y); + msg.WriteFloat(initialFlingPoint.z); + */ + + msg.WriteFloat(initialFlingForce.x); + msg.WriteFloat(initialFlingForce.y); + msg.WriteFloat(initialFlingForce.z); + } +} + +/* +================ +hhMPDeathProxy::ReadFromSnapshot +================ +*/ +void hhMPDeathProxy::ReadFromSnapshot( const idBitMsgDelta &msg ) { + player.SetSpawnId(msg.ReadBits(32)); + + bool isRagging = !!msg.ReadBits(1); + + bool physSync = !!msg.ReadBits(1); + + if (physSync) { + if (isRagging && !IsActiveAF()) { //then start ragging on the client + if (player.IsValid()) { //update model for player before initiating ragdoll + UpdateModelForPlayer(); + } + StartRagdoll(); + } + GetPhysics()->ReadFromSnapshot(msg); + } + else { //if we are not properly sync'ing then use just an initial orientation + initialPos.x = msg.ReadFloat(); + initialPos.y = msg.ReadFloat(); + initialPos.z = msg.ReadFloat(); + + initialRot.x = msg.ReadFloat(); + initialRot.y = msg.ReadFloat(); + initialRot.z = msg.ReadFloat(); + + initialFlingForce.x = msg.ReadFloat(); + initialFlingForce.y = msg.ReadFloat(); + initialFlingForce.z = msg.ReadFloat(); + + if (!hasInitial) { //if not set at all yet, put it here. + GetPhysics()->SetOrigin(initialPos); //set before we start to ragdoll, so that the af starts out at a valid orientation + GetPhysics()->SetAxis(initialRot.ToMat3()); + + hasInitial = true; + } + + if (isRagging && !IsActiveAF()) { //then start ragging on the client + if (player.IsValid()) { //update model for player before initiating ragdoll + UpdateModelForPlayer(); + } + StartRagdoll(); + + //set the initial orientation again after beginning ragdoll + GetPhysics()->SetOrigin(initialPos); + GetPhysics()->SetAxis(initialRot.ToMat3()); + } + } +} + +/* +================ +hhMPDeathProxy::ClientPredictionThink +================ +*/ +void hhMPDeathProxy::ClientPredictionThink( void ) { + if (!gameLocal.isNewFrame) { + return; + } + + Think(); + if (hasInitial && !didFling) { + GetPhysics()->AddForce(0, GetPhysics()->GetOrigin(0), initialFlingForce*256.0f*256.0f); + didFling = true; + } +} + + +//========================================================================== +// +// hhPossessedProxy +// +// A version of the spirit proxy spawned when the player is possessed +//========================================================================== + +CLASS_DECLARATION( hhSpiritProxy, hhPossessedProxy ) +END_CLASS + +void hhPossessedProxy::ActivateProxy( hhPlayer *owner, const idVec3& origin, const idMat3 &bboxAxis, const idMat3& newViewAxis, const idAngles& newViewAngles, const idMat3& newEyeAxis ) { + hhSpiritProxy::ActivateProxy(owner, origin, bboxAxis, newViewAxis, newViewAngles, newEyeAxis); + + Hide(); + physicsObj.SetContents( 0 ); +} + +/* +=============== +hhSpiritProxy::DrawPlayerIcons +=============== +*/ +void hhSpiritProxy::DrawPlayerIcons( void ) { + if ( !NeedsIcon() ) { + //playerIcon.FreeIcon(); + playerTeamIcon.FreeIcon(); + return; + } + //player->UpdatePlayerIcons(); //update the owner's icon status + //playerIcon.Draw( this, INVALID_JOINT ); + playerTeamIcon.Draw( this, INVALID_JOINT ); +} + +/* +=============== +hhSpiritProxy::HidePlayerIcons +=============== +*/ +void hhSpiritProxy::HidePlayerIcons( void ) { + //playerIcon.FreeIcon(); + playerTeamIcon.FreeIcon(); +} + +/* +=============== +hhSpiritProxy::NeedsIcon +============== +*/ +bool hhSpiritProxy::NeedsIcon( void ) { + if (IsType(hhMPDeathProxy::Type)) { + return false; + } + if (!player.IsValid()) { + return false; + } + if (IsHidden()) { + return false; + } + return player->entityNumber != gameLocal.localClientNum && ( /*player->isLagged || player->isChatting ||*/ gameLocal.gameType == GAME_TDM ) && gameLocal.EntInClientSnapshot(entityNumber); +} diff --git a/src/Prey/prey_spiritproxy.h b/src/Prey/prey_spiritproxy.h new file mode 100644 index 0000000..40ebada --- /dev/null +++ b/src/Prey/prey_spiritproxy.h @@ -0,0 +1,200 @@ + +#ifndef __PREY_SPIRITPROXY_H__ +#define __PREY_SPIRITPROXY_H__ + +// nla - Forward declare +class hhPlayer; + +extern const idEventDef EV_OrientToGravity; + +// SPIRIT PROXY =============================================================== + +class hhSpiritProxy : public idActor { +public: + CLASS_PROTOTYPE( hhSpiritProxy ); + hhSpiritProxy( void ); + void Spawn( void ); + static hhSpiritProxy *CreateProxy( const char *name, hhPlayer *owner, const idVec3& origin, const idMat3& bboxAxis, const idMat3& newViewAxis, const idAngles& newViewAngles, const idMat3& newEyeAxis ); + + virtual void SetModel( const char *modelname ); //rww + + virtual void UpdateModelForPlayer(void); //rww + virtual void Think(); + virtual void ActivateProxy( hhPlayer *owner, const idVec3& origin, const idMat3& bboxAxis, const idMat3& newViewAxis, const idAngles& newViewAngles, const idMat3& newEyeAxis ); + virtual void DeactivateProxy( void ); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual bool IsWallWalking() const; + virtual bool IsCrouching() const; + virtual bool ShouldRemainAlignedToAxial(); + virtual void RestorePlayerLocation( const idVec3& origin, const idMat3& bboxAxis, const idVec3& viewDir, const idAngles& angles ); + virtual void StartAnimation(); + hhPlayer* GetPlayer(void) const { return player.GetEntity(); } // JRM + void OrientToGravity( bool orient ); + virtual void ShouldRemainAlignedToAxial( bool remainAligned ); + virtual bool ShouldRemainAlignedToAxial() const { return physicsObj.ShouldRemainAlignedToAxial(); } + virtual void UpdateModelTransform( void ); // mdl + virtual bool AllowCollision(const trace_t &collision); //rww + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - network code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + virtual void NetZombify(void); + + //rww - for mp player icons transferring to the prox + void DrawPlayerIcons( void ); + void HidePlayerIcons( void ); + bool NeedsIcon( void ); + + ID_INLINE float GetActivationTime( void ) const { return activationTime; } + +public: + //Overridden methods + virtual void SetAxis( const idMat3& axis ) { idEntity::SetAxis( axis ); } + + void Event_SpawnEffect(); + void Event_OrientToGravity( bool orient ); + void Event_ResetGravity(); + void Event_ShouldRemainAlignedToAxial( bool remainAligned ); + void Event_AssignSpiritFx( hhEntityFx* fx ); + +protected: + void CrashLand(const idVec3 &oldOrigin, const idVec3 &oldVelocity); + + hhPhysics_Player physicsObj; + + idEntityPtr player; + + idAngles viewAngles; + idMat3 eyeAxis; + + idEntityPtr spiritFx; + + int cachedCurrentWeapon; + int activationTime; // Saved to keep the player from being bounced back into the body too quickly if damaged + + //rww - only matters for client keeping track of if animation has started. no need to save/restore. + bool clientAnimated; + //rww - only for telling clients which anim we are playing + int netAnimType; + + //rww - for keeping track of player model changes in mp + int playerModelNum; + + //rww - showing player status icons on the proxy in mp + //proxy could optionally support lag/chat/etc if we wanted to sync those states on the proxy itself + //idPlayerIcon playerIcon; + hhPlayerTeamIcon playerTeamIcon; +}; + +/* +===================== +hhSpiritProxy::IsWallWalking +===================== +*/ +ID_INLINE bool hhSpiritProxy::IsWallWalking() const { + return physicsObj.IsWallWalking(); +} + +/* +===================== +hhSpiritProxy::IsCrouching +===================== +*/ +ID_INLINE bool hhSpiritProxy::IsCrouching() const { + return physicsObj.IsCrouching(); +} + +/* +===================== +hhSpiritProxy::ShouldRemainAlignedToAxial +===================== +*/ +ID_INLINE bool hhSpiritProxy::ShouldRemainAlignedToAxial() { + return physicsObj.ShouldRemainAlignedToAxial(); +} + +// DEATH PROXY ================================================================ + +class hhDeathProxy : public hhSpiritProxy { +public: + CLASS_PROTOTYPE( hhDeathProxy ); + + ~hhDeathProxy(); + + void Spawn(); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void ActivateProxy( hhPlayer *owner, const idVec3& origin, const idMat3 &bboxAxis, const idMat3& newViewAxis, const idAngles& newViewAngles, const idMat3& newEyeAxis ); + virtual void StartAnimation(); + +protected: + void Event_SpawnEffect(); + void Event_Activate(); + +protected: + idVec3 lastPhysicalLocation; // location to return to from deathwalk + idMat3 lastPhysicalAxis; // orientation to return to from deathwalk +}; + +// MP DEATHWALK PROXY ================================================================ +//rww - for corpses in multiplayer +class hhMPDeathProxy : public hhDeathProxy { +public: + CLASS_PROTOTYPE( hhMPDeathProxy ); + + void Spawn(); + virtual void ActivateProxy( hhPlayer *owner, const idVec3& origin, const idMat3 &bboxAxis, const idMat3& newViewAxis, const idAngles& newViewAngles, const idMat3& newEyeAxis ); + + virtual void Event_CorpseRemove(void); + + void SetFling(const idVec3 &point, const idVec3 &force); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + +private: + bool hasInitial; + bool didFling; + idVec3 initialPos; + idCQuat initialRot; + idVec3 initialFlingForce; +}; + +// DEATHWALK PROXY ================================================================ +//rww - this is the entity that sits in the middle of deathwalk and moves up/down. + +class hhDeathWalkProxy : public hhSpiritProxy { +public: + CLASS_PROTOTYPE( hhDeathWalkProxy ); + + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + virtual void Think(); + virtual void ActivateProxy( hhPlayer *owner, const idVec3& origin, const idMat3& bboxAxis, const idMat3& newViewAxis, const idAngles& newViewAngles, const idMat3& newEyeAxis ); + void SetInitialPos(const idVec3 &pos); + virtual void StartAnimation(); + +protected: + idVec3 initialPos; + float bodyMoveScale; + int timeSinceStage2Started; +}; + +// POSSESSED PROXY ================================================================ +// An invisible proxy that is spawned when the player is possessed + +class hhPossessedProxy : public hhSpiritProxy { +public: + CLASS_PROTOTYPE( hhPossessedProxy ); + + virtual void ActivateProxy( hhPlayer *owner, const idVec3& origin, const idMat3& bboxAxis, const idMat3& newViewAxis, const idAngles& newViewAngles, const idMat3& newEyeAxis ); + +protected: +}; + +#endif /* __PREY_SPIRITPROXY_H__ */ diff --git a/src/Prey/prey_spiritsecret.cpp b/src/Prey/prey_spiritsecret.cpp new file mode 100644 index 0000000..3f7a362 --- /dev/null +++ b/src/Prey/prey_spiritsecret.cpp @@ -0,0 +1,72 @@ +//************************************************************************** +//** +//** PREY_SPIRITSECRET.CPP +//** +//** Game code for the spirit secret entities +//** Spirit secret entities are entities that are only visible +//** and blockable in normal mode. +//************************************************************************** + +// HEADER FILES ------------------------------------------------------------ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +// MACROS ------------------------------------------------------------------ + +// TYPES ------------------------------------------------------------------- + +// CLASS DECLARATIONS ------------------------------------------------------ + +CLASS_DECLARATION( idEntity, hhSpiritSecret ) + EVENT( EV_Activate, hhSpiritSecret::Event_Activate ) +END_CLASS + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// hhSpiritSecret::Spawn +// +//========================================================================== + +void hhSpiritSecret::Spawn(void) { + fl.takedamage = false; + + if ( !spawnArgs.GetInt( "start_off" ) ) { + GetPhysics()->SetContents( CONTENTS_FORCEFIELD | CONTENTS_SHOOTABLE ); + } else { // Start the secret off + GetPhysics()->SetContents( 0 ); + Hide(); + } + + SetShaderParm( SHADERPARM_MODE, gameLocal.random.CRandomFloat() ); +} + +//========================================================================== +// +// hhSpiritSecret::Event_Activate +// +//========================================================================== + +void hhSpiritSecret::Event_Activate( idEntity *activator ) { + if ( IsHidden() ) { + Show(); + GetPhysics()->SetContents( CONTENTS_FORCEFIELD | CONTENTS_SHOOTABLE ); + } else { + Hide(); + GetPhysics()->SetContents( 0 ); + } +} diff --git a/src/Prey/prey_spiritsecret.h b/src/Prey/prey_spiritsecret.h new file mode 100644 index 0000000..52c47d1 --- /dev/null +++ b/src/Prey/prey_spiritsecret.h @@ -0,0 +1,15 @@ + +#ifndef __PREY_SPIRITSECRET_H__ +#define __PREY_SPIRITSECRET_H__ + +class hhSpiritSecret : public idEntity { +public: + CLASS_PROTOTYPE( hhSpiritSecret ); + + void Spawn( void ); + +protected: + void Event_Activate( idEntity *activator ); +}; + +#endif /* __PREY_SPIRITSECRET_H__ */ diff --git a/src/Prey/prey_vehiclefirecontroller.cpp b/src/Prey/prey_vehiclefirecontroller.cpp new file mode 100644 index 0000000..1b6f561 --- /dev/null +++ b/src/Prey/prey_vehiclefirecontroller.cpp @@ -0,0 +1,225 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION( hhFireController, hhVehicleFireController ) +END_CLASS + +/* +================ +hhVehicleFireController::Init +================ +*/ +void hhVehicleFireController::Init( const idDict* viewDict, hhVehicle* self, idActor* owner ) { + hhFireController::Init( viewDict ); + + this->owner = owner; + this->self = self; + + recoil = dict->GetFloat( "recoil" ); + + if ( owner && owner->IsType( idAI::Type ) ) { + spread = dict->GetFloat( "monster_spread" ); + } + + SetBarrelOffsetList( "muzzleOffset", barrelOffsets ); +} + +/* +================ +hhVehicleFireController::UsesCrosshair +================ +*/ +bool hhVehicleFireController::UsesCrosshair() const { + return bCrosshair; +} + +/* +================ +hhVehicleFireController::Clear +================ +*/ +void hhVehicleFireController::Clear() { + hhFireController::Clear(); + + nextFireTime = gameLocal.GetTime(); + barrelOffsets.Clear(); + + owner = NULL; + self = NULL; +} + +/* +================ +hhVehicleFireController::Save +================ +*/ +void hhVehicleFireController::Save( idSaveGame *savefile ) const { + self.Save( savefile ); + owner.Save( savefile ); + savefile->WriteFloat( recoil ); + + int num = barrelOffsets.Num(); + savefile->WriteInt( num ); + for( int i = 0; i < num; i++ ) { + savefile->WriteVec3( barrelOffsets[i] ); + } + + savefile->WriteInt( nextFireTime ); +} + +/* +================ +hhVehicleFireController::Restore +================ +*/ +void hhVehicleFireController::Restore( idRestoreGame *savefile ) { + self.Restore( savefile ); + owner.Restore( savefile ); + savefile->ReadFloat( recoil ); + + int num; + savefile->ReadInt( num ); + idVec3 tmp; + for( int i = 0; i < num; i++ ) { + savefile->ReadVec3( tmp ); + barrelOffsets.Append( tmp ); + } + + savefile->ReadInt( nextFireTime ); +} + +/* +================ +hhVehicleFireController::WeaponFeedback +================ +*/ +void hhVehicleFireController::WeaponFeedback() { + if( self.IsValid() && self->GetPhysics() ) { + hhPhysics_Vehicle* selfPhysics = static_cast( self->GetPhysics() ); + self->ApplyImpulse( gameLocal.world, 0, self->GetOrigin() + selfPhysics->GetCenterOfMass(), -self->GetAxis()[0] * GetRecoil() * selfPhysics->GetMass() ); + } +} + +/* +================ +hhVehicleFireController::SetBarrelOffsetList +================ +*/ +void hhVehicleFireController::SetBarrelOffsetList( const char* keyPrefix, hhCycleList& offsetList ) { + const idKeyValue* kv = dict->MatchPrefix( keyPrefix ); + while( kv ) { + offsetList.Append( dict->GetVector(kv->GetKey().c_str()) ); + kv = dict->MatchPrefix( keyPrefix, kv ); + } +} + +/* +================= +hhVehicleFireController::DetermineAimAxis +================= +*/ +idMat3 hhVehicleFireController::DetermineAimAxis( const idVec3& muzzlePos, const idMat3& weaponAxis ) { + idAngles aimAngles; + idVec3 aimPos; + + aimPos = idVec3( 0.0f, 0.0f, owner->EyeHeight() ) + dict->GetVector( "offset_gunTarget" ); + aimPos *= self->GetFireAxis(); + aimPos += self->GetFireOrigin(); + + aimAngles = (aimPos - muzzlePos).ToAngles(); + aimAngles[2] = weaponAxis.ToAngles()[2]; + return aimAngles.ToMat3(); +} + +/* +================ +hhVehicleFireController::LaunchProjectiles +================ +*/ +bool hhVehicleFireController::LaunchProjectiles( const idVec3& pushVelocity ) { + if( nextFireTime > gameLocal.GetTime() ) { + return false; + } + + if( !hhFireController::LaunchProjectiles(pushVelocity) ) { + return false; + } + + gameLocal.AlertAI( owner.GetEntity() ); + nextFireTime = gameLocal.GetTime() + SEC2MS( GetFireDelay() ); + return true; +} + +/* +================ +hhFireController::AmmoAvailable +================ +*/ +int hhVehicleFireController::AmmoAvailable() const { + if ( self.IsValid() ) { + return self->HasPower( AmmoRequired() ); + } else { + return 0; + } +} + +/* +================ +hhFireController::UseAmmo +================ +*/ +void hhVehicleFireController::UseAmmo() { + if( self.IsValid() ) { + self->ConsumePower( AmmoRequired() ); + } +} + +/* +================ +hhVehicleFireController::GetCollisionBBox +================ +*/ +const idBounds& hhVehicleFireController::GetCollisionBBox() { + return self->GetPhysics()->GetAbsBounds(); +} + +/* +================ +hhVehicleFireController::CalculateMuzzlePosition +================ +*/ +void hhVehicleFireController::CalculateMuzzlePosition( idVec3& origin, idMat3& axis ) { + axis = self->GetFireAxis(); + origin = barrelOffsets.Next() * axis + self->GetFireOrigin(); +} + +/* +================ +hhVehicleFireController::GetProjectileOwner +================ +*/ +idEntity *hhVehicleFireController::GetProjectileOwner() const { + return self.GetEntity(); +} + +/* +================ +hhVehicleFireController::GetSelf +================ +*/ +hhRenderEntity *hhVehicleFireController::GetSelf() { + return self.GetEntity(); +} + +/* +================ +hhVehicleFireController::GetSelfConst +================ +*/ +const hhRenderEntity *hhVehicleFireController::GetSelfConst() const { + return self.GetEntity(); +} + diff --git a/src/Prey/prey_vehiclefirecontroller.h b/src/Prey/prey_vehiclefirecontroller.h new file mode 100644 index 0000000..f0e8b76 --- /dev/null +++ b/src/Prey/prey_vehiclefirecontroller.h @@ -0,0 +1,47 @@ +#ifndef __HH_VEHICLE_FIRE_CONTROLLER_H +#define __HH_VEHICLE_FIRE_CONTROLLER_H + +#include "gamesys/Class.h" + +class hhVehicleFireController : public hhFireController { + CLASS_PROTOTYPE(hhVehicleFireController); + +public: + virtual void Clear(); + virtual void Init( const idDict* viewDict, hhVehicle* self, idActor* owner ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool LaunchProjectiles( const idVec3& pushVelocity ); + virtual void WeaponFeedback(); + virtual idMat3 DetermineAimAxis( const idVec3& muzzlePos, const idMat3& weaponAxis ); + + virtual int AmmoAvailable() const; + virtual void UseAmmo(); + + virtual float GetRecoil() const { return recoil; } + virtual bool UsesCrosshair() const; + + //rww - made public + hhCycleList barrelOffsets; + +protected: + virtual const idBounds& GetCollisionBBox(); + virtual idEntity* GetProjectileOwner() const; + void SetBarrelOffsetList( const char* keyPrefix, hhCycleList& offsetList ); + void SaveBarrelOffsetList( const hhCycleList& offsetList, idSaveGame *savefile ) const; + void RestoreBarrelOffsetList( hhCycleList& offsetList, idRestoreGame *savefile ); + + virtual void CalculateMuzzlePosition( idVec3& origin, idMat3& axis ); + virtual hhRenderEntity *GetSelf(); + virtual const hhRenderEntity *GetSelfConst() const; + +protected: + idEntityPtr self; + idEntityPtr owner; + + float recoil; + int nextFireTime; +}; + +#endif \ No newline at end of file diff --git a/src/Prey/prey_weapon.cpp b/src/Prey/prey_weapon.cpp new file mode 100644 index 0000000..fee20a2 --- /dev/null +++ b/src/Prey/prey_weapon.cpp @@ -0,0 +1,4 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" diff --git a/src/Prey/prey_weapon.h b/src/Prey/prey_weapon.h new file mode 100644 index 0000000..b8b460b --- /dev/null +++ b/src/Prey/prey_weapon.h @@ -0,0 +1,5 @@ +#ifndef __PREY_WEAPON_H__ +#define __PREY_WEAPON_H__ + + +#endif /* !__PREY_WEAPON_H__ */ diff --git a/src/Prey/prey_weaponautocannon.cpp b/src/Prey/prey_weaponautocannon.cpp new file mode 100644 index 0000000..2755f86 --- /dev/null +++ b/src/Prey/prey_weaponautocannon.cpp @@ -0,0 +1,363 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/*********************************************************************** + + hhWeaponAutoCannon + +***********************************************************************/ +const idEventDef EV_Weapon_AdjustHeat( "adjustHeat", "f" ); +const idEventDef EV_Weapon_GetHeatLevel( "getHeatLevel", "", 'f' ); + +const idEventDef EV_Weapon_SpawnRearGasFX( "spawnRearGasFX" ); +const idEventDef EV_SpawnSparkLocal( "", "s" ); + +const idEventDef EV_Broadcast_AssignLeftRearFx( "", "e" ); +const idEventDef EV_Broadcast_AssignRightRearFx( "", "e" ); + +const idEventDef EV_Weapon_OverHeatNetEvent( "overHeatNetEvent" ); //rww + +CLASS_DECLARATION( hhWeapon, hhWeaponAutoCannon ) + EVENT( EV_Weapon_AdjustHeat, hhWeaponAutoCannon::Event_AdjustHeat ) + EVENT( EV_Weapon_GetHeatLevel, hhWeaponAutoCannon::Event_GetHeatLevel ) + EVENT( EV_Weapon_SpawnRearGasFX, hhWeaponAutoCannon::Event_SpawnRearGasFX ) + EVENT( EV_SpawnSparkLocal, hhWeaponAutoCannon::Event_SpawnSparkLocal ) + EVENT( EV_Broadcast_AssignLeftRearFx, hhWeaponAutoCannon::Event_AssignLeftRearFx ) + EVENT( EV_Broadcast_AssignRightRearFx, hhWeaponAutoCannon::Event_AssignRightRearFx ) + EVENT( EV_Weapon_OverHeatNetEvent, hhWeaponAutoCannon::Event_OverHeatNetEvent ) +END_CLASS + +/* +================ +hhWeaponAutoCannon::Spawn +================ +*/ +void hhWeaponAutoCannon::Spawn() { + BecomeActive( TH_TICKER ); + + beamSystem.Clear(); + rearGasFxL.Clear(); + rearGasFxR.Clear(); +} + +/* +================ +hhWeaponAutoCannon::~hhWeaponAutoCannon +================ +*/ +hhWeaponAutoCannon::~hhWeaponAutoCannon() { + SAFE_REMOVE( beamSystem ); + SAFE_REMOVE( rearGasFxL ); + SAFE_REMOVE( rearGasFxR ); +} + +/* +================ +hhWeaponAutoCannon::ParseDef +================ +*/ +void hhWeaponAutoCannon::ParseDef( const char *objectname ) { + hhWeapon::ParseDef( objectname ); + + SetHeatLevel( 0.0f ); + + InitBoneInfo(); + + if (owner.IsValid() && owner.GetEntity() && owner.GetEntity() == gameLocal.GetLocalPlayer()) { //rww - let's make the beam locally, since it is a client entity. no need for the server to do anything. + ProcessEvent( &EV_SpawnSparkLocal, dict->GetString("beam_spark") ); + } + //BroadcastBeam( dict->GetString("beam_spark"), EV_SpawnSparkLocal ); +} + +/* +================ +hhWeaponAutoCannon::UpdateGUI +================ +*/ +void hhWeaponAutoCannon::UpdateGUI() { + if ( GetRenderEntity()->gui[ 0 ] && state != idStr(WP_HOLSTERED) ) { + GetRenderEntity()->gui[ 0 ]->SetStateFloat( "temperature", GetHeatLevel() ); + } +} + +/* +================ +hhWeaponAutoCannon::Ticker +================ +*/ +void hhWeaponAutoCannon::Ticker() { + idVec3 boneOriginL, boneOriginR; + idMat3 boneAxisL, boneAxisR; + + if( beamSystem.IsValid() ) { + GetJointWorldTransform( sparkBoneL, boneOriginL, boneAxisL ); + GetJointWorldTransform( sparkBoneR, boneOriginR, boneAxisR ); + + if( (boneOriginL - boneOriginR).Length() > sparkGapSize ) { + if( !beamSystem->IsHidden() ) { + beamSystem->Hide(); + SetShaderParm( SHADERPARM_MODE, 0.0f ); + } + } else if ( owner->CanShowWeaponViewmodel() ) { + if( beamSystem->IsHidden() ) { + beamSystem->Show(); + SetShaderParm( SHADERPARM_MODE, 1.0f ); + } + } + + beamSystem->SetOrigin( boneOriginL ); + beamSystem->SetTargetLocation( boneOriginR ); + } +} + +/* +================ +hhWeaponAutoCannon::InitBoneInfo +================ +*/ +void hhWeaponAutoCannon::InitBoneInfo() { + assert( dict ); + + GetJointHandle( dict->GetString("joint_sparkL"), sparkBoneL ); + GetJointHandle( dict->GetString("joint_sparkR"), sparkBoneR ); +} + +/* +================ +hhWeaponAutoCannon::SetHeatLevel +================ +*/ +void hhWeaponAutoCannon::SetHeatLevel( const float heatLevel ) { + this->heatLevel = heatLevel; + + SetShaderParm( SHADERPARM_MISC, heatLevel ); +} + +/* +================ +hhWeaponAutoCannon::AdjustHeat +================ +*/ +void hhWeaponAutoCannon::AdjustHeat( const float amount ) { + SetHeatLevel( hhMath::ClampFloat(0.0f, 1.0f, GetHeatLevel() + amount) ); + +//#if _DEBUG +// gameLocal.Printf("HeatLevel: %.2f\n", GetHeatLevel()); +//#endif +} + +/* +================ +hhWeaponAutoCannon::PresentWeapon +================ +*/ +void hhWeaponAutoCannon::PresentWeapon( bool showViewModel ) { + if( IsHidden() || !owner->CanShowWeaponViewmodel() || pm_thirdPerson.GetBool() ) { + if( beamSystem.IsValid() ) { + beamSystem->Activate( false ); + } + } else { + if( beamSystem.IsValid() ) { + beamSystem->Activate( true ); + } + } + + hhWeapon::PresentWeapon( showViewModel ); +} + +void hhWeaponAutoCannon::Show() { + if ( beamSystem.IsValid() ) + beamSystem->Show(); + hhWeapon::Show(); +} + +void hhWeaponAutoCannon::Hide() { + if ( beamSystem.IsValid() ) + beamSystem->Hide(); + hhWeapon::Hide(); +} + +void hhWeaponAutoCannon::WriteToSnapshot( idBitMsgDelta &msg ) const { + hhWeapon::WriteToSnapshot( msg ); + + msg.WriteFloat( GetHeatLevel() ); +} + +void hhWeaponAutoCannon::ReadFromSnapshot( const idBitMsgDelta &msg ) { + hhWeapon::ReadFromSnapshot( msg ); + + SetHeatLevel( msg.ReadFloat() ); +} + +/* +================ +hhWeaponAutoCannon::Event_SpawnSparkLocal +================ +*/ +void hhWeaponAutoCannon::Event_SpawnSparkLocal( const char* defName ) { + assert(dict); //rww - this could happen if the server sent a beam event before we initialized the weap on the client + sparkGapSize = dict->GetFloat( "sparkGapSize" ); + + SAFE_REMOVE( beamSystem ); + beamSystem = hhBeamSystem::SpawnBeam( GetOrigin(), defName, mat3_identity, true ); + if( !beamSystem.IsValid() ) { + return; + } + + //rww - this particular beam not a network entity + beamSystem->fl.networkSync = false; + beamSystem->fl.clientEvents = true; + + beamSystem->fl.neverDormant = true; + beamSystem->GetRenderEntity()->weaponDepthHack = true; + beamSystem->GetRenderEntity()->allowSurfaceInViewID = owner->entityNumber + 1; +} + +/* +================ +hhWeaponAutoCannon::Event_SpawnRearGasFX +================ +*/ +void hhWeaponAutoCannon::Event_SpawnRearGasFX() { + if( !owner->CanShowWeaponViewmodel() || pm_thirdPerson.GetBool() ) + return; + + hhFxInfo fxInfo; + + float fxHeatThreshold = hhMath::ClampFloat( 0.0f, 1.0f, dict->GetFloat("heatThreshold") ); + if( fxHeatThreshold >= GetHeatLevel() ) { + return; + } + + //Checking if fx's are done yet. Only want to get in when fx's are done + if( !rearGasFxL.IsValid() && !rearGasFxR.IsValid() ) { + fxInfo.UseWeaponDepthHack( true ); + fxInfo.RemoveWhenDone( true ); + + BroadcastFxInfoAlongBone( dict->RandomPrefix("fx_rearGas", gameLocal.random), dict->GetString("joint_rearGasFxL"), &fxInfo, &EV_Broadcast_AssignLeftRearFx, false ); + BroadcastFxInfoAlongBone( dict->RandomPrefix("fx_rearGas", gameLocal.random), dict->GetString("joint_rearGasFxR"), &fxInfo, &EV_Broadcast_AssignRightRearFx, false ); + + if( GetHeatLevel() >= 1.0f ) + StartSound( "snd_overheat", SND_CHANNEL_BODY, 0, false, NULL ); + else + StartSound( "snd_steam_vent", SND_CHANNEL_BODY, 0, false, NULL ); + } +} + +/* +================ +hhWeaponAutoCannon::Event_AssignLeftRearFx +================ +*/ +void hhWeaponAutoCannon::Event_AssignLeftRearFx( hhEntityFx* fx ) { + rearGasFxL = fx; +} + +/* +================ +hhWeaponAutoCannon::Event_AssignRightRearFx +================ +*/ +void hhWeaponAutoCannon::Event_AssignRightRearFx( hhEntityFx* fx ) { + rearGasFxR = fx; +} + +/* +================ +hhWeaponAutoCannon::Event_AdjustHeat +================ +*/ +void hhWeaponAutoCannon::Event_AdjustHeat( const float fAmount ) { + if (gameLocal.isClient) { //rww - don't adjust on client + return; + } + AdjustHeat( fAmount ); +} + +/* +================ +hhWeaponAutoCannon::Event_GetHeatLevel +================ +*/ +void hhWeaponAutoCannon::Event_GetHeatLevel() { + idThread::ReturnFloat( GetHeatLevel() ); +} + +/* +================ +hhWeaponAutoCannon::Event_OverHeatNetEvent +================ +*/ +void hhWeaponAutoCannon::Event_OverHeatNetEvent() { //rww + if (gameLocal.isClient) { + return; + } + + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + ServerSendEvent( EVENT_OVERHEAT, &msg, false, -1 ); +} + +/* +================ +hhWeaponAutoCannon::ClientReceiveEvent +================ +*/ +bool hhWeaponAutoCannon::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { //rww + switch (event) { + case EVENT_OVERHEAT: { + SetState("OverHeated", 10); + return true; + } + default: { + return hhWeapon::ClientReceiveEvent( event, time, msg ); + } + } +} + + +/* +================ +hhWeaponAutoCannon::Save +================ +*/ +void hhWeaponAutoCannon::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( heatLevel ); + savefile->WriteFloat( sparkGapSize ); + + beamSystem.Save( savefile ); + + savefile->WriteInt( sparkBoneL.view ); + savefile->WriteInt( sparkBoneL.world ); + + savefile->WriteInt( sparkBoneR.view ); + savefile->WriteInt( sparkBoneR.world ); + + rearGasFxL.Save( savefile ); + rearGasFxR.Save( savefile ); +} + +/* +================ +hhWeaponAutoCannon::Restore +================ +*/ +void hhWeaponAutoCannon::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( heatLevel ); + savefile->ReadFloat( sparkGapSize ); + + beamSystem.Restore( savefile ); + + savefile->ReadInt( reinterpret_cast ( sparkBoneL.view ) ); + savefile->ReadInt( reinterpret_cast ( sparkBoneL.world ) ); + + savefile->ReadInt( reinterpret_cast ( sparkBoneR.view ) ); + savefile->ReadInt( reinterpret_cast ( sparkBoneR.world ) ); + + rearGasFxL.Restore( savefile ); + rearGasFxR.Restore( savefile ); +} diff --git a/src/Prey/prey_weaponautocannon.h b/src/Prey/prey_weaponautocannon.h new file mode 100644 index 0000000..d385acd --- /dev/null +++ b/src/Prey/prey_weaponautocannon.h @@ -0,0 +1,67 @@ +#ifndef __HH_WEAPON_AUTOCANNON_H +#define __HH_WEAPON_AUTOCANNON_H + +/*********************************************************************** + + hhWeaponAutoCannon + +***********************************************************************/ +class hhWeaponAutoCannon : public hhWeapon { + CLASS_PROTOTYPE( hhWeaponAutoCannon ); + + public: + enum { + EVENT_OVERHEAT = hhWeapon::EVENT_MAXEVENTS, + EVENT_MAXEVENTS + }; + + void Spawn(); + virtual ~hhWeaponAutoCannon(); + + virtual void ParseDef( const char* objectname ); + virtual void UpdateGUI(); + + virtual void Show(); + virtual void Hide(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + protected: + void Ticker(); + void AdjustHeat( const float amount ); + + void InitBoneInfo(); + void SpawnSpark(); + + void SetHeatLevel( const float heatLevel ); + float GetHeatLevel() const { return heatLevel; } + + virtual void PresentWeapon( bool showViewModel ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + protected: + void Event_SpawnRearGasFX(); + void Event_AssignLeftRearFx( hhEntityFx* fx ); + void Event_AssignRightRearFx( hhEntityFx* fx ); + void Event_AdjustHeat( const float amount ); + void Event_GetHeatLevel(); + void Event_SpawnSparkLocal( const char* defName ); + void Event_OverHeatNetEvent(); //rww + + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); //rww + + protected: + float heatLevel; + + float sparkGapSize; + idEntityPtr beamSystem; + weaponJointHandle_t sparkBoneL; + weaponJointHandle_t sparkBoneR; + + idEntityPtr rearGasFxL; + idEntityPtr rearGasFxR; +}; + +#endif \ No newline at end of file diff --git a/src/Prey/prey_weaponcrawlergrenade.cpp b/src/Prey/prey_weaponcrawlergrenade.cpp new file mode 100644 index 0000000..e85ed95 --- /dev/null +++ b/src/Prey/prey_weaponcrawlergrenade.cpp @@ -0,0 +1,51 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/*********************************************************************** + + hhWeaponCrawlerGrenade + +***********************************************************************/ +const idEventDef EV_SpawnBloodSpray( "spawnBloodSpray" ); + +CLASS_DECLARATION( hhWeapon, hhWeaponCrawlerGrenade ) + EVENT( EV_SpawnBloodSpray, hhWeaponCrawlerGrenade::Event_SpawnBloodSpray ) +END_CLASS + +/* +================= +hhWeaponCrawlerGrenade::WriteToSnapshot +================= +*/ +void hhWeaponCrawlerGrenade::WriteToSnapshot( idBitMsgDelta &msg ) const { + hhWeapon::WriteToSnapshot(msg); + msg.WriteBits(WEAPON_ALTMODE, 1); +} + +/* +================= +hhWeaponCrawlerGrenade::ReadFromSnapshot +================= +*/ +void hhWeaponCrawlerGrenade::ReadFromSnapshot( const idBitMsgDelta &msg ) { + hhWeapon::ReadFromSnapshot(msg); + WEAPON_ALTMODE = !!msg.ReadBits(1); +} + +/* +================= +hhWeaponCrawlerGrenade::Event_SpawnBloodSpray +================= +*/ +void hhWeaponCrawlerGrenade::Event_SpawnBloodSpray() { + hhFxInfo fxInfo; + fxInfo.UseWeaponDepthHack( true ); + if (WEAPON_ALTMODE) { + BroadcastFxInfoAlongBonePrefix( dict, "fx_blood", "joint_AltBloodFx", &fxInfo ); + } + else { + BroadcastFxInfoAlongBonePrefix( dict, "fx_blood", "joint_bloodFx", &fxInfo ); + } +} \ No newline at end of file diff --git a/src/Prey/prey_weaponcrawlergrenade.h b/src/Prey/prey_weaponcrawlergrenade.h new file mode 100644 index 0000000..025e50a --- /dev/null +++ b/src/Prey/prey_weaponcrawlergrenade.h @@ -0,0 +1,21 @@ +#ifndef __HH_WEAPON_CRAWLERGRENADE_H +#define __HH_WEAPON_CRAWLERGRENADE_H + +/*********************************************************************** + + hhWeaponCrawlerGrenade + +***********************************************************************/ +class hhWeaponCrawlerGrenade : public hhWeapon { + CLASS_PROTOTYPE( hhWeaponCrawlerGrenade ); + + public: + //rww - network friendliness + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + protected: + void Event_SpawnBloodSpray(); +}; + +#endif \ No newline at end of file diff --git a/src/Prey/prey_weaponfirecontroller.cpp b/src/Prey/prey_weaponfirecontroller.cpp new file mode 100644 index 0000000..17bdb22 --- /dev/null +++ b/src/Prey/prey_weaponfirecontroller.cpp @@ -0,0 +1,574 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION( hhFireController, hhWeaponFireController ) +END_CLASS + +/* +================ +hhWeaponFireController::Clear +================ +*/ +void hhWeaponFireController::Clear() { + hhFireController::Clear(); + + self = NULL; + owner = NULL; + + brassDef = NULL; + brassDelay = -1; + + ammoType = 0; + clipSize = 0; // 0 means no reload + ammoClip = 0; + lowAmmo = 0; + + // weapon kick + muzzle_kick_time = 0; + muzzle_kick_maxtime = 0; + muzzle_kick_angles.Zero(); + muzzle_kick_offset.Zero(); + + aimDist.Zero(); + + // joints from models + barrelJoints.Clear(); + ejectJoints.Clear(); +} + +/* +================ +hhWeaponFireController::Init +================ +*/ +void hhWeaponFireController::Init( const idDict* viewDict, hhWeapon* self, hhPlayer* owner ) { + const char *brassDefName = NULL; + + hhFireController::Init( viewDict ); + + this->self = self; + this->owner = owner; + + muzzleFlash.lightId = LIGHTID_VIEW_MUZZLE_FLASH + owner->entityNumber; + //muzzleFlash.allowLightInViewID = owner->entityNumber+1; + + if( !dict ) { + return; + } + + muzzle_kick_time = SEC2MS( dict->GetFloat( "muzzle_kick_time" ) ); + muzzle_kick_maxtime = SEC2MS( dict->GetFloat( "muzzle_kick_maxtime" ) ); + muzzle_kick_angles = dict->GetAngles( "muzzle_kick_angles" ); + muzzle_kick_offset = dict->GetVector( "muzzle_kick_offset" ); + + // find some joints in the model for locating effects + SetWeaponJointHandleList( "joint_barrel", barrelJoints ); + SetWeaponJointHandleList( "joint_eject", ejectJoints ); + + // get the brass def + SetBrassDict( dict->GetString("def_ejectBrass") ); + + ammoType = GetAmmoType( dict->GetString( "ammoType" ) ); + clipSize = dict->GetInt( "clipSize" ); + lowAmmo = dict->GetInt( "lowAmmo" ); + + if( ( ammoType < 0 ) || ( ammoType >= AMMO_NUMTYPES ) ) { + gameLocal.Warning( "Unknown ammotype in object '%s'", dict->GetString("classname") ); + } + + //HUMANHEAD bjk: set in hhWeapon::GetWeaponDef + ammoClip = 0; + /*if ( clipSize ) { + ammoClip = owner->HasAmmo( ammoType, ammoRequired ); + if ( ( ammoClip < 0 ) || ( ammoClip > clipSize ) ) { + // first time using this weapon so have it fully loaded to start + ammoClip = clipSize; + } + }*/ + + aimDist = dict->GetVec2( "aimDist", "20 50" ); + scriptFunction = dict->GetString( "script_function", "Fire" ); +} + +/* +================ +hhWeaponFireController::SetWeaponJointHandleList +================ +*/ +void hhWeaponFireController::SetWeaponJointHandleList( const char* keyPrefix, hhCycleList& jointList ) { + weaponJointHandle_t handle; + const idKeyValue* kv = dict->MatchPrefix( keyPrefix ); + while( kv ) { + self->GetJointHandle( kv->GetValue().c_str(), handle ); + jointList.Append( handle ); + kv = dict->MatchPrefix( keyPrefix, kv ); + } +} + +/* +================ +hhWeaponFireController::SaveWeaponJointHandleList +================ +*/ +void hhWeaponFireController::SaveWeaponJointHandleList( const hhCycleList& jointList, idSaveGame *savefile ) const { + const weaponJointHandle_t* handle = NULL; + + int num = jointList.Num(); + savefile->WriteInt( num ); + for( int ix = 0; ix < num; ++ix ) { + handle = &jointList[ix]; + if( handle ) { + savefile->WriteInt( handle->view ); + savefile->WriteInt( handle->world ); + } + } +} + +/* +================ +hhWeaponFireController::RestoreWeaponJointHandleList +================ +*/ +void hhWeaponFireController::RestoreWeaponJointHandleList( hhCycleList& jointList, idRestoreGame *savefile ) { + //weaponJointHandle_t* handle = NULL; + + jointList.Clear(); + + int num; + savefile->ReadInt( num ); + weaponJointHandle_t handle; + for( int ix = 0; ix < num; ++ix ) { + savefile->ReadInt( (int&)handle.view ); + savefile->ReadInt( (int&)handle.world ); + jointList.Append( handle ); + } +} + +/* +================ +hhWeaponFireController::SetBrassDict +================ +*/ +void hhWeaponFireController::SetBrassDict( const char* name ) { + brassDefName = name; + if ( name[0] ) { + brassDef = gameLocal.FindEntityDefDict( name, false ); + if ( !brassDef ) { + gameLocal.Warning( "Unknown brass '%s'", name ); + } + } else { + brassDef = NULL; + } +} + +/* +================ +hhWeaponFireController::LaunchProjectiles +================ +*/ +bool hhWeaponFireController::LaunchProjectiles( const idVec3& pushVelocity ) { + if( !hhFireController::LaunchProjectiles(pushVelocity) ) { + return false; + } + + if( !gameLocal.isClient ) { + owner->AddProjectilesFired( numProjectiles ); + } + + //HUMANHEAD bjk PATCH 7-27-06 + if( gameLocal.isMultiplayer ) { + owner->inventory.lastShot[owner->GetWeaponNum( self->spawnArgs.GetString( "classname" ) )] = gameLocal.time + 1000; + } + + return true; +} + +/* +================= +hhWeaponFireController::DetermineAimAxis +================= +*/ +idMat3 hhWeaponFireController::DetermineAimAxis( const idVec3& muzzlePos, const idMat3& weaponAxis ) { + float traceDist = 1024.0f; // was CM_MAX_TRACE_DIST + float aimTraceDist; + trace_t aimTrace = self->GetEyeTraceInfo(); + + //HUMANHEAD bjk + if( aimTrace.fraction < 1.0f ) { + idVec3 eyePos = owner->GetEyePosition(); + + // Perform eye trace + gameLocal.clip.TracePoint( aimTrace, eyePos, eyePos + weaponAxis[0] * traceDist * 4.0f, + MASK_SHOT_RENDERMODEL | CONTENTS_GAME_PORTAL, owner.GetEntity() ); + + if ( aimTrace.fraction < 1.0f ) { // CJR: Check for portals + idEntity *ent = gameLocal.entities[ aimTrace.c.entityNum ]; // cannot use GetTraceEntity() as the portal could be bound to something + if ( ent && ent->IsType( hhPortal::Type ) ) { + aimTrace.endpos = eyePos + weaponAxis[0] * traceDist; + aimTrace.fraction = 1.0f; + } + } + + aimTraceDist = aimTrace.fraction * traceDist * 4.0f; + } else { + aimTraceDist = aimTrace.fraction * traceDist; + } //HUMANHEAD END + + idVec3 aimVector( aimTrace.endpos - muzzlePos ); + idVec3 aimDir = aimVector; + + aimDir.Normalize(); + aimDir.Lerp( weaponAxis[0], aimDir, hhUtils::CalculateScale(aimTraceDist, aimDist[0], aimDist[1]) ); + + idAngles aimAngles( aimDir.ToAngles() ); + + aimAngles.roll = weaponAxis.ToAngles().roll; + return aimAngles.ToMat3(); +} + +/* +================ +hhWeaponFireController::WeaponFeedback +================ +*/ +void hhWeaponFireController::WeaponFeedback() { + if( owner.IsValid() ) { + owner->WeaponFireFeedback( dict ); + } +} + +/* +================ +hhWeaponFireController::EjectBrass +================ +*/ +void hhWeaponFireController::EjectBrass() { + idMat3 axis; + idVec3 origin; + idEntity* ent = NULL; + idDebris* brass = NULL; + + if( !g_showBrass.GetBool() || !brassDef ) { + return; + } + + if (gameLocal.isClient && !gameLocal.isNewFrame) { //rww + return; + } + + if( TransformBrass(ejectJoints.Next(), origin, axis) ) { + gameLocal.SpawnEntityDef( *brassDef, &ent, true, gameLocal.isClient ); //rww - localized debris + HH_ASSERT( ent && ent->IsType(idDebris::Type) ); + + brass = static_cast( ent ); + brass->Create( owner.GetEntity(), origin, axis ); + brass->Launch(); + brass->fl.networkSync = false; //rww + brass->fl.clientEvents = true; //rww + //HACK: this should be in debris object. Just not sure if I should re-write it or not. + brass->SetShaderParm( SHADERPARM_TIME_OF_DEATH, MS2SEC(gameLocal.GetTime()) ); + } +} + +/* +================= +hhWeaponFireController::TransformBrass +================= +*/ +bool hhWeaponFireController::TransformBrass( const weaponJointHandle_t& handle, idVec3 &origin, idMat3 &axis ) { + if( !self->GetJointWorldTransform(handle, origin, axis) ) { + return false; + } + + axis = self->GetAxis(); + + return true; +} + +/* +================ +hhWeaponFireController::CalculateMuzzleRise +================ +*/ +void hhWeaponFireController::CalculateMuzzleRise( idVec3& origin, idMat3& axis ) { + int time; + float amount; + idAngles ang; + idVec3 offset; + + time = self->GetKickEndTime() - gameLocal.GetTime(); + 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; +} + +/* +================ +hhWeaponFireController::UpdateMuzzleKick +================ +*/ +void hhWeaponFireController::UpdateMuzzleKick() { + // add some to the kick time, incrementally moving repeat firing weapons back + if ( self->GetKickEndTime() < gameLocal.GetTime() ) { + self->SetKickEndTime( gameLocal.GetTime() ); + } + self->SetKickEndTime( self->GetKickEndTime() + muzzle_kick_time ); + if ( self->GetKickEndTime() > gameLocal.GetTime() + muzzle_kick_maxtime ) { + self->SetKickEndTime( gameLocal.GetTime() + muzzle_kick_maxtime ); + } +} + +/* +================ +hhWeaponFireController::CalculateMuzzlePosition +================ +*/ +void hhWeaponFireController::CalculateMuzzlePosition( idVec3& origin, idMat3& axis ) { + if( !barrelJoints.Num() || !self->GetJointWorldTransform(barrelJoints.Next(), origin, axis) ) { + origin = GetMuzzlePosition(); + axis = self->GetAxis(); + } +} + +/* +================ +hhWeaponFireController::UseAmmo +================ +*/ +void hhWeaponFireController::UseAmmo() { + if( owner.IsValid() ) { + owner->UseAmmo( GetAmmoType(), AmmoRequired() ); + if ( ClipSize() && AmmoRequired() ) { + ammoClip--; + } + } +} + +/* +================ +hhWeaponFireController::AddToClip +================ +*/ +void hhWeaponFireController::AddToClip( int amount ) { + ammoClip += amount; + if ( ammoClip > clipSize ) { + ammoClip = clipSize; + } + + if ( ammoClip > AmmoAvailable() ) { + ammoClip = AmmoAvailable(); + } +} + +/* +================ +hhWeaponFireController::GetAmmoType +================ +*/ +ammo_t hhWeaponFireController::GetAmmoType( 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 ] ) { + ammoname = "ammo_none"; + } + + if ( !ammoDict->GetInt( ammoname, "-1", num ) ) { + gameLocal.Error( "Unknown ammo type '%s'", ammoname ); + } + + if ( ( num < 0 ) || ( num >= AMMO_NUMTYPES ) ) { + gameLocal.Error( "Ammo type '%s' value out of range. Maximum ammo types is %d.\n", ammoname, AMMO_NUMTYPES ); + } + + return ( ammo_t )num; +} + +/* +================ +hhWeaponFireController::Save +================ +*/ +void hhWeaponFireController::Save( idSaveGame *savefile ) const { + self.Save( savefile ); + owner.Save( savefile ); + + savefile->WriteString( brassDefName ); + savefile->WriteInt( brassDelay ); + savefile->WriteString( scriptFunction ); + + savefile->WriteInt( ammoType ); + savefile->WriteInt( clipSize ); + savefile->WriteInt( ammoClip ); + savefile->WriteInt( lowAmmo ); + + savefile->WriteInt( muzzle_kick_time ); + savefile->WriteInt( muzzle_kick_maxtime ); + savefile->WriteAngles( muzzle_kick_angles ); + savefile->WriteVec3( muzzle_kick_offset ); + + savefile->WriteVec2( aimDist ); + + SaveWeaponJointHandleList( barrelJoints, savefile ); + SaveWeaponJointHandleList( ejectJoints, savefile ); + +} + +/* +================ +hhWeaponFireController::Restore +================ +*/ +void hhWeaponFireController::Restore( idRestoreGame *savefile ) { + self.Restore( savefile ); + owner.Restore( savefile ); + savefile->ReadString( brassDefName ); + savefile->ReadInt( brassDelay ); + savefile->ReadString( scriptFunction ); + + savefile->ReadInt( reinterpret_cast ( ammoType ) ); + savefile->ReadInt( clipSize ); + savefile->ReadInt( ammoClip ); + savefile->ReadInt( lowAmmo ); + + savefile->ReadInt( muzzle_kick_time ); + savefile->ReadInt( muzzle_kick_maxtime ); + savefile->ReadAngles( muzzle_kick_angles ); + savefile->ReadVec3( muzzle_kick_offset ); + + savefile->ReadVec2( aimDist ); + + RestoreWeaponJointHandleList( barrelJoints, savefile ); + RestoreWeaponJointHandleList( ejectJoints, savefile ); + + SetBrassDict( brassDefName ); +} + +/* +================ +hhWeaponFireController::AmmoAvailable +================ +*/ +int hhWeaponFireController::AmmoAvailable() const { + if ( owner.IsValid() ) { + return owner->HasAmmo( GetAmmoType(), AmmoRequired() ); + } else { + return 0; + } +} + +/* +================ +hhWeaponFireController::GetProjectileOwner +================ +*/ +idEntity *hhWeaponFireController::GetProjectileOwner() const { + return owner.GetEntity(); +} + +/* +================ +hhWeaponFireController::GetCollisionBBox +================ +*/ +const idBounds& hhWeaponFireController::GetCollisionBBox() { + return owner->GetPhysics()->GetAbsBounds(); +} + +/* +================ +hhWeaponFireController::GetSelf +================ +*/ +hhRenderEntity *hhWeaponFireController::GetSelf() { + return self.GetEntity(); +} + +/* +================ +hhWeaponFireController::GetSelfConst +================ +*/ +const hhRenderEntity *hhWeaponFireController::GetSelfConst() const { + return self.GetEntity(); +} + +/* +================ +hhWeaponFireController::UsesCrosshair +================ +*/ +bool hhWeaponFireController::UsesCrosshair() const { + return (self.IsValid() && bCrosshair && self->ShowCrosshair()); +} + +//rww - net friendliness +void hhWeaponFireController::WriteToSnapshot( idBitMsgDelta &msg ) const +{ + msg.WriteBits(self.GetSpawnId(), 32); + msg.WriteBits(owner.GetSpawnId(), 32); + + msg.WriteBits(ammoClip, self->GetClipBits()); + + //RWWTODO: Added lowAmmo int in case it needs to be sent --paul +} + +void hhWeaponFireController::ReadFromSnapshot( const idBitMsgDelta &msg ) +{ + self.SetSpawnId(msg.ReadBits(32)); + owner.SetSpawnId(msg.ReadBits(32)); + + ammoClip = msg.ReadBits(self->GetClipBits()); +} + +/* +================ +hhWeaponFireController::UpdateWeaponJoints +================ +*/ +void hhWeaponFireController::UpdateWeaponJoints(void) { //rww + barrelJoints.Clear(); + ejectJoints.Clear(); + + SetWeaponJointHandleList( "joint_barrel", barrelJoints ); + SetWeaponJointHandleList( "joint_eject", ejectJoints ); +} + +/* +================ +hhWeaponFireController::CheckThirdPersonMuzzle +================ +*/ +bool hhWeaponFireController::CheckThirdPersonMuzzle(idVec3 &origin, idMat3 &axis) { //rww + if (barrelJoints.Num() <= 0) { + return false; + } + + int i = barrelJoints.GetCurrentIndex(); + return self->GetJointWorldTransform(barrelJoints[i], origin, axis, true); +} diff --git a/src/Prey/prey_weaponfirecontroller.h b/src/Prey/prey_weaponfirecontroller.h new file mode 100644 index 0000000..1fbc8ce --- /dev/null +++ b/src/Prey/prey_weaponfirecontroller.h @@ -0,0 +1,138 @@ +#ifndef __HH_WEAPON_FIRE_CONTROLLER_H +#define __HH_WEAPON_FIRE_CONTROLLER_H + +struct weaponJointHandle_t { + jointHandle_t view; + jointHandle_t world; + + weaponJointHandle_t() { Clear(); } + void Clear() { view = INVALID_JOINT; world = INVALID_JOINT; } +}; + +/*********************************************************************** + + hhWeaponFireController + + Base class that manages the firing projectiles. +***********************************************************************/ +class hhWeaponFireController : public hhFireController { + CLASS_PROTOTYPE(hhWeaponFireController) + +public: + virtual void Clear(); + virtual void Init( const idDict* viewDict, hhWeapon* self, hhPlayer* owner ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool LaunchProjectiles( const idVec3& pushVelocity ); + virtual idMat3 DetermineAimAxis( const idVec3& muzzlePos, const idMat3& weaponAxis ); + virtual void WeaponFeedback(); + + void CalculateMuzzleRise( idVec3& origin, idMat3& axis ); + void UpdateMuzzleKick(); + + virtual void EjectBrass(); + virtual bool TransformBrass( const weaponJointHandle_t& handle, idVec3& origin, idMat3& axis ); + idStr GetScriptFunction() { return scriptFunction; } + const char * GetString( const char *key ) { return dict->GetString( key ); } + + ID_INLINE virtual bool HasAmmo() const; + virtual void UseAmmo(); + virtual int AmmoAvailable() const; + void AddToClip( int amount ); + static ammo_t GetAmmoType( const char *ammoname ); + ID_INLINE ammo_t GetAmmoType() const; + ID_INLINE int AmmoInClip() const; + ID_INLINE int ClipSize() const; + virtual bool UsesCrosshair() const; + ID_INLINE int LowAmmo() const; + + //rww - net friendliness + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + + void UpdateWeaponJoints(void); //rww + + virtual bool CheckThirdPersonMuzzle(idVec3 &origin, idMat3 &axis); //rww +protected: + void SetWeaponJointHandleList( const char* keyPrefix, hhCycleList& jointList ); + void SaveWeaponJointHandleList( const hhCycleList& jointList, idSaveGame *savefile ) const; + void RestoreWeaponJointHandleList( hhCycleList& jointList, idRestoreGame *savefile ); + + void SetBrassDict( const char* name ); + + virtual void CalculateMuzzlePosition( idVec3& origin, idMat3& axis ); + virtual idEntity* GetProjectileOwner() const; + virtual const idBounds& GetCollisionBBox(); + + virtual hhRenderEntity *GetSelf(); + virtual const hhRenderEntity *GetSelfConst() const; + +protected: + idEntityPtr self; + idEntityPtr owner; + + idStr scriptFunction; + idStr brassDefName; // HUMANHEAD mdl: Added to save/load brassDef dict + const idDict * brassDef; + int brassDelay; + + ammo_t ammoType; + int clipSize; // 0 means no reload + int ammoClip; + int lowAmmo; + + // weapon kick + int muzzle_kick_time; + int muzzle_kick_maxtime; + idAngles muzzle_kick_angles; + idVec3 muzzle_kick_offset; + + idVec2 aimDist; + + // joints from models + hhCycleList barrelJoints; + hhCycleList ejectJoints; +}; + +/* +================ +hhWeaponFireController::HasAmmo +================ +*/ +ID_INLINE bool hhWeaponFireController::HasAmmo() const { + return hhFireController::HasAmmo() && ( (ClipSize() == 0) || (AmmoInClip() > 0) ); +} + +/* +================ +hhWeaponFireController::GetAmmoType +================ +*/ +ammo_t hhWeaponFireController::GetAmmoType() const { + return ammoType; +} + +/* +================ +hhWeaponFireController::AmmoInClip +================ +*/ +int hhWeaponFireController::AmmoInClip() const { + return ammoClip; +} + +/* +================ +hhWeaponFireController::ClipSize +================ +*/ +int hhWeaponFireController::ClipSize() const { + return clipSize; +} + +int hhWeaponFireController::LowAmmo() const { + return lowAmmo; +} + +#endif \ No newline at end of file diff --git a/src/Prey/prey_weaponhider.cpp b/src/Prey/prey_weaponhider.cpp new file mode 100644 index 0000000..a210879 --- /dev/null +++ b/src/Prey/prey_weaponhider.cpp @@ -0,0 +1,95 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +#define FIRE_PREDICTION_TOLERANCE 0.16f //rww - time lag to allow prediction to handle fire and reload times + +CLASS_DECLARATION( hhWeaponFireController, hhHiderWeaponAltFireController ) +END_CLASS + +//============================================================================= +// +// hhHiderWeaponAltFireController::GetProjectileDict +// +//============================================================================= + +const idDict* hhHiderWeaponAltFireController::GetProjectileDict() const +{ + switch( self->AmmoInClip() ) { + case 1: return gameLocal.FindEntityDefDict( dict->GetString("def_projectile1"), false ); + case 2: return gameLocal.FindEntityDefDict( dict->GetString("def_projectile2"), false ); + case 3: return gameLocal.FindEntityDefDict( dict->GetString("def_projectile3"), false ); + case 4: return gameLocal.FindEntityDefDict( dict->GetString("def_projectile4"), false ); + default: return gameLocal.FindEntityDefDict( dict->GetString("def_projectile"), false ); + } +} + +CLASS_DECLARATION( hhWeapon, hhWeaponHider ) +END_CLASS + +hhWeaponHider::hhWeaponHider() { + nextPredictionAttack = 0.0f; + lastPredictionAttack = 0.0f; + nextPredictionTimeSkip = 0; +} + +void hhWeaponHider::ClientPredictionThink( void ) { //rww + hhWeapon::ClientPredictionThink(); + if (!gameLocal.isNewFrame) { + return; + } + if (owner.IsValid()) { + if (fabsf(WEAPON_NEXTATTACK-nextPredictionAttack) > FIRE_PREDICTION_TOLERANCE) { //allow some margin of error for prediction + if (owner->entityNumber == gameLocal.localClientNum) { + if (nextPredictionTimeSkip < gameLocal.time) { + if (nextPredictionTimeSkip) { + WEAPON_NEXTATTACK = nextPredictionAttack; + nextPredictionTimeSkip = 0; + } + else { + nextPredictionTimeSkip = gameLocal.time + 300; + } + } + } + else { + if (nextPredictionTimeSkip < gameLocal.time) { //for non-local clients, sync up if we go out of range on every new prediction frame + WEAPON_NEXTATTACK = nextPredictionAttack; + } + else { + owner->forcePredictionButtons |= BUTTON_ATTACK; + } + } + } + else if (owner->entityNumber == gameLocal.localClientNum) { + nextPredictionTimeSkip = 0; + } + } +} + +void hhWeaponHider::WriteToSnapshot( idBitMsgDelta &msg ) const { //rww + hhWeapon::WriteToSnapshot(msg); + + msg.WriteFloat(WEAPON_NEXTATTACK); +} +void hhWeaponHider::ReadFromSnapshot( const idBitMsgDelta &msg ) { //rww + hhWeapon::ReadFromSnapshot(msg); + + nextPredictionAttack = msg.ReadFloat(); + + if (owner.IsValid()) { + if (owner->entityNumber != gameLocal.localClientNum) { + if (fabsf(WEAPON_NEXTATTACK-nextPredictionAttack) > FIRE_PREDICTION_TOLERANCE) { //ensure this client "fires" his next prediction frame + owner->forcePredictionButtons |= BUTTON_ATTACK; + if (lastPredictionAttack != nextPredictionAttack) { + nextPredictionTimeSkip = gameLocal.time + 300; //give a few snapshots to straighten out for the local client + } + } + else { + owner->forcePredictionButtons &= ~BUTTON_ATTACK; + } + } + } + + lastPredictionAttack = nextPredictionAttack; +} diff --git a/src/Prey/prey_weaponhider.h b/src/Prey/prey_weaponhider.h new file mode 100644 index 0000000..1c8fc8a --- /dev/null +++ b/src/Prey/prey_weaponhider.h @@ -0,0 +1,42 @@ +#ifndef __HH_WEAPON_HIDER_H +#define __HH_WEAPON_HIDER_H + + +//============================================================================= +// +// hhHiderWeaponAltFireController +// +//============================================================================= +class hhHiderWeaponAltFireController : public hhWeaponFireController { + CLASS_PROTOTYPE(hhHiderWeaponAltFireController); +public: + ID_INLINE int AmmoRequired() const; + virtual const idDict* GetProjectileDict() const; +}; + +ID_INLINE int hhHiderWeaponAltFireController::AmmoRequired() const { + return self->AmmoInClip(); +} + + +class hhWeaponHider : public hhWeapon { + CLASS_PROTOTYPE( hhWeaponHider ); + + public: + hhWeaponHider(); + float nextPredictionAttack; //rww - does not need to be saved/restored, used only in client code. + float lastPredictionAttack; //rww - does not need to be saved/restored, used only in client code. + int nextPredictionTimeSkip; //rww - does not need to be saved/restored, used only in client code. + + protected: + ID_INLINE virtual hhWeaponFireController* CreateAltFireController(); + virtual void ClientPredictionThink( void ); //rww + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; //rww + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); //rww +}; + +ID_INLINE hhWeaponFireController* hhWeaponHider::CreateAltFireController() { + return new hhHiderWeaponAltFireController; +} + +#endif \ No newline at end of file diff --git a/src/Prey/prey_weaponrifle.cpp b/src/Prey/prey_weaponrifle.cpp new file mode 100644 index 0000000..72e8026 --- /dev/null +++ b/src/Prey/prey_weaponrifle.cpp @@ -0,0 +1,466 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/*********************************************************************** + + hhWeaponZoomable + +***********************************************************************/ +const idEventDef EV_Weapon_ZoomIn( "zoomIn" ); +const idEventDef EV_Weapon_ZoomOut( "zoomOut" ); + +CLASS_DECLARATION( hhWeapon, hhWeaponZoomable ) + EVENT( EV_Weapon_ZoomIn, hhWeaponZoomable::Event_ZoomIn ) + EVENT( EV_Weapon_ZoomOut, hhWeaponZoomable::Event_ZoomOut ) +END_CLASS + +/* +================ +hhWeaponZoomable::ZoomIn +================ +*/ +void hhWeaponZoomable::ZoomIn() { + if( owner.IsValid() && dict ) { + owner->GetZoomFov().Init( gameLocal.GetTime(), SEC2MS(dict->GetFloat("zoomDuration")), owner->CalcFov(true), GetZoomFov() ); + } + + clientZoomTime = gameLocal.time + CLIENT_ZOOM_FUDGE; //rww + + bZoomed = true; +} + +/* +================ +hhWeaponZoomable::ZoomOut +================ +*/ +void hhWeaponZoomable::ZoomOut() { + if( owner.IsValid() && dict ) { + owner->GetZoomFov().Init( gameLocal.GetTime(), SEC2MS(dict->GetFloat("zoomDuration")), owner->CalcFov(true), g_fov.GetInteger() ); + } + + clientZoomTime = gameLocal.time + CLIENT_ZOOM_FUDGE; //rww + + bZoomed = false; +} + +/* +================ +hhWeaponZoomable::ZoomIn +================ +*/ +void hhWeaponZoomable::ZoomInStep() { + int maxFov = spawnArgs.GetInt( "zoomFovMax" ); + if ( zoomFov < maxFov ) { + zoomFov += spawnArgs.GetInt( "zoomFovStep" ); + StartSound( "snd_scope_click", SND_CHANNEL_ANY, 0, false, NULL ); + if ( zoomFov > maxFov ) { + zoomFov = maxFov; + } + ZoomIn(); + } + bZoomed = true; + owner->inventory.zoomFov = zoomFov; +} + +/* +================ +hhWeaponZoomable::ZoomOut +================ +*/ +void hhWeaponZoomable::ZoomOutStep() { + int minFov = spawnArgs.GetInt( "zoomFovMin" ); + if ( zoomFov > minFov ) { + zoomFov -= spawnArgs.GetInt( "zoomFovStep" ); + StartSound( "snd_scope_click", SND_CHANNEL_ANY, 0, false, NULL ); + if ( zoomFov < minFov ) { + zoomFov = minFov; + } + ZoomIn(); + } + bZoomed = true; + owner->inventory.zoomFov = zoomFov; +} + +/* +================ +hhWeaponZoomable::Event_ZoomIn +================ +*/ +void hhWeaponZoomable::Event_ZoomIn() { + ZoomIn(); +} + +/* +================ +hhWeaponZoomable::Event_ZoomOut +================ +*/ +void hhWeaponZoomable::Event_ZoomOut() { + ZoomOut(); +} + +/* +================ +hhWeaponZoomable::WriteToSnapshot +================ +*/ +void hhWeaponZoomable::WriteToSnapshot( idBitMsgDelta &msg ) const { + hhWeapon::WriteToSnapshot(msg); + + msg.WriteBits(renderEntity.suppressSurfaceInViewID, GENTITYNUM_BITS); //if suppressSurfaceInViewID were to equal the last ent+1 this would be bad + //but, since it should never be world/none, this should be fine. + msg.WriteBits(bZoomed, 1); + msg.WriteBits(WEAPON_ALTMODE, 1); + + msg.WriteBits(IsHidden(), 1); +} + +/* +================ +hhWeaponZoomable::ReadFromSnapshot +================ +*/ +void hhWeaponZoomable::ReadFromSnapshot( const idBitMsgDelta &msg ) { + hhWeapon::ReadFromSnapshot(msg); + + int suppressSurfInViewID = msg.ReadBits(GENTITYNUM_BITS); + bool zoomed = !!msg.ReadBits(1); + bool weaponAltMode = !!msg.ReadBits(1); + bool hidden = !!msg.ReadBits(1); + if (clientZoomTime < gameLocal.time) { + renderEntity.suppressSurfaceInViewID = suppressSurfInViewID; + bZoomed = zoomed; + WEAPON_ALTMODE = weaponAltMode; + if (hidden != IsHidden()) { + if (hidden) { + Hide(); + } + else { + Show(); + } + } + } +} + +/* +================ +hhWeaponZoomable::Save +================ +*/ +void hhWeaponZoomable::Save( idSaveGame *savefile ) const { + savefile->WriteBool( bZoomed ); +} + +/* +================ +hhWeaponZoomable::Restore +================ +*/ +void hhWeaponZoomable::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( bZoomed ); +} + +/*********************************************************************** + + hhWeaponRifle + +***********************************************************************/ +//This probably should be on entity but since no body else needs it I'll leave it here +const idEventDef EV_SuppressSurfaceInViewID( "", "d" ); + +CLASS_DECLARATION( hhWeaponZoomable, hhWeaponRifle ) + EVENT( EV_SuppressSurfaceInViewID, hhWeaponRifle::Event_SuppressSurfaceInViewID ) +END_CLASS + +/* +================ +hhWeaponRifle::Spawn +================ +*/ +void hhWeaponRifle::Spawn(void) { + zoomOverlayGui = NULL; + clientZoomTime = 0; //rww - no need to save/restore this + fl.clientEvents = true; //rww - for "prediction" +} + +/* +================ +hhWeaponRifle::~hhWeaponRifle +================ +*/ +hhWeaponRifle::~hhWeaponRifle(void) { + // Ensure that the scope view is disabled anytime the rifle goes away + ZoomOut(); + + zoomOverlayGui = NULL; + + SAFE_REMOVE( laserSight ); +} + +/* +================ +hhWeaponRifle::ParseDef +================ +*/ +void hhWeaponRifle::ParseDef( const char* objectname ) { + hhWeaponZoomable::ParseDef( objectname ); + + SAFE_REMOVE(laserSight); + + //rww - client-server-localized. + laserSight = static_cast( gameLocal.SpawnClientObject(dict->GetString("beam_laserSight"), NULL) ); + if ( laserSight.IsValid() ) { // cjr - Don't allow the laser show up in the player's zoomed view + assert(owner.IsValid()); + laserSight->GetRenderEntity()->suppressSurfaceInViewID = owner->entityNumber + 1; + laserSight->fl.neverDormant = true; + laserSight->snapshotOwner = this; //rww + } + + DeactivateLaserSight(); + + if( owner->inventory.zoomFov ) + zoomFov = owner->inventory.zoomFov; + WEAPON_ALTMODE = false; +} + +/* +================ +hhWeaponRifle::Ticker +================ +*/ +void hhWeaponRifle::Ticker() { + trace_t trace; + idVec3 start; + idMat3 axis; + + if( laserSight.IsValid() && laserSight->IsActive() ) { + GetJointWorldTransform( dict->GetString("joint_laserSight"), start, axis ); + gameLocal.clip.TracePoint( trace, start, start + GetAxis()[0] * CM_MAX_TRACE_DIST, MASK_VISIBILITY, owner.GetEntity() ); + laserSight->SetOrigin( start ); + laserSight->SetTargetLocation( trace.endpos ); + } + + hhWeaponZoomable::Ticker(); +} + +/* +================ +hhWeaponRifle::ZoomIn +================ +*/ +void hhWeaponRifle::ZoomIn() { + CancelEvents( &EV_SuppressSurfaceInViewID ); + + bool alreadyZoomed = bZoomed; + + hhWeaponZoomable::ZoomIn(); + + SetViewAnglesSensitivity( owner->GetZoomFov().GetEndValue() ); + + ProcessEvent( &EV_SuppressSurfaceInViewID, owner->entityNumber + 1 ); + RestoreGUI( "gui_zoomOverlay", &zoomOverlayGui ); + + if (zoomOverlayGui && !alreadyZoomed) { + zoomOverlayGui->HandleNamedEvent("zoomIn"); + + // Display Tips + if (!gameLocal.isMultiplayer && g_tips.GetBool()) { + gameLocal.SetTip(zoomOverlayGui, "_impulse15", "#str_41156", NULL, NULL, "tip1"); + gameLocal.SetTip(zoomOverlayGui, "_impulse14", "#str_41157", NULL, NULL, "tip2"); + zoomOverlayGui->HandleNamedEvent( "tipWindowUp" ); + } + } + + ActivateLaserSight(); + + if (owner.IsValid() && owner.GetEntity()) { + if (owner->entityNumber == gameLocal.localClientNum) { + renderSystem->SetScopeView( true ); // CJR: Enable special render scope view + } + owner->bScopeView = true; + } +} + +/* +================ +hhWeaponRifle::ZoomOut +================ +*/ +void hhWeaponRifle::ZoomOut() { + CancelEvents( &EV_SuppressSurfaceInViewID ); + + bool wasZoomed = bZoomed; + + hhWeaponZoomable::ZoomOut(); + + if (owner.IsValid() && owner.GetEntity()) { //rww - this seems to be possible when a player disconnects while zoomed i think + SetViewAnglesSensitivity( owner->GetZoomFov().GetEndValue() ); + } + + // Remove tips + if (zoomOverlayGui) { + zoomOverlayGui->HandleNamedEvent( "tipWindowDown" ); + } + + if ( dict ) { + PostEventMS( &EV_SuppressSurfaceInViewID, SEC2MS(dict->GetFloat("zoomDuration")) * 0.5f, 0 ); + } + zoomOverlayGui = NULL; + + if( wasZoomed ) { + SetShaderParm(7, -MS2SEC(gameLocal.time)); + } + + DeactivateLaserSight(); + + if (owner.IsValid() && owner.GetEntity()) { + if (owner->entityNumber == gameLocal.localClientNum) { + renderSystem->SetScopeView( false ); // CJR: Disable special render scope view + } + owner->bScopeView = false; + } +} + +/* +================ +hhWeaponRifle::ActivateLaserSight +================ +*/ +void hhWeaponRifle::ActivateLaserSight() { + if( laserSight.IsValid() ) { + laserSight->Activate( true ); + BecomeActive( TH_TICKER ); + } +} + +/* +================ +hhWeaponRifle::DeactivateLaserSight +================ +*/ +void hhWeaponRifle::DeactivateLaserSight() { + if( laserSight.IsValid() ) { + laserSight->Activate( false ); + BecomeInactive( TH_TICKER ); + } +} + +/* +================ +hhWeaponRifle::UpdateGUI +================ +*/ +void hhWeaponRifle::UpdateGUI() { + float parmValue = 0.0f; + idAngles angles; + + if( zoomOverlayGui ) {//This is for the sniper + float minFov = spawnArgs.GetFloat( "zoomFovMin" ); + float maxFov = spawnArgs.GetFloat( "zoomFovMax" ); + parmValue = idMath::ClampFloat(0.3f, 1.0f, 1.0f - 0.7f*(zoomFov - minFov)/(maxFov - minFov)); + zoomOverlayGui->SetStateFloat( "fov", parmValue ); + + angles = GetAxis().ToAngles(); + parmValue = angles.Normalize360().yaw; + zoomOverlayGui->SetStateFloat( "yaw", parmValue ); + zoomOverlayGui->StateChanged( gameLocal.time ); + zoomOverlayGui->Redraw( gameLocal.GetTime() ); + } +} + +/* +================ +hhWeaponRifle::Event_SuppressSurfaceInViewID +================ +*/ +void hhWeaponRifle::Event_SuppressSurfaceInViewID( const int id ) { + renderEntity.suppressSurfaceInViewID = id; +} + +/* +================ +hhWeaponRifle::Save +================ +*/ +void hhWeaponRifle::Save( idSaveGame *savefile ) const { + laserSight.Save( savefile ); + savefile->WriteUserInterface( zoomOverlayGui, false ); +} + +/* +================ +hhWeaponRifle::Restore +================ +*/ +void hhWeaponRifle::Restore( idRestoreGame *savefile ) { + laserSight.Restore( savefile ); + savefile->ReadUserInterface( zoomOverlayGui ); +} + +/* +================ +hhWeaponRifle::WriteToSnapshot +================ +*/ +void hhWeaponRifle::WriteToSnapshot( idBitMsgDelta &msg ) const { + hhWeaponZoomable::WriteToSnapshot(msg); + + bool laserSightActive = (laserSight.IsValid() && IsActive(TH_TICKER)); + msg.WriteBits(laserSightActive, 1); + + if (zoomOverlayGui) { + msg.WriteBits(1, 1); + } + else { + msg.WriteBits(0, 1); + } +} + +/* +================ +hhWeaponRifle::ReadFromSnapshot +================ +*/ +void hhWeaponRifle::ReadFromSnapshot( const idBitMsgDelta &msg ) { + hhWeaponZoomable::ReadFromSnapshot(msg); + + bool laserSightActive = !!msg.ReadBits(1); + if (laserSight.IsValid() && IsActive(TH_TICKER) != laserSightActive) { + if (laserSightActive) { + ActivateLaserSight(); + } + else { + DeactivateLaserSight(); + } + } + + bool guiActive = !!msg.ReadBits(1); + if (clientZoomTime < gameLocal.time) { + if ((!!zoomOverlayGui) != guiActive) { + if (guiActive) { + if (!zoomOverlayGui) { + RestoreGUI( "gui_zoomOverlay", &zoomOverlayGui ); + if (zoomOverlayGui) { + zoomOverlayGui->HandleNamedEvent("zoomIn"); + } + } + } + else { + if (zoomOverlayGui) { + zoomOverlayGui = NULL; + } + } + } + } +} + +CLASS_DECLARATION( hhWeaponFireController, hhSniperRifleFireController ) +END_CLASS + +void hhSniperRifleFireController::CalculateMuzzlePosition( idVec3& origin, idMat3& axis ) { + origin = owner->GetEyePosition(); + axis = self->GetAxis(); +} \ No newline at end of file diff --git a/src/Prey/prey_weaponrifle.h b/src/Prey/prey_weaponrifle.h new file mode 100644 index 0000000..ddb8bba --- /dev/null +++ b/src/Prey/prey_weaponrifle.h @@ -0,0 +1,91 @@ +#ifndef __HH_WEAPON_RIFLE_H +#define __HH_WEAPON_RIFLE_H + +#define CLIENT_ZOOM_FUDGE 100 + +/*********************************************************************** + + hhWeaponZoomable + +***********************************************************************/ +class hhWeaponZoomable : public hhWeapon { + CLASS_PROTOTYPE( hhWeaponZoomable ); + + public: + hhWeaponZoomable() : hhWeapon(), bZoomed( false ) { } + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - network friendliness + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + ID_INLINE bool IsZoomed() const { return bZoomed; } + virtual void ZoomInStep(); + virtual void ZoomOutStep(); + + void Event_ZoomIn(); + void Event_ZoomOut(); + protected: + virtual void ZoomIn(); + virtual void ZoomOut(); + protected: + bool bZoomed; + + public: + int clientZoomTime; //rww - a bit hacky, to prevent jittering in prediction +}; + +/*********************************************************************** + + hhWeaponRifle + +***********************************************************************/ +class hhWeaponRifle : public hhWeaponZoomable { + CLASS_PROTOTYPE( hhWeaponRifle ); + + public: + void Spawn(); + virtual ~hhWeaponRifle(); + + virtual void UpdateGUI(); + + virtual void Ticker(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - network friendliness + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + virtual void ZoomIn(); + virtual void ZoomOut(); + + protected: + virtual void ParseDef( const char* objectname ); + + virtual void ActivateLaserSight(); + virtual void DeactivateLaserSight(); + + protected: + ID_INLINE virtual hhWeaponFireController* CreateAltFireController(); + void Event_SuppressSurfaceInViewID( const int id ); + + protected: + idUserInterface* zoomOverlayGui; + idEntityPtr laserSight; +}; + +class hhSniperRifleFireController : public hhWeaponFireController { + CLASS_PROTOTYPE(hhSniperRifleFireController); +public: + void CalculateMuzzlePosition( idVec3& origin, idMat3& axis ); +}; + +ID_INLINE hhWeaponFireController* hhWeaponRifle::CreateAltFireController() { + return new hhSniperRifleFireController; +} + +#endif \ No newline at end of file diff --git a/src/Prey/prey_weaponrocketlauncher.cpp b/src/Prey/prey_weaponrocketlauncher.cpp new file mode 100644 index 0000000..5eb2e0f --- /dev/null +++ b/src/Prey/prey_weaponrocketlauncher.cpp @@ -0,0 +1,29 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/*********************************************************************** + + hhWeaponRocketLauncherFireController + +***********************************************************************/ +CLASS_DECLARATION( hhWeaponFireController, hhRocketLauncherFireController ) +END_CLASS + +/*********************************************************************** + + hhWeaponRocketLauncherAltFireController + +***********************************************************************/ +CLASS_DECLARATION( hhRocketLauncherFireController, hhRocketLauncherAltFireController ) +END_CLASS + +/*********************************************************************** + + hhWeaponRocketLauncher + +***********************************************************************/ +CLASS_DECLARATION( hhWeapon, hhWeaponRocketLauncher ) +END_CLASS + diff --git a/src/Prey/prey_weaponrocketlauncher.h b/src/Prey/prey_weaponrocketlauncher.h new file mode 100644 index 0000000..f4b2cb7 --- /dev/null +++ b/src/Prey/prey_weaponrocketlauncher.h @@ -0,0 +1,97 @@ +#ifndef __HH_WEAPON_ROCKETLAUNCHER_H +#define __HH_WEAPON_ROCKETLAUNCHER_H + +/*********************************************************************** + + hhRocketLauncherFireController + +***********************************************************************/ +class hhRocketLauncherFireController : public hhWeaponFireController { + CLASS_PROTOTYPE(hhRocketLauncherFireController) + + public: + ID_INLINE virtual void EjectBrass(); + + protected: + ID_INLINE virtual bool TransformBrass( const weaponJointHandle_t& handle, idVec3 &origin, idMat3 &axis ); +}; + +/* +================ +hhRocketLauncherFireController::EjectBrass +================ +*/ +ID_INLINE void hhRocketLauncherFireController::EjectBrass() { + for( int ix = ejectJoints.Num() - 1; ix >= 0; --ix ) { + hhWeaponFireController::EjectBrass(); + } +} + +/* +================ +hhRocketLauncherFireController::TransformBrass +================ +*/ +ID_INLINE bool hhRocketLauncherFireController::TransformBrass( const weaponJointHandle_t& handle, idVec3 &origin, idMat3 &axis ) { + return self->GetJointWorldTransform( handle, origin, axis ); +} + +/*********************************************************************** + + hhRocketLauncherAltFireController + +***********************************************************************/ +class hhRocketLauncherAltFireController : public hhRocketLauncherFireController { + CLASS_PROTOTYPE(hhRocketLauncherAltFireController) + + protected: + ID_INLINE virtual idMat3 DetermineProjectileAxis( const idMat3& muzzleAxis ); +}; + +/* +================ +hhRocketLauncherAltFireController::DetermineProjectileAxis +================ +*/ +ID_INLINE idMat3 hhRocketLauncherAltFireController::DetermineProjectileAxis( const idMat3& muzzleAxis ) { + float ang = spread * hhMath::Sqrt( gameLocal.random.RandomFloat() ); + float yaw = DEG2RAD(yawSpread); + float spin = hhMath::TWO_PI * gameLocal.random.RandomFloat(); + idVec3 dir = muzzleAxis[ 0 ] + muzzleAxis[ 2 ] * ( hhMath::Sin(ang) * hhMath::Sin(spin) ) - muzzleAxis[ 1 ] * ( hhMath::Sin(ang+yaw) * hhMath::Cos(spin) ); + dir.Normalize(); + + return dir.ToMat3(); +} + +/*********************************************************************** + + hhWeaponRocketLauncher + +***********************************************************************/ +class hhWeaponRocketLauncher : public hhWeapon { + CLASS_PROTOTYPE( hhWeaponRocketLauncher ); + + protected: + ID_INLINE virtual hhWeaponFireController* CreateFireController(); + ID_INLINE virtual hhWeaponFireController* CreateAltFireController(); +}; + +/* +================ +hhWeaponRocketLauncher::CreateFireController +================ +*/ +ID_INLINE hhWeaponFireController* hhWeaponRocketLauncher::CreateFireController() { + return new hhRocketLauncherFireController; +} + +/* +================ +hhWeaponRocketLauncher::CreateAltFireController +================ +*/ +ID_INLINE hhWeaponFireController* hhWeaponRocketLauncher::CreateAltFireController() { + return new hhRocketLauncherAltFireController; +} + +#endif \ No newline at end of file diff --git a/src/Prey/prey_weaponsoulstripper.cpp b/src/Prey/prey_weaponsoulstripper.cpp new file mode 100644 index 0000000..a54c5d0 --- /dev/null +++ b/src/Prey/prey_weaponsoulstripper.cpp @@ -0,0 +1,1427 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_KillBeam( "killBeam", NULL, NULL ); + +CLASS_DECLARATION( hhWeaponFireController, hhSoulStripperAltFireController ) +END_CLASS + +/*********************************************************************** + + hhSoulStripperAltFireController::LaunchProjectiles + +***********************************************************************/ +void hhSoulStripperAltFireController::LaunchProjectiles( const idVec3& launchOrigin, const idMat3& aimAxis, const idVec3& pushVelocity, idEntity* projOwner ) { + // Spawn minion + idDict args; + + idVec3 minionOffset = dict->GetVector( "minionOffset" ); + args.SetVector( "origin", launchOrigin + aimAxis * minionOffset ); + args.SetMatrix( "rotation", aimAxis ); + const char *minionDef = dict->GetString( "def_minion" ); + gameLocal.SpawnObject( minionDef, &args ); + + // Spawn blast effect + hhFxInfo fxInfo; + fxInfo.SetNormal( aimAxis[0] ); + fxInfo.RemoveWhenDone( true ); + self->BroadcastFxInfo( dict->GetString( "fx_blastflash" ), launchOrigin, aimAxis, &fxInfo ); +} + +CLASS_DECLARATION( hhWeaponFireController, hhBeamBasedFireController ) +END_CLASS + +/*********************************************************************** + + hhBeamBasedFireController::LaunchProjectiles + +***********************************************************************/ +void hhBeamBasedFireController::LaunchProjectiles( const idVec3& launchOrigin, const idMat3& aimAxis, const idVec3& pushVelocity, idEntity* projOwner ) { + idVec3 start, end; + trace_t results; + + float traceLength; + + idVec3 boneOrigin; + idMat3 boneAxis; + + if ( projTime < gameLocal.time ) { + hhFireController::LaunchProjectiles(launchOrigin, aimAxis, pushVelocity, projOwner); + projTime = gameLocal.time + dict->GetInt("projRate", "100"); + } + + start = launchOrigin; + + traceLength = dict->GetFloat("traceLength","100"); + + end = start + traceLength * aimAxis[0]; + + // Check to see if the beam should attempt to hit something + if (owner.IsValid()) { + //rww - can hit owners due to different angles in mp (although not the local client since the trace is done on the client every frame) + gameLocal.clip.TracePoint( results, start, end, MASK_SHOT_RENDERMODEL | CONTENTS_GAME_PORTAL, owner.GetEntity() ); + } + else { + gameLocal.clip.TracePoint( results, start, end, MASK_SHOT_RENDERMODEL | CONTENTS_GAME_PORTAL, NULL ); + } + + if ( !shotbeam.IsValid() ) { + shotbeam = hhBeamSystem::SpawnBeam( launchOrigin, dict->GetString( "beam" ), aimAxis, true ); + if( !shotbeam.IsValid() ) + return; + + if (self->GetOwner()) { + shotbeam->snapshotOwner = self->GetOwner(); //rww - this beam is client-local, but will remove itself when the gun is not actively in the snapshot (meaning, it's not actively updated) + } + } + + shotbeam->Activate( true ); + + hhFxInfo fxInfo; + fxInfo.SetNormal( results.c.normal ); + fxInfo.RemoveWhenDone( true ); + + if( !impactFx.IsValid() ) { + const char* str=dict->GetString("fx_impact"); + if ( str && str[0] ) + impactFx=self.GetEntity()->SpawnFxLocal( str, results.endpos, results.c.normal.ToMat3(), &fxInfo, true ); + } + + if( impactFx.IsValid() ) { + if (self.IsValid() && self->GetOwner()) { + impactFx->snapshotOwner = self->GetOwner(); //rww + } + static_cast( impactFx.GetEntity() )->SetFxInfo(fxInfo); + impactFx->Nozzle(true); + impactFx->SetOrigin(results.endpos-aimAxis[0]*20); + } + + idVec3 beamOrigin; + idMat3 beamAxis; + CalculateMuzzlePosition( beamOrigin, beamAxis ); // Must use this instead of launchOrigin, since launch origin is forced into the player's bbox + + if (gameLocal.isMultiplayer) { //rww - the visual origin of the beam needs to be from the player's world weapon joint to avoid the optic blast + idVec3 tpOrigin; + idMat3 tpAxis; + if (CheckThirdPersonMuzzle(tpOrigin, tpAxis)) { + beamOrigin = tpOrigin; + //just keep the existing axis + } + } + shotbeam->SetOrigin( beamOrigin ); + shotbeam->SetAxis( aimAxis ); + shotbeam->SetTargetLocation( results.endpos-aimAxis[0]*40 ); + float length = (results.endpos - start).Length(); + shotbeam->SetBeamOffsetScale( length/150.0f ); + + GetSelf()->CancelEvents( &EV_KillBeam ); + GetSelf()->PostEventSec( &EV_KillBeam, dict->GetFloat( "beamTime", "0.1" ) ); +} + +void hhBeamBasedFireController::KillBeam() +{ + if( impactFx.IsValid() ) { + impactFx->Nozzle(false); + if (gameLocal.isClient) { + impactFx->snapshotOwner = NULL; //rww - prevent it from being unhidden between now and the removal frame on the client + } + } + + if( shotbeam.IsValid() ) + shotbeam->Activate( false ); +} + +void hhBeamBasedFireController::Think() +{ + if( shotbeam.IsValid() ) { + idVec3 beamOrigin; + idMat3 beamAxis; + CalculateMuzzlePosition( beamOrigin, beamAxis ); // Must use this instead of launchOrigin, since launch origin is forced into the player's bbox + idVec3 adjustedOrigin = AssureInsideCollisionBBox( beamOrigin, GetSelf()->GetAxis(), GetCollisionBBox(), projectileMaxHalfDimension ); + idMat3 aimAxis = DetermineAimAxis( adjustedOrigin, GetSelf()->GetAxis() ); + + if (gameLocal.isMultiplayer) { //rww - the visual origin of the beam needs to be from the player's world weapon joint to avoid the optic blast + idVec3 tpOrigin; + idMat3 tpAxis; + if (CheckThirdPersonMuzzle(tpOrigin, tpAxis)) { + beamOrigin = tpOrigin; + //just keep the existing axis + } + } + shotbeam->SetOrigin( beamOrigin ); + shotbeam->SetAxis( aimAxis ); + } +} + +/*********************************************************************** + + hhBeamBasedFireController::Init + +***********************************************************************/ +void hhBeamBasedFireController::Init( const idDict* viewDict, hhWeapon* self, hhPlayer* owner ) +{ + hhWeaponFireController::Init( viewDict, self, owner ); + + shotbeam = NULL; + projTime = 0; +} + +/*********************************************************************** + + hhBeamBasedFireController::~hhBeamBasedFireController + +***********************************************************************/ +hhBeamBasedFireController::~hhBeamBasedFireController() +{ + KillBeam(); + if( impactFx.IsValid() ) + SAFE_REMOVE(impactFx); + if( shotbeam.IsValid() ) + SAFE_REMOVE(shotbeam); +} + +void hhBeamBasedFireController::Save( idSaveGame *savefile ) const { + shotbeam.Save( savefile ); + savefile->WriteInt( projTime ); + impactFx.Save( savefile ); +} + +void hhBeamBasedFireController::Restore( idRestoreGame *savefile ) { + shotbeam.Restore( savefile ); + savefile->ReadInt( projTime ); + impactFx.Restore( savefile ); +} + +CLASS_DECLARATION( hhBeamBasedFireController, hhSunbeamFireController ) +END_CLASS + +void hhSunbeamFireController::LaunchProjectiles( const idVec3& launchOrigin, const idMat3& aimAxis, const idVec3& pushVelocity, idEntity* projOwner ) { + hhBeamBasedFireController::LaunchProjectiles(launchOrigin, aimAxis, pushVelocity, projOwner); + //HUMANHEAD PCF rww 05/18/06 - since values are assuming 60hz, make them relative + float backpush = dict->GetFloat("backpush","0.0")*(60.0f/(float)USERCMD_HZ); + float slowwalk = dict->GetFloat("slowwalk","0.0")*(60.0f/(float)USERCMD_HZ); + int knockback = dict->GetInt("knockback","0")*(60/USERCMD_HZ); + //HUMANHEAD END + + hhPhysics_Player* pp = static_cast(owner->GetPlayerPhysics()); + idVec3 v = pp->GetLinearVelocity()+owner->GetAxis()[0]*backpush; + float f = pp->GetLinearVelocity()*owner->GetAxis()[0]; + v += owner->GetAxis()[0] * ( (f>0) ? slowwalk*f : 0 ); + pp->SetLinearVelocity(v); + pp->SetKnockBack(knockback); +} + +void hhSunbeamFireController::Think() +{ + idVec3 start, end; + + if( shotbeam.IsValid() ) { + start = shotbeam->GetOrigin(); + end = shotbeam->GetTargetLocation(); + float length = (end - start).Length(); + + idVec3 beamOrigin; + idMat3 beamAxis; + CalculateMuzzlePosition( beamOrigin, beamAxis ); // Must use this instead of launchOrigin, since launch origin is forced into the player's bbox + idVec3 adjustedOrigin = AssureInsideCollisionBBox( beamOrigin, GetSelf()->GetAxis(), GetCollisionBBox(), projectileMaxHalfDimension ); + idMat3 aimAxis = DetermineAimAxis( adjustedOrigin, GetSelf()->GetAxis() ); + end = beamOrigin + length * aimAxis[0]; + + if (gameLocal.isMultiplayer) { //rww - the visual origin of the beam needs to be from the player's world weapon joint to avoid the optic blast + idVec3 tpOrigin; + idMat3 tpAxis; + if (CheckThirdPersonMuzzle(tpOrigin, tpAxis)) { + beamOrigin = tpOrigin; + //just keep the existing axis + } + } + shotbeam->SetOrigin( beamOrigin ); + shotbeam->SetAxis( aimAxis ); + shotbeam->SetTargetLocation( end ); + + if( impactFx.IsValid() ) + impactFx->SetOrigin( end ); + } +} + +CLASS_DECLARATION( hhWeaponFireController, hhPlasmaFireController ) +END_CLASS + +//============================================================================= +// +// hhPlasmaFireController::LaunchProjectiles +// +//============================================================================= +bool hhPlasmaFireController::LaunchProjectiles( const idVec3& pushVelocity ) { + char* projStr = "projectile_plasma"; + + fireDelay = dict->GetFloat( "fireRate" ); + spread = DEG2RAD( dict->GetFloat("spread") ); + yawSpread = dict->GetFloat("yawSpread"); + + usercmd_t cmd; + idAngles temp; + owner->GetPilotInput(cmd,temp); + + idVec3 dir(cmd.forwardmove, -cmd.rightmove, cmd.upmove); + + if( dir.x > 0 ) { + projStr = "projectile_plasmaf"; + fireDelay = 0.1f; + } + + if( dir.x < 0 ) { + spread = DEG2RAD(10); + fireDelay = 0.08f; + projStr = "projectile_plasmab"; + } + + if( dir.y != 0 ) { + spread *= 0.4f; + yawSpread +=8; + } + + if( dir.z < 0 ) { //crouch + spread = DEG2RAD(0); + fireDelay = 1.0f; + projStr = "projectile_plasmac"; + } + + if( !owner->AI_ONGROUND ) { //jump + fireDelay = 0.06f; + projStr = "projectile_plasmaj"; + } + + projectile = gameLocal.FindEntityDefDict( projStr, false ); + + return hhWeaponFireController::LaunchProjectiles( pushVelocity ); +} + +/*********************************************************************** + + hhWeaponSoulStripper + +***********************************************************************/ + +//const idEventDef EV_DamageBeamTarget( "", NULL ); +const idEventDef EV_GetAnimPostFix( "getAnimPostfix", "", 's' ); +const idEventDef EV_Leech( "leech", NULL, 'd' ); +const idEventDef EV_EndLeech( "endLeech", NULL, 'f' ); +const idEventDef EV_LightFadeIn( "", "f" ); +const idEventDef EV_LightFadeOut( "", "f" ); +const idEventDef EV_LightFade( "lightFade", "fff" ); +const idEventDef EV_GetFireFunction( "getFireFunction", NULL, 's' ); + +CLASS_DECLARATION( hhWeapon, hhWeaponSoulStripper ) + EVENT( EV_PostSpawn, hhWeaponSoulStripper::Event_PostSpawn ) + EVENT( EV_GetAnimPostFix, hhWeaponSoulStripper::Event_GetAnimPostFix ) + EVENT( EV_Leech, hhWeaponSoulStripper::Event_Leech ) + EVENT( EV_EndLeech, hhWeaponSoulStripper::Event_EndLeech ) + EVENT( AI_PlayAnim, hhWeaponSoulStripper::Event_PlayAnim ) + EVENT( AI_PlayCycle, hhWeaponSoulStripper::Event_PlayCycle ) + EVENT( EV_LightFadeIn, hhWeaponSoulStripper::Event_LightFadeIn ) + EVENT( EV_LightFadeOut, hhWeaponSoulStripper::Event_LightFadeOut ) + EVENT( EV_LightFade, hhWeaponSoulStripper::Event_LightFade ) + EVENT( EV_GetFireFunction, hhWeaponSoulStripper::Event_GetFireFunction ) + EVENT( EV_KillBeam, hhWeaponSoulStripper::Event_KillBeam ) +END_CLASS + +//============================================================================= +// +// hhWeaponSoulStripper::hhWeaponSoulStripper +// +//============================================================================= + +hhWeaponSoulStripper::hhWeaponSoulStripper() { + // Initialize SFX on the weapon + beam = NULL; // Beam is properly spawned in PostSpawn + beamCanA1 = NULL; // Beam is properly spawned in PostSpawn + beamCanB1 = NULL; // Beam is properly spawned in PostSpawn + beamCanC1 = NULL; // Beam is properly spawned in PostSpawn + beamCanA2 = NULL; // Beam is properly spawned in PostSpawn + beamCanB2 = NULL; // Beam is properly spawned in PostSpawn + beamCanC2 = NULL; // Beam is properly spawned in PostSpawn + beamCanA3 = NULL; // Beam is properly spawned in PostSpawn + beamCanB3 = NULL; // Beam is properly spawned in PostSpawn + beamCanC3 = NULL; // Beam is properly spawned in PostSpawn + cansValid = false; + + fxCanA = NULL; + fxCanB = NULL; + fxCanC = NULL; + + targetTime = 0; + + fcDeclNum = 0; + + netInitialized = false; + //HUMANHEAD PCF mdl 05/04/06 - Initialize beamLightHandle here + beamLightHandle = -1; +} + +//============================================================================= +// +// hhWeaponSoulStripper::Spawn +// +//============================================================================= + +void hhWeaponSoulStripper::Spawn() { + BecomeActive( TH_TICKER ); + fl.clientEvents = true; //rww +} + +//============================================================================= +// +// hhWeaponSoulStripper::ParseDef +// +//============================================================================= + +void hhWeaponSoulStripper::ParseDef( const char *objectname ) { + hhWeapon::ParseDef( objectname ); + + maxBeamLength = dict->GetFloat( "maxBeamLength", "1024" ); + + // Initialize weapon logic + targetNode = NULL; + beamLength = 0.0f; + + if (gameLocal.isMultiplayer) + { //rww - then do postspawn now. + Event_PostSpawn(); + } + else + { // Spawn the beams and SFX + PostEventMS( &EV_PostSpawn, 0 ); + } +} + +//============================================================================= +// +// hhWeaponSoulStripper::Event_PostSpawn +// +//============================================================================= + +void hhWeaponSoulStripper::Event_PostSpawn() { //rww - note that this function can get called more than once in a leechgun's life time in mp on the client. + idVec3 boneOrigin; + idMat3 boneAxis; + + GetJointWorldTransform( dict->GetString("attach_beam"), boneOrigin, boneAxis); + + if (!beam.IsValid()) { + beam = hhBeamSystem::SpawnBeam( boneOrigin, dict->GetString( "beam_stripper" ), boneAxis, true ); + if ( beam.IsValid() ) { + beam->Activate( false ); + } + } + DestroyCans(); + SpawnCans(); + + // Spawn beam light + memset( &beamLight, 0, sizeof( beamLight ) ); + beamLight.lightId = LIGHTID_VIEW_MUZZLE_FLASH + owner->entityNumber; + + beamLight.origin = GetOrigin(); + beamLight.axis = GetAxis(); + beamLight.pointLight = true; + beamLight.shader = declManager->FindMaterial( dict->GetString( "mtr_light" ) ); + beamLight.shaderParms[ SHADERPARM_RED ] = 1.0f; + beamLight.shaderParms[ SHADERPARM_GREEN ] = 1.0f; + beamLight.shaderParms[ SHADERPARM_BLUE ] = 1.0f; + beamLight.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; + + beamLight.lightRadius[0] = 115; + beamLight.lightRadius[1] = 115; + beamLight.lightRadius[2] = 1; + + //HUMANHEAD PCF mdl 05/04/06 - Moved this to the constructor + //beamLightHandle = -1; + + const idDict *energyDef; + const char* str = owner->inventory.energyType; + if ( !str || !str[0] ) + return; + + energyDef = gameLocal.FindEntityDefDict( str ); + if ( !energyDef ) + return; + + if (gameLocal.isClient) { //rww - do the minimal fireController update for the client here. + const idDeclEntityDef *fcDecl = gameLocal.FindEntityDef( energyDef->GetString("def_fireInfo"), false ); + if (fcDecl) { + const char *fcSpawnClass = fcDecl->dict.GetString("spawnclass", NULL); + if (!fcSpawnClass || !fcSpawnClass[0]) { + fcSpawnClass = "hhWeaponFireController"; //default to regular fire controller + } + idTypeInfo *cls = idClass::GetClass(fcSpawnClass); + if (cls) { + ClientUpdateFC(cls->typeNum, fcDecl->Index()); + } + } + } + else { + GiveEnergy( energyDef->GetString("def_fireInfo"), false ); + } +} + +void hhWeaponSoulStripper::CheckCans(void) { + bool wantCans = true; + if (owner.IsValid() && owner->entityNumber != gameLocal.localClientNum) { //rww - we don't need cannister fx unless we are the client that owns this weapon. (or spectating him) + if ( gameLocal.localClientNum >= 0 && gameLocal.entities[ gameLocal.localClientNum ] && gameLocal.entities[ gameLocal.localClientNum ]->IsType( idPlayer::Type ) ) { + idPlayer *p = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.localClientNum ] ); + if ( !p->spectating || p->spectator != owner->entityNumber ) { + wantCans = false; + } + } else { + wantCans = false; + } + } + + if (wantCans != cansValid) { + DestroyCans(); + if (wantCans) { + SpawnCans(); + } + } +} + +void hhWeaponSoulStripper::SpawnCans() { + if (owner.IsValid() && owner->entityNumber != gameLocal.localClientNum) { //rww - we don't need cannister fx unless we are the client that owns this weapon. (or spectating him) + if ( gameLocal.localClientNum >= 0 && gameLocal.entities[ gameLocal.localClientNum ] && gameLocal.entities[ gameLocal.localClientNum ]->IsType( idPlayer::Type ) ) { + idPlayer *p = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.localClientNum ] ); + if ( !p->spectating || p->spectator != owner->entityNumber ) { + return; + } + } else { + return; + } + } + + // Spawn individual canister beams (top to center) + beamCanA1 = SpawnCanisterBeam( "attach_topA", "attach_soulA", beam_canTop ); + beamCanB1 = SpawnCanisterBeam( "attach_topB", "attach_soulB", beam_canTop ); + beamCanC1 = SpawnCanisterBeam( "attach_topC", "attach_soulC", beam_canTop ); + + // Spawn individual canister beams (bottom to center) + beamCanA2 = SpawnCanisterBeam( "attach_bottomA", "attach_soulA", beam_canBot ); + beamCanB2 = SpawnCanisterBeam( "attach_bottomB", "attach_soulB", beam_canBot ); + beamCanC2 = SpawnCanisterBeam( "attach_bottomC", "attach_soulC", beam_canBot ); + + // Spawn individual canister beams (faint bottom to top glow) + beamCanA3 = SpawnCanisterBeam( "attach_bottomA", "attach_topA", beam_canGlow ); + beamCanB3 = SpawnCanisterBeam( "attach_bottomB", "attach_topB", beam_canGlow ); + beamCanC3 = SpawnCanisterBeam( "attach_bottomC", "attach_topC", beam_canGlow ); + + fxCanA = SpawnCanisterFx( "attach_bottomA", fx_can ); + fxCanB = SpawnCanisterFx( "attach_bottomB", fx_can ); + fxCanC = SpawnCanisterFx( "attach_bottomC", fx_can ); + + cansValid = true; +} + +void hhWeaponSoulStripper::DestroyCans() { + SAFE_REMOVE( beamCanA1 ); + SAFE_REMOVE( beamCanB1 ); + SAFE_REMOVE( beamCanC1 ); + + SAFE_REMOVE( beamCanA2 ); + SAFE_REMOVE( beamCanB2 ); + SAFE_REMOVE( beamCanC2 ); + + SAFE_REMOVE( beamCanA3 ); + SAFE_REMOVE( beamCanB3 ); + SAFE_REMOVE( beamCanC3 ); + + SAFE_REMOVE( fxCanA ); + SAFE_REMOVE( fxCanB ); + SAFE_REMOVE( fxCanC ); + + cansValid = false; +} + +//============================================================================= +// +// hhWeaponSoulStripper::SpawnCanisterBeam +// +//============================================================================= + +hhBeamSystem *hhWeaponSoulStripper::SpawnCanisterBeam( const char *bottom, const char *top, const idStr &beamName ) { + hhBeamSystem *system = NULL; + idVec3 boneOrigin; + idMat3 boneAxis; + + if( beamName.IsEmpty() ) + return NULL; + + GetJointWorldTransform( dict->GetString(bottom), boneOrigin, boneAxis); + + system = hhBeamSystem::SpawnBeam( boneOrigin, beamName, boneAxis, true ); + if ( !system ) { + return NULL; + } + + if (owner.IsValid()) { + system->snapshotOwner = owner.GetEntity(); //rww + } + + GetJointWorldTransform( dict->GetString(top), boneOrigin, boneAxis); + + system->SetTargetLocation( boneOrigin ); + system->GetRenderEntity()->weaponDepthHack = true; + system->GetRenderEntity()->allowSurfaceInViewID = owner->entityNumber+1; + system->fl.neverDormant = true; + + return system; +} + +//============================================================================= +// +// hhWeaponSoulStripper::SpawnCanisterSprite +// +//============================================================================= + +idEntity *hhWeaponSoulStripper::SpawnCanisterSprite( const char *attach, const char *spriteName ) { + idEntity *ent; + const char *jointName; + + assert(gameLocal.isMultiplayer); //rww - i don't see this being used in mp, but if it is, it needs to be reworked. + + jointName = dict->GetString( attach ); + ent = gameLocal.SpawnObject( dict->GetString( spriteName ) ); + if ( ent ) { + ent->MoveToJoint( this, jointName ); + ent->BindToJoint( this, jointName, false ); + ent->GetRenderEntity()->weaponDepthHack = true; + } + + return ent; +} + +hhEntityFx* hhWeaponSoulStripper::SpawnCanisterFx( const char *attach, const idStr &name ) { + const char *jointName; + idVec3 boneOrigin; + idMat3 boneAxis; + hhEntityFx* fx; + + if( name.IsEmpty() ) + return NULL; + + jointName = dict->GetString( attach ); + + GetJointWorldTransform( dict->GetString(attach), boneOrigin, boneAxis); + + hhFxInfo fxInfo; + fxInfo.SetEntity( this ); + fxInfo.SetBindBone( jointName ); + fx = SpawnFxLocal( name, boneOrigin, boneAxis, &fxInfo, true ); + fx->GetRenderEntity()->weaponDepthHack = true; + fx->GetRenderEntity()->allowSurfaceInViewID = owner->entityNumber+1; + //don't attach canister fx to snapshotOwner, since they are local and temporary + /* + if (owner.IsValid()) { + fx->snapshotOwner = owner.GetEntity(); //rww + } + */ + + return fx; +} + +//============================================================================= +// +// hhWeaponSoulStripper::CreateAltFireController +// +//============================================================================= +hhWeaponFireController* hhWeaponSoulStripper::CreateAltFireController() { + return hhWeapon::CreateAltFireController(); + //return new hhSoulStripperAltFireController; +} + +//============================================================================= +// +// hhWeaponSoulStripper::~hhWeaponSoulStripper +// +//============================================================================= + +hhWeaponSoulStripper::~hhWeaponSoulStripper() { + SAFE_REMOVE( beam ); + + DestroyCans(); + + // Remove the beam light + if ( beamLightHandle != -1 ) { + gameRenderWorld->FreeLightDef( beamLightHandle ); + beamLightHandle = -1; + } + + StopSound( SND_CHANNEL_WEAPON, false ); //rww - don't broadcast this. you're removing the entity on the client too, so not only would it get + //called there too, but once you receive the event the entity will be gone and it will be tossed. +} + +//============================================================================= +// +// hhWeaponSoulStripper::UpdateGUI +// +//============================================================================= +void hhWeaponSoulStripper::UpdateGUI() { + if ( renderEntity.gui[0] ) { + renderEntity.gui[0]->SetStateInt( "souls", (GetAnimPostfix()>0) ? GetAnimPostfix()-'A'+1 : 0 ); + float p = 3.0f * owner->inventory.AmmoPercentage(owner.GetEntity(), GetAmmoType()); + renderEntity.gui[0]->SetStateFloat( "soulpercentageA", idMath::ClampFloat(0.0f, 1.0f, p - 0.0f) ); + renderEntity.gui[0]->SetStateFloat( "soulpercentageB", idMath::ClampFloat(0.0f, 1.0f, p - 1.0f) ); + renderEntity.gui[0]->SetStateFloat( "soulpercentageC", idMath::ClampFloat(0.0f, 1.0f, p - 2.0f) ); + renderEntity.gui[0]->SetStateBool( "leeching", (targetNode) ? true : false); + + const char* str; + if( targetNode ) { + const idDict *energyDef; + str = targetNode->spawnArgs.GetString( "def_energy" ); + if ( str && str[0] ) { + energyDef = gameLocal.FindEntityDefDict( str ); + if ( energyDef ) { + renderEntity.gui[0]->SetStateFloat( "meter", (gameLocal.time - targetTime)/energyDef->GetFloat( "leech_time", "500" ) ); + renderEntity.gui[0]->SetStateInt( "leechtype", energyDef->GetInt("beamType")+1 ); + } + } + } + + if( fireController ) { + str = fireController->GetString("intgui"); + if( str && str[0] ) + renderEntity.gui[0]->SetStateInt( "type", str[0]-'1'+1 ); + } + else { + renderEntity.gui[0]->SetStateInt( "type", 0 ); + } + } +} + +void hhWeaponSoulStripper::Event_Leech() { + idVec3 start, end; + trace_t results; + int captured = 0; + + if ( !beam.IsValid() ) { + idThread::ReturnInt( 0 ); + return; + } + + start = GetOrigin(); + + // Currently latched onto a target, check to see if the target is within the valid cone and there is still a LOS to the target + if ( targetNode ) { + end = beam->GetTargetLocation(); + + // Verify that the target is within a cone in front of the weapon + idVec3 vecToTarget = end - start; + vecToTarget.Normalize(); + float dpX = GetAxis()[0] * vecToTarget; + float dpY = GetAxis()[1] * vecToTarget; + + // Target isn't in the cone, so reset the trace + if ( idMath::Fabs( dpY ) >= spawnArgs.GetFloat( "beamTargetAngle", "0.4" ) || !targetNode->CanLeech() ) { + //ReleaseEntity(); + targetNode->LeechTrigger(GetOwner(),"leech_end"); + targetNode = NULL; + end = start + maxBeamLength * GetAxis()[0]; + } + } + else + end = start + maxBeamLength * GetAxis()[0]; + + // Check to see if the beam should attempt to hit something + if (owner.IsValid()) { + //rww - can hit owners due to different angles in mp (although not the local client since the trace is done on the client every frame) + gameLocal.clip.TracePoint( results, start, end, MASK_SHOT_RENDERMODEL | CONTENTS_GAME_PORTAL, owner.GetEntity() ); + } + else { + gameLocal.clip.TracePoint( results, start, end, MASK_SHOT_RENDERMODEL | CONTENTS_GAME_PORTAL, this ); + } + + beamLength = (results.endpos - start).Length(); + + // Hit something + if ( results.fraction < 1.0f ) { + captured = CaptureEnergy( results ); + + // Update the main beam + UpdateBeam( start, true ); // struck an entity + } + + //if( owner.IsValid() ) { + // owner->WeaponFireFeedback( dict ); + //} + + // Return true if the beam has struck an entity + idThread::ReturnFloat( (float)captured ); +} + +//============================================================================= +// +// hhWeaponSoulStripper::Event_RecoilBeam +// +//============================================================================= + +void hhWeaponSoulStripper::Event_EndLeech() { + idVec3 start; + idMat3 axis; + + if ( !beam.IsValid() ) { + return; + } + + if ( targetNode ) + targetNode->LeechTrigger(GetOwner(),"leech_end"); + targetNode = NULL; + + // Update the main beam + UpdateBeam( start, false ); + + if( owner.IsValid() ) { + owner->WeaponFireFeedback( dict ); + } + + idThread::ReturnFloat( 0 ); +} + +//============================================================================= +// +// hhWeaponSoulStripper::Ticker +// +// Controls the length of the beam and firing projectiles +//============================================================================= + +void hhWeaponSoulStripper::Ticker() { + // Update canister beams + UpdateCanisterBeam( beamCanA1.GetEntity(), "attach_topA", "attach_soulA" ); + UpdateCanisterBeam( beamCanB1.GetEntity(), "attach_topB", "attach_soulB" ); + UpdateCanisterBeam( beamCanC1.GetEntity(), "attach_topC", "attach_soulC" ); + UpdateCanisterBeam( beamCanA2.GetEntity(), "attach_bottomA", "attach_soulA" ); + UpdateCanisterBeam( beamCanB2.GetEntity(), "attach_bottomB", "attach_soulB" ); + UpdateCanisterBeam( beamCanC2.GetEntity(), "attach_bottomC", "attach_soulC" ); + UpdateCanisterBeam( beamCanA3.GetEntity(), "attach_bottomA", "attach_topA" ); + UpdateCanisterBeam( beamCanB3.GetEntity(), "attach_bottomB", "attach_topB" ); + UpdateCanisterBeam( beamCanC3.GetEntity(), "attach_bottomC", "attach_topC" ); + + hhBeamBasedFireController *beamController; + hhSunbeamFireController *sun; + if ( fireController && fireController->IsType(hhBeamBasedFireController::Type) ) { + if ( fireController->IsType(hhSunbeamFireController::Type) ) { + sun = static_cast(fireController); + sun->Think(); + } + else { + beamController = static_cast(fireController); + beamController->Think(); + } + } + + // Update our current animation if cans changed + if ( GetAnimPostfix() != lastCanState ) { + if ( GetAnimator()->IsAnimating( gameLocal.time ) && lastAnim.Length() != 0 ) { + idStr animname = lastAnim; + animname.Append( GetAnimPostfix() ); + int anim = GetAnimator()->GetAnim( animname ); + if( anim ) + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS(4) ); + } + + lastCanState = GetAnimPostfix(); + } +} + +//============================================================================= +// +// hhWeaponSoulStripper::UpdateBeam +// +// Updates the beam system +// - beam length +// - scale issues (don't let the beam wiggle as wildly when it is pulled close) +// - beam light +//============================================================================= + +void hhWeaponSoulStripper::UpdateBeam( idVec3 start, bool struckEntity ) { + float scale; + idVec3 boneOrigin; + idMat3 boneAxis; + + if ( !beam.IsValid() ) { + return; + } + + if ( targetNode ) { + beam->Activate( true ); + + GetJointWorldTransform( dict->GetString("attach_beam"), boneOrigin, boneAxis); + + beam->SetOrigin( boneOrigin ); + + beam->SetArcVector( GetAxis()[0] ); + scale = beamLength / 150.0f; + if( scale > 1 ) { + scale = 1; + } + + if ( targetNode->entityNumber < ENTITYNUM_MAX_NORMAL ) { + beam->SetTargetLocation( targetNode->leechPoint ); + } + + int beamType=0; + const idDict *energyDef; + const char* str = targetNode->spawnArgs.GetString( "def_energy" ); + if ( str && str[0] ) { + energyDef = gameLocal.FindEntityDefDict( str ); + if ( energyDef ) + beamType=energyDef->GetInt("beamType"); + } + + beam->SetShaderParm( SHADERPARM_MODE, beamType ); + beamLight.shaderParms[ SHADERPARM_MODE ] = beamType; + + beam->SetBeamOffsetScale( scale ); + + // Update the beam light + beamLight.axis[0] = GetAxis()[2]; + beamLight.axis[1] = GetAxis()[1]; + beamLight.axis[2] = GetAxis()[0]; // Re-align the light to point along the beam + beamLight.origin = (GetOrigin() + GetAxis()[0] * beamLength * 0.25f); // Set beam origin to the midpoint of the weapon origin and the target + beamLight.lightRadius[2] = 128.0f + beamLength * 0.5f; // Scale the light size based upon the beam (larger than half, so the beam extends beyond the extents slightly) + + if ( beamLightHandle != -1 ) { + gameRenderWorld->UpdateLightDef( beamLightHandle, &beamLight ); + } else { + beamLightHandle = gameRenderWorld->AddLightDef( &beamLight ); + } + } else { + beam->Activate( false ); + + // Hide the beam light + if ( beamLightHandle != -1 ) { + gameRenderWorld->FreeLightDef( beamLightHandle ); + beamLightHandle = -1; + } + } +} + +//============================================================================= +// +// hhWeaponSoulStripper::UpdateCanisterBeam +// +//============================================================================= + +void hhWeaponSoulStripper::UpdateCanisterBeam( hhBeamSystem *system, const char *top, const char *bottom ) { + idVec3 boneOrigin; + idMat3 boneAxis; + + if ( !system ) { + return; + } + + GetJointWorldTransform( dict->GetString( top ), boneOrigin, boneAxis); + system->SetTargetLocation( boneOrigin ); + GetJointWorldTransform( dict->GetString( bottom ), boneOrigin, boneAxis); + system->SetOrigin( boneOrigin ); +} + +//============================================================================= +// +// hhWeaponSoulStripper::CaptureEnergy +// +//============================================================================= + +int hhWeaponSoulStripper::CaptureEnergy( trace_t &results ) { + idEntity *hitEntity; + hhEnergyNode* hitNode; + + hitEntity = gameLocal.GetTraceEntity(results); + + if ( hitEntity && hitEntity->IsType( hhEnergyNode::Type ) ) { + hitNode = static_cast(hitEntity); + if ( !hitNode->CanLeech() ) + return 0; + } + else { + if ( targetNode ) + targetNode->LeechTrigger(GetOwner(),"leech_end"); + targetNode = NULL; + return 0; + } + + if ( targetNode != hitNode ) { + if ( targetNode ) + targetNode->LeechTrigger(GetOwner(),"leech_end"); + targetTime = gameLocal.time; + targetNode = hitNode; + targetNode->LeechTrigger(GetOwner(),"leech_start"); + } + + const idDict *energyDef; + const char* str = targetNode->spawnArgs.GetString( "def_energy" ); + if ( !str || !str[0] ) + return 0; // blank node + + energyDef = gameLocal.FindEntityDefDict( str ); + if ( !energyDef ) + return 0; + + if ( gameLocal.time - targetTime < energyDef->GetInt( "leech_time", "500" ) ) + return 1; //not enough time + + targetNode->LeechTrigger(GetOwner(),"leech_success"); + targetNode->Finish(); + targetTime = gameLocal.time; + + //create new firecontroller for new energy type + if( !GiveEnergy( energyDef->GetString("def_fireInfo"), true ) ) + return 0; + + StartSound( "snd_intake", SND_CHANNEL_ANY ); + SpawnCanisterFx( "attach_sidevent", spawnArgs.GetString("fx_sidevent") ); + SpawnCanisterFx( "attach_steam", spawnArgs.GetString("fx_steam") ); + + owner->inventory.energyType = str; + + return 2; +} + +bool hhWeaponSoulStripper::GiveEnergy( const char *energyType, bool fill ) { + if ( !energyType || !energyType[0] ) + return false; + + //create new firecontroller for new energy type + if (gameLocal.isClient) //rww - handle this properly through snapshots + return true; + + SAFE_DELETE_PTR(fireController); + const idDeclEntityDef *fcDecl = gameLocal.FindEntityDef( energyType, false ); + const idDict* infoDict = fcDecl ? &fcDecl->dict : NULL; + fcDeclNum = 0; + if( infoDict ) { + const char *spawn; + idTypeInfo *cls; + + if( fill ) { + int num = owner->GetWeaponNum("weaponobj_soulstripper"); + assert(num); + owner->weaponInfo[ num ].ammoMax = infoDict->GetInt("ammoAmount"); + owner->spawnArgs.SetInt( "max_ammo_energy", infoDict->GetInt("ammoAmount") ); + owner->inventory.ammo[owner->inventory.AmmoIndexForAmmoClass("ammo_energy")]=0; + owner->Give( "ammo_energy", infoDict->GetString("ammoAmount") ); + } + beam_canTop = infoDict->GetString( "beam_canTop" ); + beam_canBot = infoDict->GetString( "beam_canBot" ); + beam_canGlow = infoDict->GetString( "beam_canGlow" ); + fx_can = infoDict->GetString( "fx_can" ); + + DestroyCans(); + SpawnCans(); + + infoDict->GetString( "spawnclass", NULL, &spawn ); + if ( !spawn || !spawn[0] ) { //rww - changed to be in line with client behavior + spawn = "hhWeaponFireController"; //default to regular fire controller + } + cls = idClass::GetClass( spawn ); + if ( !cls || !cls->IsType( hhWeaponFireController::Type ) ) { + common->Warning( "Could not spawn. Class '%s' not found.", spawn ); + return false; + } + fireController = static_cast( cls->CreateInstance() ); + if ( !fireController ) { + common->Warning( "Could not spawn '%s'. Instance could not be created.", spawn ); + return false; + } + fcDeclNum = fcDecl->Index(); + /* + else { + fireController = CreateFireController(); + } + */ + + fireController->Init( infoDict, this, owner.GetEntity() ); + } + + //if it failed, use generic empty controller + if ( !fireController ) { + const idDict* infoDict = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_fireInfo"), false ); + if( infoDict ) { + fireController = CreateFireController(); + fireController->Init( infoDict, this, owner.GetEntity() ); + } + } + return true; +} + +//============================================================================= +// +// hhWeaponSoulStripper::GetAnimPostfix +// +// Appends to the passed in postfix. +//============================================================================= + +char hhWeaponSoulStripper::GetAnimPostfix() { + // Choose the anim postfix based upon ammo + float p=owner->inventory.AmmoPercentage(owner.GetEntity(), GetAmmoType()); + if(p > 0) + return (int)(2.9f*p) + 'A'; + else + return 0; +} + +//============================================================================= +// +// hhWeaponSoulStripper::Event_GetAnimPostFix +// +//============================================================================= + +void hhWeaponSoulStripper::Event_GetAnimPostFix() { + idStr postfix( GetAnimPostfix() ); + idThread::ReturnString( postfix.c_str() ); +} + +//============================================================================= +// +// hhWeaponSoulStripper::Event_PlayAnim +// +//============================================================================= + +void hhWeaponSoulStripper::Event_PlayAnim( int channel, const char *animname ) { + lastAnim = ""; + + idStr anim = animname; + anim.Append( GetAnimPostfix() ); + hhWeapon::Event_PlayAnim( channel, anim.c_str() ); +} + +//============================================================================= +// +// hhWeaponSoulStripper::Event_PlayCycle +// +//============================================================================= + +void hhWeaponSoulStripper::Event_PlayCycle( int channel, const char *animname ) { + lastAnim = animname; + + idStr anim = animname; + anim.Append( GetAnimPostfix() ); + hhWeapon::Event_PlayCycle( channel, anim.c_str() ); +} + +//============================================================================= +// +// hhWeaponSoulStripper::PresentWeapon +// +//============================================================================= + +void hhWeaponSoulStripper::PresentWeapon( bool showViewModel ) { + if ( IsHidden() || !owner->CanShowWeaponViewmodel() || pm_thirdPerson.GetBool() ) { + if ( beamCanA1.IsValid() ) beamCanA1->Activate( false ); + if ( beamCanB1.IsValid() ) beamCanB1->Activate( false ); + if ( beamCanC1.IsValid() ) beamCanC1->Activate( false ); + if ( beamCanA2.IsValid() ) beamCanA2->Activate( false ); + if ( beamCanB2.IsValid() ) beamCanB2->Activate( false ); + if ( beamCanC2.IsValid() ) beamCanC2->Activate( false ); + if ( beamCanA3.IsValid() ) beamCanA3->Activate( false ); + if ( beamCanB3.IsValid() ) beamCanB3->Activate( false ); + if ( beamCanC3.IsValid() ) beamCanC3->Activate( false ); + if ( fxCanA.IsValid() ) fxCanA->Hide(); + if ( fxCanB.IsValid() ) fxCanB->Hide(); + if ( fxCanC.IsValid() ) fxCanC->Hide(); + } else { + if ( beamCanA1.IsValid() ) beamCanA1->Activate( true ); + if ( beamCanB1.IsValid() ) beamCanB1->Activate( true ); + if ( beamCanC1.IsValid() ) beamCanC1->Activate( true ); + if ( beamCanA2.IsValid() ) beamCanA2->Activate( true ); + if ( beamCanB2.IsValid() ) beamCanB2->Activate( true ); + if ( beamCanC2.IsValid() ) beamCanC2->Activate( true ); + if ( beamCanA3.IsValid() ) beamCanA3->Activate( true ); + if ( beamCanB3.IsValid() ) beamCanB3->Activate( true ); + if ( beamCanC3.IsValid() ) beamCanC3->Activate( true ); + if ( fxCanA.IsValid() ) fxCanA->Show(); + if ( fxCanB.IsValid() ) fxCanB->Show(); + if ( fxCanC.IsValid() ) fxCanC->Show(); + } + + hhWeapon::PresentWeapon( showViewModel ); +} + +//============================================================================= +// +// hhWeaponSoulStripper::Show +// +//============================================================================= + +void hhWeaponSoulStripper::Show() { + if ( beamCanA1.IsValid() ) beamCanA1->Show(); + if ( beamCanB1.IsValid() ) beamCanB1->Show(); + if ( beamCanC1.IsValid() ) beamCanC1->Show(); + if ( beamCanA2.IsValid() ) beamCanA2->Show(); + if ( beamCanB2.IsValid() ) beamCanB2->Show(); + if ( beamCanC2.IsValid() ) beamCanC2->Show(); + if ( beamCanA3.IsValid() ) beamCanA3->Show(); + if ( beamCanB3.IsValid() ) beamCanB3->Show(); + if ( beamCanC3.IsValid() ) beamCanC3->Show(); + if ( fxCanA.IsValid() ) fxCanA->Show(); + if ( fxCanB.IsValid() ) fxCanB->Show(); + if ( fxCanC.IsValid() ) fxCanC->Show(); + + hhWeapon::Show(); +} + +//============================================================================= +// +// hhWeaponSoulStripper::Hide +// +//============================================================================= + +void hhWeaponSoulStripper::Hide() { + if ( beamCanA1.IsValid() ) beamCanA1->Hide(); + if ( beamCanB1.IsValid() ) beamCanB1->Hide(); + if ( beamCanC1.IsValid() ) beamCanC1->Hide(); + if ( beamCanA2.IsValid() ) beamCanA2->Hide(); + if ( beamCanB2.IsValid() ) beamCanB2->Hide(); + if ( beamCanC2.IsValid() ) beamCanC2->Hide(); + if ( beamCanA3.IsValid() ) beamCanA3->Hide(); + if ( beamCanB3.IsValid() ) beamCanB3->Hide(); + if ( beamCanC3.IsValid() ) beamCanC3->Hide(); + if ( fxCanA.IsValid() ) fxCanA->Hide(); + if ( fxCanB.IsValid() ) fxCanB->Hide(); + if ( fxCanC.IsValid() ) fxCanC->Hide(); + + hhWeapon::Hide(); +} + + +/* +================ +hhWeaponSoulStripper::Save +================ +*/ +void hhWeaponSoulStripper::Save( idSaveGame *savefile ) const { + savefile->WriteRenderLight( beamLight ); + //HUMANHEAD PCF mdl 05/04/06 - Don't save light handles + //savefile->WriteInt( beamLightHandle ); + + beam.Save( savefile ); + beamCanA1.Save( savefile ); + beamCanB1.Save( savefile ); + beamCanC1.Save( savefile ); + beamCanA2.Save( savefile ); + beamCanB2.Save( savefile ); + beamCanC2.Save( savefile ); + beamCanA3.Save( savefile ); + beamCanB3.Save( savefile ); + beamCanC3.Save( savefile ); + fxCanA.Save( savefile ); + fxCanB.Save( savefile ); + fxCanC.Save( savefile ); + savefile->WriteBool(cansValid); + + savefile->WriteFloat( beamLength ); + savefile->WriteFloat( maxBeamLength ); + savefile->WriteObject( targetNode ); + savefile->WriteVec3( targetOffset ); + + savefile->WriteString( lastAnim ); +// savefile->WriteInt( lastAltAmmo ); +} + +/* +================ +hhWeaponSoulStripper::Restore +================ +*/ +void hhWeaponSoulStripper::Restore( idRestoreGame *savefile ) { + savefile->ReadRenderLight( beamLight ); + //HUMANHEAD PCF mdl 05/04/06 - Don't save light handles + //savefile->ReadInt( beamLightHandle ); + + beam.Restore( savefile ); + beamCanA1.Restore( savefile ); + beamCanB1.Restore( savefile ); + beamCanC1.Restore( savefile ); + beamCanA2.Restore( savefile ); + beamCanB2.Restore( savefile ); + beamCanC2.Restore( savefile ); + beamCanA3.Restore( savefile ); + beamCanB3.Restore( savefile ); + beamCanC3.Restore( savefile ); + fxCanA.Restore( savefile ); + fxCanB.Restore( savefile ); + fxCanC.Restore( savefile ); + savefile->ReadBool(cansValid); + + savefile->ReadFloat( beamLength ); + savefile->ReadFloat( maxBeamLength ); + savefile->ReadObject( reinterpret_cast ( targetNode ) ); + savefile->ReadVec3( targetOffset ); + + savefile->ReadString( lastAnim );; +} + +/* +================= +hhWeaponSoulStripper::ReadFromSnapshot +rww - update the fire controller on the client +================= +*/ +void hhWeaponSoulStripper::ClientUpdateFC(int fcType, int fcDefNumber) { + SAFE_DELETE_PTR(fireController); + + idTypeInfo *typeInfo = idClass::GetType(fcType); + const idDeclEntityDef *fcDecl = static_cast(declManager->DeclByIndex(DECL_ENTITYDEF, fcDefNumber, false)); + if (typeInfo && fcDecl) { + int num = owner->GetWeaponNum("weaponobj_soulstripper"); + assert(num); + owner->weaponInfo[ num ].ammoMax = fcDecl->dict.GetInt("ammoAmount"); + owner->spawnArgs.SetInt( "max_ammo_energy", fcDecl->dict.GetInt("ammoAmount") ); + beam_canTop = fcDecl->dict.GetString( "beam_canTop" ); + beam_canBot = fcDecl->dict.GetString( "beam_canBot" ); + beam_canGlow = fcDecl->dict.GetString( "beam_canGlow" ); + fx_can = fcDecl->dict.GetString( "fx_can" ); + DestroyCans(); + SpawnCans(); + + fireController = static_cast(typeInfo->CreateInstance()); + if (fireController) { + fireController->Init(&fcDecl->dict, this, owner.GetEntity()); + } + } +} + +/* +================= +hhWeaponSoulStripper::WriteToSnapshot +rww - write applicable weapon values to snapshot +================= +*/ +void hhWeaponSoulStripper::WriteToSnapshot( idBitMsgDelta &msg ) const { + //target entity + if (targetNode) { + msg.WriteBits(1, 1); + msg.WriteBits(targetNode->entityNumber, GENTITYNUM_BITS); + } + else { + msg.WriteBits(0, 1); + } + + //write the fire controller type + if (fireController) { + msg.WriteBits(fireController->GetType()->typeNum, idClass::GetTypeNumBits()); + msg.WriteBits(gameLocal.ServerRemapDecl(-1, DECL_ENTITYDEF, fcDeclNum ), gameLocal.entityDefBits); + } + else { + msg.WriteBits(0, idClass::GetTypeNumBits()); + msg.WriteBits(0, gameLocal.entityDefBits); + } + + hhWeapon::WriteToSnapshot(msg); +} + +/* +================= +hhWeaponSoulStripper::ReadFromSnapshot +rww - read applicable weapon values from snapshot +================= +*/ +void hhWeaponSoulStripper::ReadFromSnapshot( const idBitMsgDelta &msg ) { + //target entity + bool hasEnt = !!msg.ReadBits(1); + if (hasEnt) { + int entNum = msg.ReadBits(GENTITYNUM_BITS); + targetNode = static_cast(gameLocal.entities[entNum]); + } + else { + targetNode = NULL; + } + + int fcType = msg.ReadBits(idClass::GetTypeNumBits()); + int fcDefNumber = gameLocal.ClientRemapDecl(DECL_ENTITYDEF, msg.ReadBits(gameLocal.entityDefBits)); + + //standard weapon stuff (primarily done before switch logic to get owner) + hhWeapon::ReadFromSnapshot(msg); + + if (owner.IsValid() && fcType && (!fireController || fcType != fireController->GetType()->typeNum || !netInitialized)) { + //if there is no fire controller on the client or the fire controller does not much the type given by the server, switch + ClientUpdateFC(fcType, fcDefNumber); + netInitialized = true; + } +} + +/* +================ +hhWeaponSoulStripper::GetClipBits +================ +*/ +int hhWeaponSoulStripper::GetClipBits(void) const { + return 12; //0-4096 +} + +void hhWeaponSoulStripper::Event_LightFadeIn( float fadetime ) { + if (gameLocal.isMultiplayer) { //rww - none of this in mp + return; + } + idEntity *ent; + idEntity *next; + idLight *light; + idVec3 color; + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = next ) { + next = ent->spawnNode.Next(); + if( !gameLocal.InPlayerPVS(ent) ) + continue; + if ( !ent->IsType( idLight::Type ) ) + continue; + light = static_cast( ent ); + if ( light->GetMaterial()->IsFogLight() ) + continue; + light->GetColor( color ); + if ( color == idVec3(0.001f,0.f,0.f) ) + light->FadeIn( fadetime ); + } +} + +void hhWeaponSoulStripper::Event_LightFadeOut( float fadetime ) { + if (gameLocal.isMultiplayer) { //rww - none of this in mp + return; + } + idEntity *ent; + idEntity *next; + idLight *light; + idVec3 color; + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = next ) { + next = ent->spawnNode.Next(); + if( !gameLocal.InPlayerPVS(ent) ) + continue; + if ( !ent->IsType( idLight::Type ) ) + continue; + light = static_cast( ent ); + if ( light->GetMaterial()->IsFogLight() ) + continue; + light->GetColor( color ); + if( light->spawnArgs.GetVector("_color", "1 1 1") == color && light->GetCurrentLevel()==1 ) + light->Fade(idVec4(0.001f,0.f,0.f,0.f), fadetime ); + } +} + +void hhWeaponSoulStripper::Event_LightFade( float fadeOut, float pause, float fadeIn ) { + PostEventSec( &EV_LightFadeOut, 0, fadeOut ); + PostEventSec( &EV_LightFadeIn, pause, fadeIn ); +} + +void hhWeaponSoulStripper::Event_GetFireFunction() { + if ( fireController ) { + idThread::ReturnString( fireController->GetScriptFunction() ); + } +} + +void hhWeaponSoulStripper::Event_KillBeam() { + if ( fireController && fireController->IsType(hhBeamBasedFireController::Type) ) { + hhBeamBasedFireController *beamController = static_cast(fireController); + beamController->KillBeam(); + } +} + +/*void hhWeaponSoulStripper::Event_GetFireSound() { + if ( fireController ) { + idThread::ReturnString( fireController->GetScriptFunction() ); + } +}*/ \ No newline at end of file diff --git a/src/Prey/prey_weaponsoulstripper.h b/src/Prey/prey_weaponsoulstripper.h new file mode 100644 index 0000000..1201e3d --- /dev/null +++ b/src/Prey/prey_weaponsoulstripper.h @@ -0,0 +1,182 @@ +#ifndef __HH_WEAPON_SOULSTRIPPER_H +#define __HH_WEAPON_SOULSTRIPPER_H + + +/*********************************************************************** + + hhSoulStripperAltFireController + +***********************************************************************/ +class hhSoulStripperAltFireController : public hhWeaponFireController { + CLASS_PROTOTYPE(hhSoulStripperAltFireController); +public: + virtual void LaunchProjectiles( const idVec3& launchOrigin, const idMat3& aimAxis, const idVec3& pushVelocity, idEntity* projOwner ); +}; + + +/*********************************************************************** + + hhBeamBasedFireController + +***********************************************************************/ +class hhBeamBasedFireController : public hhWeaponFireController { + CLASS_PROTOTYPE(hhBeamBasedFireController); +public: + virtual ~hhBeamBasedFireController(); + virtual void Init( const idDict* viewDict, hhWeapon* self, hhPlayer* owner ); + virtual void LaunchProjectiles( const idVec3& launchOrigin, const idMat3& aimAxis, const idVec3& pushVelocity, idEntity* projOwner ); + + void KillBeam(); + void Think(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +protected: + idEntityPtr shotbeam; + int projTime; + idEntityPtr impactFx; +}; + +/*********************************************************************** + + hhSunbeamFireController + +***********************************************************************/ +class hhSunbeamFireController : public hhBeamBasedFireController { + CLASS_PROTOTYPE(hhSunbeamFireController); +public: + virtual void LaunchProjectiles( const idVec3& launchOrigin, const idMat3& aimAxis, const idVec3& pushVelocity, idEntity* projOwner ); + void Think(); +}; + +/*********************************************************************** + + hhPlasmaFireController + +***********************************************************************/ +class hhPlasmaFireController : public hhWeaponFireController { + CLASS_PROTOTYPE(hhPlasmaFireController); +public: + virtual bool LaunchProjectiles( const idVec3& pushVelocity ); +}; + +/*********************************************************************** + + hhWeaponSoulStripper + +***********************************************************************/ +#define MAX_SOUL_AMMO 3 + +typedef enum +{ + CAP_NONE = 0, + CAP_ENTITY, + CAP_INTAKE +} captureType_t; + +class hhWeaponSoulStripper : public hhWeapon { + CLASS_PROTOTYPE( hhWeaponSoulStripper ); + + public: + hhWeaponSoulStripper(); + void Spawn(); + virtual ~hhWeaponSoulStripper(); + + virtual void ParseDef( const char* objectname ); + + virtual void UpdateGUI(); + + virtual void Show(); + virtual void Hide(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - network friendliness + virtual void ClientUpdateFC(int fcType, int fcDefNumber); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + virtual int GetClipBits(void) const; + + // beam light + renderLight_t beamLight; + int beamLightHandle; + + bool GiveEnergy( const char *energyType, bool fill ); + + void CheckCans(void); //rww + protected: + virtual void Ticker(); + char GetAnimPostfix(); + virtual void PresentWeapon( bool showViewModel ); + virtual hhWeaponFireController* CreateAltFireController(); + + void UpdateBeam( idVec3 start, bool struckEntity ); + void UpdateCanisterBeam( hhBeamSystem *system, const char *top, const char *bottom ); + + int CaptureEnergy( trace_t &results ); + + void Event_PostSpawn(); + void Event_GetAnimPostFix(); + void Event_Leech(); + void Event_EndLeech(); + + void Event_PlayAnim( int channel, const char *animname ); + void Event_PlayCycle( int channel, const char *animname ); + + void SpawnCans(); + void DestroyCans(); + + hhBeamSystem *SpawnCanisterBeam( const char *bottom, const char *top, const idStr &beamName ); + idEntity *SpawnCanisterSprite( const char *attach, const char *spriteName ); + hhEntityFx *SpawnCanisterFx( const char *attach, const idStr &name ); + + void Event_LightFadeIn( float fadetime ); + void Event_LightFadeOut( float fadetime ); + void Event_LightFade( float fadeOut, float pause, float fadeIn ); + void Event_GetFireFunction(); + void Event_KillBeam(); + + protected: + idEntityPtr beam; + + // Beams for each canister + idEntityPtr beamCanA1; // bottom to center + idEntityPtr beamCanB1; // bottom to center + idEntityPtr beamCanC1; // bottom to center + idEntityPtr beamCanA2; // top to center + idEntityPtr beamCanB2; // top to center + idEntityPtr beamCanC2; // top to center + idEntityPtr beamCanA3; // top to bottom + idEntityPtr beamCanB3; // top to bottom + idEntityPtr beamCanC3; // top to bottom + + idEntityPtr fxCanA; + idEntityPtr fxCanB; + idEntityPtr fxCanC; + + float beamLength; + float maxBeamLength; + + hhEnergyNode *targetNode; // Target struck by the beam + idVec3 targetOffset; // Offset from the origin of target + int targetTime; + + idStr lastAnim; // For smoothing the animation when picking up an ammo_soul -mdl + char lastCanState; + + bool empty; + + int fcDeclNum; //rww - needed for networking (and a good idea to have around anyway) + + idStr beam_canTop; + idStr beam_canBot; + idStr beam_canGlow; + idStr fx_can; + bool netInitialized; //rww + bool cansValid; //rww +}; + +#endif \ No newline at end of file diff --git a/src/Prey/prey_weaponspiritbow.cpp b/src/Prey/prey_weaponspiritbow.cpp new file mode 100644 index 0000000..a833a05 --- /dev/null +++ b/src/Prey/prey_weaponspiritbow.cpp @@ -0,0 +1,378 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/*********************************************************************** + + hhSpiritBowFireController + +***********************************************************************/ +CLASS_DECLARATION( hhWeaponFireController, hhSpiritBowFireController ) +END_CLASS + +/* +================= +hhSpiritBowFireController::AmmoRequired +================= +*/ +int hhSpiritBowFireController::AmmoRequired() const { + if( owner->IsDeathWalking() || owner->IsPossessed() ) { + return 0; + } + else { // CJR: will use any available ammo until the player is at zero ammo + int ammoAmount = owner->inventory.ammo[ GetAmmoType() ]; + if ( ammoAmount > 0 && ammoAmount < ammoRequired ) { + return ammoAmount; + } + } + + return ammoRequired; +} + +/* +================= +hhSpiritBowFireController::GetProjectileDict +================= +*/ +const idDict* hhSpiritBowFireController::GetProjectileDict() const +{ + //if( owner->inventory.maxHealth > 100 && !owner->IsDeathWalking() && !gameLocal.isMultiplayer ) + // return gameLocal.FindEntityDefDict( dict->GetString("def_projectileSuper"), false ); + + return hhWeaponFireController::GetProjectileDict(); +} + +/*********************************************************************** + + hhWeaponSpiritBow + +***********************************************************************/ +const idEventDef EV_UpdateBowVision( "updateBowVision" ); +const idEventDef EV_StartSeeThroughWalls( "startBowVision" ); +const idEventDef EV_FadeOutSeeThroughWalls( "fadeOutBowVision" ); +const idEventDef EV_StopSeeThroughWalls( "stopBowVision" ); +const idEventDef EV_BowVisionIsEnabled( "visionIsEnabled", NULL, 'd' ); + +CLASS_DECLARATION( hhWeaponZoomable, hhWeaponSpiritBow ) + EVENT( EV_UpdateBowVision, hhWeaponSpiritBow::Event_UpdateBowVision ) + EVENT( EV_StartSeeThroughWalls, hhWeaponSpiritBow::Event_StartSeeThroughWalls ) + EVENT( EV_FadeOutSeeThroughWalls, hhWeaponSpiritBow::Event_FadeOutSeeThroughWalls ) + EVENT( EV_StopSeeThroughWalls, hhWeaponSpiritBow::Event_StopSeeThroughWalls ) + EVENT( EV_BowVisionIsEnabled, hhWeaponSpiritBow::Event_BowVisionIsEnabled ) +END_CLASS + +/* +================= +hhWeaponSpiritBow::~hhWeaponSpiritBow +================= +*/ +hhWeaponSpiritBow::~hhWeaponSpiritBow() { + if( BowVisionIsEnabled() ) { + BowVisionIsEnabled( false ); + StopBowVision(); + } +} + +/* +================= +hhWeaponSpiritBow::Spawn +================= +*/ +void hhWeaponSpiritBow::Spawn() { + updateRover = 0; //rww - must be initialized! (sync'd over net) + BowVisionIsEnabled( false ); +} + +/* +================= +hhWeaponSpiritBow::BeginAltAttack +================= +*/ +void hhWeaponSpiritBow::BeginAltAttack( void ) { + if (owner->inventory.requirements.bCanUseBowVision) { + hhWeapon::BeginAltAttack(); + } +} + +/* +================= +hhWeaponSpiritBow::Ticker +================= +*/ +void hhWeaponSpiritBow::Ticker() { + // Fade out the alt-mode effect + if (!fadeAlpha.IsDone(gameLocal.GetTime())) { + float alpha = fadeAlpha.GetCurrentValue(gameLocal.GetTime()); + owner->playerView.SetViewOverlayColor(idVec4(alpha, alpha, alpha, alpha)); + } +} + +/* +================= +hhWeaponSpiritBow::GetProxyOf +================= +*/ +hhTargetProxy *hhWeaponSpiritBow::GetProxyOf( const idEntity *target ) { + hhTargetProxy *proxy; + // Determine whether this target is proxied by searching their bind list + for( idEntity* entity = target->GetTeamChain(); entity; entity = entity->GetTeamChain() ) { + if( entity && entity->IsType(hhTargetProxy::Type) ) { + proxy = static_cast(entity); + if( owner == proxy->GetOwner() ) { + return proxy; + } + } + } + + return NULL; +} + +/* +================= +hhWeaponSpiritBow::ProxyShouldBeVisible +================= +*/ +#define NOTVISIBLE 0 +#define ISVISIBLE 1 +#define FORCEVISIBILITY 2 +bool hhWeaponSpiritBow::ProxyShouldBeVisible( const idEntity* ent ) { + int visibilityType = ent->spawnArgs.GetInt("bowVisibilityType", "1"); + + if( visibilityType == FORCEVISIBILITY ) { + return true; + } + + return ent->fl.takedamage && visibilityType >= ISVISIBLE; +} + +/* +================= +hhWeaponSpiritBow::StopBowVision +================= +*/ +void hhWeaponSpiritBow::StopBowVision() { + if( !owner.IsValid() || !owner.GetEntity() || !owner->IsType(hhPlayer::Type) ) { + return; + } + + // Remove overlay + if( owner->IsSpiritWalking() ) { + owner->playerView.SetViewOverlayMaterial( declManager->FindMaterial(owner->spawnArgs.GetString("mtr_Spiritwalk")) ); + } + else { + owner->playerView.SetViewOverlayMaterial( NULL ); + } +} + +/* +================= +hhWeaponSpiritBow::Event_UpdateBowVision +================= +*/ +void hhWeaponSpiritBow::Event_UpdateBowVision() { + hhTargetProxy *proxy = NULL; + idEntity *ent = NULL; + + // In order to hit all possible entities (active or inactive) at low cost, we use a rover + // to traverse the entity list a little each tick, making a complete traversal about once + // a second. + + float fuse = altFireController->GetProjectileDict()->GetFloat( "fuse" ); + idVec3 velocity = altFireController->GetProjectileDict()->GetVector( "velocity" ); + float maxDistance = velocity.Length() * fuse; + float distSquared = maxDistance * maxDistance; + + // for all damageable active entities in the fuse radius + for (int ix=0; ix= MAX_GENTITIES) { + updateRover = 0; + } + ent = gameLocal.entities[updateRover]; + if (!ent || owner==ent ) { + continue; + } + + if( !ProxyShouldBeVisible(ent) ) { + continue; + } + + // PVS Check? + + if ((ent->GetOrigin() - GetOrigin()).LengthSqr() > distSquared) { + continue; + } + + // if already has proxy, continue + proxy = GetProxyOf(ent); + if (proxy) { + proxy->StayAlive(); + continue; + } + + if (!gameLocal.isClient) { + proxy = static_cast( gameLocal.SpawnObject(dict->GetString("def_targetproxy")) ); + if (proxy) { + proxy->SetOriginal(ent); + proxy->SetOwner(owner.GetEntity()); + proxy->UpdateVisualState(); + } + } + } +} + +/* +================= +hhWeaponSpiritBow::Event_StartSeeThroughWalls +================= +*/ +void hhWeaponSpiritBow::Event_StartSeeThroughWalls() { + // Cancel any pending removals/fades + CancelEvents( &EV_StopSeeThroughWalls ); + + updateRover = 0; // start at begining of list + + fadeAlpha.Init(gameLocal.GetTime(), BOWVISION_FADEIN_DURATION, 0.0f, 1.0f); + + StartSound( "snd_altmodefadein", SND_CHANNEL_ANY, 0, true, NULL ); + + // Apply overlay material + const idMaterial *mat = declManager->FindMaterial( spawnArgs.GetString("mtr_overlay") ); + owner->playerView.SetViewOverlayMaterial( mat ); + owner->playerView.SetViewOverlayColor( idVec4(1.0f, 1.0f, 1.0f, 0.0f) ); + + BowVisionIsEnabled( true ); +} + +/* +================= +hhWeaponSpiritBow::Event_FadeOutSeeThroughWalls + Fading the effect out, notify proxies to start fading too +================= +*/ +void hhWeaponSpiritBow::Event_FadeOutSeeThroughWalls() { + idEntity *ent; + hhTargetProxy *proxy; + + fadeAlpha.Init(gameLocal.GetTime(), BOWVISION_FADEOUT_DURATION, 1.0f, 0.0f); + owner->playerView.SetViewOverlayColor(colorWhite); + + StartSound( "snd_altmodefadeout", SND_CHANNEL_ANY, 0, true, NULL ); + +/*FIXME: Should use this format instead for speed +for ( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->IsType( idLight::Type ) ) { + idLight *light = static_cast(ent); + } +}*/ + //TODO: Could store all created(owned) proxies in a list, for faster freeing here + // Unmark all proxies that were set for this owner + for (int ix=0; ixIsType(hhTargetProxy::Type)) { + continue; + } + + proxy = static_cast(ent); + if (owner==proxy->GetOwner()) { + proxy->ProxyFinished(); + } + } + + PostEventMS( &EV_StopSeeThroughWalls, BOWVISION_FADEOUT_DURATION ); + + BowVisionIsEnabled( false ); +} + +/* +================= +hhWeaponSpiritBow::Event_StopSeeThroughWalls +================= +*/ +void hhWeaponSpiritBow::Event_StopSeeThroughWalls() { + BowVisionIsEnabled( false ); + StopBowVision(); +} + +/* +================= +hhWeaponSpiritBow::Event_BowVisionIsEnabled +================= +*/ +void hhWeaponSpiritBow::Event_BowVisionIsEnabled() { + idThread::ReturnInt( BowVisionIsEnabled() ); +} + +/* +================ +hhWeaponSpiritBow::Save +================ +*/ +void hhWeaponSpiritBow::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( fadeAlpha.GetStartTime() ); // idInterpolate + savefile->WriteFloat( fadeAlpha.GetDuration() ); + savefile->WriteFloat( fadeAlpha.GetStartValue() ); + savefile->WriteFloat( fadeAlpha.GetEndValue() ); + + savefile->WriteInt( updateRover ); + savefile->WriteBool( visionEnabled ); +} + +/* +================ +hhWeaponSpiritBow::Restore +================ +*/ +void hhWeaponSpiritBow::Restore( idRestoreGame *savefile ) { + float set; + + savefile->ReadFloat( set ); // idInterpolate + fadeAlpha.SetStartTime( set ); + savefile->ReadFloat( set ); + fadeAlpha.SetDuration( set ); + savefile->ReadFloat( set ); + fadeAlpha.SetStartValue( set ); + savefile->ReadFloat( set ); + fadeAlpha.SetEndValue( set ); + + savefile->ReadInt( updateRover ); + savefile->ReadBool( visionEnabled ); +} + +/* +================= +hhWeaponSpiritBow::WriteToSnapshot +rww - write applicable weapon values to snapshot +================= +*/ +void hhWeaponSpiritBow::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteBits(visionEnabled, 1); + msg.WriteBits(updateRover, GENTITYNUM_BITS); + +// msg.WriteFloat(fadeAlpha.GetCurrentValue(gameLocal.time)); + msg.WriteFloat(fadeAlpha.GetDuration()); +// msg.WriteFloat(fadeAlpha.GetEndTime()); + msg.WriteFloat(fadeAlpha.GetEndValue()); + msg.WriteFloat(fadeAlpha.GetStartTime()); + msg.WriteFloat(fadeAlpha.GetStartValue()); + + hhWeapon::WriteToSnapshot(msg); +} + +/* +================= +hhWeaponSpiritBow::ReadFromSnapshot +rww - read applicable weapon values from snapshot +================= +*/ +void hhWeaponSpiritBow::ReadFromSnapshot( const idBitMsgDelta &msg ) { + visionEnabled = !!msg.ReadBits(1); + updateRover = msg.ReadBits(GENTITYNUM_BITS); + + fadeAlpha.SetDuration(msg.ReadFloat()); + fadeAlpha.SetEndValue(msg.ReadFloat()); + fadeAlpha.SetStartTime(msg.ReadFloat()); + fadeAlpha.SetStartValue(msg.ReadFloat()); + + hhWeapon::ReadFromSnapshot(msg); +} diff --git a/src/Prey/prey_weaponspiritbow.h b/src/Prey/prey_weaponspiritbow.h new file mode 100644 index 0000000..ef2122a --- /dev/null +++ b/src/Prey/prey_weaponspiritbow.h @@ -0,0 +1,63 @@ +#ifndef __HH_WEAPON_SPIRITBOW_H +#define __HH_WEAPON_SPIRITBOW_H + +/*********************************************************************** + + hhWeaponSpiritBow + +***********************************************************************/ + +class hhSpiritBowFireController : public hhWeaponFireController { + CLASS_PROTOTYPE(hhSpiritBowFireController); +public: + int AmmoRequired() const; + virtual const idDict* GetProjectileDict() const; +}; + +class hhWeaponSpiritBow : public hhWeaponZoomable { + CLASS_PROTOTYPE( hhWeaponSpiritBow ); + + public: + ~hhWeaponSpiritBow(); + + void Spawn(); + + bool BowVisionIsEnabled() const { return visionEnabled; } + void BowVisionIsEnabled( bool enabled ) { visionEnabled = enabled; } + void StopBowVision(); + virtual void BeginAltAttack( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - network friendliness + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + protected: + virtual void Ticker(); + hhTargetProxy * GetProxyOf( const idEntity *target ); + bool ProxyShouldBeVisible( const idEntity* ent ); + ID_INLINE virtual hhWeaponFireController* CreateFireController(); + + protected: + void Event_StartSeeThroughWalls(); + void Event_FadeOutSeeThroughWalls(); + void Event_StopSeeThroughWalls(); + void Event_UpdateBowVision(); + + void Event_BowVisionIsEnabled(); + + protected: + idInterpolate fadeAlpha; + int updateRover; + + bool visionEnabled; +}; + +ID_INLINE hhWeaponFireController* hhWeaponSpiritBow::CreateFireController() { + return new hhSpiritBowFireController; +} + + +#endif \ No newline at end of file diff --git a/src/Prey/sys_debugger.cpp b/src/Prey/sys_debugger.cpp new file mode 100644 index 0000000..ffbce3c --- /dev/null +++ b/src/Prey/sys_debugger.cpp @@ -0,0 +1,1102 @@ +// sys_debugger.cpp +// +// HUMANHEAD: debugger functions +// + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/* TODO: + make dormant filter? + make physics filter? + Optimize: + preallocate all the string ptrs in DisplayCell and don't free them every frame +*/ + +#if INGAME_DEBUGGER_ENABLED + +hhDebugger debugger; + +int hhDebugger::sortColumn = 1; +bool hhDebugger::sortAscending = false; + +//========================================================================= +// +// hhDisplayCell +// +//========================================================================= + +hhDisplayCell::hhDisplayCell() { + string = NULL; + type = COLUMNTYPE_NUMERIC; + value = 0.0f; +} +hhDisplayCell::~hhDisplayCell() { + if (string) { + delete string; + string = NULL; + } +} +inline void hhDisplayCell::AllocateString() { + if (!string) { + string = new idStr; + } +} +inline void hhDisplayCell::operator=( const hhDisplayCell &cell ) { + if (cell.string) { + AllocateString(); + *string = *cell.string; + } + type = cell.type; + value = cell.value; +} +inline void hhDisplayCell::operator=(const idStr &text) { + AllocateString(); + *string = text; + type = COLUMNTYPE_ALPHA; +} +inline void hhDisplayCell::operator=(const char *text) { + AllocateString(); + *string = text; + type = COLUMNTYPE_ALPHA; +} +inline void hhDisplayCell::operator=(const int i) { + value = (float)i; + type = COLUMNTYPE_NUMERIC; +} +inline void hhDisplayCell::operator=(const float f) { + value = f; + type = COLUMNTYPE_NUMERIC; +} +inline hhDisplayCell & hhDisplayCell::operator+=(const int i) { + if (type == COLUMNTYPE_ALPHA) { + AllocateString(); + *this = atoi(String()) + i; + } + else { + value += (float)i; + } + type = COLUMNTYPE_NUMERIC; + return *this; +} +inline hhDisplayCell & hhDisplayCell::operator+=(const float f) { + if (type == COLUMNTYPE_ALPHA) { + AllocateString(); + *this = (float)atof(String()) + f; + } + else { + value += f; + } + type = COLUMNTYPE_NUMERIC; + return *this; +} +inline bool hhDisplayCell::IsInt() const { + return ((type == COLUMNTYPE_NUMERIC) && (value == (int)value) ); +} +inline int hhDisplayCell::StringLength() { + if (type == COLUMNTYPE_NUMERIC) { + return strlen(String()); + } + AllocateString(); + return string->Length(); +} +inline EColumnDataType hhDisplayCell::Type() const { + return type; +} +inline float hhDisplayCell::Float() const { + if (type == COLUMNTYPE_ALPHA) { + return (float)atof(String()); + } + return value; +} +inline int hhDisplayCell::Int() const { + if (type == COLUMNTYPE_ALPHA) { + return atoi(String()); + } + return (int)value; +} +const char *hhDisplayCell::String() const { + static idStr workStr; + if (type == COLUMNTYPE_NUMERIC) { + if (IsInt()) { + workStr = idStr((int)value); + } + else { + workStr = idStr(value); + } + return workStr.c_str(); + } + if (!string) { + return ""; + } + return string->c_str(); +} + +//========================================================================= +// +// hhDebugger +// +//========================================================================= + +hhDebugger::hhDebugger() { + bInitialized = false; + bClassCollapse = true; + bDrawEntities = false; + bPointer = false; + includeFilters = excludeFilters = 0; + filterClass = NULL; + potentialSet.Clear(); + displayList.Clear(); + selectedEntity = NULL; + sortColumn = 1; + sortAscending = false; + debugMode = DEBUGMODE_NONE; + gui=NULL; + displayColumnsUsed = 0; + + xaxis.Set(25, 0, 0); + yaxis.Set( 0, 25, 0); + zaxis.Set( 0, 0, 25); +} + +hhDebugger::~hhDebugger() { +} + +void hhDebugger::Shutdown() { + // Debugger must be shutdown manually because of the dict memeber which + // must be Cleared before the global string pool is emptied + dict.Clear(); + potentialSet.Clear(); + displayList.Clear(); + // release gui resources + bInitialized = false; +} + +void hhDebugger::Initialize() { + if (!gui) { + gui = uiManager->FindGui("guis/debugger.gui", true); + } + + SetMode(DEBUGMODE_STATS); + SetClassCollapse(true); + SetDrawEntities(false); + SetPointer(false); + ClearAllFilters(); + ExcludeFilter(FILTER_CLOSE); + ExcludeFilter(FILTER_DISTANT); + + bInitialized = true; +} + +void hhDebugger::Reset() { + selectedEntity = NULL; + filterClass = NULL; + Initialize(); +} + + +//========================================================================= +// +// hhDebugger::CaptureInput +// +//========================================================================= +void hhDebugger::CaptureInput(bool bCapture) { + if (bCapture && !bInteractive) { + // Just becoming interactive + const char *cmds = gui->Activate(true, gameLocal.time); + HandleGuiCommands(cmds); + } + else if (bInteractive && !bCapture) { + // Just becoming non-interactive + const char *cmds = gui->Activate(false, gameLocal.time); + HandleGuiCommands(cmds); + } + + bInteractive = bCapture; +} + +//========================================================================= +// +// hhDebugger::ColorForIndex +// +//========================================================================= +idVec4 hhDebugger::ColorForIndex(int index, float alpha) { + idVec4 color; + int bitmask = (index % 7) + 1; + color.x = (bitmask & 1) ? 1.0f : 0.0f; + color.y = (bitmask & 2) ? 1.0f : 0.0f; + color.z = (bitmask & 4) ? 1.0f : 0.0f; + color.w = alpha; + return color; +} + +//========================================================================= +// +// hhDebugger::ColorForEntity +// +//========================================================================= +idVec4 hhDebugger::ColorForEntity(idEntity *ent, float alpha) { + // Choose color based on editor_color if possible + idVec3 color3; + idVec4 color; + if (ent->spawnArgs.GetVector("editor_color", "0 0 1", color3)) { + color.x = color3.x; + color.y = color3.y; + color.z = color3.z; + color.w = alpha; + } + else { + color = ColorForIndex(ent->entityNumber, alpha); + } + return color; +} + +//========================================================================= +// +// hhDebugger::SetSortToColumn +// +//========================================================================= +void hhDebugger::SetSortToColumn(int col) { + if (hhDebugger::sortColumn == col) { + hhDebugger::sortAscending ^= 1; + } + else { + hhDebugger::sortColumn = col; + } +} + +//========================================================================= +// +// Filter routines +// +//========================================================================= +int hhDebugger::IndexForFilter(int filter) { //OBS + int index = hhMath::ILog2(filter); + return index; +} + +int hhDebugger::FilterForIndex(int index) { + return 1<SetStateInt(va("filter%i", filterIndex), FILTERSTATE_INCLUDED); + gui->HandleNamedEvent("SetFilter"); + } +} + +void hhDebugger::ExcludeFilter(int filterIndex) { + int filter = FilterForIndex(filterIndex); + excludeFilters |= filter; + includeFilters &= ~filter; + if (gui) { + gui->SetStateInt(va("filter%i", filterIndex), FILTERSTATE_EXCLUDED); + gui->HandleNamedEvent("SetFilter"); + } +} + +void hhDebugger::ClearFilter(int filterIndex) { + int filter = FilterForIndex(filterIndex); + includeFilters &= (~filter); + excludeFilters &= (~filter); + if (gui) { + gui->SetStateInt(va("filter%i", filterIndex), FILTERSTATE_NONE); + gui->HandleNamedEvent("SetFilter"); + } +} + +void hhDebugger::ClearAllFilters() { + for (int ix=0; ixDeleteStateVar( va( "listStats_item_%i", row ) ); + } + + gui->SetStateInt("mode", debugMode); + gui->HandleNamedEvent("SetMode"); + } +} + +void hhDebugger::SetClassCollapse(bool on) { + bClassCollapse = on; + if (gui) { + gui->SetStateBool("classcollapse", bClassCollapse); + gui->HandleNamedEvent("SetClassCollapse"); + } +} + +void hhDebugger::SetDrawEntities(bool on) { + bDrawEntities = on; + if (gui) { + gui->SetStateBool("drawEntities", bDrawEntities); + gui->HandleNamedEvent("SetDrawEntities"); + } +} + +void hhDebugger::SetPointer(bool on) { + bPointer = on; + if (gui) { + gui->SetStateBool("pointer", bPointer); + gui->HandleNamedEvent("SetPointer"); + } +} + +void hhDebugger::SetSelectedEntity(idEntity *ent) { + selectedEntity = ent; +} + + +//========================================================================= +// +// hhDebugger::HandleGuiCommands +// +//========================================================================= +bool hhDebugger::HandleGuiCommands(const char *commands) { + bool ret = false; + + if (commands && commands[0]) { + idLexer src; + src.LoadMemory( commands, strlen( commands ), "guiCommands" ); + + if (HandleSingleGuiCommand(gameLocal.GetLocalPlayer(), &src)) { + ret = true; + } + } + return ret; +} + +//========================================================================= +// +// hhDebugger::HandleSingleGuiCommand +// +//========================================================================= +bool hhDebugger::HandleSingleGuiCommand(idEntity *entityGui, idLexer *src) { + idToken token; + + if (!src->ReadToken(&token)) { + return false; + } + else if (token == ";") { + return false; + } + else if (token.IcmpPrefix("guicmd_title_") == 0) { // "title_cNN" + token.ToLower(); + token.Strip("guicmd_title_c"); // "NN" + int col = atoi(token.c_str()); + SetSortToColumn(col); + return true; + } + else if (token.IcmpPrefix("guicmd_select") == 0) { // "guicmd_select" + int row = gui->State().GetInt("listStats_sel_0", "-1"); + if (row >= 0) { + TranslateRowCommand(row); + } + } + else if (token.IcmpPrefix("guicmd_cyclemode") == 0) { // "guicmd_cyclemode" + SetMode( (EDebugMode)((GetMode() + 1) % NUM_DEBUGMODES) ); + } + else if (token.IcmpPrefix("guicmd_drawentities") == 0) { // "guicmd_drawentities" + SetDrawEntities(!GetDrawEntities()); + } + else if (token.IcmpPrefix("guicmd_classcollapse") == 0) { // "guicmd_classcollapse" + SetClassCollapse(!GetClassCollapse()); + } + else if (token.IcmpPrefix("guicmd_pointer") == 0) { // "guicmd_pointer" + SetPointer(!GetPointer()); + } + else if (token.IcmpPrefix("guicmd_filter") == 0) { // "guicmd_filter" + token.ToLower(); + token.Strip("guicmd_filter"); // "NN" + int filterIndex = atoi(token.c_str()); + + // toggle 3-way state + Toggle3wayFilter(filterIndex); + + // If turning off the class filter, revert back to ClassCollapse + if (filterIndex == FILTER_CLASS && !FilterIncluded(FILTER_CLASS)) { + SetClassCollapse(true); + } + } + + src->UnreadToken(&token); + return false; +} + +//========================================================================= +// +// hhDebugger::TranslateRowCommand +// +//========================================================================= +void hhDebugger::TranslateRowCommand(int row) { + if (GetMode() != DEBUGMODE_STATS) { + return; + } + + // clicked on a row + if (GetClassCollapse()) { + idTypeInfo *clickedClass = ClassForRow(row); + if (clickedClass) { + IncludeFilter(FILTER_CLASS); + filterClass = clickedClass; + SetClassCollapse(false); + } + } + else { + idEntity *clickedEntity = EntityForRow(row); + if (clickedEntity) { + if (!selectedEntity.IsValid() || clickedEntity != selectedEntity.GetEntity()) { + selectedEntity = clickedEntity; + } + else { + if (GetMode()==DEBUGMODE_STATS) { + SetMode(DEBUGMODE_GAMEINFO1); + } + } + } + } +} + +//========================================================================= +// +// hhDebugger::DeterminePotentialSet +// +//========================================================================= +void hhDebugger::DeterminePotentialSet() { + + float maxDistSquared = g_maxShowDistance.GetFloat()*g_maxShowDistance.GetFloat(); + float minDistSquared = DEBUG_MIN_ENT_DIST*DEBUG_MIN_ENT_DIST; + idPlayer *player = gameLocal.GetLocalPlayer(); + idVec3 playerPosition = player->GetEyePosition(); + idEntity *ent; + + potentialSet.Clear(); +/*FIXME: Should use this format instead for speed +for ( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->IsType( idLight::Type ) ) { + idLight *light = static_cast(ent); + } +}*/ + //FIXME: Use gameEdit->FindEntity ??? + for (int ix=0; ixGetType() != filterClass) ) { + continue; + } + if (FilterExcluded(FILTER_CLASS) && (ent->GetType() == filterClass) ) { + continue; + } + + if (FilterIncluded(FILTER_VISIBLE) && ent->IsHidden()) { // worthless, replace with more specific active filters (THINK,ANIM,UPDATEVISUALS,PHYSICS) + continue; + } + if (FilterExcluded(FILTER_VISIBLE) && !ent->IsHidden()) { + continue; + } + + if (FilterIncluded(FILTER_ACTIVE) && !ent->IsActive()) { + continue; + } + if (FilterExcluded(FILTER_ACTIVE) && ent->IsActive()) { + continue; + } + + if (FilterIncluded(FILTER_MONSTERS) && !ent->IsType(idAI::Type)) { + continue; + } + if (FilterExcluded(FILTER_MONSTERS) && ent->IsType(idAI::Type)) { + continue; + } + + if (FilterIncluded(FILTER_HASMODEL) && ent->GetModelDefHandle() == -1) { + continue; + } + if (FilterExcluded(FILTER_HASMODEL) && ent->GetModelDefHandle() != -1) { + continue; + } + + if (FilterIncluded(FILTER_EXPENSIVE) && ent->thinkMS < g_expensiveMS.GetFloat()) { + continue; + } + if (FilterExcluded(FILTER_EXPENSIVE) && ent->thinkMS > g_expensiveMS.GetFloat()) { + continue; + } + + if (FilterIsActive(FILTER_ANIMATING)) { + bool isAnimating = ent->GetAnimator() && ent->GetAnimator()->IsAnimating( gameLocal.time ); + if (FilterIncluded(FILTER_ANIMATING) && !isAnimating) { + continue; + } + if (FilterExcluded(FILTER_ANIMATING) && isAnimating) { + continue; + } + } + + if (FilterIsActive(FILTER_DORMANT)) { + + bool isDormant = ent->fl.isDormant; + if (FilterIncluded(FILTER_DORMANT) && !isDormant) { + continue; + } + if (FilterExcluded(FILTER_DORMANT) && isDormant) { + continue; + } + } + + if (FilterIsActive(FILTER_ACTIVESOUNDS)) { + bool playingSounds = ent->GetSoundEmitter() && ent->GetSoundEmitter()->CurrentlyPlaying(); + if (FilterIncluded(FILTER_ACTIVESOUNDS) && !playingSounds) { + continue; + } + if (FilterExcluded(FILTER_ACTIVESOUNDS) && playingSounds) { + continue; + } + } + + if (FilterIsActive(FILTER_CLOSE) || FilterIsActive(FILTER_DISTANT)) { + idVec3 entPos = ent->GetPhysics()->GetOrigin(); + idVec3 toEnt = entPos - playerPosition; + float distsqr = toEnt.LengthSqr(); + if (FilterIncluded(FILTER_CLOSE) && (distsqr > minDistSquared)) { + continue; + } + if (FilterExcluded(FILTER_CLOSE) && (distsqr < minDistSquared)) { + continue; + } + if (FilterIncluded(FILTER_DISTANT) && (distsqr < maxDistSquared)) { + continue; + } + if (FilterExcluded(FILTER_DISTANT) && (distsqr > maxDistSquared)) { + continue; + } + } + + potentialSet.Append(ent); + } +} + +//========================================================================= +// +// hhDebugger::DetermineSelectionSet +// +//========================================================================= +void hhDebugger::DetermineSelectionSet() { + + // Clear selectedEntity if it has been removed +// if (selectedEntity && !gameLocal.entities[selectedEntity->entityNumber]) { +// selectedEntity = NULL; +// } + + if (!GetPointer()) { + return; + } + + idPlayer *player = gameLocal.GetLocalPlayer(); + idVec3 playerPosition = player->GetEyePosition(); + idVec4 color; + idVec3 entPos; + idVec3 toEnt; + idEntity *ent = NULL; + idEntity *bestEnt = NULL; + float bestScore = 0.0f; + + int num = potentialSet.Num(); + for (int ix=0; ixGetPhysics()->GetOrigin(); + toEnt = entPos - playerPosition; + float dist = toEnt.Length(); + float score = (toEnt * (player->viewAngles.ToMat3()[0])) / dist; + if (score > bestScore) { + bestScore = score; + bestEnt = ent; + } + } + + selectedEntity = bestEnt; +} + +//========================================================================= +// +// idListDefaultCompare +// +// Compares two pointers to hhDisplayItem. Used to sort. +//========================================================================= +template<> +ID_INLINE int idListSortCompare( const hhDisplayItem *a, const hhDisplayItem *b ) { + int sortColumn = hhDebugger::sortColumn; + + if (hhDebugger::sortAscending) { + if (a->column[sortColumn].Type() == COLUMNTYPE_ALPHA) { + return ( idStr::Icmp(b->column[sortColumn].String(), a->column[sortColumn].String() ) ); + } + else { // Sort numerically - need to multiply up because we're comparing integers + return (int)(1000.0 * ( a->column[sortColumn].Float() - + b->column[sortColumn].Float() )); // sort by percentage + } + } + else { + if (a->column[sortColumn].Type() == COLUMNTYPE_ALPHA) { + return ( idStr::Icmp(a->column[sortColumn].String(), b->column[sortColumn].String() ) ); + } + else { // Sort numerically - need to multiply up because we're comparing integers + return (int)(1000.0 * ( b->column[sortColumn].Float() - + a->column[sortColumn].Float() )); // sort by percentage + } + } +} + +const char *GetThinkFlags(idEntity *ent) { + static char buffer[20]; + + buffer[0] = 0; + if (ent->thinkFlags & TH_THINK) { + strcat(buffer, "T"); + } + if (ent->thinkFlags & TH_ANIMATE) { + strcat(buffer, "A"); + } + if (ent->thinkFlags & TH_PHYSICS) { + strcat(buffer, "P"); + } + if (ent->thinkFlags & TH_UPDATEVISUALS) { + strcat(buffer, "R"); + } + if (ent->thinkFlags & TH_UPDATEPARTICLES) { + strcat(buffer, "U"); + } + if (ent->thinkFlags & TH_TICKER) { + strcat(buffer, "K"); + } + + return buffer; +} + +//========================================================================= +// +// hhDebugger::FillDisplayList_Stats +// +//========================================================================= +void hhDebugger::FillDisplayList_Stats() { + idEntity *ent; + int slot, ix, num; + float totalThinkMS = 0.0f; + + num = potentialSet.Num(); + for (ix=0; ixGetClassname(); + slot = displayList.AddUnique(item); + } + else { + item.column[0] = ent->name; + slot = displayList.Append(item); // All entity names should be unique but just in case + } + + // Fill columns with appropriate stats + hhDisplayItem *row = &displayList[slot]; + if (GetClassCollapse()) { + //class count dormant (unused) active time % events + row->column[1] += 1; // Count - number in this class + if (ent->fl.isDormant) { // HUMANHEAD JRM - changed to fl.isDormant + row->column[2] += 1; // dormant + } + + // Column 3 is unused + row->column[3] = 0; + + if (ent->IsActive()) { + row->column[4] += 1; // active - number that are active + row->column[5] += ent->thinkMS; // thinkMS + totalThinkMS += ent->thinkMS; // column 6 is percentage + } + row->column[7] += idEvent::NumQueuedEvents(ent); + displayColumnsUsed = 8; + } + else { + //entity entitynum thFlags/active time % events + row->column[1] = ent->entityNumber; + if (ent->fl.isDormant) { // HUMANHEAD JRM - Changed to fl.isDormant + row->column[2] = 1; // dormant + } + + // Column 3 is unused + row->column[3] = 0; + + if (ent->IsActive()) { + row->column[4] = GetThinkFlags(ent); // active - number that are active + row->column[5] = ent->thinkMS; // thinkMS + totalThinkMS += ent->thinkMS; // column 6 is percentage + } + row->column[7] += idEvent::NumQueuedEvents(ent); + displayColumnsUsed = 8; + } + } + + // divide by totalMS to get percentage + num = displayList.Num(); + float recip = totalThinkMS > 0.0f ? (1.0f / totalThinkMS) : 0.0f; + for (ix=0; ixspawnArgs.GetNumKeyVals(); + for (int ix=0; ixspawnArgs.GetKeyVal(ix); + if (!kv->GetKey().IcmpPrefix("editor_")) { + continue; + } + + // Create a row with these two columns + hhDisplayItem item; + item.column[0] = kv->GetKey(); + item.column[1] = kv->GetValue(); + displayList.Append(item); + } + displayColumnsUsed = 2; + } +} + +//========================================================================= +// +// hhDebugger::FillDisplayList_GameInfo +// +//========================================================================= +void hhDebugger::FillDisplayList_GameInfo(int page) { + const idKeyValue *kv; + + if (selectedEntity.IsValid()) { + // Retrieve dictionary of variables from entity + dict.Clear(); + + selectedEntity->FillDebugVars(&dict, page); + + // Create a cleared item + hhDisplayItem item; + + // Fill item with dictionary entries + int num = dict.GetNumKeyVals(); + for (int ix=0; ixGetKey(); + item.column[1] = kv->GetValue(); + displayList.Append(item); + } + + displayColumnsUsed = 2; + } +} + +//========================================================================= +// +// hhDebugger::UpdateGUI_Entity +// +//========================================================================= +void hhDebugger::UpdateGUI_Entity() { + + // Fill display list depending on mode + displayList.Clear(); + displayColumnsUsed = 0; + switch(GetMode()) { + case DEBUGMODE_STATS: FillDisplayList_Stats(); break; + case DEBUGMODE_SPAWNARGS: FillDisplayList_SpawnArgs(); break; + case DEBUGMODE_GAMEINFO1: FillDisplayList_GameInfo(1); break; + case DEBUGMODE_GAMEINFO2: FillDisplayList_GameInfo(2); break; + case DEBUGMODE_GAMEINFO3: FillDisplayList_GameInfo(3); break; + case DEBUGMODE_NONE: break; + } + + // Sort display list based on sortColumn + displayList.Sort(); + + FillGUI(); + gui->StateChanged(gameLocal.time); // Notify the gui that variables have changed +} + +//========================================================================= +// +// hhDebugger::FillGUI +// +//========================================================================= +void hhDebugger::FillGUI() { + + int row,col; + int numrows = displayList.Num(); + int numcols = displayColumnsUsed; + char rowText[1024]; + + for (row=0; rowSetStateString( va("listStats_item_%i", row ), rowText ); + } + else { + gui->DeleteStateVar( va( "listStats_item_%i", row ) ); + } + } +} + +//========================================================================= +// +// hhDebugger::DrawPotentialSet +// +//========================================================================= +void hhDebugger::DrawPotentialSet() { + idVec4 color; + idVec3 entPos; + idEntity *ent; + + if (!GetDrawEntities()) { + return; + } + + // Draw potential entities as crosses + int num = potentialSet.Num(); + for (int ix=0; ixGetPhysics()) { + continue; + } + + // Draw cross at all entities and label + color = ColorForEntity(ent, 0.5f); + entPos = ent->GetPhysics()->GetOrigin(); + + hhUtils::DebugCross(color, entPos, 25); + gameRenderWorld->DrawText(ent->name.c_str(), entPos, 0.15f, color, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1, 0); + } +} + +//========================================================================= +// +// hhDebugger::DrawSelectionSet +// +//========================================================================= +void hhDebugger::DrawSelectionSet() { + if (selectedEntity.IsValid() && selectedEntity->GetPhysics()) { + idVec3 entPos = selectedEntity->GetPhysics()->GetOrigin(); + idVec4 color = ColorForEntity(selectedEntity.GetEntity(), 1.0f); + + // Draw selected entities as highlighted + if (GetMode() != DEBUGMODE_GAMEINFO2) { + hhUtils::DebugCross(color*2, entPos, 10, 0); + } + + gameRenderWorld->DebugBox(color*2, + idBox(selectedEntity->GetPhysics()->GetBounds(), entPos, selectedEntity->GetPhysics()->GetAxis()), 0); + + // Allow entity to do custom debug drawing + switch(GetMode()) { + case DEBUGMODE_GAMEINFO1: + selectedEntity->DrawDebug(1); + break; + case DEBUGMODE_GAMEINFO2: + selectedEntity->DrawDebug(2); + break; + case DEBUGMODE_GAMEINFO3: + selectedEntity->DrawDebug(3); + break; + default: + selectedEntity->DrawDebug(0); + break; + } + } +} + +//========================================================================= +// +// hhDebugger::DrawGUI +// +//========================================================================= +void hhDebugger::DrawGUI() { + PROFILE_SCOPE("Profilers", PROFMASK_NORMAL); + if (gui) { + gui->Redraw(gameLocal.time); + } +} + +//========================================================================= +// +// hhDebugger::UpdateDebugger +// +//========================================================================= +void hhDebugger::UpdateDebugger() { + PROFILE_SCOPE("Profilers", PROFMASK_NORMAL); + + if (!gameLocal.GetLocalPlayer()) { + return; + } + + uiManager->SetDebuggerInteractive(IsInteractive()); + + // HUMANHEAD PCF pdm 05/14/06: Don't initialize/load the gui unless it's actually being used. + if (g_debugger.GetInteger() && !bInitialized) { + Initialize(); + } + + CaptureInput(gui && g_debugger.GetInteger() == 2); + + if (gui && g_debugger.GetInteger()) { + HandleFrameEvents(); + + DeterminePotentialSet(); + DetermineSelectionSet(); + + UpdateGUI_Entity(); + + DrawPotentialSet(); + DrawSelectionSet(); + + DrawGUI(); + + if (IsInteractive()) { + gui->DrawCursor(); + } + } +} + +// This code stolen from GuiFrameEvents() +void hhDebugger::HandleFrameEvents() { + const char *cmd; + sysEvent_t ev; + static int oldMouseX=0; + static int oldMouseY=0; + static int oldButton=0; + static bool oldDown=false; + int newX, newY; + int newButton; + bool buttonDown; + + memset( &ev, 0, sizeof( ev ) ); + + if (bInteractive) { + // fake up a mouse event based on the deltas tracked + // by the async usercmd generation + uiManager->GetMouseState( &newX, &newY, &newButton, &buttonDown ); + if ( newX != oldMouseX || newY != oldMouseY ) { + ev.evType = SE_MOUSE; + ev.evValue = newX - oldMouseX; + ev.evValue2 = newY - oldMouseY; + cmd = gui->HandleEvent( &ev, gameLocal.time ); + if ( cmd && cmd[0] ) { + HandleGuiCommands( cmd ); + } + + oldMouseX = newX; + oldMouseY = newY; + } + + if ( newButton != oldButton || buttonDown != oldDown) { + ev.evType = SE_KEY; + ev.evValue = newButton; + ev.evValue2 = buttonDown; + cmd = gui->HandleEvent( &ev, gameLocal.time ); + if ( cmd && cmd[0] ) { + HandleGuiCommands( cmd ); + } + oldButton = newButton; + oldDown = buttonDown; + } + } + + ev.evType = SE_NONE; + cmd = gui->HandleEvent( &ev, gameLocal.time ); + if ( cmd && cmd[0] ) { + gameLocal.Printf( "frame event returned: '%s'\n", cmd ); + } +} + +#endif //INGAME_DEBUGGER_ENABLED \ No newline at end of file diff --git a/src/Prey/sys_debugger.h b/src/Prey/sys_debugger.h new file mode 100644 index 0000000..03d9dcb --- /dev/null +++ b/src/Prey/sys_debugger.h @@ -0,0 +1,187 @@ +#ifndef __SYS_DEBUGGER_H__ +#define __SYS_DEBUGGER_H__ + +#if INGAME_DEBUGGER_ENABLED + +#define DEBUG_MIN_ENT_DIST 75 // Minimum distance to entity for FILTER_DISTANCE +#define DEBUG_NUM_COLS 10 // Number of cols in debug_columns.gui (max supported) +#define DEBUG_NUM_ROWS 200 // Number of rows in debug_columns.gui (max supported) +#define NUM_DISPLAYLIST_COLUMNS 10 + +class idTypeInfo; + +enum EDebugMode { + DEBUGMODE_NONE=0, + DEBUGMODE_STATS, + DEBUGMODE_SPAWNARGS, + DEBUGMODE_GAMEINFO1, + DEBUGMODE_GAMEINFO2, + DEBUGMODE_GAMEINFO3, + NUM_DEBUGMODES +}; + +enum EColumnDataType { + COLUMNTYPE_ALPHA, + COLUMNTYPE_NUMERIC +}; + +// Filter bitmasks +#define FILTER_NONE 0 +#define FILTER_CLASS 1 +#define FILTER_ACTIVE 2 +#define FILTER_DISTANT 3 +#define FILTER_MONSTERS 4 +#define FILTER_ACTIVESOUNDS 5 +#define FILTER_HASMODEL 6 +#define FILTER_ANIMATING 7 +#define FILTER_EXPENSIVE 8 +#define FILTER_VISIBLE 9 +#define FILTER_CLOSE 10 +#define FILTER_DORMANT 11 +#define NUM_FILTERS 12 + +#define FILTERSTATE_NONE 0 +#define FILTERSTATE_INCLUDED 1 +#define FILTERSTATE_EXCLUDED 2 + +class hhDisplayCell { +public: + hhDisplayCell(); + ~hhDisplayCell(); + + inline void operator=( const idStr &text ); + inline void operator=( const char *text ); + inline void operator=( const int i ); + inline void operator=( const float f ); + inline void operator=( const hhDisplayCell &cell ); + + inline hhDisplayCell & operator+=( const int i ); + inline hhDisplayCell & operator+=( const float f ); + + inline bool IsInt() const; + inline int StringLength(); + inline EColumnDataType Type() const; + inline float Float() const; + inline int Int() const; + const char * String() const; + +private: + inline void AllocateString(); + + idStr *string; + EColumnDataType type; + float value; +}; + + + +class hhDisplayItem { +public: + hhDisplayCell column[NUM_DISPLAYLIST_COLUMNS]; + friend bool operator==( const hhDisplayItem &a, const hhDisplayItem &b ) { + return !idStr::Icmp(a.column[0].String(), b.column[0].String()); + }; + // idList<>::Append() uses operator= on it's objects, so we need to override the + // default memcpy() functionality, with something that constructs a new idStr in hhDisplayCell + // Force idList<> to copy each element individually, so hhDisplayCell::operator=() is used + inline void operator=( const hhDisplayItem &item ) { + for (int ix=0; ix selectedEntity; // Entity currently selected by GUI choice or pointer + + idVec3 xaxis; + idVec3 yaxis; + idVec3 zaxis; + + idTypeInfo *filterClass; // Class to be used when FILTER_CLASS is active + int includeFilters; // Bitmask of current inclusion filters used to cull potentialSet + int excludeFilters; // Bitmask of current exclusion filters used to cull potentialSet + + idList potentialSet; // All entities that pass the filters + idList displayList; + int displayColumnsUsed; // Number of columns of display list actually used + + EDebugMode debugMode; // Mode for debugger display + idUserInterface *gui; + idDict dict; // Scratch pad dictionary, needs to be persistent over + // the time of UpdateDebugger() +}; + + +extern hhDebugger debugger; + +#endif // INGAME_DEBUGGER_ENABLED + +#endif // __SYS_DEBUGGER_H__ diff --git a/src/Prey/sys_preycmds.cpp b/src/Prey/sys_preycmds.cpp new file mode 100644 index 0000000..19bd278 --- /dev/null +++ b/src/Prey/sys_preycmds.cpp @@ -0,0 +1,1121 @@ +// sys_preycmds.cpp +// + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +#if INGAME_PROFILER_ENABLED +void Profiler_ZoomOut_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_TRAVERSEOUT); } +void Profiler_Mode_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_TOGGLEMODE); } +void Profiler_MSMode_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_TOGGLEMS); } +void Profiler_Smooth_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_TOGGLESMOOTHING); } +void Profiler_ToggleCapture_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_TOGGLECAPTURE); } +void Profiler_Toggle_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_TOGGLE); } +void Profiler_Release_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_RELEASE); } +void Profiler_In0_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_IN0); } +void Profiler_In1_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_IN1); } +void Profiler_In2_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_IN2); } +void Profiler_In3_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_IN3); } +void Profiler_In4_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_IN4); } +void Profiler_In5_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_IN5); } +void Profiler_In6_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_IN6); } +void Profiler_In7_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_IN7); } +void Profiler_In8_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_IN8); } +void Profiler_In9_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_IN9); } +void Profiler_In10_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_IN10); } +void Profiler_In11_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_IN11); } +void Profiler_In12_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_IN12); } +void Profiler_In13_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_IN13); } +void Profiler_In14_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_IN14); } +#endif + +void Cmd_GetMapName_f( const idCmdArgs &args ) { + gameLocal.Printf( "%s\n", gameLocal.GetMapName() ); +} + +void Cmd_TestText_f( const idCmdArgs &args ) { + char translated[1024]; + + if ( args.Argc() < 2 ) { + gameLocal.Printf("Usage: testtext \n"); + return; + } + common->FixupKeyTranslations(args.Argv(1), translated, 1024); // No passing idStr between game and engine + gameLocal.Printf("%s\n", translated); +} + +void Cmd_GetBinds_f( const idCmdArgs &args ) { + const char *entityname; + + if ( args.Argc() < 2 ) { + gameLocal.Printf("Usage: getbinds \n"); + return; + } + + if ( !gameLocal.CheatsOk() ) { + return; + } + + // get name of entity + entityname = args.Argv( 1 ); + + // find entity by name or classname + idEntity *master = gameLocal.FindEntity(entityname); + if (master) { + int count = 0; + for (int i = 0; i < gameLocal.num_entities; i++) { + idEntity *ent = gameLocal.entities[i]; + if ( ent && ent->GetBindMaster() == master ) { + gameLocal.Printf(" %s bound (%s)\n", ent->GetName(), ent->GetClassname()); + count++; + } + } + gameLocal.Printf("%d entities bound to %s\n", count, entityname); + } +} + +void Cmd_SpiritWalkMode_f( const idCmdArgs &args ) { + if ( args.Argc() < 2 ) { + gameLocal.Printf("Usage: envirosuit <1|0>\n"); + return; + } + + bool on = atoi(args.Argv( 1 )) != 0; + gameLocal.SpiritWalkSoundMode( on ); +} + +void Cmd_DialogMode_f( const idCmdArgs &args ) { + if ( args.Argc() < 2 ) { + gameLocal.Printf("Usage: dialogmode <1|0>\n"); + return; + } + + bool on = atoi(args.Argv( 1 )) != 0; + idPlayer *player = gameLocal.GetLocalPlayer(); + if (player && player->IsType(hhPlayer::Type)) { + if (on) { + static_cast(player)->DialogStart(0, 1, 1); + } + else { + static_cast(player)->DialogStop(); + } + } +} + +void Cmd_EntitySize_f( const idCmdArgs &args ) { + //TEMP: For memory optimization + gameLocal.Printf("Sizeof(idDict): %d\n", (int)sizeof(idDict)); + gameLocal.Printf("Sizeof(idList): %d\n", 16); + gameLocal.Printf("Sizeof(idMat3): %d\n", (int)sizeof(idMat3)); + gameLocal.Printf("Sizeof(idVec3): %d\n", (int)sizeof(idVec3)); + + gameLocal.Printf("Sizeof(renderEntity_t): %d\n", (int)sizeof(renderEntity_t)); + gameLocal.Printf("Sizeof(refSound_t): %d\n", (int)sizeof(refSound_t)); + gameLocal.Printf("Sizeof(idAnimator): %d\n", (int)sizeof(idAnimator)); + + gameLocal.Printf("Sizeof(idClass): %d\n", (int)sizeof(idClass)); + gameLocal.Printf("sizeof(idEntity): %d\n", (int)sizeof(idEntity)); + gameLocal.Printf("Sizeof(idAI): %d\n", (int)sizeof(idAI)); + gameLocal.Printf("Sizeof(hhPlayer): %d\n", (int)sizeof(hhPlayer)); + + gameLocal.Printf("Number entities: %d\n", gameLocal.num_entities); + gameLocal.Printf("sizeof(idEntity): %d\n", (int)sizeof(idEntity)); + gameLocal.Printf("Total mem cost: %d\n", gameLocal.num_entities * (int)sizeof(idEntity)); +} + +void Cmd_Assert_f( const idCmdArgs &args ) { + assert(0); +} + +void Cmd_Dormant_f( const idCmdArgs &args ) { + const char *classname; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() != 2 ) { + gameLocal.Printf("Usage: dormant \n"); + return; + } + + classname = args.Argv( 1 ); + const idTypeInfo *searchType = idClass::GetClass(classname); + + if (searchType) { + // make all entities of this class: neverdormant + int count = 0; + for (int i = 0; i < gameLocal.num_entities; i++) { + idEntity *ent = gameLocal.entities[i]; + if (ent && ent->IsType(*searchType)) { + ent->fl.neverDormant = false; + count++; + } + } + gameLocal.Printf("%d %s entities unmarked neverdormant\n", count, classname); + } + else { + gameLocal.Printf("class not found, make sure capitalization is correct\n"); + } +} + +void Cmd_UnDormant_f( const idCmdArgs &args ) { + const char *classname; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() != 2 ) { + gameLocal.Printf("Usage: undormant \n"); + return; + } + + classname = args.Argv( 1 ); + const idTypeInfo *searchType = idClass::GetClass(classname); + + if (searchType) { + // make all entities of this class: neverdormant + int count = 0; + for (int i = 0; i < gameLocal.num_entities; i++) { + idEntity *ent = gameLocal.entities[i]; + // if (ent && !idStr::Icmp(ent->GetClassname(), classname)) { + if (ent && ent->IsType(*searchType)) { + ent->fl.neverDormant = true; + count++; + } + } + + gameLocal.Printf("%d %s entities marked neverdormant\n", count, classname); + } + else { + gameLocal.Printf("class not found, make sure capitalization is correct\n"); + } +} + +void Cmd_TestHealthPulse_f( const idCmdArgs &args ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if (player && player->hud) { + player->healthPulse = true; + } +} +void Cmd_TestSpiritPulse_f( const idCmdArgs &args ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if (player && player->hud) { + player->spiritPulse = true; + } +} + +void Cmd_SpawnABunch_f( const idCmdArgs &args ) { + const char *key, *value; + float yaw; + idVec3 org; + idPlayer *player; + idDict dict; + int i; + const char *classname; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk( false ) ) { + return; + } + + if ( args.Argc() < 2 || !args.Argc() & 1 ) { // must always have an even number of arguments + gameLocal.Printf( "usage: spawn classname []\n" ); + return; + } + + int numberToSpawn = atoi(args.Argv(2)); + + classname = args.Argv( 1 ); + dict.Set( "classname", classname ); + + for (int ix=0; ixviewAngles.yaw + gameLocal.random.CRandomFloat()*10; + dict.Set( "angle", va( "%f", yaw + 180 ) ); + org = player->GetPhysics()->GetOrigin() + idAngles( 0, yaw, 0 ).ToForward() * 80 + idVec3( 0, 0, 1 )*(10+gameLocal.random.RandomFloat()*50); + dict.Set( "origin", org.ToString() ); + + for( i = 3; i < args.Argc() - 1; i += 2 ) { + key = args.Argv( i ); + value = args.Argv( i + 1 ); + dict.Set( key, value ); + } + + //HACK + if (!idStr::Icmpn(classname, "movable_", 8)) { + idVec3 vel = idAngles(0,yaw,0).ToForward() * gameLocal.random.RandomFloat()*150; + dict.SetVector("init_velocity", vel); + } + + gameLocal.SpawnEntityDef( dict ); + } +} + +void Cmd_ListDictionary_f( const idCmdArgs &args ) { + const idDict *dict=NULL; + idEntity *ent=NULL; + const char *classname=NULL; + idStr key; + const idKeyValue *kv=NULL; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() < 2 ) { + gameLocal.Printf("Usage: Dict [key]\n"); + return; + } + + // get classname + classname = args.Argv( 1 ); + dict = gameLocal.FindEntityDefDict( classname ); + if (!dict) { + gameLocal.Printf("Unknown entity definition: %s\n", classname); + return; + } + + // handle specific key + if (args.Argc()==3) { + key = args.Argv( 2 ); + + kv = dict->FindKey(key); + if (kv) { + gameLocal.Printf(" %30s %s\n", kv->GetKey().c_str(), kv->GetValue().c_str()); + } + } + else { + int num = dict->GetNumKeyVals(); + for (int ix=0; ixGetKeyVal(ix); + if (kv) { + gameLocal.Printf(" %30s %s\n", kv->GetKey().c_str(), kv->GetValue().c_str()); + } + } + } +} + +// id has a version of this as well, but ours is better +// Trigger all entities matching given name or class +void Cmd_HHTrigger_f( const idCmdArgs &args ) { + idVec3 origin; + idAngles angles; + idPlayer *player; + const char *classname; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() != 2 ) { + gameLocal.Printf( "usage: trigger \n" ); + return; + } + + // get name/class of entity + classname = args.Argv( 1 ); + + // find entity by name or classname + int count=0; + for (int i = 0; i < gameLocal.num_entities; i++) { + idEntity *ent = gameLocal.entities[i]; + if ( ent && (!idStr::Icmp(ent->name, classname) || !idStr::Icmp(ent->GetClassname(), classname)) ) { + ent->Signal( SIG_TRIGGER ); + ent->ProcessEvent( &EV_Activate, player ); + ent->TriggerGuis(); + count++; + } + } + + // failed by name. try by entity number + if ( count == 0 ) { + int entityNumber = atoi(args.Argv( 1 )); + if ( entityNumber < gameLocal.num_entities ) { + idEntity *ent = gameLocal.entities[entityNumber]; + if ( ent ) { + ent->Signal( SIG_TRIGGER ); + ent->ProcessEvent( &EV_Activate, player ); + ent->TriggerGuis(); + } + } + } + + gameLocal.Printf( "Triggered %d entities\n", count ); +} + +// CJRPERSISTENTMERGE: id has a version of this as well, but ours is better +// Remove all of a specified class from the level +void Cmd_HHRemove_f( const idCmdArgs &args ) { + idPlayer *player; + const char *classname; + + if ( args.Argc() < 2 ) { + gameLocal.Printf("Usage: remove \n"); + return; + } + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + // get name/class of entity + classname = args.Argv( 1 ); + + // find entity by name or classname + int count = 0; + for (int i = 0; i < gameLocal.num_entities; i++) { + idEntity *ent = gameLocal.entities[i]; + if ( ent && (!idStr::Icmp(ent->name, classname) || !idStr::Icmp(ent->GetClassname(), classname)) ) { + ent->PostEventMS( &EV_Remove, 0 ); + count++; + } + } + + gameLocal.Printf("%d %s entities removed\n", count, classname); +} + +// Hide all of a specified class or a specific name +void Cmd_Hide_f( const idCmdArgs &args ) { + idPlayer *player; + idEntity *ent = NULL; + const char *classname; + + if ( args.Argc() < 2 ) { + gameLocal.Printf("Usage: hide \n"); + return; + } + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + // get name/class of entity + classname = args.Argv( 1 ); + + // find entity by name or classname + int count = 0; + for (int i = 0; i < gameLocal.num_entities; i++) { + idEntity *ent = gameLocal.entities[i]; + if ( ent && (!idStr::Icmp(ent->name, classname) || !idStr::Icmp(ent->GetClassname(), classname)) ) { + ent->Hide(); + count++; + } + } + + gameLocal.Printf("%d %s entities hidden\n", count, classname); +} + +// Show all of a specified class or a specific name +void Cmd_Show_f( const idCmdArgs &args ) { + idPlayer *player; + idEntity *ent = NULL; + const char *classname; + + if ( args.Argc() < 2 ) { + gameLocal.Printf("Usage: show \n"); + return; + } + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + // get name/class of entity + classname = args.Argv( 1 ); + + // find entity by name or classname + int count = 0; + for (int i = 0; i < gameLocal.num_entities; i++) { + idEntity *ent = gameLocal.entities[i]; + if ( ent && (!idStr::Icmp(ent->name, classname) || !idStr::Icmp(ent->GetClassname(), classname)) ) { + ent->Show(); + count++; + } + } + + gameLocal.Printf("%d %s entities shown\n", count, classname); +} + +/* +============ +Cmd_SpawnDebrisMass_f +============ +*/ +void Cmd_SpawnDebrisMass_f(const idCmdArgs &args) { + const char *temp; + + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() < 2 ) { + gameLocal.Printf( "Usage: SpawnDebrisMass entityDef\n" ); + return; + } + + temp = args.Argv( 1 ); + + hhUtils::SpawnDebrisMass( temp, vec3_origin ); + + +} + +void Cmd_SetPlayerGravity_f( const idCmdArgs &args ) { + idVec3 gravDirection; + idStr temp; + hhPlayer *player; + int argIndex = 0; + + if ( args.Argc() < 3 ) { + gameLocal.Printf("Usage: SetPlayerGravity \n"); + return; + } + + player = static_cast( gameLocal.GetLocalPlayer() ); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + for( int ix = 0; ix < 3; ++ix ) { + temp = args.Argv( ++argIndex ); + if( temp.Icmp("-") == 0 ) { + temp = args.Argv( ++argIndex ); + gravDirection[ix] = -1 * atof( temp.c_str() ); + } else { + gravDirection[ix] = atof( temp.c_str() ); + } + } + + if( player->GetPhysics() ) { + player->OrientToGravity( true ); + player->GetPhysics()->SetGravity( gravDirection ); + } +} + + +//========================================================================= +// Cmd_ClosePortal_f +// +// Closes a portal if it intersects my current bounds (for testing) +//========================================================================= +void Cmd_ClosePortal_f(const idCmdArgs &args) { + if (!gameLocal.CheatsOk()) { + return; + } + idPlayer *player = gameLocal.GetLocalPlayer(); + qhandle_t areaPortal = gameRenderWorld->FindPortal( player->GetPhysics()->GetAbsBounds() ); + if ( areaPortal ) { + gameLocal.SetPortalState( areaPortal, PS_BLOCK_ALL ); + gameLocal.Printf("Portal closed\n"); + } +} + +//========================================================================= +// Cmd_OpenPortal_f +// +// Opens a portal if it intersects my current bounds (for testing) +//========================================================================= +void Cmd_OpenPortal_f(const idCmdArgs &args) { + if (!gameLocal.CheatsOk()) { + return; + } + idPlayer *player = gameLocal.GetLocalPlayer(); + qhandle_t areaPortal = gameRenderWorld->FindPortal( player->GetPhysics()->GetAbsBounds() ); + if ( areaPortal ) { + gameLocal.SetPortalState( areaPortal, PS_BLOCK_NONE ); + gameLocal.Printf("Portal opened\n"); + } +} + +//========================================================================= +// +// Cmd_CallScriptFunc_f +// +//========================================================================= +void Cmd_CallScriptFunc_f( const idCmdArgs &args ) { + if( !gameLocal.CheatsOk() ) { + return; + } + + if( args.Argc() <= 1 ) { + gameLocal.Printf( "Usage: call