This repository has been archived on 2025-02-27. You can view files and clone it, but cannot push or open issues or pull requests.
CnC_Renegade/Code/Combat/sakurabossgameobj.cpp

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), &center_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 ;
}