CnC_Remastered_Collection/TIBERIANDAWN/TURRET.CPP

508 lines
27 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: F:\projects\c&c\vcs\code\turret.cpv 2.18 16 Oct 1995 16:50:38 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 : TURRET.CPP *
* *
* Programmer : Joe L. Bostic *
* *
* Start Date : April 25, 1994 *
* *
* Last Update : August 13, 1995 [JLB] *
* *
*---------------------------------------------------------------------------------------------*
* Functions: *
* TurretClass::AI -- Handles the reloading of the turret weapon. *
* TurretClass::Can_Fire -- Determines if turret can fire upon target. *
* TurretClass::Debug_Dump -- Debug printing of turret values. *
* TurretClass::Fire_At -- Try to fire upon the target specified. *
* TurretClass::Fire_Coord -- Determines the coorindate that projectile would appear. *
* TurretClass::Fire_Direction -- Determines the directinon of firing. *
* TurretClass::Ok_To_Move -- Queries whether the vehicle can move. *
* TurretClass::TurretClass -- Normal constructor for the turret class. *
* TurretClass::TurretClass -- The default constructor for turret class objects. *
* TurretClass::Unlimbo -- Unlimboes turret object. *
* TurretClass::~TurretClass -- Default destructor for turret class objects. *
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#include "function.h"
#include "turret.h"
/***********************************************************************************************
* TurretClass::~TurretClass -- Default destructor for turret class objects. *
* *
* This is the default destructor for turret class objects. It does nothing. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 08/13/1995 JLB : Created. *
*=============================================================================================*/
TurretClass::~TurretClass(void)
{
}
/***********************************************************************************************
* TurretClass::TurretClass -- The default constructor for turret class objects. *
* *
* This is the default constructor for turret class objects. It does nothing. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 08/13/1995 JLB : Created. *
*=============================================================================================*/
TurretClass::TurretClass(void)
{
}
/***********************************************************************************************
* TurretClass::TurretClass -- Normal constructor for the turret class. *
* *
* This is the normal constructor for the turret class. It merely sets the turret up to *
* face north. *
* *
* INPUT: classid -- The type id for this particular unit. *
* *
* house -- The house that this unit will belong to. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 02/02/1995 JLB : Created. *
*=============================================================================================*/
TurretClass::TurretClass(UnitType classid, HousesType house) :
DriveClass(classid, house)
{
Reload = 0;
}
#ifdef CHEAT_KEYS
/***********************************************************************************************
* TurretClass::Debug_Dump -- Debug printing of turret values. *
* *
* This routine is used to display the current values of this turret *
* class instance. It is primarily used in the debug output screen. *
* *
* INPUT: x,y -- Monochrome screen coordinates to display data. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 05/12/1994 JLB : Created. *
*=============================================================================================*/
void TurretClass::Debug_Dump(MonoClass *mono) const
{
mono->Set_Cursor(36, 3);mono->Printf("%02X:%02X", SecondaryFacing.Current(), SecondaryFacing.Desired());
mono->Set_Cursor(28, 7);mono->Printf("%2d", Arm);
DriveClass::Debug_Dump(mono);
}
#endif
/***********************************************************************************************
* TurretClass::Ok_To_Move -- Queries whether the vehicle can move. *
* *
* This virtual routine is used to determine if the vehicle is allowed *
* to start moving. It is typically called when the vehicle desires *
* to move but needs confirmation from the turret logic before *
* proceeding. This happens when dealing with a vehicle that must have *
* its turret face the same direction as the body before the vehicle *
* may begin movement. *
* *
* INPUT: dir -- The facing the unit wants to travel in. *
* *
* OUTPUT: bool; Can the unit begin forward movement now? *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 05/12/1994 JLB : Created. *
*=============================================================================================*/
bool TurretClass::Ok_To_Move(DirType dir)
{
if (Class->IsLockTurret) {
if (IsRotating) {
return(false);
} else {
if (SecondaryFacing.Difference(dir)) {
SecondaryFacing.Set_Desired(dir);
return(false);
}
}
}
return(true);
}
/***********************************************************************************************
* TurretClass::AI -- Handles the reloading of the turret weapon. *
* *
* This processes the reloading of the turret. It does this by decrementing the arming *
* countdown timer and when it reaches zero, the turret may fire. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 06/21/1994 JLB : Created. *
*=============================================================================================*/
void TurretClass::AI(void)
{
DriveClass::AI();
/*
** A unit with a constant rotating radar dish is handled here.
*/
if (Class->IsRadarEquipped) {
SecondaryFacing.Set((DirType)(SecondaryFacing.Current() + 8));
Mark(MARK_CHANGE);
} else {
IsRotating = false;
if (Class->IsTurretEquipped) {
if (IsTurretLockedDown) {
SecondaryFacing.Set_Desired(PrimaryFacing.Current());
}
if (SecondaryFacing.Is_Rotating()) {
if (SecondaryFacing.Rotation_Adjust(Class->ROT+1)) {
Mark(MARK_CHANGE);
}
/*
** If no further rotation is necessary, flag that the rotation
** has stopped.
*/
IsRotating = SecondaryFacing.Is_Rotating();
} else {
if (!IsTurretLockedDown && !Target_Legal(TarCom)) {
if (!Target_Legal(NavCom)) {
SecondaryFacing.Set_Desired(PrimaryFacing.Current());
} else {
SecondaryFacing.Set_Desired(Direction(NavCom));
}
}
}
}
}
}
/***********************************************************************************************
* TurretClass::Fire_At -- Try to fire upon the target specified. *
* *
* This routine is the auto-fire logic for the turret. It will check *
* to see if firing is technically legal given the specified target. *
* If it is legal to fire, it does so. It is safe to call this routine *
* every game tick. *
* *
* 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 * TurretClass::Fire_At(TARGET target, int which)
{
BulletClass * bullet = NULL;
WeaponTypeClass const * weapon = (which == 0) ? &Weapons[Class->Primary] : &Weapons[Class->Secondary];
if (Can_Fire(target, which) == FIRE_OK) {
bullet = DriveClass::Fire_At(target, which);
if (bullet) {
/*
** Possible reload timer set.
*/
if (*this == UNIT_MSAM && Reload == 0) {
Reload = TICKS_PER_SECOND * 30;
}
}
}
return(bullet);
}
/***********************************************************************************************
* TurretClass::Can_Fire -- Determines if turret can fire upon target. *
* *
* This routine determines if the turret can fire upon the target *
* specified. *
* *
* INPUT: target -- The target to fire upon. *
* *
* which -- Which weapon to use to determine legality to fire. 0=primary, *
* 1=secondary. *
* *
* OUTPUT: Returns the fire status type that indicates if firing is allowed and if not, why. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 04/26/1994 JLB : Created. *
* 06/01/1994 JLB : Returns reason why it can't fire. *
*=============================================================================================*/
FireErrorType TurretClass::Can_Fire(TARGET target, int which) const
{
DirType dir; // The facing to impart upon the projectile.
int diff;
FireErrorType fire = DriveClass::Can_Fire(target, which);
if (fire == FIRE_OK) {
WeaponTypeClass const * weapon = (which == 0) ? &Weapons[Class->Primary] : &Weapons[Class->Secondary];
/*
** If this unit cannot fire while moving, then bail.
*/
if ((!Class->IsTurretEquipped || Class->IsLockTurret) && 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 && !BulletTypeClass::As_Reference(weapon->Fires).IsHoming) {
return(FIRE_ROTATING);
}
dir = Direction(target);
/*
** 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);
/*
** Special flame tank logic.
*/
if (weapon->Fires == BULLET_FLAME) {
if (Dir_Facing(dir) == Dir_Facing(PrimaryFacing)) {
diff = 0;
}
}
if (BulletTypeClass::As_Reference(weapon->Fires).IsHoming) {
diff >>= 2;
}
if (diff < 8) {
return(DriveClass::Can_Fire(target, which));
}
return(FIRE_FACING);
}
return(fire);
}
/***********************************************************************************************
* TurretClass::Fire_Coord -- Determines the coorindate that projectile would appear. *
* *
* Use this routine to determine the exact coordinate that a projectile would appear if it *
* were fired from this unit. For units with turrets, typically, this would be at the end *
* of the barrel. *
* *
* INPUT: none *
* *
* OUTPUT: Returns with coordinate of where a projectile should appear if this unit were *
* to fire one. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 12/28/1994 JLB : Created. *
*=============================================================================================*/
COORDINATE TurretClass::Fire_Coord(int which) const
{
COORDINATE coord = Center_Coord();
int dist = 0;
int lateral = 0;
DirType dir = PrimaryFacing.Current();
if (Class->IsTurretEquipped) {
dir = SecondaryFacing.Current();
}
switch (Class->Type) {
case UNIT_GUNBOAT:
coord = Coord_Move(coord, PrimaryFacing.Current(), Pixel2Lepton[Class->TurretOffset]);
dist = 0x0060;
break;
case UNIT_ARTY:
coord = Coord_Move(coord, DIR_N, 0x0040);
dist = 0x0060;
break;
case UNIT_FTANK:
dist = 0x30;
if (IsSecondShot) {
coord = Coord_Move(coord, (DirType)(dir+DIR_E), 0x20);
} else {
coord = Coord_Move(coord, (DirType)(dir+DIR_W), 0x20);
}
break;
case UNIT_HTANK:
coord = Coord_Move(coord, DIR_N, 0x0040);
if (which == 0) {
dist = 0x00C0;
lateral = 0x0028;
} else {
dist = 0x0008;
lateral = 0x0040;
}
if (IsSecondShot) {
coord = Coord_Move(coord, (DirType)(dir+DIR_E), lateral);
} else {
coord = Coord_Move(coord, (DirType)(dir+DIR_W), lateral);
}
break;
case UNIT_LTANK:
coord = Coord_Move(coord, DIR_N, 0x0020);
dist = 0x00C0;
break;
case UNIT_MTANK:
coord = Coord_Move(coord, DIR_N, 0x0030);
dist = 0x00C0;
break;
case UNIT_APC:
case UNIT_JEEP:
case UNIT_BUGGY:
coord = Coord_Move(coord, DIR_N, 0x0030);
dist = 0x0030;
break;
#ifdef PETROGLYPH_EXAMPLE_MOD
case UNIT_NUKE_TANK:
coord = Coord_Move(coord, DIR_N, 0x00A0);
dist = 0x00A0;
break;
#endif //PETROGLYPH_EXAMPLE_MOD
}
if (dist) {
coord = Coord_Move(coord, dir, dist);
}
return(coord);
}
/***********************************************************************************************
* TurretClass::Unlimbo -- Unlimboes turret object. *
* *
* This routine is called when a turret equipped unit unlimboes. It sets the turret to *
* face the same direction as the body. *
* *
* INPUT: coord -- The coordinate where the unit is unlimboing. *
* *
* dir -- The desired body and turret facing to use. *
* *
* OUTPUT: Was the unit unlimboed successfully? *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 06/25/1995 JLB : Created. *
*=============================================================================================*/
bool TurretClass::Unlimbo(COORDINATE coord, DirType dir)
{
if (DriveClass::Unlimbo(coord, dir)) {
SecondaryFacing = dir;
return(true);
}
return(false);
}
/***********************************************************************************************
* TurretClass::Fire_Direction -- Determines the directinon of firing. *
* *
* This routine will return with the facing that a projectile will travel if it was *
* fired at this instant. The facing should match the turret facing for those units *
* equipped with a turret. If the unit doesn't have a turret, then it will be the facing *
* of the body. *
* *
* INPUT: none *
* *
* OUTPUT: Returns with the default firing direction for a projectile. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 06/25/1995 JLB : Created. *
*=============================================================================================*/
DirType TurretClass::Fire_Direction(void) const
{
if (Class->IsTurretEquipped) {
if (*this == UNIT_MSAM) {
int diff1 = SecondaryFacing.Difference(DIR_E);
int diff2 = SecondaryFacing.Difference(DIR_W);
diff1 = ABS(diff1);
diff2 = ABS(diff2);
int diff = MIN(diff1, diff2);
int adj = Fixed_To_Cardinal(ABS(SecondaryFacing.Difference(DIR_N)), 64-diff);
if (SecondaryFacing.Difference(DIR_N) < 0) {
return(DirType)(SecondaryFacing - (DirType)adj);
} else {
return(DirType)(SecondaryFacing + (DirType)adj);
}
}
return(SecondaryFacing.Current());
}
return(PrimaryFacing.Current());
}