2844 lines
72 KiB
C++
2844 lines
72 KiB
C++
/*
|
|
** Command & Conquer Renegade(tm)
|
|
** Copyright 2025 Electronic Arts Inc.
|
|
**
|
|
** This program is free software: you can redistribute it and/or modify
|
|
** it under the terms of the GNU General Public License as published by
|
|
** the Free Software Foundation, either version 3 of the License, or
|
|
** (at your option) any later version.
|
|
**
|
|
** This program is distributed in the hope that it will be useful,
|
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
** GNU General Public License for more details.
|
|
**
|
|
** You should have received a copy of the GNU General Public License
|
|
** along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/***********************************************************************************************
|
|
*** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ***
|
|
***********************************************************************************************
|
|
* *
|
|
* Project Name : Combat *
|
|
* *
|
|
* $Archive:: /Commando/Code/Combat/sakurabossgameobj.cpp $*
|
|
* *
|
|
* Author:: Patrick Smith *
|
|
* *
|
|
* $Modtime:: 1/16/02 6:01p $*
|
|
* *
|
|
* $Revision:: 35 $*
|
|
* *
|
|
*---------------------------------------------------------------------------------------------*
|
|
* Functions: *
|
|
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
|
#include "sakurabossgameobj.h"
|
|
#include "wwhack.h"
|
|
#include "simpledefinitionfactory.h"
|
|
#include "persistfactory.h"
|
|
#include "combatchunkid.h"
|
|
#include "debug.h"
|
|
#include "animcontrol.h"
|
|
#include "crandom.h"
|
|
#include "weapons.h"
|
|
#include "rendobj.h"
|
|
#include "wwaudio.h"
|
|
#include "audiblesound.h"
|
|
#include "explosion.h"
|
|
#include "phys.h"
|
|
#include "rbody.h"
|
|
#include "pscene.h"
|
|
#include "soldier.h"
|
|
#include "waypath.h"
|
|
#include "waypoint.h"
|
|
#include "pathfind.h"
|
|
#include "pathmgr.h"
|
|
#include "string_ids.h"
|
|
#include "translateobj.h"
|
|
#include "translatedb.h"
|
|
#include "wwprofile.h"
|
|
#include "conversationmgr.h"
|
|
#include "activeconversation.h"
|
|
#include "conversation.h"
|
|
#include "combat.h"
|
|
|
|
|
|
DECLARE_FORCE_LINK (SakuraBoss)
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// General constants
|
|
//////////////////////////////////////////////////////////////////////////
|
|
static const char * LEFT_ROCKET_BONE = "BN_MISSILE_WNGL";
|
|
static const char * RIGHT_ROCKET_BONE = "BN_MISSILE_WNGR";
|
|
static const char * ROCKET_DOOR_ANIMATION = "V_COMMANCHE.A_COMM_MISL";
|
|
static const char * GATLING_MUZZLE = "MUZZLEA0";
|
|
static const char * LEFT_ROCKET_MUZZLE = "MUZZLEB01";
|
|
static const char * RIGHT_ROCKET_MUZZLE = "MUZZLEB0";
|
|
static const char * LEFT_ROCKET_MESH = "V_COM_MISSILEL";
|
|
static const char * RIGHT_ROCKET_MESH = "V_COM_MISSILER";
|
|
static float ROCKETS_IN_FRAME_NUM = 0.0F;
|
|
static float ROCKETS_OUT_FRAME_NUM = 10.0F;
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Waypath IDs
|
|
//////////////////////////////////////////////////////////////////////////
|
|
const int WID_LONG_STRAFING_RUN = 1700009;
|
|
const int WID_LONG_STRAFING_RUN2 = 1700016;
|
|
|
|
const int WID_TO_LIFT_AREA_SOUTH = 1700018;
|
|
const int WID_TO_LIFT_AREA_NORTH = 1700019;
|
|
|
|
const int WID_TIBFIELD_TO_VSTRAFE = 1700004;
|
|
const int WID_LSTRAF2_TO_VSTRAFE = 1700017;
|
|
|
|
const int WID_VSTRAFE_TO_TIBFIELD = 1700006;
|
|
const int WID_LSTRAFE_TO_TIBFIELD = 1700002;
|
|
const int WID_REFINERY_TO_TIBFIELD = 1700012;
|
|
|
|
const int WID_LSTRAFE_TO_REFINERY = 1700010;
|
|
const int WID_VSTRAFE_TO_REFINERY = 1700014;
|
|
const int WID_PPLANT_TO_REFINERY = 1700007;
|
|
|
|
const int WID_VSTRAFE_TO_PPLANT = 1700015;
|
|
const int WID_REFINERY_TO_PPLANT = 1700008;
|
|
|
|
const int WID_REFINERY_TO_LSTRAFE2 = 1700003;
|
|
const int WID_VSTRAFE_TO_LSTRAFE2 = 1700005;
|
|
|
|
const int WID_REFINERY_TO_LSTRAFE = 1700011;
|
|
const int WID_VSTRAFE_TO_LSTRAFE = 1700013;
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Weapon and state enumerations
|
|
//////////////////////////////////////////////////////////////////////////
|
|
enum
|
|
{
|
|
WEAPON_GATLING_GUN = 0x01,
|
|
WEAPON_ROCKETS_LEFT = 0x02,
|
|
WEAPON_ROCKETS_RIGHT = 0x04,
|
|
WEAPON_ALL = WEAPON_GATLING_GUN | WEAPON_ROCKETS_LEFT | WEAPON_ROCKETS_RIGHT,
|
|
};
|
|
|
|
enum
|
|
{
|
|
ROCKET_STATE_OPENING = 1,
|
|
ROCKET_STATE_OPEN,
|
|
ROCKET_STATE_CLOSING,
|
|
ROCKET_STATE_CLOSED,
|
|
ROCKET_STATE_FIRING
|
|
};
|
|
|
|
enum
|
|
{
|
|
GATLING_STATE_NORMAL = 1,
|
|
GATLING_STATE_REVVING_UP,
|
|
GATLING_STATE_FIRING
|
|
};
|
|
|
|
enum
|
|
{
|
|
SAKURA_STATE_NORMAL = 1,
|
|
SAKURA_STATE_TAUNTING,
|
|
};
|
|
|
|
enum
|
|
{
|
|
VEHICLE_STATE_HOVERING = 1,
|
|
VEHICLE_STATE_MOVING,
|
|
};
|
|
|
|
enum
|
|
{
|
|
STATE_CIRCLE_POWER_PLANT = 1,
|
|
STATE_CIRCLE_REFINERY,
|
|
STATE_ATTACK_LIFT_AREA,
|
|
STATE_LONG_STRAFING_RUN,
|
|
STATE_LAND_IN_TIBERIUM_FIELD,
|
|
STATE_VALLEY_STRAFE,
|
|
STATE_LONG_STRAFING_RUN2,
|
|
STATE_IN_TRANSITION
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Weapon constants
|
|
//////////////////////////////////////////////////////////////////////////
|
|
enum
|
|
{
|
|
ROCKET_RIGHT = 0,
|
|
ROCKET_LEFT = 1
|
|
};
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Taunt constants
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
const char *TAUNT_IDS[6] =
|
|
{
|
|
"IDS_SAKURA_BOSS_TAUNT1",
|
|
"IDS_SAKURA_BOSS_TAUNT2",
|
|
"IDS_SAKURA_BOSS_TAUNT3",
|
|
"IDS_SAKURA_BOSS_TAUNT4",
|
|
"IDS_SAKURA_BOSS_TAUNT5",
|
|
"IDS_SAKURA_BOSS_TAUNT6"
|
|
};
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Save/load constants
|
|
//////////////////////////////////////////////////////////////////////////
|
|
enum
|
|
{
|
|
CHUNKID_DEF_PARENT = 0x09070458,
|
|
CHUNKID_DEF_VARIABLES,
|
|
CHUNKID_DEF_ROCKET_DEFENSEOBJ_DEF,
|
|
|
|
VARID_DEF_GATLING_DEF_ID = 1,
|
|
VARID_DEF_ROCKET_DEF_ID,
|
|
VARID_DEF_GATLING_REV_SOUND_ID,
|
|
VARID_DEF_ROCKET_DOOR_SOUND_ID,
|
|
VARID_DEF_ROCKET_DESTROYED_EXPLOSION_ID
|
|
};
|
|
|
|
enum
|
|
{
|
|
CHUNKID_PARENT = 0x09070459,
|
|
CHUNKID_VARIABLES,
|
|
CHUNKID_ROCKETL_DEFENSE_OBJ,
|
|
CHUNKID_ROCKETR_DEFENSE_OBJ,
|
|
CHUNKID_LAST_DAMAGER,
|
|
CHUNKID_CURR_TARGET,
|
|
CHUNKID_PILOT,
|
|
CHUNKID_PATH,
|
|
|
|
VARID_AVAILABLE_WEAPONS = 1,
|
|
VARID_OVERALL_STATE,
|
|
VARID_NEXT_OVERALL_STATE,
|
|
VARID_FACE_TARGET_IN_TRANSITION,
|
|
VARID_ROCKET_LAUNCHER_STATE,
|
|
VARID_GATTLING_GUN_STATE,
|
|
VARID_SAKURA_STATE,
|
|
VARID_VEHICLE_STATE,
|
|
VARID_MOVE_TO_LOCATION,
|
|
VARID_GATTLING_GUN_STATE_TIME_LEFT,
|
|
VARID_SAKURA_TAUNT_TIME_LEFT,
|
|
VARID_ROCKET_LAUNCHER_STATE_TIME_LEFT,
|
|
VARID_OVERALL_STATE_TIME_LEFT,
|
|
VARID_TARGET_TIME_LEFT,
|
|
VARID_CURRENT_HEALTH,
|
|
VARID_TARGET_ANGLE,
|
|
VARID_TARGET_POS,
|
|
VARID_IS_ATTACKING,
|
|
VARID_CURRENT_MUZZLE_TM,
|
|
VARID_CURRENT_MUZZLE_INDEX,
|
|
VARID_TILT_ANGLE,
|
|
VARID_CHOPPER_TILT_BONE_INDEX
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Factories
|
|
//////////////////////////////////////////////////////////////////////////
|
|
SimplePersistFactoryClass<SakuraBossGameObjDef, CHUNKID_GAME_OBJECT_DEF_SAKURA_BOSS> _SakuraBossGameObjDefPersistFactory;
|
|
SimplePersistFactoryClass<SakuraBossGameObj, CHUNKID_GAME_OBJECT_SAKURA_BOSS> _SakuraBossGameObjPersistFactory;
|
|
DECLARE_DEFINITION_FACTORY(SakuraBossGameObjDef, CLASSID_GAME_OBJECT_DEF_SAKURA_BOSS, "Sakura Boss") _SakuraBossGameObjDefDefFactory;
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// SakuraBossGameObjDef
|
|
//
|
|
//////////////////////////////////////////////////////////////////////////
|
|
SakuraBossGameObjDef::SakuraBossGameObjDef (void) :
|
|
GattlingGunDefID (0),
|
|
RocketLauncherDefID (0),
|
|
GattlingGunRevSoundDefID (0),
|
|
RocketDestroyedExplosionID (0),
|
|
RocketDoorOpenSoundID (0)
|
|
{
|
|
PARAM_SEPARATOR (SakuraBossGameObjDef, "Rocket Launcher Defense Settings");
|
|
DEFENSEOBJECTDEF_EDITABLE_PARAMS (SakuraBossGameObjDef, RocketsDefense);
|
|
PARAM_SEPARATOR (SakuraBossGameObjDef, "");
|
|
|
|
PARAM_SEPARATOR (SakuraBossGameObjDef, "Weapons");
|
|
EDITABLE_PARAM (SakuraBossGameObjDef, ParameterClass::TYPE_WEAPONOBJDEFINITIONID, RocketLauncherDefID);
|
|
EDITABLE_PARAM (SakuraBossGameObjDef, ParameterClass::TYPE_WEAPONOBJDEFINITIONID, GattlingGunDefID);
|
|
EDITABLE_PARAM (SakuraBossGameObjDef, ParameterClass::TYPE_SOUNDDEFINITIONID, GattlingGunRevSoundDefID);
|
|
EDITABLE_PARAM (SakuraBossGameObjDef, ParameterClass::TYPE_SOUNDDEFINITIONID, RocketDoorOpenSoundID);
|
|
EDITABLE_PARAM (SakuraBossGameObjDef, ParameterClass::TYPE_EXPLOSIONDEFINITIONID, RocketDestroyedExplosionID);
|
|
PARAM_SEPARATOR (SakuraBossGameObjDef, "");
|
|
return ;
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// ~SakuraBossGameObjDef
|
|
//
|
|
//////////////////////////////////////////////////////////////////////////
|
|
SakuraBossGameObjDef::~SakuraBossGameObjDef (void)
|
|
{
|
|
return ;
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Get_Class_ID
|
|
//
|
|
//////////////////////////////////////////////////////////////////////////
|
|
uint32
|
|
SakuraBossGameObjDef::Get_Class_ID (void) const
|
|
{
|
|
return CLASSID_GAME_OBJECT_DEF_SAKURA_BOSS;
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Create
|
|
//
|
|
//////////////////////////////////////////////////////////////////////////
|
|
PersistClass *
|
|
SakuraBossGameObjDef::Create (void) const
|
|
{
|
|
SakuraBossGameObj *obj = new SakuraBossGameObj;
|
|
obj->Init(*this);
|
|
return obj;
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Save
|
|
//
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool
|
|
SakuraBossGameObjDef::Save (ChunkSaveClass &csave)
|
|
{
|
|
csave.Begin_Chunk (CHUNKID_DEF_PARENT);
|
|
VehicleGameObjDef::Save (csave);
|
|
csave.End_Chunk ();
|
|
|
|
csave.Begin_Chunk (CHUNKID_DEF_VARIABLES);
|
|
Save_Variables (csave);
|
|
csave.End_Chunk();
|
|
|
|
csave.Begin_Chunk (CHUNKID_DEF_ROCKET_DEFENSEOBJ_DEF);
|
|
RocketsDefense.Save (csave);
|
|
csave.End_Chunk();
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Load
|
|
//
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool
|
|
SakuraBossGameObjDef::Load (ChunkLoadClass &cload)
|
|
{
|
|
while (cload.Open_Chunk ()) {
|
|
switch(cload.Cur_Chunk_ID ()) {
|
|
|
|
case CHUNKID_DEF_PARENT:
|
|
VehicleGameObjDef::Load (cload);
|
|
break;
|
|
|
|
case CHUNKID_DEF_VARIABLES:
|
|
Load_Variables (cload);
|
|
break;
|
|
|
|
case CHUNKID_DEF_ROCKET_DEFENSEOBJ_DEF:
|
|
RocketsDefense.Load (cload);
|
|
break;
|
|
|
|
default:
|
|
Debug_Say (("Unrecognized SakuraBossGameObjDef chunkID\n"));
|
|
break;
|
|
}
|
|
|
|
cload.Close_Chunk ();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Save_Variables
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void
|
|
SakuraBossGameObjDef::Save_Variables (ChunkSaveClass &csave)
|
|
{
|
|
WRITE_MICRO_CHUNK (csave, VARID_DEF_GATLING_DEF_ID, GattlingGunDefID);
|
|
WRITE_MICRO_CHUNK (csave, VARID_DEF_ROCKET_DEF_ID, RocketLauncherDefID);
|
|
WRITE_MICRO_CHUNK (csave, VARID_DEF_GATLING_REV_SOUND_ID, GattlingGunRevSoundDefID);
|
|
WRITE_MICRO_CHUNK (csave, VARID_DEF_ROCKET_DOOR_SOUND_ID, RocketDoorOpenSoundID);
|
|
WRITE_MICRO_CHUNK (csave, VARID_DEF_ROCKET_DESTROYED_EXPLOSION_ID, RocketDestroyedExplosionID);
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Load_Variables
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void
|
|
SakuraBossGameObjDef::Load_Variables (ChunkLoadClass &cload)
|
|
{
|
|
while (cload.Open_Micro_Chunk ()) {
|
|
switch (cload.Cur_Micro_Chunk_ID ()) {
|
|
|
|
READ_MICRO_CHUNK (cload, VARID_DEF_GATLING_DEF_ID, GattlingGunDefID);
|
|
READ_MICRO_CHUNK (cload, VARID_DEF_ROCKET_DEF_ID, RocketLauncherDefID);
|
|
READ_MICRO_CHUNK (cload, VARID_DEF_GATLING_REV_SOUND_ID, GattlingGunRevSoundDefID);
|
|
READ_MICRO_CHUNK (cload, VARID_DEF_ROCKET_DOOR_SOUND_ID, RocketDoorOpenSoundID);
|
|
READ_MICRO_CHUNK (cload, VARID_DEF_ROCKET_DESTROYED_EXPLOSION_ID, RocketDestroyedExplosionID);
|
|
|
|
default:
|
|
Debug_Say (("Unrecognized SakuraBossGameObjDef Variable chunkID %d\n", cload.Cur_Micro_Chunk_ID ()));
|
|
break;
|
|
}
|
|
|
|
cload.Close_Micro_Chunk ();
|
|
}
|
|
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Get_Factory
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
const PersistFactoryClass &
|
|
SakuraBossGameObjDef::Get_Factory (void) const
|
|
{
|
|
return _SakuraBossGameObjDefPersistFactory;
|
|
}
|
|
|
|
|
|
/*
|
|
**
|
|
** Start of SakuraBossGameObj
|
|
**
|
|
*/
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// SakuraBossGameObj
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
SakuraBossGameObj::SakuraBossGameObj (void) :
|
|
GattlingGun (NULL),
|
|
RockerLauncherLeft (NULL),
|
|
RockerLauncherRight (NULL),
|
|
AvailableWeapons (WEAPON_ALL),
|
|
OverallState (STATE_ATTACK_LIFT_AREA),
|
|
NextOverallState (STATE_ATTACK_LIFT_AREA),
|
|
RocketLauncherState (ROCKET_STATE_CLOSED),
|
|
GattlingGunState (GATLING_STATE_NORMAL),
|
|
SakuraState (SAKURA_STATE_NORMAL),
|
|
VehicleState (VEHICLE_STATE_HOVERING),
|
|
GattlingGunStateTimeLeft (0),
|
|
SakuraTauntTimeLeft (0),
|
|
CurrentMuzzleIndex (0),
|
|
RocketLauncherStateTimeLeft (0),
|
|
OverallStateTimeLeft (10.0F),
|
|
TargetTimeLeft (0),
|
|
FaceTargetInTransition (false),
|
|
CurrentHealth (1.0F),
|
|
TargetAngle (0),
|
|
IsAttacking (false),
|
|
Path (NULL),
|
|
ChopperTiltBoneIndex (0),
|
|
TiltAngle (0),
|
|
AvailableTaunts (0xFFFFFF)
|
|
{
|
|
Path = new PathClass;
|
|
|
|
Shuffle_Taunt_List ();
|
|
TauntList[0] = 1;//IDS_SAKURA_BOSS_TAUNT1;
|
|
TauntList[1] = 2;//IDS_SAKURA_BOSS_TAUNT2;
|
|
TauntList[2] = 3;//IDS_SAKURA_BOSS_TAUNT3;
|
|
TauntList[3] = 4;//IDS_SAKURA_BOSS_TAUNT4;
|
|
TauntList[4] = 5;//IDS_SAKURA_BOSS_TAUNT5;
|
|
TauntList[5] = 6;//IDS_SAKURA_BOSS_TAUNT6;
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// ~SakuraBossGameObj
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
SakuraBossGameObj::~SakuraBossGameObj (void)
|
|
{
|
|
REF_PTR_RELEASE (Path);
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Get_Factory
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
const PersistFactoryClass &
|
|
SakuraBossGameObj::Get_Factory (void) const
|
|
{
|
|
return _SakuraBossGameObjPersistFactory;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
//
|
|
// Init
|
|
//
|
|
////////////////////////////////////////////////////////////////
|
|
void SakuraBossGameObj::Init( void )
|
|
{
|
|
Init( Get_Definition() );
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Init
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void
|
|
SakuraBossGameObj::Init (const SakuraBossGameObjDef &definition)
|
|
{
|
|
VehicleGameObj::Init (definition);
|
|
|
|
//
|
|
// Initialize the defense for the left and right rocket launchers
|
|
//
|
|
// Pass a NULL for the owner so we don't think we have killed Sakura too early
|
|
LeftRocketDefenseObject.Init (definition.RocketsDefense, NULL);
|
|
RightRocketDefenseObject.Init (definition.RocketsDefense, NULL);
|
|
|
|
//
|
|
// Lookup the weapon definitions
|
|
//
|
|
const WeaponDefinitionClass *gatling_gun_def = WeaponManager::Find_Weapon_Definition (definition.GattlingGunDefID);
|
|
const WeaponDefinitionClass *rocket_launcher_def = WeaponManager::Find_Weapon_Definition (definition.RocketLauncherDefID);
|
|
|
|
//
|
|
// Create the gatling gun
|
|
//
|
|
if (gatling_gun_def != NULL) {
|
|
GattlingGun = new WeaponClass (gatling_gun_def);
|
|
GattlingGun->Set_Owner (this);
|
|
GattlingGun->Init_Muzzle_Flash (Peek_Physical_Object ()->Peek_Model ());
|
|
}
|
|
|
|
//
|
|
// Create the rocket launchers
|
|
//
|
|
if (rocket_launcher_def != NULL) {
|
|
RockerLauncherLeft = new WeaponClass (rocket_launcher_def);
|
|
RockerLauncherRight = new WeaponClass (rocket_launcher_def);
|
|
RockerLauncherLeft->Set_Owner (this);
|
|
RockerLauncherRight->Set_Owner (this);
|
|
RockerLauncherLeft->Init_Muzzle_Flash (Peek_Physical_Object ()->Peek_Model ());
|
|
RockerLauncherRight->Init_Muzzle_Flash (Peek_Physical_Object ()->Peek_Model ());
|
|
}
|
|
|
|
//
|
|
// Make sure we have an animation controller
|
|
//
|
|
if (Get_Anim_Control () == NULL) {
|
|
Set_Anim_Control (new SimpleAnimControlClass);
|
|
Get_Anim_Control ()->Set_Model (Peek_Model ());
|
|
}
|
|
|
|
//
|
|
// Make sure the engine is on
|
|
//
|
|
Enable_Engine (true);
|
|
|
|
Pilot.Initialize (this);
|
|
Pilot.Set_Arrived_Dist (1.0F);
|
|
Pilot.Set_Aggressivness (1.0F);
|
|
Pilot.Set_Is_Exact_Z_Important (true);
|
|
|
|
//
|
|
// Lookup the chopper-tilting bone index
|
|
//
|
|
ChopperTiltBoneIndex = Peek_Model ()->Get_Bone_Index ("V_CHOPPER");
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Get_Definition
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
const SakuraBossGameObjDef &
|
|
SakuraBossGameObj::Get_Definition (void) const
|
|
{
|
|
return (const SakuraBossGameObjDef &)BaseGameObj::Get_Definition ();
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Save
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
bool
|
|
SakuraBossGameObj::Save (ChunkSaveClass & csave)
|
|
{
|
|
csave.Begin_Chunk (CHUNKID_PARENT);
|
|
VehicleGameObj::Save (csave);
|
|
csave.End_Chunk ();
|
|
|
|
csave.Begin_Chunk (CHUNKID_ROCKETL_DEFENSE_OBJ);
|
|
LeftRocketDefenseObject.Save (csave);
|
|
csave.End_Chunk ();
|
|
|
|
csave.Begin_Chunk (CHUNKID_ROCKETR_DEFENSE_OBJ);
|
|
RightRocketDefenseObject.Save (csave);
|
|
csave.End_Chunk ();
|
|
|
|
csave.Begin_Chunk (CHUNKID_LAST_DAMAGER);
|
|
LastDamager.Save (csave);
|
|
csave.End_Chunk ();
|
|
|
|
csave.Begin_Chunk (CHUNKID_CURR_TARGET);
|
|
CurrentTarget.Save (csave);
|
|
csave.End_Chunk ();
|
|
|
|
csave.Begin_Chunk (CHUNKID_PILOT);
|
|
Pilot.Save (csave);
|
|
csave.End_Chunk ();
|
|
|
|
csave.Begin_Chunk (CHUNKID_VARIABLES);
|
|
Save_Variables (csave);
|
|
csave.End_Chunk ();
|
|
|
|
if (Path != NULL) {
|
|
csave.Begin_Chunk (CHUNKID_PATH);
|
|
Path->Save (csave);
|
|
csave.End_Chunk();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Load
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
bool
|
|
SakuraBossGameObj::Load (ChunkLoadClass &cload)
|
|
{
|
|
while (cload.Open_Chunk ()) {
|
|
switch(cload.Cur_Chunk_ID ()) {
|
|
|
|
case CHUNKID_PARENT:
|
|
VehicleGameObj::Load (cload);
|
|
break;
|
|
|
|
case CHUNKID_ROCKETL_DEFENSE_OBJ:
|
|
LeftRocketDefenseObject.Load (cload);
|
|
break;
|
|
|
|
case CHUNKID_ROCKETR_DEFENSE_OBJ:
|
|
RightRocketDefenseObject.Load (cload);
|
|
break;
|
|
|
|
case CHUNKID_LAST_DAMAGER:
|
|
LastDamager.Load (cload);
|
|
break;
|
|
|
|
case CHUNKID_CURR_TARGET:
|
|
CurrentTarget.Load (cload);
|
|
break;
|
|
|
|
case CHUNKID_PILOT:
|
|
Pilot.Load (cload);
|
|
break;
|
|
|
|
case CHUNKID_PATH:
|
|
{
|
|
Path = new PathClass;
|
|
Path->Load (cload);
|
|
}
|
|
break;
|
|
|
|
case CHUNKID_VARIABLES:
|
|
Load_Variables (cload);
|
|
break;
|
|
|
|
default:
|
|
Debug_Say (("Unrecognized SakuraBossGameObj chunk ID\n"));
|
|
break;
|
|
|
|
}
|
|
|
|
cload.Close_Chunk ();
|
|
}
|
|
|
|
SaveLoadSystemClass::Register_Post_Load_Callback (this);
|
|
return true;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// On_Post_Load
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void
|
|
SakuraBossGameObj::On_Post_Load (void)
|
|
{
|
|
VehicleGameObj::On_Post_Load ();
|
|
|
|
//
|
|
// Initialize the defense for the left and right rocket launchers
|
|
//
|
|
// Pass a NULL for the owner so we don't think we have killed Sakura too early
|
|
//LeftRocketDefenseObject.Init (Get_Definition ().RocketsDefense, NULL);
|
|
//RightRocketDefenseObject.Init (Get_Definition ().RocketsDefense, NULL);
|
|
|
|
//
|
|
// Lookup the weapon definitions
|
|
//
|
|
const WeaponDefinitionClass *gatling_gun_def = WeaponManager::Find_Weapon_Definition (Get_Definition ().GattlingGunDefID);
|
|
const WeaponDefinitionClass *rocket_launcher_def = WeaponManager::Find_Weapon_Definition (Get_Definition ().RocketLauncherDefID);
|
|
|
|
//
|
|
// Create the gatling gun
|
|
//
|
|
if (gatling_gun_def != NULL) {
|
|
GattlingGun = new WeaponClass (gatling_gun_def);
|
|
GattlingGun->Set_Owner (this);
|
|
GattlingGun->Init_Muzzle_Flash (Peek_Physical_Object ()->Peek_Model ());
|
|
}
|
|
|
|
//
|
|
// Create the rocket launchers
|
|
//
|
|
if (rocket_launcher_def != NULL) {
|
|
RockerLauncherLeft = new WeaponClass (rocket_launcher_def);
|
|
RockerLauncherRight = new WeaponClass (rocket_launcher_def);
|
|
RockerLauncherLeft->Set_Owner (this);
|
|
RockerLauncherRight->Set_Owner (this);
|
|
RockerLauncherLeft->Init_Muzzle_Flash (Peek_Physical_Object ()->Peek_Model ());
|
|
RockerLauncherRight->Init_Muzzle_Flash (Peek_Physical_Object ()->Peek_Model ());
|
|
}
|
|
|
|
//
|
|
// Make sure the engine is on
|
|
//
|
|
//Enable_Engine (true);
|
|
|
|
//Pilot.Initialize (this);
|
|
//Pilot.Set_Arrived_Dist (1.0F);
|
|
//Pilot.Set_Aggressivness (1.0F);
|
|
|
|
//
|
|
// Lookup the chopper-tilting bone index
|
|
//
|
|
ChopperTiltBoneIndex = Peek_Model ()->Get_Bone_Index ("V_CHOPPER");
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Save_Variables
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void
|
|
SakuraBossGameObj::Save_Variables (ChunkSaveClass &csave)
|
|
{
|
|
WRITE_MICRO_CHUNK (csave, VARID_AVAILABLE_WEAPONS, AvailableWeapons);
|
|
WRITE_MICRO_CHUNK (csave, VARID_OVERALL_STATE, OverallState);
|
|
WRITE_MICRO_CHUNK (csave, VARID_NEXT_OVERALL_STATE, NextOverallState);
|
|
WRITE_MICRO_CHUNK (csave, VARID_FACE_TARGET_IN_TRANSITION, FaceTargetInTransition);
|
|
WRITE_MICRO_CHUNK (csave, VARID_ROCKET_LAUNCHER_STATE, RocketLauncherState);
|
|
WRITE_MICRO_CHUNK (csave, VARID_GATTLING_GUN_STATE, GattlingGunState);
|
|
WRITE_MICRO_CHUNK (csave, VARID_SAKURA_STATE, SakuraState);
|
|
WRITE_MICRO_CHUNK (csave, VARID_VEHICLE_STATE, VehicleState);
|
|
WRITE_MICRO_CHUNK (csave, VARID_MOVE_TO_LOCATION, MoveToLocation);
|
|
WRITE_MICRO_CHUNK (csave, VARID_GATTLING_GUN_STATE_TIME_LEFT, GattlingGunStateTimeLeft);
|
|
WRITE_MICRO_CHUNK (csave, VARID_SAKURA_TAUNT_TIME_LEFT, SakuraTauntTimeLeft);
|
|
WRITE_MICRO_CHUNK (csave, VARID_ROCKET_LAUNCHER_STATE_TIME_LEFT, RocketLauncherStateTimeLeft);
|
|
WRITE_MICRO_CHUNK (csave, VARID_OVERALL_STATE_TIME_LEFT, OverallStateTimeLeft);
|
|
WRITE_MICRO_CHUNK (csave, VARID_TARGET_TIME_LEFT, TargetTimeLeft);
|
|
WRITE_MICRO_CHUNK (csave, VARID_CURRENT_HEALTH, CurrentHealth);
|
|
WRITE_MICRO_CHUNK (csave, VARID_TARGET_ANGLE, TargetAngle);
|
|
WRITE_MICRO_CHUNK (csave, VARID_TARGET_POS, TargetPos);
|
|
WRITE_MICRO_CHUNK (csave, VARID_IS_ATTACKING, IsAttacking);
|
|
WRITE_MICRO_CHUNK (csave, VARID_CURRENT_MUZZLE_TM, CurrentMuzzleTM);
|
|
WRITE_MICRO_CHUNK (csave, VARID_CURRENT_MUZZLE_INDEX, CurrentMuzzleIndex);
|
|
WRITE_MICRO_CHUNK (csave, VARID_TILT_ANGLE, TiltAngle);
|
|
WRITE_MICRO_CHUNK (csave, VARID_CHOPPER_TILT_BONE_INDEX, ChopperTiltBoneIndex);
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Load_Variables
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void
|
|
SakuraBossGameObj::Load_Variables (ChunkLoadClass &cload)
|
|
{
|
|
while (cload.Open_Micro_Chunk ()) {
|
|
switch (cload.Cur_Micro_Chunk_ID ()) {
|
|
|
|
READ_MICRO_CHUNK (cload, VARID_AVAILABLE_WEAPONS, AvailableWeapons);
|
|
READ_MICRO_CHUNK (cload, VARID_OVERALL_STATE, OverallState);
|
|
READ_MICRO_CHUNK (cload, VARID_NEXT_OVERALL_STATE, NextOverallState);
|
|
READ_MICRO_CHUNK (cload, VARID_FACE_TARGET_IN_TRANSITION, FaceTargetInTransition);
|
|
READ_MICRO_CHUNK (cload, VARID_ROCKET_LAUNCHER_STATE, RocketLauncherState);
|
|
READ_MICRO_CHUNK (cload, VARID_GATTLING_GUN_STATE, GattlingGunState);
|
|
READ_MICRO_CHUNK (cload, VARID_SAKURA_STATE, SakuraState);
|
|
READ_MICRO_CHUNK (cload, VARID_VEHICLE_STATE, VehicleState);
|
|
READ_MICRO_CHUNK (cload, VARID_MOVE_TO_LOCATION, MoveToLocation);
|
|
READ_MICRO_CHUNK (cload, VARID_GATTLING_GUN_STATE_TIME_LEFT, GattlingGunStateTimeLeft);
|
|
READ_MICRO_CHUNK (cload, VARID_SAKURA_TAUNT_TIME_LEFT, SakuraTauntTimeLeft);
|
|
READ_MICRO_CHUNK (cload, VARID_ROCKET_LAUNCHER_STATE_TIME_LEFT, RocketLauncherStateTimeLeft);
|
|
READ_MICRO_CHUNK (cload, VARID_OVERALL_STATE_TIME_LEFT, OverallStateTimeLeft);
|
|
READ_MICRO_CHUNK (cload, VARID_TARGET_TIME_LEFT, TargetTimeLeft);
|
|
READ_MICRO_CHUNK (cload, VARID_CURRENT_HEALTH, CurrentHealth);
|
|
READ_MICRO_CHUNK (cload, VARID_TARGET_ANGLE, TargetAngle);
|
|
READ_MICRO_CHUNK (cload, VARID_TARGET_POS, TargetPos);
|
|
READ_MICRO_CHUNK (cload, VARID_IS_ATTACKING, IsAttacking);
|
|
READ_MICRO_CHUNK (cload, VARID_CURRENT_MUZZLE_TM, CurrentMuzzleTM);
|
|
READ_MICRO_CHUNK (cload, VARID_CURRENT_MUZZLE_INDEX, CurrentMuzzleIndex);
|
|
READ_MICRO_CHUNK (cload, VARID_TILT_ANGLE, TiltAngle);
|
|
READ_MICRO_CHUNK (cload, VARID_CHOPPER_TILT_BONE_INDEX, ChopperTiltBoneIndex);
|
|
|
|
default:
|
|
Debug_Say (("Unrecognized SakuraBossGameObj Variable chunkID %d\n", cload.Cur_Micro_Chunk_ID ()));
|
|
break;
|
|
}
|
|
|
|
cload.Close_Micro_Chunk ();
|
|
}
|
|
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Apply_Control
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void
|
|
SakuraBossGameObj::Apply_Control (void)
|
|
{
|
|
VehicleGameObj::Apply_Control ();
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Update_Decision_Data
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void
|
|
SakuraBossGameObj::Update_Decision_Data (void)
|
|
{
|
|
//
|
|
// Update our current health
|
|
//
|
|
DefenseObjectClass *defense_object = Get_Defense_Object ();
|
|
if (defense_object != NULL) {
|
|
CurrentHealth = defense_object->Get_Health () / defense_object->Get_Health_Max ();
|
|
}
|
|
|
|
//
|
|
// Determine if any of our weapons are currently attacking the player
|
|
//
|
|
IsAttacking = false;
|
|
if ( RocketLauncherState != ROCKET_STATE_CLOSED ||
|
|
GattlingGunState != GATLING_STATE_NORMAL)
|
|
{
|
|
IsAttacking = true;
|
|
}
|
|
|
|
//
|
|
// Update information about our current target
|
|
//
|
|
TargetAngle = 0;
|
|
TargetPos.Set (0, 0, 0);
|
|
if (CurrentTarget != NULL) {
|
|
|
|
//
|
|
// Get the target's position
|
|
//
|
|
CurrentTarget.Get_Ptr ()->Get_Position (&TargetPos);
|
|
|
|
//
|
|
// Get the target's position relative to ours
|
|
//
|
|
Vector3 obj_space_target;
|
|
Matrix3D::Inverse_Transform_Vector (Get_Transform (), TargetPos, &obj_space_target);
|
|
|
|
//
|
|
// What angle is the target off from our current facing?
|
|
//
|
|
TargetAngle = WWMath::Atan2 (obj_space_target.Y, obj_space_target.X);
|
|
TargetAngle = WWMath::Wrap (TargetAngle, DEG_TO_RADF (-180), DEG_TO_RADF (180));
|
|
}
|
|
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Think
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void
|
|
SakuraBossGameObj::Think (void)
|
|
{
|
|
Pilot.Think ();
|
|
VehicleGameObj::Think ();
|
|
|
|
WWPROFILE( "Sakura Think" );
|
|
|
|
//
|
|
// Find a target if necessary
|
|
//
|
|
Update_Target ();
|
|
|
|
//
|
|
// Update our 'snapshot' of the world's (and our) state
|
|
//
|
|
Update_Decision_Data ();
|
|
|
|
//
|
|
// Update the individual parts that make-up the boss
|
|
//
|
|
Update_Rocket_State ();
|
|
Update_Gattling_Gun_State ();
|
|
Update_Sakura_State ();
|
|
Update_Vehicle_State ();
|
|
Update_Tilt ();
|
|
|
|
//
|
|
// Now, look at the bigger picture and determine
|
|
// what each part should be doing
|
|
//
|
|
Decide_New_Overall_State ();
|
|
Update_Overall_State ();
|
|
|
|
//
|
|
// Update the weapons
|
|
//
|
|
if (RockerLauncherLeft != NULL) {
|
|
CurrentMuzzleIndex = ROCKET_LEFT;
|
|
RockerLauncherLeft->Update ();
|
|
RockerLauncherLeft->Set_Total_Rounds (1000);
|
|
}
|
|
|
|
if (RockerLauncherRight != NULL) {
|
|
CurrentMuzzleIndex = ROCKET_RIGHT;
|
|
RockerLauncherRight->Update ();
|
|
RockerLauncherRight->Set_Total_Rounds (1000);
|
|
}
|
|
|
|
if (GattlingGun != NULL) {
|
|
GattlingGun->Update ();
|
|
GattlingGun->Set_Total_Rounds (1000);
|
|
}
|
|
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Request_Overall_State
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void
|
|
SakuraBossGameObj::Request_Overall_State (int new_state)
|
|
{
|
|
if (OverallState != STATE_IN_TRANSITION && OverallState != new_state) {
|
|
|
|
switch (new_state)
|
|
{
|
|
case STATE_CIRCLE_POWER_PLANT:
|
|
|
|
if ( OverallState == STATE_CIRCLE_REFINERY ||
|
|
OverallState == STATE_ATTACK_LIFT_AREA)
|
|
{
|
|
Do_Waypath (WID_REFINERY_TO_PPLANT);
|
|
Set_Overall_State (STATE_IN_TRANSITION);
|
|
|
|
} else if ( OverallState == STATE_LONG_STRAFING_RUN ||
|
|
OverallState == STATE_LAND_IN_TIBERIUM_FIELD ||
|
|
OverallState == STATE_LONG_STRAFING_RUN2)
|
|
{
|
|
Set_Overall_State (STATE_CIRCLE_POWER_PLANT);
|
|
|
|
} else if (OverallState == STATE_VALLEY_STRAFE) {
|
|
|
|
Do_Waypath (WID_VSTRAFE_TO_PPLANT);
|
|
Set_Overall_State (STATE_IN_TRANSITION);
|
|
}
|
|
|
|
break;
|
|
|
|
case STATE_CIRCLE_REFINERY:
|
|
|
|
if ( OverallState == STATE_CIRCLE_POWER_PLANT ||
|
|
OverallState == STATE_LAND_IN_TIBERIUM_FIELD ||
|
|
OverallState == STATE_LONG_STRAFING_RUN2)
|
|
{
|
|
Do_Waypath (WID_PPLANT_TO_REFINERY);
|
|
Set_Overall_State (STATE_IN_TRANSITION);
|
|
|
|
} else if (OverallState == STATE_ATTACK_LIFT_AREA) {
|
|
|
|
Set_Overall_State (STATE_CIRCLE_REFINERY);
|
|
|
|
} else if (OverallState == STATE_VALLEY_STRAFE) {
|
|
|
|
Do_Waypath (WID_VSTRAFE_TO_REFINERY);
|
|
Set_Overall_State (STATE_IN_TRANSITION);
|
|
|
|
} else if (OverallState == STATE_LONG_STRAFING_RUN) {
|
|
|
|
Do_Waypath (WID_LSTRAFE_TO_REFINERY);
|
|
Set_Overall_State (STATE_IN_TRANSITION);
|
|
}
|
|
|
|
break;
|
|
|
|
case STATE_LAND_IN_TIBERIUM_FIELD:
|
|
|
|
if ( OverallState == STATE_CIRCLE_REFINERY ||
|
|
OverallState == STATE_ATTACK_LIFT_AREA) {
|
|
|
|
Do_Waypath (WID_REFINERY_TO_TIBFIELD);
|
|
Set_Overall_State (STATE_IN_TRANSITION);
|
|
|
|
} else if ( OverallState == STATE_CIRCLE_POWER_PLANT ||
|
|
OverallState == STATE_LONG_STRAFING_RUN2)
|
|
{
|
|
Set_Overall_State (STATE_LAND_IN_TIBERIUM_FIELD);
|
|
|
|
} else if (OverallState == STATE_LONG_STRAFING_RUN) {
|
|
|
|
Do_Waypath (WID_LSTRAFE_TO_TIBFIELD);
|
|
Set_Overall_State (STATE_IN_TRANSITION);
|
|
|
|
} else if (OverallState == STATE_VALLEY_STRAFE) {
|
|
|
|
Do_Waypath (WID_VSTRAFE_TO_TIBFIELD);
|
|
Set_Overall_State (STATE_IN_TRANSITION);
|
|
}
|
|
|
|
break;
|
|
|
|
case STATE_VALLEY_STRAFE:
|
|
|
|
if ( OverallState == STATE_CIRCLE_REFINERY ||
|
|
OverallState == STATE_CIRCLE_POWER_PLANT ||
|
|
OverallState == STATE_LONG_STRAFING_RUN)
|
|
{
|
|
Set_Overall_State (STATE_VALLEY_STRAFE);
|
|
|
|
} else if (OverallState == STATE_ATTACK_LIFT_AREA) {
|
|
|
|
Set_Overall_State (STATE_VALLEY_STRAFE);
|
|
|
|
} else if (OverallState == STATE_LONG_STRAFING_RUN2) {
|
|
|
|
Do_Waypath (WID_LSTRAF2_TO_VSTRAFE);
|
|
Set_Overall_State (STATE_IN_TRANSITION);
|
|
|
|
} else if (OverallState == STATE_LAND_IN_TIBERIUM_FIELD) {
|
|
|
|
Do_Waypath (WID_TIBFIELD_TO_VSTRAFE);
|
|
Set_Overall_State (STATE_IN_TRANSITION);
|
|
}
|
|
|
|
break;
|
|
|
|
case STATE_ATTACK_LIFT_AREA:
|
|
|
|
if ( OverallState == STATE_CIRCLE_REFINERY ||
|
|
OverallState == STATE_LONG_STRAFING_RUN ||
|
|
OverallState == STATE_VALLEY_STRAFE)
|
|
{
|
|
Do_Waypath (WID_TO_LIFT_AREA_SOUTH);
|
|
Set_Overall_State (STATE_IN_TRANSITION);
|
|
|
|
} else if ( OverallState == STATE_CIRCLE_POWER_PLANT ||
|
|
OverallState == STATE_LONG_STRAFING_RUN2 ||
|
|
OverallState == STATE_LAND_IN_TIBERIUM_FIELD)
|
|
{
|
|
Do_Waypath (WID_TO_LIFT_AREA_NORTH);
|
|
Set_Overall_State (STATE_IN_TRANSITION);
|
|
}
|
|
|
|
break;
|
|
|
|
case STATE_LONG_STRAFING_RUN2:
|
|
|
|
if ( OverallState == STATE_CIRCLE_REFINERY ||
|
|
OverallState == STATE_ATTACK_LIFT_AREA) {
|
|
|
|
Do_Waypath (WID_REFINERY_TO_LSTRAFE2);
|
|
Set_Overall_State (STATE_IN_TRANSITION);
|
|
|
|
} else if (OverallState == STATE_VALLEY_STRAFE) {
|
|
|
|
Do_Waypath (WID_VSTRAFE_TO_LSTRAFE2);
|
|
Set_Overall_State (STATE_IN_TRANSITION);
|
|
|
|
} else if ( OverallState == STATE_CIRCLE_POWER_PLANT ||
|
|
OverallState == STATE_LAND_IN_TIBERIUM_FIELD ||
|
|
OverallState == STATE_LONG_STRAFING_RUN)
|
|
{
|
|
Set_Overall_State (STATE_LONG_STRAFING_RUN2);
|
|
}
|
|
|
|
break;
|
|
|
|
case STATE_LONG_STRAFING_RUN:
|
|
|
|
if ( OverallState == STATE_CIRCLE_REFINERY ||
|
|
OverallState == STATE_ATTACK_LIFT_AREA)
|
|
{
|
|
Do_Waypath (WID_REFINERY_TO_LSTRAFE);
|
|
Set_Overall_State (STATE_IN_TRANSITION);
|
|
|
|
} else if (OverallState == STATE_VALLEY_STRAFE) {
|
|
|
|
Do_Waypath (WID_VSTRAFE_TO_LSTRAFE);
|
|
Set_Overall_State (STATE_IN_TRANSITION);
|
|
|
|
} else if ( OverallState == STATE_CIRCLE_POWER_PLANT ||
|
|
OverallState == STATE_LAND_IN_TIBERIUM_FIELD ||
|
|
OverallState == STATE_LONG_STRAFING_RUN2)
|
|
{
|
|
Set_Overall_State (STATE_LONG_STRAFING_RUN);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
NextOverallState = new_state;
|
|
}
|
|
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Set_Overall_State
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void
|
|
SakuraBossGameObj::Set_Overall_State (int new_state)
|
|
{
|
|
if (OverallState != new_state) {
|
|
|
|
switch (new_state)
|
|
{
|
|
case STATE_CIRCLE_POWER_PLANT:
|
|
Pilot.Set_Destination (Vector3 (-63.34F, 21.26F, 26.0F));
|
|
Pilot.Set_Mode (PilotClass::MODE_CIRCLE_POINT);
|
|
Pilot.Set_Circle_Bounds (DEG_TO_RADF (100), DEG_TO_RADF (225));
|
|
Pilot.Set_Circle_Dist (30);
|
|
break;
|
|
|
|
case STATE_CIRCLE_REFINERY:
|
|
{
|
|
Vector3 destination (-166.0F, 40.13F, 42.23F);
|
|
Pilot.Set_Destination (destination);
|
|
Pilot.Set_Mode (PilotClass::MODE_HOVER);
|
|
destination += Vector3 (0, -10, 0);
|
|
Pilot.Set_Target (&destination);
|
|
}
|
|
break;
|
|
|
|
case STATE_ATTACK_LIFT_AREA:
|
|
{
|
|
Vector3 destination (-161.78F, 79.03F, 18.88F);
|
|
Pilot.Set_Destination (destination);
|
|
Pilot.Set_Mode (PilotClass::MODE_HOVER);
|
|
|
|
destination.X += 10;
|
|
Pilot.Set_Target (&destination);
|
|
}
|
|
break;
|
|
|
|
case STATE_LONG_STRAFING_RUN2:
|
|
Pilot.Set_Max_Speed (100.0F);
|
|
Do_Waypath (WID_LONG_STRAFING_RUN2);
|
|
break;
|
|
|
|
case STATE_LONG_STRAFING_RUN:
|
|
Pilot.Set_Max_Speed (100.0F);
|
|
Do_Waypath (WID_LONG_STRAFING_RUN);
|
|
break;
|
|
|
|
case STATE_VALLEY_STRAFE:
|
|
{
|
|
Vector3 destination (-84.33F, 77.99F, 26.77F);
|
|
Pilot.Set_Destination (destination);
|
|
Pilot.Set_Mode (PilotClass::MODE_HOVER);
|
|
}
|
|
break;
|
|
|
|
|
|
case STATE_LAND_IN_TIBERIUM_FIELD:
|
|
{
|
|
Vector3 destination (-107.0F, -10.0F, 15.0F);
|
|
Pilot.Set_Destination (destination);
|
|
Pilot.Set_Mode (PilotClass::MODE_HOVER);
|
|
}
|
|
break;
|
|
|
|
case STATE_IN_TRANSITION:
|
|
FaceTargetInTransition = false;
|
|
break;
|
|
}
|
|
|
|
OverallState = new_state;
|
|
|
|
//
|
|
// Force a new state change in a little while
|
|
//
|
|
OverallStateTimeLeft = WWMath::Random_Float (5.0F, 10.0F);
|
|
}
|
|
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Should_Change_Overall_State
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
bool
|
|
SakuraBossGameObj::Should_Change_Overall_State (void)
|
|
{
|
|
if (OverallState == STATE_IN_TRANSITION || VehicleState == VEHICLE_STATE_MOVING) {
|
|
return false;
|
|
}
|
|
|
|
bool retval = false;
|
|
|
|
//
|
|
// We need to change states if our timer has told us to...
|
|
//
|
|
OverallStateTimeLeft -= TimeManager::Get_Frame_Seconds ();
|
|
if (OverallStateTimeLeft <= 0) {
|
|
retval = true;
|
|
} else if (CurrentTarget != NULL) {
|
|
|
|
switch (OverallState)
|
|
{
|
|
case STATE_CIRCLE_POWER_PLANT:
|
|
|
|
if (TargetPos.Z < 12.0F) {
|
|
retval = true;
|
|
}
|
|
|
|
break;
|
|
|
|
case STATE_CIRCLE_REFINERY:
|
|
|
|
if (TargetPos.X > -91.0F || TargetPos.Y > 69.0F) {
|
|
retval = true;
|
|
}
|
|
|
|
break;
|
|
|
|
case STATE_ATTACK_LIFT_AREA:
|
|
|
|
if (TargetPos.X > -100.0F || TargetPos.Y < 50.0F) {
|
|
retval = true;
|
|
}
|
|
|
|
break;
|
|
|
|
case STATE_LAND_IN_TIBERIUM_FIELD:
|
|
|
|
if (TargetPos.X > -119.0F && TargetPos.X < -95.0F && TargetPos.Y < 0.0F) {
|
|
retval = true;
|
|
}
|
|
|
|
break;
|
|
|
|
case STATE_VALLEY_STRAFE:
|
|
|
|
if (TargetPos.Y < 20.0F && (TargetPos.X < -138.0F || TargetPos.X > -78.0F)) {
|
|
retval = true;
|
|
}
|
|
|
|
break;
|
|
|
|
case STATE_LONG_STRAFING_RUN:
|
|
|
|
if (TargetPos.X > -100.0F && TargetPos.Y > 35.0F) {
|
|
retval = true;
|
|
}
|
|
|
|
break;
|
|
|
|
case STATE_LONG_STRAFING_RUN2:
|
|
|
|
if (TargetPos.X > -80.0F || TargetPos.Y < 10.0F) {
|
|
retval = true;
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Decide_New_Overall_State
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void
|
|
SakuraBossGameObj::Decide_New_Overall_State (void)
|
|
{
|
|
//
|
|
// Do we need to change state now?
|
|
//
|
|
if (Should_Change_Overall_State ()) {
|
|
|
|
//
|
|
// If we are going to change states, then stop attacking the target
|
|
//
|
|
Stop_Attacking ();
|
|
|
|
//
|
|
// If we've been injured enough, bump up the long strafing run possibility
|
|
//
|
|
if (CurrentHealth < 0.45F && FreeRandom.Get_Int (4) == 1) {
|
|
|
|
//
|
|
// Flip a coin and do a strafing run
|
|
//
|
|
if (FreeRandom.Get_Int (2) == 0) {
|
|
Request_Overall_State (STATE_LONG_STRAFING_RUN);
|
|
} else {
|
|
Request_Overall_State (STATE_LONG_STRAFING_RUN2);
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// Based on this position determine which state we want to go to...
|
|
//
|
|
if ( TargetPos.X < -104.0F &&
|
|
TargetPos.Y > 69.0F && TargetPos.Y < 85.8F)
|
|
{
|
|
Request_Overall_State (STATE_ATTACK_LIFT_AREA);
|
|
|
|
} else if ( OverallState != STATE_CIRCLE_REFINERY &&
|
|
TargetPos.X < -125.0F && TargetPos.Y > -19.0F) {
|
|
|
|
Request_Overall_State (STATE_CIRCLE_REFINERY);
|
|
|
|
} else if ( OverallState != STATE_CIRCLE_POWER_PLANT &&
|
|
TargetPos.X > -84.0F && TargetPos.Z > 11.0F) {
|
|
|
|
Request_Overall_State (STATE_CIRCLE_POWER_PLANT);
|
|
|
|
} else if ( OverallState != STATE_LAND_IN_TIBERIUM_FIELD &&
|
|
TargetPos.Y < 39.0F) {
|
|
|
|
Request_Overall_State (STATE_LAND_IN_TIBERIUM_FIELD);
|
|
|
|
} else if (OverallState != STATE_VALLEY_STRAFE) {
|
|
|
|
Request_Overall_State (STATE_VALLEY_STRAFE);
|
|
|
|
} else if (FreeRandom.Get_Int (5) == 1) {
|
|
|
|
//
|
|
// Flip a coin and do a strafing run
|
|
//
|
|
if (FreeRandom.Get_Int (2) == 0) {
|
|
Request_Overall_State (STATE_LONG_STRAFING_RUN);
|
|
} else {
|
|
Request_Overall_State (STATE_LONG_STRAFING_RUN2);
|
|
}
|
|
}
|
|
}
|
|
|
|
OverallStateTimeLeft = WWMath::Random_Float (5.0F, 10.0F);
|
|
}
|
|
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Update_Overall_State
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void
|
|
SakuraBossGameObj::Update_Overall_State (void)
|
|
{
|
|
|
|
switch (OverallState)
|
|
{
|
|
case STATE_CIRCLE_POWER_PLANT:
|
|
|
|
if (CurrentTarget != NULL) {
|
|
Pilot.Set_Circle_Pos (TargetPos);
|
|
} else {
|
|
Pilot.Set_Target (NULL);
|
|
}
|
|
|
|
//
|
|
// Use the gattling guns on the player
|
|
//
|
|
Attack_Target (0, 1);
|
|
break;
|
|
|
|
case STATE_CIRCLE_REFINERY:
|
|
|
|
if (CurrentTarget != NULL) {
|
|
Vector3 destination (-166.0F, 40.13F, 42.23F);
|
|
|
|
bool target_player = true;
|
|
|
|
//
|
|
// Should the commanche 'face' the player or look straight ahead
|
|
//
|
|
if (TargetPos.Y < 21.0F && TargetPos.X < -151.0F) {
|
|
target_player = false;
|
|
}
|
|
|
|
//
|
|
// Have the commanche strafe by the player
|
|
//
|
|
if (TargetPos.X > -140.0F && TargetPos.Y > 34.0F) {
|
|
|
|
destination.X += 10.0F;
|
|
destination.Y = TargetPos.Y + 1.0F;
|
|
destination.Y = min (destination.Y, 78.0F);
|
|
destination.Z -= 20.0F;
|
|
|
|
} else {
|
|
|
|
if (target_player || TargetPos.X > -151.0F) {
|
|
destination.X += 20.0F;
|
|
destination.Z -= 20.0F;
|
|
}
|
|
|
|
//
|
|
// Let the commanche move backwards (if necessary)
|
|
//
|
|
if (TargetPos.Y > -6.0F) {
|
|
destination.Y = TargetPos.Y + 46.0F;
|
|
destination.Y = min (destination.Y, 78.0F);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Don't let the commanche dip below the player's level
|
|
//
|
|
destination.Z = max (TargetPos.Z + 10.0F, destination.Z);
|
|
|
|
Pilot.Set_Destination (destination);
|
|
Pilot.Set_Mode (PilotClass::MODE_HOVER);
|
|
|
|
if (target_player) {
|
|
|
|
Pilot.Set_Target (&TargetPos);
|
|
|
|
} else {
|
|
|
|
Vector3 position;
|
|
Get_Position (&position);
|
|
position += Vector3 (0, -10, 0);
|
|
Pilot.Set_Target (&position);
|
|
}
|
|
|
|
//
|
|
// Start attacking the player if we are within 4 meters of
|
|
// our expected destination
|
|
//
|
|
Vector3 curr_position;
|
|
Get_Position (&curr_position);
|
|
if ((destination - curr_position).Length2 () < 16) {
|
|
|
|
//
|
|
// Use the rockets to attack the player
|
|
//
|
|
Attack_Target (1, 0);
|
|
}
|
|
|
|
} else {
|
|
Pilot.Set_Target (NULL);
|
|
}
|
|
|
|
break;
|
|
|
|
case STATE_ATTACK_LIFT_AREA:
|
|
{
|
|
Vector3 destination (-161.78F, 79.03F, 18.88F);
|
|
destination.Z = max (TargetPos.Z + 7.0F, 18.88F);
|
|
Pilot.Set_Destination (destination);
|
|
|
|
destination.X += 10;
|
|
Pilot.Set_Target (&destination);
|
|
|
|
//
|
|
// Make sure we are attacking the player
|
|
//
|
|
if (IsAttacking == false) {
|
|
|
|
//
|
|
// Attack with the gattling gun if the player is close,
|
|
// otherwise attack with the rockets
|
|
//
|
|
if (TargetPos.X > -130.0F) {
|
|
Attack_Target (1, 0);
|
|
} else {
|
|
Attack_Target (0, 1);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case STATE_LONG_STRAFING_RUN2:
|
|
case STATE_LONG_STRAFING_RUN:
|
|
{
|
|
bool should_fire = false;
|
|
|
|
//
|
|
// Determine if we should start firing or not...
|
|
// We should fire on the target if they are in front of us
|
|
//
|
|
if (CurrentTarget != NULL) {
|
|
|
|
//
|
|
// Transform the target into our coordinate system. Is the target in front of us?
|
|
//
|
|
if (TargetAngle > DEG_TO_RADF (-75) && TargetAngle < DEG_TO_RADF (75)) {
|
|
|
|
Vector3 curr_position;
|
|
Get_Position (&curr_position);
|
|
|
|
//
|
|
// Are we inside the valley? Note: We don't want to fire if we have
|
|
// no chance of hitting the target
|
|
//
|
|
if ( curr_position.Y >= -40.0F && curr_position.Y <= 140.0F &&
|
|
curr_position.X >= -178.0F)
|
|
{
|
|
should_fire = true;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
Pilot.Set_Target (NULL);
|
|
}
|
|
|
|
//
|
|
// Turn the gattling guns on/off
|
|
//
|
|
if (should_fire) {
|
|
Set_Gattling_Gun_State (GATLING_STATE_FIRING);
|
|
} else {
|
|
Set_Gattling_Gun_State (GATLING_STATE_NORMAL);
|
|
}
|
|
|
|
//
|
|
// Restore the vehicle's speed when we've finished moving
|
|
//
|
|
if (VehicleState != VEHICLE_STATE_MOVING) {
|
|
|
|
//
|
|
// Target the player
|
|
//
|
|
Pilot.Set_Target (&TargetPos);
|
|
|
|
//
|
|
// Restore the speed
|
|
//
|
|
Pilot.Set_Max_Speed (20.0F);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case STATE_VALLEY_STRAFE:
|
|
|
|
if (CurrentTarget != NULL) {
|
|
Vector3 destination (-84.33F, 77.99F, 26.77F);
|
|
|
|
if (TargetPos.Y < 42.0F) {
|
|
|
|
//
|
|
// Simply match the player's position along our strafe path
|
|
//
|
|
destination.X = WWMath::Clamp (TargetPos.X, -143.0F, -84.33F);
|
|
} else {
|
|
|
|
//
|
|
// Try to stay targetted on the player by strafing along the path
|
|
//
|
|
if (TargetPos.X > -113.0F) {
|
|
destination.X = WWMath::Clamp (TargetPos.X - 35.0F, -143.0F, -84.33F);
|
|
} else {
|
|
destination.X = WWMath::Clamp (TargetPos.X + 35.0F, -143.0F, -84.33F);
|
|
}
|
|
}
|
|
|
|
Pilot.Set_Destination (destination);
|
|
Pilot.Set_Mode (PilotClass::MODE_HOVER);
|
|
Pilot.Set_Target (&TargetPos);
|
|
|
|
//
|
|
// Start attacking the player if we are within 4 meters of
|
|
// our expected destination
|
|
//
|
|
Vector3 curr_position;
|
|
Get_Position (&curr_position);
|
|
if ((destination - curr_position).Length2 () < 16) {
|
|
|
|
//
|
|
// Make sure we are attacking the player (with either weapon)
|
|
//
|
|
Attack_Target (1, 1);
|
|
}
|
|
|
|
} else {
|
|
Pilot.Set_Target (NULL);
|
|
}
|
|
|
|
break;
|
|
|
|
case STATE_LAND_IN_TIBERIUM_FIELD:
|
|
{
|
|
|
|
if (CurrentTarget != NULL) {
|
|
Pilot.Set_Target (&TargetPos);
|
|
} else {
|
|
Pilot.Set_Target (NULL);
|
|
}
|
|
|
|
|
|
//
|
|
// Start attacking the player if we are within 4 meters of
|
|
// our expected destination
|
|
//
|
|
Vector3 curr_position;
|
|
Vector3 destination = Pilot.Get_Destination ();
|
|
Get_Position (&curr_position);
|
|
if ((destination - curr_position).Length2 () < 16) {
|
|
|
|
//
|
|
// Attack the player with either weapon
|
|
//
|
|
Attack_Target (1, 1);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case STATE_IN_TRANSITION:
|
|
|
|
//
|
|
// If we've finished moving, then we are done with the transition
|
|
// and are ready to move on to the new state.
|
|
//
|
|
if (VehicleState != VEHICLE_STATE_MOVING) {
|
|
Set_Overall_State (NextOverallState);
|
|
} else if (FaceTargetInTransition) {
|
|
|
|
if (CurrentTarget != NULL) {
|
|
Pilot.Set_Target (&TargetPos);
|
|
} else {
|
|
Pilot.Set_Target (NULL);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Update_Tilt
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void
|
|
SakuraBossGameObj::Update_Tilt (void)
|
|
{
|
|
const float TILT_RATE = DEG_TO_RADF (25.0F);
|
|
|
|
//
|
|
// Lookup the model we are going to tilt
|
|
//
|
|
RenderObjClass *model = Peek_Model ();
|
|
WWASSERT (model != NULL);
|
|
|
|
if (ChopperTiltBoneIndex > 0) {
|
|
|
|
//
|
|
// Determine what the maximum tilt allowed is...
|
|
//
|
|
float max_tilt = DEG_TO_RADF (35.0F);
|
|
if (OverallState == STATE_LAND_IN_TIBERIUM_FIELD) {
|
|
max_tilt = DEG_TO_RADF (10.0F);
|
|
}
|
|
|
|
//
|
|
// Decide if we should be in a tilted or normal state
|
|
//
|
|
if (OverallState != STATE_IN_TRANSITION) {
|
|
|
|
//
|
|
// Determine how far we need to tile to look straight at the player
|
|
//
|
|
float target_angle = DEG_TO_RADF (25.0F);
|
|
if (CurrentTarget != NULL) {
|
|
|
|
//
|
|
// Get the target's position relative to ours
|
|
//
|
|
Matrix3D untilted_tm (1);
|
|
untilted_tm.Rotate_Z (Get_Transform ().Get_Z_Rotation ());
|
|
untilted_tm.Set_Translation (Get_Transform ().Get_Translation ());
|
|
Vector3 obj_space_target;
|
|
Matrix3D::Inverse_Transform_Vector (untilted_tm, TargetPos, &obj_space_target);
|
|
|
|
//
|
|
// How far do we need to tilt to be facing the player?
|
|
//
|
|
target_angle = -WWMath::Atan2 (obj_space_target.Z, obj_space_target.X);
|
|
target_angle = WWMath::Clamp (target_angle, -max_tilt, max_tilt);
|
|
}
|
|
|
|
//
|
|
// Increate our tilt percent
|
|
//
|
|
if (TiltAngle < target_angle) {
|
|
TiltAngle += TimeManager::Get_Frame_Seconds () * TILT_RATE;
|
|
TiltAngle = min (TiltAngle, target_angle);
|
|
} else {
|
|
TiltAngle -= TimeManager::Get_Frame_Seconds () * TILT_RATE;
|
|
TiltAngle = max (TiltAngle, target_angle);
|
|
}
|
|
|
|
//
|
|
// Check to see if we should start the tilting process
|
|
//
|
|
if (!model->Is_Bone_Captured (ChopperTiltBoneIndex)) {
|
|
model->Capture_Bone (ChopperTiltBoneIndex);
|
|
TiltAngle = 0;
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// Decrease our tilt percent
|
|
//
|
|
TiltAngle -= (TimeManager::Get_Frame_Seconds () * TILT_RATE);
|
|
TiltAngle = max (TiltAngle, 0.0F);
|
|
|
|
//
|
|
// Check to see if we should release our control of the bone
|
|
//
|
|
if (TiltAngle == 0 && model->Is_Bone_Captured (ChopperTiltBoneIndex)) {
|
|
model->Release_Bone (ChopperTiltBoneIndex);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Make sure our percent is clamped between 0 and 1
|
|
//
|
|
//TiltPercent = WWMath::Clamp (TiltPercent, 0, 1.0F);
|
|
|
|
//
|
|
// Do we need to tilt the bone?
|
|
//
|
|
if (model->Is_Bone_Captured (ChopperTiltBoneIndex)) {
|
|
|
|
//
|
|
// Tilt the model up to 25 degrees
|
|
//
|
|
Matrix3D bone_tm (1);
|
|
bone_tm.Rotate_Y (TiltAngle);
|
|
model->Control_Bone (ChopperTiltBoneIndex, bone_tm);
|
|
}
|
|
}
|
|
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Attack_Target
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void
|
|
SakuraBossGameObj::Attack_Target
|
|
(
|
|
float rocket_priority,
|
|
float gattling_priority
|
|
)
|
|
{
|
|
//
|
|
// Don't attack if we don't have a target, or we are already attacking
|
|
//
|
|
if (CurrentTarget == NULL || IsAttacking || SakuraState != SAKURA_STATE_NORMAL) {
|
|
return ;
|
|
}
|
|
|
|
//
|
|
// There's a 1 in 6 chance that Sakura will taunt the player
|
|
// instead of attacking
|
|
//
|
|
if (FreeRandom.Get_Int (4) == 2) {
|
|
Set_Sakura_State (SAKURA_STATE_TAUNTING);
|
|
} else {
|
|
|
|
//
|
|
// If both rockets are gone, then decrease its priority so
|
|
// we won't choose them
|
|
//
|
|
if ( ((AvailableWeapons & WEAPON_ROCKETS_LEFT) == 0) &&
|
|
((AvailableWeapons & WEAPON_ROCKETS_RIGHT) == 0))
|
|
{
|
|
rocket_priority = gattling_priority - 1.0F;
|
|
}
|
|
|
|
//
|
|
// Decide which weapon to use
|
|
//
|
|
if (rocket_priority > gattling_priority) {
|
|
Set_Rocket_State (ROCKET_STATE_OPENING);
|
|
} else if (gattling_priority > rocket_priority) {
|
|
Set_Gattling_Gun_State (GATLING_STATE_REVVING_UP);
|
|
} else {
|
|
|
|
//
|
|
// Priorities are equal, so toss a coin and pick a weapon
|
|
//
|
|
if (FreeRandom.Get_Int (2) == 1) {
|
|
Set_Rocket_State (ROCKET_STATE_OPENING);
|
|
} else {
|
|
Set_Gattling_Gun_State (GATLING_STATE_REVVING_UP);
|
|
}
|
|
}
|
|
}
|
|
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Stop_Attacking
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void
|
|
SakuraBossGameObj::Stop_Attacking (void)
|
|
{
|
|
//
|
|
// Force the rockets closed
|
|
//
|
|
if (RocketLauncherState != ROCKET_STATE_CLOSED) {
|
|
Set_Rocket_State (ROCKET_STATE_CLOSING);
|
|
}
|
|
|
|
//
|
|
// Force the gattling gun to stop
|
|
//
|
|
if (GattlingGunState != GATLING_STATE_NORMAL) {
|
|
Set_Gattling_Gun_State (GATLING_STATE_NORMAL);
|
|
}
|
|
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Set_Vehicle_State
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void
|
|
SakuraBossGameObj::Set_Vehicle_State (int new_state)
|
|
{
|
|
if (VehicleState != new_state) {
|
|
|
|
switch (new_state)
|
|
{
|
|
case VEHICLE_STATE_HOVERING:
|
|
case VEHICLE_STATE_MOVING:
|
|
break;
|
|
}
|
|
|
|
VehicleState = new_state;
|
|
}
|
|
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Update_Vehicle_State
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void
|
|
SakuraBossGameObj::Update_Vehicle_State (void)
|
|
{
|
|
//
|
|
// If we have a current target, then check to make sure
|
|
// we are pointing all our weapons at that target
|
|
//
|
|
if (TargetAngle > DEG_TO_RADF (-90) && TargetAngle < DEG_TO_RADF (90)) {
|
|
Set_Targeting (TargetPos, true);
|
|
} else {
|
|
Vector3 target_pos = Get_Transform () * Vector3 (100.0F, 0, 0) ;
|
|
Set_Targeting (target_pos, true);
|
|
}
|
|
|
|
switch (VehicleState)
|
|
{
|
|
case VEHICLE_STATE_HOVERING:
|
|
break;
|
|
|
|
case VEHICLE_STATE_MOVING:
|
|
{
|
|
//
|
|
// Get the next point on the path
|
|
//
|
|
Vector3 curr_pos;
|
|
Vector3 next_pos;
|
|
Get_Position (&curr_pos);
|
|
|
|
const AABoxClass &bounding_box = Peek_Model ()->Get_Bounding_Box ();
|
|
Vector3 center_to_tip_vector;
|
|
Matrix3D::Rotate_Vector (Get_Transform (), Vector3 (bounding_box.Extent.X / 2.0F, 0, 0), ¢er_to_tip_vector);
|
|
Vector3 tip_curr_pos = curr_pos + center_to_tip_vector;
|
|
tip_curr_pos.Z = curr_pos.Z;
|
|
|
|
Path->Evaluate_Next_Point (tip_curr_pos, next_pos);
|
|
PathClass::STATE_DESC result = Path->Get_State ();
|
|
|
|
//
|
|
// Check to see if we need to continue along the path of
|
|
// if we are finished
|
|
//
|
|
if (Pilot.Get_Mode () == PilotClass::MODE_HOVER) {
|
|
Set_Vehicle_State (VEHICLE_STATE_HOVERING);
|
|
} else if (result < PathClass::FIRST_ERROR) {
|
|
Pilot.Set_Next_Point (next_pos);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Set_Sakura_State
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void
|
|
SakuraBossGameObj::Set_Sakura_State (int new_state)
|
|
{
|
|
if (SakuraState != new_state) {
|
|
|
|
switch (new_state)
|
|
{
|
|
case SAKURA_STATE_NORMAL:
|
|
break;
|
|
|
|
case SAKURA_STATE_TAUNTING:
|
|
{
|
|
//
|
|
// Find the first available taunt
|
|
//
|
|
int taunt_id = 0;
|
|
for (int index = 0; index < MAX_TAUNTS; index ++) {
|
|
if (TauntList[index] != 0) {
|
|
taunt_id = TauntList[index] - 1;
|
|
TauntList[index] = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Reshuffle the taunts if necessary
|
|
//
|
|
if (index >= (MAX_TAUNTS - 1)) {
|
|
Shuffle_Taunt_List ();
|
|
}
|
|
|
|
//
|
|
// Default to a 2 second taunt deault
|
|
//
|
|
SakuraTauntTimeLeft = 2.0F;
|
|
|
|
//
|
|
// Lookup the conversation for this taunt
|
|
//
|
|
ConversationClass *conv = ConversationMgrClass::Find_Conversation (TAUNT_IDS[taunt_id]);
|
|
if (conv != NULL) {
|
|
ActiveConversationClass *taunt_conversation = ConversationMgrClass::Create_New_Conversation (conv);
|
|
if (taunt_conversation != NULL) {
|
|
|
|
//
|
|
// Add Sakura and the player to the list
|
|
//
|
|
taunt_conversation->Add_Orator (NULL);
|
|
OratorClass *orator = taunt_conversation->Add_Orator (COMBAT_STAR);
|
|
orator->Set_Flag (OratorClass::FLAG_DONT_MOVE, true);
|
|
orator->Set_Flag (OratorClass::FLAG_DONT_TURN_HEAD, false);
|
|
orator->Set_Flag (OratorClass::FLAG_DONT_FACE, true);
|
|
|
|
SakuraTauntTimeLeft = taunt_conversation->Get_Conversation_Time ();
|
|
|
|
//
|
|
// Start the conversation
|
|
//
|
|
taunt_conversation->Start_Conversation ();
|
|
REF_PTR_RELEASE (taunt_conversation);
|
|
}
|
|
|
|
REF_PTR_RELEASE (conv);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
SakuraState = new_state;
|
|
}
|
|
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Shuffle_Taunt_List
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void
|
|
SakuraBossGameObj::Shuffle_Taunt_List (void)
|
|
{
|
|
::memset (TauntList, 0, sizeof (TauntList));
|
|
|
|
//
|
|
// Grab an entry from the taunt id list
|
|
//
|
|
for (int index = 0; index < MAX_TAUNTS; index ++) {
|
|
|
|
//
|
|
// Choose a random list entry to plug this taunt ID into
|
|
//
|
|
int list_index = 0;
|
|
do
|
|
{
|
|
list_index = FreeRandom.Get_Int ( MAX_TAUNTS );
|
|
} while (TauntList[list_index] != 0);
|
|
|
|
//
|
|
// Stick this taunt ID into the list
|
|
//
|
|
TauntList[list_index] = index + 1;
|
|
}
|
|
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Update_Sakura_State
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void
|
|
SakuraBossGameObj::Update_Sakura_State (void)
|
|
{
|
|
switch (SakuraState)
|
|
{
|
|
case SAKURA_STATE_NORMAL:
|
|
break;
|
|
|
|
case SAKURA_STATE_TAUNTING:
|
|
SakuraTauntTimeLeft -= TimeManager::Get_Frame_Seconds ();
|
|
if (SakuraTauntTimeLeft <= 0) {
|
|
Set_Sakura_State (SAKURA_STATE_NORMAL);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Set_Gattling_Gun_State
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void
|
|
SakuraBossGameObj::Set_Gattling_Gun_State (int new_state)
|
|
{
|
|
if (GattlingGunState != new_state) {
|
|
|
|
switch (new_state)
|
|
{
|
|
case GATLING_STATE_NORMAL:
|
|
GattlingGun->Set_Primary_Triggered (false);
|
|
break;
|
|
|
|
case GATLING_STATE_REVVING_UP:
|
|
{
|
|
AudibleSoundClass *sound = WWAudioClass::Get_Instance()->Create_Sound (Get_Definition().GattlingGunRevSoundDefID);
|
|
if (sound != NULL) {
|
|
|
|
//
|
|
// We will continue to 'rev' the gatling gun until the sound ends
|
|
//
|
|
GattlingGunStateTimeLeft = (sound->Get_Duration () / 1000.0F);
|
|
|
|
//
|
|
// Attach the sound to the gatling gun's muzzle, and add it to the world
|
|
//
|
|
sound->Attach_To_Object (Peek_Model (), GATLING_MUZZLE);
|
|
sound->Add_To_Scene (true);
|
|
REF_PTR_RELEASE (sound);
|
|
} else {
|
|
GattlingGunStateTimeLeft = 1.0F;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case GATLING_STATE_FIRING:
|
|
|
|
//
|
|
// Pick a random amount of time to fire at the player
|
|
//
|
|
GattlingGunStateTimeLeft = WWMath::Random_Float (5.0F, 10.0F);
|
|
if (GattlingGun != NULL) {
|
|
|
|
//
|
|
// Start firing the gatling gun at the player
|
|
//
|
|
GattlingGun->Set_Primary_Triggered (true);
|
|
}
|
|
break;
|
|
}
|
|
|
|
GattlingGunState = new_state;
|
|
}
|
|
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Update_Gattling_Gun_State
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void
|
|
SakuraBossGameObj::Update_Gattling_Gun_State (void)
|
|
{
|
|
switch ( GattlingGunState )
|
|
{
|
|
case GATLING_STATE_REVVING_UP:
|
|
GattlingGunStateTimeLeft -= TimeManager::Get_Frame_Seconds ();
|
|
if (GattlingGunStateTimeLeft <= 0) {
|
|
|
|
//
|
|
// We've finished revving up the gatling gun, so now
|
|
// start firing at the player
|
|
//
|
|
Set_Gattling_Gun_State (GATLING_STATE_FIRING);
|
|
}
|
|
break;
|
|
|
|
case GATLING_STATE_NORMAL:
|
|
break;
|
|
|
|
case GATLING_STATE_FIRING:
|
|
|
|
//
|
|
// Target the player
|
|
//
|
|
ScriptableGameObj *target = CurrentTarget.Get_Ptr ();
|
|
if (target != NULL) {
|
|
Set_Targeting (TargetPos, true);
|
|
}
|
|
|
|
//
|
|
// Stop firing when we've shot at the player for a few seconds
|
|
//
|
|
GattlingGunStateTimeLeft -= TimeManager::Get_Frame_Seconds ();
|
|
if (GattlingGunStateTimeLeft <= 0) {
|
|
Set_Gattling_Gun_State (GATLING_STATE_NORMAL);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Set_Rocket_State
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void
|
|
SakuraBossGameObj::Set_Rocket_State (int new_state)
|
|
{
|
|
if (RocketLauncherState != new_state) {
|
|
|
|
switch (new_state)
|
|
{
|
|
case ROCKET_STATE_OPENING:
|
|
{
|
|
//
|
|
// Play the door open sound on the left rocket launcher
|
|
//
|
|
if (AvailableWeapons & WEAPON_ROCKETS_LEFT) {
|
|
Matrix3D left_tm = Peek_Model ()->Get_Bone_Transform (LEFT_ROCKET_MUZZLE);
|
|
WWAudioClass::Get_Instance()->Create_Instant_Sound (Get_Definition ().RocketDoorOpenSoundID, left_tm);
|
|
}
|
|
|
|
//
|
|
// Play the door open sound on the right rocket launcher
|
|
//
|
|
if (AvailableWeapons & WEAPON_ROCKETS_RIGHT) {
|
|
Matrix3D right_tm = Peek_Model ()->Get_Bone_Transform (RIGHT_ROCKET_MUZZLE);
|
|
WWAudioClass::Get_Instance()->Create_Instant_Sound (Get_Definition ().RocketDoorOpenSoundID, right_tm);
|
|
}
|
|
|
|
//
|
|
// Open the rocket doors
|
|
//
|
|
Open_Rocket_Launchers (true);
|
|
}
|
|
break;
|
|
|
|
case ROCKET_STATE_CLOSING:
|
|
{
|
|
//
|
|
// Close the rocket doors
|
|
//
|
|
Open_Rocket_Launchers (false);
|
|
|
|
//
|
|
// Stop firing
|
|
//
|
|
if (RockerLauncherLeft != NULL && RockerLauncherRight != NULL) {
|
|
RockerLauncherLeft->Set_Primary_Triggered (false);
|
|
RockerLauncherRight->Set_Primary_Triggered (false);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ROCKET_STATE_FIRING:
|
|
{
|
|
//
|
|
// Pick a random amount of time to fire at the player
|
|
//
|
|
RocketLauncherStateTimeLeft = WWMath::Random_Float (2.0F, 6.0F);
|
|
if (RockerLauncherLeft != NULL && RockerLauncherRight != NULL) {
|
|
|
|
RockerLauncherLeft->Set_Target (TargetPos);
|
|
RockerLauncherRight->Set_Target (TargetPos);
|
|
|
|
//
|
|
// Start launching rockets at the player
|
|
//
|
|
if (AvailableWeapons & WEAPON_ROCKETS_LEFT) {
|
|
RockerLauncherLeft->Set_Primary_Triggered (true);
|
|
}
|
|
|
|
if (AvailableWeapons & WEAPON_ROCKETS_RIGHT) {
|
|
RockerLauncherRight->Set_Primary_Triggered (true);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ROCKET_STATE_OPEN:
|
|
RocketLauncherStateTimeLeft = 2.0F;
|
|
break;
|
|
|
|
case ROCKET_STATE_CLOSED:
|
|
break;
|
|
}
|
|
|
|
RocketLauncherState = new_state;
|
|
}
|
|
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Update_Rocket_State
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void
|
|
SakuraBossGameObj::Update_Rocket_State (void)
|
|
{
|
|
switch ( RocketLauncherState )
|
|
{
|
|
case ROCKET_STATE_OPENING:
|
|
{
|
|
//
|
|
// Are the rocket launcher doors completely open?
|
|
//
|
|
AnimControlClass *anim_control = Get_Anim_Control ();
|
|
if (anim_control != NULL && anim_control->Get_Current_Frame () == ROCKETS_OUT_FRAME_NUM) {
|
|
Set_Rocket_State (ROCKET_STATE_OPEN);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ROCKET_STATE_CLOSING:
|
|
{
|
|
//
|
|
// Are the rocket launcher doors completely closed?
|
|
//
|
|
AnimControlClass *anim_control = Get_Anim_Control ();
|
|
if (anim_control != NULL && anim_control->Get_Current_Frame () == ROCKETS_IN_FRAME_NUM) {
|
|
Set_Rocket_State (ROCKET_STATE_CLOSED);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ROCKET_STATE_FIRING:
|
|
{
|
|
RockerLauncherLeft->Set_Target (TargetPos);
|
|
RockerLauncherRight->Set_Target (TargetPos);
|
|
|
|
RocketLauncherStateTimeLeft -= TimeManager::Get_Frame_Seconds ();
|
|
if (RocketLauncherStateTimeLeft <= 0) {
|
|
|
|
//
|
|
// Stop firing
|
|
//
|
|
RockerLauncherLeft->Set_Primary_Triggered (false);
|
|
RockerLauncherRight->Set_Primary_Triggered (false);
|
|
|
|
//
|
|
// Close the rocket doors
|
|
//
|
|
Set_Rocket_State (ROCKET_STATE_CLOSING);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ROCKET_STATE_OPEN:
|
|
{
|
|
RocketLauncherStateTimeLeft -= TimeManager::Get_Frame_Seconds ();
|
|
if (RocketLauncherStateTimeLeft <= 0) {
|
|
|
|
//
|
|
// Commence firing
|
|
//
|
|
Set_Rocket_State (ROCKET_STATE_FIRING);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ROCKET_STATE_CLOSED:
|
|
break;
|
|
}
|
|
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Apply_Damage
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void
|
|
SakuraBossGameObj::Apply_Damage_Extended
|
|
(
|
|
const OffenseObjectClass & damager,
|
|
float scale,
|
|
const Vector3 & direction,
|
|
const char * collision_box_name
|
|
)
|
|
{
|
|
if ( !CombatManager::I_Am_Server() ) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// If rockets are out, and the player shot the rocket, then
|
|
// apply the damage to the rocket (not the whole vehicle)
|
|
//
|
|
bool damaged_rockets = false;
|
|
if (RocketLauncherState != ROCKET_STATE_CLOSED && collision_box_name != NULL) {
|
|
|
|
if (::strstr (collision_box_name, LEFT_ROCKET_MESH) != NULL) {
|
|
damaged_rockets = true;
|
|
|
|
//
|
|
// Apply the damage to the left rocket launcher
|
|
//
|
|
float health_left = LeftRocketDefenseObject.Apply_Damage (damager, scale);
|
|
if (health_left <= 0) {
|
|
Blow_Off_Weapon (WEAPON_ROCKETS_LEFT);
|
|
}
|
|
|
|
} else if (::strstr (collision_box_name, RIGHT_ROCKET_MESH) != NULL) {
|
|
damaged_rockets = true;
|
|
|
|
//
|
|
// Apply the damage to the right rocket launcher
|
|
//
|
|
float health_left = RightRocketDefenseObject.Apply_Damage (damager, scale);
|
|
if (health_left <= 0) {
|
|
Blow_Off_Weapon (WEAPON_ROCKETS_RIGHT);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the rockets weren't specifically targetted, then let the damage occur normally
|
|
//
|
|
if (damaged_rockets == false) {
|
|
VehicleGameObj::Apply_Damage_Extended (damager, scale, direction, collision_box_name);
|
|
}
|
|
|
|
//
|
|
// Remember who was last shooting at us
|
|
//
|
|
LastDamager = damager.Get_Owner ();
|
|
CurrentTarget = LastDamager;
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Blow_Off_Weapon
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void
|
|
SakuraBossGameObj::Blow_Off_Weapon (int weapon_id)
|
|
{
|
|
Matrix3D tm (1);
|
|
|
|
//
|
|
// Determine where to create the explosion at
|
|
//
|
|
if (weapon_id == WEAPON_ROCKETS_LEFT) {
|
|
tm = Peek_Model ()->Get_Bone_Transform (LEFT_ROCKET_MUZZLE);
|
|
} else {
|
|
tm = Peek_Model ()->Get_Bone_Transform (RIGHT_ROCKET_MUZZLE);
|
|
}
|
|
|
|
//
|
|
// Create an explosion
|
|
//
|
|
ExplosionManager::Create_Explosion_At (Get_Definition ().RocketDestroyedExplosionID, tm, NULL);
|
|
|
|
//
|
|
// Find the rocket launcher model
|
|
//
|
|
RenderObjClass *weapon_model = NULL;
|
|
if (weapon_id == WEAPON_ROCKETS_LEFT) {
|
|
weapon_model = Peek_Model ()->Get_Sub_Object_By_Name (LEFT_ROCKET_MESH);
|
|
} else {
|
|
weapon_model = Peek_Model ()->Get_Sub_Object_By_Name (RIGHT_ROCKET_MESH);
|
|
}
|
|
|
|
//
|
|
// Remove the destroyed rocket launcher model from the apache
|
|
//
|
|
if (weapon_model != NULL) {
|
|
Peek_Model ()->Remove_Sub_Object (weapon_model);
|
|
REF_PTR_RELEASE (weapon_model);
|
|
}
|
|
|
|
//
|
|
// 'Shake' the vehicle
|
|
//
|
|
PhysClass *phys_obj = Peek_Physical_Object ();
|
|
if (phys_obj != NULL && phys_obj->As_RigidBodyClass () != NULL) {
|
|
RigidBodyClass *rigid_body = phys_obj->As_RigidBodyClass ();
|
|
|
|
Vector3 center = Get_Transform ().Get_Translation ();
|
|
Vector3 y_axis = Get_Transform ().Get_Y_Vector ();
|
|
|
|
//
|
|
// Calculate a position 1 meter to the left and right of the
|
|
// apache's center of mass
|
|
//
|
|
Vector3 left_imp_pos = center + y_axis;
|
|
Vector3 right_imp_pos = center - y_axis;
|
|
|
|
//
|
|
// Determine how much impulse to apply on each side
|
|
//
|
|
Vector3 left_imp (0, 0, rigid_body->Get_Mass () * 10);
|
|
Vector3 right_imp (0, 0, -rigid_body->Get_Mass () * 10);
|
|
if (weapon_id == WEAPON_ROCKETS_RIGHT) {
|
|
left_imp.Z = -left_imp.Z;
|
|
right_imp.Z = -right_imp.Z;
|
|
}
|
|
|
|
//
|
|
// Make the vehicle 'react' to the explosion
|
|
//
|
|
rigid_body->Apply_Impulse (left_imp, left_imp_pos);
|
|
rigid_body->Apply_Impulse (right_imp, right_imp_pos);
|
|
}
|
|
|
|
//
|
|
// Make sure the weapon stops firing
|
|
//
|
|
if (weapon_id == WEAPON_ROCKETS_LEFT) {
|
|
RockerLauncherLeft->Set_Primary_Triggered (false);
|
|
} else {
|
|
RockerLauncherRight->Set_Primary_Triggered (false);
|
|
}
|
|
|
|
//
|
|
// Remove the weapon from the list of available weapons
|
|
//
|
|
AvailableWeapons &= ~weapon_id;
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Open_Rocket_Launchers
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void
|
|
SakuraBossGameObj::Open_Rocket_Launchers (bool onoff)
|
|
{
|
|
AnimControlClass *anim_control = Get_Anim_Control ();
|
|
if (anim_control != NULL) {
|
|
|
|
//
|
|
// Set the rocket-door animation
|
|
//
|
|
anim_control->Set_Animation (ROCKET_DOOR_ANIMATION);
|
|
anim_control->Set_Mode (ANIM_MODE_TARGET);
|
|
anim_control->Set_Target_Frame (onoff ? ROCKETS_OUT_FRAME_NUM : ROCKETS_IN_FRAME_NUM);
|
|
}
|
|
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Are_Rocker_Launchers_Ready
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
bool
|
|
SakuraBossGameObj::Are_Rocker_Launchers_Ready (void)
|
|
{
|
|
bool retval = false;
|
|
|
|
//
|
|
// Rocket launchers are ready if the animation has been played to end
|
|
//
|
|
AnimControlClass *anim_control = Get_Anim_Control ();
|
|
if (anim_control != NULL) {
|
|
retval = (anim_control->Get_Current_Frame () == ROCKETS_OUT_FRAME_NUM);
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Move_To_Location
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void
|
|
SakuraBossGameObj::Move_To_Location (const Vector3 &pos, float speed)
|
|
{
|
|
MoveToLocation = pos;
|
|
Set_Vehicle_State (VEHICLE_STATE_MOVING);
|
|
|
|
//
|
|
// Create an action that will be responsible for moving the vehicle
|
|
// to this location.
|
|
//
|
|
Pilot.Set_Mode (PilotClass::MODE_FLY_TO_POINT);
|
|
Pilot.Set_Destination (pos);
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Get_Muzzle
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
const Matrix3D &
|
|
SakuraBossGameObj::Get_Muzzle (int /*index*/)
|
|
{
|
|
CurrentMuzzleTM = Get_Transform ();
|
|
|
|
RenderObjClass *model = Peek_Model ();
|
|
if (model != NULL) {
|
|
|
|
if (GattlingGunState == GATLING_STATE_FIRING) {
|
|
|
|
//
|
|
// Simply return the muzzle bone of the gatling gun
|
|
//
|
|
CurrentMuzzleTM = model->Get_Bone_Transform (GATLING_MUZZLE);
|
|
|
|
} else {
|
|
|
|
|
|
//
|
|
// Toggle rocket launchers.
|
|
//
|
|
// Note: This is done because we use a separate
|
|
// weapon for each rocket launcher. However, the weapon asks
|
|
// us for a muzzle without telling us which weapon it is, so
|
|
// to simulate attacking from both sides simultaneously we alternate
|
|
// between the two muzzles.
|
|
//
|
|
//CurrentMuzzleIndex = !CurrentMuzzleIndex;
|
|
|
|
//
|
|
// Make sure we aren't attacking from a side that's been blown off.
|
|
//
|
|
if ((AvailableWeapons & WEAPON_ROCKETS_LEFT) == 0) {
|
|
CurrentMuzzleIndex = ROCKET_RIGHT;
|
|
} else if ((AvailableWeapons & WEAPON_ROCKETS_RIGHT) == 0) {
|
|
CurrentMuzzleIndex = ROCKET_LEFT;
|
|
}
|
|
|
|
if (CurrentMuzzleIndex == ROCKET_RIGHT) {
|
|
CurrentMuzzleTM = model->Get_Bone_Transform (RIGHT_ROCKET_MUZZLE);
|
|
} else {
|
|
CurrentMuzzleTM = model->Get_Bone_Transform (LEFT_ROCKET_MUZZLE);
|
|
}
|
|
}
|
|
}
|
|
|
|
return CurrentMuzzleTM;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Update_Target
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void
|
|
SakuraBossGameObj::Update_Target (void)
|
|
{
|
|
//
|
|
// Check to see if its time to choose a new target yet
|
|
//
|
|
TargetTimeLeft -= TimeManager::Get_Frame_Seconds ();
|
|
if (TargetTimeLeft <= 0) {
|
|
|
|
//
|
|
// Choose either the closest commando or the last person to damage us
|
|
//
|
|
if (FreeRandom.Get_Int (2) == 0 && LastDamager != NULL) {
|
|
CurrentTarget = LastDamager;
|
|
} else {
|
|
CurrentTarget.Set_Ptr (Find_Closest_Human_Player ());
|
|
}
|
|
|
|
//
|
|
// Wait a random amount of time before we choose a new target
|
|
//
|
|
TargetTimeLeft = WWMath::Random_Float (5.0F, 20.0F);
|
|
} else if (CurrentTarget == NULL) {
|
|
TargetTimeLeft = TargetTimeLeft * 0.25F;
|
|
}
|
|
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Find_Closest_Human_Player
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
SoldierGameObj *
|
|
SakuraBossGameObj::Find_Closest_Human_Player (void)
|
|
{
|
|
SoldierGameObj *closest_human_player = NULL;
|
|
float closest_distance = 9999.0F;
|
|
|
|
//
|
|
// Get our current position
|
|
//
|
|
Vector3 sakura_position;
|
|
Get_Position (&sakura_position);
|
|
|
|
AABoxClass valley_box;
|
|
valley_box.Center.Set (-105.0F, 35.0F, 20.0F);
|
|
valley_box.Extent.Set (75.0F, 70.5F, 14.0F);
|
|
|
|
//
|
|
// Collect the dynamic physics objects in the valley
|
|
//
|
|
NonRefPhysListClass objs_in_valley;
|
|
PhysicsSceneClass::Get_Instance ()->Collect_Objects (valley_box, false, true, &objs_in_valley);
|
|
|
|
//
|
|
// Loop over all the objects
|
|
//
|
|
NonRefPhysListIterator it (&objs_in_valley);
|
|
for (it.First (); !it.Is_Done (); it.Next ()) {
|
|
PhysicalGameObj *gameobj = NULL;
|
|
|
|
//
|
|
// Get the game object from this physics object
|
|
//
|
|
if (it.Peek_Obj()->Get_Observer () != NULL) {
|
|
gameobj = ((CombatPhysObserverClass *)it.Peek_Obj()->Get_Observer())->As_PhysicalGameObj();
|
|
}
|
|
|
|
//
|
|
// Check to see if this is a commando game object
|
|
//
|
|
if (gameobj != NULL) {
|
|
SoldierGameObj *soldier = gameobj->As_SoldierGameObj ();
|
|
if (soldier != NULL && soldier->Is_Human_Controlled()) {
|
|
|
|
Vector3 position;
|
|
soldier->Get_Position (&position);
|
|
|
|
//
|
|
// Is this the closest commando we've found so far?
|
|
//
|
|
float distance = (position - sakura_position).Length ();
|
|
if (distance < closest_distance) {
|
|
closest_human_player = soldier;
|
|
closest_distance = distance;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return closest_human_player;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Do_Waypath
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void
|
|
SakuraBossGameObj::Do_Waypath (int waypath_id, int start_id, int end_id)
|
|
{
|
|
WaypathClass *waypath = PathfindClass::Get_Instance()->Find_Waypath (waypath_id);
|
|
if (waypath != NULL) {
|
|
|
|
//
|
|
// Initialize our pathfind object with the waypath
|
|
//
|
|
PathObjectClass path_obj;
|
|
path_obj.Initialize (*Peek_Physical_Object ());
|
|
path_obj.Set_Max_Speed (Get_Max_Speed ());
|
|
path_obj.Set_Turn_Radius (Get_Turn_Radius ());
|
|
path_obj.Set_Flag (PathObjectClass::IS_VEHICLE, true);
|
|
Path->Set_Path_Object (path_obj);
|
|
Path->Set_Traversal_Type (PathClass::SPLINE);
|
|
Path->Set_Movement_Radius (5.0F);
|
|
Path->Initialize (waypath, start_id, end_id);
|
|
Path->Set_Movement_Directions (PathClass::MOVE_X | PathClass::MOVE_Y | PathClass::MOVE_Z);
|
|
|
|
//
|
|
// Find the starting and ending waypoints
|
|
//
|
|
int count = waypath->Get_Point_Count ();
|
|
int start_index = 0;
|
|
int end_index = count - 1;
|
|
for (int index = 0; index < count; index ++) {
|
|
if (waypath->Get_Point (index)->Get_ID () == start_id) {
|
|
start_index = index;
|
|
}
|
|
if (waypath->Get_Point (index)->Get_ID () == end_id) {
|
|
end_index = index;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Get the world space locations of the starting and ending waypoints
|
|
//
|
|
Vector3 start_pos = waypath->Get_Point (start_index)->Get_Position ();
|
|
Vector3 end_pos = waypath->Get_Point (end_index)->Get_Position ();
|
|
|
|
//
|
|
// Initialize the pilot code with the new flight path
|
|
//
|
|
Pilot.Set_Mode (PilotClass::MODE_FLY_TO_POINT);
|
|
Pilot.Set_Next_Point (start_pos);
|
|
Pilot.Set_Destination (end_pos);
|
|
Pilot.Set_Arrived_Dist (1.0F);
|
|
Pilot.Set_Hover_Dist (10.0F);
|
|
|
|
Set_Vehicle_State (VEHICLE_STATE_MOVING);
|
|
}
|
|
|
|
return ;
|
|
}
|