From fc5cd5a7751e61e6805c3a6834f4ac279e22ce92 Mon Sep 17 00:00:00 2001 From: PG-SteveT <63470275+PG-SteveT@users.noreply.github.com> Date: Mon, 22 Jun 2020 09:43:21 -0700 Subject: [PATCH 1/2] Command & Conquer Remastered post-launch patch Improvements to harvester resource finding logic. Don't allow the Advanced Comm Center to be capturable in skirmish or multiplayer. Increased failed pathfinding fudge factor. Buildings accept the Guard command if they can attack. Don't allow force capturing of ally structures. Fixes for laser Orcas in S3cr3t M1ss10n. Properly restore them after save. Reset Orcas after loads. Fixed flag animation rendering in CTF. Potentially fix a crash if aircraft are destroyed outside the map bounds. Fixed legacy Obelisk line rendering. Fix out-of-bounds crash in TD; issue was already fixed in RA. Disable capture flag on Commandos. Drop the flag when entering the limbo state. Fixed end game race condition, winning team ID is always sent before individual player win/lose messages. Fixed Chan spawn on SCB10EA. Don't show enter cursor for enemy units on refineries and repair pads. Changing right-click support for first put building on hold, and then subsequenct right-clicks to decrement that queue count for 1x or 5x; Then, 1x or 5x Left click will resume from hold. Don't debug reveal legacy rendering when a player is defeated. Fixed crash when loading saves of custom campaign maps. Reset harvester archived target when given a direct harvest order. Prevent NOD cargo planes from being force attacked. Fixed unit selection on load. Migrated queued repair pad functionality from RA to TD. Randomly animate infantry in area guard mode. Fixed crash accessing inactive objects. Added some walls in SCG08EB to prevent civilians from killing themselves. TD + RA: Audio: Overiding "Our base is under attack" cooldown timing from legacy from 2 minutes to 30 seconds, so it will be heard 4x as often. Fixed adjacent cell out-of-bounds crash issues. Kill player on disconnect Fixed and improved build time calculations to be consistent between TD and RA. Don't show health bars for cloaked Stealth Tanks in the legacy renderer. Fix selection of individual control groups after mixed selection. More adjustments to SCG08EB; switch C7 to C5, and add civilian AI to avoid Tiberium. Extra safety checks for units that have no weapons and aircraft that can't hunt. Fix loading of multiple infantry onto an APC. Additional safety checks for invalid coordinates. Prevent units from being instantly repaired. Fix map passability. Fail Allied mission 5B if the spy re-boards the starting transport (matches 5A and 5C behavior). Fixed multiplayer formation move causing units to move at light speed. Ignore movement destination checks if a unit is part of a mission-driven team. Fix buffer overrun crash. Ignore mines when determining win conditions. Fixed river passability in Blue Lakes. --- REDALERT/ADATA.CPP | 26 ++++ REDALERT/AIRCRAFT.CPP | 31 +++-- REDALERT/ANIM.CPP | 47 ++++++-- REDALERT/ANIM.H | 5 +- REDALERT/BUILDING.CPP | 7 +- REDALERT/CELL.CPP | 157 +++++++++++++----------- REDALERT/CELL.H | 14 ++- REDALERT/COMBAT.CPP | 3 +- REDALERT/CONQUER.CPP | 41 ++++--- REDALERT/DEFINES.H | 1 + REDALERT/DISPLAY.CPP | 2 +- REDALERT/DLLInterface.cpp | 92 ++++++++++---- REDALERT/DLLInterface.h | 3 +- REDALERT/DRIVE.CPP | 2 +- REDALERT/EXTERNS.H | 3 +- REDALERT/FACTORY.CPP | 24 +--- REDALERT/FINDPATH.CPP | 4 +- REDALERT/GLOBALS.CPP | 3 +- REDALERT/HOUSE.CPP | 27 +++-- REDALERT/IOMAP.CPP | 12 +- REDALERT/MiscAsm.cpp | 2 +- REDALERT/OBJECT.CPP | 2 +- REDALERT/REINF.CPP | 3 +- REDALERT/SCENARIO.CPP | 37 ++++++ REDALERT/TEAM.CPP | 18 +-- REDALERT/TEAM.H | 6 + REDALERT/TECHNO.CPP | 156 ++++++++++++------------ REDALERT/TECHNO.H | 1 - REDALERT/TYPE.H | 6 +- REDALERT/UNIT.CPP | 84 ++++++++----- REDALERT/UNIT.H | 3 +- REDALERT/VESSEL.CPP | 4 +- REDALERT/WIN32LIB/DrawMisc.cpp | 2 +- TIBERIANDAWN/ADATA.CPP | 27 ++++- TIBERIANDAWN/AIRCRAFT.CPP | 17 ++- TIBERIANDAWN/ANIM.CPP | 41 +++++-- TIBERIANDAWN/ANIM.H | 13 +- TIBERIANDAWN/BUILDING.CPP | 54 ++++++++- TIBERIANDAWN/BUILDING.H | 1 + TIBERIANDAWN/BULLET.CPP | 4 +- TIBERIANDAWN/CELL.CPP | 136 ++++++++++++--------- TIBERIANDAWN/CELL.H | 14 ++- TIBERIANDAWN/COMBAT.CPP | 3 +- TIBERIANDAWN/CONQUER.CPP | 14 +-- TIBERIANDAWN/DEFINES.H | 4 + TIBERIANDAWN/DLLInterface.cpp | 73 ++++++++--- TIBERIANDAWN/DLLInterface.h | 3 +- TIBERIANDAWN/DRIVE.CPP | 14 ++- TIBERIANDAWN/FACTORY.CPP | 19 +-- TIBERIANDAWN/FINDPATH.CPP | 16 ++- TIBERIANDAWN/FOOT.CPP | 63 ++++++---- TIBERIANDAWN/FUNCTION.H | 1 + TIBERIANDAWN/HOUSE.CPP | 27 +++-- TIBERIANDAWN/IDATA.CPP | 3 +- TIBERIANDAWN/INFANTRY.CPP | 36 +++++- TIBERIANDAWN/INI.CPP | 5 + TIBERIANDAWN/IOMAP.CPP | 17 +++ TIBERIANDAWN/MAP.CPP | 2 +- TIBERIANDAWN/MiscAsm.cpp | 2 +- TIBERIANDAWN/OBJECT.CPP | 6 + TIBERIANDAWN/OBJECT.H | 1 + TIBERIANDAWN/OVERLAY.CPP | 3 +- TIBERIANDAWN/SAVELOAD.CPP | 4 +- TIBERIANDAWN/SCENARIO.CPP | 33 +++-- TIBERIANDAWN/TECHNO.CPP | 78 +++++++++--- TIBERIANDAWN/TYPE.H | 8 +- TIBERIANDAWN/UNIT.CPP | 187 +++++++++++++++++++++-------- TIBERIANDAWN/UNIT.H | 3 +- TIBERIANDAWN/WIN32LIB/DrawMisc.cpp | 2 +- TIBERIANDAWN/WIN32LIB/FACINGFF.h | 2 +- 70 files changed, 1191 insertions(+), 573 deletions(-) diff --git a/REDALERT/ADATA.CPP b/REDALERT/ADATA.CPP index 0d7f38d..d3ab825 100644 --- a/REDALERT/ADATA.CPP +++ b/REDALERT/ADATA.CPP @@ -2112,6 +2112,31 @@ static AnimTypeClass const MineExp1( ANIM_NONE ); +static AnimTypeClass const Flag( + ANIM_FLAG, // Animation number. + "FLAGFLY", // Data name of animation. + 21, // Maximum dimension of animation. + 0, // Biggest animation stage. + false, // Theater specific art imagery? + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + -1, // Ending frame of loop back. + -1, // Number of animation stages. + -1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); + #ifdef FIXIT_ANTS static AnimTypeClass const Ant1Death( ANIM_ANT1_DEATH, // Animation number. @@ -2424,6 +2449,7 @@ void AnimTypeClass::Init_Heap(void) new AnimTypeClass(CrateTQuake); new AnimTypeClass(ParaBomb); new AnimTypeClass(MineExp1); + new AnimTypeClass(Flag); #ifdef FIXIT_ANTS new AnimTypeClass(Ant1Death); new AnimTypeClass(Ant2Death); diff --git a/REDALERT/AIRCRAFT.CPP b/REDALERT/AIRCRAFT.CPP index 7722529..16e1a66 100644 --- a/REDALERT/AIRCRAFT.CPP +++ b/REDALERT/AIRCRAFT.CPP @@ -1621,23 +1621,28 @@ ResultType AircraftClass::Take_Damage(int & damage, int distance, WarheadType wa */ switch (res) { case RESULT_DESTROYED: - Kill_Cargo(source); - Death_Announcement(); - new AnimClass(ANIM_FBALL1, Target_Coord()); + { + Kill_Cargo(source); + Death_Announcement(); + COORDINATE coord = Target_Coord(); + if (!(coord & HIGH_COORD_MASK)) { + new AnimClass(ANIM_FBALL1, coord); + } - /* - ** Parachute a survivor if possible. - */ - if (Class->IsCrew && Percent_Chance(90) && Map[Center_Coord()].Is_Clear_To_Move(SPEED_FOOT, true, false)) { - InfantryClass * infantry = new InfantryClass(INFANTRY_E1, House->Class->House); - if (infantry != NULL) { - if (!infantry->Paradrop(Center_Coord())) { - delete infantry; + /* + ** Parachute a survivor if possible. + */ + if (Class->IsCrew && Percent_Chance(90) && Map[Center_Coord()].Is_Clear_To_Move(SPEED_FOOT, true, false)) { + InfantryClass * infantry = new InfantryClass(INFANTRY_E1, House->Class->House); + if (infantry != NULL) { + if (!infantry->Paradrop(Center_Coord())) { + delete infantry; + } } } - } - delete this; + delete this; + } break; default: diff --git a/REDALERT/ANIM.CPP b/REDALERT/ANIM.CPP index 65d3a16..09796df 100644 --- a/REDALERT/ANIM.CPP +++ b/REDALERT/ANIM.CPP @@ -260,6 +260,24 @@ void AnimClass::Draw_It(int x, int y, WindowNumberType window) const void const * transtable = NULL; int shapenum = Class->Start + Fetch_Stage(); void const * remap = NULL; + ShapeFlags_Type flags = SHAPE_CENTER|SHAPE_WIN_REL; + bool alt = false; + + /* + ** Some animations require special fixups. + */ + switch (Class->Type) { + case ANIM_ATOM_BLAST: + transtable = Map.UnitShadow; + break; + + case ANIM_FLAG: + x += (ICON_PIXEL_W / 2) - 2; + y += (3 * ICON_PIXEL_H / 4) - Get_Build_Frame_Height(shapefile); + transtable = Map.UnitShadow; + alt = true; + break; + } /* ** If the translucent table hasn't been determined yet, then check to see if it @@ -267,13 +285,19 @@ void AnimClass::Draw_It(int x, int y, WindowNumberType window) const */ if (transtable == NULL && Class->IsWhiteTrans) transtable = DisplayClass::WhiteTranslucentTable; if (transtable == NULL && Class->IsTranslucent) transtable = DisplayClass::TranslucentTable; - if (Class->Type == ANIM_ATOM_BLAST) transtable = Map.UnitShadow; /* ** Set the shape flags to properly take into account any fading or ghosting ** table necessary. */ - ShapeFlags_Type flags = SHAPE_CENTER|SHAPE_WIN_REL; + if (alt) { + flags = flags | SHAPE_FADING; + if (OwnerHouse != HOUSE_NONE) { + remap = HouseClass::As_Pointer(OwnerHouse)->Remap_Table(false); + } else { + remap = ColorRemaps[PCOLOR_GOLD].RemapTable; + } + } if (transtable != NULL) flags = flags | SHAPE_GHOST; /* @@ -357,6 +381,7 @@ short const * AnimClass::Overlap_List(void) const ( MAP_CELL_W * 2) - 1, ( MAP_CELL_W * 2), ( MAP_CELL_W * 2) + 1, REFRESH_EOL }; + static short const OverlapFlag[] = { 0, 1, -MAP_CELL_W, -(MAP_CELL_W-1), MAP_CELL_W, MAP_CELL_W+1, REFRESH_EOL }; if (IsToDelete) { static short const _list[] = {REFRESH_EOL}; @@ -367,6 +392,10 @@ short const * AnimClass::Overlap_List(void) const return(OverlapAtom); } + if (Class->Type == ANIM_FLAG) { + return(OverlapFlag); + } + #ifdef PARTIAL IsTheaterShape = Class->IsTheater; if (Class->Get_Image_Data() != NULL) { @@ -512,7 +541,7 @@ void AnimClass::operator delete(void * ptr) * 05/31/1994 JLB : Created. * * 08/03/1994 JLB : Added a delayed affect parameter. * *=============================================================================================*/ -AnimClass::AnimClass(AnimType animnum, COORDINATE coord, unsigned char timedelay, unsigned char loop) : +AnimClass::AnimClass(AnimType animnum, COORDINATE coord, unsigned char timedelay, char loop) : ObjectClass(RTTI_ANIM, Anims.ID(this)), Class(AnimTypes.Ptr((int)animnum)), xObject(TARGET_NONE), @@ -559,8 +588,12 @@ IsTheaterShape = false; Map.Sight_From(Coord_Cell(coord), Rule.DropZoneRadius / CELL_LEPTON_W, PlayerPtr, false); } - Loops = (unsigned char)(max(loop, 1) * Class->Loops); - Loops = (unsigned char)max(Loops, 1); + if (Class->Loops >= 0) { + Loops = (char)(max(loop, 1) * Class->Loops); + Loops = (char)max(Loops, 1); + } else { + Loops = Class->Loops; + } /* ** If the animation starts immediately, then play the associated sound effect now. @@ -812,8 +845,8 @@ void AnimClass::AI(void) ** Determine if this animation should loop another time. If so, then start the loop ** but if not, then proceed into the animation termination handler. */ - if (Loops) Loops--; - if (Loops) { + if (Loops > 0) Loops--; + if (Loops != 0) { Set_Stage(Class->LoopStart); } else { diff --git a/REDALERT/ANIM.H b/REDALERT/ANIM.H index ff69a8a..2dc3390 100644 --- a/REDALERT/ANIM.H +++ b/REDALERT/ANIM.H @@ -51,7 +51,7 @@ class AnimClass : public ObjectClass, public StageClass { public: - AnimClass(AnimType animnum, COORDINATE coord, unsigned char timedelay=0, unsigned char loop=1); + AnimClass(AnimType animnum, COORDINATE coord, unsigned char timedelay=0, char loop=1); AnimClass(NoInitClass const & x) : ObjectClass(x), Class(x), StageClass(x) {}; virtual ~AnimClass(void); @@ -79,6 +79,7 @@ class AnimClass : public ObjectClass, public StageClass { void Set_Visible_Flags(unsigned flags) { VisibleFlags = flags; } unsigned Get_Visible_Flags() const { return (Delay == 0) ? VisibleFlags : 0; } + virtual HousesType Owner(void) const {return OwnerHouse;}; virtual bool Can_Place_Here(COORDINATE ) const {return true;} virtual bool Mark(MarkType mark=MARK_CHANGE); virtual bool Render(bool forced) const; @@ -121,7 +122,7 @@ class AnimClass : public ObjectClass, public StageClass { ** This counter tells how many more times the animation should loop before it ** terminates. */ - unsigned char Loops; + char Loops; protected: void Middle(void); diff --git a/REDALERT/BUILDING.CPP b/REDALERT/BUILDING.CPP index 8a8e2d8..ac2b939 100644 --- a/REDALERT/BUILDING.CPP +++ b/REDALERT/BUILDING.CPP @@ -358,6 +358,9 @@ RadioMessageType BuildingClass::Receive_Message(RadioClass * from, RadioMessageT */ case RADIO_OVER_OUT: Begin_Mode(BSTATE_IDLE); + if (*this == STRUCT_REPAIR) { + Assign_Mission(MISSION_GUARD); + } TechnoClass::Receive_Message(from, message, param); return(RADIO_ROGER); @@ -4811,8 +4814,8 @@ int BuildingClass::Mission_Unload(void) ** Scatter everything around the weapon's factory door. */ for (FacingType f = FACING_FIRST; f < FACING_COUNT; f++) { - CellClass * cptr = &cellptr->Adjacent_Cell(f); - if (cptr->Cell_Building() == NULL) { + CellClass * cptr = cellptr->Adjacent_Cell(f); + if (cptr && cptr->Cell_Building() == NULL) { cptr->Incoming(coord, true, true); } } diff --git a/REDALERT/CELL.CPP b/REDALERT/CELL.CPP index a8e0ed6..e27b5cd 100644 --- a/REDALERT/CELL.CPP +++ b/REDALERT/CELL.CPP @@ -125,7 +125,8 @@ CellClass::CellClass(void) : Land(LAND_CLEAR), OverrideLand(LAND_NONE), IsMappedByPlayerMask(0), - IsVisibleByPlayerMask(0) + IsVisibleByPlayerMask(0), + CTFFlag(NULL) { for (int zone = MZONE_FIRST; zone < MZONE_COUNT; zone++) { Zones[zone] = 0; @@ -212,7 +213,7 @@ TechnoClass * CellClass::Cell_Techno(int x, int y) const if (Cell_Occupier()) { object = Cell_Occupier(); - while (object) { + while (object && object->IsActive) { if (object->Is_Techno()) { COORDINATE coord = Coord_Fraction(object->Center_Coord()); long dist = Distance(coord, click); @@ -248,7 +249,7 @@ ObjectClass * CellClass::Cell_Find_Object(RTTIType rtti) const ObjectClass * object = Cell_Occupier(); - while (object != NULL) { + while (object != NULL && object->IsActive) { if (object->What_Am_I() == rtti) { return(object); } @@ -1236,14 +1237,6 @@ void CellClass::Draw_It(int x, int y, bool objects) const #endif } - /* - ** Draw the flag if there is one located at this cell. - */ - if (IsFlagged) { - void const * flag_remap = HouseClass::As_Pointer(Owner)->Remap_Table(false, REMAP_NORMAL); - CC_Draw_Shape(MFCD::Retrieve("FLAGFLY.SHP"), Frame % 14, x+(ICON_PIXEL_W/2), y+(ICON_PIXEL_H/2), WINDOW_TACTICAL, SHAPE_CENTER|SHAPE_GHOST|SHAPE_FADING, flag_remap, DisplayClass::UnitShadow); - } - #ifdef CHEAT_KEYS } #endif @@ -1263,22 +1256,20 @@ void CellClass::Draw_It(int x, int y, bool objects) const ** hack overpass after the cells are redrawn so that subs can be ** redrawn separately. */ - ObjectClass * optr[20 + ARRAY_SIZE(Overlapper)]; - int count = 0; + static DynamicVectorClass optr(20 + ARRAY_SIZE(Overlapper)); + optr.Delete_All(); ObjectClass * object = Cell_Occupier(); while (object != NULL) { if (!object->IsActive) break; - optr[count] = object; + optr.Add(object); object->IsToDisplay = true; object = object->Next; - count++; } for (int index = 0; index < ARRAY_SIZE(Overlapper); index++) { object = Overlapper[index]; if (object != NULL && object->IsActive) { object->IsToDisplay = true; - optr[count] = object; - count++; + optr.Add(object); } } @@ -1286,7 +1277,7 @@ void CellClass::Draw_It(int x, int y, bool objects) const ** Sort the object list so that objects will be drawn from ** back to front. */ - switch (count) { + switch (optr.Count()) { /* ** If there are zero or one object, then sorting is @@ -1325,14 +1316,14 @@ void CellClass::Draw_It(int x, int y, bool objects) const ** a quicksort. */ default: - qsort(optr, count, sizeof(optr[0]), _ocompare); + qsort(&optr[0], optr.Count(), sizeof(ObjectClass*), _ocompare); break; } /* ** Draw any objects that happen to be in or overlapping this cell. */ - for (int index = 0; index < count; index++) { + for (int index = 0; index < optr.Count(); index++) { object = optr[index]; int xx,yy; if (object->IsToDisplay && (!object->Is_Techno() || ((TechnoClass *)object)->Visual_Character() == VISUAL_NORMAL) && Map.Coord_To_Pixel(object->Render_Coord(), xx, yy)) { @@ -1608,9 +1599,9 @@ void CellClass::Wall_Update(void) static FacingType _offsets[5] = {FACING_N, FACING_E, FACING_S, FACING_W, FACING_NONE}; for (unsigned index = 0; index < (sizeof(_offsets)/sizeof(_offsets[0])); index++) { - CellClass & newcell = Adjacent_Cell(_offsets[index]); + CellClass * newcell = Adjacent_Cell(_offsets[index]); - if (newcell.Overlay != OVERLAY_NONE && OverlayTypeClass::As_Reference(newcell.Overlay).IsWall) { + if (newcell && newcell->Overlay != OVERLAY_NONE && OverlayTypeClass::As_Reference(newcell->Overlay).IsWall) { int icon = 0; /* @@ -1618,45 +1609,46 @@ void CellClass::Wall_Update(void) ** cells. */ for (unsigned i = 0; i < 4; i++) { - if (newcell.Adjacent_Cell(_offsets[i]).Overlay == newcell.Overlay) { + CellClass * adjcell = newcell->Adjacent_Cell(_offsets[i]); + if (adjcell && adjcell->Overlay == newcell->Overlay) { icon |= 1 << i; } } - newcell.OverlayData = (newcell.OverlayData & 0xFFF0) | icon; + newcell->OverlayData = (newcell->OverlayData & 0xFFF0) | icon; /* ** Handle special cases for the incomplete damaged wall sets. If a damage stage ** is calculated, but there is no artwork for it, then consider the wall to be ** completely destroyed. */ - if (newcell.Overlay == OVERLAY_BRICK_WALL && newcell.OverlayData == 48) { - newcell.Overlay = OVERLAY_NONE; - newcell.OverlayData = 0; - Detach_This_From_All(::As_Target(newcell.Cell_Number()), true); + if (newcell->Overlay == OVERLAY_BRICK_WALL && newcell->OverlayData == 48) { + newcell->Overlay = OVERLAY_NONE; + newcell->OverlayData = 0; + Detach_This_From_All(::As_Target(newcell->Cell_Number()), true); } - if (newcell.Overlay == OVERLAY_SANDBAG_WALL && newcell.OverlayData == 16) { - newcell.Overlay = OVERLAY_NONE; - newcell.OverlayData = 0; - Detach_This_From_All(::As_Target(newcell.Cell_Number()), true); + if (newcell->Overlay == OVERLAY_SANDBAG_WALL && newcell->OverlayData == 16) { + newcell->Overlay = OVERLAY_NONE; + newcell->OverlayData = 0; + Detach_This_From_All(::As_Target(newcell->Cell_Number()), true); } - if (newcell.Overlay == OVERLAY_CYCLONE_WALL && newcell.OverlayData == 32) { - newcell.Overlay = OVERLAY_NONE; - newcell.OverlayData = 0; - Detach_This_From_All(::As_Target(newcell.Cell_Number()), true); + if (newcell->Overlay == OVERLAY_CYCLONE_WALL && newcell->OverlayData == 32) { + newcell->Overlay = OVERLAY_NONE; + newcell->OverlayData = 0; + Detach_This_From_All(::As_Target(newcell->Cell_Number()), true); } - if (newcell.Overlay == OVERLAY_FENCE && (newcell.OverlayData == 16 || newcell.OverlayData == 32)) { - newcell.Overlay = OVERLAY_NONE; - newcell.OverlayData = 0; - Detach_This_From_All(::As_Target(newcell.Cell_Number()), true); + if (newcell->Overlay == OVERLAY_FENCE && (newcell->OverlayData == 16 || newcell->OverlayData == 32)) { + newcell->Overlay = OVERLAY_NONE; + newcell->OverlayData = 0; + Detach_This_From_All(::As_Target(newcell->Cell_Number()), true); } - if (newcell.Overlay == OVERLAY_BARBWIRE_WALL && newcell.OverlayData == 16) { - newcell.Overlay = OVERLAY_NONE; - newcell.OverlayData = 0; - Detach_This_From_All(::As_Target(newcell.Cell_Number()), true); + if (newcell->Overlay == OVERLAY_BARBWIRE_WALL && newcell->OverlayData == 16) { + newcell->Overlay = OVERLAY_NONE; + newcell->OverlayData = 0; + Detach_This_From_All(::As_Target(newcell->Cell_Number()), true); } - newcell.Recalc_Attributes(); - newcell.Redraw_Objects(); + newcell->Recalc_Attributes(); + newcell->Redraw_Objects(); } } } @@ -1773,10 +1765,14 @@ int CellClass::Reduce_Wall(int damage) OverlayData = 0; Recalc_Attributes(); Redraw_Objects(); - Adjacent_Cell(FACING_N).Wall_Update(); - Adjacent_Cell(FACING_W).Wall_Update(); - Adjacent_Cell(FACING_S).Wall_Update(); - Adjacent_Cell(FACING_E).Wall_Update(); + CellClass * ncell = Adjacent_Cell(FACING_N); + if (ncell) ncell->Wall_Update(); + CellClass * wcell = Adjacent_Cell(FACING_W); + if (wcell) wcell->Wall_Update(); + CellClass * scell = Adjacent_Cell(FACING_S); + if (scell) scell->Wall_Update(); + CellClass * ecell = Adjacent_Cell(FACING_E); + if (ecell) ecell->Wall_Update(); Detach_This_From_All(As_Target()); /* @@ -2022,27 +2018,24 @@ void CellClass::Incoming(COORDINATE threat, bool forced, bool nokidding) * HISTORY: * * 03/19/1995 JLB : Created. * *=============================================================================================*/ -CellClass const & CellClass::Adjacent_Cell(FacingType face) const +CellClass const * CellClass::Adjacent_Cell(FacingType face) const { assert((unsigned)Cell_Number() <= MAP_CELL_TOTAL); + if (face == FACING_NONE) { + return(this); + } + if ((unsigned)face >= FACING_COUNT) { - return(*this); + return(NULL); } - //The top row doesn't have any adjacent cells to the north. - LLL - if (ID < MAP_CELL_W && (face == FACING_N || face == FACING_NE || face == FACING_NW)) { - return (*this); + CELL newcell = ::Adjacent_Cell(Cell_Number(), face); + if ((unsigned)newcell >= MAP_CELL_TOTAL) { + return(NULL); } - //The bottom row doesn't have any adjacent cells to the south. - LLL - if ((ID > MAP_CELL_TOTAL - MAP_CELL_W) && (face == FACING_S || face == FACING_SE || face == FACING_SW)) { - return (*this); - } - - CellClass const * ptr = this + AdjacentCell[face]; - if ((unsigned)ptr->Cell_Number() > MAP_CELL_TOTAL) return(*this); - return(*ptr); + return &Map[newcell]; } @@ -2144,10 +2137,10 @@ long CellClass::Tiberium_Adjust(bool pregame) */ for (FacingType face = FACING_FIRST; face < FACING_COUNT; face++) { if ((unsigned)::Adjacent_Cell(Cell_Number(), face) >= MAP_CELL_TOTAL) continue; - CellClass & adj = Adjacent_Cell(face); + CellClass * adj = Adjacent_Cell(face); - if (adj.Overlay != OVERLAY_NONE && - OverlayTypeClass::As_Reference(adj.Overlay).Land == LAND_TIBERIUM) { + if (adj && adj->Overlay != OVERLAY_NONE && + OverlayTypeClass::As_Reference(adj->Overlay).Land == LAND_TIBERIUM) { count++; } } @@ -2763,6 +2756,7 @@ bool CellClass::Flag_Place(HousesType house) if (!IsFlagged && Is_Clear_To_Move(SPEED_TRACK, false, false)) { IsFlagged = true; Owner = house; + Flag_Update(); Redraw_Objects(); return(true); } @@ -2791,6 +2785,7 @@ bool CellClass::Flag_Remove(void) if (IsFlagged) { IsFlagged = false; Owner = HOUSE_NONE; + Flag_Update(); Redraw_Objects(); return(true); } @@ -2798,6 +2793,34 @@ bool CellClass::Flag_Remove(void) } +void CellClass::Flag_Update(void) +{ + if (IsFlagged && !CTFFlag) { + Flag_Create(); + } else if (!IsFlagged && CTFFlag) { + Flag_Destroy(); + } +} + + +void CellClass::Flag_Create(void) +{ + if (!CTFFlag) { + CTFFlag = new AnimClass(ANIM_FLAG, Cell_Coord()); + if (CTFFlag) { + CTFFlag->OwnerHouse = Owner; + } + } +} + + +void CellClass::Flag_Destroy(void) +{ + delete CTFFlag; + CTFFlag = NULL; +} + + /*********************************************************************************************** * CellClass::Shimmer -- Causes all objects in the cell to shimmer. * * * @@ -3088,7 +3111,7 @@ bool CellClass::Spread_Tiberium(bool forced) } FacingType offset = Random_Pick(FACING_N, FACING_NW); for (FacingType index = FACING_N; index < FACING_COUNT; index++) { - CellClass * newcell = &Adjacent_Cell(index+offset); + CellClass * newcell = Adjacent_Cell(index+offset); if (newcell != NULL && newcell->Can_Tiberium_Germinate()) { new OverlayClass(Random_Pick(OVERLAY_GOLD1, OVERLAY_GOLD4), newcell->Cell_Number()); diff --git a/REDALERT/CELL.H b/REDALERT/CELL.H index 94ac7eb..7754c04 100644 --- a/REDALERT/CELL.H +++ b/REDALERT/CELL.H @@ -231,8 +231,8 @@ class CellClass COORDINATE Cell_Coord(void) const; COORDINATE Closest_Free_Spot(COORDINATE coord, bool any=false) const; COORDINATE Free_Spot(void) const {return Closest_Free_Spot(Cell_Coord());} - CellClass & Adjacent_Cell(FacingType face) {return (CellClass &)((*((CellClass const *)this)).Adjacent_Cell(face));} - CellClass const & Adjacent_Cell(FacingType face) const; + CellClass * Adjacent_Cell(FacingType face) {return (CellClass *)((*((CellClass const *)this)).Adjacent_Cell(face));} + CellClass const * Adjacent_Cell(FacingType face) const; InfantryClass * Cell_Infantry(void) const; LandType Land_Type(void) const {return((OverrideLand != LAND_NONE) ? OverrideLand : Land);} ObjectClass * Cell_Find_Object(RTTIType rtti) const; @@ -263,6 +263,9 @@ class CellClass void Overlap_Up(ObjectClass * object); bool Flag_Place(HousesType house); bool Flag_Remove(void); + void Flag_Update(void); + void Flag_Create(void); + void Flag_Destroy(void); /* ** File I/O. @@ -323,10 +326,15 @@ class CellClass LandType OverrideLand; // The overriden land type of this cell. + /* + ** Points to the flag animation on this cell in CTF games. + */ + AnimClass* CTFFlag; + /* ** Some additional padding in case we need to add data to the class and maintain backwards compatibility for save/load */ - unsigned char SaveLoadPadding[32]; + unsigned char SaveLoadPadding[28]; }; #endif \ No newline at end of file diff --git a/REDALERT/COMBAT.CPP b/REDALERT/COMBAT.CPP index 66f6da9..4e6a839 100644 --- a/REDALERT/COMBAT.CPP +++ b/REDALERT/COMBAT.CPP @@ -190,7 +190,8 @@ void Explosion_Damage(COORDINATE coord, int strength, TechnoClass * source, Warh ** further than one cell away. */ if (i != FACING_NONE) { - cellptr = &Map[cell].Adjacent_Cell(i); + cellptr = Map[cell].Adjacent_Cell(i); + if (!cellptr) continue; } /* diff --git a/REDALERT/CONQUER.CPP b/REDALERT/CONQUER.CPP index 1262c2f..4ba6cd2 100644 --- a/REDALERT/CONQUER.CPP +++ b/REDALERT/CONQUER.CPP @@ -3921,6 +3921,8 @@ void Handle_Team(int team, int action) TeamEvent = (char)action + 1; } + TeamFormDataStruct& team_form_data = TeamFormData[PlayerPtr->Class->House]; + AllowVoice = true; switch (action) { @@ -3935,9 +3937,11 @@ void Handle_Team(int team, int action) ** If a non team member is currently selected, then deselect all objects ** before selecting this team. */ - if (CurrentObject.Count()) { - if (CurrentObject[0]->Is_Foot() && ((FootClass *)CurrentObject[0])->Group != team) { + for (index = 0; index < CurrentObject.Count(); index++) { + ObjectClass * obj = CurrentObject[index]; + if (obj->Is_Foot() && ((FootClass *)obj)->Group != team) { Unselect_All(); + break; } } for (index = 0; index < Vessels.Count(); index++) { @@ -4036,8 +4040,8 @@ void Handle_Team(int team, int action) case 2: { long minx = 0x7FFFFFFFL, miny = 0x7FFFFFFFL; long maxx = 0, maxy = 0; - TeamSpeed[team] = SPEED_WHEEL; - TeamMaxSpeed[team] = MPH_LIGHT_SPEED; + team_form_data.TeamSpeed[team] = SPEED_WHEEL; + team_form_data.TeamMaxSpeed[team] = MPH_LIGHT_SPEED; for (index = 0; index < Units.Count(); index++) { UnitClass * obj = Units.Ptr(index); if (obj && !obj->IsInLimbo && obj->House->IsPlayerControl) { @@ -4051,9 +4055,9 @@ void Handle_Team(int team, int action) if (xc > maxx) maxx = xc; if (yc < miny) miny = yc; if (yc > maxy) maxy = yc; - if (obj->Class->MaxSpeed < TeamMaxSpeed[team]) { - TeamMaxSpeed[team] = obj->Class->MaxSpeed; - TeamSpeed[team] = obj->Class->Speed; + if (obj->Class->MaxSpeed < team_form_data.TeamMaxSpeed[team]) { + team_form_data.TeamMaxSpeed[team] = obj->Class->MaxSpeed; + team_form_data.TeamSpeed[team] = obj->Class->Speed; } } } @@ -4072,9 +4076,9 @@ void Handle_Team(int team, int action) if (xc > maxx) maxx = xc; if (yc < miny) miny = yc; if (yc > maxy) maxy = yc; - if (obj->Class->MaxSpeed < TeamMaxSpeed[team]) { - TeamMaxSpeed[team] = obj->Class->MaxSpeed; - TeamSpeed[team] = obj->Class->Speed; + if (obj->Class->MaxSpeed < team_form_data.TeamMaxSpeed[team]) { + team_form_data.TeamMaxSpeed[team] = obj->Class->MaxSpeed; + team_form_data.TeamSpeed[team] = obj->Class->Speed; } } } @@ -4093,8 +4097,8 @@ void Handle_Team(int team, int action) if (xc > maxx) maxx = xc; if (yc < miny) miny = yc; if (yc > maxy) maxy = yc; - if (obj->Class->MaxSpeed < TeamMaxSpeed[team]) { - TeamMaxSpeed[team] = obj->Class->MaxSpeed; + if (obj->Class->MaxSpeed < team_form_data.TeamMaxSpeed[team]) { + team_form_data.TeamMaxSpeed[team] = obj->Class->MaxSpeed; } } } @@ -5300,8 +5304,7 @@ static void Do_Record_Playback(void) Session.RecordFile.Write (&TeamEvent, sizeof(TeamEvent)); Session.RecordFile.Write (&TeamNumber, sizeof(TeamNumber)); Session.RecordFile.Write (&FormationEvent, sizeof(FormationEvent)); - Session.RecordFile.Write (TeamMaxSpeed, sizeof(TeamMaxSpeed)); - Session.RecordFile.Write (TeamSpeed, sizeof(TeamSpeed)); + Session.RecordFile.Write (TeamFormData, sizeof(TeamFormData)); Session.RecordFile.Write (&FormMove, sizeof(FormMove)); Session.RecordFile.Write (&FormSpeed, sizeof(FormSpeed)); Session.RecordFile.Write (&FormMaxSpeed, sizeof(FormMaxSpeed)); @@ -5372,11 +5375,11 @@ static void Do_Record_Playback(void) Toggle_Formation(); } -Session.RecordFile.Read (TeamMaxSpeed, sizeof(TeamMaxSpeed)); -Session.RecordFile.Read (TeamSpeed, sizeof(TeamSpeed)); -Session.RecordFile.Read (&FormMove, sizeof(FormMove)); -Session.RecordFile.Read (&FormSpeed, sizeof(FormSpeed)); -Session.RecordFile.Read (&FormMaxSpeed, sizeof(FormMaxSpeed)); + Session.RecordFile.Read (TeamFormData, sizeof(TeamFormData)); + Session.RecordFile.Read (&FormMove, sizeof(FormMove)); + Session.RecordFile.Read (&FormSpeed, sizeof(FormSpeed)); + Session.RecordFile.Read (&FormMaxSpeed, sizeof(FormMaxSpeed)); + /* ** The map isn't drawn in playback mode, so draw it here. */ diff --git a/REDALERT/DEFINES.H b/REDALERT/DEFINES.H index c541e07..6dcc9ba 100644 --- a/REDALERT/DEFINES.H +++ b/REDALERT/DEFINES.H @@ -2323,6 +2323,7 @@ typedef enum AnimType : char { ANIM_CRATE_TQUAKE, ANIM_PARA_BOMB, ANIM_MINE_EXP1, + ANIM_FLAG, #ifdef FIXIT_ANTS ANIM_ANT1_DEATH, diff --git a/REDALERT/DISPLAY.CPP b/REDALERT/DISPLAY.CPP index c865e5a..a42e8d1 100644 --- a/REDALERT/DISPLAY.CPP +++ b/REDALERT/DISPLAY.CPP @@ -4024,7 +4024,7 @@ void DisplayClass::Mouse_Left_Release(CELL cell, int x, int y, ObjectClass * obj /* ** Only consider objects that are owned by the player. */ - if (!foot->IsOwnedByPlayer) continue; + if (!foot->Is_Owned_By_Player()) continue; /* ** If another member of this team has been discovered and diff --git a/REDALERT/DLLInterface.cpp b/REDALERT/DLLInterface.cpp index 0842ddb..4cfa6b6 100644 --- a/REDALERT/DLLInterface.cpp +++ b/REDALERT/DLLInterface.cpp @@ -107,6 +107,7 @@ typedef enum { #define RANDOM_START_POSITION 0x7f +#define KILL_PLAYER_ON_DISCONNECT 1 @@ -1999,18 +2000,34 @@ extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Player_Switch_To_AI(uin return; } - HousesType house; - HouseClass *ptr; - GlyphX_Debug_Print("CNC_Handle_Player_Switch_To_AI"); if (GAME_TO_PLAY == GAME_NORMAL) { return; } +#ifdef KILL_PLAYER_ON_DISCONNECT + + /* + ** Kill player's units on disconnect. + */ if (player_id != 0) { DLLExportClass::Set_Player_Context(player_id); + if (PlayerPtr) { + PlayerPtr->Flag_To_Die(); + } + } + +#else //KILL_PLAYER_ON_DISCONNECT + + if (player_id != 0) { + + HousesType house; + HouseClass *ptr; + + DLLExportClass::Set_Player_Context(player_id); + if (PlayerPtr) { PlayerPtr->WasHuman = true; PlayerPtr->IsHuman = false; @@ -2055,6 +2072,9 @@ extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Player_Switch_To_AI(uin } } } + +#endif //KILL_PLAYER_ON_DISCONNECT + } @@ -2898,12 +2918,24 @@ void DLLExportClass::On_Multiplayer_Game_Over(void) event.GameOver.SabotagedStructureType = 0; event.GameOver.TimerRemaining = -1; - // Trigger an event for each human player - for (int i=0 ; iPlayer.ID); - if (player_ptr->IsHuman) { + if (!player_ptr->IsDefeated) { event.GlyphXPlayerID = Get_GlyphX_Player_ID(player_ptr); - event.GameOver.PlayerWins = !player_ptr->IsDefeated; + event.GameOver.IsHuman = player_ptr->IsHuman; + event.GameOver.PlayerWins = true; + event.GameOver.RemainingCredits = player_ptr->Available_Money(); + EventCallback(event); + } + } + + for (int i = 0; i < player_count; i++) { + HouseClass *player_ptr = HouseClass::As_Pointer(Session.Players[i]->Player.ID); + if (player_ptr->IsHuman && player_ptr->IsDefeated) { + event.GlyphXPlayerID = Get_GlyphX_Player_ID(player_ptr); + event.GameOver.IsHuman = true; + event.GameOver.PlayerWins = false; event.GameOver.RemainingCredits = player_ptr->Available_Money(); EventCallback(event); } @@ -4135,6 +4167,10 @@ extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Sidebar_Request(Sidebar switch (request_type) { + // MBL 06.02.2020 - Changing right-click support for first put building on hold, and then subsequenct right-clicks to decrement that queue count for 1x or 5x; Then, 1x or 5x Left click will resume from hold + // Handle and fall through to start construction (from hold state) below + case SIDEBAR_REQUEST_START_CONSTRUCTION_MULTI: + case SIDEBAR_REQUEST_START_CONSTRUCTION: DLLExportClass::Start_Construction(player_id, buildable_type, buildable_id); break; @@ -4372,7 +4408,7 @@ bool DLLExportClass::Get_Sidebar_State(uint64 player_id, unsigned char *buffer_i if (tech) { sidebar_entry.Cost = tech->Cost * PlayerPtr->CostBias; sidebar_entry.PowerProvided = 0; - sidebar_entry.BuildTime = tech->Time_To_Build(); // sidebar_entry.BuildTime = tech->Time_To_Build() / 60; + sidebar_entry.BuildTime = tech->Time_To_Build(PlayerPtr->Class->House); // sidebar_entry.BuildTime = tech->Time_To_Build() / 60; strncpy(sidebar_entry.AssetName, tech->IniName, CNC_OBJECT_ASSET_NAME_LENGTH); } else { sidebar_entry.Cost = 0; @@ -4527,7 +4563,7 @@ bool DLLExportClass::Get_Sidebar_State(uint64 player_id, unsigned char *buffer_i if (tech) { sidebar_entry.Cost = tech->Cost; sidebar_entry.PowerProvided = 0; - sidebar_entry.BuildTime = tech->Time_To_Build(); // sidebar_entry.BuildTime = tech->Time_To_Build() / 60; + sidebar_entry.BuildTime = tech->Time_To_Build(PlayerPtr->Class->House); // sidebar_entry.BuildTime = tech->Time_To_Build() / 60; strncpy(sidebar_entry.AssetName, tech->IniName, CNC_OBJECT_ASSET_NAME_LENGTH); } else { sidebar_entry.Cost = 0; @@ -7376,8 +7412,12 @@ void DLLExportClass::Selected_Guard_Mode(uint64 player_id) for (int index = 0; index < CurrentObject.Count(); index++) { ObjectClass const * tech = CurrentObject[index]; - if (tech != NULL && tech->Can_Player_Move() && tech->Can_Player_Fire()) { - OutList.Add(EventClass(TargetClass(tech), MISSION_GUARD_AREA)); + if (tech != NULL && tech->Can_Player_Fire()) { + if (tech->Can_Player_Move()) { + OutList.Add(EventClass(TargetClass(tech), MISSION_GUARD_AREA)); + } else { + OutList.Add(EventClass(TargetClass(tech), MISSION_GUARD)); + } } } } @@ -7458,6 +7498,8 @@ void DLLExportClass::Team_Units_Formation_Toggle_On(uint64 player_id) int index; bool setform = 0; + TeamFormDataStruct& team_form_data = TeamFormData[PlayerPtr->Class->House]; + // // Recording support // @@ -7481,8 +7523,8 @@ void DLLExportClass::Team_Units_Formation_Toggle_On(uint64 player_id) team = obj->Group; if (team < MAX_TEAMS) { setform = obj->XFormOffset == (int)0x80000000; - TeamSpeed[team] = SPEED_WHEEL; - TeamMaxSpeed[team] = MPH_LIGHT_SPEED; + team_form_data.TeamSpeed[team] = SPEED_WHEEL; + team_form_data.TeamMaxSpeed[team] = MPH_LIGHT_SPEED; break; } } @@ -7500,8 +7542,8 @@ void DLLExportClass::Team_Units_Formation_Toggle_On(uint64 player_id) team = obj->Group; if (team < MAX_TEAMS) { setform = obj->XFormOffset == (int)0x80000000; - TeamSpeed[team] = SPEED_WHEEL; - TeamMaxSpeed[team] = MPH_LIGHT_SPEED; + team_form_data.TeamSpeed[team] = SPEED_WHEEL; + team_form_data.TeamMaxSpeed[team] = MPH_LIGHT_SPEED; break; } } @@ -7521,8 +7563,8 @@ void DLLExportClass::Team_Units_Formation_Toggle_On(uint64 player_id) team = obj->Group; if (team < MAX_TEAMS) { setform = obj->XFormOffset == 0x80000000UL; - TeamSpeed[team] = SPEED_WHEEL; - TeamMaxSpeed[team] = MPH_LIGHT_SPEED; + team_form_data.TeamSpeed[team] = SPEED_WHEEL; + team_form_data.TeamMaxSpeed[team] = MPH_LIGHT_SPEED; break; } } @@ -7547,9 +7589,9 @@ void DLLExportClass::Team_Units_Formation_Toggle_On(uint64 player_id) if (xc > maxx) maxx = xc; if (yc < miny) miny = yc; if (yc > maxy) maxy = yc; - if (obj->Class->MaxSpeed < TeamMaxSpeed[team]) { - TeamMaxSpeed[team] = obj->Class->MaxSpeed; - TeamSpeed[team] = obj->Class->Speed; + if (obj->Class->MaxSpeed < team_form_data.TeamMaxSpeed[team]) { + team_form_data.TeamMaxSpeed[team] = obj->Class->MaxSpeed; + team_form_data.TeamSpeed[team] = obj->Class->Speed; } } else { obj->XFormOffset = obj->YFormOffset = (int)0x80000000; @@ -7568,8 +7610,8 @@ void DLLExportClass::Team_Units_Formation_Toggle_On(uint64 player_id) if (xc > maxx) maxx = xc; if (yc < miny) miny = yc; if (yc > maxy) maxy = yc; - if (obj->Class->MaxSpeed < TeamMaxSpeed[team]) { - TeamMaxSpeed[team] = obj->Class->MaxSpeed; + if (obj->Class->MaxSpeed < team_form_data.TeamMaxSpeed[team]) { + team_form_data.TeamMaxSpeed[team] = obj->Class->MaxSpeed; } } else { obj->XFormOffset = obj->YFormOffset = (int)0x80000000; @@ -7588,8 +7630,8 @@ void DLLExportClass::Team_Units_Formation_Toggle_On(uint64 player_id) if (xc > maxx) maxx = xc; if (yc < miny) miny = yc; if (yc > maxy) maxy = yc; - if (obj->Class->MaxSpeed < TeamMaxSpeed[team]) { - TeamMaxSpeed[team] = obj->Class->MaxSpeed; + if (obj->Class->MaxSpeed < team_form_data.TeamMaxSpeed[team]) { + team_form_data.TeamMaxSpeed[team] = obj->Class->MaxSpeed; } } else { obj->XFormOffset = obj->YFormOffset = 0x80000000UL; @@ -8158,7 +8200,7 @@ void DLLExportClass::Debug_Heal_Unit(int x, int y) CellClass* cells[cellcount]; cells[0] = cellptr; for (FacingType index = FACING_N; index < FACING_COUNT; index++) { - cells[(int)index + 1] = &cellptr->Adjacent_Cell(index); + cells[(int)index + 1] = cellptr->Adjacent_Cell(index); } for (int index = 0; index < cellcount; index++) { diff --git a/REDALERT/DLLInterface.h b/REDALERT/DLLInterface.h index 04cbd9f..7e4525f 100644 --- a/REDALERT/DLLInterface.h +++ b/REDALERT/DLLInterface.h @@ -29,7 +29,7 @@ struct CarryoverObjectStruct; ** ** */ -#define CNC_DLL_API_VERSION 0x100 +#define CNC_DLL_API_VERSION 0x101 @@ -627,6 +627,7 @@ struct EventCallbackStruct { // // Single-player data // + bool IsHuman; bool PlayerWins; //This should specify player id const char* MovieName; const char* MovieName2; diff --git a/REDALERT/DRIVE.CPP b/REDALERT/DRIVE.CPP index 47698c1..5264337 100644 --- a/REDALERT/DRIVE.CPP +++ b/REDALERT/DRIVE.CPP @@ -1372,7 +1372,7 @@ void DriveClass::AI(void) land = Map[Center_Coord()].Land_Type(); } if (IsLocked && Mission != MISSION_ENTER && Target_Legal(NavCom) && !Is_In_Same_Zone(As_Cell(NavCom)) && - land != LAND_ROCK && land != LAND_WATER && land != LAND_RIVER) { + land != LAND_ROCK && land != LAND_WATER && land != LAND_RIVER && !Team) { Stop_Driver(); Assign_Destination(TARGET_NONE); } else { diff --git a/REDALERT/EXTERNS.H b/REDALERT/EXTERNS.H index 4526440..72bd676 100644 --- a/REDALERT/EXTERNS.H +++ b/REDALERT/EXTERNS.H @@ -255,8 +255,7 @@ extern DynamicVectorClass HouseTriggers[HOUSE_COUNT]; extern BaseClass Base; /* These variables are used to keep track of the slowest speed of a team */ -extern MPHType TeamMaxSpeed[MAX_TEAMS]; -extern SpeedType TeamSpeed[MAX_TEAMS]; +extern TeamFormDataStruct TeamFormData[HOUSE_COUNT]; extern bool FormMove; extern SpeedType FormSpeed; extern MPHType FormMaxSpeed; diff --git a/REDALERT/FACTORY.CPP b/REDALERT/FACTORY.CPP index 8ba74e1..9f6456d 100644 --- a/REDALERT/FACTORY.CPP +++ b/REDALERT/FACTORY.CPP @@ -445,29 +445,13 @@ bool FactoryClass::Start(void) int time; if (Object) { - time = Object->Time_To_Build(); -// } else { -// time = TICKS_PER_MINUTE * 5; + time = Object->Class_Of().Time_To_Build(House->Class->House); } - /* - ** Adjust time according to IQ setting of computer controlled house. The - ** build time will range from double normal time at the slowest to - ** just normal time at the fastest. - */ - if (!House->IsHuman && Rule.Diff[House->Difficulty].IsBuildSlowdown) { - time = time * Inverse(fixed(House->IQ+Rule.MaxIQ, Rule.MaxIQ*2)); - } + time /= STEP_COUNT; + time = Bound(time, 1, 255); - int rate = time / Bound(House->Power_Fraction(), fixed(1, 16), fixed(1)); -// int frac = House->Power_Fraction(); -// frac = Bound(frac, 0x0010, 0x0100); -// int rate = (time*256) / frac; - - rate /= STEP_COUNT; - rate = Bound(rate, 1, 255); - - Set_Rate(rate); + Set_Rate(time); IsSuspended = false; return(true); } diff --git a/REDALERT/FINDPATH.CPP b/REDALERT/FINDPATH.CPP index 2a3107d..edcfeb1 100644 --- a/REDALERT/FINDPATH.CPP +++ b/REDALERT/FINDPATH.CPP @@ -629,7 +629,7 @@ top_of_list: // MBL 09.30.2019: We hit a runtime bounds crash where END (-1 / 0xFF) was being poked into +1 just past the end of the moves_right[] array; // The FacingType moves_left[] and moves_right[] arrays already have MAX_MLIST_SIZE+2 as their size, which may have been a previous attempted fix; - // We are now passing MAX_MLIST_SIZE, since the sizeof calculations included the +2 buffering; This code does not exist in Tiberian Dawn. + // We are now passing MAX_MLIST_SIZE, since the sizeof calculations included the +2 buffering; #if 0 left = Follow_Edge(startcell, next, &pleft, COUNTERCLOCK, direction, threat, threat_stage, sizeof(moves_left)/sizeof(moves_left[0]), threshhold); // left = Follow_Edge(startcell, next, &pleft, COUNTERCLOCK, direction, threat, threat_stage, follow_len, threshhold); @@ -649,7 +649,7 @@ top_of_list: // MBL 09.30.2019: We hit a runtime bounds crash where END (-1 / 0xFF) was being poked into +1 just past the end of the moves_right[] array; // The FacingType moves_left[] and moves_right[] arrays already have MAX_MLIST_SIZE+2 as their size, which may have been a previous attempted fix; - // We are now passing MAX_MLIST_SIZE, since the sizeof calculations included the +2 buffering; This code does not exist in Tiberian Dawn. + // We are now passing MAX_MLIST_SIZE, since the sizeof calculations included the +2 buffering; #if 0 right = Follow_Edge(startcell, next, &pright, CLOCK, direction, threat, threat_stage, sizeof(moves_right)/sizeof(moves_right[0]), threshhold); // right = Follow_Edge(startcell, next, &pright, CLOCK, direction, threat, threat_stage, follow_len, threshhold); diff --git a/REDALERT/GLOBALS.CPP b/REDALERT/GLOBALS.CPP index 9e8bb14..8472407 100644 --- a/REDALERT/GLOBALS.CPP +++ b/REDALERT/GLOBALS.CPP @@ -130,8 +130,7 @@ template<> FixedIHeapClass * CCPtr::Heap = &SmudgeTypes; #endif /* These variables are used to keep track of the slowest speed of a team */ -MPHType TeamMaxSpeed[MAX_TEAMS]; -SpeedType TeamSpeed[MAX_TEAMS]; +TeamFormDataStruct TeamFormData[HOUSE_COUNT]; bool FormMove; SpeedType FormSpeed; MPHType FormMaxSpeed; diff --git a/REDALERT/HOUSE.CPP b/REDALERT/HOUSE.CPP index a228ab6..63d6230 100644 --- a/REDALERT/HOUSE.CPP +++ b/REDALERT/HOUSE.CPP @@ -1125,6 +1125,7 @@ void HouseClass::AI(void) unit->Mark(MARK_CHANGE); } else { CELL cell = As_Cell(FlagLocation); + Map[cell].Flag_Update(); Map[cell].Redraw_Objects(); } } @@ -1823,7 +1824,11 @@ void HouseClass::Attacked(BuildingClass* source) if (SpeakAttackDelay == 0 && PlayerPtr->Class->House == Class->House) { #endif Speak(VOX_BASE_UNDER_ATTACK, NULL, source ? source->Center_Coord() : 0); - SpeakAttackDelay = Options.Normalize_Delay(TICKS_PER_MINUTE * Rule.SpeakDelay); + + // MBL 06.13.2020 - Timing change from 2 minute cooldown, per https://jaas.ea.com/browse/TDRA-6784 + // SpeakAttackDelay = Options.Normalize_Delay(TICKS_PER_MINUTE * Rule.SpeakDelay); // 2 minutes + // SpeakAttackDelay = Options.Normalize_Delay(TICKS_PER_MINUTE/2); // 30 seconds as requested + SpeakAttackDelay = Options.Normalize_Delay( (TICKS_PER_MINUTE/2)+(TICKS_PER_SECOND*5) ); // Tweaked for accuracy /* ** If there is a trigger event associated with being attacked, process it @@ -3762,7 +3767,6 @@ void HouseClass::MPlayer_Defeated(void) */ if (PlayerPtr == this) { Session.ObiWan = 1; - Debug_Unshroud = true; HidPage.Clear(); Map.Flag_To_Redraw(true); @@ -3919,11 +3923,6 @@ void HouseClass::MPlayer_Defeated(void) #endif // MPATH } - - /* - ** Be sure our messages get displayed, even if we're about to exit. - */ - Map.Render(); } @@ -4411,10 +4410,14 @@ void HouseClass::Sell_Wall(CELL cell) Map[cell].OverlayData = 0; Map[cell].Owner = HOUSE_NONE; Map[cell].Wall_Update(); - Map[cell].Adjacent_Cell(FACING_N).Wall_Update(); - Map[cell].Adjacent_Cell(FACING_W).Wall_Update(); - Map[cell].Adjacent_Cell(FACING_S).Wall_Update(); - Map[cell].Adjacent_Cell(FACING_E).Wall_Update(); + CellClass * ncell = Map[cell].Adjacent_Cell(FACING_N); + if (ncell) ncell->Wall_Update(); + CellClass * wcell = Map[cell].Adjacent_Cell(FACING_W); + if (wcell) wcell->Wall_Update(); + CellClass * scell = Map[cell].Adjacent_Cell(FACING_S); + if (scell) scell->Wall_Update(); + CellClass * ecell = Map[cell].Adjacent_Cell(FACING_E); + if (ecell) ecell->Wall_Update(); Map[cell].Recalc_Attributes(); Map[cell].Redraw_Objects(); Map.Radar_Pixel(cell); @@ -8016,7 +8019,7 @@ void HouseClass::Check_Pertinent_Structures(void) BuildingClass *b = Buildings.Ptr(index); if (b && b->IsActive && b->House == this) { - if (!b->Class->IsWall) { + if (!b->Class->IsWall && *b != STRUCT_APMINE && *b != STRUCT_AVMINE) { if (!b->IsInLimbo && b->Strength > 0) { any_good_buildings = true; break; diff --git a/REDALERT/IOMAP.CPP b/REDALERT/IOMAP.CPP index c46ff5d..06f0dc1 100644 --- a/REDALERT/IOMAP.CPP +++ b/REDALERT/IOMAP.CPP @@ -142,6 +142,8 @@ void CellClass::Code_Pointers(void) Overlapper[index] = NULL; } } + + assert(CTFFlag == NULL); } @@ -177,6 +179,8 @@ void CellClass::Decode_Pointers(void) assert(Overlapper[index] != NULL); } } + + CTFFlag = NULL; } @@ -480,8 +484,14 @@ void DisplayClass::Decode_Pointers(void) *=============================================================================================*/ void MapClass::Code_Pointers(void) { + CELL cell; + + for (cell = 0; cell < MAP_CELL_TOTAL; cell++) { + (*this)[cell].Flag_Destroy(); + } + CellClass * cellptr = &(*this)[(CELL)0]; - for (CELL cell = 0; cell < MAP_CELL_TOTAL; cell++) { + for (cell = 0; cell < MAP_CELL_TOTAL; cell++) { cellptr->Code_Pointers(); cellptr++; } diff --git a/REDALERT/MiscAsm.cpp b/REDALERT/MiscAsm.cpp index cd8e639..0373f1b 100644 --- a/REDALERT/MiscAsm.cpp +++ b/REDALERT/MiscAsm.cpp @@ -428,7 +428,7 @@ dxisbig: #if (0) /* - ; $Header: //depot/Projects/Mobius/QA/Project/Run/SOURCECODE/REDALERT/MiscAsm.cpp#33 $ + ; $Header: //depot/Projects/Mobius/QA/Project/Run/SOURCECODE/REDALERT/MiscAsm.cpp#57 $ ;*************************************************************************** ;** C O N F I D E N T I A L --- W E S T W O O D A S S O C I A T E S ** ;*************************************************************************** diff --git a/REDALERT/OBJECT.CPP b/REDALERT/OBJECT.CPP index 1322c4a..0ee931a 100644 --- a/REDALERT/OBJECT.CPP +++ b/REDALERT/OBJECT.CPP @@ -2270,7 +2270,7 @@ int ObjectTypeClass::Cost_Of(void) const * HISTORY: * * 07/19/1995 JLB : Created. * *=============================================================================================*/ -int ObjectTypeClass::Time_To_Build(void) const +int ObjectTypeClass::Time_To_Build(HousesType ) const { return(0); } diff --git a/REDALERT/REINF.CPP b/REDALERT/REINF.CPP index bdd2cb1..5dd9d22 100644 --- a/REDALERT/REINF.CPP +++ b/REDALERT/REINF.CPP @@ -329,7 +329,8 @@ static TechnoClass * _Who_Can_Pop_Out_Of(CELL origin) for (int f = -1; f < 8; f++) { CellClass * ptr = cellptr; if (f != -1) { - ptr = &ptr->Adjacent_Cell(FacingType(f)); + ptr = ptr->Adjacent_Cell(FacingType(f)); + if (!ptr) continue; } BuildingClass * building = ptr->Cell_Building(); diff --git a/REDALERT/SCENARIO.CPP b/REDALERT/SCENARIO.CPP index 429563a..8215a02 100644 --- a/REDALERT/SCENARIO.CPP +++ b/REDALERT/SCENARIO.CPP @@ -2470,7 +2470,10 @@ bool Read_Scenario_INI(char * fname, bool ) /* ** Special cases: ** Gold Rush multiplayer map cell 9033 - LAND_ROCK + ** The Lake District multiplayer map cell 8482 - LAND_ROCK + ** Blue Lakes multiplayer map cell 11937 - LAND_RIVER ** USSR mission 13 - fixup trigger action + ** Allied mission 5B - fail mission if spy re-boards the transport at mission start ** Allied mission 9A - fail mission if tech center is destroyed before being spied ** Aftermath: Brother in Arms - have transports move to separate waypoints ** Aftermath: Let's Make a Steal - Make the pillboxes un-capturable @@ -2480,11 +2483,45 @@ bool Read_Scenario_INI(char * fname, bool ) Map[(CELL)9033].Override_Land_Type(LAND_ROCK); } + if (_stricmp(Scen.ScenarioName, "scm93ea.ini") == 0) { + Map[(CELL)8482].Override_Land_Type(LAND_ROCK); + } + + if (_stricmp(Scen.ScenarioName, "scmh4ea.ini") == 0) { + Map[(CELL)11937].Override_Land_Type(LAND_RIVER); + } + if (_stricmp(Scen.ScenarioName, "scu13ea.ini") == 0) { TriggerTypeClass* trigger = TriggerTypes.Ptr(11); trigger->Action1.Trigger.Set_Raw(39); } + if (_stricmp(Scen.ScenarioName, "scg05eb.ini") == 0) { + TeamTypeClass* spy1_team = TeamTypeClass::From_Name("spy1"); + assert(spy1_team != NULL); + spy1_team->MissionList[spy1_team->MissionCount].Mission = TMISSION_SET_GLOBAL; + spy1_team->MissionList[spy1_team->MissionCount].Data.Value = 16; + spy1_team->MissionCount++; + + TriggerTypeClass* los3_trigger = new TriggerTypeClass(); + los3_trigger->IsPersistant = TriggerTypeClass::VOLATILE; + los3_trigger->House = HOUSE_GREECE; + los3_trigger->EventControl = MULTI_AND; + los3_trigger->ActionControl = MULTI_AND; + los3_trigger->Event1.Event = TEVENT_GLOBAL_SET; + los3_trigger->Event1.Data.Value = 16; + los3_trigger->Event2.Event = TEVENT_ALL_DESTROYED; + los3_trigger->Event2.Data.House = HOUSE_GREECE; + los3_trigger->Action1.Action = TACTION_LOSE; + los3_trigger->Action1.Data.Value = -255; + los3_trigger->Action2.Action = TACTION_NONE; + los3_trigger->Action2.Data.Value = -1; + + TriggerTypeClass* frc1_trigger = TriggerTypeClass::From_Name("frc1"); + assert(frc1_trigger != NULL); + frc1_trigger->Action1.Trigger = los3_trigger; + } + if (_stricmp(Scen.ScenarioName, "scg09ea.ini") == 0) { TriggerTypeClass* spyd_trigger = TriggerTypeClass::From_Name("Spyd"); assert(spyd_trigger != NULL); diff --git a/REDALERT/TEAM.CPP b/REDALERT/TEAM.CPP index f9b26a3..29657a9 100644 --- a/REDALERT/TEAM.CPP +++ b/REDALERT/TEAM.CPP @@ -2485,6 +2485,7 @@ int TeamClass::TMission_Formation(void) int xdir = 0; int ydir = 0; bool evenodd = 1; + HousesType house = (member != NULL) ? member->Owner() : HOUSE_NONE; /* ** Assign appropriate formation offsets for each of the members @@ -2606,9 +2607,10 @@ int TeamClass::TMission_Formation(void) /* ** Now calculate the group's movement type and speed */ - if (Formation != FORMATION_NONE) { - TeamSpeed[group] = SPEED_WHEEL; - TeamMaxSpeed[group] = MPH_LIGHT_SPEED; + if (Formation != FORMATION_NONE && house != HOUSE_NONE) { + TeamFormDataStruct& team_form_data = TeamFormData[house]; + team_form_data.TeamSpeed[group] = SPEED_WHEEL; + team_form_data.TeamMaxSpeed[group] = MPH_LIGHT_SPEED; member = Member; while (member != NULL) { RTTIType mytype = member->What_Am_I(); @@ -2634,9 +2636,9 @@ int TeamClass::TMission_Formation(void) } if (speedcheck) { - if (memmax < TeamMaxSpeed[group]) { - TeamMaxSpeed[group] = memmax; - TeamSpeed[group] = memspeed; + if (memmax < team_form_data.TeamMaxSpeed[group]) { + team_form_data.TeamMaxSpeed[group] = memmax; + team_form_data.TeamSpeed[group] = memspeed; } } member = member->Member; @@ -2648,8 +2650,8 @@ int TeamClass::TMission_Formation(void) */ member = Member; while (member != NULL) { - member->FormationSpeed = TeamSpeed[group]; - member->FormationMaxSpeed = TeamMaxSpeed[group]; + member->FormationSpeed = team_form_data.TeamSpeed[group]; + member->FormationMaxSpeed = team_form_data.TeamMaxSpeed[group]; if (member->What_Am_I() == RTTI_INFANTRY) { member->FormationSpeed = SPEED_FOOT; member->FormationMaxSpeed = MPH_SLOW_ISH; diff --git a/REDALERT/TEAM.H b/REDALERT/TEAM.H index fce8402..2abe430 100644 --- a/REDALERT/TEAM.H +++ b/REDALERT/TEAM.H @@ -47,6 +47,12 @@ */ #define STRAY_DISTANCE 2 +struct TeamFormDataStruct +{ + MPHType TeamMaxSpeed[MAX_TEAMS]; + SpeedType TeamSpeed[MAX_TEAMS]; +}; + class TeamClass : public AbstractClass { public: diff --git a/REDALERT/TECHNO.CPP b/REDALERT/TECHNO.CPP index 2a9e3c8..8e45c10 100644 --- a/REDALERT/TECHNO.CPP +++ b/REDALERT/TECHNO.CPP @@ -637,80 +637,6 @@ TechnoClass::TechnoClass(RTTIType rtti, int id, HousesType house) : } -/*********************************************************************************************** - * TechnoClass::Time_To_Build -- Determines the time it would take to build this. * - * * - * Use this routine to determine the amount of time it would take to build an object of * - * this type. * - * * - * INPUT: none * - * * - * OUTPUT: Returns with the time it should take (unmodified by outside factors) to build * - * this object. The time is expressed in game frames. * - * * - * WARNINGS: The time will usually be modified by power status and handicap rating for the * - * owning house. The value returned is merely the raw unmodified time to build. * - * * - * HISTORY: * - * 07/29/1996 JLB : Created. * - * 09/27/1996 JLB : Takes into account the power availability. * - *=============================================================================================*/ -//#define UNIT_BUILD_BIAS fixed(5,4) -//#define UNIT_BUILD_BIAS fixed(6,4) -#define UNIT_BUILD_BIAS fixed(1,1) -//#define UNIT_BUILD_BIAS fixed(5,1) - -extern int UnitBuildPenalty; - -int TechnoClass::Time_To_Build(void) const -{ - int val = Class_Of().Time_To_Build(); - -#ifdef FIXIT_VERSION_3 - if (Session.Type == GAME_NORMAL ) { -#else - if (Session.Type == GAME_NORMAL || - PlayingAgainstVersion == VERSION_RED_ALERT_104 || - PlayingAgainstVersion == VERSION_RED_ALERT_107){ -#endif - val *= House->BuildSpeedBias; - }else{ - if (What_Am_I() == RTTI_BUILDING || What_Am_I() == RTTI_INFANTRY) { - val *= House->BuildSpeedBias; - } else { - val *= House->BuildSpeedBias * fixed (UnitBuildPenalty, 100); //UNIT_BUILD_BIAS; - } - } - - - /* - ** Adjust the time to build based on the power output of the owning house. - */ - fixed power = House->Power_Fraction(); - if (power > 1) power = 1; - if (power < 1 && power > fixed::_3_4) power = fixed::_3_4; - if (power < fixed::_1_2) power = fixed::_1_2; - power.Inverse(); - val *= power; - - int divisor = House->Factory_Count(What_Am_I()); - if (divisor != 0) { -#ifdef FIXIT_CSII // checked - ajw 9/28/98 -// Hack: allow the multiple-factory bonus, but only up to two factories if -// this is an AM<->AM game. - if(NewUnitsEnabled) { - val /= min(divisor,2); - } else { - val /= divisor; - } -#else - val /= divisor; -#endif - } - return(val); -} - - /*********************************************************************************************** * TechnoClass::Is_Visible_On_Radar -- Is this object visible on player's radar screen? * * * @@ -3458,8 +3384,9 @@ void TechnoClass::Player_Assign_Mission(MissionType mission, TARGET target, TARG if (Is_Foot()) { const FootClass* foot = (const FootClass*)this; if (foot->Group < MAX_TEAMS) { - speed = TeamSpeed[foot->Group]; - maxspeed = TeamMaxSpeed[foot->Group]; + TeamFormDataStruct& team_form_data = TeamFormData[foot->Owner()]; + speed = team_form_data.TeamSpeed[foot->Group]; + maxspeed = team_form_data.TeamMaxSpeed[foot->Group]; } } Queue_Mission(TargetClass(this), mission, target, destination, speed, maxspeed); @@ -6494,9 +6421,82 @@ int TechnoTypeClass::Get_Ownable(void) const * HISTORY: * * 07/29/1995 JLB : Created. * *=============================================================================================*/ -int TechnoTypeClass::Time_To_Build(void) const + //#define UNIT_BUILD_BIAS fixed(5,4) + //#define UNIT_BUILD_BIAS fixed(6,4) +#define UNIT_BUILD_BIAS fixed(1,1) +//#define UNIT_BUILD_BIAS fixed(5,1) + +extern int UnitBuildPenalty; + +int TechnoTypeClass::Time_To_Build(HousesType house) const { - return(Cost * Rule.BuildSpeedBias * fixed(TICKS_PER_MINUTE, 1000)); + int time = Cost * Rule.BuildSpeedBias * fixed(TICKS_PER_MINUTE, 1000); + + HouseClass* hptr = HouseClass::As_Pointer(house); + if (!hptr || !hptr->IsActive) { + return time; + } + +#ifdef FIXIT_VERSION_3 + if (Session.Type == GAME_NORMAL) { +#else + if (Session.Type == GAME_NORMAL || + PlayingAgainstVersion == VERSION_RED_ALERT_104 || + PlayingAgainstVersion == VERSION_RED_ALERT_107) { +#endif + time *= hptr->BuildSpeedBias; + } + else { + if (What_Am_I() == RTTI_BUILDINGTYPE || What_Am_I() == RTTI_INFANTRYTYPE) { + time *= hptr->BuildSpeedBias; + } + else { + time *= hptr->BuildSpeedBias * fixed(UnitBuildPenalty, 100); //UNIT_BUILD_BIAS; + } + } + + /* + ** Adjust time according to IQ setting of computer controlled house. The + ** build time will range from double normal time at the slowest to + ** just normal time at the fastest. + */ + if (!hptr->IsHuman && Rule.Diff[hptr->Difficulty].IsBuildSlowdown) { + time = time * Inverse(fixed(hptr->IQ + Rule.MaxIQ, Rule.MaxIQ * 2)); + } + + /* + ** Adjust the time to build based on the power output of the owning house. + */ + fixed power = hptr->Power_Fraction(); + fixed scale(1); + if (power == 0) { + scale = fixed(4, 1); + } + else if (power < fixed::_1_2) { + scale = fixed(5, 2); + } + else if (power < 1) { + scale = fixed(3, 2); + } + time *= scale; + + int divisor = hptr->Factory_Count(What_Am_I()); + if (divisor != 0) { +#ifdef FIXIT_CSII // checked - ajw 9/28/98 + // Hack: allow the multiple-factory bonus, but only up to two factories if + // this is an AM<->AM game. + if (NewUnitsEnabled) { + time /= min(divisor, 2); + } + else { + time /= divisor; + } +#else + time /= divisor; +#endif + } + + return(time); } diff --git a/REDALERT/TECHNO.H b/REDALERT/TECHNO.H index aa9e84c..689fe2a 100644 --- a/REDALERT/TECHNO.H +++ b/REDALERT/TECHNO.H @@ -277,7 +277,6 @@ class TechnoClass : public RadioClass, int Anti_Air(void) const; int Anti_Armor(void) const; int Anti_Infantry(void) const; - int Time_To_Build(void) const; int What_Weapon_Should_I_Use(TARGET target) const; virtual ActionType What_Action(CELL cell) const; virtual ActionType What_Action(ObjectClass const * target) const; diff --git a/REDALERT/TYPE.H b/REDALERT/TYPE.H index fb56bb9..52eb543 100644 --- a/REDALERT/TYPE.H +++ b/REDALERT/TYPE.H @@ -292,7 +292,7 @@ class ObjectTypeClass : public AbstractTypeClass virtual void Dimensions(int &width, int &height) const; virtual bool Create_And_Place(CELL , HousesType =HOUSE_NONE) const = 0; virtual int Cost_Of(void) const; - virtual int Time_To_Build(void) const; + virtual int Time_To_Build(HousesType house) const; virtual ObjectClass * Create_One_Of(HouseClass *) const = 0; virtual short const * Occupy_List(bool placement=false) const; virtual short const * Overlap_List(void) const; @@ -572,7 +572,7 @@ class TechnoTypeClass : public ObjectTypeClass virtual int Repair_Step(void) const; virtual void const * Get_Cameo_Data(void) const; virtual int Cost_Of(void) const; - virtual int Time_To_Build(void) const; + virtual int Time_To_Build(HousesType house) const; virtual int Get_Ownable(void) const; virtual bool Read_INI(CCINIClass & ini); @@ -1789,7 +1789,7 @@ class AnimTypeClass : public ObjectTypeClass ** This is the normal loop count for this animation. Usually this is one, but ** for some animations, it may be larger. */ - unsigned Loops; + int Loops; /* ** This is the sound effect to play when this animation starts. Usually, this diff --git a/REDALERT/UNIT.CPP b/REDALERT/UNIT.CPP index bdcdb65..712f0b1 100644 --- a/REDALERT/UNIT.CPP +++ b/REDALERT/UNIT.CPP @@ -1276,6 +1276,18 @@ void UnitClass::Active_Click_With(ActionType action, CELL cell) } +void UnitClass::Player_Assign_Mission(MissionType mission, TARGET target, TARGET destination) +{ + assert(Units.ID(this) == ID); + assert(IsActive); + + if (mission == MISSION_HARVEST) { + ArchiveTarget = TARGET_NONE; + } + DriveClass::Player_Assign_Mission(mission, target, destination); +} + + /*********************************************************************************************** * UnitClass::Enter_Idle_Mode -- Unit enters idle mode state. * * * @@ -2144,7 +2156,10 @@ void UnitClass::Draw_It(int x, int y, WindowNumberType window) const ** If this unit is carrying the flag, then draw that on top of everything else. */ if (Flagged != HOUSE_NONE) { - CC_Draw_Shape(this, "FLAGFLY", MFCD::Retrieve("FLAGFLY.SHP"), Frame % 14, x, y, window, SHAPE_CENTER|SHAPE_FADING|SHAPE_GHOST, HouseClass::As_Pointer(Flagged)->Remap_Table(false, Class->Remap), Map.UnitShadow, DIR_N, 0x0100, Flagged); + shapefile = MFCD::Retrieve("FLAGFLY.SHP"); + int flag_x = x + (ICON_PIXEL_W / 2) - 2; + int flag_y = y + (3 * ICON_PIXEL_H / 4) - Get_Build_Frame_Height(shapefile); + CC_Draw_Shape(this, "FLAGFLY", shapefile, Frame % 14, flag_x, flag_y, window, SHAPE_CENTER|SHAPE_FADING|SHAPE_GHOST, HouseClass::As_Pointer(Flagged)->Remap_Table(false, Class->Remap), Map.UnitShadow, DIR_N, 0x0100, Flagged); } DriveClass::Draw_It(x, y, window); @@ -2163,14 +2178,14 @@ void UnitClass::Draw_It(int x, int y, WindowNumberType window) const * * * x,y -- Relative offset from the center cell to perform the check upon. * * * - * OUTPUT: bool; Is it located directly over a Tiberium patch? * + * OUTPUT: int; Amount of Tiberium at this location. * * * * WARNINGS: none * * * * HISTORY: * * 07/18/1994 JLB : Created. * *=============================================================================================*/ -bool UnitClass::Tiberium_Check(CELL & center, int x, int y) +int UnitClass::Tiberium_Check(CELL & center, int x, int y) { assert(Units.ID(this) == ID); assert(IsActive); @@ -2179,20 +2194,20 @@ bool UnitClass::Tiberium_Check(CELL & center, int x, int y) ** If the specified offset from the origin will cause it ** to spill past the map edge, then abort this cell check. */ - if (Cell_X(center)+x < Map.MapCellX) return(false); - if (Cell_X(center)+x >= Map.MapCellX+Map.MapCellWidth) return(false); - if (Cell_Y(center)+y < Map.MapCellY) return(false); - if (Cell_Y(center)+y >= Map.MapCellY+Map.MapCellHeight) return(false); + if (Cell_X(center)+x < Map.MapCellX) return(0); + if (Cell_X(center)+x >= Map.MapCellX+Map.MapCellWidth) return(0); + if (Cell_Y(center)+y < Map.MapCellY) return(0); + if (Cell_Y(center)+y >= Map.MapCellY+Map.MapCellHeight) return(0); center = XY_Cell(Cell_X(center)+x, Cell_Y(center)+y); if ((Session.Type != GAME_NORMAL || (!IsOwnedByPlayer || Map[center].IsMapped))) { - if (Map[Coord].Zones[Class->MZone] != Map[center].Zones[Class->MZone]) return(false); + if (Map[Coord].Zones[Class->MZone] != Map[center].Zones[Class->MZone]) return(0); if (!Map[center].Cell_Techno() && Map[center].Land_Type() == LAND_TIBERIUM) { - return(true); + return(Map[center].OverlayData); } } - return(false); + return(0); } @@ -2228,31 +2243,42 @@ bool UnitClass::Goto_Tiberium(int rad) ** Perform a ring search outward from the center. */ for (int radius = 1; radius < rad; radius++) { + CELL cell = center; + CELL bestcell = 0; + int tiberium = 0; + int besttiberium = 0; for (int x = -radius; x <= radius; x++) { - CELL cell = center; - if (Tiberium_Check(cell, x, -radius)) { - Assign_Destination(::As_Target(cell)); - return(false); + tiberium = Tiberium_Check(cell, x, -radius); + if (tiberium > besttiberium) { + bestcell = cell; + besttiberium = tiberium; } cell = center; - if (Tiberium_Check(cell, x, +radius)) { - Assign_Destination(::As_Target(cell)); - return(false); + tiberium = Tiberium_Check(cell, x, +radius); + if (tiberium > besttiberium) { + bestcell = cell; + besttiberium = tiberium; } cell = center; - if (Tiberium_Check(cell, -radius, x)) { - Assign_Destination(::As_Target(cell)); - return(false); + tiberium = Tiberium_Check(cell, -radius, x); + if (tiberium > besttiberium) { + bestcell = cell; + besttiberium = tiberium; } cell = center; - if (Tiberium_Check(cell, +radius, x)) { - Assign_Destination(::As_Target(cell)); - return(false); + tiberium = Tiberium_Check(cell, +radius, x); + if (tiberium > besttiberium) { + bestcell = cell; + besttiberium = tiberium; } } + if (bestcell) { + Assign_Destination(::As_Target(bestcell)); + return(false); + } } } } @@ -2827,6 +2853,7 @@ int UnitClass::Mission_Harvest(void) Set_Rate(2); Set_Stage(0); Status = HARVESTING; + ArchiveTarget = ::As_Target(Coord_Cell(Coord)); return(1); } else { @@ -2874,7 +2901,6 @@ int UnitClass::Mission_Harvest(void) IsHarvesting = false; if (Tiberium_Load() == 1) { Status = FINDHOME; - ArchiveTarget = ::As_Target(Coord_Cell(Coord)); } else { if (!Goto_Tiberium(Rule.TiberiumShortScan / CELL_LEPTON_W) && !Target_Legal(NavCom)) { ArchiveTarget = TARGET_NONE; @@ -2885,6 +2911,8 @@ int UnitClass::Mission_Harvest(void) } } return(1); + } else if (!Target_Legal(NavCom) && ArchiveTarget == TARGET_NONE) { + ArchiveTarget = ::As_Target(Coord_Cell(Coord)); } return(1); // return(TICKS_PER_SECOND*Rule.OreDumpRate); @@ -3502,16 +3530,14 @@ ActionType UnitClass::What_Action(ObjectClass const * object) const /* ** Special return to friendly refinery action. */ - if (House->IsPlayerControl && object->Is_Techno() && ((TechnoClass const *)object)->House->Is_Ally(this)) { - if (object->What_Am_I() == RTTI_BUILDING && ((UnitClass *)this)->Transmit_Message(RADIO_CAN_LOAD, (TechnoClass*)object) == RADIO_ROGER) { - action = ACTION_ENTER; - } + if (Is_Owned_By_Player() && House->Class->House == object->Owner() && object->What_Am_I() == RTTI_BUILDING && ((UnitClass *)this)->Transmit_Message(RADIO_CAN_LOAD, (TechnoClass*)object) == RADIO_ROGER) { + action = ACTION_ENTER; } /* ** Special return to friendly repair factory action. */ - if (House->IsPlayerControl && action == ACTION_SELECT && object->What_Am_I() == RTTI_BUILDING) { + if (Is_Owned_By_Player() && House->Class->House == object->Owner() && action == ACTION_SELECT && object->What_Am_I() == RTTI_BUILDING) { BuildingClass * building = (BuildingClass *)object; if (building->Class->Type == STRUCT_REPAIR && ((UnitClass *)this)->Transmit_Message(RADIO_CAN_LOAD, building) == RADIO_ROGER && !building->In_Radio_Contact() && !building->Is_Something_Attached()) { action = ACTION_MOVE; diff --git a/REDALERT/UNIT.H b/REDALERT/UNIT.H index 778d6a8..2660062 100644 --- a/REDALERT/UNIT.H +++ b/REDALERT/UNIT.H @@ -137,7 +137,7 @@ class UnitClass : public DriveClass bool Try_To_Deploy(void); virtual void Scatter(COORDINATE threat, bool forced=false, bool nokidding=false); - bool Tiberium_Check(CELL ¢er, int x, int y); + int Tiberium_Check(CELL ¢er, int x, int y); bool Flag_Attach(HousesType house); bool Flag_Remove(void); bool Goto_Tiberium(int radius); @@ -185,6 +185,7 @@ class UnitClass : public DriveClass virtual ActionType What_Action(ObjectClass const * object) const; virtual void Active_Click_With(ActionType action, ObjectClass * object); virtual void Active_Click_With(ActionType action, CELL cell); + virtual void Player_Assign_Mission(MissionType mission, TARGET target, TARGET destination); /* ** Combat related. diff --git a/REDALERT/VESSEL.CPP b/REDALERT/VESSEL.CPP index 08163f2..bb5a316 100644 --- a/REDALERT/VESSEL.CPP +++ b/REDALERT/VESSEL.CPP @@ -1824,8 +1824,8 @@ int VesselClass::Mission_Unload(void) ** 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->Is_Clear_To_Move(SPEED_TRACK, true, true)) { + CellClass * cellptr = Map[Coord].Adjacent_Cell(face); + if (cellptr && cellptr->Is_Clear_To_Move(SPEED_TRACK, true, true)) { cellptr->Incoming(0, true); } } diff --git a/REDALERT/WIN32LIB/DrawMisc.cpp b/REDALERT/WIN32LIB/DrawMisc.cpp index f26bad2..70a9ba1 100644 --- a/REDALERT/WIN32LIB/DrawMisc.cpp +++ b/REDALERT/WIN32LIB/DrawMisc.cpp @@ -4842,7 +4842,7 @@ extern "C" int __cdecl Confine_Rect ( int * x , int * y , int w , int h , int wi /* -; $Header: //depot/Projects/Mobius/QA/Project/Run/SOURCECODE/REDALERT/WIN32LIB/DrawMisc.cpp#33 $ +; $Header: //depot/Projects/Mobius/QA/Project/Run/SOURCECODE/REDALERT/WIN32LIB/DrawMisc.cpp#57 $ ;*************************************************************************** ;** C O N F I D E N T I A L --- W E S T W O O D A S S O C I A T E S ** ;*************************************************************************** diff --git a/TIBERIANDAWN/ADATA.CPP b/TIBERIANDAWN/ADATA.CPP index 201bdf8..3958f21 100644 --- a/TIBERIANDAWN/ADATA.CPP +++ b/TIBERIANDAWN/ADATA.CPP @@ -2360,6 +2360,30 @@ static AnimTypeClass const ChemBall( ANIM_NONE ); +static AnimTypeClass const Flag( + ANIM_FLAG, // Animation number. + "FLAGFLY", // Data name of animation. + 21, // Maximum dimension of animation. + 0, // Biggest animation stage. + false, // Normalized animation rate? + false, // Uses white translucent table? + false, // Scorches the ground? + false, // Forms a crater? + false, // Sticks to unit in square? + false, // Ground level animation? + false, // Translucent colors in this animation? + false, // Is this a flame thrower animation? + 0x0000, // Damage to apply per tick (fixed point). + 1, // Delay between frames. + 0, // Starting frame number. + 0, // Loop start frame number. + -1, // Ending frame of loop back. + -1, // Number of animation stages. + -1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); + AnimTypeClass const * const AnimTypeClass::Pointers[ANIM_COUNT] = { @@ -2459,6 +2483,7 @@ AnimTypeClass const * const AnimTypeClass::Pointers[ANIM_COUNT] = { &StegDie, &RaptDie, &ChemBall, + &Flag, &Fire3Virtual, &Fire2Virtual, &Fire1Virtual, @@ -2502,7 +2527,7 @@ AnimTypeClass::AnimTypeClass(AnimType anim, char const *name, int size, int bigg IsWhiteTrans = iswhitetrans; LoopEnd = loopend; LoopStart = loopstart; - Loops = (unsigned char)loops; + Loops = (char)loops; Size = size; Sound = sound; Stages = stages; diff --git a/TIBERIANDAWN/AIRCRAFT.CPP b/TIBERIANDAWN/AIRCRAFT.CPP index c8ee3f3..f0ffded 100644 --- a/TIBERIANDAWN/AIRCRAFT.CPP +++ b/TIBERIANDAWN/AIRCRAFT.CPP @@ -607,6 +607,10 @@ int AircraftClass::Mission_Hunt(void) { Validate(); if (Class->IsFixedWing) { + if (Class->Primary == WEAPON_NONE && Class->Secondary == WEAPON_NONE) { + Assign_Mission(MISSION_RETREAT); + return(1); + } enum { LOOK_FOR_TARGET, FLY_TO_TARGET, @@ -1600,10 +1604,15 @@ ResultType AircraftClass::Take_Damage(int & damage, int distance, WarheadType wa switch (res) { case RESULT_DESTROYED: - Kill_Cargo(source); - Death_Announcement(); - new AnimClass(ANIM_FBALL1, Target_Coord()); - Delete_This(); + { + Kill_Cargo(source); + Death_Announcement(); + COORDINATE coord = Target_Coord(); + if (!(coord & 0xC000C000L)) { + new AnimClass(ANIM_FBALL1, coord); + } + Delete_This(); + } break; default: diff --git a/TIBERIANDAWN/ANIM.CPP b/TIBERIANDAWN/ANIM.CPP index d65d448..12644a1 100644 --- a/TIBERIANDAWN/ANIM.CPP +++ b/TIBERIANDAWN/ANIM.CPP @@ -270,6 +270,12 @@ void AnimClass::Draw_It(int x, int y, WindowNumberType window) case ANIM_ATOM_BLAST: transtable = Map.UnitShadow; break; + + case ANIM_FLAG: + x += (ICON_PIXEL_W / 2) - 2; + y += (3 * ICON_PIXEL_H / 4) - Get_Build_Frame_Height(shapefile); + transtable = Map.UnitShadow; + break; } /* @@ -285,7 +291,11 @@ void AnimClass::Draw_It(int x, int y, WindowNumberType window) */ if (IsAlternate) { flags = flags | SHAPE_FADING; - remap = Map.RemapTables[HOUSE_GOOD][0]; + if (OwnerHouse != HOUSE_NONE) { + remap = HouseClass::As_Pointer(OwnerHouse)->Remap_Table(false, false); + } else { + remap = Map.RemapTables[HOUSE_GOOD][0]; + } } if (transtable) flags = flags | SHAPE_GHOST; @@ -380,6 +390,8 @@ short const * AnimClass::Overlap_List(void) const REFRESH_EOL }; + static short const OverlapFlag[] = { 0, 1, -MAP_CELL_W, -(MAP_CELL_W-1), MAP_CELL_W, MAP_CELL_W+1, REFRESH_EOL }; + switch (Class->Type) { case ANIM_CHEM_N: case ANIM_FLAME_N: @@ -419,6 +431,9 @@ short const * AnimClass::Overlap_List(void) const case ANIM_ATOM_BLAST: return(OverlapAtom); + case ANIM_FLAG: + return(OverlapFlag); + default: break; } @@ -550,12 +565,12 @@ void AnimClass::operator delete(void *ptr) * 05/31/1994 JLB : Created. * * 08/03/1994 JLB : Added a delayed affect parameter. * *=============================================================================================*/ -AnimClass::AnimClass(AnimType animnum, COORDINATE coord, unsigned char timedelay, unsigned char loop, bool alt) : +AnimClass::AnimClass(AnimType animnum, COORDINATE coord, unsigned char timedelay, char loop, bool alt) : Class(&AnimTypeClass::As_Reference(animnum)) { Object = 0; SortTarget = TARGET_NONE; - Owner = HOUSE_NONE; + OwnerHouse = HOUSE_NONE; if (Class->Stages == -1) { ((int&)Class->Stages) = Get_Build_Frame_Count(Class->Get_Image_Data()); @@ -590,8 +605,12 @@ AnimClass::AnimClass(AnimType animnum, COORDINATE coord, unsigned char timedelay */ Delay = timedelay; - Loops = (unsigned char)(MAX(loop, (unsigned char)1) * Class->Loops); - Loops = (unsigned char)MAX(Loops, (unsigned char)1); + if (Class->Loops >= 0) { + Loops = (char)(MAX(loop, (char)1) * Class->Loops); + Loops = (char)MAX(Loops, (char)1); + } else { + Loops = Class->Loops; + } IsToDelete = false; IsBrandNew = true; @@ -798,8 +817,8 @@ void AnimClass::AI(void) ** Determine if this animation should loop another time. If so, then start the loop ** but if not, then proceed into the animation termination handler. */ - if (Loops) Loops--; - if (Loops) { + if (Loops > 0) Loops--; + if (Loops != 0) { Set_Stage(Class->LoopStart); } else { if (Class->VirtualAnim != ANIM_NONE) { @@ -992,11 +1011,11 @@ void AnimClass::Middle(void) */ BuildingClass * building = NULL; TechnoClass * backup = NULL; - if (Owner != HOUSE_NONE) { + if (OwnerHouse != HOUSE_NONE) { for (int index = 0; index < Logic.Count(); index++) { ObjectClass * obj = Logic[index]; - if (obj && obj->Is_Techno() && obj->Owner() == Owner) { + if (obj && obj->Is_Techno() && obj->Owner() == OwnerHouse) { backup = (TechnoClass *)obj; if (obj->What_Am_I() == RTTI_BUILDING && *((BuildingClass *)obj) == STRUCT_TEMPLE) { building = (BuildingClass *)obj; @@ -1108,11 +1127,11 @@ void AnimClass::Middle(void) case ANIM_ION_CANNON: { BuildingClass * building = NULL; TechnoClass * backup = NULL; - if (Owner != HOUSE_NONE) { + if (OwnerHouse != HOUSE_NONE) { for (int index = 0; index < Logic.Count(); index++) { ObjectClass * obj = Logic[index]; - if (obj && obj->Is_Techno() && obj->Owner() == Owner && !obj->IsInLimbo) { + if (obj && obj->Is_Techno() && obj->Owner() == OwnerHouse && !obj->IsInLimbo) { backup = (TechnoClass *)obj; if (obj->What_Am_I() == RTTI_BUILDING && *((BuildingClass *)obj) == STRUCT_EYE) { building = (BuildingClass *)obj; diff --git a/TIBERIANDAWN/ANIM.H b/TIBERIANDAWN/ANIM.H index 084c34e..2a78888 100644 --- a/TIBERIANDAWN/ANIM.H +++ b/TIBERIANDAWN/ANIM.H @@ -47,11 +47,12 @@ class AnimClass : public ObjectClass, private StageClass { static void * AnimClass::operator new(size_t size); static void AnimClass::operator delete(void *ptr); - AnimClass(void) : Class(0) {Owner=HOUSE_NONE;Object=0;}; // Default constructor does nothing. - AnimClass(AnimType animnum, COORDINATE coord, unsigned char timedelay=0, unsigned char loop=1, bool alt=false); + AnimClass(void) : Class(0) {OwnerHouse=HOUSE_NONE;Object=0;}; // Default constructor does nothing. + AnimClass(AnimType animnum, COORDINATE coord, unsigned char timedelay=0, char loop=1, bool alt=false); virtual ~AnimClass(void); operator AnimType(void) const {return Class->Type;}; virtual RTTIType What_Am_I(void) const {return RTTI_ANIM;}; + virtual HousesType Owner(void) const {return OwnerHouse;}; /*--------------------------------------------------------------------- ** Member function prototypes. @@ -114,13 +115,13 @@ class AnimClass : public ObjectClass, private StageClass { ** is used when damage is caused by this animation during the middle of its ** animation. */ - HousesType Owner; + HousesType OwnerHouse; /* ** This counter tells how many more times the animation should loop before it ** terminates. */ - unsigned char Loops; + char Loops; protected: void Middle(void); @@ -146,7 +147,9 @@ class AnimClass : public ObjectClass, private StageClass { */ unsigned IsBrandNew:1; - // Use alternate color when drawing? + /* + ** Use alternate color when drawing + */ unsigned IsAlternate:1; /* diff --git a/TIBERIANDAWN/BUILDING.CPP b/TIBERIANDAWN/BUILDING.CPP index 60bf1aa..8a19967 100644 --- a/TIBERIANDAWN/BUILDING.CPP +++ b/TIBERIANDAWN/BUILDING.CPP @@ -293,7 +293,32 @@ RadioMessageType BuildingClass::Receive_Message(RadioClass * from, RadioMessageT Begin_Mode(BSTATE_FULL); } - if (Transmit_Message(RADIO_NEED_TO_MOVE, from) == RADIO_ROGER) { + /* + ** If this building is already in radio contact, then it might + ** be able to satisfy the request to load by bumping off any + ** preoccupying task. + */ + if (*this == STRUCT_REPAIR) { + if (Contact_With_Whom() != from) { + if (Transmit_Message(RADIO_ON_DEPOT) == RADIO_ROGER) { + if (Transmit_Message(RADIO_NEED_REPAIR) == RADIO_NEGATIVE) { + Transmit_Message(RADIO_RUN_AWAY); + Transmit_Message(RADIO_OVER_OUT); + return(RADIO_ROGER); + } + } + } + } + + /* + ** 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) { if (*this == STRUCT_HELIPAD) { param = As_Target(); } else { @@ -308,7 +333,7 @@ RadioMessageType BuildingClass::Receive_Message(RadioClass * from, RadioMessageT /* ** Tell the harvester to move to the docking pad of the refinery. */ - if (Transmit_Message(RADIO_MOVE_HERE, param, from) == RADIO_YEA_NOW_WHAT) { + if (Transmit_Message(RADIO_MOVE_HERE, param) == RADIO_YEA_NOW_WHAT) { /* ** Since the harvester is already there, tell it to begin the backup @@ -1899,7 +1924,7 @@ void BuildingClass::Drop_Debris(TARGET source) CELL cell; /* - ** Special case for Moebius to run from destroyed technology + ** Special case for Chan to run from destroyed technology ** building. */ if (GameToPlay == GAME_NORMAL && *this == STRUCT_MISSION && PlayerPtr->ActLike == HOUSE_BAD && Scenario == 10) { @@ -1907,7 +1932,7 @@ void BuildingClass::Drop_Debris(TARGET source) ScenarioInit++; if (i->Unlimbo(Center_Coord(), DIR_N)) { - i->Trigger = TriggerClass::As_Pointer("CHAN"); + i->Trigger = TriggerClass::As_Pointer("win"); i->Strength = Random_Pick(5, (int)i->Class->MaxStrength); ScenarioInit--; i->Scatter(0, true); @@ -1915,6 +1940,7 @@ void BuildingClass::Drop_Debris(TARGET source) i->Assign_Mission(MISSION_GUARD_AREA); } else { delete i; + PlayerPtr->Flag_To_Win(); } ScenarioInit--; } @@ -3523,7 +3549,7 @@ bool BuildingClass::Toggle_Primary(void) bool BuildingClass::Captured(HouseClass * newowner) { Validate(); - if (Class->IsCaptureable && newowner != House) { + if (Can_Capture() && newowner != House) { switch (Owner()) { case HOUSE_GOOD: Speak(VOX_GDI_CAPTURED); @@ -3803,6 +3829,21 @@ bool BuildingClass::Can_Demolish_Unit(void) const } +bool BuildingClass::Can_Capture(void) const +{ + bool can_capture = Class->IsCaptureable; + if (*this == STRUCT_EYE) { + // Don't allow the Advanced Comm Center to be capturable in skirmish, MP, or beyond scenario 13 in SP + if (GameToPlay == GAME_NORMAL) { + can_capture &= Scenario < 13; + } else { + can_capture = false; + } + } + return(can_capture); +} + + /*********************************************************************************************** * BuildingClass::Mission_Guard -- Handles guard mission for combat buildings. * * * @@ -5020,7 +5061,8 @@ int BuildingClass::Mission_Unload(void) ** Scatter everything around the weapon's factory door. */ for (FacingType f = FACING_FIRST; f < FACING_COUNT; f++) { - CellClass * cptr = &cellptr->Adjacent_Cell(f); + CellClass * cptr = cellptr->Adjacent_Cell(f); + if (!cptr) continue; UnitClass * cellunit = cptr->Cell_Unit(); if ((cellunit && cellunit != unit) || cptr->Cell_Infantry()) { cptr->Incoming(coord, true, true); diff --git a/TIBERIANDAWN/BUILDING.H b/TIBERIANDAWN/BUILDING.H index 61b7d46..16c28ac 100644 --- a/TIBERIANDAWN/BUILDING.H +++ b/TIBERIANDAWN/BUILDING.H @@ -185,6 +185,7 @@ class BuildingClass : public TechnoClass virtual ActionType What_Action(CELL cell) const; virtual bool Can_Demolish(void) const; virtual bool Can_Demolish_Unit(void) const; + virtual bool Can_Capture(void) const; virtual ObjectTypeClass const & Class_Of(void) const {return *Class;}; virtual int Refund_Amount(void) const; virtual DirType Fire_Direction(void) const; diff --git a/TIBERIANDAWN/BULLET.CPP b/TIBERIANDAWN/BULLET.CPP index ef187eb..fb09e7b 100644 --- a/TIBERIANDAWN/BULLET.CPP +++ b/TIBERIANDAWN/BULLET.CPP @@ -471,9 +471,9 @@ void BulletClass::AI(void) // Fix for Nuke or Atom Bomb killing structures and units during animation sequence and not getting kills tracked // Per https://jaas.ea.com/browse/TDRA-6610 // - if (newanim && Class->Explosion == ANIM_ATOM_BLAST && newanim->Owner == HOUSE_NONE) { + if (newanim && Class->Explosion == ANIM_ATOM_BLAST && newanim->OwnerHouse == HOUSE_NONE) { if (Payback && Payback->House && Payback->House->Class) { - newanim->Owner = Payback->House->Class->House; + newanim->OwnerHouse = Payback->House->Class->House; } } } diff --git a/TIBERIANDAWN/CELL.CPP b/TIBERIANDAWN/CELL.CPP index d09a532..22274fa 100644 --- a/TIBERIANDAWN/CELL.CPP +++ b/TIBERIANDAWN/CELL.CPP @@ -211,7 +211,7 @@ TechnoClass * CellClass::Cell_Techno(int x, int y) const if (Cell_Occupier()) { object = Cell_Occupier(); - while (object) { + while (object && object->IsActive) { if (object->Is_Techno()) { COORDINATE coord; // Coordinate relative to cell corner. long dist; @@ -248,7 +248,7 @@ ObjectClass * CellClass::Cell_Find_Object(RTTIType rtti) const Validate(); ObjectClass * object = Cell_Occupier(); - while (object) { + while (object && object->IsActive) { if (object->What_Am_I() == rtti) { return(object); } @@ -1132,14 +1132,6 @@ void CellClass::Draw_It(int x, int y, int draw_type) const } #endif } - - /* - ** Draw the flag if there is one located at this cell. - */ - if (IsFlagged) { - void const * remap = HouseClass::As_Pointer(Owner)->Remap_Table(false, false); - CC_Draw_Shape(MixFileClass::Retrieve("FLAGFLY.SHP"), Frame % 14, x+(ICON_PIXEL_W/2), y+(ICON_PIXEL_H/2), WINDOW_TACTICAL, SHAPE_CENTER|SHAPE_GHOST|SHAPE_FADING, remap, Map.UnitShadow); - } } } } @@ -1202,13 +1194,13 @@ void CellClass::Concrete_Calc(void) */ index = 0; for (int i = 0; i < (sizeof(_even)/sizeof(_even[0])); i++) { - CellClass & cellptr = Adjacent_Cell(*ptr++); + CellClass * cellptr = Adjacent_Cell(*ptr++); // if ((cellptr->IsConcrete) || // cellptr->Concrete == C_UPDOWN_RIGHT || // cellptr->Concrete == C_UPDOWN_LEFT) { - if (cellptr.Overlay == OVERLAY_CONCRETE) { + if (cellptr && cellptr->Overlay == OVERLAY_CONCRETE) { index |= (1<Overlay != OVERLAY_NONE && OverlayTypeClass::As_Reference(newcell->Overlay).IsWall) { int icon = 0; /* @@ -1409,41 +1401,42 @@ void CellClass::Wall_Update(void) ** cells. */ for (unsigned i = 0; i < 4; i++) { - if (newcell.Adjacent_Cell(_offsets[i]).Overlay == newcell.Overlay) { + CellClass * adjcell = newcell->Adjacent_Cell(_offsets[i]); + if (adjcell && adjcell->Overlay == newcell->Overlay) { icon |= 1 << i; } } - newcell.OverlayData = (newcell.OverlayData & 0xFFF0) | icon; -// newcell.OverlayData = icon; + newcell->OverlayData = (newcell->OverlayData & 0xFFF0) | icon; +// newcell->OverlayData = icon; /* ** Handle special cases for the incomplete damaged wall sets. If a damage stage ** is calculated, but there is no artwork for it, then consider the wall to be ** completely destroyed. */ - if (newcell.Overlay == OVERLAY_BRICK_WALL && newcell.OverlayData == 48) { - newcell.Overlay = OVERLAY_NONE; - newcell.OverlayData = 0; - ObjectClass::Detach_This_From_All(::As_Target(newcell.Cell_Number()), true); + if (newcell->Overlay == OVERLAY_BRICK_WALL && newcell->OverlayData == 48) { + newcell->Overlay = OVERLAY_NONE; + newcell->OverlayData = 0; + ObjectClass::Detach_This_From_All(::As_Target(newcell->Cell_Number()), true); } - if (newcell.Overlay == OVERLAY_SANDBAG_WALL && newcell.OverlayData == 16) { - newcell.Overlay = OVERLAY_NONE; - newcell.OverlayData = 0; - ObjectClass::Detach_This_From_All(::As_Target(newcell.Cell_Number()), true); + if (newcell->Overlay == OVERLAY_SANDBAG_WALL && newcell->OverlayData == 16) { + newcell->Overlay = OVERLAY_NONE; + newcell->OverlayData = 0; + ObjectClass::Detach_This_From_All(::As_Target(newcell->Cell_Number()), true); } - if (newcell.Overlay == OVERLAY_CYCLONE_WALL && newcell.OverlayData == 32) { - newcell.Overlay = OVERLAY_NONE; - newcell.OverlayData = 0; - ObjectClass::Detach_This_From_All(::As_Target(newcell.Cell_Number()), true); + if (newcell->Overlay == OVERLAY_CYCLONE_WALL && newcell->OverlayData == 32) { + newcell->Overlay = OVERLAY_NONE; + newcell->OverlayData = 0; + ObjectClass::Detach_This_From_All(::As_Target(newcell->Cell_Number()), true); } - if (newcell.Overlay == OVERLAY_BARBWIRE_WALL && newcell.OverlayData == 16) { - newcell.Overlay = OVERLAY_NONE; - newcell.OverlayData = 0; - ObjectClass::Detach_This_From_All(::As_Target(newcell.Cell_Number()), true); + if (newcell->Overlay == OVERLAY_BARBWIRE_WALL && newcell->OverlayData == 16) { + newcell->Overlay = OVERLAY_NONE; + newcell->OverlayData = 0; + ObjectClass::Detach_This_From_All(::As_Target(newcell->Cell_Number()), true); } - newcell.Recalc_Attributes(); - newcell.Redraw_Objects(); + newcell->Recalc_Attributes(); + newcell->Redraw_Objects(); } } } @@ -1550,10 +1543,14 @@ int CellClass::Reduce_Wall(int damage) OverlayData = 0; Recalc_Attributes(); Redraw_Objects(); - Adjacent_Cell(FACING_N).Wall_Update(); - Adjacent_Cell(FACING_W).Wall_Update(); - Adjacent_Cell(FACING_S).Wall_Update(); - Adjacent_Cell(FACING_E).Wall_Update(); + CellClass * ncell = Adjacent_Cell(FACING_N); + if (ncell) ncell->Wall_Update(); + CellClass * wcell = Adjacent_Cell(FACING_W); + if (wcell) wcell->Wall_Update(); + CellClass * scell = Adjacent_Cell(FACING_S); + if (scell) scell->Wall_Update(); + CellClass * ecell = Adjacent_Cell(FACING_E); + if (ecell) ecell->Wall_Update(); return(true); } } @@ -1813,28 +1810,23 @@ void CellClass::Incoming(COORDINATE threat, bool forced, bool nokidding) * HISTORY: * * 03/19/1995 JLB : Created. * *=============================================================================================*/ -CellClass const & CellClass::Adjacent_Cell(FacingType face) const +CellClass const * CellClass::Adjacent_Cell(FacingType face) const { Validate(); + if (face == FACING_NONE) { + return(this); + } + if ((unsigned)face >= FACING_COUNT) { - return(*this); + return(NULL); } - CELL id = Cell_Number(); - //The top row doesn't have any adjacent cells to the north. - LLL - if (id < MAP_CELL_W && (face == FACING_N || face == FACING_NE || face == FACING_NW)) { - return (*this); + CELL newcell = ::Adjacent_Cell(Cell_Number(), face); + if ((unsigned)newcell >= MAP_CELL_TOTAL) { + return(NULL); } - //The bottom row doesn't have any adjacent cells to the south. - LLL - if ((id > MAP_CELL_TOTAL - MAP_CELL_W) && (face == FACING_S || face == FACING_SE || face == FACING_SW)) { - return (*this); - } - - CellClass const * ptr = this + AdjacentCell[face]; - if (ptr->Cell_Number() & 0xF000) return(*this); - return(*ptr); -// return(*(this + AdjacentCell[face])); + return &Map[newcell]; } @@ -1911,10 +1903,10 @@ long CellClass::Tiberium_Adjust(bool pregame) CELL cell = Cell_Number() + AdjacentCell[face]; if ((unsigned)cell >= MAP_CELL_TOTAL) continue; - CellClass & adj = Adjacent_Cell(face); + CellClass * adj = Adjacent_Cell(face); - if (adj.Overlay != OVERLAY_NONE && - OverlayTypeClass::As_Reference(adj.Overlay).Land == LAND_TIBERIUM) { + if (adj && adj->Overlay != OVERLAY_NONE && + OverlayTypeClass::As_Reference(adj->Overlay).Land == LAND_TIBERIUM) { count++; } } @@ -2386,6 +2378,7 @@ bool CellClass::Flag_Place(HousesType house) if (!IsFlagged && Is_Generally_Clear()) { IsFlagged = true; Owner = house; + Flag_Update(); Redraw_Objects(); return(true); } @@ -2413,6 +2406,7 @@ bool CellClass::Flag_Remove(void) if (IsFlagged) { IsFlagged = false; Owner = HOUSE_NONE; + Flag_Update(); Redraw_Objects(); return(true); } @@ -2420,6 +2414,34 @@ bool CellClass::Flag_Remove(void) } +void CellClass::Flag_Update(void) +{ + if (IsFlagged && !CTFFlag) { + Flag_Create(); + } else if (!IsFlagged && CTFFlag) { + Flag_Destroy(); + } +} + + +void CellClass::Flag_Create(void) +{ + if (!CTFFlag) { + CTFFlag = new AnimClass(ANIM_FLAG, Cell_Coord(), 0, 1, true); + if (CTFFlag) { + CTFFlag->OwnerHouse = Owner; + } + } +} + + +void CellClass::Flag_Destroy(void) +{ + delete CTFFlag; + CTFFlag = NULL; +} + + /*********************************************************************************************** * CellClass::Shimmer -- Causes all objects in the cell to shimmer. * * * diff --git a/TIBERIANDAWN/CELL.H b/TIBERIANDAWN/CELL.H index 091d294..963897a 100644 --- a/TIBERIANDAWN/CELL.H +++ b/TIBERIANDAWN/CELL.H @@ -194,8 +194,8 @@ class CellClass bool Is_Generally_Clear(bool ignore_cloaked = false) const; TARGET As_Target(void) const {return ::As_Target(Cell_Number());}; BuildingClass * Cell_Building(void) const; - CellClass const & Adjacent_Cell(FacingType face) const; - CellClass & Adjacent_Cell(FacingType face) {return (CellClass &)((*((CellClass const *)this)).Adjacent_Cell(face));}; + CellClass const * Adjacent_Cell(FacingType face) const; + CellClass * Adjacent_Cell(FacingType face) {return (CellClass *)((*((CellClass const *)this)).Adjacent_Cell(face));}; COORDINATE Cell_Coord(void) const; int Cell_Color(bool override=false) const; CELL Cell_Number(void) const; @@ -221,6 +221,9 @@ class CellClass void Overlap_Up(ObjectClass * object); bool Flag_Place(HousesType house); bool Flag_Remove(void); + void Flag_Update(void); + void Flag_Create(void); + void Flag_Destroy(void); /* ** File I/O. @@ -286,10 +289,15 @@ class CellClass LandType OverrideLand; // The overriden land type of this cell. + /* + ** Points to the flag animation on this cell in CTF games. + */ + AnimClass* CTFFlag; + /* ** Some additional padding in case we need to add data to the class and maintain backwards compatibility for save/load */ - unsigned char SaveLoadPadding[32]; + unsigned char SaveLoadPadding[28]; }; #endif \ No newline at end of file diff --git a/TIBERIANDAWN/COMBAT.CPP b/TIBERIANDAWN/COMBAT.CPP index c2898bf..fc1cb2f 100644 --- a/TIBERIANDAWN/COMBAT.CPP +++ b/TIBERIANDAWN/COMBAT.CPP @@ -160,7 +160,8 @@ void Explosion_Damage(COORDINATE coord, unsigned strength, TechnoClass * source, ** further than one cell away. */ if (i != FACING_NONE) { - cellptr = &Map[cell].Adjacent_Cell(i); + cellptr = Map[cell].Adjacent_Cell(i); + if (!cellptr) continue; } /* diff --git a/TIBERIANDAWN/CONQUER.CPP b/TIBERIANDAWN/CONQUER.CPP index cf88fff..01d93d0 100644 --- a/TIBERIANDAWN/CONQUER.CPP +++ b/TIBERIANDAWN/CONQUER.CPP @@ -3140,15 +3140,13 @@ void Handle_Team(int team, int action) ** If a non team member is currently selected, then deselect all objects ** before selecting this team. */ - if (CurrentObject.Count()) { - switch (CurrentObject[0]->What_Am_I()) { - case RTTI_UNIT: - case RTTI_INFANTRY: - case RTTI_AIRCRAFT: - if (((FootClass *)CurrentObject[0])->Group != team) { - Unselect_All(); - } + for (index = 0; index < CurrentObject.Count(); index++) { + ObjectClass * obj = CurrentObject[index]; + if (obj->What_Am_I() == RTTI_UNIT || obj->What_Am_I() == RTTI_INFANTRY || obj->What_Am_I() == RTTI_AIRCRAFT) { + if (((FootClass *)obj)->Group != team) { + Unselect_All(); break; + } } } for (index = 0; index < Units.Count(); index++) { diff --git a/TIBERIANDAWN/DEFINES.H b/TIBERIANDAWN/DEFINES.H index db2d5b3..a5c3179 100644 --- a/TIBERIANDAWN/DEFINES.H +++ b/TIBERIANDAWN/DEFINES.H @@ -1509,6 +1509,7 @@ typedef enum AnimType : char { ANIM_STEG_DIE, ANIM_RAPT_DIE, ANIM_CHEM_BALL, // Chemical warrior explosion. + ANIM_FLAG, // CTF flag. ANIM_FIRE_SMALL_VIRTUAL, // Small flame animation (virtual). ANIM_FIRE_MED_VIRTUAL, // Medium flame animation (virtual). @@ -1630,6 +1631,9 @@ typedef enum RadioMessageType : unsigned char { RADIO_PUNCH, // "Take this punch, you.. you.." RADIO_PREPARE_TO_BOX,// "Fancy a little fisticuffs, eh?" + RADIO_NEED_REPAIR, // "Are you in need of service depot work?" + RADIO_ON_DEPOT, // "Are you sitting on a service depot?" + RADIO_COUNT } RadioMessageType; diff --git a/TIBERIANDAWN/DLLInterface.cpp b/TIBERIANDAWN/DLLInterface.cpp index b2962b9..abbfabb 100644 --- a/TIBERIANDAWN/DLLInterface.cpp +++ b/TIBERIANDAWN/DLLInterface.cpp @@ -106,7 +106,7 @@ typedef enum { */ #define RANDOM_START_POSITION 0x7f - +#define KILL_PLAYER_ON_DISCONNECT 1 @@ -1695,7 +1695,7 @@ extern "C" __declspec(dllexport) bool __cdecl CNC_Save_Load(bool save, const cha } result = Load_Game(file_path_and_name); - + DLLExportClass::Set_Player_Context(DLLExportClass::GlyphxPlayerIDs[0], true); Set_Logic_Page(SeenBuff); VisiblePage.Clear(); @@ -1747,9 +1747,6 @@ extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Player_Switch_To_AI(uin if (PlayerWins || PlayerLoses || DLLExportClass::Get_Game_Over()) { return; } - - HousesType house; - HouseClass *ptr; GlyphX_Debug_Print("CNC_Handle_Player_Switch_To_AI"); @@ -1757,9 +1754,28 @@ extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Player_Switch_To_AI(uin return; } +#ifdef KILL_PLAYER_ON_DISCONNECT + + /* + ** Kill player's units on disconnect. + */ if (player_id != 0) { DLLExportClass::Set_Player_Context(player_id); + if (PlayerPtr) { + PlayerPtr->Flag_To_Die(); + } + } + +#else //KILL_PLAYER_ON_DISCONNECT + + if (player_id != 0) { + + HousesType house; + HouseClass *ptr; + + DLLExportClass::Set_Player_Context(player_id); + if (PlayerPtr) { PlayerPtr->WasHuman = true; PlayerPtr->IsHuman = false; @@ -1803,6 +1819,9 @@ extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Player_Switch_To_AI(uin } } } + +#endif //KILL_PLAYER_ON_DISCONNECT + } @@ -2489,18 +2508,26 @@ void DLLExportClass::On_Multiplayer_Game_Over(void) event.GameOver.SabotagedStructureType = 0; event.GameOver.TimerRemaining = -1; - // Trigger an event for each human player - for (int i=0 ; iIsHuman == true ) - { - event.GlyphXPlayerID = Get_GlyphX_Player_ID(player_ptr); - event.GameOver.PlayerWins = !player_ptr->IsDefeated; - event.GameOver.RemainingCredits = player_ptr->Available_Money(); - EventCallback(event); - } + if (player_ptr != NULL && !player_ptr->IsDefeated) { + event.GlyphXPlayerID = Get_GlyphX_Player_ID(player_ptr); + event.GameOver.IsHuman = player_ptr->IsHuman; + event.GameOver.PlayerWins = true; + event.GameOver.RemainingCredits = player_ptr->Available_Money(); + EventCallback(event); + } + } + + for (int i = 0; i < MPlayerCount; i++) { + HouseClass* player_ptr = HouseClass::As_Pointer(MPlayerHouses[i]); //HouseClass::As_Pointer(HOUSE_MULTI2); + if (player_ptr != NULL && player_ptr->IsHuman && player_ptr->IsDefeated) { + event.GlyphXPlayerID = Get_GlyphX_Player_ID(player_ptr); + event.GameOver.IsHuman = true; + event.GameOver.PlayerWins = false; + event.GameOver.RemainingCredits = player_ptr->Available_Money(); + EventCallback(event); } } } @@ -3761,6 +3788,10 @@ extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Sidebar_Request(Sidebar switch (request_type) { + // MBL 06.02.2020 - Changing right-click support for first put building on hold, and then subsequenct right-clicks to decrement that queue count for 1x or 5x; Then, 1x or 5x Left click will resume from hold + // Handle and fall through to start construction (from hold state) below + case SIDEBAR_REQUEST_START_CONSTRUCTION_MULTI: + case SIDEBAR_REQUEST_START_CONSTRUCTION: DLLExportClass::Start_Construction(player_id, buildable_type, buildable_id); break; @@ -6627,8 +6658,12 @@ void DLLExportClass::Selected_Guard_Mode(uint64 player_id) for (int index = 0; index < CurrentObject.Count(); index++) { ObjectClass const * tech = CurrentObject[index]; - if (tech && tech->Can_Player_Move() && tech->Can_Player_Fire()) { - OutList.Add(EventClass(tech->As_Target(), MISSION_GUARD_AREA)); + if (tech && tech->Can_Player_Fire()) { + if (tech->Can_Player_Move()) { + OutList.Add(EventClass(tech->As_Target(), MISSION_GUARD_AREA)); + } else { + OutList.Add(EventClass(tech->As_Target(), MISSION_GUARD)); + } } } } @@ -7158,7 +7193,7 @@ void DLLExportClass::Debug_Heal_Unit(int x, int y) CellClass* cells[cellcount]; cells[0] = cellptr; for (FacingType index = FACING_N; index < FACING_COUNT; index++) { - cells[(int)index + 1] = &cellptr->Adjacent_Cell(index); + cells[(int)index + 1] = cellptr->Adjacent_Cell(index); } for (int index = 0; index < cellcount; index++) { diff --git a/TIBERIANDAWN/DLLInterface.h b/TIBERIANDAWN/DLLInterface.h index d2f2a2d..a52c00c 100644 --- a/TIBERIANDAWN/DLLInterface.h +++ b/TIBERIANDAWN/DLLInterface.h @@ -28,7 +28,7 @@ struct CarryoverObjectStruct; ** ** */ -#define CNC_DLL_API_VERSION 0x100 +#define CNC_DLL_API_VERSION 0x101 @@ -623,6 +623,7 @@ struct EventCallbackStruct { // // Single-player data // + bool IsHuman; bool PlayerWins; const char* MovieName; const char* MovieName2; diff --git a/TIBERIANDAWN/DRIVE.CPP b/TIBERIANDAWN/DRIVE.CPP index f2ad696..dfd1cda 100644 --- a/TIBERIANDAWN/DRIVE.CPP +++ b/TIBERIANDAWN/DRIVE.CPP @@ -521,8 +521,8 @@ void DriveClass::Assign_Destination(TARGET target) ** facility is currently not involved with some other unit (radio or unloading). */ if (b && *b == STRUCT_REPAIR) { - if (b->In_Radio_Contact()) { - target = 0; + if (b->In_Radio_Contact() && (b->Contact_With_Whom() != this)) { + ArchiveTarget = target; } else { /* @@ -1598,12 +1598,18 @@ void DriveClass::Mark_Track(COORDINATE headto, MarkType type) if (TrackIndex < cellidx && cellidx != -1) { COORDINATE offset = Smooth_Turn(ptr[cellidx].Offset, &dir); - Map[Coord_Cell(offset)].Flag.Occupy.Vehicle = value; + CELL cell = Coord_Cell(offset); + if ((unsigned)cell < MAP_CELL_TOTAL) { + Map[cell].Flag.Occupy.Vehicle = value; + } } } } } - Map[Coord_Cell(headto)].Flag.Occupy.Vehicle = value; + CELL cell = Coord_Cell(headto); + if ((unsigned)cell < MAP_CELL_TOTAL) { + Map[cell].Flag.Occupy.Vehicle = value; + } } } diff --git a/TIBERIANDAWN/FACTORY.CPP b/TIBERIANDAWN/FACTORY.CPP index b9fbe93..79b35e9 100644 --- a/TIBERIANDAWN/FACTORY.CPP +++ b/TIBERIANDAWN/FACTORY.CPP @@ -546,23 +546,10 @@ bool FactoryClass::Start(void) time = TICKS_PER_MINUTE * 5; } - /* - ** Adjust time according to IQ setting of computer controlled house. The - ** build time will range from double normal time at the slowest to - ** just normal time at the fastest. - */ - if (!House->IsHuman && Rule.Diff[House->Difficulty].IsBuildSlowdown) { - time = (int)((time*Rule.MaxIQ*2.0f) / (House->IQ+Rule.MaxIQ)); - } + time /= STEP_COUNT; + time = Bound(time, 1, 255); - int frac = House->Power_Fraction(); - frac = Bound(frac, 0x0010, 0x0100); - int rate = (time*256) / frac; - - rate /= STEP_COUNT; - rate = Bound(rate, 1, 255); - - Set_Rate(rate); + Set_Rate(time); IsSuspended = false; return(true); } diff --git a/TIBERIANDAWN/FINDPATH.CPP b/TIBERIANDAWN/FINDPATH.CPP index 028bf2d..92f245c 100644 --- a/TIBERIANDAWN/FINDPATH.CPP +++ b/TIBERIANDAWN/FINDPATH.CPP @@ -98,6 +98,8 @@ #define MAX_MLIST_SIZE 300 #define THREAT_THRESHOLD 5 +#define MAX_PATH_EDGE_FOLLOW 400 + #ifdef NEVER typedef enum { FACING_N, // North @@ -734,8 +736,14 @@ top_of_list: pleft.Overlap = LeftOverlap; Mem_Copy(path.Command, pleft.Command, path.Length); Mem_Copy(path.Overlap, pleft.Overlap, sizeof(LeftOverlap)); + // MBL 09.30.2019: We hit a runtime bounds crash where END (-1 / 0xFF) was being poked into +1 just past the end of the moves_right[] array; + // The FacingType moves_left[] and moves_right[] arrays already have MAX_MLIST_SIZE+2 as their size, which may have been a previous attempted fix; + // We are now passing MAX_MLIST_SIZE, since the sizeof calculations included the +2 buffering; + #if 0 left = Follow_Edge(startcell, next, &pleft, COUNTERCLOCK, direction, threat, threat_stage, sizeof(moves_left), threshhold); // left = Follow_Edge(startcell, next, &pleft, COUNTERCLOCK, direction, threat, threat_stage, follow_len, threshhold); + #endif + left = Follow_Edge(startcell, next, &pleft, COUNTERCLOCK, direction, threat, threat_stage, MAX_MLIST_SIZE, threshhold); if (left) { follow_len = MIN(maxlen, pleft.Length + (pleft.Length >> 1)); @@ -761,8 +769,14 @@ top_of_list: pright.Overlap = RightOverlap; Mem_Copy(path.Command, pright.Command, path.Length); Mem_Copy(path.Overlap, pright.Overlap, sizeof(RightOverlap)); + // MBL 09.30.2019: We hit a runtime bounds crash where END (-1 / 0xFF) was being poked into +1 just past the end of the moves_right[] array; + // The FacingType moves_left[] and moves_right[] arrays already have MAX_MLIST_SIZE+2 as their size, which may have been a previous attempted fix; + // We are now passing MAX_MLIST_SIZE, since the sizeof calculations included the +2 buffering; + #if 0 right = Follow_Edge(startcell, next, &pright, CLOCK, direction, threat, threat_stage, sizeof(moves_right), threshhold); // right = Follow_Edge(startcell, next, &pright, CLOCK, direction, threat, threat_stage, follow_len, threshhold); + #endif + right = Follow_Edge(startcell, next, &pright, CLOCK, direction, threat, threat_stage, MAX_MLIST_SIZE, threshhold); /* ** If we are in debug mode then let us know how well our right path @@ -1119,7 +1133,7 @@ bool FootClass::Follow_Edge(CELL start, CELL target, PathType *path, FacingType online = true; } cellcount++; - if (cellcount==100) { + if (cellcount==MAX_PATH_EDGE_FOLLOW) { // DrawPath = true; // Debug_Find_Path = true; // Debug_Draw_Map("Loop failure", start, target, false); diff --git a/TIBERIANDAWN/FOOT.CPP b/TIBERIANDAWN/FOOT.CPP index 9d878c3..c71453f 100644 --- a/TIBERIANDAWN/FOOT.CPP +++ b/TIBERIANDAWN/FOOT.CPP @@ -1006,6 +1006,7 @@ int FootClass::Mission_Guard_Area(void) Target_Something_Nearby(THREAT_AREA); Coord = old; if (Target_Legal(TarCom)) return(1); + Random_Animate(); } else { Approach_Target(); } @@ -1258,7 +1259,7 @@ void FootClass::Active_Click_With(ActionType action, ObjectClass * object) break; case ACTION_ENTER: - if (Can_Player_Move() && object && object->Is_Techno() && !((RadioClass *)object)->In_Radio_Contact()) { + if (Can_Player_Move() && object && object->Is_Techno() /*&& !((RadioClass *)object)->In_Radio_Contact()*/) { Player_Assign_Mission(MISSION_ENTER, TARGET_NONE, object->As_Target()); } break; @@ -1498,6 +1499,18 @@ RadioMessageType FootClass::Receive_Message(RadioClass * from, RadioMessageType { switch (message) { + /* + ** Answers if this object is located on top of a service depot. + */ + case RADIO_ON_DEPOT: + if (Map[Coord_Cell(Center_Coord())].Cell_Building() != NULL) { + BuildingClass const * building = Map[Coord_Cell(Center_Coord())].Cell_Building(); + if (*building == STRUCT_REPAIR) { + return(RADIO_ROGER); + } + } + return(RADIO_NEGATIVE); + /* ** Intercept the repair request and if this object is moving, then no repair ** is possible. @@ -1524,7 +1537,7 @@ RadioMessageType FootClass::Receive_Message(RadioClass * from, RadioMessageType Assign_Mission(MISSION_GUARD); } if (!IsRotating && !Target_Legal(NavCom)) { - Scatter(0, true); + Scatter(0, true, true); } break; @@ -1590,36 +1603,40 @@ RadioMessageType FootClass::Receive_Message(RadioClass * from, RadioMessageType int FootClass::Mission_Enter(void) { /* - ** If radio contact has not yet been established with the transport, try to - ** establish contact now. + ** Find out who to coordinate with. If in radio contact, then this the transporter is + ** defined. If not in radio contact, then try the archive target value to see if that + ** is suitable. */ - if (!In_Radio_Contact()) { - TechnoClass * techno = As_Techno(ArchiveTarget); - if (!techno) techno = As_Techno(NavCom); - if (techno) { + TechnoClass * contact = Contact_With_Whom(); + if (contact == NULL) { + contact = As_Techno(ArchiveTarget); + } + if (contact == NULL) { + contact = As_Techno(NavCom); + } - /* - ** If the transport is already in radio contact, do nothing. Try to - ** establish radio contact later. - */ - if (Transmit_Message(RADIO_HELLO, techno) == RADIO_ROGER) { - Assign_Destination(TARGET_NONE); - } - } else { - ArchiveTarget = TARGET_NONE; + /* + ** If in contact, then let the transporter handle the movement coordination. + */ + if (contact != NULL) { + + /* + ** If the transport says to "bug off", then abort the enter mission. The transport may + ** likely say all is 'ok' with the "RADIO ROGER", then try again later. + */ + if (Transmit_Message(RADIO_DOCKING, contact) != RADIO_ROGER && !IsTethered) { + Transmit_Message(RADIO_OVER_OUT); Enter_Idle_Mode(); } } else { /* - ** Since radio contact exists with the transport, maintain a dialogue so that - ** the transport can give proper instructions to the passenger. + ** Since there is no potential object to enter, then abort this + ** mission with some default standby mission. */ - if (Transmit_Message(RADIO_DOCKING) != RADIO_ROGER) { - Transmit_Message(RADIO_OVER_OUT); - Enter_Idle_Mode(); - } + ArchiveTarget = TARGET_NONE; + Enter_Idle_Mode(); } return(TICKS_PER_SECOND/2); } diff --git a/TIBERIANDAWN/FUNCTION.H b/TIBERIANDAWN/FUNCTION.H index 94ce1fe..3d07eb8 100644 --- a/TIBERIANDAWN/FUNCTION.H +++ b/TIBERIANDAWN/FUNCTION.H @@ -669,6 +669,7 @@ void Do_Win(void); void Do_Restart(void); void Fill_In_Data(void); bool Restate_Mission(char const * name, int button1, int button2); +void Fixup_Scenario(void); /* ** SCORE.CPP diff --git a/TIBERIANDAWN/HOUSE.CPP b/TIBERIANDAWN/HOUSE.CPP index b0a8505..c96650c 100644 --- a/TIBERIANDAWN/HOUSE.CPP +++ b/TIBERIANDAWN/HOUSE.CPP @@ -1221,6 +1221,7 @@ void HouseClass::AI(void) unit->Mark(MARK_CHANGE); } else { CELL cell = As_Cell(FlagLocation); + Map[cell].Flag_Update(); Map[cell].Redraw_Objects(); } } @@ -1665,7 +1666,11 @@ void HouseClass::Attacked(BuildingClass* source) Validate(); if (SpeakAttackDelay.Expired() && PlayerPtr->Class->House == Class->House) { Speak(VOX_BASE_UNDER_ATTACK, NULL, source ? source->Center_Coord() : 0); - SpeakAttackDelay.Set(Options.Normalize_Delay(SPEAK_DELAY)); + + // MBL 06.13.2020 - Timing change from 2 minute cooldown, per https://jaas.ea.com/browse/TDRA-6784 + // SpeakAttackDelay.Set(Options.Normalize_Delay(SPEAK_DELAY)); // 2 minutes + // SpeakAttackDelay.Set(Options.Normalize_Delay(TICKS_PER_MINUTE/2)); // 30 seconds as requested + SpeakAttackDelay.Set(Options.Normalize_Delay( (TICKS_PER_MINUTE/2)+(TICKS_PER_SECOND*5) )); // Tweaked for accuracy /* ** If there is a trigger event associated with being attacked, process it @@ -2655,7 +2660,7 @@ bool HouseClass::Place_Special_Blast(SpecialWeaponType id, CELL cell) case SPC_ION_CANNON: if (IonCannon.Is_Ready()) { anim = new AnimClass(ANIM_ION_CANNON, Cell_Coord(cell)); - if (anim) anim->Owner = Class->House; + if (anim) anim->OwnerHouse = Class->House; if (this == PlayerPtr) { Map.IsTargettingMode = false; } @@ -4055,7 +4060,6 @@ void HouseClass::MPlayer_Defeated(void) ------------------------------------------------------------------------*/ if (PlayerPtr == this) { MPlayerObiWan = 1; - Debug_Unshroud = true; HiddenPage.Clear(); Map.Flag_To_Redraw(true); @@ -4313,11 +4317,6 @@ void HouseClass::MPlayer_Defeated(void) MPlayerCount = 0; } } - - /*------------------------------------------------------------------------ - Be sure our messages get displayed, even if we're about to exit. - ------------------------------------------------------------------------*/ - Map.Render(); } @@ -4738,10 +4737,14 @@ void HouseClass::Sell_Wall(CELL cell) Map[cell].OverlayData = 0; Map[cell].Owner = HOUSE_NONE; Map[cell].Wall_Update(); - Map[cell].Adjacent_Cell(FACING_N).Wall_Update(); - Map[cell].Adjacent_Cell(FACING_W).Wall_Update(); - Map[cell].Adjacent_Cell(FACING_S).Wall_Update(); - Map[cell].Adjacent_Cell(FACING_E).Wall_Update(); + CellClass * ncell = Map[cell].Adjacent_Cell(FACING_N); + if (ncell) ncell->Wall_Update(); + CellClass * wcell = Map[cell].Adjacent_Cell(FACING_W); + if (wcell) wcell->Wall_Update(); + CellClass * scell = Map[cell].Adjacent_Cell(FACING_S); + if (scell) scell->Wall_Update(); + CellClass * ecell = Map[cell].Adjacent_Cell(FACING_E); + if (ecell) ecell->Wall_Update(); Map[cell].Recalc_Attributes(); Map[cell].Redraw_Objects(); ObjectClass::Detach_This_From_All(::As_Target(cell), true); diff --git a/TIBERIANDAWN/IDATA.CPP b/TIBERIANDAWN/IDATA.CPP index 1853d23..7410c6b 100644 --- a/TIBERIANDAWN/IDATA.CPP +++ b/TIBERIANDAWN/IDATA.CPP @@ -572,7 +572,7 @@ static InfantryTypeClass const Commando( false, // Is this a civlian? false, // Always use the given name for the infantry? false, // Is this a "fraidycat" run-away type infantry? - true, // Can this infantry type capture a building? + false, // Can this infantry type capture a building? false, // Theater specific graphic image? -1, // Number of shots it has (default). &CommandoDos[0][0], // ptr to DO table @@ -1648,6 +1648,7 @@ InfantryTypeClass::InfantryTypeClass ( IsCapture = is_capture; IsFraidyCat = is_fraidycat; IsCivilian = is_civilian; + IsAvoidingTiberium = false; Type = type; FireLaunch = firelaunch; ProneLaunch = pronelaunch; diff --git a/TIBERIANDAWN/INFANTRY.CPP b/TIBERIANDAWN/INFANTRY.CPP index 1db1082..585fce8 100644 --- a/TIBERIANDAWN/INFANTRY.CPP +++ b/TIBERIANDAWN/INFANTRY.CPP @@ -1000,7 +1000,7 @@ void InfantryClass::Assign_Target(TARGET target) */ if (!Target_Legal(NavCom) && Class->IsCapture && Class->Primary == WEAPON_NONE) { BuildingClass const * building = As_Building(target); - if (building && building->Class->IsCaptureable && (GameToPlay != GAME_NORMAL || *building != STRUCT_EYE && Scenario < 13)) { + if (building && building->Can_Capture()) { Assign_Destination(target); } } @@ -2086,8 +2086,12 @@ void InfantryClass::Scatter(COORDINATE threat, bool forced, bool nokidding) newcell = Adjacent_Cell(Coord_Cell(Coord), newface); if (Map.In_Radar(newcell) && Can_Enter_Cell(newcell) == MOVE_OK) { - Assign_Mission(MISSION_MOVE); - Assign_Destination(::As_Target(newcell)); + if (!Class->IsAvoidingTiberium || + Map[newcell].Overlay == OVERLAY_NONE || + OverlayTypeClass::As_Reference(Map[newcell].Overlay).Land != LAND_TIBERIUM) { + Assign_Mission(MISSION_MOVE); + Assign_Destination(::As_Target(newcell)); + } } } } @@ -2909,7 +2913,7 @@ ActionType InfantryClass::What_Action(ObjectClass * object) const if (object->Owner() != Owner() && ((object->What_Am_I() == RTTI_AIRCRAFT && ((AircraftClass *)object)->Pip_Count() == 0 && *((AircraftClass *)object) == AIRCRAFT_TRANSPORT) || (object->What_Am_I() == RTTI_BUILDING && - ((BuildingClass *)object)->Class->IsCaptureable && (GameToPlay != GAME_NORMAL || *((BuildingClass *)object) != STRUCT_EYE || Scenario < 13) )) + ((BuildingClass *)object)->Can_Capture())) ) { action = ACTION_CAPTURE; @@ -2978,6 +2982,13 @@ void InfantryClass::Read_INI(char *buffer) */ classid = InfantryTypeClass::From_Name(strtok(NULL, ",\n\r")); + /* + ** Special case: replace C7 with C5 on scg08eb + */ + if (GameToPlay == GAME_NORMAL && PlayerPtr->ActLike == HOUSE_GOOD && Scenario == 8 && ScenVar == SCEN_VAR_B && classid == INFANTRY_C7) { + classid = INFANTRY_C5; + } + if (classid != INFANTRY_NONE) { if (HouseClass::As_Pointer(inhouse) != NULL) { @@ -3009,7 +3020,12 @@ void InfantryClass::Read_INI(char *buffer) infantry->Trigger->AttachCount++; } - if (infantry->Unlimbo(coord, dir)) { + /* + ** Special case: delete pre-placed Chan on scb10ea; he will spawn from the Tech Center. + */ + bool is_scb10ea_chan = GameToPlay == GAME_NORMAL && PlayerPtr->ActLike == HOUSE_BAD && Scenario == 10 && ScenVar == SCEN_VAR_A && *infantry == INFANTRY_CHAN; + + if (!is_scb10ea_chan && infantry->Unlimbo(coord, dir)) { infantry->Strength = Fixed_To_Cardinal(infantry->Class_Of().MaxStrength, strength); if (GameToPlay == GAME_NORMAL || infantry->House->IsHuman) { infantry->Assign_Mission(mission); @@ -3181,6 +3197,11 @@ int InfantryClass::Made_A_Kill(void) void InfantryClass::Set_Occupy_Bit(CELL cell, int spot_index) { Validate(); + + if ((unsigned)cell >= MAP_CELL_TOTAL) { + return; + } + /* ** Set the occupy postion for the spot that we passed in */ @@ -3208,6 +3229,11 @@ void InfantryClass::Set_Occupy_Bit(CELL cell, int spot_index) void InfantryClass::Clear_Occupy_Bit(CELL cell, int spot_index) { Validate(); + + if ((unsigned)cell >= MAP_CELL_TOTAL) { + return; + } + /* ** Clear the occupy bit for the infantry in that cell */ diff --git a/TIBERIANDAWN/INI.CPP b/TIBERIANDAWN/INI.CPP index 25d95dc..1bfcad7 100644 --- a/TIBERIANDAWN/INI.CPP +++ b/TIBERIANDAWN/INI.CPP @@ -554,6 +554,11 @@ bool Read_Scenario_Ini(char *root, bool fresh) Map[(CELL)2015].Override_Land_Type(LAND_ROCK); } + /* + ** Scenario fix-up (applied on loaded games as well) + */ + Fixup_Scenario(); + /* ** Multi-player last-minute fixups: ** - If computer players are disabled, remove all computer-owned houses diff --git a/TIBERIANDAWN/IOMAP.CPP b/TIBERIANDAWN/IOMAP.CPP index bf8f4c0..b5bb117 100644 --- a/TIBERIANDAWN/IOMAP.CPP +++ b/TIBERIANDAWN/IOMAP.CPP @@ -218,6 +218,11 @@ void CellClass::Code_Pointers(void) if (IsTrigger) { CellTriggers[Cell_Number()] = (TriggerClass *)CellTriggers[Cell_Number()]->As_Target(); } + + /* + ------------------------ Convert flag pointer ------------------------- + */ + assert(CTFFlag == NULL); } @@ -299,6 +304,11 @@ void CellClass::Decode_Pointers(void) CellTriggers[Cell_Number()] = As_Trigger( (TARGET)CellTriggers[Cell_Number()], false ); Check_Ptr((void *)CellTriggers[Cell_Number()],__FILE__,__LINE__); } + + /* + ** Convert flag pointer. + */ + CTFFlag = NULL; } @@ -1012,6 +1022,13 @@ void MapClass::Code_Pointers(void) { CELL cell; + /* + ------------------------- Destroy all flag animations -------------------- + */ + for (cell = 0; cell < MAP_CELL_TOTAL; cell++) { + (*this)[cell].Flag_Destroy(); + } + /* ------------------------- Code the cell pointers ------------------------- */ diff --git a/TIBERIANDAWN/MAP.CPP b/TIBERIANDAWN/MAP.CPP index 1d1f853..bb789a5 100644 --- a/TIBERIANDAWN/MAP.CPP +++ b/TIBERIANDAWN/MAP.CPP @@ -980,7 +980,7 @@ void MapClass::Logic(void) if (Map.In_Radar(cell)) { FacingType offset = Random_Pick(FACING_N, FACING_NW); for (FacingType index = FACING_N; index < FACING_COUNT; index++) { - CellClass *newcell = &(*this)[cell].Adjacent_Cell(index+offset); + CellClass *newcell = (*this)[cell].Adjacent_Cell(index+offset); if (newcell && newcell->Cell_Object() == NULL && newcell->Land_Type() == LAND_CLEAR && newcell->Overlay == OVERLAY_NONE) { bool found = false; diff --git a/TIBERIANDAWN/MiscAsm.cpp b/TIBERIANDAWN/MiscAsm.cpp index e78ffc5..2aea545 100644 --- a/TIBERIANDAWN/MiscAsm.cpp +++ b/TIBERIANDAWN/MiscAsm.cpp @@ -428,7 +428,7 @@ dxisbig: #if (0) /* - ; $Header: //depot/Projects/Mobius/QA/Project/Run/SOURCECODE/TIBERIANDAWN/MiscAsm.cpp#33 $ + ; $Header: //depot/Projects/Mobius/QA/Project/Run/SOURCECODE/TIBERIANDAWN/MiscAsm.cpp#57 $ ;*************************************************************************** ;** C O N F I D E N T I A L --- W E S T W O O D A S S O C I A T E S ** ;*************************************************************************** diff --git a/TIBERIANDAWN/OBJECT.CPP b/TIBERIANDAWN/OBJECT.CPP index 989a268..ce137f0 100644 --- a/TIBERIANDAWN/OBJECT.CPP +++ b/TIBERIANDAWN/OBJECT.CPP @@ -489,6 +489,12 @@ bool ObjectClass::Can_Demolish_Unit(void) const } +bool ObjectClass::Can_Capture(void) const +{ + return(false); +} + + /*********************************************************************************************** * ObjectClass::Can_Player_Fire -- Can the player give this object an attack mission? * * * diff --git a/TIBERIANDAWN/OBJECT.H b/TIBERIANDAWN/OBJECT.H index 020e493..2bf51cb 100644 --- a/TIBERIANDAWN/OBJECT.H +++ b/TIBERIANDAWN/OBJECT.H @@ -159,6 +159,7 @@ class ObjectClass : public AbstractClass virtual bool Can_Repair(void) const; virtual bool Can_Demolish(void) const; virtual bool Can_Demolish_Unit(void) const; + virtual bool Can_Capture(void) const; virtual bool Can_Player_Fire(void) const; virtual bool Can_Player_Move(void) const; diff --git a/TIBERIANDAWN/OVERLAY.CPP b/TIBERIANDAWN/OVERLAY.CPP index 73a1c54..7ea4940 100644 --- a/TIBERIANDAWN/OVERLAY.CPP +++ b/TIBERIANDAWN/OVERLAY.CPP @@ -309,7 +309,8 @@ bool OverlayClass::Mark(MarkType mark) static FacingType _face[4] = {FACING_N, FACING_E, FACING_S, FACING_W}; for (int index = 0; index < (sizeof(_face)/sizeof(_face[0])); index++) { - cellptr->Adjacent_Cell(_face[index]).Concrete_Calc(); + CellClass * adjcell = cellptr->Adjacent_Cell(_face[index]); + if (adjcell) adjcell->Concrete_Calc(); } } } diff --git a/TIBERIANDAWN/SAVELOAD.CPP b/TIBERIANDAWN/SAVELOAD.CPP index 5b5c790..8f477c2 100644 --- a/TIBERIANDAWN/SAVELOAD.CPP +++ b/TIBERIANDAWN/SAVELOAD.CPP @@ -539,12 +539,14 @@ bool Load_Game(const char *file_name) Map.Init_IO(); Map.Flag_To_Redraw(true); + Fixup_Scenario(); + ScenarioInit = 0; /* ** Fixup remap tables. ST - 2/28/2020 1:50PM */ - for (HousesType house = HOUSE_MULTI1; house < HOUSE_COUNT; house++) { + for (HousesType house = HOUSE_FIRST; house < HOUSE_COUNT; house++) { HouseClass * hptr = HouseClass::As_Pointer(house); if (hptr && hptr->IsActive) { hptr->Init_Data(hptr->RemapColor, hptr->ActLike, hptr->Credits); diff --git a/TIBERIANDAWN/SCENARIO.CPP b/TIBERIANDAWN/SCENARIO.CPP index 8beaef1..f4742fa 100644 --- a/TIBERIANDAWN/SCENARIO.CPP +++ b/TIBERIANDAWN/SCENARIO.CPP @@ -138,16 +138,6 @@ bool Start_Scenario(char *root, bool briefing) } #endif - /* - ** Hack for laser-firing Orcas in the PATSUX secret mission - */ - if (GameToPlay == GAME_NORMAL && Scenario == 72) { - ((AircraftTypeClass&)AircraftTypeClass::As_Reference(AIRCRAFT_ORCA)).Primary = WEAPON_OBELISK_LASER; - } - else { - ((AircraftTypeClass&)AircraftTypeClass::As_Reference(AIRCRAFT_ORCA)).Primary = WEAPON_DRAGON; - } - /* ** Set the options values, since the palette has been initialized by Read_Scenario */ @@ -786,4 +776,27 @@ bool Restate_Mission(char const * name, int button1, int button2) #endif } return(false); +} + +void Fixup_Scenario(void) +{ + /* + ** "Fraidycat" civilians avoid wandering into Tiberium for SCG08EB, since they're mission-critical. + */ + bool is_scg08ea = GameToPlay == GAME_NORMAL && PlayerPtr->ActLike == HOUSE_GOOD && Scenario == 8 && ScenVar == SCEN_VAR_B; + for (InfantryType index = INFANTRY_FIRST; index < INFANTRY_COUNT; index++) { + InfantryTypeClass& infantry_type = (InfantryTypeClass&)InfantryTypeClass::As_Reference(index); + if (infantry_type.IsFraidyCat) { + infantry_type.IsAvoidingTiberium = is_scg08ea; + } + } + + /* + ** Laser-firing Orcas in the PATSUX secret mission + */ + if (GameToPlay == GAME_NORMAL && Scenario == 72) { + ((AircraftTypeClass&)AircraftTypeClass::As_Reference(AIRCRAFT_ORCA)).Primary = WEAPON_OBELISK_LASER; + } else { + ((AircraftTypeClass&)AircraftTypeClass::As_Reference(AIRCRAFT_ORCA)).Primary = WEAPON_DRAGON; + } } \ No newline at end of file diff --git a/TIBERIANDAWN/TECHNO.CPP b/TIBERIANDAWN/TECHNO.CPP index 0d27c96..87bf3cc 100644 --- a/TIBERIANDAWN/TECHNO.CPP +++ b/TIBERIANDAWN/TECHNO.CPP @@ -290,7 +290,15 @@ unsigned short TechnoTypeClass::Get_Ownable(void) const *=============================================================================================*/ int TechnoTypeClass::Time_To_Build(HousesType house) const { - int cost = Raw_Cost(); + /* + ** Base time is the raw cost. + */ + int time = Raw_Cost(); + + HouseClass* hptr = HouseClass::As_Pointer(house); + if (!hptr || !hptr->IsActive) { + return time; + } /* ** For computer controlled buildings, slow down production on @@ -299,9 +307,8 @@ int TechnoTypeClass::Time_To_Build(HousesType house) const if (PlayerPtr->Difficulty != DIFF_HARD && GameToPlay == GAME_NORMAL && What_Am_I() == RTTI_BUILDINGTYPE && - PlayerPtr->Class->House != house) { - - cost = (cost + (PlayerPtr->Difficulty == DIFF_EASY ? 4000 : 2000)) / 2; + PlayerPtr->Class->House != hptr->Class->House) { + time = (time + (PlayerPtr->Difficulty == DIFF_EASY ? 4000 : 2000)) / 2; } /* @@ -309,10 +316,35 @@ int TechnoTypeClass::Time_To_Build(HousesType house) const ** an airfield. */ if (What_Am_I() == RTTI_UNITTYPE && !(Ownable & HOUSEF_GOOD)) { - return (cost - (cost/4)); + time -= (time / 4); } - return(cost); + /* + ** Adjust time according to IQ setting of computer controlled house. The + ** build time will range from double normal time at the slowest to + ** just normal time at the fastest. + */ + if (!hptr->IsHuman && Rule.Diff[hptr->Difficulty].IsBuildSlowdown) { + time = (int)((time * Rule.MaxIQ * 2.0f) / (hptr->IQ + Rule.MaxIQ)); + } + + /* + ** Adjust the time to build based on the power output of the owning house. + */ + int power = hptr->Power_Fraction(); + int inv_scale(256); + if (power == 0x000) { + inv_scale = 64; + } + else if (power < 0x080) { + inv_scale = 103; + } + else if (power < 0x0100) { + inv_scale = 171; + } + time = (time * 256) / inv_scale; + + return(time); } @@ -988,7 +1020,9 @@ void TechnoClass::Draw_It(int x, int y, WindowNumberType window) { Clear_Redraw_Flag(); - const bool show_health_bar = (Strength > 0) && (Is_Selected_By_Player() || + const bool show_health_bar = + (Strength > 0) && ((Cloak != CLOAKED) || Is_Owned_By_Player()) && + (Is_Selected_By_Player() || ((Special.HealthBarDisplayMode == SpecialClass::HB_DAMAGED) && (Strength < Techno_Type_Class()->MaxStrength)) || (Special.HealthBarDisplayMode == SpecialClass::HB_ALWAYS)); @@ -1013,10 +1047,8 @@ void TechnoClass::Draw_It(int x, int y, WindowNumberType window) for (int i = start_line; i < LineCount; i++) { CC_Draw_Line(Lines[i][0], Lines[i][1], Lines[i][2], Lines[i][3], (unsigned char)Lines[i][4], LineFrame, window); } - if ((window == WINDOW_VIRTUAL) && (++LineFrame >= LineMaxFrames)) { - LineCount = 0; - LineFrame = 0; - LineMaxFrames = 0; + if (window == WINDOW_VIRTUAL) { + LineFrame++; } } @@ -1968,8 +2000,13 @@ void TechnoClass::AI(void) /* ** Handle line delay logic. */ - if ((LineMaxFrames > 0) && (LineCount > 0)) { + if (LineMaxFrames > 0) { Map.Flag_To_Redraw(true); + if (LineFrame >= LineMaxFrames) { + LineCount = 0; + LineFrame = 0; + LineMaxFrames = 0; + } } /* @@ -2249,6 +2286,9 @@ BulletClass * TechnoClass::Fire_At(TARGET target, int which) COORDINATE fire_coord; // Coordinate of firing position. TechnoTypeClass const & tclass = *Techno_Type_Class(); ObjectClass *object; + if ((which == 0 && tclass.Primary == WEAPON_NONE) || (which != 0 && tclass.Secondary == WEAPON_NONE)) { + return(NULL); + } WeaponTypeClass const *weapon = (which == 0) ? &Weapons[tclass.Primary] : &Weapons[tclass.Secondary]; BulletTypeClass const &btype = BulletTypeClass::As_Reference(weapon->Fires); @@ -2558,11 +2598,17 @@ ActionType TechnoClass::What_Action(ObjectClass * object) const //if (IsOwnedByPlayer && (ctrldown || !House->Is_Ally(object)) && (ctrldown || object->Class_Of().IsLegalTarget || (Special.IsTreeTarget && object->What_Am_I() == RTTI_TERRAIN))) { if (Can_Player_Move() || In_Range(object, 0)) { // Check for anti-air capability - if ((object->What_Am_I() != RTTI_AIRCRAFT) || - ((Techno_Type_Class()->Primary != WEAPON_NONE) && BulletTypeClass::As_Reference(Weapons[Techno_Type_Class()->Primary].Fires).IsAntiAircraft) || - ((Techno_Type_Class()->Secondary != WEAPON_NONE) && BulletTypeClass::As_Reference(Weapons[Techno_Type_Class()->Secondary].Fires).IsAntiAircraft) || - (((AircraftClass *)object)->Altitude == 0)) { + if (object->What_Am_I() != RTTI_AIRCRAFT) { return(ACTION_ATTACK); + } else { + AircraftClass* aircraft = (AircraftClass*)object; + if (*aircraft != AIRCRAFT_CARGO) { + if (((Techno_Type_Class()->Primary != WEAPON_NONE) && BulletTypeClass::As_Reference(Weapons[Techno_Type_Class()->Primary].Fires).IsAntiAircraft) || + ((Techno_Type_Class()->Secondary != WEAPON_NONE) && BulletTypeClass::As_Reference(Weapons[Techno_Type_Class()->Secondary].Fires).IsAntiAircraft) || + (aircraft->Altitude == 0)) { + return(ACTION_ATTACK); + } + } } } } diff --git a/TIBERIANDAWN/TYPE.H b/TIBERIANDAWN/TYPE.H index e90b454..5efab0e 100644 --- a/TIBERIANDAWN/TYPE.H +++ b/TIBERIANDAWN/TYPE.H @@ -1111,6 +1111,12 @@ class InfantryTypeClass : public TechnoTypeClass */ unsigned IsCivilian:1; + /* + ** For "fraidycat" infantry types that will run away from any damage causing + ** events, this controls whether they avoid wandering into Tiberium. + */ + unsigned IsAvoidingTiberium:1; + /* ** This value represents the unit class. It can serve as a unique ** identification number for this unit class. @@ -1642,7 +1648,7 @@ class AnimTypeClass : public ObjectTypeClass ** This is the normal loop count for this animation. Usually this is one, but ** for some animations, it may be larger. */ - unsigned char Loops; + char Loops; /* ** This is the sound effect to play when this animation starts. Usually, this diff --git a/TIBERIANDAWN/UNIT.CPP b/TIBERIANDAWN/UNIT.CPP index b6a1c44..8cf678d 100644 --- a/TIBERIANDAWN/UNIT.CPP +++ b/TIBERIANDAWN/UNIT.CPP @@ -583,6 +583,13 @@ RadioMessageType UnitClass::Receive_Message(RadioClass * from, RadioMessageType Validate(); switch (message) { + /* + ** Checks to see if this object is in need of service depot processing. + */ + case RADIO_NEED_REPAIR: + if (!IsDriving && !Target_Legal(NavCom) && Health_Ratio() >= 0x100) return(RADIO_NEGATIVE); + break; + /* ** Asks if the passenger can load on this transport. */ @@ -623,6 +630,38 @@ RadioMessageType UnitClass::Receive_Message(RadioClass * from, RadioMessageType ** to the impatient unit. */ case RADIO_DOCKING: + + /* + ** 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()) { + Transmit_Message(RADIO_HELLO, from); + } else { + + /* + ** This causes the potential passenger to think that all is ok and to + ** hold on for a bit. + */ + return(RADIO_ROGER); + } + } + if (Class->IsTransporter && *this == UNIT_APC && How_Many() < Class->Max_Passengers()) { TarComClass::Receive_Message(from, message, param); @@ -1215,6 +1254,16 @@ void UnitClass::Active_Click_With(ActionType action, ObjectClass * object) void UnitClass::Active_Click_With(ActionType action, CELL cell) {TarComClass::Active_Click_With(action, cell);}; +void UnitClass::Player_Assign_Mission(MissionType mission, TARGET target, TARGET destination) +{ + Validate(); + if (mission == MISSION_HARVEST) { + ArchiveTarget = TARGET_NONE; + } + TarComClass::Player_Assign_Mission(mission, target, destination); +} + + /*********************************************************************************************** * UnitClass::Enter_Idle_Mode -- Unit enters idle mode state. * * * @@ -1744,7 +1793,7 @@ void UnitClass::Per_Cell_Process(bool center) return; default: - Scatter(true); + Scatter(0, true); break; } } @@ -1755,28 +1804,39 @@ void UnitClass::Per_Cell_Process(bool center) ** When breaking away from a transport object or building, possibly ** scatter or otherwise begin normal unit operations. */ - if (IsTethered && center && Mission != MISSION_ENTER && (*this != UNIT_APC || IsDriving || !Target_Legal(TarCom))) { - TechnoClass * contact = Contact_With_Whom(); - if (Transmit_Message(RADIO_UNLOADED) == RADIO_RUN_AWAY) { - if (*this == UNIT_HARVESTER && contact && contact->What_Am_I() == RTTI_BUILDING) { - Assign_Mission(MISSION_HARVEST); - } else if (!Target_Legal(NavCom)) { - Scatter(0, true); - } + if (IsTethered && center && + (Mission != MISSION_ENTER || (As_Techno(NavCom) != NULL && Contact_With_Whom() != As_Techno(NavCom))) && + Mission != MISSION_UNLOAD) { + /* + ** Special hack check to make sure that even though it has moved one + ** cell, if it is still on the building (e.g., service depot), have + ** it scatter again. + */ + if (Map[cell].Cell_Building() != NULL && !Target_Legal(NavCom)) { + Scatter(0, true, true); } else { - if (*this == UNIT_HARVESTER) { - if (Target_Legal(ArchiveTarget)) { + TechnoClass * contact = Contact_With_Whom(); + if (Transmit_Message(RADIO_UNLOADED) == RADIO_RUN_AWAY) { + if (*this == UNIT_HARVESTER && contact && contact->What_Am_I() == RTTI_BUILDING) { Assign_Mission(MISSION_HARVEST); - Assign_Destination(ArchiveTarget); - ArchiveTarget = TARGET_NONE; - } else { + } else if (!Target_Legal(NavCom)) { + Scatter(0, true); + } + } else { + if (*this == UNIT_HARVESTER) { + if (Target_Legal(ArchiveTarget)) { + Assign_Mission(MISSION_HARVEST); + Assign_Destination(ArchiveTarget); + ArchiveTarget = TARGET_NONE; + } else { - /* - ** Since there is no place to go, move away to clear - ** the pad for another harvester. - */ - if (!Target_Legal(NavCom)) { - Scatter(0, true); + /* + ** Since there is no place to go, move away to clear + ** the pad for another harvester. + */ + if (!Target_Legal(NavCom)) { + Scatter(0, true); + } } } } @@ -2204,7 +2264,10 @@ void UnitClass::Draw_It(int x, int y, WindowNumberType window) ** If this unit is carrying the flag, then draw that on top of everything else. */ if (Flagged != HOUSE_NONE) { - CC_Draw_Shape(this, "FLAGFLY", MixFileClass::Retrieve("FLAGFLY.SHP"), Frame % 14, x, y, window, SHAPE_CENTER|SHAPE_FADING|SHAPE_GHOST, HouseClass::As_Pointer(Flagged)->Remap_Table(false, false), Map.UnitShadow, Flagged); + shapefile = MixFileClass::Retrieve("FLAGFLY.SHP"); + int flag_x = x + (ICON_PIXEL_W / 2) - 2; + int flag_y = y + (3 * ICON_PIXEL_H / 4) - Get_Build_Frame_Height(shapefile); + CC_Draw_Shape(this, "FLAGFLY", shapefile, Frame % 14, flag_x, flag_y, window, SHAPE_CENTER|SHAPE_FADING|SHAPE_GHOST, HouseClass::As_Pointer(Flagged)->Remap_Table(false, false), Map.UnitShadow, Flagged); } TarComClass::Draw_It(x, y, window); @@ -2221,34 +2284,34 @@ void UnitClass::Draw_It(int x, int y, WindowNumberType window) * * * INPUT: none * * * - * OUTPUT: bool; Is it located directly over a Tiberium patch? * + * OUTPUT: int; Amount of Tiberium at this location. * * * * WARNINGS: none * * * * HISTORY: * * 07/18/1994 JLB : Created. * *=============================================================================================*/ -bool UnitClass::Tiberium_Check(CELL ¢er, int x, int y) +int UnitClass::Tiberium_Check(CELL ¢er, int x, int y) { Validate(); /* ** If the specified offset from the origin will cause it ** to spill past the map edge, then abort this cell check. */ - if (Cell_X(center)+x < Map.MapCellX) return(false); - if (Cell_X(center)+x >= Map.MapCellX+Map.MapCellWidth) return(false); - if (Cell_Y(center)+y < Map.MapCellY) return(false); - if (Cell_Y(center)+y >= Map.MapCellY+Map.MapCellHeight) return(false); + if (Cell_X(center)+x < Map.MapCellX) return(0); + if (Cell_X(center)+x >= Map.MapCellX+Map.MapCellWidth) return(0); + if (Cell_Y(center)+y < Map.MapCellY) return(0); + if (Cell_Y(center)+y >= Map.MapCellY+Map.MapCellHeight) return(0); center = XY_Cell(Cell_X(center)+x, Cell_Y(center)+y); //using function for IsVisible so we have different results for different players - JAS 2019/09/30 if ((GameToPlay != GAME_NORMAL || (!IsOwnedByPlayer || Map[center].Is_Visible(PlayerPtr)))) { if (!Map[center].Cell_Techno() && Map[center].Land_Type() == LAND_TIBERIUM) { - return(true); + return(Map[center].OverlayData); } } - return(false); + return(0); } @@ -2265,32 +2328,42 @@ bool UnitClass::Goto_Tiberium(void) ** Perform a ring search outward from the center. */ for (int radius = 1; radius < 64; radius++) { + CELL cell = center; + CELL bestcell = 0; + int tiberium = 0; + int besttiberium = 0; for (int x = -radius; x <= radius; x++) { - CELL cell = center; - if (Tiberium_Check(cell, x, -radius)) { - Assign_Destination(::As_Target(cell)); - return(false); + tiberium = Tiberium_Check(cell, x, -radius); + if (tiberium > besttiberium) { + bestcell = cell; + besttiberium = tiberium; } cell = center; - if (Tiberium_Check(cell, x, +radius)) { - Assign_Destination(::As_Target(cell)); - return(false); + tiberium = Tiberium_Check(cell, x, +radius); + if (tiberium > besttiberium) { + bestcell = cell; + besttiberium = tiberium; } cell = center; - if (Tiberium_Check(cell, -radius, x)) { - Assign_Destination(::As_Target(cell)); - return(false); + tiberium = Tiberium_Check(cell, -radius, x); + if (tiberium > besttiberium) { + bestcell = cell; + besttiberium = tiberium; } - cell = center; - if (Tiberium_Check(cell, +radius, x)) { - Assign_Destination(::As_Target(cell)); - return(false); + tiberium = Tiberium_Check(cell, +radius, x); + if (tiberium > besttiberium) { + bestcell = cell; + besttiberium = tiberium; } } + if (bestcell) { + Assign_Destination(::As_Target(bestcell)); + return(false); + } } } } @@ -2589,6 +2662,7 @@ int UnitClass::Mission_Harvest(void) Set_Rate(2); Set_Stage(0); Status = HARVESTING; + ArchiveTarget = ::As_Target(Coord_Cell(Coord)); return(1); } else { @@ -2623,7 +2697,6 @@ int UnitClass::Mission_Harvest(void) IsHarvesting = false; if (Tiberium_Load() == 0x0100) { Status = FINDHOME; - ArchiveTarget = ::As_Target(Coord_Cell(Coord)); } else { if (!Goto_Tiberium() && !Target_Legal(NavCom)) { ArchiveTarget = TARGET_NONE; @@ -2634,6 +2707,8 @@ int UnitClass::Mission_Harvest(void) } } return(1); + } else if (!Target_Legal(NavCom) && ArchiveTarget == TARGET_NONE) { + ArchiveTarget = ::As_Target(Coord_Cell(Coord)); } break; @@ -2823,7 +2898,7 @@ short const * UnitClass::Overlap_List(void) const if (Is_Selected_By_Player() || IsFiring) { size += 24; } - if (Is_Selected_By_Player() || Class->IsGigundo || IsAnimAttached) { + if (Is_Selected_By_Player() || Class->IsGigundo || IsAnimAttached || Flagged != HOUSE_NONE) { size = ICON_PIXEL_W*2; } return(Coord_Spillage_List(Coord, size)+1); @@ -3245,7 +3320,11 @@ void UnitClass::Scatter(COORDINATE threat, bool forced, bool nokidding) { Validate(); if (*this != UNIT_GUNBOAT && *this != UNIT_HOVER) { - if (!PrimaryFacing.Is_Rotating() && ((!Target_Legal(TarCom) && !Target_Legal(NavCom)) || forced || nokidding || Random_Pick(1, 4) == 1)) { + if (PrimaryFacing.Is_Rotating()) return; + if (Target_Legal(NavCom) && !nokidding) return; + if (threat == 0) { + Assign_Destination(::As_Target(Map.Nearby_Location(Coord_Cell(Coord)))); + } else if (((!Target_Legal(TarCom) && !Target_Legal(NavCom)) || forced || nokidding || Random_Pick(1, 4) == 1)) { FacingType toface; FacingType newface; CELL newcell; @@ -3375,7 +3454,13 @@ bool UnitClass::Limbo(void) if (!IsInLimbo) { Stop_Driver(); } - return(TarComClass::Limbo()); + if (TarComClass::Limbo()) { + if (Flagged != HOUSE_NONE) { + HouseClass::As_Pointer(Flagged)->Flag_Attach(Coord_Cell(Coord)); + } + return(true); + } + return(false); } @@ -3543,16 +3628,14 @@ ActionType UnitClass::What_Action(ObjectClass * object) const /* ** Special return to friendly refinery action. */ - if (IsOwnedByPlayer && object->Is_Techno() && ((TechnoClass const *)object)->House->Is_Ally(this)) { - if (object->What_Am_I() == RTTI_BUILDING && ((UnitClass *)this)->Transmit_Message(RADIO_CAN_LOAD, (TechnoClass*)object) == RADIO_ROGER) { - action = ACTION_ENTER; - } + if (Is_Owned_By_Player() && House->Class->House == object->Owner() && object->What_Am_I() == RTTI_BUILDING && ((UnitClass *)this)->Transmit_Message(RADIO_CAN_LOAD, (TechnoClass*)object) == RADIO_ROGER) { + action = ACTION_ENTER; } /* ** Special return to friendly repair factory action. */ - if (IsOwnedByPlayer && action == ACTION_SELECT && object->What_Am_I() == RTTI_BUILDING) { + if (Is_Owned_By_Player() && House->Class->House == object->Owner() && action == ACTION_SELECT && object->What_Am_I() == RTTI_BUILDING) { BuildingClass * building = (BuildingClass *)object; if (building->Class->Type == STRUCT_REPAIR && !building->In_Radio_Contact() && !building->Is_Something_Attached()) { action = ACTION_MOVE; diff --git a/TIBERIANDAWN/UNIT.H b/TIBERIANDAWN/UNIT.H index 26f75f2..e16e56e 100644 --- a/TIBERIANDAWN/UNIT.H +++ b/TIBERIANDAWN/UNIT.H @@ -75,7 +75,7 @@ class UnitClass : public TarComClass bool Goto_Clear_Spot(void); bool Try_To_Deploy(void); - bool Tiberium_Check(CELL ¢er, int x, int y); + int Tiberium_Check(CELL ¢er, int x, int y); bool Flag_Attach(HousesType house); bool Flag_Remove(void); void Find_LZ(void); @@ -125,6 +125,7 @@ class UnitClass : public TarComClass virtual ActionType What_Action(ObjectClass * object) const; virtual void Active_Click_With(ActionType action, ObjectClass * object); virtual void Active_Click_With(ActionType action, CELL cell); + virtual void Player_Assign_Mission(MissionType mission, TARGET target=TARGET_NONE, TARGET destination=TARGET_NONE); virtual void Response_Select(void); virtual void Response_Move(void); virtual void Response_Attack(void); diff --git a/TIBERIANDAWN/WIN32LIB/DrawMisc.cpp b/TIBERIANDAWN/WIN32LIB/DrawMisc.cpp index 82cbede..05f9a52 100644 --- a/TIBERIANDAWN/WIN32LIB/DrawMisc.cpp +++ b/TIBERIANDAWN/WIN32LIB/DrawMisc.cpp @@ -4841,7 +4841,7 @@ extern "C" int __cdecl Confine_Rect ( int * x , int * y , int w , int h , int wi /* -; $Header: //depot/Projects/Mobius/QA/Project/Run/SOURCECODE/TIBERIANDAWN/WIN32LIB/DrawMisc.cpp#33 $ +; $Header: //depot/Projects/Mobius/QA/Project/Run/SOURCECODE/TIBERIANDAWN/WIN32LIB/DrawMisc.cpp#57 $ ;*************************************************************************** ;** C O N F I D E N T I A L --- W E S T W O O D A S S O C I A T E S ** ;*************************************************************************** diff --git a/TIBERIANDAWN/WIN32LIB/FACINGFF.h b/TIBERIANDAWN/WIN32LIB/FACINGFF.h index 9a14a60..9af5356 100644 --- a/TIBERIANDAWN/WIN32LIB/FACINGFF.h +++ b/TIBERIANDAWN/WIN32LIB/FACINGFF.h @@ -321,7 +321,7 @@ int __cdecl Desired_Facing8(long x1, long y1, long x2, long y2); /* - ; $Header: //depot/Projects/Mobius/QA/Project/Run/SOURCECODE/TIBERIANDAWN/WIN32LIB/FACINGFF.h#33 $ + ; $Header: //depot/Projects/Mobius/QA/Project/Run/SOURCECODE/TIBERIANDAWN/WIN32LIB/FACINGFF.h#57 $ ;*************************************************************************** ;** C O N F I D E N T I A L --- W E S T W O O D A S S O C I A T E S ** ;*************************************************************************** From 93a1af2eff3634f05c0e2ede19c2e5e72125ae95 Mon Sep 17 00:00:00 2001 From: PG-SteveT <63470275+PG-SteveT@users.noreply.github.com> Date: Wed, 24 Jun 2020 15:08:18 -0700 Subject: [PATCH 2/2] Create tgautil.py --- SCRIPTS/tgautil.py | 163 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 SCRIPTS/tgautil.py diff --git a/SCRIPTS/tgautil.py b/SCRIPTS/tgautil.py new file mode 100644 index 0000000..6b174ec --- /dev/null +++ b/SCRIPTS/tgautil.py @@ -0,0 +1,163 @@ +''' +Copyright 2020 Electronic Arts Inc. + +This program is 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 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 +''' +import argparse +import io +import json +from PIL import Image +import os +import StringIO +import sys +import zipfile + +def overwrite_prompt(question, default=False): + prompt = " [Y/n] " if default else " [y/N] " + while True: + sys.stdout.write(question + prompt) + choice = raw_input().lower() + if choice == '': + return default + elif choice == 'y': + return True + elif choice == 'n': + return False + else: + sys.stdout.write("\n") + +def crop(tga_file): + with Image.open(tga_file) as image: + image = image.convert('RGBA') + alpha = image.split()[-1] + left, top, right, bottom = 0, 0, image.width, image.height + found_left, found_top, found_right, found_bottom = False, False, False, False + for y in range(0, image.height): + if found_top and found_bottom: + break + for x in range(0, image.width): + if found_top and found_bottom: + break + if not found_top and alpha.getpixel((x, y)) != 0: + top = y + found_top = True + if not found_bottom and alpha.getpixel((x, image.height - y - 1)) != 0: + bottom = image.height - y + found_bottom = True + for x in range(0, image.width): + if found_left and found_right: + break + for y in range(top, bottom): + if found_left and found_right: + break + if not found_left and alpha.getpixel((x, y)) != 0: + left = x + found_left = True + if not found_right and alpha.getpixel((image.width - x - 1, y)) != 0: + right = image.width - x + found_right = True + tga_data = StringIO.StringIO() + meta = None + if left == 0 and top == 0 and right == image.width and bottom == image.height: + image.save(tga_data, 'TGA') + else: + image.crop((left, top, right, bottom)).save(tga_data, 'TGA') + meta = json.dumps({ + 'size': [image.width, image.height], + 'crop': [left, top, right, bottom] + }, separators=(',',':')) + return (tga_data.getvalue(), meta) + +def expand(tga_data, meta, tga_file): + with Image.open(io.BytesIO(tga_data)) as image: + if meta: + crop = meta['crop'] + image_size = (crop[2] - crop[0], crop[3] - crop[1]) + image = image.resize(image_size) + expanded_crop = (crop[0], crop[1], crop[2], crop[3]) + expanded_size = (meta['size'][0], meta['size'][1]) + with Image.new('RGBA', expanded_size, (0, 0, 0, 0)) as expanded: + expanded.paste(image, expanded_crop) + expanded.save(tga_file) + else: + image.save(tga_file) + +def zip(args): + if not os.path.isdir(args.directory): + print >> sys.stderr, '\'{}\' does not exist or is not a directory\n'.format(args.directory) + sys.exit(1) + tga_files = [f for f in os.listdir(args.directory) if os.path.isfile(os.path.join(args.directory, f)) and os.path.splitext(f)[1].lower() == '.tga'] + if not tga_files: + print >> sys.stderr, '\'{}\' does not contain any TGA files\n'.format(args.directory) + sys.exit(1) + out_file = os.path.basename(os.path.normpath(args.directory)).upper() + '.ZIP' + if os.path.exists(out_file): + if not os.path.isfile(out_file): + print >> sys.stderr, '\'{}\' already exists and is not a file\n'.format(out_file) + sys.exit(1) + if not args.yes and not overwrite_prompt('\'{}\' already exists, overwrite?'.format(out_file)): + sys.exit(0) + with zipfile.ZipFile(out_file, 'w', zipfile.ZIP_DEFLATED) as zip: + for tga_file in tga_files: + tga_data, meta = crop(os.path.join(args.directory, tga_file)) + zip.writestr(tga_file, tga_data) + if meta: + zip.writestr(os.path.splitext(tga_file)[0] + '.meta', meta) + print 'Wrote ZIP archive \'{}\''.format(out_file) + +def unzip(args): + if not os.path.isfile(args.archive): + print >> sys.stderr, '\'{}\' does not exist or is not a file\n'.format(args.archive) + sys.exit(1) + out_dir = os.path.normpath(os.path.splitext(args.archive)[0]) + if os.path.exists(out_dir): + if not os.path.isdir(out_dir): + print >> sys.stderr, '\'{}\' already exists and is not a directory\n'.format(out_dir) + sys.exit(1) + if len(os.listdir(out_dir)) > 0: + if not args.yes and not overwrite_prompt('\'{}\' is not empty, overwrite?'.format(out_dir)): + sys.exit(0) + else: + os.mkdir(out_dir) + files = {} + with zipfile.ZipFile(args.archive, 'r', zipfile.ZIP_DEFLATED) as zip: + for filename in zip.namelist(): + fileparts = os.path.splitext(filename) + name, ext = fileparts[0].lower(), fileparts[1].lower() + data = files.setdefault(name, {'tga': None, 'meta': None}) + if data['tga'] is None and ext == '.tga': + data['tga'] = zip.read(filename) + elif data['meta'] is None and ext == '.meta': + data['meta'] = json.loads(zip.read(filename).decode('ascii')) + if data['tga'] is not None and data['meta'] is not None: + expand(data['tga'], data['meta'], os.path.join(out_dir, name) + '.tga') + del files[name] + for name, data in files.items(): + expand(data['tga'], None, os.path.join(out_dir, name) + '.tga') + print 'Extracted files to \'{}\''.format(out_dir) + +parser = argparse.ArgumentParser(description='TGA archive utility.') +subparsers = parser.add_subparsers() + +parser_zip = subparsers.add_parser('z', help='Build a ZIP archive from a directory of TGA files.') +parser_zip.add_argument('directory', help='Directory of TGA files.') +parser_zip.add_argument('-o', '--out', nargs='?', help='Output archive path (defaults to input directory name with ZIP extension in the current path).') +parser_zip.add_argument('-y', '--yes', action='store_true', help='Confirm overwrite of existing ZIP archives.') +parser_zip.set_defaults(func=zip) + +parser_unzip = subparsers.add_parser('u', help='Extract a ZIP archive of TGA files to a directory.') +parser_unzip.add_argument('archive', help='ZIP archive of TGA files.') +parser_unzip.add_argument('-o', '--out', nargs='?', help='Output directory (defaults to directory with name of the ZIP archive in the current path).') +parser_unzip.add_argument('-y', '--yes', action='store_true', help='Confirm overwrite of files in output directory.') +parser_unzip.set_defaults(func=unzip) + +args = parser.parse_args() +args.func(args)