as released 2006-10-09

This commit is contained in:
archive 2006-10-09 00:00:00 +00:00
commit 0d7517fbb2
668 changed files with 382447 additions and 0 deletions

58
EULA.Development Kit.rtf Normal file
View File

@ -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
}

BIN
ReadMe.rtf Normal file

Binary file not shown.

View File

@ -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/ )

View File

@ -0,0 +1 @@
My Mod!

Binary file not shown.

View File

@ -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/ )

View File

@ -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/ )

File diff suppressed because it is too large Load Diff

View File

@ -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<int> 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<idVec3> 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<float> 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<idVec3> 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<hhDock> 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__ */

Binary file not shown.

3722
src/2005MayaImport.vcproj Normal file

File diff suppressed because it is too large Load Diff

6529
src/2005game.vcproj Normal file

File diff suppressed because it is too large Load Diff

1124
src/2005idlib.vcproj Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,47 @@
#ifdef _WIN32
#define _BOOL
#include <maya/MStatus.h>
#include <maya/MString.h>
#include <maya/MFileIO.h>
#include <maya/MLibrary.h>
#include <maya/MPoint.h>
#include <maya/MVector.h>
#include <maya/MMatrix.h>
#include <maya/MTransformationMatrix.h>
#include <maya/MEulerRotation.h>
#include <maya/MObject.h>
#include <maya/MArgList.h>
#include <maya/MGlobal.h>
#include <maya/MDagPath.h>
#include <maya/MFnDagNode.h>
#include <maya/MItDag.h>
#include <maya/MTime.h>
#include <maya/MAnimControl.h>
#include <maya/MFnGeometryFilter.h>
#include <maya/MFnSet.h>
#include <maya/MSelectionList.h>
#include <maya/MFloatArray.h>
#include <maya/MFnWeightGeometryFilter.h>
#include <maya/MFnSkinCluster.h>
#include <maya/MItDependencyNodes.h>
#include <maya/MFnMesh.h>
#include <maya/MDagPathArray.h>
#include <maya/MItGeometry.h>
#include <maya/MPlugArray.h>
#include <maya/MPlug.h>
#include <maya/MFloatPointArray.h>
#include <maya/MFnAttribute.h>
#include <maya/MFnMatrixData.h>
#include <maya/MItDependencyGraph.h>
#include <maya/MItMeshPolygon.h>
#include <maya/MFnTransform.h>
#include <maya/MQuaternion.h>
#include <maya/MFnCamera.h>
#include <maya/MFloatMatrix.h>
#include <maya/MFnEnumAttribute.h>
#undef _BOOL
#endif // _WIN32

View File

@ -0,0 +1,47 @@
#ifdef _WIN32
#define _BOOL
#include <maya/MStatus.h>
#include <maya/MString.h>
#include <maya/MFileIO.h>
#include <maya/MLibrary.h>
#include <maya/MPoint.h>
#include <maya/MVector.h>
#include <maya/MMatrix.h>
#include <maya/MTransformationMatrix.h>
#include <maya/MEulerRotation.h>
#include <maya/MObject.h>
#include <maya/MArgList.h>
#include <maya/MGlobal.h>
#include <maya/MDagPath.h>
#include <maya/MFnDagNode.h>
#include <maya/MItDag.h>
#include <maya/MTime.h>
#include <maya/MAnimControl.h>
#include <maya/MFnGeometryFilter.h>
#include <maya/MFnSet.h>
#include <maya/MSelectionList.h>
#include <maya/MFloatArray.h>
#include <maya/MFnWeightGeometryFilter.h>
#include <maya/MFnSkinCluster.h>
#include <maya/MItDependencyNodes.h>
#include <maya/MFnMesh.h>
#include <maya/MDagPathArray.h>
#include <maya/MItGeometry.h>
#include <maya/MPlugArray.h>
#include <maya/MPlug.h>
#include <maya/MFloatPointArray.h>
#include <maya/MFnAttribute.h>
#include <maya/MFnMatrixData.h>
#include <maya/MItDependencyGraph.h>
#include <maya/MItMeshPolygon.h>
#include <maya/MFnTransform.h>
#include <maya/MQuaternion.h>
#include <maya/MFnCamera.h>
#include <maya/MFloatMatrix.h>
#include <maya/MFnEnumAttribute.h>
#undef _BOOL
#endif // _WIN32

434
src/MayaImport/exporter.h Normal file
View File

@ -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<idNamePair> renamejoints;
idList<idNamePair> remapjoints;
idStrList keepjoints;
idStrList skipmeshes;
idStrList keepmeshes;
idList<idAnimGroup *> exportgroups;
idList<idAnimGroup> 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<idExportJoint> mayaNode;
idHierarchy<idExportJoint> 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<exportVertex_t> verts;
idList<exportTriangle_t> tris;
idList<exportWeight_t> weights;
idList<exportUV_t> uv;
idExportMesh() { keep = true; };
void ShareVerts( void );
void GetBounds( idBounds &bounds ) const;
void Merge( idExportMesh *mesh );
};
/*
==============================================================================
idExportModel
==============================================================================
*/
class idExportModel {
public:
idExportJoint *exportOrigin;
idList<idExportJoint> joints;
idHierarchy<idExportJoint> mayaHead;
idHierarchy<idExportJoint> exportHead;
idList<int> cameraCuts;
idList<cameraFrame_t> camera;
idList<idBounds> bounds;
idList<jointFrame_t> jointFrames;
idList<jointFrame_t *> frames;
int frameRate;
int numFrames;
int skipjoints;
int export_joints;
idList<idExportMesh *> 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<idNamePair> &renamejoints, idStr &prefix );
bool RemapParents( idList<idNamePair> &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 );
};

View File

