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/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) 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 ** ;***************************************************************************