CnC_Remastered_Collection/REDALERT/VESSEL.CPP
PG-SteveT ae72fce5dd August 6th Patch Update
Accumulated DLL source code changes since June 22nd patch
2020-08-06 09:44:54 -07:00

2451 lines
109 KiB
C++

//
// Copyright 2020 Electronic Arts Inc.
//
// TiberianDawn.DLL and RedAlert.dll and corresponding source code 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.
// TiberianDawn.DLL and RedAlert.dll and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
/* $Header: /CounterStrike/VESSEL.CPP 1 3/03/97 10:26a Joe_bostic $ */
/***********************************************************************************************
*** 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 : Command & Conquer *
* *
* File Name : VESSEL.CPP *
* *
* Programmer : Joe L. Bostic *
* *
* Start Date : 03/13/96 *
* *
* Last Update : July 31, 1996 [JLB] *
* *
*---------------------------------------------------------------------------------------------*
* Functions: *
* VesselClass::AI -- Handles the AI processing for vessel objects. *
* VesselClass::Assign_Destination -- Assign a destination for this vessel. *
* VesselClass::Can_Enter_Cell -- Determines if the vessel can enter the cell specified. *
* VesselClass::Can_Fire -- Determines if this vessel can fire its weapon. *
* VesselClass::Is_Allowed_To_Recloak -- Can the vessel recloak now? *
* VesselClass::Class_Of -- Fetches a reference to the vessel's class data. *
* VesselClass::Combat_AI -- Handles firing and target selection for the vessel. *
* VesselClass::Debug_Dump -- Dumps the vessel status information to the mono monitor. *
* VesselClass::Draw_It -- Draws the vessel. *
* VesselClass::Edge_Of_World_AI -- Determine if vessel is off the edge of the world. *
* VesselClass::Enter_Idle_Mode -- Causes the vessel to enter its default idle mode. *
* VesselClass::Fire_Coord -- Fetches the coordinate the firing originates from. *
* VesselClass::Greatest_Threat -- Determines the greatest threat (best target) for the vesse*
* VesselClass::Init -- Initialize the vessel heap system. *
* VesselClass::Mission_Retreat -- Perform the retreat mission. *
* VesselClass::Overlap_List -- Fetches the overlap list for this vessel object. *
* VesselClass::Per_Cell_Process -- Performs once-per-cell action. *
* VesselClass::Read_INI -- Read the vessel data from the INI database. *
* VesselClass::Repair_AI -- Process any self-repair required of this vessel. *
* VesselClass::Rotation_AI -- Handles turret and body rotation for this vessel. *
* VesselClass::Shape_Number -- Calculates the shape number for the ship body. *
* VesselClass::Start_Driver -- Starts the vessel by reserving the location it is moving to. *
* VesselClass::Take_Damage -- Assign damage to the vessel. *
* VesselClass::VesselClass -- Constructor for vessel class objects. *
* VesselClass::What_Action -- Determines action to perform on specified cell. *
* VesselClass::Write_INI -- Write all vessel scenario data to the INI database. *
* VesselClass::~VesselClass -- Destructor for vessel objects. *
* operator delete -- Deletes a vessel's memory block. *
* operator new -- Allocates a vessel object memory block. *
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#include "function.h"
/***********************************************************************************************
* VesselClass::VesselClass -- Constructor for vessel class objects. *
* *
* This is the normal constructor for vessel class objects. It will set up a vessel that *
* is valid excepting that it won't be placed on the map. *
* *
* INPUT: classid -- The type of vessel this will be. *
* *
* house -- The owner of this vessel. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 03/14/1996 JLB : Created. *
*=============================================================================================*/
VesselClass::VesselClass(VesselType classid, HousesType house) :
DriveClass(RTTI_VESSEL, Vessels.ID(this), house),
Class(VesselTypes.Ptr((int)classid)),
IsToSelfRepair(false),
IsSelfRepairing(false),
DoorShutCountDown(0),
PulseCountDown(0),
SecondaryFacing(PrimaryFacing)
{
House->Tracking_Add(this);
/*
** The ammo member is actually part of the techno class, but must be initialized
** manually here because this is where we first have access to the class pointer.
*/
Ammo = Class->MaxAmmo;
/*
** For two shooters, clear out the second shot flag -- it will be set the first time
** the object fires. For non two shooters, set the flag since it will never be cleared
** and the second shot flag tells the system that normal rearm times apply -- this is
** what is desired for non two shooters.
*/
IsSecondShot = !Class->Is_Two_Shooter();
Strength = Class->MaxStrength;
/*
** The techno class cloakabilty flag is set according to the type
** class cloakability flag.
*/
IsCloakable = Class->IsCloakable;
/*
** Keep count of the number of units created.
*/
// if (Session.Type == GAME_INTERNET) {
// House->UnitTotals->Increment_Unit_Total((int)classid);
// }
}
/***********************************************************************************************
* VesselClass::~VesselClass -- Destructor for vessel objects. *
* *
* The destructor will destroy the vessel and ensure that it is properly removed from the *
* game engine. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 03/14/1996 JLB : Created. *
*=============================================================================================*/
VesselClass::~VesselClass(void)
{
if (GameActive && Class.Is_Valid()) {
/*
** Remove this member from any team it may be associated with. This must occur at the
** top most level of the inheritance hierarchy because it may call virtual functions.
*/
if (Team.Is_Valid()) {
Team->Remove(this);
Team = NULL;
}
House->Tracking_Remove(this);
/*
** If there are any cargo members, delete them.
*/
while (Is_Something_Attached()) {
delete Detach_Object();
}
Limbo();
}
ID = -1;
}
/***********************************************************************************************
* operator new -- Allocates a vessel object memory block. *
* *
* This routine is used to allocate a block of memory from the vessel heap. If there is *
* no more space in the heap, then this routine will return NULL. *
* *
* INPUT: none *
* *
* OUTPUT: Returns with the pointer to the allocated block of memory. *
* *
* WARNINGS: This routine could return NULL. *
* *
* HISTORY: *
* 03/14/1996 JLB : Created. *
*=============================================================================================*/
void * VesselClass::operator new(size_t)
{
void * ptr = Vessels.Alloc();
if (ptr != NULL) {
((VesselClass *)ptr)->Set_Active();
}
return(ptr);
}
/***********************************************************************************************
* operator delete -- Deletes a vessel's memory block. *
* *
* This overloaded delete operator will return the vessel's memory back to the pool of *
* memory used for vessel allocation. *
* *
* INPUT: ptr -- Pointer to the vessel's memory block. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 03/14/1996 JLB : Created. *
*=============================================================================================*/
void VesselClass::operator delete(void * ptr)
{
if (ptr != NULL) {
assert(((VesselClass *)ptr)->IsActive);
((VesselClass *)ptr)->IsActive = false;
}
Vessels.Free((VesselClass *)ptr);
}
/***********************************************************************************************
* VesselClass::Class_Of -- Fetches a reference to the vessel's class data. *
* *
* This routine will return with a reference to the static class data for this vessel. *
* *
* INPUT: none *
* *
* OUTPUT: Returns with a reference to the class data structure associated with this vessel. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 03/14/1996 JLB : Created. *
*=============================================================================================*/
ObjectTypeClass const & VesselClass::Class_Of(void) const
{
assert(IsActive);
return(*Class);
}
/***********************************************************************************************
* VesselClass::Can_Enter_Cell -- Determines if the vessel can enter the cell specified. *
* *
* This routine is used by find path and other movement logic to determine if this *
* vessel can enter the cell specified. *
* *
* INPUT: cell -- The cell to check this vessel against. *
* *
* OUTPUT: Returns with the movement restriction associated with movement into this object. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 03/14/1996 JLB : Created. *
*=============================================================================================*/
MoveType VesselClass::Can_Enter_Cell(CELL cell, FacingType ) const
{
assert(Vessels.ID(this) == ID);
assert(IsActive);
if ((unsigned)cell >= MAP_CELL_TOTAL) return(MOVE_NO);
CellClass const * cellptr = &Map[cell];
/*
** Moving off the edge of the map is not allowed unless
** this is a loaner vehicle.
*/
if (!ScenarioInit && !Map.In_Radar(cell) && !Is_Allowed_To_Leave_Map()) {
return(MOVE_NO);
}
MoveType retval = MOVE_OK;
/*
** If there is blocking terrain (such as ice), then the vessel
** can't move there.
*/
if (cellptr->Cell_Terrain() != NULL) {
return(MOVE_NO);
}
/*
** If the cell is out and out impassable because of underlying terrain, then
** return this immutable fact.
*/
if (Ground[cellptr->Land_Type()].Cost[Class->Speed] == 0) {
return(MOVE_NO);
}
/*
** If some allied object has reserved the cell, then consider the cell
** as blocked by a moving object.
*/
if (cellptr->Flag.Composite) {
if (cellptr->Flag.Occupy.Building) {
return(MOVE_NO);
}
TechnoClass * techno = cellptr->Cell_Techno();
if (techno != NULL && techno->Is_Cloaked(this)) {
return(MOVE_CLOAK);
}
/*
** If reserved by a vehicle, then consider this blocked terrain.
*/
if (cellptr->Flag.Occupy.Vehicle) {
retval = MOVE_MOVING_BLOCK;
}
}
/*
** Return with the most severe reason why this cell would be impassable.
*/
return(retval);
}
/***********************************************************************************************
* VesselClass::Shape_Number -- Calculates the shape number for the ship body. *
* *
* This routine will return with the shape number to use for the ship's body. *
* *
* INPUT: none *
* *
* OUTPUT: Returns with the shape number to use for the ship's body when drawing. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 07/31/1996 JLB : Created. *
*=============================================================================================*/
int VesselClass::Shape_Number(void) const
{
/*
** For eight facing units, adjust the facing number accordingly.
*/
FacingType facing = Dir_Facing(PrimaryFacing.Current());
int shapenum = UnitClass::BodyShape[Dir_To_16(PrimaryFacing)*2]>>1;
/*
** Special case code for transport. The north/south facing is in frame
** 0. The east/west facing is in frame 3.
*/
if (*this == VESSEL_TRANSPORT) {
shapenum = 0;
}
#ifdef FIXIT_CARRIER // checked - ajw 9/28/98
if (*this == VESSEL_CARRIER) {
shapenum = 0;
}
#endif
/*
** Door opening and closing animation stage check.
*/
if (!Is_Door_Closed()) {
shapenum = Door_Stage();
}
return(shapenum);
}
/***********************************************************************************************
* VesselClass::Draw_It -- Draws the vessel. *
* *
* Draws the vessel on the tactical display. This routine is called by the map rendering *
* process to display the vessel. *
* *
* INPUT: x,y -- The pixel coordinate to draw this vessel at. *
* *
* window-- The window to base clipping and coordinates upon when drawing. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 03/14/1996 JLB : Created. *
*=============================================================================================*/
void VesselClass::Draw_It(int x, int y, WindowNumberType window) const
{
assert(Vessels.ID(this) == ID);
assert(IsActive);
/*
** Verify the legality of the unit class.
*/
void const * shapefile = Get_Image_Data();
if (shapefile == NULL) return;
/*
** Need to know the shape name for the overlay now. ST - 8/19/2019 1:37PM
*/
const char *turret_shape_name = NULL;
/*
** If drawing of this unit is not explicitly prohibited, then proceed
** with the render process.
*/
const bool is_hidden = (Visual_Character() == VISUAL_HIDDEN) && (window != WINDOW_VIRTUAL);
if (!is_hidden) {
int facing = Dir_To_32(PrimaryFacing);
int tfacing = Dir_To_32(SecondaryFacing);
DirType rotation = DIR_N;
int scale = 0x0100;
/*
** Actually perform the draw. Overlay an optional shimmer effect as necessary.
*/
Techno_Draw_Object(shapefile, Shape_Number(), x, y, window, rotation, scale);
/*
** If there is a turret, then it must be rendered as well. This may include
** firing animation if required.
*/
if (Class->IsTurretEquipped) {
int xx = x;
int yy = y;
/*
** Determine which turret shape to use. This depends on if there
** is any firing animation in progress.
*/
int shapenum = TechnoClass::BodyShape[tfacing]+32;
DirType turdir = DirType(Dir_To_16(PrimaryFacing)*16);
switch (Class->Type) {
case VESSEL_CA:
turret_shape_name = "TURR";
shapefile = Class->TurretShapes;
shapenum = TechnoClass::BodyShape[Dir_To_32(SecondaryFacing)];
Class->Turret_Adjust(turdir, xx, yy);
// Add shape file name forl new shape draw intercept. ST - 8/19/2019 1:42PM
//Techno_Draw_Object(shapefile, shapenum, xx, yy, window);
Techno_Draw_Object_Virtual(shapefile, shapenum, xx, yy, window, DIR_N, 0x0100, turret_shape_name);
xx = x;
yy = y;
turdir = DirType(Dir_To_16(PrimaryFacing+DIR_S)*16);
Class->Turret_Adjust(turdir, xx, yy);
break;
case VESSEL_DD:
turret_shape_name = "SSAM";
shapefile = Class->SamShapes;
shapenum = TechnoClass::BodyShape[Dir_To_32(SecondaryFacing)];
Class->Turret_Adjust(turdir, xx, yy);
break;
case VESSEL_PT:
turret_shape_name = "MGUN";
shapefile = Class->MGunShapes;
shapenum = TechnoClass::BodyShape[Dir_To_32(SecondaryFacing)];
Class->Turret_Adjust(turdir, xx, yy);
break;
default:
shapenum = TechnoClass::BodyShape[Dir_To_32(SecondaryFacing)];
Class->Turret_Adjust(turdir, xx, yy);
break;
}
/*
** Actually perform the draw. Overlay an optional shimmer effect as necessary.
*/
// Add shape file name forl new shape draw intercept. ST - 8/19/2019 1:42PM
if (turret_shape_name) {
Techno_Draw_Object_Virtual(shapefile, shapenum, xx, yy, window, DIR_N, 0x0100, turret_shape_name);
} else {
Techno_Draw_Object(shapefile, shapenum, xx, yy, window);
}
}
}
DriveClass::Draw_It(x, y, window);
/*
** Patch so the transport will draw its passengers on top of itself.
*/
if (!Is_Door_Closed() && IsTethered && In_Radio_Contact() && !Contact_With_Whom()->IsInLimbo) {
TechnoClass * contact = Contact_With_Whom();
assert(contact->IsActive);
int xxx = x + ((int)Lepton_To_Pixel((int)Coord_X(contact->Render_Coord())) - (int)Lepton_To_Pixel((int)Coord_X(Render_Coord())));
int yyy = y + ((int)Lepton_To_Pixel((int)Coord_Y(contact->Render_Coord())) - (int)Lepton_To_Pixel((int)Coord_Y(Render_Coord())));
contact->Draw_It(xxx, yyy, window);
contact->IsToDisplay = false;
}
}
#ifdef CHEAT_KEYS
/***********************************************************************************************
* VesselClass::Debug_Dump -- Dumps the vessel status information to the mono monitor. *
* *
* This routine will display the vessel's status information. The information is dumped to *
* the monochrome monitor. *
* *
* INPUT: mono -- Pointer to the monochrome screen that the information will be displayed *
* to. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 03/20/1996 JLB : Created. *
*=============================================================================================*/
void VesselClass::Debug_Dump(MonoClass * mono) const
{
assert(Vessels.ID(this) == ID);
assert(IsActive);
mono->Set_Cursor(0, 0);
mono->Print(Text_String(TXT_DEBUG_SHIP));
mono->Set_Cursor(47, 5);mono->Printf("%02X:%02X", SecondaryFacing.Current(), SecondaryFacing.Desired());
mono->Fill_Attrib(66, 13, 12, 1, IsSelfRepairing ? MonoClass::INVERSE : MonoClass::NORMAL);
mono->Fill_Attrib(66, 14, 12, 1, IsToSelfRepair ? MonoClass::INVERSE : MonoClass::NORMAL);
DriveClass::Debug_Dump(mono);
}
#endif
/***********************************************************************************************
* VesselClass::Overlap_List -- Fetches the overlap list for this vessel object. *
* *
* This routine will fetch the overlap list for this vessel type. It takes into *
* consideration any movement the vessel may be doing. *
* *
* INPUT: none *
* *
* OUTPUT: Returns with a pointer to the overlap list for this vessel. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 03/20/1996 JLB : Created. *
*=============================================================================================*/
short const * VesselClass::Overlap_List(bool redraw) const
{
assert(Vessels.ID(this) == ID);
assert(IsActive);
#ifdef PARTIAL
if (Height == 0 && redraw && Class->DimensionData != NULL) {
Rect rect;
int shapenum = Shape_Number();
if (Class->DimensionData[shapenum].Is_Valid()) {
rect = Class->DimensionData[shapenum];
} else {
rect = Class->DimensionData[shapenum] = Shape_Dimensions(Get_Image_Data(), shapenum);
}
if (IsSelected) {
rect = Union(rect, Rect(-32, 32, 64, 64));
}
return(Coord_Spillage_List(Coord, rect, true));
}
#else
redraw = redraw;
#endif
return(Coord_Spillage_List(Coord, 56)+1);
}
/***********************************************************************************************
* VesselClass::AI -- Handles the AI processing for vessel objects. *
* *
* This routine is called once for each vessel object during each main game loop. All *
* normal AI processing is handled here. This includes dispatching and maintaining any *
* processing that is specific to vessel objects. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 03/20/1996 JLB : Created. *
* 07/16/1996 JLB : Prefers anti-sub weapons if firing on subs. *
*=============================================================================================*/
void VesselClass::AI(void)
{
assert(Vessels.ID(this) == ID);
assert(IsActive);
if (Mission == MISSION_NONE && MissionQueue == MISSION_NONE) {
Enter_Idle_Mode();
}
/*
** HACK ALERT:
** If the ship finds itself in a hunt order but it has no weapons, then tell it
** to sail off the map instead.
*/
if (Mission == MISSION_HUNT && !Is_Weapon_Equipped()) {
Assign_Mission(MISSION_RETREAT);
}
/*
** Act on new orders if the unit is at a good position to do so.
*/
if (!IsDriving && Is_Door_Closed() /*Mission != MISSION_UNLOAD*/) {
Commence();
}
#ifndef CLIPDRAW
if (Map.In_View(Coord_Cell(Center_Coord()))) {
Mark(MARK_CHANGE);
}
#endif
#ifdef FIXIT_CARRIER // checked - ajw 9/28/98
// Re-stock the ammo of any on-board helicopters on an aircraft carrier.
if (*this == VESSEL_CARRIER && How_Many()) {
if (!MoebiusCountDown) {
MoebiusCountDown = Rule.ReloadRate * TICKS_PER_MINUTE;
ObjectClass *obj = Attached_Object();
while (obj) {
long bogus;
((AircraftClass *)obj)->Receive_Message(this,RADIO_RELOAD,bogus);
obj = (obj->Next);
}
}
}
#endif
/*
** Process base class AI routine. If as a result of this, the vessel gets
** destroyed, then detect this fact and bail early.
*/
DriveClass::AI();
if (!IsActive) {
return;
}
/*
** Handle body and turret rotation.
*/
Rotation_AI();
/*
** Handle any combat processing required.
*/
Combat_AI();
/*
** Delete this unit if it finds itself off the edge of the map and it is in
** guard or other static mission mode.
*/
if (Edge_Of_World_AI()) {
return;
}
if (Class->Max_Passengers() > 0) {
/*
** Double check that there is a passenger that is trying to load or unload.
** If not, then close the door.
*/
if (!Is_Door_Closed() && Mission != MISSION_UNLOAD && Transmit_Message(RADIO_TRYING_TO_LOAD) != RADIO_ROGER && !(long)DoorShutCountDown) {
LST_Close_Door();
}
}
/*
** Don't start a new mission unless the vehicle is in the center of
** a cell (not driving) and the door (if any) is closed.
*/
if (!IsDriving && Is_Door_Closed()/*&& Mission != MISSION_UNLOAD*/) {
Commence();
}
/*
** Do a step of repair here, if appropriate.
*/
Repair_AI();
}
/***********************************************************************************************
* VesselClass::Per_Cell_Process -- Performs once-per-cell action. *
* *
* This routine is called when the vessel travels one cell. It handles any processes that *
* must occur on a per-cell basis. *
* *
* INPUT: why -- Specifies the circumstances under which this routine was called. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 03/19/1996 JLB : Created. *
*=============================================================================================*/
void VesselClass::Per_Cell_Process(PCPType why)
{
assert(Vessels.ID(this) == ID);
assert(IsActive);
BStart(BENCH_PCP);
if (why == PCP_END) {
CELL cell = Coord_Cell(Coord);
/*
** The unit performs looking around at this time. If the
** unit moved further than one square during the last track
** move, don't do an incremental look. Do a full look around
** instead.
*/
Look(!IsPlanningToLook);
IsPlanningToLook = false;
if (IsToSelfRepair) {
for (FacingType face = FACING_N; face < FACING_COUNT; face++) {
CELL cell = Coord_Cell(Adjacent_Cell(Center_Coord(), face));
SmartPtr<BuildingClass> whom;
whom = Map[cell].Cell_Building();
if (whom != NULL && ((*whom == STRUCT_SHIP_YARD) || (*whom == STRUCT_SUB_PEN)) ) {
// MBL 04.27.2020: Make only audible to the correct player
// if (IsOwnedByPlayer) Speak(VOX_REPAIRING);
if (IsOwnedByPlayer) Speak(VOX_REPAIRING, House);
IsSelfRepairing = true;
IsToSelfRepair = false;
break;
}
}
}
/*
** If this is a loaner unit and is is off the edge of the
** map, then it gets eliminated.
*/
if (Edge_Of_World_AI()) {
BEnd(BENCH_PCP);
return;
}
}
if (IsActive) {
DriveClass::Per_Cell_Process(why);
}
BEnd(BENCH_PCP);
}
/***********************************************************************************************
* VesselClass::What_Action -- Determines what action would occur if clicked on object. *
* *
* Use this function to determine what action would likely occur if the specified object *
* were clicked on while this unit was selected as current. This function controls, not *
* only the action to perform, but indirectly controls the cursor shape to use as well. *
* *
* INPUT: object -- The object that to check for against "this" object. *
* *
* OUTPUT: Returns with the default action to perform. If no clear action can be determined, *
* then ACTION_NONE is returned. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 04/16/1996 BWG : Created. *
*=============================================================================================*/
ActionType VesselClass::What_Action(ObjectClass const * object) const
{
assert(Vessels.ID(this) == ID);
assert(IsActive);
ActionType action = DriveClass::What_Action(object);
if (action == ACTION_SELF) {
if (Class->Max_Passengers() == 0 || !How_Many() ) {
action = ACTION_NONE;
} else {
// check to see if the transporter can unload.
bool found = 0;
#ifdef FIXIT_CARRIER // checked - ajw 9/28/98
if (*this != VESSEL_CARRIER)
#endif
for (FacingType face = FACING_N; face < FACING_COUNT && !found; face++) {
CELL cellnum = Adjacent_Cell(Coord_Cell(Coord), face);
CellClass * cell = &Map[cellnum];
if (Map.In_Radar(cellnum)) {
if (Ground[cell->Land_Type()].Cost[SPEED_FOOT] == 0 || cell->Flag.Occupy.Building || cell->Flag.Occupy.Vehicle || cell->Flag.Occupy.Monolith || (cell->Flag.Composite & 0x01F) == 0x01F) {
continue;
} else {
found = true;
}
}
}
if (!found) {
action = ACTION_NONE;
}
}
}
/*
** Special return to friendly repair factory action.
*/
if (House->IsPlayerControl && action == ACTION_SELECT && object->What_Am_I() == RTTI_BUILDING) {
BuildingClass * building = (BuildingClass *)object;
if (building->Class->ToBuild == RTTI_VESSELTYPE && building->House->Is_Ally(this)) {
action = ACTION_ENTER;
}
}
#ifdef FIXIT_CSII // checked - ajw 9/28/98
if (action == ACTION_ATTACK && object->What_Am_I() == RTTI_VESSEL &&
(*this == VESSEL_MISSILESUB || *this == VESSEL_CA) ) {
action = ACTION_NOMOVE;
}
#endif
/*
** If it doesn't know what to do with the object, then just
** say it can't move there.
*/
if (action == ACTION_NONE) action = ACTION_NOMOVE;
return(action);
}
/***********************************************************************************************
* VesselClass::Active_Click_With -- Intercepts the active click to see if deployment is possib*
* *
* This routine intercepts the active click operation. It check to see if this is a self *
* deployment request (MCV's have this ability). If it is, then the object is initiated *
* to self deploy. In the other cases, it passes the operation down to the lower *
* classes for processing. *
* *
* INPUT: action -- The action requested of the unit. *
* *
* object -- The object that the mouse pointer is over. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 04/16/1996 BWG : Created. *
*=============================================================================================*/
void VesselClass::Active_Click_With(ActionType action, ObjectClass * object)
{
assert(Vessels.ID(this) == ID);
assert(IsActive);
// if (action != What_Action(object)) {
action = What_Action(object);
switch (action) {
case ACTION_ENTER:
action = ACTION_MOVE;
// BRR 10/18/96 IsToSelfRepair = true;
break;
default:
// action = ACTION_NONE;
break;
}
// }
// if (action == ACTION_ENTER) {
// BRR 10/18/96 IsToSelfRepair = true;
// action = ACTION_MOVE;
// } else {
// if (action != ACTION_NONE) {
// BRR 10/18/96 IsSelfRepairing = IsToSelfRepair = false;
// }
// }
DriveClass::Active_Click_With(action, object);
}
/***********************************************************************************************
* VesselClass::Active_Click_With -- Performs specified action on specified cell. *
* *
* This routine is called when the mouse has been clicked over a cell and this unit must *
* now respond. Notice that this is merely a placeholder function that exists because there *
* is another function of the same name that needs to be overloaded. C++ has scoping *
* restrictions when there are two identically named functions that are overridden in *
* different classes -- it handles it badly, hence the existence of this routine. *
* *
* INPUT: action -- The action to perform on the cell specified. *
* *
* cell -- The cell that the action is to be performed on. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 04/16/1996 BWG : Created. *
*=============================================================================================*/
void VesselClass::Active_Click_With(ActionType action, CELL cell)
{
assert(Vessels.ID(this) == ID);
assert(IsActive);
// BRR 10/18/96 IsToSelfRepair = false;
// if (action != ACTION_NONE) {
// BRR 10/18/96 IsSelfRepairing = false;
// }
DriveClass::Active_Click_With(action, cell);
}
/***********************************************************************************************
* VesselClass::Take_Damage -- Assign damage to the vessel. *
* *
* This routine is called to apply damage to this vessel. The amount and type of damage *
* to apply is passed as parameters. This routine could end up destroying the vessel. *
* *
* INPUT: damage -- Reference to the amount of damage to apply to this vessel. The damage *
* value will be adjusted so that the actual damage applied will be *
* stored into this variable for possible subsequent examination. *
* *
* distance -- The distance from the center of the damage to the vessel itself. *
* *
* warhead -- The warhead type of damage to apply. *
* *
* source -- The perpetrator of this damage. Knowing who was responsible allows *
* retaliation logic. *
* *
* forced -- Is this damage forced upon the vessel by some supernatural means? *
* *
* OUTPUT: Returns with the result of the damage applied. This enumeration indicates the *
* general effect of the damage. Examine this return value to see if the vessel *
* has been destroyed. *
* *
* WARNINGS: The vessel could be destroyed by the call to this routine! *
* *
* HISTORY: *
* 05/13/1996 JLB : Created. *
*=============================================================================================*/
ResultType VesselClass::Take_Damage(int & damage, int distance, WarheadType warhead, TechnoClass * source, bool forced)
{
assert(Vessels.ID(this) == ID);
assert(IsActive);
ResultType res = RESULT_NONE;
/*
** In order for a this to be damaged, it must either be a unit
** with a crew or a sandworm.
*/
res = FootClass::Take_Damage(damage, distance, warhead, source, forced);
if (res == RESULT_DESTROYED) {
Death_Announcement(source);
if (Class->Explosion != ANIM_NONE) {
AnimType anim = Class->Explosion;
new AnimClass(anim, Coord);
/*
** Very strong units that have an explosion will also rock the
** screen when they are destroyed.
*/
if (Class->MaxStrength > 400) {
int shakes = Class->MaxStrength / 150;
Shake_The_Screen(shakes, Owner());
if (source && Owner() != source->Owner()) {
Shake_The_Screen(shakes, source->Owner());
}
}
}
/*
** Possibly have the crew member run away.
*/
Mark(MARK_UP);
while (Is_Something_Attached()) {
FootClass * object = Detach_Object();
/*
** Only infantry can run from a destroyed vehicle. Even then, it is not a sure
** thing.
*/
object->Record_The_Kill(source);
delete object;
}
/*
** Finally, delete the vehicle.
*/
delete this;
} else {
/*
** When damaged and below half strength, start smoking if
** it isn't already smoking (and it's not a submarine).
*/
#ifdef FIXIT_CSII // checked - ajw 9/28/98
if (Health_Ratio() <= Rule.ConditionYellow && !IsAnimAttached && (*this != VESSEL_SS && *this != VESSEL_MISSILESUB) ) {
#else
if (Health_Ratio() <= Rule.ConditionYellow && !IsAnimAttached && (*this != VESSEL_SS) ) {
#endif
AnimClass * anim = new AnimClass(ANIM_SMOKE_M, Coord_Add(Coord, XYP_Coord(0, -8)));
if (anim != NULL) anim->Attach_To(this);
}
}
return(res);
}
/***********************************************************************************************
* VesselClass::Can_Fire -- Determines if this vessel can fire its weapon. *
* *
* This routine is used to determine if this vessel can fire its weapon at the target *
* specified. *
* *
* INPUT: target -- The target candidate to determine if firing upon is valid. *
* *
* which -- Which weapon to use when considering the candidate as a potential *
* target. *
* *
* OUTPUT: Returns with the fire error type. This enum indicates if the vessel and fire. If *
* it can't fire, then the enum indicates why. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 05/13/1996 JLB : Created. *
*=============================================================================================*/
FireErrorType VesselClass::Can_Fire(TARGET target, int which) const
{
assert(Vessels.ID(this) == ID);
assert(IsActive);
DirType dir; // The facing to impart upon the projectile.
int diff;
#ifdef FIXIT_CARRIER // checked - ajw 9/28/98
if (*this == VESSEL_CARRIER) {
if(!How_Many() || Arm) {
return(FIRE_REARM);
} else {
return(FIRE_OK);
}
}
#endif
FireErrorType fire = DriveClass::Can_Fire(target, which);
if(*this==VESSEL_DD) {
Mono_Set_Cursor(0,0);
}
if (fire == FIRE_OK || fire == FIRE_CLOAKED) {
WeaponTypeClass const * weapon = (which == 0) ? Class->PrimaryWeapon : Class->SecondaryWeapon;
/*
** Ensure that a torpedo will never be fired upon a non naval target.
** Unless that non-naval target is a naval building (sub pen/ship yard)
*/
bool isseatarget = Is_Target_Vessel(target);
bool isbridgetarget = false;
if (weapon->Bullet->IsSubSurface) {
isbridgetarget = Is_Target_Cell(target); // enable shooting at bridges
isseatarget |= isbridgetarget;
}
BuildingClass * bldg = ::As_Building(target);
if (bldg != NULL && bldg->Class->Speed == SPEED_FLOAT) {
isseatarget = true;
}
dir = Direction(target);
if (weapon->Bullet->IsSubSurface) {
if (!isseatarget && Is_Target_Object(target)) {
return(FIRE_CANT);
}
/*
** If it's a torpedo, let's check line-of-sight to make sure that
** there's only water squares between us and the target.
*/
ObjectClass * obj = As_Object(target);
COORDINATE coord = Center_Coord();
if (obj != NULL) {
int totaldist = ::Distance(coord, obj->Center_Coord());
while (totaldist > CELL_LEPTON_W) {
coord = Coord_Move(coord, dir, CELL_LEPTON_W);
if (Map[coord].Land_Type() != LAND_WATER) {
if (!isbridgetarget) {
return(FIRE_RANGE);
}
}
/*
** Check for friendly boats in the way.
*/
TechnoClass * tech = Map[coord].Cell_Techno();
if (tech != NULL && tech != this && House->Is_Ally(tech)) {
return(FIRE_RANGE);
}
totaldist -= CELL_LEPTON_W;
}
}
}
/*
** Depth charges are only good against submarines.
*/
if (weapon->Bullet->IsAntiSub) {
if (!isseatarget) {
return(FIRE_CANT);
} else {
#ifdef FIXIT_CSII // checked - ajw 9/28/98
if (Is_Target_Vessel(target) && (*As_Vessel(target) != VESSEL_SS && *As_Vessel(target) != VESSEL_MISSILESUB) ) {
#else
if (Is_Target_Vessel(target) && *As_Vessel(target) != VESSEL_SS) {
#endif
if (!Is_Target_Vessel(target) || !weapon->Bullet->IsSubSurface) {
return(FIRE_CANT);
}
}
}
} else {
#ifdef FIXIT_CSII // checked - ajw 9/28/98
if (Is_Target_Vessel(target) && (*As_Vessel(target) == VESSEL_SS || *As_Vessel(target) == VESSEL_MISSILESUB)) {
#else
if (Is_Target_Vessel(target) && *As_Vessel(target) == VESSEL_SS) {
#endif
return(FIRE_CANT);
}
}
/*
** If this unit cannot fire while moving, then bail.
*/
if (!Class->IsTurretEquipped && Target_Legal(NavCom)) {
return(FIRE_MOVING);
}
/*
** If the turret is rotating and the projectile isn't a homing type, then
** firing must be delayed until the rotation stops.
*/
if (!IsFiring && IsRotating && weapon->Bullet->ROT == 0) {
return(FIRE_ROTATING);
}
/*
** Determine if the turret facing isn't too far off of facing the target.
*/
if (Class->IsTurretEquipped) {
diff = SecondaryFacing.Difference(dir);
} else {
diff = PrimaryFacing.Difference(dir);
}
diff = ABS(diff);
if (weapon->Bullet->ROT != 0) {
diff >>= 2;
}
if (diff > 8) {
return(FIRE_FACING);
}
}
return(fire);
}
/***********************************************************************************************
* VesselClass::Fire_Coord -- Fetches the coordinate the firing originates from. *
* *
* This routine is called to determine the coordinate that a fired projectile will *
* originate from. *
* *
* INPUT: which -- Which weapon is this query directed at? *
* *
* OUTPUT: Returns with the coordinate where a projectile would appear if it were fired. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 05/13/1996 JLB : Created. *
*=============================================================================================*/
FireDataType VesselClass::Fire_Data(int which) const
{
assert(Vessels.ID(this) == ID);
assert(IsActive);
COORDINATE coord = Center_Coord();
if (*this == VESSEL_CA) {
if (IsSecondShot) {
coord = Coord_Move(coord, PrimaryFacing + DIR_S, 0x0100);
} else {
coord = Coord_Move(coord, PrimaryFacing, 0x0100);
}
coord = Coord_Move(coord, DIR_N, 0x0030);
return{coord,0x0040};
}
if (*this == VESSEL_PT) {
coord = Coord_Move(coord, PrimaryFacing, 0x0080);
coord = Coord_Move(coord, DIR_N, 0x0020);
return{coord,0x0010};
}
return(DriveClass::Fire_Data(which));
}
COORDINATE VesselClass::Fire_Coord(int which) const
{
assert(Vessels.ID(this) == ID);
assert(IsActive);
COORDINATE coord = Center_Coord();
if (*this == VESSEL_CA) {
if (IsSecondShot) {
coord = Coord_Move(coord, PrimaryFacing + DIR_S, 0x0100);
} else {
coord = Coord_Move(coord, PrimaryFacing, 0x0100);
}
coord = Coord_Move(coord, DIR_N, 0x0030);
coord = Coord_Move(coord, Turret_Facing(), 0x0040);
return(coord);
}
if (*this == VESSEL_PT) {
coord = Coord_Move(coord, PrimaryFacing, 0x0080);
coord = Coord_Move(coord, DIR_N, 0x0020);
coord = Coord_Move(coord, Turret_Facing(), 0x0010);
return(coord);
}
return(DriveClass::Fire_Coord(which));
}
/***********************************************************************************************
* VesselClass::Init -- Initialize the vessel heap system. *
* *
* This routine is used to clear out the vessel heap. It is called whenever a scenario is *
* being initialized prior to scenario or saved game loading. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: All vessel objects are invalid after this routine is called. *
* *
* HISTORY: *
* 05/13/1996 JLB : Created. *
*=============================================================================================*/
void VesselClass::Init(void)
{
Vessels.Free_All();
}
/***********************************************************************************************
* VesselClass::Greatest_Threat -- Determines the greatest threat (best target) for the vessel *
* *
* This routine is used by ships to determine what target they should go after. *
* *
* INPUT: threat -- The threat type that this ship should go after (as determined by the *
* team mission or general self defense principles). *
* *
* OUTPUT: Returns with the target that this ship should attack. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 05/13/1996 JLB : Created. *
*=============================================================================================*/
TARGET VesselClass::Greatest_Threat(ThreatType threat) const
{
if (*this == VESSEL_SS) {
threat = threat & ThreatType(THREAT_RANGE|THREAT_AREA);
threat = threat | THREAT_BOATS;
//BG: get subs to attack buildings also.
threat = threat | THREAT_BUILDINGS;
threat = threat | THREAT_FACTORIES;
} else {
if ((threat & (THREAT_GROUND|THREAT_POWER|THREAT_FACTORIES|THREAT_TIBERIUM|THREAT_BASE_DEFENSE|THREAT_BOATS)) == 0) {
if (Class->PrimaryWeapon != NULL) {
threat = threat | Class->PrimaryWeapon->Allowed_Threats();
}
if (Class->SecondaryWeapon != NULL) {
threat = threat | Class->SecondaryWeapon->Allowed_Threats();
}
// threat = threat | THREAT_GROUND | THREAT_BOATS;
}
// Cruisers can never hit infantry anyway, so take 'em out of the list
// of possible targets.
if (*this == VESSEL_CA) {
threat = (ThreatType) (threat & (~THREAT_INFANTRY));
}
}
#ifdef FIXIT_CARRIER // checked - ajw 9/28/98
if (*this == VESSEL_CARRIER) {
return(TARGET_NONE);
}
#endif
return(FootClass::Greatest_Threat(threat));
}
/***********************************************************************************************
* VesselClass::Enter_Idle_Mode -- Causes the vessel to enter its default idle mode. *
* *
* This routine is called when the vessel is finished with what it is doing, but the next *
* action is not known. This routine will determine what is the appropriate course of *
* action for this vessel and then start it doing that. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 07/09/1996 JLB : Created. *
*=============================================================================================*/
void VesselClass::Enter_Idle_Mode(bool )
{
assert(Vessels.ID(this) == ID);
assert(IsActive);
MissionType order = MISSION_GUARD;
/*
** A movement mission without a NavCom would be pointless to have a radio contact since
** no radio coordination occurs on a just a simple movement mission.
*/
if (Mission == MISSION_MOVE && !Target_Legal(NavCom)) {
Transmit_Message(RADIO_OVER_OUT);
}
Handle_Navigation_List();
if (Target_Legal(NavCom)) {
order = MISSION_MOVE;
} else {
if (Class->PrimaryWeapon == NULL) {
if (IsALoaner && Class->Max_Passengers() > 0 && Is_Something_Attached() && !Team) {
order = MISSION_UNLOAD;
} else {
order = MISSION_GUARD;
Assign_Target(TARGET_NONE);
Assign_Destination(TARGET_NONE);
}
} else {
if (Mission == MISSION_GUARD || Mission == MISSION_GUARD_AREA || MissionControl[Mission].IsParalyzed || MissionControl[Mission].IsZombie) {
return;
}
if (House->IsHuman || Team.Is_Valid()) {
order = MISSION_GUARD;
} else {
if (House->IQ < Rule.IQGuardArea) {
order = MISSION_GUARD;
} else {
order = MISSION_GUARD_AREA;
}
}
}
}
Assign_Mission(order);
}
/***********************************************************************************************
* VesselClass::Receive_Message -- Handles receiving a radio message. *
* *
* This is the handler function for when a vessel receives a radio *
* message. Typical use of this is when a unit unloads from a lst *
* class so that clearing of the transport is successful. *
* *
* INPUT: from -- Pointer to the originator of the message. *
* *
* message -- The radio message received. *
* *
* param -- Reference to an optional parameter the might be needed to return *
* information back to the originator of the message. *
* *
* OUTPUT: Returns with the radio message response. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 05/31/1996 BWG : Created. *
*=============================================================================================*/
RadioMessageType VesselClass::Receive_Message(RadioClass * from, RadioMessageType message, long & param)
{
assert(Vessels.ID(this) == ID);
assert(IsActive);
switch (message) {
/*
** Asks if the passenger can load on this transport.
*/
case RADIO_CAN_LOAD:
if (Class->Max_Passengers() == 0 || from == NULL || !House->Is_Ally(from->Owner())) return(RADIO_STATIC);
if (How_Many() < Class->Max_Passengers()) {
#ifdef FIXIT_CARRIER // checked - ajw 9/28/98
if(*this == VESSEL_CARRIER && from->What_Am_I() == RTTI_AIRCRAFT) {
return(RADIO_ROGER);
}
#endif
/*
** Before saying "Sure, come on board", make sure we're adjacent to
** the shore.
*/
CELL cell;
Desired_Load_Dir(from, cell);
if(cell) {
return(RADIO_ROGER);
}
}
return(RADIO_NEGATIVE);
/*
** This message is sent by the passenger when it determines that it has
** entered the transport.
*/
case RADIO_IM_IN:
#ifdef FIXIT_CARRIER // checked - ajw 9/28/98
if(*this != VESSEL_CARRIER) {
#endif
if (How_Many() == Class->Max_Passengers()) {
LST_Close_Door();
} else {
DoorShutCountDown = TICKS_PER_SECOND;
}
#ifdef FIXIT_CARRIER // checked - ajw 9/28/98
}
#endif
return(RADIO_ATTACH);
/*
** Docking maintenance message received. Check to see if new orders should be given
** to the impatient unit.
*/
case RADIO_DOCKING:
/*
** If this transport is moving, then always abort the docking request.
*/
if (IsDriving || Target_Legal(NavCom)) {
return(RADIO_NEGATIVE);
}
/*
** Check for the case of a docking message arriving from a unit that does not
** have formal radio contact established. This might be a unit that is standing
** by. If this transport is free to proceed with normal docking operation, then
** establish formal contact now. If the transport is completely full, then break
** off contact. In all other cases, just tell the pending unit to stand by.
*/
if (Contact_With_Whom() != from) {
/*
** Can't ever load up so tell the passenger to bug off.
*/
if (How_Many() >= Class->Max_Passengers()) {
return(RADIO_NEGATIVE);
}
/*
** Establish contact and let the loading process proceed normally.
*/
if (!In_Radio_Contact()) {
param = TARGET_NONE;
Transmit_Message(RADIO_HELLO, from);
Transmit_Message(RADIO_MOVE_HERE, param);
return(RADIO_ROGER);
} else {
/*
** This causes the potential passenger to think that all is ok and to
** hold on for a bit.
*/
return(RADIO_ROGER);
}
}
/*
**
*/
if (Class->Max_Passengers() > 0 && *this == VESSEL_TRANSPORT && How_Many() < Class->Max_Passengers()) {
DriveClass::Receive_Message(from, message, param);
if (!IsDriving && !IsRotating) {
// if (!IsDriving && !IsRotating && !IsTethered) {
/*
** If the potential passenger needs someplace to go, then figure out a good
** spot and tell it to go.
*/
if (Transmit_Message(RADIO_NEED_TO_MOVE, from) == RADIO_ROGER) {
CELL cell;
DirType dir = Desired_Load_Dir(from, cell);
/*
** If no adjacent free cells are detected, then passenger loading
** cannot occur. Break radio contact.
*/
if (cell == 0) {
Transmit_Message(RADIO_OVER_OUT, from);
} else {
param = (long)::As_Target(cell);
/*
** If it is now facing the correct direction, then open the
** transport doors. Close the doors if the transport is full or needs
** to rotate.
*/
if (!Is_Door_Open()) {
LST_Open_Door();
}
/*
** Tell the potential passenger where it should go. If the passenger is
** already at the staging location, then tell it to move onto the transport
** directly.
*/
if (Transmit_Message(RADIO_MOVE_HERE, param, from) == RADIO_YEA_NOW_WHAT) {
if (Is_Door_Open()) {
param = (long)As_Target();
Transmit_Message(RADIO_TETHER);
if (Transmit_Message(RADIO_MOVE_HERE, param, from) != RADIO_ROGER) {
Transmit_Message(RADIO_OVER_OUT, from);
} else {
Contact_With_Whom()->Unselect();
}
}
}
}
}
}
return(RADIO_ROGER);
}
#ifdef FIXIT_CARRIER // checked - ajw 9/28/98
if (Class->Max_Passengers() > 0 && *this == VESSEL_CARRIER && How_Many() < Class->Max_Passengers()) {
TechnoClass::Receive_Message(from, message, param);
/*
** Establish contact with the object if this building isn't already in contact
** with another.
*/
if (!In_Radio_Contact()) {
Transmit_Message(RADIO_HELLO, from);
}
if (Transmit_Message(RADIO_NEED_TO_MOVE) == RADIO_ROGER) {
param = As_Target();
if (Transmit_Message(RADIO_MOVE_HERE, param) == RADIO_YEA_NOW_WHAT) {
Transmit_Message(RADIO_TETHER);
}
}
return(RADIO_ROGER);
}
#endif
break;
/*
** When this message is received, it means that the other object
** has already turned its radio off. Turn this radio off as well.
*/
case RADIO_OVER_OUT:
if (Mission == MISSION_RETURN) {
Assign_Mission(MISSION_GUARD);
}
DriveClass::Receive_Message(from, message, param);
return(RADIO_ROGER);
}
return(DriveClass::Receive_Message(from, message, param));
}
/***********************************************************************************************
* VesselClass::Desired_Load_Dir -- Determines the best cell and facing for loading. *
* *
* This routine examines the unit and adjacent cells in order to find the best facing *
* for the transport and best staging cell for the potential passengers. This location is *
* modified by adjacent cell passability and direction of the potential passenger. *
* *
* INPUT: passenger -- Pointer to the potential passenger. *
* *
* moveto -- Reference to the cell number that specifies where the potential *
* passenger should move to first. *
* *
* OUTPUT: Returns with the direction the transport should face before opening the transport *
* door. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 06/01/1996 BWG : Created. *
*=============================================================================================*/
DirType VesselClass::Desired_Load_Dir(ObjectClass * passenger, CELL & moveto) const
{
assert(Vessels.ID(this) == ID);
assert(IsActive);
/*
** Determine the ideal facing that provides the least resistance. This would be the direction
** of the potential passenger or the current transport facing if it is going to unload.
*/
DirType faceto;
if (passenger != NULL) {
faceto = Direction(passenger);
} else {
faceto = PrimaryFacing.Current() + DIR_S;
}
/*
** Sweep through the adjacent cells in order to find the best candidate.
*/
FacingType bestdir = FACING_N;
int bestval = -1;
for (FacingType face = FACING_N; face < FACING_COUNT; face++) {
int value = 0;
CELL cellnum = Adjacent_Cell(Coord_Cell(Coord), face);
/*
** Base the initial value of the potential cell according to whether the passenger is
** allowed to enter the cell. If it can't, then give such a negative value to the
** cell so that it is prevented from ever choosing that cell for load/unload.
*/
if (passenger != NULL) {
value = (passenger->Can_Enter_Cell(cellnum) == MOVE_OK || Coord_Cell(passenger->Coord) == cellnum) ? 128 : -128;
} else {
CellClass * cell = &Map[cellnum];
if (Ground[cell->Land_Type()].Cost[SPEED_FOOT] == 0 || cell->Flag.Occupy.Building || cell->Flag.Occupy.Vehicle || cell->Flag.Occupy.Monolith || (cell->Flag.Composite & 0x01F) == 0x01F) {
value = -128;
} else {
if (cell->Cell_Techno() && !House->Is_Ally(cell->Cell_Techno())) {
value = -128;
} else {
value = 128;
}
}
}
#if(0)
/*
** Give more weight to the cells that require the least rotation of the transport or the
** least roundabout movement for the potential passenger.
*/
value -= (int)ABS((int)(signed char)Facing_Dir(face) - (int)(signed char)faceto);
if (face == FACING_S) {
value -= 100;
}
if (face == FACING_SW || face == FACING_SE) value += 64;
#endif
/*
** If the value for the potential cell is greater than the last recorded potential
** value, then record this cell as the best candidate.
*/
if (bestval == -1 || value > bestval) {
bestval = value;
bestdir = face;
// } else {
// ObjectClass * obj = Map[cellnum].Cell_Occupier();
// if (obj) obj->Scatter(Coord, true);
}
}
/*
** If a suitable direction was found, then return with the direction value.
*/
moveto = 0;
if (bestval > 0) {
static DirType _desired_to_actual[FACING_COUNT] = {DIR_S, DIR_SW, DIR_NW, DIR_NW, DIR_NE, DIR_NE, DIR_NE, DIR_SE};
moveto = Adjacent_Cell(Coord_Cell(Coord), bestdir);
return(_desired_to_actual[bestdir]);
}
return(DIR_N);
}
/***********************************************************************************************
* VesselClass::LST_Open_Door -- Opens a LST door. *
* *
* This routine will initiate opening of the doors on the LST. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 06/01/1996 BWG : Created. *
*=============================================================================================*/
void VesselClass::LST_Open_Door(void)
{
assert(Vessels.ID(this) == ID);
assert(IsActive);
if (!IsDriving && !IsRotating) {
Open_Door(5, 6);
}
}
/***********************************************************************************************
* VesselClass::LST_Close_Door -- Closes a LST door. *
* *
* This routine will initiate closing of the LST door. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 06/01/1996 BWG : Created. *
*=============================================================================================*/
void VesselClass::LST_Close_Door(void)
{
assert(Vessels.ID(this) == ID);
assert(IsActive);
Close_Door(5, 6);
}
/***********************************************************************************************
* VesselClass::Mission_Unload -- Handles unloading cargo. *
* *
* This is the AI control sequence for when a transport desires to unload its cargo. *
* *
* INPUT: none *
* *
* OUTPUT: Returns with the delay before calling this routine again. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 06/01/1996 BWG : Created. *
*=============================================================================================*/
int VesselClass::Mission_Unload(void)
{
assert(Vessels.ID(this) == ID);
assert(IsActive);
enum {
INITIAL_CHECK,
MANEUVERING,
OPENING_DOOR,
UNLOADING,
CLOSING_DOOR
};
DirType dir;
CELL cell;
switch (Class->Type) {
case VESSEL_TRANSPORT:
switch (Status) {
case INITIAL_CHECK:
dir = Desired_Load_Dir(NULL, cell);
if (How_Many() > 0 && cell != 0) {
Do_Turn(dir);
Status = MANEUVERING;
return(1);
} else {
if (!How_Many()) { // don't break out if still carrying passengers
Assign_Mission(MISSION_GUARD);
}
}
break;
case MANEUVERING:
if (!IsRotating) {
LST_Open_Door();
if (Is_Door_Opening()) {
Status = OPENING_DOOR;
return(1);
}
}
break;
case OPENING_DOOR:
if (Is_Door_Open()) {
Status = UNLOADING;
return(1);
} else {
if (!Is_Door_Opening()) {
Status = INITIAL_CHECK;
}
}
break;
case UNLOADING:
if (How_Many()) {
/*
** Don't do anything if still in radio contact.
*/
if (In_Radio_Contact()) return(TICKS_PER_SECOND);
FootClass * passenger = Detach_Object();
if (passenger != NULL) {
DirType toface = DIR_S + PrimaryFacing;
bool placed = false;
for (FacingType face = FACING_N; face < FACING_COUNT; face++) {
DirType newface = toface + Facing_Dir(face);
CELL newcell = Adjacent_Cell(Coord_Cell(Coord), newface);
if (passenger->Can_Enter_Cell(newcell) == MOVE_OK) {
ScenarioInit++;
passenger->Unlimbo(Coord_Move(Coord, newface, CELL_LEPTON_W/2), newface);
ScenarioInit--;
passenger->Assign_Mission(MISSION_MOVE);
passenger->Assign_Destination(::As_Target(newcell));
passenger->Commence();
Transmit_Message(RADIO_HELLO, passenger);
Transmit_Message(RADIO_TETHER, passenger);
if (passenger->What_Am_I() == RTTI_UNIT) {
((UnitClass *)passenger)->IsToScatter = true;
}
placed = true;
break;
}
}
/*
** If the attached unit could NOT be deployed, then re-attach
** it and then bail out of this deploy process.
*/
if (!placed) {
Attach(passenger);
/*
** Tell everyone around the transport to scatter.
*/
for (FacingType face = FACING_N; face < FACING_COUNT; face++) {
CellClass * cellptr = Map[Coord].Adjacent_Cell(face);
if (cellptr && cellptr->Is_Clear_To_Move(SPEED_TRACK, true, true)) {
cellptr->Incoming(0, true);
}
}
// Status = CLOSING_DOOR;
}
else {
passenger->Look(false);
}
}
} else {
Status = CLOSING_DOOR;
}
break;
/*
** Close LST door in preparation for normal operation.
*/
case CLOSING_DOOR:
if (Is_Door_Open()) {
LST_Close_Door();
}
if (Is_Door_Closed()) {
if (IsALoaner) {
Assign_Mission(MISSION_RETREAT);
} else {
Assign_Mission(MISSION_GUARD);
}
}
break;
}
break;
default:
break;
}
return(MissionControl[Mission].Normal_Delay());
}
/***********************************************************************************************
* VesselClass::Assign_Destination -- Assign a destination for this vessel. *
* *
* This routine is called when a destination is to be assigned to this vessel. *
* *
* INPUT: target -- The destination to assign to this vessel. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 07/09/1996 JLB : Created. *
*=============================================================================================*/
void VesselClass::Assign_Destination(TARGET target)
{
assert(IsActive);
/*
** Abort early if there is anything wrong with the parameters
** or the unit already is assigned the specified destination.
*/
if (target == NavCom) return;
/*
** Transport vehicles must tell all passengers that are about to load, that they
** cannot proceed. This is accomplished with a radio message to this effect.
*/
if (In_Radio_Contact() && Class->Max_Passengers() > 0 && (Contact_With_Whom()->Is_Infantry() || Contact_With_Whom()->What_Am_I() == RTTI_UNIT)) {
long param = TARGET_NONE;
Transmit_Message(RADIO_MOVE_HERE, param); // should stop objects heading toward this transport.
Transmit_Message(RADIO_OVER_OUT);
if (!Is_Door_Closed()) {
LST_Close_Door();
}
}
if (!Is_Door_Closed()) {
LST_Close_Door();
}
DriveClass::Assign_Destination(target);
}
/***********************************************************************************************
* VesselClass::Pip_Count -- Fetches the number of pips to display on vessel. *
* *
* This routine is used to fetch the number of "fullness" pips to display on the vessel. *
* This will be the number of passengers on a transport. *
* *
* INPUT: none *
* *
* OUTPUT: Returns with the number of pips to draw on this unit. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 06/25/1995 JLB : Created. *
*=============================================================================================*/
int VesselClass::Pip_Count(void) const
{
if (Techno_Type_Class()->Max_Passengers() > 0) {
int passengers = 0;
ObjectClass const * object = Attached_Object();
for (int index = 0; index < Class_Of().Max_Pips(); index++) {
if (object != NULL) {
passengers++;
object = object->Next;
}
}
return passengers;
}
return 0;
}
/***********************************************************************************************
* VesselClass::Mission_Retreat -- Perform the retreat mission. *
* *
* This will cause the vessel to run away from the battlefield. It searches for an escape *
* map edge according to the reinforcement edge specified in the house. *
* *
* INPUT: none *
* *
* OUTPUT: Returns with the number of game frames to delay before this routine is called *
* again. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 07/09/1996 JLB : Created. *
*=============================================================================================*/
int VesselClass::Mission_Retreat(void)
{
assert(Vessels.ID(this) == ID);
assert(IsActive);
enum {
PICK_RETREAT_POINT,
TRAVEL
};
switch (Status) {
case PICK_RETREAT_POINT:
IsALoaner = true;
if (!Target_Legal(NavCom)) {
// CELL cell = Map.Calculated_Cell(House->Control.Edge, (Team.Is_Valid()) ? Team->Class->Origin : -1, -1, Class->Speed);
CELL cell = Map.Calculated_Cell(House->Control.Edge, (Team.Is_Valid()) ? Team->Class->Origin : -1, Coord_Cell(Center_Coord()), Class->Speed);
if (Team.Is_Valid()) {
Team->Remove(this);
}
Assign_Destination(::As_Target(cell));
}
Status = TRAVEL;
return(1);
case TRAVEL:
if (!Target_Legal(NavCom)) {
Status = PICK_RETREAT_POINT;
}
break;
default:
break;
}
return(MissionControl[Mission].Normal_Delay());
}
/***********************************************************************************************
* VesselClass::Is_Allowed_To_Recloak -- Can the vessel recloak now? *
* *
* Asking this question is part of the recloak process. If the answer is no, then *
* recloaking is postponed. This facilitates keeping submarines visible for longer than *
* they otherwise would be. *
* *
* INPUT: none *
* *
* OUTPUT: bool; Can this vessel recloak now? *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 07/09/1996 BWG : Created. *
*=============================================================================================*/
bool VesselClass::Is_Allowed_To_Recloak(void) const
{
return(PulseCountDown == 0);
}
/***********************************************************************************************
* VesselClass::Read_INI -- Read the vessel data from the INI database. *
* *
* This will read and create all vessels specified in the INI database. This routine is *
* called when the scenario starts. *
* *
* INPUT: ini -- Reference to the INI database to read the vessel data from. *
* *
* OUTPUT: none *
* *
* WARNINGS: Vessels will be created and placed on the map by this function. *
* *
* HISTORY: *
* 07/09/1996 JLB : Created. *
*=============================================================================================*/
void VesselClass::Read_INI(CCINIClass & ini)
{
VesselClass * vessel; // Working vessel pointer.
HousesType inhouse; // Vessel house.
VesselType classid; // Vessel class.
char buf[128];
int len = ini.Entry_Count(INI_Name());
for (int index = 0; index < len; index++) {
char const * entry = ini.Get_Entry(INI_Name(), index);
ini.Get_String(INI_Name(), entry, NULL, buf, sizeof(buf));
inhouse = HouseTypeClass::From_Name(strtok(buf, ","));
if (inhouse != HOUSE_NONE) {
classid = VesselTypeClass::From_Name(strtok(NULL, ","));
if (classid != VESSEL_NONE) {
if (HouseClass::As_Pointer(inhouse) != NULL) {
vessel = new VesselClass(classid, inhouse);
if (vessel != NULL) {
/*
** Read the raw data.
*/
int strength = atoi(strtok(NULL, ",\r\n"));
CELL cell = atoi(strtok(NULL, ",\r\n"));
COORDINATE coord = Cell_Coord(cell);
DirType dir = (DirType)atoi(strtok(NULL, ",\r\n"));
MissionType mission = MissionClass::Mission_From_Name(strtok(NULL, ",\n\r"));
vessel->Trigger = NULL;
TriggerTypeClass * tp = TriggerTypeClass::From_Name(strtok(NULL, ",\r\n"));
if (tp != NULL) {
TriggerClass * tt = Find_Or_Make(tp);
if (tt != NULL) {
tt->AttachCount++;
vessel->Trigger = tt;
}
}
if (vessel->Unlimbo(coord, dir)) {
vessel->Strength = (int)vessel->Class->MaxStrength * fixed(strength, 256);
if (vessel->Strength > vessel->Class->MaxStrength-3) vessel->Strength = vessel->Class->MaxStrength;
// vessel->Strength = Fixed_To_Cardinal(vessel->Class->MaxStrength, strength);
if (Session.Type == GAME_NORMAL || vessel->House->IsHuman) {
vessel->Assign_Mission(mission);
vessel->Commence();
} else {
vessel->Enter_Idle_Mode();
}
} else {
/*
** If the vessel could not be unlimboed, then this is a catastrophic error
** condition. Delete the vessel.
*/
delete vessel;
}
}
}
}
}
}
}
/***********************************************************************************************
* VesselClass::Write_INI -- Write all vessel scenario data to the INI database. *
* *
* This routine is used to add the vessel data (needed for scenario start) to the INI *
* database specified. If there was any preexisting vessel data in the database, it will *
* be cleared *
* *
* INPUT: ini -- Reference to the ini database to store the vessel data into. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 07/09/1996 JLB : Created. *
*=============================================================================================*/
void VesselClass::Write_INI(CCINIClass & ini)
{
/*
** First, clear out all existing vessel data from the ini file.
*/
ini.Clear(INI_Name());
/*
** Write the vessel data out.
*/
for (int index = 0; index < Vessels.Count(); index++) {
VesselClass * vessel = Vessels.Ptr(index);
if (vessel != NULL && !vessel->IsInLimbo && vessel->IsActive) {
char uname[10];
char buf[128];
sprintf(uname, "%d", index);
sprintf(buf, "%s,%s,%d,%u,%d,%s,%s",
vessel->House->Class->IniName,
vessel->Class->IniName,
vessel->Health_Ratio()*256,
Coord_Cell(vessel->Coord),
vessel->PrimaryFacing.Current(),
MissionClass::Mission_Name(vessel->Mission),
vessel->Trigger.Is_Valid() ? vessel->Trigger->Class->IniName : "None"
);
ini.Put_String(INI_Name(), uname, buf);
}
}
}
/***********************************************************************************************
* VesselClass::Start_Driver -- Starts the vessel by reserving the location it is moving to. *
* *
* This routine is called when the vessel starts moving. It will reserve the destination *
* cell so that it won't be occupied by another vessel as this one is travelling. *
* *
* INPUT: headto -- The coordinate that will be headed to. *
* *
* OUTPUT: bool; Was the destination location successfully marked? *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 07/09/1996 JLB : Created. *
*=============================================================================================*/
bool VesselClass::Start_Driver(COORDINATE & headto)
{
assert(Vessels.ID(this) == ID);
assert(IsActive);
if (DriveClass::Start_Driver(headto) && IsActive) { //BG IsActive can be cleared by Start_Driver
Mark_Track(headto, MARK_DOWN);
return(true);
}
return(false);
}
/***********************************************************************************************
* VesselClass::What_Action -- Determines action to perform on specified cell. *
* *
* This routine will determine what action to perform if the mouse were clicked over the *
* cell specified. *
* *
* INPUT: cell -- The cell that the mouse might be clicked on. *
* *
* OUTPUT: Returns with the action type that this unit will perform if the mouse were *
* clicked of the cell specified. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 07/11/1996 BWG : Created. *
*=============================================================================================*/
ActionType VesselClass::What_Action(CELL cell) const
{
assert(Vessels.ID(this) == ID);
assert(IsActive);
ActionType action = DriveClass::What_Action(cell);
if (action == ACTION_NOMOVE && Map[cell].Land_Type() == LAND_BEACH) {
return(ACTION_MOVE);
}
if (action == ACTION_NOMOVE && Class->PrimaryWeapon != NULL && Class->PrimaryWeapon->Bullet->IsSubSurface && Map[cell].Is_Bridge_Here()) {
return(ACTION_ATTACK);
}
return(action);
}
/***********************************************************************************************
* VesselClass::Rotation_AI -- Handles turret and body rotation for this vessel. *
* *
* Any turret or body rotation for this vessel will be handled by this routine. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: Only call this routine once per vessel per game logic loop. *
* *
* HISTORY: *
* 07/29/1996 JLB : Created. *
*=============================================================================================*/
void VesselClass::Rotation_AI(void)
{
if (Target_Legal(TarCom) && !IsRotating) {
DirType dir = Direction(TarCom);
if (Class->IsTurretEquipped) {
SecondaryFacing.Set_Desired(dir);
}
}
IsRotating = false;
if (Class->IsTurretEquipped) {
if (SecondaryFacing.Is_Rotating()) {
Mark(MARK_CHANGE_REDRAW);
if (SecondaryFacing.Rotation_Adjust((Class->ROT * House->GroundspeedBias)+1)) {
Mark(MARK_CHANGE_REDRAW);
}
/*
** If no further rotation is necessary, flag that the rotation
** has stopped.
*/
IsRotating = SecondaryFacing.Is_Rotating();
}
}
}
/***********************************************************************************************
* VesselClass::Combat_AI -- Handles firing and target selection for the vessel. *
* *
* This routine will process firing logic for the vessel. It includes searching for targets *
* and performing any adjustments necessary to bring the target to bear. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: Only call this routine once per vessel per game logic loop. *
* *
* HISTORY: *
* 07/29/1996 JLB : Created. *
*=============================================================================================*/
void VesselClass::Combat_AI(void)
{
if (Target_Legal(TarCom) && Is_Weapon_Equipped()) {
/*
** Determine which weapon can fire. First check for the primary weapon. If that weapon
** cannot fire, then check any secondary weapon. If neither weapon can fire, then the
** failure code returned is that from the primary weapon.
*/
int primary = What_Weapon_Should_I_Use(TarCom);
FireErrorType ok = Can_Fire(TarCom, primary);
switch (ok) {
case FIRE_OK:
Fire_At(TarCom, primary);
break;
case FIRE_FACING:
if (Class->IsTurretEquipped) {
SecondaryFacing.Set_Desired(Direction(TarCom));
} else {
if (!PrimaryFacing.Is_Rotating()) {
PrimaryFacing.Set_Desired(Direction(TarCom));
}
}
break;
case FIRE_CLOAKED:
Mark(MARK_OVERLAP_UP);
IsFiring = false;
Mark(MARK_OVERLAP_DOWN);
Do_Uncloak();
break;
}
}
}
/***********************************************************************************************
* VesselClass::Edge_Of_World_AI -- Determine if vessel is off the edge of the world. *
* *
* In addition to detecting the edge of world case, this routine will delete the vessel *
* if it occurs. *
* *
* INPUT: none *
* *
* OUTPUT: bool; Was the vessel deleted by this routine? *
* *
* WARNINGS: Be sure to examine the return value and if true, abort any further processing *
* for this vessel since it has been deleted. This routine should be called once *
* per vessel per game logic loop. *
* *
* HISTORY: *
* 07/29/1996 JLB : Created. *
*=============================================================================================*/
bool VesselClass::Edge_Of_World_AI(void)
{
if (!IsDriving && !Map.In_Radar(Coord_Cell(Coord)) && IsLocked) {
if (Team.Is_Valid()) Team->IsLeaveMap = true;
Stun();
delete this;
return(true);
}
return(false);
}
/***********************************************************************************************
* VesselClass::Repair_AI -- Process any self-repair required of this vessel. *
* *
* When a vessel repairs, it does so 'by itself' and not under direct control of another *
* object. This self repair logic is processed here. Upon repair completion of money *
* exhuastion, the repair process will terminate. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: Call this routine only once per vessel per game logic loop. *
* *
* HISTORY: *
* 07/29/1996 BWG : Created. *
*=============================================================================================*/
void VesselClass::Repair_AI(void)
{
if (IsSelfRepairing) {
if ((Frame % (TICKS_PER_MINUTE * Rule.RepairRate)) == 0) {
Mark(MARK_CHANGE);
int cost = Class->Repair_Cost();
int step = Class->Repair_Step();
if (House->Available_Money() >= cost) {
House->Spend_Money(cost);
Strength += step;
if (Strength >= Class->MaxStrength) {
Strength = Class->MaxStrength;
IsSelfRepairing = IsToSelfRepair = false;
// MBL 04.27.2020: Make only audible to the correct player
// if (IsOwnedByPlayer) Speak(VOX_UNIT_REPAIRED);
if (IsOwnedByPlayer) Speak(VOX_UNIT_REPAIRED, House);
}
}
}
}
}
#ifdef FIXIT_CARRIER // checked - ajw 9/28/98
/***********************************************************************************************
* VesselClass::Fire_At -- Try to fire upon the target specified. *
* *
* This routine is the auto-fire logic for the ship. It will check *
* to see if we're an aircraft carrier, and if so, launch one of our *
* aircraft. If we're not an aircraft carrier, it lets the higher-level *
* Fire_At logic take over. *
* *
* INPUT: target -- The target to fire upon. *
* *
* which -- Which weapon to use when firing. 0=primary, 1=secondary. *
* *
* OUTPUT: bool; Did firing occur? *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 04/26/1994 JLB : Created. *
*=============================================================================================*/
BulletClass * VesselClass::Fire_At(TARGET target, int which)
{
//PG assert(Units.ID(this) == ID);
assert(IsActive);
if (*this == VESSEL_CARRIER) {
Arm = CarrierLaunchDelay;
FootClass * passenger = Detach_Object();
if (passenger != NULL) {
ScenarioInit++;
passenger->Unlimbo(Center_Coord());
ScenarioInit--;
passenger->Assign_Mission(MISSION_ATTACK);
passenger->Assign_Target(TarCom);
passenger->Commence();
// If we've launched our last aircraft, discontinue attacking.
if (!How_Many()) Assign_Target(TARGET_NONE);
}
} else {
return DriveClass::Fire_At(target, which);
}
return (NULL);
}
#endif