@ -0,0 +1,47 @@
#ifdef _WIN32
#define _BOOL
#include <maya/MStatus.h>
#include <maya/MString.h>
#include <maya/MFileIO.h>
#include <maya/MLibrary.h>
#include <maya/MPoint.h>
#include <maya/MVector.h>
#include <maya/MMatrix.h>
#include <maya/MTransformationMatrix.h>
#include <maya/MEulerRotation.h>
#include <maya/MObject.h>
#include <maya/MArgList.h>
#include <maya/MGlobal.h>
#include <maya/MDagPath.h>
#include <maya/MFnDagNode.h>
#include <maya/MItDag.h>
#include <maya/MTime.h>
#include <maya/MAnimControl.h>
#include <maya\MFnGeometryFilter.h>
#include <maya\MFnSet.h>
#include <maya\MSelectionList.h>
#include <maya\MFloatArray.h>
#include <maya\MFnWeightGeometryFilter.h>
#include <maya\MFnSkinCluster.h>
#include <maya\MItDependencyNodes.h>
#include <maya\MFnMesh.h>
#include <maya\MDagPathArray.h>
#include <maya\MItGeometry.h>
#include <maya\MPlugArray.h>
#include <maya\MPlug.h>
#include <maya\MFloatPointArray.h>
#include <maya\MFnAttribute.h>
#include <maya\MFnMatrixData.h>
#include <maya/MItDependencyGraph.h>
#include <maya/MItMeshPolygon.h>
#include <maya/MFnTransform.h>
#include <maya/MQuaternion.h>
#include <maya/MFnCamera.h>
#include <maya/MFloatMatrix.h>
#include <maya/MFnEnumAttribute.h>
#undef _BOOL
#endif // _WIN32

3152
src/MayaImport/maya_main.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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__ */

View File

@ -0,0 +1,4 @@
EXPORTS
dllEntry
Maya_ConvertModel
Maya_Shutdown

38
src/PREY.sln Normal file
View File

@ -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

268
src/Prey/ai_Navigator.cpp Normal file
View File

@ -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<hhAI *>(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 *)
*/

41
src/Prey/ai_Navigator.h Normal file
View File

@ -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__ */

529
src/Prey/ai_centurion.cpp Normal file
View File

@ -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("<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<idEntity> 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<idStr>* 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<idEntity> 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<idAI *> (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<idAI *> (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<idActor *> ( other ) );
}
AI_PUSHED = true;
}
#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build

84
src/Prey/ai_centurion.h Normal file
View File

@ -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<idStr>* 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<idStr>* 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<idEntity> armchop_Target;
idEntityPtr<idEntity> 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<idEntity> > obstacles;
#endif
};
#endif //__PREY_AI_CENTURION_H__

115
src/Prey/ai_crawler.cpp Normal file
View File

@ -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<hhPlayer *>(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<hhItem*>(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;
}

24
src/Prey/ai_crawler.h Normal file
View File

@ -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__

1669
src/Prey/ai_creaturex.cpp Normal file

File diff suppressed because it is too large Load Diff

139
src/Prey/ai_creaturex.h Normal file
View File

@ -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<hhBeamSystem> laserRight;
idEntityPtr<hhBeamSystem> laserLeft;
idEntityPtr<hhBeamSystem> preLaserRight;
idEntityPtr<hhBeamSystem> preLaserLeft;
idList< idEntityPtr<hhBeamSystem> > leftBeamList;
idList< idEntityPtr<hhBeamSystem> > rightBeamList;
idEntityPtr<hhBeamSystem> leftRechargeBeam;
idEntityPtr<hhBeamSystem> rightRechargeBeam;
idEntityPtr<hhBeamSystem> leftDamageBeam;
idEntityPtr<hhBeamSystem> rightDamageBeam;
idEntityPtr<hhBeamSystem> leftRetractBeam;
idEntityPtr<hhBeamSystem> rightRetractBeam;
idEntityPtr<hhEntityFx> muzzleLeftFx;
idEntityPtr<hhEntityFx> muzzleRightFx;
idEntityPtr<hhEntityFx> impactLeftFx;
idEntityPtr<hhEntityFx> impactRightFx;
idEntityPtr<hhEntityFx> rechargeLeftFx;
idEntityPtr<hhEntityFx> rechargeRightFx;
idEntityPtr<hhEntityFx> retractLeftFx;
idEntityPtr<hhEntityFx> 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<hhMonsterAI> leftRecharger;
idEntityPtr<hhMonsterAI> rightRecharger;
bool bScripted;
#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
};
#endif /* __PREY_AI_CREATURE_H__ */

660
src/Prey/ai_droid.cpp Normal file
View File

@ -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("<endZipBeams>");
const idEventDef EV_HealerReset("<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

73
src/Prey/ai_droid.h Normal file
View File

@ -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<hhBeamSystem> > beamBurstList;
idEntityPtr<hhBeamSystem> beamZip;
idEntityPtr<idEntity> staticPoint;
idEntityPtr<hhProjectileTracking> 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

View File

@ -0,0 +1,801 @@
#include "../idlib/precompiled.h"
#pragma hdrstop
#include "prey_local.h"
const idEventDef EV_AcidBlast("acidBlast");
const idEventDef EV_AcidDrip("<acidDrip>");
const idEventDef EV_DeathCloud("<deathCloud>");
const idEventDef EV_LaunchPod("launchPod");
const idEventDef EV_NewPod("<newPod>", "e");
const idEventDef EV_SpawnBlastDebris("<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("<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( "<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<hhBindController *>( 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<hhPod>(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<hhPlayer *> (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<hhSpiritProxy *> (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<const idActor *>( ent );
if( actor && actor->IsType(hhDeathProxy::Type) ) {
return ATTACK_IGNORE;
}
if ( ent->IsType( hhMonsterAI::Type ) ) {
const hhMonsterAI *entAI = static_cast<const hhMonsterAI *>( 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

View File

@ -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<hhPod> > podList;
idScriptInt AI_PODCOUNT;
idScriptBool AI_CHARGEDONE;
idScriptBool AI_SWOOP;
idScriptInt AI_DODGEDAMAGE;
int nextWoundTime;
idEntityPtr<hhBindController> bindController; // Bind controller for tractor beam
#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
};
#endif

View File

@ -0,0 +1,838 @@
#include "../idlib/precompiled.h"
#pragma hdrstop
#include "prey_local.h"
const idEventDef MA_AntiProjectileAttack("<antiProjectileAttack>", "e");
const idEventDef MA_StartPreDeath("<startPreDeath>" );
const idEventDef MA_HandlePassageway("<handlePassageway>", NULL);
const idEventDef MA_EnterPassageway("<enterPassageway>", "e",NULL);
const idEventDef MA_ExitPassageway("<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; i<numProj; i++) {
projectile = hhProjectile::SpawnProjectile( projectileDef );
HH_ASSERT( projectile );
//bjk: uses random now
float ang = spread * hhMath::Sqrt( gameLocal.random.RandomFloat() );
float spin = hhMath::TWO_PI * gameLocal.random.RandomFloat();
dir = muzzleAxis[ 0 ] + muzzleAxis[ 2 ] * ( hhMath::Sin(ang) * hhMath::Sin(spin) ) - muzzleAxis[ 1 ] * ( hhMath::Sin(ang+yaw) * hhMath::Cos(spin) );
dir.Normalize();
projectile->Create(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<targets.Num();i++)
{
if(!targets[i].GetEntity()) {
continue;
}
idStr key("target");
if(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<hhMonsterAI*>(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<targets.Num();i++)
{
if(!targets[i].GetEntity())
continue;
idStr key("target");
if(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<hhHarvesterSimple*>(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<idEntity> (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;i<currPassageway->targets.Num();i++) {
if(currPassageway->targets[i] != NULL && currPassageway->targets[i].GetEntity()->IsType(hhAIPassageway::Type)) {
if (isRandom) {
possibleExits[count++] = static_cast<hhAIPassageway*>(currPassageway->targets[i].GetEntity());
} else {
tmp = static_cast<hhAIPassageway*>(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<hhAIPassageway *> (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;i<count;i++) {
// We'll have to try again later when the coast is clear
if(ents[i] && ents[i] != this && !ents[i]->IsHidden() && (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<idStr>* 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<hhAIPassageway *> (gameLocal.FindEntityOfType( hhAIPassageway::Type, NULL ));
while (passage) {
dist = (passage->GetOrigin() - GetOrigin()).Length();
if (dist < nearDist) {
nearDist = dist;
nearest = passage;
}
passage = reinterpret_cast<hhAIPassageway *> (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

View File

@ -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<idStr>* 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<idStr>* parmList);
virtual void Event_Activate(idEntity *activator);
virtual void Event_CanBecomeSolid(void);
int lastAntiProjectileAttack;
int lastPassagewayTime;
idEntityPtr<idEntity> lastPassageway;
idEntityPtr<idEntity> nextPassageway;
idScriptFloat AI_CLIMB_RANGE;
idScriptFloat AI_PASSAGEWAY_HEALTH;
idEntityPtr<hhEntityFx> fxSmoke[MAX_HARVESTER_LEGS];
int passageCount;
bool bSmokes;
bool bGibOnDeath;
#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
};
#endif

File diff suppressed because it is too large Load Diff

128
src/Prey/ai_hunter_simple.h Normal file
View File

@ -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<hhMonsterAI> ally;
idScriptBool AI_ALLOW_ORDERS;
idScriptBool AI_SHOTBLOCK;
protected:
idVec3 nextMovePos;
idEntityPtr<hhBeamSystem> beamLaser;
idAngles kickAngles;
idAngles kickSpeed;
idEntityPtr<hhEntityFx> sniperFx;
idEntityPtr<hhEntityFx> muzzleFx;
idEntityPtr<hhEntityFx> 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<idEntity> enemyPortal;
idVec3 lastMoveOrigin;
int nextBlockCheckTime;
int nextSpiritCheck; //HUMANHEAD jsh PCF 5/2/06 hunter combat fixes
};
#endif /* __PREY_AI_HUNTER_SIMPLE_H__ */

136
src/Prey/ai_inspector.cpp Normal file
View File

@ -0,0 +1,136 @@
#include "../idlib/precompiled.h"
#pragma hdrstop
#include "prey_local.h"
const idEventDef MA_CheckReactions( "<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<hhAIInspector*>(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<hhAIInspector*>(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 );
}

28
src/Prey/ai_inspector.h Normal file
View File

@ -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<idEntity> checkReaction;
};
#endif

View File

@ -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("<dodgeProjectile>", "e");
const idEventDef MA_AppendHoverFx( "<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;i<mineList.Num();i++ ) {
if ( mineList[i].IsValid() ) {
trace_t collision;
memset( &collision, 0, sizeof( collision ) );
collision.endAxis = mineList[i]->GetPhysics()->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;i<ThrustSide_Total;i++) {
fxThrusters[ThrustType_Idle][i] = NULL;
fxThrusters[ThrustType_Hover][i] = NULL;
}
if ( !spawnArgs.GetBool( "bAlwaysGib" ) && !spawnArgs.GetBool( "no_pre_death" ) ) {
allowPreDeath = true;
} else {
allowPreDeath = false;
}
freezeDamage = false;
specialDamage = false;
Event_CreateJetFx();
//give random model offset
float offset_min = spawnArgs.GetFloat( "offset_z_min", "0" );
float offset_max = spawnArgs.GetFloat( "offset_z_max", "0" );
modelOffset.z = gameLocal.random.RandomFloat() * (offset_max-offset_min) + offset_min;
walkIK.Init( this, IK_ANIM, modelOffset );
}
void hhJetpackHarvesterSimple::Event_RemoveJetFx(void) {
for(int i=0;i<ThrustSide_Total;i++) {
SAFE_REMOVE(fxThrusters[ThrustType_Idle][i]);
SAFE_REMOVE(fxThrusters[ThrustType_Hover][i]);
}
}
void hhJetpackHarvesterSimple::Event_CreateJetFx(void) {
const char *bones[ThrustSide_Total];
bones[ThrustSide_Left] = spawnArgs.GetString( "bone_thrust_left" );
bones[ThrustSide_Right] = spawnArgs.GetString( "bone_thrust_right" );
const char *psystem = spawnArgs.GetString( "fx_thruster_idle" );
if ( psystem && *psystem ) {
for(int i=0;i<ThrustSide_Total;i++) {
hhFxInfo fxInfo;
idVec3 bonePos;
idMat3 boneAxis;
this->GetJointWorldTransform( 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<ThrustSide_Total;i++) {
hhFxInfo fxInfo;
idVec3 bonePos;
idMat3 boneAxis;
GetJointWorldTransform( bones[i], bonePos, boneAxis );
fxInfo.SetEntity( this );
fxInfo.SetBindBone( bones[i] );
fxInfo.Toggle();
fxInfo.RemoveWhenDone( false );
if( fxThrusters[ThrustType_Hover][i] != NULL ) {
gameLocal.Warning("Jetpack harvester already had flamejet made.");
}
BroadcastFxInfo( psystem, bonePos, boneAxis, &fxInfo, &MA_AppendHoverFx );
}
}
}
void hhJetpackHarvesterSimple::Event_AppendToThrusterList( hhEntityFx* fx ) {
for( int ix = 0; ix < ThrustSide_Total; ++ix ) {
if( fxThrusters[ThrustType_Idle][ix] != NULL ) {
continue;
}
fxThrusters[ThrustType_Idle][ix] = fx;
return;
}
}
void hhJetpackHarvesterSimple::Event_AppendHoverFx( hhEntityFx* fx ) {
for( int ix = 0; ix < ThrustSide_Total; ++ix ) {
if( fxThrusters[ThrustType_Hover][ix] != NULL ) {
continue;
}
fxThrusters[ThrustType_Hover][ix] = fx;
return;
}
}
void hhJetpackHarvesterSimple::Event_LaunchMine( const idList<idStr>* 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<hhHarvesterMine*>( gameLocal.SpawnObject(projectileDefName) );
mine->Create( this, jointPos, jointAxis );
mine->Launch( jointPos, jointAxis, physicsObj.GetPushedLinearVelocity() );
}
void hhJetpackHarvesterSimple::Hide( void ) {
hhMonsterAI::Hide();
for(int i=0;i<ThrustSide_Total;i++) {
if(fxThrusters[ThrustType_Idle][i].GetEntity()) {
fxThrusters[ThrustType_Idle][i]->Nozzle(FALSE);
}
if(fxThrusters[ThrustType_Hover][i].GetEntity()) {
fxThrusters[ThrustType_Hover][i]->Nozzle(FALSE);
}
}
}
void hhJetpackHarvesterSimple::Show( void ) {
hhMonsterAI::Show();
for(int i=0;i<ThrustSide_Total;i++) {
if(fxThrusters[ThrustType_Idle][i].GetEntity()) {
fxThrusters[ThrustType_Idle][i]->Nozzle(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<hhProjectile> &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;i<mineList.Num();i++ ) {
if ( !mineList[i].IsValid() || mineList[i].GetEntity()->health <= 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;i<ThrustSide_Total;i++) {
SAFE_REMOVE(fxThrusters[ThrustType_Idle][i]);
SAFE_REMOVE(fxThrusters[ThrustType_Hover][i]);
}
if ( !frozen && allowPreDeath && !AI_BOUND ) {
allowPreDeath = false;
trace_t TraceInfo;
gameLocal.clip.TracePoint(TraceInfo, GetOrigin(), GetOrigin() + GetPhysics()->GetGravityNormal() * 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;i<mineList.Num();i++ ) {
if ( mineList[i].IsValid() ) {
trace_t collision;
memset( &collision, 0, sizeof( collision ) );
collision.endAxis = mineList[i]->GetPhysics()->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

View File

@ -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<idStr>* 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<idStr>* 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<hhEntityFx> fxThrusters[ThrustType_Total][ThrustSide_Total];
int lastAntiProjectileAttack;
bool allowPreDeath;
idList< idEntityPtr<hhProjectile> > mineList;
bool freezeDamage;
bool specialDamage;
#endif
};
#endif

View File

@ -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( "<assignShieldFx>", "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( "<assignHeadFx>", "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<hhMoveable*>(ent);
if ( throwMoveable ) {
throwMoveable->Event_Unhover();
}
}
}
void hhKeeperSimple::Spawn( void ) {
beamAttack = idEntityPtr<hhBeamSystem> ( 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> (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<hhMoveable*>(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<hhMoveable*>(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<idEntity*> 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;i<numHit;i++) {
if(entList[i] && !entList[i]->IsHidden() && (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<idStr> xferKeys;
idList<idStr> 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<hhMoveable*>(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

View File

@ -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<hhBeamSystem> beamTelepathic; // Beam used when we are telepathically controlling an object
idEntityPtr<hhBeamSystem> beamAttack;
idEntityPtr<hhEntityFx> shieldFx;
idEntityPtr<hhEntityFx> headFx;
idEntityPtr<idEntity> triggerEntity;
idEntityPtr<idEntity> throwEntity;
int nextShieldImpact;
float shieldAlpha;
bool bThrowing;
#endif
};
#endif

63
src/Prey/ai_mutate.cpp Normal file
View File

@ -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

15
src/Prey/ai_mutate.h Normal file
View File

@ -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

View File

@ -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("<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<hhMonsterAI *>(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<idActor*>(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<const idActor *>( 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<const idActor *>( ent );
if ( actor->IsType( idPlayer::Type ) && static_cast<const idPlayer *>(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<const hhPlayer*>( 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 );
}

View File

@ -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__

144
src/Prey/ai_passageway.cpp Normal file
View File

@ -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<hhMonsterAI*>(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

49
src/Prey/ai_passageway.h Normal file
View File

@ -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<idAI> 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

View File

@ -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;
}

View File

@ -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<hhSpiritProxy> possessedProxy;
int nextDrop;
};
#endif //__PREY_AI_POSSESSED_TOMMY_H__

639
src/Prey/ai_reaction.cpp Normal file
View File

@ -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<Cause_Total;i++) {
if(!CauseToStr((Cause)i).Icmp(str)) {
return (Cause)i;
}
}
return Cause_Invalid;
}
//
// StrToEffect()
//
hhReactionDesc::Effect hhReactionDesc::StrToEffect(const char* str) {
for(int i=0;i<Effect_Total;i++)
{
if(!EffectToStr((Effect)i).Icmp(str))
return (Effect)i;
}
return Effect_Invalid;
}
//
// Set()
//
void hhReactionDesc::Set(const idDict &keys) {
cause = StrToCause(keys.GetString("cause"));
effect = StrToEffect(keys.GetString("effect"));
flags = 0;
effectRadius = keys.GetFloat("effect_radius","-1.0f");
effectMinRadius = keys.GetFloat("effect_min_radius","0.0f");
if(keys.GetBool("snap_to_point")) {
flags |= flag_SnapToPoint;
}
if(keys.GetBool("effect_all_players")) {
flags |= flag_EffectAllPlayers;
}
if(keys.GetBool("effect_all_monsters")) {
flags |= flag_EffectAllMonsters;
}
if(keys.GetBool("req_novehicle")) {
flags |= flagReq_NoVehicle;
}
if(keys.GetBool("exclusive")) {
flags |= flag_Exclusive;
}
if(keys.GetBool("effect_listener")) {
flags |= flag_EffectListener;
}
if(keys.GetBool("anim_face_cause_dir")) {
flags |= flag_AnimFaceCauseDir;
}
if(keys.GetBool("anim_trigger_cause")) {
flags |= flag_AnimTriggerCause;
}
idStr keyString("");
if(keys.GetString("req_key", "", keyString)) {
if(keyString.Length() > 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<hhReactionVolume*>(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<hhReactionVolume*>(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<int &> (effect));
savefile->ReadInt(reinterpret_cast<int &> (cause));
savefile->ReadInt(reinterpret_cast<int &> (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<idClass *&> (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<idClass *&> (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<numEnts;i++) {
if ( entityList[i] == ent ) {
return true;
}
}
return false;
}
//
// GetValidEntitiesInside()
//
void hhReactionVolume::GetValidEntitiesInside(idList<idEntity*> &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;i<numEnts;i++) {
if(!entityList[i] || entityList[i]->IsHidden())
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;i<reactionDescs.Num();i++) {
if(strcmp(reactionDescs[i]->name.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;i<dict.GetNumKeyVals();i++) {
kv = dict.GetKeyVal(i);
idStr key;
key = kv->GetKey();
// 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);
}
}

261
src/Prey/ai_reaction.h Normal file
View File

@ -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<idEntity> 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<idEntity*> &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("<undefined>");
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<hhReactionVolume*> 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<hhReactionVolume*> 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 <desc> to <causeEntity> to get the effect in <desc> applied to <effectEntity>
//
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<idEntity> 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<idEntity> causeEntity; // The entity to apply the cause to
idList<hhReactionTarget> 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<hhReactionDesc*> reactionDescs;
};
#endif

227
src/Prey/ai_spawncase.cpp Normal file
View File

@ -0,0 +1,227 @@
#include "../idlib/precompiled.h"
#pragma hdrstop
#include "prey_local.h"
//
// hhAISpawnCase
//
const idEventDef EV_CreateAI( "<createAI>", NULL);
const idEventDef EV_AutoTrigger( "<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<idAI*>(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);
}
}

34
src/Prey/ai_spawncase.h Normal file
View File

@ -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<idAI> 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

403
src/Prey/ai_speech.cpp Normal file
View File

@ -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;i<SpeechPoolSize;i++) {
speechPool[i].Clear();
}
};
//
// getFreeSpeech()
//
hhAISpeech* hhAISpeechHandler::GetFreeSpeech()
{
for(int i=0;i<SpeechPoolSize;i++) {
if(speechPool[i].IsCleared())
return &speechPool[i];
}
return NULL;
};
//
// AddSpeechToQueue()
//
void hhAISpeechHandler::AddSpeechToQueue(hhAISpeech *speech)
{
// First entry?
if(!firstSpeech || !lastSpeech)
{
// this means empty list, so they both better be empty
HH_ASSERT(lastSpeech == NULL);
HH_ASSERT(firstSpeech == NULL);
speech->next = 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<TT_Total;i++)
{
if(potential.tokens[i] == desired.tokens[i])
rating += TokenMatchWeight[i];
else if(potential.tokens[i] == TokenWildCard)
rating += TokenWildcardMatchWeight[i];
}
return rating;
};
//
// ExtractSpeechTokens()
//
bool hhAISpeechHandler::ExtractSpeechTokens(idStr speechDesc, SpeechTokens &outTokens)
{
int firstBreak = speechDesc.Find('_');
if(firstBreak < 0)
return FALSE;
int lastBreak = speechDesc.Find("_", true, firstBreak+1);
if(lastBreak < 0)
return FALSE;
outTokens.tokens[TT_Desire] = speechDesc.Mid(0, firstBreak-1);
outTokens.tokens[TT_How] = speechDesc.Mid(firstBreak, lastBreak-(firstBreak));
outTokens.tokens[TT_What] = speechDesc.Mid(lastBreak+1, (speechDesc.Length()-1) - lastBreak);
int findFreqBreak = outTokens.tokens[TT_What].Find(FreqToken);
if(findFreqBreak >= 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<idClass *&> (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

102
src/Prey/ai_speech.h Normal file
View File

@ -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

533
src/Prey/ai_sphereboss.cpp Normal file
View File

@ -0,0 +1,533 @@
#include "../idlib/precompiled.h"
#pragma hdrstop
#include "prey_local.h"
const idEventDef EV_UpdateTarget( "<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<idEntity*> 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;i<lastTargetPos.Num(); i++) {
savefile->WriteVec3( 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;i<num; i++) {
savefile->ReadVec3( 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

45
src/Prey/ai_sphereboss.h Normal file
View File

@ -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<idVec3> lastTargetPos;
int lastNodeIndex;
int nextShieldImpact;
#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
};
#endif

104
src/Prey/anim_baseanim.cpp Normal file
View File

@ -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 );
}

29
src/Prey/anim_baseanim.h Normal file
View File

@ -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__ */

173
src/Prey/force_converge.cpp Normal file
View File

@ -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;
}

44
src/Prey/force_converge.h Normal file
View File

@ -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<idEntity> ent; // Entity to apply forces to
idEntityPtr<idEntity> 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

100
src/Prey/game_afs.cpp Normal file
View File

@ -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();
}

49
src/Prey/game_afs.h Normal file
View File

@ -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__

76
src/Prey/game_alarm.cpp Normal file
View File

@ -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();
}
}

21
src/Prey/game_alarm.h Normal file
View File

@ -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

244
src/Prey/game_anim.cpp Normal file
View File

@ -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<idStr>;
}
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<idStr>;
}
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;
}

23
src/Prey/game_anim.h Normal file
View File

@ -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__ */

276
src/Prey/game_animBlend.cpp Normal file
View File

@ -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 );
}

View File

@ -0,0 +1,6 @@
#ifndef __PREY_GAME_ANIMBLEND_H__
#define __PREY_GAME_ANIMBLEND_H__
#endif /* __PREY_GAME_ANIMBLEND_H__ */

View File

@ -0,0 +1,287 @@
#include "../idlib/precompiled.h"
#pragma hdrstop
#include "prey_local.h"
const idEventDef EV_PlayCycle( "<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();
}

View File

@ -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<idEntity> passenger;
int spawnTime;
bool hadPassenger;
idVec3 deltaScale; //scale anim delta movement by this
};
#endif /* __PREY_GAME_ANIMDRIVEN_H__ */

View File

@ -0,0 +1,424 @@
#include "../idlib/precompiled.h"
#pragma hdrstop
#include "prey_local.h"
const idEventDef EV_CheckCycleRotate( "<checkCycleRotate>" );
const idEventDef EV_CheckThaw( "<checkThaw>" );
const idEventDef EV_SpawnFxAlongBone( "spawnFXAlongBone", "d" );
const idEventDef EV_Silent( "<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<idStr>* 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<idActor*>(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 );
}
}

View File

@ -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<jawFlapInfo_t> 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<idStr>* fxParms );
};
#endif

View File

@ -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<float>
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<float>
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<idClass *&>( 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();
}

View File

@ -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<float> guiScale;
idEntity * attachedConsole;
};
#endif /* __GAME_ANIMATEDGUI_H__ */

251
src/Prey/game_animator.cpp Normal file
View File

@ -0,0 +1,251 @@
//**************************************************************************
//**
//** hhAnimated
//**
//**************************************************************************
// HEADER FILES ------------------------------------------------------------
#include "../idlib/precompiled.h"
#pragma hdrstop
#include "prey_local.h"
const idEventDef EV_PositionDefaultPose("<positionDefaultPose>");
const idEventDef EV_StartDefaultAnim("<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 );
}

32
src/Prey/game_animator.h Normal file
View File

@ -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__ */

View File

@ -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("<pmgamestart>", NULL);
const idEventDef EV_PlayerRespawn("<pmplayerspawn>", NULL);
const idEventDef EV_MonsterRespawn("<pmmonsterspawn>", "d");
const idEventDef EV_ToggleFruit("<pmtogglefruit>", NULL);
const idEventDef EV_GameOver("<pmgameover>", NULL);
const idEventDef EV_NextMap("<pmnextmap>", 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; y<ARCADE_GRID_HEIGHT; y++) {
for (int x=0; x<ARCADE_GRID_WIDTH; x++) {
startingGrid[x][y] = maze[y*ARCADE_GRID_WIDTH+x] - '0';
}
}
}
void hhArcadeGame::Save(idSaveGame *savefile) const {
int i,j;
savefile->Write(startingGrid, ARCADE_GRID_WIDTH*ARCADE_GRID_HEIGHT);
for (i=0; i<ARCADE_GRID_WIDTH; i++) {
for (j=0; j<ARCADE_GRID_HEIGHT; j++) {
grid[i][j].Save(savefile);
}
}
for (i=0; i<4; i++) {
monsters[i].Save(savefile);
}
player.Save(savefile);
fruit.Save(savefile);
savefile->WriteVec2(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; i<ARCADE_GRID_WIDTH; i++) {
for (j=0; j<ARCADE_GRID_HEIGHT; j++) {
grid[i][j].Restore(savefile);
}
}
for (i=0; i<4; i++) {
monsters[i].Restore(savefile);
}
player.Restore(savefile);
fruit.Restore(savefile);
savefile->ReadVec2(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; x<ARCADE_GRID_WIDTH; x++) {
for (int y=0; y<ARCADE_GRID_HEIGHT; y++) {
if (x != skipX || y != skipY) {
if (grid[x][y].GetType() == type) {
xOut = x;
yOut = y;
return;
}
}
}
}
}
void hhArcadeGame::ResetMap() {
int x, y;
// Make a copy of the grid that we can modify it, transposing in the process
for (x=0; x<ARCADE_GRID_WIDTH; x++) {
for (y=0; y<ARCADE_GRID_HEIGHT; y++) {
grid[x][y].SetType(startingGrid[x][y]);
grid[x][y].SetVisible(true);
}
}
FindType(ARCADE_FRUIT, x, y);
fruit.Set(x, y, false);
}
void hhArcadeGame::ResetPlayerAndMonsters() {
int x, y;
FindType(ARCADE_PLAYERSTART, x, y);
player.moveDelay = 200;
player.nextMoveTime = gameLocal.time;
player.isPlayer = true;
player.Set(x, y);
player.dying = false;
bPoweredUp = false;
StopSound(SND_CHANNEL_ITEM, true);
FindType(ARCADE_MONSTERSTART, x, y);
monsters[0].Set(x, y);
monsters[1].Set(x, y);
monsters[2].Set(x, y);
monsters[3].Set(x, y);
for (int ix=0; ix<4; ix++) {
monsters[ix].SetVisible(true);
monsters[ix].moveDelay = 300;
monsters[ix].nextMoveTime = gameLocal.time;
monsters[ix].vulnerableTimeRemaining = 0;
}
}
void hhArcadeGame::ResetGame() {
level = 1;
score = 0;
lives = 3;
nextScoreBoost = 10000;
bSimulating = false;
nextPelletTime = gameLocal.time;
bPoweredUp = false;
}
idVec2 hhArcadeGame::SmoothMovement(MovingGamePiece &piece) {
idVec2 curPos;
idVec2 lastGuiPos;
idVec2 lastGuiMove;
curPos.x = piece.x;
curPos.y = piece.y;
lastGuiPos.x = (curPos.x - piece.lastMove.x) * ARCADE_CELL_WIDTH;
lastGuiPos.y = (curPos.y - piece.lastMove.y) * ARCADE_CELL_HEIGHT;
lastGuiMove.x = piece.lastMove.x * ARCADE_CELL_WIDTH;
lastGuiMove.y = piece.lastMove.y * ARCADE_CELL_HEIGHT;
float alpha = (gameLocal.time - piece.lastMoveTime) / (float)piece.moveDelay;
alpha = idMath::ClampFloat(0.0f, 1.0f, alpha);
return lastGuiPos + lastGuiMove * alpha;
}
void hhArcadeGame::UpdateView() {
int ix;
idVec2 guiPos;
idUserInterface *gui = renderEntity.gui[0];
if (gui) {
if (player.Changed()) {
guiPos = SmoothMovement(player);
gui->SetStateInt("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; x<ARCADE_GRID_WIDTH; x++) {
for (int y=0; y<ARCADE_GRID_HEIGHT; y++) {
if (grid[x][y].Changed()) {
if (grid[x][y].GetType() == ARCADE_PELLET) {
gui->SetStateInt(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<ARCADE_GRID_WIDTH; i++) {
for (j=0; j<ARCADE_GRID_HEIGHT; j++) {
x = (startX + i) % ARCADE_GRID_WIDTH;
y = (startY + j) % ARCADE_GRID_HEIGHT;
// For each valid pellet, make all adjacent pellets visible
if (grid[x][y].GetType() == ARCADE_PELLET && grid[x][y].IsVisible()) {
if (grid[x-1][y].GetType() == ARCADE_PELLET && !grid[x-1][y].IsVisible()) {
grid[x-1][y].SetVisible(true);
growth++;
}
if (grid[x+1][y].GetType() == ARCADE_PELLET && !grid[x+1][y].IsVisible()) {
grid[x+1][y].SetVisible(true);
growth++;
}
if (grid[x][y-1].GetType() == ARCADE_PELLET && !grid[x][y-1].IsVisible()) {
grid[x][y-1].SetVisible(true);
growth++;
}
if (grid[x][y+1].GetType() == ARCADE_PELLET && !grid[x][y+1].IsVisible()) {
grid[x][y+1].SetVisible(true);
growth++;
}
if (growth > 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<ARCADE_GRID_WIDTH; x++) {
for (y=0; levelCompleted && y<ARCADE_GRID_HEIGHT; y++) {
type = grid[x][y].GetType();
if (grid[x][y].IsVisible() && (type == ARCADE_PELLET || type == ARCADE_POWERUP)) {
levelCompleted = false;
}
}
}
if (levelCompleted) {
AddScore(2000);
bSimulating = false;
if (victoryAmount > 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<hhPlayer*>(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 );
}
}

188
src/Prey/game_arcadegame.h Normal file
View File

@ -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

181
src/Prey/game_barrel.cpp Normal file
View File

@ -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();
}

30
src/Prey/game_barrel.h Normal file
View File

@ -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

View File

@ -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<idClass *&>(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<idAFEntity_Base *>(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<hhPlayer *>(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<hhPlayer *>(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<idActor *>(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<idAFEntity_Base*>(ent)->spawnArgs.GetInt("hangID");
}
}
}
void hhBindController::Detach() {
idEntity *rider = GetRider();
hhPlayer *player = rider && rider->IsType(hhPlayer::Type) ? static_cast<hhPlayer *>(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<idActor *>(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<idActor*>( 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();
}

View File

@ -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<idEntity> 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

508
src/Prey/game_blackjack.cpp Normal file
View File

@ -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<card_t>
for( i = 0; i < PlayerHand.Num(); i++ ) {
savefile->Write(&PlayerHand[i], sizeof(card_t));
}
savefile->WriteInt( DealerHand.Num() ); // Saving of idList<card_t>
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<PlayerHand.Num(); ix++) {
PlayerScore += PlayerHand[ix].Value;
PlayerAces += PlayerHand[ix].Value == 11 ? 1 : 0;
}
while (PlayerScore > 21 && PlayerAces) {
PlayerScore -= 10;
PlayerAces--;
}
for (ix=0; ix<DealerHand.Num(); ix++) {
DealerScore += DealerHand[ix].Value;
DealerAces += DealerHand[ix].Value == 11 ? 1 : 0;
}
while (DealerScore > 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; ix<DealerHand.Num(); ix++) {
gui->SetStateInt(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; ix<PlayerHand.Num(); ix++) {
gui->SetStateInt(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; i<DealerHand.Num(); i++)
DealerHand[i].Visible = 1;
// Only take cards if player is NOT busted, not 5 carded, and non blackjacked
if ( PlayerScore <= 21 && PlayerHand.Num() < 5 && (PlayerScore != 21 || PlayerHand.Num() != 2) ) {
while (DealerScore <= 16 && DealerHand.Num() < 5) {
DealerHand.Append(GetCard(1));
RetallyScores();
}
}
UpdateView();
}
void hhBlackJack::DeterminePlayCommands() {
bCanStay = 1;
bCanHit = PlayerScore < 21;
bCanDouble = (PlayerHand.Num() == 2) && (Bet*2 <= PlayerCredits);
// bCanSplit = PlayerHand.Num() == 2 && (PlayerHand[0].Name == PlayerHand[1].Name);
}
bool hhBlackJack::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("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();
}

70
src/Prey/game_blackjack.h Normal file
View File

@ -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 <card_t> PlayerHand;
idList <card_t> 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__ */

130
src/Prey/game_cards.cpp Normal file
View File

@ -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<hhCard>
for (i=0; i<cards.Num(); i++) {
savefile->Write(&cards[i], sizeof(hhCard));
}
}
void hhDeck::Restore( idRestoreGame *savefile ) {
int i, num;
hhCard card;
cards.Clear(); // hhStack<hhCard>
savefile->ReadInt( num );
cards.SetNum( num );
for (i=0; i<num; i++) {
savefile->Read(&card, sizeof(hhCard));
cards[i] = card;
}
}
void hhDeck::Generate() {
int value, suit;
cards.Clear();
for (value=0; value<NUM_CARD_VALUES; value++) {
for (suit=0; suit<NUM_SUITS; suit++) {
hhCard card(value, suit);
cards.Append(card);
}
}
}
void hhDeck::Shuffle() {
int numCards = cards.Num();
for (int ix=0; ix<5; ix++) {
for (int c1=0; c1<numCards; c1++) {
int c2 = gameLocal.random.RandomInt(numCards);
idSwap(cards[c1], cards[c2]);
}
// Cut?
}
}
bool hhDeck::HasCard(hhCard &card) {
int numCards = cards.Num();
for (int ix=0; ix<numCards; ix++) {
if (cards[ix] == card) {
return true;
}
}
return false;
}
hhCard hhDeck::GetCard() {
return cards.Pop();
}
hhCard hhDeck::GetCard(int value, int suit) {
hhCard card(value, suit);
assert(HasCard(card));
// Find card and remove from deck
cards.Remove(card);
return card;
}
void hhDeck::ReturnCard(hhCard card) {
cards.Push(card);
}

63
src/Prey/game_cards.h Normal file
View File

@ -0,0 +1,63 @@
#ifndef __GAME_CARDS_H__
#define __GAME_CARDS_H__
enum suits_e {
SUIT_DIAMONDS=0,
SUIT_HEARTS,
SUIT_SPADES,
SUIT_CLUBS,
NUM_SUITS
};
enum cards_e {
CARD_DEUCE=0,
CARD_THREE,
CARD_FOUR,
CARD_FIVE,
CARD_SIX,
CARD_SEVEN,
CARD_EIGHT,
CARD_NINE,
CARD_TEN,
CARD_JACK,
CARD_QUEEN,
CARD_KING,
CARD_ACE,
NUM_CARD_VALUES
};
class hhCard {
public:
hhCard();
hhCard(int value, int suit);
char ValueName();
char SuitName();
int operator==(const hhCard &other) const;
int Value() const;
int Suit() const;
private:
int value;
int suit;
};
class hhDeck : public idClass {
CLASS_PROTOTYPE( hhDeck );
public:
hhDeck();
void Save( idSaveGame *savefile ) const;
void Restore( idRestoreGame *savefile );
void Generate();
void Shuffle();
bool HasCard(hhCard &card);
hhCard GetCard();
hhCard GetCard(int value, int suit);
void ReturnCard(hhCard card);
private:
hhStack<hhCard> cards;
};
#endif /* __GAME_CARDS_H__ */

300
src/Prey/game_cilia.cpp Normal file
View File

@ -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 );
}
}
}

36
src/Prey/game_cilia.h Normal file
View File

@ -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 */

822
src/Prey/game_console.cpp Normal file
View File

@ -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<float>
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<float>
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<idClass *&>(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; ix<MAX_RENDERENTITY_GUI; ix++) {
if (renderEntity.gui[ix]) {
renderEntity.gui[ix]->SetStateFloat(key, value);
renderEntity.gui[ix]->StateChanged(gameLocal.time);
}
}
}
void hhConsole::SetOnAllGuis(const char *key, int value) {
for (int ix=0; ix<MAX_RENDERENTITY_GUI; ix++) {
if (renderEntity.gui[ix]) {
renderEntity.gui[ix]->SetStateInt(key, value);
renderEntity.gui[ix]->StateChanged(gameLocal.time);
}
}
}
void hhConsole::SetOnAllGuis(const char *key, bool value) {
for (int ix=0; ix<MAX_RENDERENTITY_GUI; ix++) {
if (renderEntity.gui[ix]) {
renderEntity.gui[ix]->SetStateBool(key, value);
renderEntity.gui[ix]->StateChanged(gameLocal.time);
}
}
}
void hhConsole::SetOnAllGuis(const char *key, const char *value) {
for (int ix=0; ix<MAX_RENDERENTITY_GUI; ix++) {
if (renderEntity.gui[ix]) {
renderEntity.gui[ix]->SetStateString(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; ix<MAX_RENDERENTITY_GUI; ix++) {
if (renderEntity.gui[ix]) {
cmd = renderEntity.gui[ix]->HandleEvent(&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<float>
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<float>
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("<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;i<targets.Num();i++ ) {
if ( targets[i].IsValid() ) {
if ( targets[i]->IsType( 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;i<num;i++ ) {
cm = clipModels[ i ];
// don't check render entities
if ( cm->IsRenderModel() ) {
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();
}

148
src/Prey/game_console.h Normal file
View File

@ -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<float> 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<idAI> aiCurrUsedBy; // The AI is currently being using this console
int aiCurrUsedStartTime; // The time the current user STARTED using this console
idEntityPtr<idAI> 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<float> 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<idAI> currentMonster;
int maxMonsters;
int numMonsters;
bool bSpawning;
private:
bool bAlarmActive;
};
#endif /* __GAME_CONSOLE_H__ */

View File

@ -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( "<resetTarget>", NULL );
const idEventDef EV_CheckRemove( "<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

View File

@ -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

681
src/Prey/game_dda.cpp Normal file
View File

@ -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<hhMonsterAI *>(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<idStr> 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 );
}

128
src/Prey/game_dda.h Normal file
View File

@ -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<idStr> 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<idStr> locationNameData;
idList<idStr> locationData;
idList<int> healthData;
idList<idStr> healthSpiritData;
idList<idStr> ammoData;
idList<idStr> miscData;
idList<ddaDeath_t> deathData;
};
#endif // __GAME_DDA_H__

View File

@ -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<hhPlayer *>( 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<hhPlayer *>( 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<hhDeathWraithEnergy*>(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<hhPlayer *> ( 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<idEntity> 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<hhPlayer*>(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 );
}

View File

@ -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

View File

@ -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<idClass *&>(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<idStr> 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<idActor *>(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<idStr> &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<idAI*>(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 );
}

Some files were not shown because too many files have changed in this diff Show More