diff --git a/CnCTDRAMapEditor.sln b/CnCTDRAMapEditor.sln new file mode 100644 index 0000000..e6bebce --- /dev/null +++ b/CnCTDRAMapEditor.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.1022 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CnCTDRAMapEditor", "CnCTDRAMapEditor\CnCTDRAMapEditor.csproj", "{397CEF00-8930-4EC8-B15F-F7CF7193FB22}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Gold|Any CPU = Gold|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {397CEF00-8930-4EC8-B15F-F7CF7193FB22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {397CEF00-8930-4EC8-B15F-F7CF7193FB22}.Debug|Any CPU.Build.0 = Debug|Any CPU + {397CEF00-8930-4EC8-B15F-F7CF7193FB22}.Gold|Any CPU.ActiveCfg = Gold|Any CPU + {397CEF00-8930-4EC8-B15F-F7CF7193FB22}.Gold|Any CPU.Build.0 = Gold|Any CPU + {397CEF00-8930-4EC8-B15F-F7CF7193FB22}.Release|Any CPU.ActiveCfg = Release|Any CPU + {397CEF00-8930-4EC8-B15F-F7CF7193FB22}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {3A313796-A0F0-4740-98D6-037699DBC902} + EndGlobalSection +EndGlobal diff --git a/REDALERT/ADATA.CPP b/REDALERT/ADATA.CPP index 0910798..e7f470c 100644 --- a/REDALERT/ADATA.CPP +++ b/REDALERT/ADATA.CPP @@ -2132,7 +2132,61 @@ static AnimTypeClass const Flag( 0, // Loop start frame number. -1, // Ending frame of loop back. -1, // Number of animation stages. - -1, // Number of times the animation loops. + -1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); + +static AnimTypeClass const Beacon( + ANIM_BEACON, // Animation number. + "MOVEFLSH", // Data name of animation. + 21, // Maximum dimension of animation. + 0, // Biggest animation stage. + true, // 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, + -1, // Virtual stages + 0x100, // Virtual scale + NULL, // Virtual name + ANIM_BEACON_VIRTUAL // Virtual anim +); + +static AnimTypeClass const BeaconVirtual( + ANIM_BEACON_VIRTUAL, // Animation number. + "BEACON", // 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 ); @@ -2450,6 +2504,7 @@ void AnimTypeClass::Init_Heap(void) new AnimTypeClass(ParaBomb); new AnimTypeClass(MineExp1); new AnimTypeClass(Flag); + new AnimTypeClass(Beacon); #ifdef FIXIT_ANTS new AnimTypeClass(Ant1Death); new AnimTypeClass(Ant2Death); @@ -2459,6 +2514,7 @@ void AnimTypeClass::Init_Heap(void) new AnimTypeClass(Fire2Virtual); new AnimTypeClass(Fire1Virtual); new AnimTypeClass(Fire4Virtual); + new AnimTypeClass(BeaconVirtual); } /*********************************************************************************************** @@ -2529,6 +2585,9 @@ void AnimTypeClass::Init(TheaterType theater) ((void const *&)anim.ImageData) = MFCD::Retrieve(fullname); } } + + // Set up beacon image data manually since they're new animations only available in the virtual renderer + ((void const *&)As_Reference(ANIM_BEACON_VIRTUAL).ImageData) = As_Reference(ANIM_BEACON).ImageData; } } diff --git a/REDALERT/ANIM.CPP b/REDALERT/ANIM.CPP index 4c96011..b505ab2 100644 --- a/REDALERT/ANIM.CPP +++ b/REDALERT/ANIM.CPP @@ -262,6 +262,8 @@ void AnimClass::Draw_It(int x, int y, WindowNumberType window) const void const * remap = NULL; ShapeFlags_Type flags = SHAPE_CENTER|SHAPE_WIN_REL; bool alt = false; + int width = 0; + int height = 0; /* ** Some animations require special fixups. @@ -277,6 +279,12 @@ void AnimClass::Draw_It(int x, int y, WindowNumberType window) const transtable = Map.UnitShadow; alt = true; break; + + case ANIM_BEACON_VIRTUAL: + width = 29; + height = 39; + flags = flags | SHAPE_BOTTOM | SHAPE_COMPACT; + break; } /* @@ -305,7 +313,7 @@ void AnimClass::Draw_It(int x, int y, WindowNumberType window) const */ if ((window == WINDOW_VIRTUAL) || (Fetch_Stage() < Class->Stages)) { // Add 'this' parameter to call new shape draw intercept. ST - 5/22/2019 - CC_Draw_Shape(this, shapefile, shapenum, x, y, window, flags, remap, transtable, DIR_N, Class->VirtualScale); + CC_Draw_Shape(this, shapefile, shapenum, x, y, window, flags, remap, transtable, DIR_N, Class->VirtualScale, width, height); } } IsTheaterShape = false; @@ -553,7 +561,8 @@ AnimClass::AnimClass(AnimType animnum, COORDINATE coord, unsigned char timedelay IsInvisible(false), Delay(timedelay), Accum(0), - AttachLayer(LAYER_NONE) + AttachLayer(LAYER_NONE), + KillTime(0ULL) { #ifdef VIC if (Class->Stages == -1) { @@ -741,6 +750,19 @@ void AnimClass::AI(void) } } + /* + ** Check the kill time. + */ + if (KillTime > 0ULL) { + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + + unsigned long long now = (unsigned long long)ft.dwLowDateTime + ((unsigned long long)ft.dwHighDateTime << 32ULL); + if (now >= KillTime) { + IsToDelete = true; + } + } + /* ** Delete this animation and bail early if the animation is flagged to be deleted ** immediately. @@ -1255,4 +1277,20 @@ void AnimClass::Do_Atom_Damage(HousesType ownerhouse, CELL cell) //GamePalette.Set(FADE_PALETTE_SLOW, Call_Back); //TO_FIX. ST 5/8/2019 } #endif -} \ No newline at end of file +} + +void AnimClass::Set_Owner(HousesType owner) +{ + OwnerHouse = owner; + if (Target_Legal(VirtualAnimTarget)) { + As_Animation(VirtualAnimTarget)->Set_Owner(owner); + } +} + +void AnimClass::Set_Visible_Flags(unsigned flags) +{ + VisibleFlags = flags; + if (Target_Legal(VirtualAnimTarget)) { + As_Animation(VirtualAnimTarget)->Set_Visible_Flags(flags); + } +} diff --git a/REDALERT/ANIM.H b/REDALERT/ANIM.H index 2dc3390..a1c1c6b 100644 --- a/REDALERT/ANIM.H +++ b/REDALERT/ANIM.H @@ -70,15 +70,17 @@ class AnimClass : public ObjectClass, public StageClass { void Sort_Above(TARGET target); void Make_Invisible(void) {IsInvisible = true;}; void Make_Visible(void) {IsInvisible = false;}; + void Kill_At(unsigned long long kill_time) {KillTime = kill_time;} static void Do_Atom_Damage(HousesType ownerhouse, CELL cell); /* ** 2019/09/19 JAS ** Added functions for accessing which players can see this anim */ - void Set_Visible_Flags(unsigned flags) { VisibleFlags = flags; } + void Set_Visible_Flags(unsigned flags); unsigned Get_Visible_Flags() const { return (Delay == 0) ? VisibleFlags : 0; } + virtual void Set_Owner(HousesType owner); virtual HousesType Owner(void) const {return OwnerHouse;}; virtual bool Can_Place_Here(COORDINATE ) const {return true;} virtual bool Mark(MarkType mark=MARK_CHANGE); @@ -179,10 +181,15 @@ class AnimClass : public ObjectClass, public StageClass { */ TARGET VirtualAnimTarget; + /* + ** Real-time point to kill this animation. + */ + unsigned long long KillTime; + /* ** 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[24]; }; diff --git a/REDALERT/AUDIO.CPP b/REDALERT/AUDIO.CPP index 22e55c2..864074b 100644 --- a/REDALERT/AUDIO.CPP +++ b/REDALERT/AUDIO.CPP @@ -281,6 +281,8 @@ struct SoundEffectNameStruct { {"MADEXPLO", 20, IN_NOVAR}, // VOC_MAD_EXPLODE MAD tank explodes {"SHKTROP1", 20, IN_NOVAR}, // VOC_SHOCK_TROOP1 Shock Trooper fires + {"BEACON", 10, IN_NOVAR}, // VOC_BEACON Beacon sound. + #endif }; diff --git a/REDALERT/BUILDING.CPP b/REDALERT/BUILDING.CPP index 7f0481e..9c7c6eb 100644 --- a/REDALERT/BUILDING.CPP +++ b/REDALERT/BUILDING.CPP @@ -1850,7 +1850,7 @@ void BuildingClass::Active_Click_With(ActionType action, CELL cell) OutList.Add(EventClass(EventClass::SELL, TargetClass(this))); COORDINATE coord = Map.Pixel_To_Coord(Get_Mouse_X(), Get_Mouse_Y()); - OutList.Add(EventClass(ANIM_MOVE_FLASH, PlayerPtr->Class->House, coord)); + OutList.Add(EventClass(ANIM_MOVE_FLASH, PlayerPtr->Class->House, coord, 1 << PlayerPtr->Class->House)); } } @@ -3152,7 +3152,7 @@ bool BuildingClass::Captured(HouseClass * newowner) assert(Buildings.ID(this) == ID); assert(IsActive); - if (Class->IsCaptureable && newowner != House) { + if (Can_Capture() && newowner != House) { #ifdef TOFIX switch (Owner()) { case HOUSE_GOOD: @@ -3457,6 +3457,19 @@ bool BuildingClass::Can_Demolish_Unit(void) const } +bool BuildingClass::Can_Capture(void) const +{ + bool can_capture = Class->IsCaptureable && Mission != MISSION_DECONSTRUCTION; + + // Only allow capturing of multiplayer-owned structures + if (Session.Type != GAME_NORMAL) { + can_capture &= House->Class->House >= HOUSE_MULTI1 && House->Class->House <= HOUSE_MULTI8; + } + + return(can_capture); +} + + /*********************************************************************************************** * BuildingClass::Mission_Guard -- Handles guard mission for combat buildings. * * * @@ -3759,6 +3772,7 @@ int BuildingClass::Mission_Deconstruction(void) Status = DURING; Begin_Mode(BSTATE_CONSTRUCTION); IsReadyToCommence = false; + IsSurvivorless = true; break; } Transmit_Message(RADIO_RUN_AWAY); diff --git a/REDALERT/BUILDING.H b/REDALERT/BUILDING.H index f0f991a..1385d10 100644 --- a/REDALERT/BUILDING.H +++ b/REDALERT/BUILDING.H @@ -246,6 +246,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 DirType Fire_Direction(void) const; virtual short const * Overlap_List(bool redraw=false) const; diff --git a/REDALERT/BULLET.CPP b/REDALERT/BULLET.CPP index 06e2c19..a97715d 100644 --- a/REDALERT/BULLET.CPP +++ b/REDALERT/BULLET.CPP @@ -1075,7 +1075,7 @@ void BulletClass::Bullet_Explodes(bool forced) // else if (aptr && anim == ANIM_ATOM_BLAST && aptr->OwnerHouse == HOUSE_NONE) { if (Payback && Payback->House && Payback->House->Class) { - aptr->OwnerHouse = Payback->House->Class->House; + aptr->Set_Owner(Payback->House->Class->House); } } } diff --git a/REDALERT/CELL.CPP b/REDALERT/CELL.CPP index 1060d7c..6a26deb 100644 --- a/REDALERT/CELL.CPP +++ b/REDALERT/CELL.CPP @@ -2818,7 +2818,7 @@ void CellClass::Flag_Create(void) CTFFlag = new AnimClass(ANIM_FLAG, Cell_Coord()); } assert(CTFFlag != NULL); - CTFFlag->OwnerHouse = Owner; + CTFFlag->Set_Owner(Owner); } } diff --git a/REDALERT/CONQUER.CPP b/REDALERT/CONQUER.CPP index 4ba6cd2..d3854b8 100644 --- a/REDALERT/CONQUER.CPP +++ b/REDALERT/CONQUER.CPP @@ -3430,11 +3430,11 @@ extern void DLL_Draw_Intercept(int shape_number, int x, int y, int width, int he extern void DLL_Draw_Pip_Intercept(const ObjectClass* object, int pip); void CC_Draw_Shape(void const * shapefile, int shapenum, int x, int y, WindowNumberType window, ShapeFlags_Type flags, void const * fadingdata, void const * ghostdata, DirType rotation); -void CC_Draw_Shape(const ObjectClass *object, void const * shapefile, int shapenum, int x, int y, WindowNumberType window, ShapeFlags_Type flags, void const * fadingdata, void const * ghostdata, DirType rotation, long virtualscale) +void CC_Draw_Shape(const ObjectClass *object, void const * shapefile, int shapenum, int x, int y, WindowNumberType window, ShapeFlags_Type flags, void const * fadingdata, void const * ghostdata, DirType rotation, long virtualscale, int width, int height) { if (window == WINDOW_VIRTUAL) { - int width = Get_Build_Frame_Width(shapefile); - int height = Get_Build_Frame_Height(shapefile); + if (width == 0) width = Get_Build_Frame_Width(shapefile); + if (height == 0) height = Get_Build_Frame_Height(shapefile); DLL_Draw_Intercept(shapenum, x, y, width, height, (int)flags, object, rotation, virtualscale, NULL, HOUSE_NONE); return; } diff --git a/REDALERT/DEFINES.H b/REDALERT/DEFINES.H index 5e3f9cb..7b941a8 100644 --- a/REDALERT/DEFINES.H +++ b/REDALERT/DEFINES.H @@ -2324,6 +2324,7 @@ typedef enum AnimType : char { ANIM_PARA_BOMB, ANIM_MINE_EXP1, ANIM_FLAG, + ANIM_BEACON, #ifdef FIXIT_ANTS ANIM_ANT1_DEATH, @@ -2335,6 +2336,7 @@ typedef enum AnimType : char { ANIM_FIRE_MED_VIRTUAL, // Medium flame animation. ANIM_FIRE_MED2_VIRTUAL, // Medium flame animation (oranger). ANIM_FIRE_TINY_VIRTUAL, // Very tiny flames. + ANIM_BEACON_VIRTUAL, // Beacon (virtual). ANIM_COUNT, ANIM_FIRST=0 @@ -3333,6 +3335,8 @@ typedef enum VocType : short { VOC_MAD_EXPLODE, VOC_SHOCK_TROOP1, + VOC_BEACON, + #endif VOC_COUNT, VOC_FIRST=0 diff --git a/REDALERT/DISPLAY.CPP b/REDALERT/DISPLAY.CPP index 95df5e4..c4798cc 100644 --- a/REDALERT/DISPLAY.CPP +++ b/REDALERT/DISPLAY.CPP @@ -996,7 +996,7 @@ void DisplayClass::Cursor_Mark(CELL pos, bool on) CELL const * ptr; CellClass * cellptr; - if (pos == -1) return; + if ((unsigned)pos >= MAP_CELL_TOTAL) return; /* ** For every cell in the CursorSize list, invoke its Redraw_Objects and @@ -2916,6 +2916,7 @@ void DisplayClass::Select_These(COORDINATE coord1, COORDINATE coord2, bool addit HouseClass * hptr = HouseClass::As_Pointer(obj->Owner()); if ( obj->Class_Of().IsSelectable && obj->What_Am_I() != RTTI_BUILDING && + (!obj->Is_Techno() || !((TechnoClass*)obj)->Is_Cloaked(PlayerPtr)) && x >= x1 && x <= x2 && y >= y1 && y <= y2) { bool old_allow_voice = AllowVoice; bool is_player_controlled = (hptr != NULL) && hptr->IsPlayerControl; @@ -2942,6 +2943,7 @@ void DisplayClass::Select_These(COORDINATE coord1, COORDINATE coord2, bool addit ** Only try to select objects that are allowed to be selected, and are within the bounding box. */ if ( aircraft->Class->IsSelectable && + !aircraft->Is_Cloaked(PlayerPtr) && !aircraft->Is_Selected_By_Player() && x >= x1 && x <= x2 && y >= y1 && y <= y2) { bool old_allow_voice = AllowVoice; diff --git a/REDALERT/DLLInterface.cpp b/REDALERT/DLLInterface.cpp index d0ead4d..d1481d4 100644 --- a/REDALERT/DLLInterface.cpp +++ b/REDALERT/DLLInterface.cpp @@ -141,6 +141,7 @@ extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Sidebar_Request(Sidebar extern "C" __declspec(dllexport) void __cdecl CNC_Handle_SuperWeapon_Request(SuperWeaponRequestEnum request_type, uint64 player_id, int buildable_type, int buildable_id, int x1, int y1); extern "C" __declspec(dllexport) void __cdecl CNC_Handle_ControlGroup_Request(ControlGroupRequestEnum request_type, uint64 player_id, unsigned char control_group_index); extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Debug_Request(DebugRequestEnum debug_request_type, uint64 player_id, const char *object_name, int x, int y, bool unshroud, bool enemy); +extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Beacon_Request(BeaconRequestEnum beacon_request_type, uint64 player_id, int pixel_x, int pixel_y); extern "C" __declspec(dllexport) bool __cdecl CNC_Set_Multiplayer_Data(int scenario_index, CNCMultiplayerOptionsStruct &game_options, int num_players, CNCPlayerInfoStruct *player_list, int max_players); extern "C" __declspec(dllexport) bool __cdecl CNC_Clear_Object_Selection(uint64 player_id); extern "C" __declspec(dllexport) bool __cdecl CNC_Select_Object(uint64 player_id, int object_type_id, int object_to_select_id); @@ -150,6 +151,7 @@ extern "C" __declspec(dllexport) void __cdecl CNC_Restore_Carryover_Objects(cons extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Player_Switch_To_AI(uint64 player_id); extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Human_Team_Wins(uint64 player_id); extern "C" __declspec(dllexport) void __cdecl CNC_Start_Mission_Timer(int time); +extern "C" __declspec(dllexport) bool __cdecl CNC_Get_Start_Game_Info(uint64 player_id, int &start_location_waypoint_index); @@ -418,14 +420,13 @@ void Display_Briefing_Text_GlyphX() void On_Sound_Effect(int sound_index, int variation, COORDINATE coord, int house) { - // MBL 06.17.2019 int voc = sound_index; if (voc == VOC_NONE) { return; } - // MBL 06.17.2019 - Borrowed from RedAlert\AUDIO.CPP Sound_Effect() + // Borrowed from RedAlert\AUDIO.CPP Sound_Effect() // #if 1 /* @@ -487,14 +488,13 @@ void On_Sound_Effect(int sound_index, int variation, COORDINATE coord, int house #if 0 - // MBL 02.26.2019 int voc = sound_index; if (voc == VOC_NONE) { return; } - // MBL 02.26.2019 - Borrowed from AUDIO.CPP Sound_Effect() + // Borrowed from AUDIO.CPP Sound_Effect() // char const * ext = ""; // ".AUD"; #ifdef TIBERIAN_DAWN @@ -531,10 +531,8 @@ void On_Sound_Effect(int sound_index, int variation, COORDINATE coord, int house } -// void On_Speech(int speech_index) // MBL 02.06.2020 void On_Speech(int speech_index, HouseClass *house) { - // DLLExportClass::On_Speech(PlayerPtr, speech_index); // MBL 02.06.2020 if (house == NULL) { DLLExportClass::On_Speech(PlayerPtr, speech_index); } @@ -622,7 +620,6 @@ extern "C" __declspec(dllexport) void __cdecl CNC_Init(const char *command_line, DLL_Startup(command_line); - // MBL DLLExportClass::Set_Event_Callback( event_callback ); DLLExportClass::Init(); @@ -1034,7 +1031,7 @@ void GlyphX_Assign_Houses(void) UseGlyphXStartLocations = true; } } - if (!preassigned) { + if (!preassigned && i < MAX_PLAYERS) { random_start_locations[num_random_start_locations] = num_start_locations; num_random_start_locations++; } @@ -1943,13 +1940,13 @@ extern "C" __declspec(dllexport) bool __cdecl CNC_Save_Load(bool save, const cha result = Load_Game(file_path_and_name); - // MBL 07.21.2020 if (result == false) { return false; } DLLExportClass::Set_Player_Context(DLLExportClass::GlyphxPlayerIDs[0], true); + DLLExportClass::Cancel_Placement(DLLExportClass::GlyphxPlayerIDs[0], -1, -1); Set_Logic_Page(SeenBuff); VisiblePage.Clear(); Map.Flag_To_Redraw(true); @@ -2148,6 +2145,40 @@ extern "C" __declspec(dllexport) void __cdecl CNC_Start_Mission_Timer(int time) } +/************************************************************************************************** +* CNC_Get_Start_Game_Info +* +* History: 8/31/2020 11:37AM - ST +**************************************************************************************************/ +extern "C" __declspec(dllexport) bool __cdecl CNC_Get_Start_Game_Info(uint64 player_id, int &start_location_waypoint_index) +{ + start_location_waypoint_index = 0; + if (!DLLExportClass::Set_Player_Context(player_id)) { + return false; + } + + start_location_waypoint_index = PlayerPtr->StartLocationOverride; + return true; +} + + +/************************************************************************************************** +* Is_Legacy_Render_Enabled -- Is the legacy rendering enabled? +* +* In: +* +* Out: True if only one human player +* +* +* +* History: 8/25/2020 5:55PM - ST +**************************************************************************************************/ +bool Is_Legacy_Render_Enabled(void) +{ + return DLLExportClass::Legacy_Render_Enabled(); +} + + /************************************************************************************************** * DLLExportClass::Init -- Init the class * @@ -4096,6 +4127,31 @@ extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Input(InputRequestEnum break; } + // MBL 09.08.2020 - Mod Support + case INPUT_REQUEST_MOD_GAME_COMMAND_1_AT_POSITION: + case INPUT_REQUEST_MOD_GAME_COMMAND_2_AT_POSITION: + case INPUT_REQUEST_MOD_GAME_COMMAND_3_AT_POSITION: + case INPUT_REQUEST_MOD_GAME_COMMAND_4_AT_POSITION: + { + DLLExportClass::Adjust_Internal_View(); + DLLForceMouseX = x1; + DLLForceMouseY = y1; + Keyboard->MouseQX = x1; + Keyboard->MouseQY = y1; + + COORDINATE coord = Map.Pixel_To_Coord(x1, y1); + CELL cell = Coord_Cell(coord); + + if (Map.Pixel_To_Coord(x1, y1)) + { + // TBD: For our ever-awesome Community Modders! + // + // PlayerPtr->Handle_Mod_Game_Command(cell, input_event - INPUT_REQUEST_MOD_GAME_COMMAND_1_AT_POSITION); + } + + break; + } + default: break; } @@ -4220,7 +4276,7 @@ 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 + // 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: @@ -4461,7 +4517,7 @@ bool DLLExportClass::Get_Sidebar_State(uint64 player_id, unsigned char *buffer_i sidebar_entry.SuperWeaponType = SW_NONE; if (tech) { - sidebar_entry.Cost = tech->Cost * PlayerPtr->CostBias; // MBL: If this gets modified, also modify below for skirmish and multiplayer + sidebar_entry.Cost = tech->Cost * PlayerPtr->CostBias; // If this gets modified, also modify below for skirmish and multiplayer sidebar_entry.PowerProvided = 0; 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); @@ -4619,7 +4675,7 @@ bool DLLExportClass::Get_Sidebar_State(uint64 player_id, unsigned char *buffer_i if (tech) { - // MBL 06.22.2020 - Updated to apply and difficulty abd/or faction price modifier; See https://jaas.ea.com/browse/TDRA-6864 + // Updated to apply and difficulty abd/or faction price modifier; See https://jaas.ea.com/browse/TDRA-6864 // If this gets modified, also modify above for non-skirmish / non-multiplayer // // sidebar_entry.Cost = tech->Cost; @@ -6014,6 +6070,22 @@ bool DLLExportClass::Get_Shroud_State(uint64 player_id, unsigned char *buffer_in return false; } + /* + ** Apply mobile gap generators + */ + static unsigned int _shroud_bits[UNIT_MAX]; + + if (GAME_TO_PLAY == GAME_GLYPHX_MULTIPLAYER) { + for (int index = 0; index < Units.Count(); index++) { + UnitClass * obj = Units.Ptr(index); + if (obj->Class->IsGapper && obj->IsActive && obj->Strength) { + if (!obj->House->Is_Ally(PlayerPtr)) { + _shroud_bits[index] = obj->Apply_Temporary_Jamming_Shroud(PlayerPtr); + } + } + } + } + CNCShroudStruct *shroud = (CNCShroudStruct*) buffer_in; unsigned int memory_needed = sizeof(*shroud) + 256; // Base amount needed. Will need more depending on how many entries there are @@ -6087,6 +6159,17 @@ bool DLLExportClass::Get_Shroud_State(uint64 player_id, unsigned char *buffer_in shroud->Count = entry_index; + if (GAME_TO_PLAY == GAME_GLYPHX_MULTIPLAYER) { + for (int index = 0; index < Units.Count(); index++) { + UnitClass * obj = Units.Ptr(index); + if (obj->Class->IsGapper && obj->IsActive && obj->Strength) { + if (!obj->House->Is_Ally(PlayerPtr)) { + obj->Unapply_Temporary_Jamming_Shroud(PlayerPtr, _shroud_bits[index]); + } + } + } + } + return true; } @@ -7551,7 +7634,7 @@ void DLLExportClass::Team_Units_Formation_Toggle_On(uint64 player_id) } // - // MBL 03.23.2020: Code here copied and modified from Toggle_Formation(), since obj->IsSelected is not supported + // Code here copied and modified from Toggle_Formation(), since obj->IsSelected is not supported // Replacing with ObjectClass::Is_Selected_By_Player(HouseClass *player); // @@ -7847,7 +7930,37 @@ extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Debug_Request(DebugRequ } +extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Beacon_Request(BeaconRequestEnum beacon_request_type, uint64 player_id, int pixel_x, int pixel_y) +{ + if (!DLLExportClass::Set_Player_Context(player_id)) { + return; + } + // Beacons are only available if legacy rendering is disabled + if (DLLExportClass::Legacy_Render_Enabled()) { + return; + } + + // Only allow one beacon per player + for (int index = 0; index < Anims.Count(); ++index) { + AnimClass* anim = Anims.Ptr(index); + if (anim != NULL && + (*anim == ANIM_BEACON || *anim == ANIM_BEACON_VIRTUAL) && + anim->OwnerHouse == PlayerPtr->Class->House) { + delete anim; + } + } + + OutList.Add(EventClass(ANIM_BEACON, PlayerPtr->Class->House, Map.Pixel_To_Coord(pixel_x, pixel_y), PlayerPtr->Get_Allies())); + + // Send sound effect to allies + for (int index = 0; index < Houses.Count(); ++index) { + HouseClass* hptr = Houses.Ptr(index); + if (hptr != NULL && hptr->IsActive && hptr->IsHuman && PlayerPtr->Is_Ally(hptr)) { + DLLExportClass::On_Sound_Effect(hptr, VOC_BEACON, "", 0, 0); + } + } +} diff --git a/REDALERT/DLLInterface.h b/REDALERT/DLLInterface.h index a9e643d..104557a 100644 --- a/REDALERT/DLLInterface.h +++ b/REDALERT/DLLInterface.h @@ -29,7 +29,7 @@ struct CarryoverObjectStruct; ** ** */ -#define CNC_DLL_API_VERSION 0x101 +#include "DLLInterfaceVersion.h" @@ -425,7 +425,11 @@ enum InputRequestEnum { INPUT_REQUEST_SELL_AT_POSITION, INPUT_REQUEST_SELECT_AT_POSITION, INPUT_REQUEST_COMMAND_AT_POSITION, - INPUT_REQUEST_SPECIAL_KEYS + INPUT_REQUEST_SPECIAL_KEYS, + INPUT_REQUEST_MOD_GAME_COMMAND_1_AT_POSITION, + INPUT_REQUEST_MOD_GAME_COMMAND_2_AT_POSITION, + INPUT_REQUEST_MOD_GAME_COMMAND_3_AT_POSITION, + INPUT_REQUEST_MOD_GAME_COMMAND_4_AT_POSITION, }; @@ -476,6 +480,18 @@ enum GameRequestEnum { }; +/************************************************************************************** +** +** Beacon Requests +** +** +*/ +enum BeaconRequestEnum { + INPUT_BEACON_NONE, + INPUT_BEACON_PLACE, +}; + + /************************************************************************************** ** ** Special Keys diff --git a/REDALERT/DLLInterfaceVersion.h b/REDALERT/DLLInterfaceVersion.h new file mode 100644 index 0000000..b05b742 --- /dev/null +++ b/REDALERT/DLLInterfaceVersion.h @@ -0,0 +1,33 @@ +// +// Copyright 2020 Electronic Arts Inc. +// +// TiberianDawn.DLL and RedAlert.dll and corresponding source code is free +// software: you can redistribute it and/or modify it under the terms of +// the GNU General Public License as published by the Free Software Foundation, +// either version 3 of the License, or (at your option) any later version. + +// TiberianDawn.DLL and RedAlert.dll and corresponding source code is distributed +// in the hope that it will be useful, but with permitted additional restrictions +// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT +// distributed with this program. You should have received a copy of the +// GNU General Public License along with permitted additional restrictions +// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection + + +#pragma once + +#ifndef DLL_INTERFACE_VERSION_H +#define DLL_INTERFACE_VERSION_H + + +/* +** DLL Interface version +** +** +** +*/ +#define CNC_DLL_API_VERSION 0x102 + + + +#endif //DLL_INTERFACE_VERSION_H diff --git a/REDALERT/EVENT.CPP b/REDALERT/EVENT.CPP index 9e84f7d..fe48e57 100644 --- a/REDALERT/EVENT.CPP +++ b/REDALERT/EVENT.CPP @@ -282,7 +282,7 @@ EventClass::EventClass(EventType type, TargetClass src, TargetClass dest) * HISTORY: * * 05/19/1995 JLB : Created. * *=============================================================================================*/ -EventClass::EventClass(AnimType anim, HousesType owner, COORDINATE coord) +EventClass::EventClass(AnimType anim, HousesType owner, COORDINATE coord, int visible) { ID = PlayerPtr->ID; Type = ANIMATION; @@ -290,6 +290,7 @@ EventClass::EventClass(AnimType anim, HousesType owner, COORDINATE coord) Data.Anim.What = anim; Data.Anim.Owner = owner; Data.Anim.Where = coord; + Data.Anim.Visible = visible; } @@ -600,14 +601,17 @@ void EventClass::Execute(void) case ANIMATION: anim = new AnimClass(Data.Anim.What, Data.Anim.Where); if (anim) { - //2019/09/19 JAS - Visibility needs to be determined per player - if (Data.Anim.Owner == HOUSE_NONE || Data.Anim.What != ANIM_MOVE_FLASH) - { - anim->Set_Visible_Flags(static_cast(-1)); - } - else - { - anim->Set_Visible_Flags(1 << Data.Anim.Owner); + anim->Set_Owner(Data.Anim.Owner); + anim->Set_Visible_Flags(static_cast(Data.Anim.Visible)); + /* + ** Beacons have a 30-second kill time. + */ + if (Data.Anim.What == ANIM_BEACON) { + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + + unsigned long long kill_time = ((unsigned long long)ft.dwLowDateTime + ((unsigned long long)ft.dwHighDateTime << 32ULL)) + 300000000ULL; + anim->Kill_At(kill_time); } } break; diff --git a/REDALERT/EVENT.H b/REDALERT/EVENT.H index 647bff6..21af244 100644 --- a/REDALERT/EVENT.H +++ b/REDALERT/EVENT.H @@ -136,6 +136,7 @@ class EventClass AnimType What; // The animation to create. HousesType Owner; // The owner of the animation (when it matters). COORDINATE Where; // The location to place the animation. + int Visible; // Who this animation is visible to. } Anim; struct { int Value; // general-purpose data @@ -239,7 +240,7 @@ class EventClass EventClass(EventType type, RTTIType object, int id); EventClass(EventType type, RTTIType object, CELL cell); EventClass(EventType type, int id, CELL cell); - EventClass(AnimType anim, HousesType owner, COORDINATE coord); + EventClass(AnimType anim, HousesType owner, COORDINATE coord, int visible = -1); EventClass(void *ptr, unsigned long size); EventClass(EventType type, void *ptr, unsigned long size); diff --git a/REDALERT/FOOT.CPP b/REDALERT/FOOT.CPP index dc2353b..3e813c6 100644 --- a/REDALERT/FOOT.CPP +++ b/REDALERT/FOOT.CPP @@ -946,8 +946,12 @@ void FootClass::Approach_Target(void) /* ** If a suitable intermediate location was found, then head toward it. ** Otherwise, head toward the enemy unit directly. + ** Infantry always head towards the target since they can enter a cell + ** in range, but still not be able to hit the target if the spot is out of range. */ - if (found) { + if (What_Am_I() == RTTI_INFANTRY) { + Assign_Destination(TarCom); + } else if (found) { Assign_Destination(::As_Target(trycell)); } else { @@ -1005,6 +1009,20 @@ int FootClass::Mission_Guard_Area(void) ArchiveTarget = ::As_Target(Coord); } + /* + ** Ensure units aren't trying to guard cells off the map. + */ + if (Target_Legal(NavCom) && Is_Target_Cell(NavCom)) { + CELL cell = As_Cell(NavCom); + int x = Cell_X(cell); + int y = Cell_Y(cell); + if (x < Map.MapCellX || y < Map.MapCellY || x >= (Map.MapCellX + Map.MapCellWidth) || y >= (Map.MapCellY + Map.MapCellHeight)) { + Assign_Target(TARGET_NONE); + Assign_Destination(TARGET_NONE); + ArchiveTarget = ::As_Target(Coord); + } + } + /* ** If this is a bomber type infantry and the current target is a building, then go into ** sabotage mode if not already. @@ -1311,7 +1329,7 @@ void FootClass::Active_Click_With(ActionType action, CELL cell) case ACTION_MOVE: if (AllowVoice) { COORDINATE coord = Map.Pixel_To_Coord(Get_Mouse_X(), Get_Mouse_Y()); - OutList.Add(EventClass(ANIM_MOVE_FLASH, PlayerPtr->Class->House, coord)); + OutList.Add(EventClass(ANIM_MOVE_FLASH, PlayerPtr->Class->House, coord, 1 << PlayerPtr->Class->House)); } // Fall into next case. diff --git a/REDALERT/FUNCTION.H b/REDALERT/FUNCTION.H index 6d99d8c..c18953e 100644 --- a/REDALERT/FUNCTION.H +++ b/REDALERT/FUNCTION.H @@ -547,7 +547,7 @@ void const *Get_Radar_Icon(void const * shapefile, int shapenum, int frames, int void CC_Draw_Shape(void const * shapefile, int shapenum, int x, int y, WindowNumberType window, ShapeFlags_Type flags, void const * fadingdata = 0, void const * ghostdata = 0, DirType rotation = DIR_N); // Added for draw intercept. ST - 1/17/2019 12:31PM -void CC_Draw_Shape(const ObjectClass *object, void const * shapefile, int shapenum, int x, int y, WindowNumberType window, ShapeFlags_Type flags, void const * fadingdata = 0, void const * ghostdata = 0, DirType rotation = DIR_N, long virtualscale = 0x0100); +void CC_Draw_Shape(const ObjectClass *object, void const * shapefile, int shapenum, int x, int y, WindowNumberType window, ShapeFlags_Type flags, void const * fadingdata = 0, void const * ghostdata = 0, DirType rotation = DIR_N, long virtualscale = 0x0100, int width = 0, int height = 0); void CC_Draw_Shape(const ObjectClass *object, const char *shape_file_name, void const * shapefile, int shapenum, int x, int y, WindowNumberType window, ShapeFlags_Type flags, void const * fadingdata = 0, void const * ghostdata = 0, DirType rotation = DIR_N, long virtualscale = 0x0100, char override_owner = HOUSE_NONE); // Added for pip draw intercept - SKY diff --git a/REDALERT/HOUSE.CPP b/REDALERT/HOUSE.CPP index 7207719..8dee841 100644 --- a/REDALERT/HOUSE.CPP +++ b/REDALERT/HOUSE.CPP @@ -1821,18 +1821,16 @@ void HouseClass::Attacked(BuildingClass* source) { assert(Houses.ID(this) == ID); - bool expired = SpeakAttackDelay == 0; - bool spoke = false; - #ifdef FIXIT_BASE_ANNOUNCE - // if (SpeakAttackDelay == 0 && ((Session.Type == GAME_NORMAL && IsPlayerControl) || PlayerPtr->Class->House == Class->House)) { - if (expired && ((Session.Type == GAME_NORMAL && IsPlayerControl) || PlayerPtr->Class->House == Class->House)) { + if (SpeakAttackDelay == 0 && ((Session.Type == GAME_NORMAL && IsPlayerControl) || PlayerPtr->Class->House == Class->House)) { #else - // if (SpeakAttackDelay == 0 && PlayerPtr->Class->House == Class->House) { - if (expired && PlayerPtr->Class->House == Class->House) { + if (SpeakAttackDelay == 0 && PlayerPtr->Class->House == Class->House) { #endif - Speak(VOX_BASE_UNDER_ATTACK, NULL, source ? source->Center_Coord() : 0); - spoke = true; + if (Session.Type == GAME_NORMAL) { + Speak(VOX_BASE_UNDER_ATTACK, NULL, source ? source->Center_Coord() : 0); + } else { + Speak(VOX_BASE_UNDER_ATTACK, this); + } // 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 @@ -1847,22 +1845,6 @@ void HouseClass::Attacked(BuildingClass* source) HouseTriggers[Class->House][index]->Spring(TEVENT_ATTACKED); } } - - // MBL 07.07.2020 - CNC Patch 3, fix for not working for all players in MP, per https://jaas.ea.com/browse/TDRA-7249 - // Separated to here as did not want to change any logic around the HouseTriggers[] Spring events - // - if (expired == true && spoke == false) - { - if (Session.Type != GAME_NORMAL) // Multiplayer - { - Speak(VOX_BASE_UNDER_ATTACK, this); - spoke = true; - - // 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 - } - } } diff --git a/REDALERT/HOUSE.H b/REDALERT/HOUSE.H index 9ae63e1..cddddc1 100644 --- a/REDALERT/HOUSE.H +++ b/REDALERT/HOUSE.H @@ -703,6 +703,7 @@ class HouseClass { bool Is_Ally(HousesType house) const; bool Is_Ally(HouseClass const * house) const; bool Is_Ally(ObjectClass const * object) const; + unsigned int Get_Allies(void) const {return Allies;} #ifdef CHEAT_KEYS void Debug_Dump(MonoClass *mono) const; #endif @@ -782,6 +783,9 @@ class HouseClass { void Init_Unit_Trackers(void); void Free_Unit_Trackers(void); + // MBL 09.08.2020 Mod support stub + void Handle_Mod_Game_Command(CELL cell, int mod_command_index); // mod_command_index = 0-3 + /* ** File I/O. */ diff --git a/REDALERT/INFANTRY.CPP b/REDALERT/INFANTRY.CPP index 60252b5..274a758 100644 --- a/REDALERT/INFANTRY.CPP +++ b/REDALERT/INFANTRY.CPP @@ -622,8 +622,14 @@ void InfantryClass::Per_Cell_Process(PCPType why) */ if (Mission == MISSION_CAPTURE) { TechnoClass * tech = cellptr->Cell_Building(); - - if (tech == NULL) tech = cellptr->Cell_Techno(); + if (tech != NULL) { + if ((tech->As_Target() == NavCom || tech->As_Target() == TarCom) && !tech->Can_Capture()) { + tech = NULL; + Assign_Destination(TARGET_NONE); + } + } else { + tech = cellptr->Cell_Techno(); + } if (tech != NULL && (tech->As_Target() == NavCom || tech->As_Target() == TarCom)) { if (*this == INFANTRY_RENOVATOR) { @@ -638,11 +644,14 @@ void InfantryClass::Per_Cell_Process(PCPType why) #else if (tech->House->Is_Ally(House)) { #endif + if (tech->Trigger.Is_Valid()) { + tech->Trigger->Spring(TEVENT_PLAYER_ENTERED, this); + } tech->Renovate(); } else { bool iscapturable = false; if (tech->What_Am_I() == RTTI_BUILDING) { - iscapturable = ((BuildingClass *)tech)->Class->IsCaptureable; + iscapturable = tech->Can_Capture(); } #ifdef FIXIT_ENGINEER // checked - ajw 9/28/98 if (tech->Health_Ratio() <= EngineerCaptureLevel && iscapturable) { @@ -1207,7 +1216,7 @@ void InfantryClass::Assign_Target(TARGET target) */ if (!Target_Legal(NavCom) && Class->IsCapture && !Is_Weapon_Equipped()) { BuildingClass const * building = As_Building(target); - if (building != NULL && building->Class->IsCaptureable) { + if (building != NULL && building->Can_Capture()) { Assign_Destination(target); } } @@ -2918,7 +2927,7 @@ ActionType InfantryClass::What_Action(ObjectClass const * object) const return(ACTION_GREPAIR); } else { - if (bldg->Class->IsCaptureable) { + if (bldg->Can_Capture()) { #ifdef FIXIT_ENGINEER // checked - ajw 9/28/98 if (bldg->Health_Ratio() <= EngineerCaptureLevel) { #else @@ -2929,7 +2938,7 @@ ActionType InfantryClass::What_Action(ObjectClass const * object) const return(ACTION_DAMAGE); } -// if (bldg->Health_Ratio() <= Rule.ConditionRed && bldg->Class->IsCaptureable) { +// if (bldg->Health_Ratio() <= Rule.ConditionRed && bldg->Can_Capture()) { } } } @@ -3050,7 +3059,7 @@ ActionType InfantryClass::What_Action(ObjectClass const * object) const if (Class->IsCapture && action == ACTION_ATTACK) { if (!House->Is_Ally(object) && ( //Disable capturing of helicopters (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) ) + (object->What_Am_I() == RTTI_BUILDING && object->Can_Capture()) ) ) { if (*this == INFANTRY_THIEF && (object->What_Am_I() == RTTI_BUILDING && ((BuildingClass *)object)->Class->Capacity == 0)) { @@ -3284,7 +3293,7 @@ int InfantryClass::Mission_Attack(void) return(1); } - if (Class->IsCapture && As_Building(TarCom) != NULL && As_Building(TarCom)->Class->IsCaptureable) { + if (Class->IsCapture && As_Building(TarCom) != NULL && As_Building(TarCom)->Can_Capture()) { Assign_Destination(TarCom); Assign_Mission(MISSION_CAPTURE); return(1); @@ -3914,7 +3923,7 @@ void InfantryClass::Doing_AI(void) } } if (anim != NULL) { - anim->OwnerHouse = House->Class->House; + anim->Set_Owner(House->Class->House); } delete this; return; diff --git a/REDALERT/License.txt b/REDALERT/License.txt index e549686..a4adf6d 100644 --- a/REDALERT/License.txt +++ b/REDALERT/License.txt @@ -1,6 +1,4 @@ -Electronic Arts Inc. released only TiberianDawn.dll, RedAlert.dll and -the Command & Conquer Map Editor and their corresponding source code -under the GPL V3 below, with additional terms at the bottom. +Electronic Arts Inc. released only TiberianDawn.dll and RedAlert.dll and their corresponding source code under the GPL V3 below, with additional terms at the bottom. GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 @@ -711,4 +709,4 @@ PROVIDED BY ELECTRONIC ARTS OR ANY AUTHORIZED REPRESENTATIVE SHALL CREATE A WARRANTY. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF OR LIMITATIONS ON IMPLIED WARRANTIES OR THE LIMITATIONS ON THE APPLICABLE STATUTORY RIGHTS OF A CONSUMER, SO SOME OR ALL OF THE ABOVE EXCLUSIONS AND LIMITATIONS MAY NOT APPLY -TO YOU. +TO YOU. \ No newline at end of file diff --git a/REDALERT/MiscAsm.cpp b/REDALERT/MiscAsm.cpp index fc6a767..7396e74 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#97 $ + ; $Header: //depot/Projects/Mobius/QA/Project/Run/SOURCECODE/REDALERT/MiscAsm.cpp#131 $ ;*************************************************************************** ;** 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 335ed41..2e3a38d 100644 --- a/REDALERT/OBJECT.CPP +++ b/REDALERT/OBJECT.CPP @@ -432,6 +432,15 @@ bool ObjectClass::Can_Demolish_Unit(void) const } +bool ObjectClass::Can_Capture(void) const +{ + assert(this != 0); + assert(IsActive); + + return(false); +} + + /*********************************************************************************************** * ObjectClass::Can_Player_Fire -- Can the player give this object an attack mission? * * * diff --git a/REDALERT/OBJECT.H b/REDALERT/OBJECT.H index c553016..0c9b1ea 100644 --- a/REDALERT/OBJECT.H +++ b/REDALERT/OBJECT.H @@ -165,6 +165,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/REDALERT/RADAR.CPP b/REDALERT/RADAR.CPP index 78bb77f..57c0c77 100644 --- a/REDALERT/RADAR.CPP +++ b/REDALERT/RADAR.CPP @@ -1409,7 +1409,7 @@ bool RadarClass::Jam_Cell(CELL cell, HouseClass * house/*KO, bool shadeit*/) for (int i = 0; i < Session.Players.Count(); i++) { HouseClass *player_house = HouseClass::As_Pointer(Session.Players[i]->Player.ID); - if (player_house->IsHuman && player_house != house) { + if (player_house->IsHuman && player_house != house && !house->Is_Ally(player_house)) { Shroud_Cell(cell, player_house); } } diff --git a/REDALERT/RedAlert.vcxproj b/REDALERT/RedAlert.vcxproj index 7161a6d..4cee2b6 100644 --- a/REDALERT/RedAlert.vcxproj +++ b/REDALERT/RedAlert.vcxproj @@ -514,6 +514,7 @@ + diff --git a/REDALERT/RedAlert.vcxproj.filters b/REDALERT/RedAlert.vcxproj.filters index 1f02003..c1e617a 100644 --- a/REDALERT/RedAlert.vcxproj.filters +++ b/REDALERT/RedAlert.vcxproj.filters @@ -1766,6 +1766,9 @@ Source Files\Resource + + Source Files + diff --git a/REDALERT/SCENARIO.CPP b/REDALERT/SCENARIO.CPP index 869e2f8..333d434 100644 --- a/REDALERT/SCENARIO.CPP +++ b/REDALERT/SCENARIO.CPP @@ -319,6 +319,14 @@ Theme.Stop(); return(false); } + /* Swap Lt. Blue and Blue color remaps in skirmish/multiplayer */ + if (Session.Type != GAME_NORMAL) { + RemapControlType temp; + memcpy(&temp, &ColorRemaps[PCOLOR_LTBLUE], sizeof(RemapControlType)); + memcpy(&ColorRemaps[PCOLOR_LTBLUE], &ColorRemaps[PCOLOR_BLUE], sizeof(RemapControlType)); + memcpy(&ColorRemaps[PCOLOR_BLUE], &temp, sizeof(RemapControlType)); + } + /* ** Play the winning movie and then start the next scenario. */ @@ -2293,11 +2301,15 @@ bool Read_Scenario_INI(char * fname, bool ) Rule.Difficulty(ini); /* - ** Fix a legacy bug with England and France country bonuses + ** - Fix a legacy bug with England and France country bonuses. + ** - Use ore growth and spread values from the special settings. */ if (Session.Type != GAME_NORMAL) { HouseTypeClass::As_Reference(HOUSE_ENGLAND).ArmorBias = fixed(9, 10); HouseTypeClass::As_Reference(HOUSE_FRANCE).ROFBias = fixed(9, 10); + + Rule.IsTGrowth = Special.IsTGrowth; + Rule.IsTSpread = Special.IsTSpread; } /* @@ -2477,6 +2489,7 @@ bool Read_Scenario_INI(char * fname, bool ) ** 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 8A - Germany is allied with Greece and itself ** 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 @@ -2525,6 +2538,17 @@ bool Read_Scenario_INI(char * fname, bool ) frc1_trigger->Action1.Trigger = los3_trigger; } + if (_stricmp(Scen.ScenarioName, "scg08ea.ini") == 0) { + for (int house = HOUSE_FIRST; house < HOUSE_COUNT; ++house) { + HouseClass* ptr = Houses.Ptr(house); + if (ptr != NULL && ptr->IsActive) { + if (ptr->Class->House == HOUSE_GREECE || ptr->Class->House == HOUSE_GERMANY) { + ptr->Make_Ally(HOUSE_GERMANY); + } + } + } + } + if (_stricmp(Scen.ScenarioName, "scg09ea.ini") == 0) { TriggerTypeClass* spyd_trigger = TriggerTypeClass::From_Name("Spyd"); assert(spyd_trigger != NULL); @@ -3202,6 +3226,7 @@ static void Create_Units(bool official) if (numtaken == 0) { int pick = Random_Pick(0, num_waypts-1); centroid = waypts[pick]; + hptr->StartLocationOverride = pick; taken[pick] = true; numtaken++; } else { @@ -3251,6 +3276,7 @@ static void Create_Units(bool official) ** Assign this best position to the house. */ centroid = waypts[best]; + hptr->StartLocationOverride = best; taken[best] = true; numtaken++; } diff --git a/REDALERT/TECHNO.CPP b/REDALERT/TECHNO.CPP index 2c14639..9f0a208 100644 --- a/REDALERT/TECHNO.CPP +++ b/REDALERT/TECHNO.CPP @@ -1647,7 +1647,7 @@ bool TechnoClass::Evaluate_Object(ThreatType method, int mask, int range, Techno ** If the scan is limited to capturable buildings only, then bail if the examined ** object isn't a capturable building. */ - if ((method & THREAT_CAPTURE) && (otype != RTTI_BUILDING || !((BuildingTypeClass const *)tclass)->IsCaptureable)) { + if ((method & THREAT_CAPTURE) && (otype != RTTI_BUILDING || !object->Can_Capture())) { BEnd(BENCH_EVAL_OBJECT); return(false); } @@ -3378,6 +3378,13 @@ BulletClass * TechnoClass::Fire_At(TARGET target, int which) } } + /* + ** For electric zaps, immediately perform bullet logic. + */ + if (weapon->IsElectric) { + bullet->AI(); + } + #endif } @@ -3560,7 +3567,7 @@ ActionType TechnoClass::What_Action(ObjectClass const * object) const ((weapon != NULL) && weapon->Bullet->IsAntiAircraft) || (object->Is_Techno() && (((TechnoClass *)object)->Height == 0))) { if (Can_Player_Move() || In_Range(object, primary)) { - if (In_Range(object, primary) || (What_Am_I() == RTTI_INFANTRY && ((InfantryClass *)this)->Class->IsCapture && object->What_Am_I() == RTTI_BUILDING && ((BuildingClass *)object)->Class->IsCaptureable)) { + if (In_Range(object, primary) || (What_Am_I() == RTTI_INFANTRY && ((InfantryClass *)this)->Class->IsCapture && object->What_Am_I() == RTTI_BUILDING && object->Can_Capture())) { return(ACTION_ATTACK); } else { if (!Can_Player_Move()) { diff --git a/REDALERT/UNIT.CPP b/REDALERT/UNIT.CPP index 04e9a34..b5af617 100644 --- a/REDALERT/UNIT.CPP +++ b/REDALERT/UNIT.CPP @@ -100,6 +100,32 @@ #include "function.h" #include "COORDA.h" + +extern void Logic_Switch_Player_Context(ObjectClass *object); +extern void Logic_Switch_Player_Context(HouseClass *object); +extern void On_Special_Weapon_Targetting(const HouseClass* player_ptr, SpecialWeaponType weapon_type); +extern bool Is_Legacy_Render_Enabled(void); + + +static int _GapShroudXTable[]={ + -1, 0, 1, + -2,-1, 0, 1, 2, + -2,-1, 0, 1, 2, + -2,-1, 0, 1, 2, + -2,-1, 0, 1, 2, + -2,-1, 0, 1, 2, + -1, 0, 1 +}; +static int _GapShroudYTable[]={ + -3,-3,-3, + -2,-2,-2,-2,-2, + -1,-1,-1,-1,-1, + 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, + 3, 3, 3 +}; + /*********************************************************************************************** * Recoil_Adjust -- Adjust pixel values in direction specified. * * * @@ -290,7 +316,8 @@ UnitClass::UnitClass(UnitType classid, HousesType house) : ShroudBits(0xFFFFFFFFUL), ShroudCenter(0), Reload(0), - SecondaryFacing(PrimaryFacing) + SecondaryFacing(PrimaryFacing), + TiberiumUnloadRefinery(TARGET_NONE) { Reload = 0; House->Tracking_Add(this); @@ -414,6 +441,20 @@ void UnitClass::AI(void) IsHarvesting = false; } + /* + ** Clear the unload refinery if not haresting or entering a refinery. + */ + if (Class->IsToHarvest) { + if (Mission != MISSION_HARVEST) { + if (Mission != MISSION_ENTER || + !In_Radio_Contact() || + Contact_With_Whom()->What_Am_I() != RTTI_BUILDING || + *((BuildingClass*)Contact_With_Whom()) != STRUCT_REFINERY) { + TiberiumUnloadRefinery = TARGET_NONE; + } + } + } + /* ** Handle combat logic for this unit. It will determine if it has a target and ** if so, if conditions are favorable for firing. When conditions permit, the @@ -2275,29 +2316,47 @@ bool UnitClass::Goto_Tiberium(int rad) int tiberium = 0; int besttiberium = 0; for (int x = -radius; x <= radius; x++) { + + /* + ** Randomize the corners. + */ + int corner[2]; + int corners[4][2] = { + {x, -radius}, + {x, +radius}, + {-radius, x}, + {+radius, x} + }; + for (int i = 0; i < 3; i++) { + int j = i + rand() / (RAND_MAX / (4 - i) + 1); + memcpy(&corner, &corners[j], sizeof(corner)); + memcpy(&corners[j], &corners[i], sizeof(corner)); + memcpy(&corners[i], corner, sizeof(corner)); + } + cell = center; - tiberium = Tiberium_Check(cell, x, -radius); + tiberium = Tiberium_Check(cell, corners[0][0], corners[0][1]); if (tiberium > besttiberium) { bestcell = cell; besttiberium = tiberium; } cell = center; - tiberium = Tiberium_Check(cell, x, +radius); + tiberium = Tiberium_Check(cell, corners[1][0], corners[1][1]); if (tiberium > besttiberium) { bestcell = cell; besttiberium = tiberium; } cell = center; - tiberium = Tiberium_Check(cell, -radius, x); + tiberium = Tiberium_Check(cell, corners[2][0], corners[2][1]); if (tiberium > besttiberium) { bestcell = cell; besttiberium = tiberium; } cell = center; - tiberium = Tiberium_Check(cell, +radius, x); + tiberium = Tiberium_Check(cell, corners[3][0], corners[3][1]); if (tiberium > besttiberium) { bestcell = cell; besttiberium = tiberium; @@ -2411,10 +2470,6 @@ bool UnitClass::Harvesting(void) * HISTORY: * * 07/18/1994 JLB : Created. * *=============================================================================================*/ -extern void Logic_Switch_Player_Context(ObjectClass *object); -extern void Logic_Switch_Player_Context(HouseClass *object); -extern void On_Special_Weapon_Targetting(const HouseClass* player_ptr, SpecialWeaponType weapon_type); - int UnitClass::Mission_Unload(void) { assert(Units.ID(this) == ID); @@ -2871,6 +2926,7 @@ int UnitClass::Mission_Harvest(void) /* ** Look for ore where we last found some - mine the same patch */ + TiberiumUnloadRefinery = TARGET_NONE; if (Target_Legal(ArchiveTarget)) { Assign_Destination(ArchiveTarget); ArchiveTarget = 0; @@ -2952,24 +3008,17 @@ int UnitClass::Mission_Harvest(void) if (!Target_Legal(NavCom)) { /* - ** Find nearby refinery and head to it? + ** Find nearby refinery and head to it. */ - BuildingClass * nearest = Find_Docking_Bay(STRUCT_REFINERY, false); - - /* - ** Since the refinery said it was ok to load, establish radio - ** contact with the refinery and then await docking orders. - */ - if (nearest != NULL && Transmit_Message(RADIO_HELLO, nearest) == RADIO_ROGER) { - Status = HEADINGHOME; - if (nearest->House == PlayerPtr && (PlayerPtr->Capacity - PlayerPtr->Tiberium) < 300 && PlayerPtr->Capacity > 500 && (PlayerPtr->ActiveBScan & (STRUCTF_REFINERY | STRUCTF_CONST))) { - Speak(VOX_NEED_MO_CAPACITY); - } - } else { - ScenarioInit++; - nearest = Find_Docking_Bay(STRUCT_REFINERY, false); - ScenarioInit--; - if (nearest != NULL) { + BuildingClass * nearest = Find_Best_Refinery(); + if (nearest != NULL) { + TiberiumUnloadRefinery = nearest->As_Target(); + if (Transmit_Message(RADIO_HELLO, nearest) == RADIO_ROGER) { + Status = HEADINGHOME; + if (nearest->House == PlayerPtr && (PlayerPtr->Capacity - PlayerPtr->Tiberium) < 300 && PlayerPtr->Capacity > 500 && (PlayerPtr->ActiveBScan & (STRUCTF_REFINERY | STRUCTF_CONST))) { + Speak(VOX_NEED_MO_CAPACITY); + } + } else { Assign_Destination(::As_Target(Nearby_Location(nearest))); } } @@ -2990,6 +3039,7 @@ int UnitClass::Mission_Harvest(void) ** no where to go. */ case GOINGTOIDLE: + TiberiumUnloadRefinery = TARGET_NONE; if (IsUseless) { if (House->ActiveBScan & STRUCTF_REPAIR) { Assign_Mission(MISSION_REPAIR); @@ -4363,6 +4413,103 @@ fixed UnitClass::Tiberium_Load(void) const } +BuildingClass* UnitClass::Tiberium_Unload_Refinery(void) const +{ + return Target_Legal(TiberiumUnloadRefinery) ? As_Building(TiberiumUnloadRefinery) : NULL; +} + + +struct RefineryData +{ + BuildingClass* Refinery; + int Distance; + int Harvesters; +}; + +static bool operator==(const RefineryData& lhs, const RefineryData& rhs) +{ + return lhs.Refinery == rhs.Refinery; +} + +static bool operator!=(const RefineryData& lhs, const RefineryData& rhs) +{ + return !(lhs == rhs); +} + +static int _refinery_compare(const void * left, const void * right) +{ + const RefineryData& lhs = *reinterpret_cast(left); + const RefineryData& rhs = *reinterpret_cast(right); + if (lhs.Distance < rhs.Distance) { + return -1; + } else if (rhs.Distance < lhs.Distance) { + return 1; + } + return 0; +} + +BuildingClass* UnitClass::Find_Best_Refinery(void) const +{ + static DynamicVectorClass _refineries; + + _refineries.Clear(); + for (int i = 0; i < Buildings.Count(); ++i) { + BuildingClass* refinery = Buildings.Ptr(i); + if (refinery != NULL && + refinery->House == House && + !refinery->IsInLimbo && + *refinery == STRUCT_REFINERY && + Map[refinery->Center_Coord()].Zones[Techno_Type_Class()->MZone] == Map[Center_Coord()].Zones[Techno_Type_Class()->MZone]) { + _refineries.Add(RefineryData{ refinery, Distance(refinery), 0 }); + } + } + + // Base case for zero or one refineries. + if (_refineries.Count() == 0) { + return NULL; + } else if (_refineries.Count() == 1) { + return _refineries[0].Refinery; + } + + // Count harvesters going to each refinery as well as the total. + int num_harvesters = 0; + for (int i = 0; i < Units.Count(); ++i) { + UnitClass* unit = Units.Ptr(i); + if (unit->IsActive && Class->IsToHarvest && unit->House == House) { + BuildingClass* refinery = unit->Tiberium_Unload_Refinery(); + if (refinery != NULL) { + int index = _refineries.ID(RefineryData{ refinery }); + assert(index >= 0); + _refineries[index].Harvesters++; + num_harvesters++; + } + } + } + + // Sort by distance (special case for 2 refineries as that's a single swap). + if (_refineries.Count() == 2) { + if (_refineries[0].Distance > _refineries[1].Distance) { + RefineryData temp = _refineries[0]; + _refineries[0] = _refineries[1]; + _refineries[1] = temp; + } + } else { + qsort(&_refineries[0], _refineries.Count(), sizeof(RefineryData), _refinery_compare); + } + + // Evenly distribute harvesters among refineries. + int harvesters_per_refinery = (num_harvesters + _refineries.Count() - 1) / _refineries.Count(); + for (int i = 0; i < _refineries.Count(); ++i) { + if (_refineries[i].Harvesters < harvesters_per_refinery) { + return _refineries[i].Refinery; + } + } + + // Fall back on closest refinery + return _refineries[0].Refinery; +} + + /*********************************************************************************************** * UnitClass::Offload_Tiberium_Bail -- Offloads one Tiberium quantum from the object. * * * @@ -4517,7 +4664,7 @@ void UnitClass::Overrun_Square(CELL cell, bool threaten) if (object->Height == 0) { AnimClass* anim = new AnimClass(ANIM_CORPSE1, object->Center_Coord()); if (anim != NULL) { - anim->OwnerHouse = object->Owner(); + anim->Set_Owner(object->Owner()); } } object->Record_The_Kill(this); @@ -5022,64 +5169,143 @@ bool UnitClass::Limbo(void) return(false); } + + +/*********************************************************************************************** + * UnitClass::Apply_Temporary_Jamming_Shroud -- Apply a temporary gap generator shroud effect * + * * + * This is intended as a temporary effect that is active only during export of the * + * shroud data * + * * + * INPUT: House to apply effect for * + * * + * OUTPUT: Bitmask of cells that effect was applied to * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 8/19/2020 12:13PM ST : Created. * + *=============================================================================================*/ +unsigned int UnitClass::Apply_Temporary_Jamming_Shroud(HouseClass *house_to_apply_for) +{ + unsigned int shroud_bits_applied = 0; + + if (!IsActive || !Strength) { + return shroud_bits_applied; + } + + if (!Class->IsGapper) { + return shroud_bits_applied; + } + + CELL shroud_center = Coord_Cell(Center_Coord()); + int centerx = Cell_X(shroud_center); + int centery = Cell_Y(shroud_center); + CELL trycell; + + for (int index = 0; index < 31; index++) { + shroud_bits_applied <<= 1; + trycell = XY_Cell(centerx + _GapShroudXTable[index], centery + _GapShroudYTable[index]); + if (Map[trycell].Is_Mapped(house_to_apply_for)) { + Map.Jam_Cell(trycell, House); + shroud_bits_applied |= 1; + } + } + + if (shroud_bits_applied) { + Map.Constrained_Look(Coord, 5 * CELL_LEPTON_W, house_to_apply_for); + } + + return shroud_bits_applied; +} + + + +/*********************************************************************************************** + * UnitClass::Unapply_Temporary_Jamming_Shroud -- Remove temporary gap generator shroud effect * + * * + * Remove gap effect added by Apply_Temporary_Jamming_Shroud * + * * + * INPUT: House to unapply effect for * + * Bitmask of cells that effect was applied to * + * * + * OUTPUT: * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 8/19/2020 12:16PM ST : Created. * + *=============================================================================================*/ +void UnitClass::Unapply_Temporary_Jamming_Shroud(HouseClass *house_to_unapply_for, unsigned int shroud_bits_applied) +{ + if (!IsActive || !Strength) { + return; + } + + if (!Class->IsGapper) { + return; + } + + CELL shroud_center = Coord_Cell(Center_Coord()); + int centerx = Cell_X(shroud_center); + int centery = Cell_Y(shroud_center); + CELL trycell; + + for (int index = 30; index >= 0 && shroud_bits_applied; index--) { + if (shroud_bits_applied & 1) { + trycell = XY_Cell(centerx + _GapShroudXTable[index], centery + _GapShroudYTable[index]); + Map.UnJam_Cell(trycell, House); + Map.Map_Cell(trycell, house_to_unapply_for); + } + shroud_bits_applied >>= 1; + } +} + + + /* ** Updated for client/server multiplayer - ST 8/12/2019 11:46AM */ void UnitClass::Shroud_Regen(void) { if (Class->IsGapper/*KO && !House->IsPlayerControl*/) { - static int _xtab[]={ - -1, 0, 1, - -2,-1, 0, 1, 2, - -2,-1, 0, 1, 2, - -2,-1, 0, 1, 2, - -2,-1, 0, 1, 2, - -2,-1, 0, 1, 2, - -1, 0, 1 - }; - static int _ytab[]={ - -3,-3,-3, - -2,-2,-2,-2,-2, - -1,-1,-1,-1,-1, - 0, 0, 0, 0, 0, - 1, 1, 1, 1, 1, - 2, 2, 2, 2, 2, - 3, 3, 3 - }; + int index; int centerx, centery; CELL trycell; - // Only restore under the shroud if it's a valid field. - if (ShroudBits != (unsigned)-1L) { - centerx = Cell_X(ShroudCenter); - centery = Cell_Y(ShroudCenter); - for (index = 30; index >= 0 && ShroudBits; index--) { - if (ShroudBits & 1) { - trycell = XY_Cell(centerx + _xtab[index], centery + _ytab[index]); -#if(0) - Map.Map_Cell(trycell, PlayerPtr); -#else - Map.UnJam_Cell(trycell, House); - Map.Map_Cell(trycell, House); -#endif + if (Session.Type != GAME_GLYPHX_MULTIPLAYER || Is_Legacy_Render_Enabled()) { + // Only restore under the shroud if it's a valid field. + if (ShroudBits != (unsigned)-1L) { + centerx = Cell_X(ShroudCenter); + centery = Cell_Y(ShroudCenter); + for (index = 30; index >= 0 && ShroudBits; index--) { + if (ShroudBits & 1) { + trycell = XY_Cell(centerx + _GapShroudXTable[index], centery + _GapShroudYTable[index]); + #if(0) + Map.Map_Cell(trycell, PlayerPtr); + #else + Map.UnJam_Cell(trycell, House); + Map.Map_Cell(trycell, House); + #endif + } + ShroudBits >>= 1; } - ShroudBits >>= 1; } - } - if(IsActive && Strength) { - // Now shroud around the new center - ShroudBits = 0L; - ShroudCenter = Coord_Cell(Center_Coord()); - centerx = Cell_X(ShroudCenter); - centery = Cell_Y(ShroudCenter); - for (index = 0; index < 31; index++) { - ShroudBits <<= 1; - trycell = XY_Cell(centerx + _xtab[index], centery + _ytab[index]); - if (Map[trycell].Is_Mapped(House)) { - Map.Jam_Cell(trycell, House); - ShroudBits |= 1; + if(IsActive && Strength) { + // Now shroud around the new center + ShroudBits = 0L; + ShroudCenter = Coord_Cell(Center_Coord()); + centerx = Cell_X(ShroudCenter); + centery = Cell_Y(ShroudCenter); + for (index = 0; index < 31; index++) { + ShroudBits <<= 1; + trycell = XY_Cell(centerx + _GapShroudXTable[index], centery + _GapShroudYTable[index]); + if (Map[trycell].Is_Mapped(House)) { + Map.Jam_Cell(trycell, House); + ShroudBits |= 1; + } } } } @@ -5094,14 +5320,15 @@ void UnitClass::Shroud_Regen(void) } else { - for (int i = 0; i < Session.Players.Count(); i++) { - HouseClass *player_house = HouseClass::As_Pointer(Session.Players[i]->Player.ID); - if (player_house->IsHuman && player_house != House) { - Map.Constrained_Look(Coord, 5 * CELL_LEPTON_W, player_house); + if (Is_Legacy_Render_Enabled()) { + for (int i = 0; i < Session.Players.Count(); i++) { + HouseClass *player_house = HouseClass::As_Pointer(Session.Players[i]->Player.ID); + if (player_house->IsHuman && player_house != House) { + Map.Constrained_Look(Coord, 5 * CELL_LEPTON_W, player_house); + } } } } - } } diff --git a/REDALERT/UNIT.H b/REDALERT/UNIT.H index 2660062..0eb65c0 100644 --- a/REDALERT/UNIT.H +++ b/REDALERT/UNIT.H @@ -111,10 +111,15 @@ class UnitClass : public DriveClass */ FacingClass SecondaryFacing; + /* + ** This is the refinery a harvester is interested in unloading at. + */ + TARGET TiberiumUnloadRefinery; + /* ** 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]; /*--------------------------------------------------------------------- ** Constructors, Destructors, and overloaded operators. @@ -145,6 +150,9 @@ class UnitClass : public DriveClass void APC_Close_Door(void); void APC_Open_Door(void); + unsigned int Apply_Temporary_Jamming_Shroud(HouseClass *house_to_apply_for); + void Unapply_Temporary_Jamming_Shroud(HouseClass *house_to_unapply_for, unsigned int shroud_bits_applied); + /* ** Query functions. */ @@ -158,6 +166,8 @@ class UnitClass : public DriveClass virtual bool Ok_To_Move(DirType facing) const; virtual FireErrorType Can_Fire(TARGET target, int which) const; virtual fixed Tiberium_Load(void) const; + virtual BuildingClass* Tiberium_Unload_Refinery(void) const; + virtual BuildingClass* Find_Best_Refinery(void) const; /* ** Coordinate inquiry functions. These are used for both display and @@ -248,4 +258,4 @@ class UnitClass : public DriveClass bool Save(Pipe & file) const; }; -#endif +#endif \ No newline at end of file diff --git a/REDALERT/WIN32LIB/DrawMisc.cpp b/REDALERT/WIN32LIB/DrawMisc.cpp index 6c3abe1..de2472d 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#97 $ +; $Header: //depot/Projects/Mobius/QA/Project/Run/SOURCECODE/REDALERT/WIN32LIB/DrawMisc.cpp#131 $ ;*************************************************************************** ;** C O N F I D E N T I A L --- W E S T W O O D A S S O C I A T E S ** ;*************************************************************************** diff --git a/TIBERIANDAWN/ADATA.CPP b/TIBERIANDAWN/ADATA.CPP index 0eeb732..1756d19 100644 --- a/TIBERIANDAWN/ADATA.CPP +++ b/TIBERIANDAWN/ADATA.CPP @@ -2379,7 +2379,58 @@ static AnimTypeClass const Flag( 0, // Loop start frame number. -1, // Ending frame of loop back. -1, // Number of animation stages. - -1, // Number of times the animation loops. + -1, // Number of times the animation loops. + VOC_NONE, // Sound effect to play. + ANIM_NONE +); + +static AnimTypeClass const Beacon( + ANIM_BEACON, // Animation number. + "MOVEFLSH", // 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, + -1, // Virtual stages + 0x100, // Virtual scale + ANIM_BEACON_VIRTUAL // Virtual anim +); + +static AnimTypeClass const BeaconVirtual( + ANIM_BEACON_VIRTUAL, // Animation number. + "BEACON", // 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 ); @@ -2484,10 +2535,12 @@ AnimTypeClass const * const AnimTypeClass::Pointers[ANIM_COUNT] = { &RaptDie, &ChemBall, &Flag, + &Beacon, &Fire3Virtual, &Fire2Virtual, &Fire1Virtual, - &Fire4Virtual + &Fire4Virtual, + &BeaconVirtual }; @@ -2570,6 +2623,9 @@ void AnimTypeClass::One_Time(void) ((void const *&)As_Reference(index).ImageData) = MixFileClass::Retrieve(fullname); } } + + // Set up beacon image data manually since they're new animations only available in the virtual renderer + ((void const *&)As_Reference(ANIM_BEACON_VIRTUAL).ImageData) = As_Reference(ANIM_BEACON).ImageData; } diff --git a/TIBERIANDAWN/AIRCRAFT.CPP b/TIBERIANDAWN/AIRCRAFT.CPP index c444d94..352c9b9 100644 --- a/TIBERIANDAWN/AIRCRAFT.CPP +++ b/TIBERIANDAWN/AIRCRAFT.CPP @@ -2368,6 +2368,11 @@ int AircraftClass::Mission_Attack(void) return(1); } + /* + ** Clear second shot flag so fire burst works correctly. + */ + IsSecondShot = false; + PrimaryFacing.Set_Desired(Direction(TarCom)); SecondaryFacing.Set_Desired(Direction(TarCom)); switch (Can_Fire(TarCom, 0)) { diff --git a/TIBERIANDAWN/ANIM.CPP b/TIBERIANDAWN/ANIM.CPP index e388218..0c39374 100644 --- a/TIBERIANDAWN/ANIM.CPP +++ b/TIBERIANDAWN/ANIM.CPP @@ -249,6 +249,8 @@ void AnimClass::Draw_It(int x, int y, WindowNumberType window) int shapenum = Class->Start + Fetch_Stage(); void const * remap = NULL; ShapeFlags_Type flags = SHAPE_CENTER|SHAPE_WIN_REL; + int width = 0; + int height = 0; /* ** Some animations require special fixups. @@ -276,6 +278,12 @@ void AnimClass::Draw_It(int x, int y, WindowNumberType window) y += (3 * ICON_PIXEL_H / 4) - Get_Build_Frame_Height(shapefile); transtable = Map.UnitShadow; break; + + case ANIM_BEACON_VIRTUAL: + width = 29; + height = 39; + flags = flags | SHAPE_BOTTOM | SHAPE_COMPACT; + break; } /* @@ -303,7 +311,7 @@ void AnimClass::Draw_It(int x, int y, WindowNumberType window) ** Draw the animation shape, but ignore legacy if beyond normal stage count. */ if ((window == WINDOW_VIRTUAL) || (Fetch_Stage() < Class->Stages)) { - CC_Draw_Shape(this, shapefile, shapenum, x, y, window, flags, remap, transtable, Class->VirtualScale); + CC_Draw_Shape(this, shapefile, shapenum, x, y, window, flags, remap, transtable, Class->VirtualScale, width, height); } } } @@ -571,6 +579,7 @@ AnimClass::AnimClass(AnimType animnum, COORDINATE coord, unsigned char timedelay Object = 0; SortTarget = TARGET_NONE; OwnerHouse = HOUSE_NONE; + KillTime = 0ULL; if (Class->Stages == -1) { ((int&)Class->Stages) = Get_Build_Frame_Count(Class->Get_Image_Data()); @@ -732,6 +741,19 @@ void AnimClass::AI(void) IsToDelete = true; } + /* + ** Check the kill time. + */ + if (KillTime > 0ULL) { + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + + unsigned long long now = (unsigned long long)ft.dwLowDateTime + ((unsigned long long)ft.dwHighDateTime << 32ULL); + if (now >= KillTime) { + IsToDelete = true; + } + } + /* ** Delete this animation and bail early if the animation is flagged to be deleted ** immediately. @@ -1292,4 +1314,20 @@ void AnimClass::Detach(TARGET target, bool all) IsToDelete = true; } } +} + +void AnimClass::Set_Owner(HousesType owner) +{ + OwnerHouse = owner; + if (VirtualAnim != NULL) { + VirtualAnim->Set_Owner(owner); + } +} + +void AnimClass::Set_Visible_Flags(unsigned flags) +{ + VisibleFlags = flags; + if (VirtualAnim != NULL) { + VirtualAnim->Set_Visible_Flags(flags); + } } \ No newline at end of file diff --git a/TIBERIANDAWN/ANIM.H b/TIBERIANDAWN/ANIM.H index 2a78888..6988f89 100644 --- a/TIBERIANDAWN/ANIM.H +++ b/TIBERIANDAWN/ANIM.H @@ -63,14 +63,16 @@ class AnimClass : public ObjectClass, private StageClass { void Sort_Above(TARGET target); void Make_Invisible(void) {IsInvisible = true;}; void Make_Visible(void) {IsInvisible = false;}; + void Kill_At(unsigned long long kill_time) {KillTime = kill_time;} /* ** 2019/09/19 JAS ** Added functions for accessing which players can see this anim */ - void Set_Visible_Flags(unsigned flags) { VisibleFlags = flags; } + void Set_Visible_Flags(unsigned flags); unsigned Get_Visible_Flags() const { return (Delay == 0) ? VisibleFlags : 0; } + virtual void Set_Owner(HousesType owner); virtual bool Can_Place_Here(COORDINATE ) const {return true;} virtual bool Mark(MarkType mark=MARK_CHANGE); virtual bool Render(bool forced); @@ -194,10 +196,15 @@ class AnimClass : public ObjectClass, private StageClass { */ AnimClass * VirtualAnim; + /* + ** Real-time point to kill this animation. + */ + unsigned long long KillTime; + /* ** 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[24]; }; diff --git a/TIBERIANDAWN/AUDIO.CPP b/TIBERIANDAWN/AUDIO.CPP index 674679e..283e9dd 100644 --- a/TIBERIANDAWN/AUDIO.CPP +++ b/TIBERIANDAWN/AUDIO.CPP @@ -254,6 +254,7 @@ struct SoundEffectNameStruct { {"DINOYES", 10, IN_NOVAR}, // VOC_DINOYES Yes Sir in dino-speak. {"DINOATK1", 10, IN_NOVAR}, // VOC_DINOATK1 Dino attack sound. {"DINODIE1", 10, IN_NOVAR}, // VOC_DINODIE1 Dino die sound. + {"BEACON", 10, IN_NOVAR }, // VOC_BEACON Beacon sound. #ifdef PETROGLYPH_EXAMPLE_MOD {"NUKE_LOB", 10, IN_NOVAR} // VOC_NUKE_LOB Mod expansion unit firing sound diff --git a/TIBERIANDAWN/BUILDING.CPP b/TIBERIANDAWN/BUILDING.CPP index 441f8ee..cebded0 100644 --- a/TIBERIANDAWN/BUILDING.CPP +++ b/TIBERIANDAWN/BUILDING.CPP @@ -2077,7 +2077,7 @@ void BuildingClass::Active_Click_With(ActionType action, CELL cell) OutList.Add(EventClass(EventClass::SELL, As_Target())); COORDINATE coord = Map.Pixel_To_Coord(Get_Mouse_X(), Get_Mouse_Y()); - OutList.Add(EventClass(ANIM_MOVE_FLASH, PlayerPtr->Class->House, coord)); + OutList.Add(EventClass(ANIM_MOVE_FLASH, PlayerPtr->Class->House, coord, 1 << PlayerPtr->Class->House)); } } @@ -3872,13 +3872,16 @@ bool BuildingClass::Can_Demolish_Unit(void) const bool BuildingClass::Can_Capture(void) const { - bool can_capture = Class->IsCaptureable; + bool can_capture = Class->IsCaptureable && Mission != MISSION_DECONSTRUCTION; // Override capturable state if this building has a capture win trigger if (GameToPlay == GAME_NORMAL) { - if (Trigger != NULL && Trigger->Action == TriggerClass::ACTION_WINLOSE) { + if (!House->IsHuman && Trigger != NULL && Trigger->Action == TriggerClass::ACTION_WINLOSE) { can_capture = true; } + } else { + // Only allow capturing of multiplayer-owned structures + can_capture &= House->Class->House >= HOUSE_MULTI1 && House->Class->House <= HOUSE_MULTI6; } return(can_capture); @@ -4161,6 +4164,7 @@ int BuildingClass::Mission_Deconstruction(void) Status = DURING; Begin_Mode(BSTATE_CONSTRUCTION); IsReadyToCommence = false; + IsSurvivorless = true; break; } Transmit_Message(RADIO_RUN_AWAY); diff --git a/TIBERIANDAWN/BULLET.CPP b/TIBERIANDAWN/BULLET.CPP index fb09e7b..eda4c99 100644 --- a/TIBERIANDAWN/BULLET.CPP +++ b/TIBERIANDAWN/BULLET.CPP @@ -473,7 +473,7 @@ void BulletClass::AI(void) // if (newanim && Class->Explosion == ANIM_ATOM_BLAST && newanim->OwnerHouse == HOUSE_NONE) { if (Payback && Payback->House && Payback->House->Class) { - newanim->OwnerHouse = Payback->House->Class->House; + newanim->Set_Owner(Payback->House->Class->House); } } } diff --git a/TIBERIANDAWN/CELL.CPP b/TIBERIANDAWN/CELL.CPP index 184001f..8a79223 100644 --- a/TIBERIANDAWN/CELL.CPP +++ b/TIBERIANDAWN/CELL.CPP @@ -389,7 +389,9 @@ void CellClass::Redraw_Objects(bool forced) if (Cell_Occupier()) { ObjectClass * optr = Cell_Occupier(); while (optr) { - optr->Mark(MARK_CHANGE); + if (optr->IsActive) { + optr->Mark(MARK_CHANGE); + } optr = optr->Next; } } @@ -2448,7 +2450,7 @@ void CellClass::Flag_Create(void) CTFFlag = new AnimClass(ANIM_FLAG, Cell_Coord(), 0, 1, true); } assert(CTFFlag != NULL); - CTFFlag->OwnerHouse = Owner; + CTFFlag->Set_Owner(Owner); } } diff --git a/TIBERIANDAWN/CONQUER.CPP b/TIBERIANDAWN/CONQUER.CPP index 01d93d0..edba61f 100644 --- a/TIBERIANDAWN/CONQUER.CPP +++ b/TIBERIANDAWN/CONQUER.CPP @@ -2730,11 +2730,11 @@ extern void DLL_Draw_Intercept(int shape_number, int x, int y, int width, int he extern void DLL_Draw_Pip_Intercept(const ObjectClass* object, int pip); extern void DLL_Draw_Line_Intercept(int x, int y, int x1, int y1, unsigned char color, int frame); -void CC_Draw_Shape(ObjectClass *object, void const * shapefile, int shapenum, int x, int y, WindowNumberType window, ShapeFlags_Type flags, void const * fadingdata, void const * ghostdata, int scale) +void CC_Draw_Shape(ObjectClass *object, void const * shapefile, int shapenum, int x, int y, WindowNumberType window, ShapeFlags_Type flags, void const * fadingdata, void const * ghostdata, int scale, int width, int height) { if (window == WINDOW_VIRTUAL) { - int width = Get_Build_Frame_Width(shapefile); - int height = Get_Build_Frame_Height(shapefile); + if (width == 0) width = Get_Build_Frame_Width(shapefile); + if (height == 0) height = Get_Build_Frame_Height(shapefile); DLL_Draw_Intercept(shapenum, x, y, width, height, (int)flags, object, NULL, -1, scale); return; } diff --git a/TIBERIANDAWN/DEFINES.H b/TIBERIANDAWN/DEFINES.H index 141699c..e5b1a18 100644 --- a/TIBERIANDAWN/DEFINES.H +++ b/TIBERIANDAWN/DEFINES.H @@ -1510,11 +1510,13 @@ typedef enum AnimType : char { ANIM_RAPT_DIE, ANIM_CHEM_BALL, // Chemical warrior explosion. ANIM_FLAG, // CTF flag. + ANIM_BEACON, // Beacon. - ANIM_FIRE_SMALL_VIRTUAL, // Small flame animation (virtual). - ANIM_FIRE_MED_VIRTUAL, // Medium flame animation (virtual). - ANIM_FIRE_MED2_VIRTUAL, // Medium flame animation (oranger) (virtual). - ANIM_FIRE_TINY_VIRTUAL, // Very tiny flames (virtual). + ANIM_FIRE_SMALL_VIRTUAL, // Small flame animation (virtual). + ANIM_FIRE_MED_VIRTUAL, // Medium flame animation (virtual). + ANIM_FIRE_MED2_VIRTUAL, // Medium flame animation (oranger) (virtual). + ANIM_FIRE_TINY_VIRTUAL, // Very tiny flames (virtual). + ANIM_BEACON_VIRTUAL, // Beacon (virtual). ANIM_COUNT, ANIM_FIRST=0 @@ -2309,6 +2311,8 @@ typedef enum VocType : char{ VOC_DINOATK1, // Dino attack sound. VOC_DINODIE1, // Dino die sound. + VOC_BEACON, // Beacon sound. + #ifdef PETROGLYPH_EXAMPLE_MOD VOC_NUKE_LOB, // Modded unit firing sound #endif diff --git a/TIBERIANDAWN/DISPLAY.CPP b/TIBERIANDAWN/DISPLAY.CPP index f24628e..163b919 100644 --- a/TIBERIANDAWN/DISPLAY.CPP +++ b/TIBERIANDAWN/DISPLAY.CPP @@ -1107,7 +1107,7 @@ void DisplayClass::Cursor_Mark(CELL pos, bool on) CELL const *ptr; CellClass *cellptr; - if (pos == -1) return; + if ((unsigned)pos >= MAP_CELL_TOTAL) return; /* ** For every cell in the CursorSize list, invoke its Redraw_Objects and @@ -2965,6 +2965,7 @@ void DisplayClass::Select_These(COORDINATE coord1, COORDINATE coord2, bool addit */ if ( obj->Class_Of().IsSelectable && obj->What_Am_I() != RTTI_BUILDING && + (!obj->Is_Techno() || !((TechnoClass*)obj)->Is_Cloaked(PlayerPtr)) && x >= x1 && x <= x2 && y >= y1 && y <= y2) { bool old_allow_voice = AllowVoice; bool is_player_controlled = obj->Owner() == PlayerPtr->Class->House; @@ -2992,6 +2993,7 @@ void DisplayClass::Select_These(COORDINATE coord1, COORDINATE coord2, bool addit ** selected, and are within the bounding box. */ if ( aircraft->Class_Of().IsSelectable && + !aircraft->Is_Cloaked(PlayerPtr) && !aircraft->Is_Selected_By_Player() && x >= x1 && x <= x2 && y >= y1 && y <= y2) { bool old_allow_voice = AllowVoice; diff --git a/TIBERIANDAWN/DLLInterface.cpp b/TIBERIANDAWN/DLLInterface.cpp index ee67bb0..7b4e9c5 100644 --- a/TIBERIANDAWN/DLLInterface.cpp +++ b/TIBERIANDAWN/DLLInterface.cpp @@ -140,6 +140,7 @@ extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Sidebar_Request(Sidebar extern "C" __declspec(dllexport) void __cdecl CNC_Handle_SuperWeapon_Request(SuperWeaponRequestEnum request_type, uint64 player_id, int buildable_type, int buildable_id, int x1, int y1); extern "C" __declspec(dllexport) void __cdecl CNC_Handle_ControlGroup_Request(ControlGroupRequestEnum request_type, uint64 player_id, unsigned char control_group_index); extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Debug_Request(DebugRequestEnum debug_request_type, uint64 player_id, const char *object_name, int x, int y, bool unshroud, bool enemy); +extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Beacon_Request(BeaconRequestEnum beacon_request_type, uint64 player_id, int pixel_x, int pixel_y); extern "C" __declspec(dllexport) bool __cdecl CNC_Set_Multiplayer_Data(int scenario_index, CNCMultiplayerOptionsStruct &game_options, int num_players, CNCPlayerInfoStruct *player_list, int max_players); extern "C" __declspec(dllexport) bool __cdecl CNC_Clear_Object_Selection(uint64 player_id); extern "C" __declspec(dllexport) bool __cdecl CNC_Select_Object(uint64 player_id, int object_type_id, int object_to_select_id); @@ -148,7 +149,7 @@ extern "C" __declspec(dllexport) void __cdecl CNC_Set_Difficulty(int difficulty) extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Player_Switch_To_AI(uint64 player_id); extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Human_Team_Wins(uint64 player_id); extern "C" __declspec(dllexport) void __cdecl CNC_Start_Mission_Timer(int time); - +extern "C" __declspec(dllexport) bool __cdecl CNC_Get_Start_Game_Info(uint64 player_id, int &start_location_waypoint_index); /* @@ -390,14 +391,13 @@ void Play_Movie_GlyphX(const char * movie_name, ThemeType theme) void On_Sound_Effect(int sound_index, int variation, COORDINATE coord) { - // MBL 02.26.2019 int voc = sound_index; if (voc == VOC_NONE) { return; } - // MBL 02.26.2019 - Borrowed from AUDIO.CPP Sound_Effect() + // Borrowed from AUDIO.CPP Sound_Effect() // #if 1 char const * ext = ""; // ".AUD"; @@ -425,16 +425,12 @@ void On_Sound_Effect(int sound_index, int variation, COORDINATE coord) } } #endif - // END MBL DLLExportClass::On_Sound_Effect(PlayerPtr, sound_index, ext, variation, coord); } -// MBL 02.06.2020 -// void On_Speech(int speech_index) void On_Speech(int speech_index, HouseClass *house) { - // DLLExportClass::On_Speech(PlayerPtr, speech_index); // MBL 02.06.2020 if (house == NULL) { DLLExportClass::On_Speech(PlayerPtr, speech_index); } @@ -504,7 +500,6 @@ extern "C" __declspec(dllexport) void __cdecl CNC_Init(const char *command_line, DLL_Startup(command_line); - // MBL DLLExportClass::Set_Event_Callback( event_callback ); DLLExportClass::Init(); @@ -879,7 +874,7 @@ void GlyphX_Assign_Houses(void) preassigned = true; } } - if (!preassigned) { + if (!preassigned && i < MAX_PLAYERS) { random_start_locations[num_random_start_locations] = num_start_locations; num_random_start_locations++; } @@ -1334,6 +1329,10 @@ extern "C" __declspec(dllexport) bool __cdecl CNC_Start_Custom_Instance(const ch GlyphXClientSidebarWidthInLeptons = 0; + Play_Movie(IntroMovie); + Play_Movie(BriefMovie); + Play_Movie(ActionMovie, TransitTheme); + /* if (!Start_Scenario(ScenarioName)) { return(false); @@ -1701,13 +1700,13 @@ extern "C" __declspec(dllexport) bool __cdecl CNC_Save_Load(bool save, const cha result = Load_Game(file_path_and_name); - // MBL 07.21.2020 if (result == false) { return false; } DLLExportClass::Set_Player_Context(DLLExportClass::GlyphxPlayerIDs[0], true); + DLLExportClass::Cancel_Placement(DLLExportClass::GlyphxPlayerIDs[0], -1, -1); Set_Logic_Page(SeenBuff); VisiblePage.Clear(); Map.Flag_To_Redraw(true); @@ -1860,6 +1859,24 @@ extern "C" __declspec(dllexport) void __cdecl CNC_Start_Mission_Timer(int time) +/************************************************************************************************** +* CNC_Get_Start_Game_Info +* +* History: 8/31/2020 11:37AM - ST +**************************************************************************************************/ +extern "C" __declspec(dllexport) bool __cdecl CNC_Get_Start_Game_Info(uint64 player_id, int &start_location_waypoint_index) +{ + start_location_waypoint_index = 0; + if (!DLLExportClass::Set_Player_Context(player_id)) { + return false; + } + + start_location_waypoint_index = PlayerPtr->StartLocationOverride; + return true; +} + + + /************************************************************************************************** * DLLExportClass::Init -- Init the class * @@ -3682,6 +3699,31 @@ extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Input(InputRequestEnum break; } + // MBL 09.08.2020 - Mod Support + case INPUT_REQUEST_MOD_GAME_COMMAND_1_AT_POSITION: + case INPUT_REQUEST_MOD_GAME_COMMAND_2_AT_POSITION: + case INPUT_REQUEST_MOD_GAME_COMMAND_3_AT_POSITION: + case INPUT_REQUEST_MOD_GAME_COMMAND_4_AT_POSITION: + { + DLLExportClass::Adjust_Internal_View(); + DLLForceMouseX = x1; + DLLForceMouseY = y1; + _Kbd->MouseQX = x1; + _Kbd->MouseQY = y1; + + COORDINATE coord = Map.Pixel_To_Coord(x1, y1); + CELL cell = Coord_Cell(coord); + + if (Map.Pixel_To_Coord(x1, y1)) + { + // TBD: For our ever-awesome Community Modders! + // + // PlayerPtr->Handle_Mod_Game_Command(cell, input_event - INPUT_REQUEST_MOD_GAME_COMMAND_1_AT_POSITION); + } + + break; + } + default: break; } @@ -3806,7 +3848,7 @@ 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 + // 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: @@ -6851,6 +6893,39 @@ extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Debug_Request(DebugRequ } +extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Beacon_Request(BeaconRequestEnum beacon_request_type, uint64 player_id, int pixel_x, int pixel_y) +{ + if (!DLLExportClass::Set_Player_Context(player_id)) { + return; + } + + // Beacons are only available if legacy rendering is disabled + if (DLLExportClass::Legacy_Render_Enabled()) { + return; + } + + // Only allow one beacon per player + for (int index = 0; index < Anims.Count(); ++index) { + AnimClass* anim = Anims.Ptr(index); + if (anim != NULL && + (*anim == ANIM_BEACON || *anim == ANIM_BEACON_VIRTUAL) && + anim->OwnerHouse == PlayerPtr->Class->House) { + delete anim; + } + } + + OutList.Add(EventClass(ANIM_BEACON, PlayerPtr->Class->House, Map.Pixel_To_Coord(pixel_x, pixel_y), PlayerPtr->Get_Allies())); + + // Send sound effect to allies + for (int index = 0; index < Houses.Count(); ++index) { + HouseClass* hptr = Houses.Ptr(index); + if (hptr != NULL && hptr->IsActive && hptr->IsHuman && PlayerPtr->Is_Ally(hptr)) { + DLLExportClass::On_Sound_Effect(hptr, VOC_BEACON, "", 0, 0); + } + } +} + + /************************************************************************************************** * DLLExportClass::Debug_Spawn_All -- Debug spawn all buildable units and structures * diff --git a/TIBERIANDAWN/DLLInterface.h b/TIBERIANDAWN/DLLInterface.h index 2e1c770..327e146 100644 --- a/TIBERIANDAWN/DLLInterface.h +++ b/TIBERIANDAWN/DLLInterface.h @@ -28,7 +28,7 @@ struct CarryoverObjectStruct; ** ** */ -#define CNC_DLL_API_VERSION 0x101 +#include "DLLInterfaceVersion.h" @@ -421,7 +421,11 @@ enum InputRequestEnum { INPUT_REQUEST_SELL_AT_POSITION, INPUT_REQUEST_SELECT_AT_POSITION, INPUT_REQUEST_COMMAND_AT_POSITION, - INPUT_REQUEST_SPECIAL_KEYS + INPUT_REQUEST_SPECIAL_KEYS, + INPUT_REQUEST_MOD_GAME_COMMAND_1_AT_POSITION, + INPUT_REQUEST_MOD_GAME_COMMAND_2_AT_POSITION, + INPUT_REQUEST_MOD_GAME_COMMAND_3_AT_POSITION, + INPUT_REQUEST_MOD_GAME_COMMAND_4_AT_POSITION, }; @@ -472,6 +476,18 @@ enum GameRequestEnum { }; +/************************************************************************************** +** +** Beacon Requests +** +** +*/ +enum BeaconRequestEnum { + INPUT_BEACON_NONE, + INPUT_BEACON_PLACE, +}; + + /************************************************************************************** ** diff --git a/TIBERIANDAWN/DLLInterfaceVersion.h b/TIBERIANDAWN/DLLInterfaceVersion.h new file mode 100644 index 0000000..b05b742 --- /dev/null +++ b/TIBERIANDAWN/DLLInterfaceVersion.h @@ -0,0 +1,33 @@ +// +// Copyright 2020 Electronic Arts Inc. +// +// TiberianDawn.DLL and RedAlert.dll and corresponding source code is free +// software: you can redistribute it and/or modify it under the terms of +// the GNU General Public License as published by the Free Software Foundation, +// either version 3 of the License, or (at your option) any later version. + +// TiberianDawn.DLL and RedAlert.dll and corresponding source code is distributed +// in the hope that it will be useful, but with permitted additional restrictions +// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT +// distributed with this program. You should have received a copy of the +// GNU General Public License along with permitted additional restrictions +// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection + + +#pragma once + +#ifndef DLL_INTERFACE_VERSION_H +#define DLL_INTERFACE_VERSION_H + + +/* +** DLL Interface version +** +** +** +*/ +#define CNC_DLL_API_VERSION 0x102 + + + +#endif //DLL_INTERFACE_VERSION_H diff --git a/TIBERIANDAWN/EVENT.CPP b/TIBERIANDAWN/EVENT.CPP index 9f3380a..e907064 100644 --- a/TIBERIANDAWN/EVENT.CPP +++ b/TIBERIANDAWN/EVENT.CPP @@ -250,7 +250,7 @@ EventClass::EventClass(EventType type, TARGET src, TARGET dest) * HISTORY: * * 05/19/1995 JLB : Created. * *=============================================================================================*/ -EventClass::EventClass(AnimType anim, HousesType owner, COORDINATE coord) +EventClass::EventClass(AnimType anim, HousesType owner, COORDINATE coord, int visible) { ID = Houses.ID(PlayerPtr); Type = ANIMATION; @@ -258,6 +258,7 @@ EventClass::EventClass(AnimType anim, HousesType owner, COORDINATE coord) Data.Anim.What = anim; Data.Anim.Owner = owner; Data.Anim.Where = coord; + Data.Anim.Visible = visible; } @@ -506,14 +507,26 @@ CCDebugString ("C&C95 - Sell packet received\n"); case ANIMATION: anim = new AnimClass(Data.Anim.What, Data.Anim.Where); if (anim) { - //2019/09/19 JAS - Visibility needs to be determined per player - if (Data.Anim.What != ANIM_MOVE_FLASH || Data.Anim.Owner == HOUSE_NONE || Special.IsVisibleTarget) + anim->Set_Owner(Data.Anim.Owner); + + if (Special.IsVisibleTarget) { anim->Set_Visible_Flags(static_cast(-1)); } else { - anim->Set_Visible_Flags(1 << Data.Anim.Owner); + anim->Set_Visible_Flags(static_cast(Data.Anim.Visible)); + } + + /* + ** Beacons have a 30-second kill time. + */ + if (Data.Anim.What == ANIM_BEACON) { + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + + unsigned long long kill_time = ((unsigned long long)ft.dwLowDateTime + ((unsigned long long)ft.dwHighDateTime << 32ULL)) + 300000000ULL; + anim->Kill_At(kill_time); } } break; diff --git a/TIBERIANDAWN/EVENT.H b/TIBERIANDAWN/EVENT.H index b880d87..7834894 100644 --- a/TIBERIANDAWN/EVENT.H +++ b/TIBERIANDAWN/EVENT.H @@ -127,6 +127,7 @@ class EventClass AnimType What; // The animation to create. HousesType Owner; // The owner of the animation (when it matters). COORDINATE Where; // The location to place the animation. + int Visible; // Who this animation is visible to. } Anim; struct { int Value; // general-purpose data @@ -208,7 +209,7 @@ class EventClass EventClass(EventType type, RTTIType object, int id); EventClass(EventType type, RTTIType object, CELL cell); EventClass(EventType type, int id, CELL cell); - EventClass(AnimType anim, HousesType owner, COORDINATE coord); + EventClass(AnimType anim, HousesType owner, COORDINATE coord, int visible = -1); // Process the event. void Execute(void); diff --git a/TIBERIANDAWN/FOOT.CPP b/TIBERIANDAWN/FOOT.CPP index 942c233..eeb5989 100644 --- a/TIBERIANDAWN/FOOT.CPP +++ b/TIBERIANDAWN/FOOT.CPP @@ -305,14 +305,20 @@ bool FootClass::Mark(MarkType mark) /* ** Inform the map of the refresh, occupation, and overlap ** request. + ** Special case is fixed-wing aircraft, which are never placed + ** or picked up since they can never land. */ switch (mark) { case MARK_UP: - Map.Pick_Up(cell, this); + if (What_Am_I() != RTTI_AIRCRAFT || !((AircraftClass*)this)->Class->IsFixedWing) { + Map.Pick_Up(cell, this); + } break; case MARK_DOWN: - Map.Place_Down(cell, this); + if (What_Am_I() != RTTI_AIRCRAFT || !((AircraftClass*)this)->Class->IsFixedWing) { + Map.Place_Down(cell, this); + } break; default: @@ -947,8 +953,10 @@ void FootClass::Approach_Target(void) /* ** If a suitable intermediate location was found, then head toward it. ** Otherwise, head toward the enemy unit directly. + ** Infantry always head towards the target since they can enter a cell + ** in range, but still not be able to hit the target if the spot is out of range. */ - if (found) { + if (found && What_Am_I() != RTTI_INFANTRY) { Assign_Destination(::As_Target(trycell)); } else { Assign_Destination(TarCom); @@ -990,6 +998,20 @@ int FootClass::Mission_Guard_Area(void) ArchiveTarget = ::As_Target(Coord_Cell(Coord)); } + /* + ** Ensure units aren't trying to guard cells off the map. + */ + if (Target_Legal(NavCom) && Is_Target_Cell(NavCom)) { + CELL cell = As_Cell(NavCom); + int x = Cell_X(cell); + int y = Cell_Y(cell); + if (x < Map.MapCellX || y < Map.MapCellY || x >= (Map.MapCellX + Map.MapCellWidth) || y >= (Map.MapCellY + Map.MapCellHeight)) { + Assign_Target(TARGET_NONE); + Assign_Destination(TARGET_NONE); + ArchiveTarget = ::As_Target(Coord_Cell(Coord)); + } + } + /* ** Make sure that the unit has not strayed too far from the home position. ** If it has, then race back to it. @@ -1320,7 +1342,7 @@ void FootClass::Active_Click_With(ActionType action, CELL cell) case ACTION_MOVE: if (AllowVoice) { COORDINATE coord = Map.Pixel_To_Coord(Get_Mouse_X(), Get_Mouse_Y()); - OutList.Add(EventClass(ANIM_MOVE_FLASH, PlayerPtr->Class->House, coord)); + OutList.Add(EventClass(ANIM_MOVE_FLASH, PlayerPtr->Class->House, coord, 1 << PlayerPtr->Class->House)); } // Fall into next case. diff --git a/TIBERIANDAWN/FUNCTION.H b/TIBERIANDAWN/FUNCTION.H index 3d07eb8..72e1565 100644 --- a/TIBERIANDAWN/FUNCTION.H +++ b/TIBERIANDAWN/FUNCTION.H @@ -360,7 +360,7 @@ void const *Get_Radar_Icon(void const *shapefile, int shapenum, int frames, int void CC_Draw_Shape(void const * shapefile, int shapenum, int x, int y, WindowNumberType window, ShapeFlags_Type flags, void const * fadingdata=0, void const * ghostdata=0); // Added for draw intercept. ST - 1/17/2019 12:31PM -void CC_Draw_Shape(ObjectClass *object, void const * shapefile, int shapenum, int x, int y, WindowNumberType window, ShapeFlags_Type flags, void const * fadingdata=0, void const * ghostdata=0, int scale=0x100); +void CC_Draw_Shape(ObjectClass *object, void const * shapefile, int shapenum, int x, int y, WindowNumberType window, ShapeFlags_Type flags, void const * fadingdata=0, void const * ghostdata=0, int scale=0x100, int width=0, int height=0); void CC_Draw_Shape(ObjectClass *object, const char *shape_file_name, void const * shapefile, int shapenum, int x, int y, WindowNumberType window, ShapeFlags_Type flags, void const * fadingdata = 0, void const * ghostdata = 0, char override_owner = HOUSE_NONE); // Added for pip draw intercept - SKY diff --git a/TIBERIANDAWN/GLOBALS.CPP b/TIBERIANDAWN/GLOBALS.CPP index 84486dc..f56744f 100644 --- a/TIBERIANDAWN/GLOBALS.CPP +++ b/TIBERIANDAWN/GLOBALS.CPP @@ -560,7 +560,7 @@ DynamicVectorClass MPlayerFilenum; /*************************************************************************** ** This value determines the max allowable # of players. */ -int MPlayerMax = 4; +int MPlayerMax = 6; /*************************************************************************** diff --git a/TIBERIANDAWN/HOUSE.CPP b/TIBERIANDAWN/HOUSE.CPP index 62bddbf..5a10987 100644 --- a/TIBERIANDAWN/HOUSE.CPP +++ b/TIBERIANDAWN/HOUSE.CPP @@ -1673,14 +1673,13 @@ void HouseClass::Attacked(BuildingClass* source) { Validate(); - bool expired = SpeakAttackDelay.Expired(); - bool spoke = false; + if (SpeakAttackDelay.Expired() && PlayerPtr->Class->House == Class->House) { - // if (SpeakAttackDelay.Expired() && PlayerPtr->Class->House == Class->House) { - if (expired && PlayerPtr->Class->House == Class->House) { - - Speak(VOX_BASE_UNDER_ATTACK, NULL, source ? source->Center_Coord() : 0); - spoke = true; + if (GameToPlay == GAME_NORMAL) { + Speak(VOX_BASE_UNDER_ATTACK, NULL, source ? source->Center_Coord() : 0); + } else { + Speak(VOX_BASE_UNDER_ATTACK, this); + } // 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 @@ -1695,23 +1694,6 @@ void HouseClass::Attacked(BuildingClass* source) HouseTriggers[Class->House][index]->Spring(EVENT_ATTACKED, Class->House); } } - - // MBL 07.07.2020 - CNC Patch 3, fix for not working for all players in MP, per https://jaas.ea.com/browse/TDRA-7249 - // Separated to here as did not want to change any logic around the HouseTriggers[] Spring events - // - if (expired == true && spoke == false) - { - if (GameToPlay != GAME_NORMAL) // Multiplayer - { - Speak(VOX_BASE_UNDER_ATTACK, this); - spoke = true; - - // 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 - } - } - // END MBL 07.07.2020 } @@ -2584,7 +2566,15 @@ ProdFailType HouseClass::Abandon_Production(RTTIType type) if (GameToPlay == GAME_GLYPHX_MULTIPLAYER) { if (IsHuman) { Sidebar_Glyphx_Abandon_Production(type, *factory, this); - // Need to clear pending object here? + + // Need to clear pending object here if legacy renderer enabled + + if (type == RTTI_BUILDINGTYPE || type == RTTI_BUILDING && Map.PendingObjectPtr) { + Map.PendingObjectPtr = 0; + Map.PendingObject = 0; + Map.PendingHouse = HOUSE_NONE; + Map.Set_Cursor_Shape(0); + } } } else { @@ -2692,7 +2682,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->OwnerHouse = Class->House; + if (anim) anim->Set_Owner(Class->House); if (this == PlayerPtr) { Map.IsTargettingMode = false; } diff --git a/TIBERIANDAWN/HOUSE.H b/TIBERIANDAWN/HOUSE.H index 0d77d1c..6df6fe6 100644 --- a/TIBERIANDAWN/HOUSE.H +++ b/TIBERIANDAWN/HOUSE.H @@ -444,6 +444,7 @@ class HouseClass { bool Is_Ally(HousesType house) const; bool Is_Ally(HouseClass const * house) const; bool Is_Ally(ObjectClass const * object) const; + unsigned int Get_Allies(void) const {return Allies;} #ifdef CHEAT_KEYS void Debug_Dump(MonoClass *mono) const; #endif @@ -517,6 +518,9 @@ class HouseClass { void Init_Unit_Trackers(void); void Free_Unit_Trackers(void); + // MBL 09.08.2020 Mod support stub + void Handle_Mod_Game_Command(CELL cell, int mod_command_index); // mod_command_index = 0-3 + #ifdef USE_RA_AI /* ** AI Functions imported from RA diff --git a/TIBERIANDAWN/INFANTRY.CPP b/TIBERIANDAWN/INFANTRY.CPP index 308c332..ee73c09 100644 --- a/TIBERIANDAWN/INFANTRY.CPP +++ b/TIBERIANDAWN/INFANTRY.CPP @@ -677,6 +677,10 @@ void InfantryClass::Per_Cell_Process(bool center) */ if (center && Mission == MISSION_CAPTURE) { TechnoClass * tech = cellptr->Cell_Techno(); + if (tech && tech->As_Target() == NavCom && tech->What_Am_I() == RTTI_BUILDING && !tech->Can_Capture()) { + tech = NULL; + Assign_Destination(TARGET_NONE); + } if (tech && tech->As_Target() == NavCom) { tech->Captured(House); Delete_This(); diff --git a/TIBERIANDAWN/INI.CPP b/TIBERIANDAWN/INI.CPP index 923768f..919958f 100644 --- a/TIBERIANDAWN/INI.CPP +++ b/TIBERIANDAWN/INI.CPP @@ -491,11 +491,15 @@ bool Read_Scenario_Ini(char *root, bool fresh) ** Build the full text of the mission objective. */ for (;;) { - char buff[16]; + int len = (sizeof(BriefingText)-strlen(BriefingText))-1; + if (len <= 0) { + break; + } + char buff[16]; sprintf(buff, "%d", index++); *stage = '\0'; - WWGetPrivateProfileString("Briefing", buff, "", stage, (sizeof(BriefingText)-strlen(BriefingText))-1, buffer); + WWGetPrivateProfileString("Briefing", buff, "", stage, len, buffer); if (strlen(stage) == 0) break; strcat(stage, " "); stage += strlen(stage); @@ -541,6 +545,7 @@ bool Read_Scenario_Ini(char *root, bool fresh) ** NOD09A - delete airstrike trigger when radar destroyed ** NOD10B cell 2015 - LAND_ROCK ** NOD13B - trigger AI production when the player reaches the transports + - fix repeating airstrike trigger ** NOD13C - delete airstrike trigger when radar destroyed */ if (_stricmp(ScenarioName, "scb07ea") == 0) { @@ -572,6 +577,10 @@ bool Read_Scenario_Ini(char *root, bool fresh) CellTriggers[340] = prod; prod->AttachCount++; CellTriggers[404] = prod; prod->AttachCount++; CellTriggers[468] = prod; prod->AttachCount++; + + TriggerClass* xxxx = TriggerClass::As_Pointer("xxxx"); + assert(xxxx != NULL); + xxxx->IsPersistant = TriggerClass::PERSISTANT; } if (_stricmp(ScenarioName, "scb13ec") == 0) { for (int index = 0; index < Buildings.Count(); ++index) { @@ -920,11 +929,15 @@ bool Read_Scenario_Ini_File(char *scenario_file_name, char* bin_file_name, const ** Build the full text of the mission objective. */ for (;;) { - char buff[16]; + int len = (sizeof(BriefingText) - strlen(BriefingText)) - 1; + if (len <= 0) { + break; + } + char buff[16]; sprintf(buff, "%d", index++); *stage = '\0'; - WWGetPrivateProfileString("Briefing", buff, "", stage, (sizeof(BriefingText) - strlen(BriefingText)) - 1, buffer); + WWGetPrivateProfileString("Briefing", buff, "", stage, len, buffer); if (strlen(stage) == 0) break; strcat(stage, " "); stage += strlen(stage); diff --git a/TIBERIANDAWN/IOOBJ.CPP b/TIBERIANDAWN/IOOBJ.CPP index 17ae31d..cfb4517 100644 --- a/TIBERIANDAWN/IOOBJ.CPP +++ b/TIBERIANDAWN/IOOBJ.CPP @@ -1560,6 +1560,10 @@ bool UnitClass::Save(FileClass & file) *=============================================================================================*/ void UnitClass::Code_Pointers(void) { + if (TiberiumUnloadRefinery) { + TiberiumUnloadRefinery = (BuildingClass *)TiberiumUnloadRefinery->As_Target(); + } + TarComClass::Code_Pointers(); } @@ -1584,6 +1588,11 @@ void UnitClass::Code_Pointers(void) *=============================================================================================*/ void UnitClass::Decode_Pointers(void) { + if (TiberiumUnloadRefinery) { + TiberiumUnloadRefinery = As_Building((TARGET)TiberiumUnloadRefinery, false); + Check_Ptr((void *)TiberiumUnloadRefinery, __FILE__, __LINE__); + } + TarComClass::Decode_Pointers(); } diff --git a/TIBERIANDAWN/License.txt b/TIBERIANDAWN/License.txt index e549686..a4adf6d 100644 --- a/TIBERIANDAWN/License.txt +++ b/TIBERIANDAWN/License.txt @@ -1,6 +1,4 @@ -Electronic Arts Inc. released only TiberianDawn.dll, RedAlert.dll and -the Command & Conquer Map Editor and their corresponding source code -under the GPL V3 below, with additional terms at the bottom. +Electronic Arts Inc. released only TiberianDawn.dll and RedAlert.dll and their corresponding source code under the GPL V3 below, with additional terms at the bottom. GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 @@ -711,4 +709,4 @@ PROVIDED BY ELECTRONIC ARTS OR ANY AUTHORIZED REPRESENTATIVE SHALL CREATE A WARRANTY. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF OR LIMITATIONS ON IMPLIED WARRANTIES OR THE LIMITATIONS ON THE APPLICABLE STATUTORY RIGHTS OF A CONSUMER, SO SOME OR ALL OF THE ABOVE EXCLUSIONS AND LIMITATIONS MAY NOT APPLY -TO YOU. +TO YOU. \ No newline at end of file diff --git a/TIBERIANDAWN/MiscAsm.cpp b/TIBERIANDAWN/MiscAsm.cpp index 8865770..6f8b857 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#97 $ + ; $Header: //depot/Projects/Mobius/QA/Project/Run/SOURCECODE/TIBERIANDAWN/MiscAsm.cpp#131 $ ;*************************************************************************** ;** 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/STARTUP.CPP b/TIBERIANDAWN/STARTUP.CPP index bc7df7e..789bf29 100644 --- a/TIBERIANDAWN/STARTUP.CPP +++ b/TIBERIANDAWN/STARTUP.CPP @@ -317,7 +317,7 @@ int DLL_Startup(const char * command_line_in) if (Parse_Command_Line(argc, argv)) { WindowsTimer = new WinTimerClass(60,FALSE); - +#if (0) int time_test = WindowsTimer->Get_System_Tick_Count(); Sleep (1000); if (WindowsTimer->Get_System_Tick_Count() == time_test){ @@ -332,6 +332,7 @@ int DLL_Startup(const char * command_line_in) #endif //FRENCH return(EXIT_FAILURE); } +#endif RawFileClass cfile("CONQUER.INI"); diff --git a/TIBERIANDAWN/TECHNO.CPP b/TIBERIANDAWN/TECHNO.CPP index 4d3993c..9d55d0a 100644 --- a/TIBERIANDAWN/TECHNO.CPP +++ b/TIBERIANDAWN/TECHNO.CPP @@ -1402,7 +1402,7 @@ bool TechnoClass::Evaluate_Object(ThreatType method, int mask, int range, Techno ** If the scan is limited to capturable buildings only, then bail if the examined ** object isn't a capturable building. */ - if ((method & THREAT_CAPTURE) && (otype != RTTI_BUILDING || !((BuildingTypeClass const *)tclass)->IsCaptureable)) { + if ((method & THREAT_CAPTURE) && (otype != RTTI_BUILDING || !object->Can_Capture())) { return(false); } diff --git a/TIBERIANDAWN/TiberianDawn.vcxproj b/TIBERIANDAWN/TiberianDawn.vcxproj index 5c53ff1..51643fd 100644 --- a/TIBERIANDAWN/TiberianDawn.vcxproj +++ b/TIBERIANDAWN/TiberianDawn.vcxproj @@ -206,6 +206,7 @@ + diff --git a/TIBERIANDAWN/TiberianDawn.vcxproj.filters b/TIBERIANDAWN/TiberianDawn.vcxproj.filters index 72cfbe6..b21671c 100644 --- a/TIBERIANDAWN/TiberianDawn.vcxproj.filters +++ b/TIBERIANDAWN/TiberianDawn.vcxproj.filters @@ -627,6 +627,9 @@ Source Files + + Source Files + diff --git a/TIBERIANDAWN/UNIT.CPP b/TIBERIANDAWN/UNIT.CPP index 039577b..e37a36d 100644 --- a/TIBERIANDAWN/UNIT.CPP +++ b/TIBERIANDAWN/UNIT.CPP @@ -396,6 +396,20 @@ void UnitClass::AI(void) return; } + /* + ** Clear the unload refinery if not haresting or entering a refinery. + */ + if (Class->IsToHarvest) { + if (Mission != MISSION_HARVEST) { + if (Mission != MISSION_ENTER || + !In_Radio_Contact() || + Contact_With_Whom()->What_Am_I() != RTTI_BUILDING || + *((BuildingClass*)Contact_With_Whom()) != STRUCT_REFINERY) { + TiberiumUnloadRefinery = NULL; + } + } + } + /* ** Rocket launchers will reload every so often. */ @@ -1198,6 +1212,7 @@ UnitClass::UnitClass(UnitType classid, HousesType house) : Reload = 0; Ammo = Class->MaxAmmo; IsCloakable = Class->IsCloakable; + TiberiumUnloadRefinery = NULL; if (Class->IsAnimating) Set_Rate(Options.Normalize_Delay(3)); /* @@ -2345,29 +2360,47 @@ bool UnitClass::Goto_Tiberium(void) int tiberium = 0; int besttiberium = 0; for (int x = -radius; x <= radius; x++) { + + /* + ** Randomize the corners. + */ + int corner[2]; + int corners[4][2] = { + {x, -radius}, + {x, +radius}, + {-radius, x}, + {+radius, x} + }; + for (int i = 0; i < 3; i++) { + int j = i + rand() / (RAND_MAX / (4 - i) + 1); + memcpy(&corner, &corners[j], sizeof(corner)); + memcpy(&corners[j], &corners[i], sizeof(corner)); + memcpy(&corners[i], corner, sizeof(corner)); + } + cell = center; - tiberium = Tiberium_Check(cell, x, -radius); + tiberium = Tiberium_Check(cell, corners[0][0], corners[0][1]); if (tiberium > besttiberium) { bestcell = cell; besttiberium = tiberium; } cell = center; - tiberium = Tiberium_Check(cell, x, +radius); + tiberium = Tiberium_Check(cell, corners[1][0], corners[1][1]); if (tiberium > besttiberium) { bestcell = cell; besttiberium = tiberium; } cell = center; - tiberium = Tiberium_Check(cell, -radius, x); + tiberium = Tiberium_Check(cell, corners[2][0], corners[2][1]); if (tiberium > besttiberium) { bestcell = cell; besttiberium = tiberium; } cell = center; - tiberium = Tiberium_Check(cell, +radius, x); + tiberium = Tiberium_Check(cell, corners[3][0], corners[3][1]); if (tiberium > besttiberium) { bestcell = cell; besttiberium = tiberium; @@ -2385,6 +2418,96 @@ bool UnitClass::Goto_Tiberium(void) } +struct RefineryData +{ + BuildingClass* Refinery; + int Distance; + int Harvesters; +}; + +static bool operator==(const RefineryData& lhs, const RefineryData& rhs) +{ + return lhs.Refinery == rhs.Refinery; +} + +static bool operator!=(const RefineryData& lhs, const RefineryData& rhs) +{ + return !(lhs == rhs); +} + +static int _refinery_compare(const void * left, const void * right) +{ + const RefineryData& lhs = *reinterpret_cast(left); + const RefineryData& rhs = *reinterpret_cast(right); + if (lhs.Distance < rhs.Distance) { + return -1; + } else if (rhs.Distance < lhs.Distance) { + return 1; + } + return 0; +} + +BuildingClass* UnitClass::Find_Best_Refinery(void) const +{ + static DynamicVectorClass _refineries; + + _refineries.Clear(); + for (int i = 0; i < Buildings.Count(); ++i) { + BuildingClass* refinery = Buildings.Ptr(i); + if (refinery != NULL && + refinery->House == House && + !refinery->IsInLimbo && + *refinery == STRUCT_REFINERY) { + _refineries.Add(RefineryData{ refinery, Distance(refinery), 0 }); + } + } + + // Base case for zero or one refineries. + if (_refineries.Count() == 0) { + return NULL; + } else if (_refineries.Count() == 1) { + return _refineries[0].Refinery; + } + + // Count harvesters going to each refinery as well as the total. + int num_harvesters = 0; + for (int i = 0; i < Units.Count(); ++i) { + UnitClass* unit = Units.Ptr(i); + if (unit->IsActive && unit->Class->IsToHarvest && unit->House == House) { + BuildingClass* refinery = unit->Tiberium_Unload_Refinery(); + if (refinery != NULL) { + int index = _refineries.ID(RefineryData{ refinery }); + assert(index >= 0); + _refineries[index].Harvesters++; + num_harvesters++; + } + } + } + + // Sort by distance (special case for 2 refineries as that's a single swap). + if (_refineries.Count() == 2) { + if (_refineries[0].Distance > _refineries[1].Distance) { + RefineryData temp = _refineries[0]; + _refineries[0] = _refineries[1]; + _refineries[1] = temp; + } + } else { + qsort(&_refineries[0], _refineries.Count(), sizeof(RefineryData), _refinery_compare); + } + + // Evenly distribute harvesters among refineries. + int harvesters_per_refinery = (num_harvesters + _refineries.Count() - 1) / _refineries.Count(); + for (int i = 0; i < _refineries.Count(); ++i) { + if (_refineries[i].Harvesters < harvesters_per_refinery) { + return _refineries[i].Refinery; + } + } + + // Fall back on closest refinery + return _refineries[0].Refinery; +} + + /*********************************************************************************************** * UnitClass::Harvesting -- Harvests tiberium at the current location. * * * @@ -2677,7 +2800,10 @@ int UnitClass::Mission_Harvest(void) Assign_Target(TARGET_NONE); Status = FINDHOME; return(1); - } else if (Goto_Tiberium()) { + } + + TiberiumUnloadRefinery = NULL; + if (Goto_Tiberium()) { IsHarvesting = true; Set_Rate(2); Set_Stage(0); @@ -2739,21 +2865,14 @@ int UnitClass::Mission_Harvest(void) if (!Target_Legal(NavCom)) { /* - ** Find nearby refinery and head to it? + ** Find nearby refinery and head to it. */ - BuildingClass * nearest = Find_Docking_Bay(STRUCT_REFINERY, false); - - /* - ** Since the refinery said it was ok to load, establish radio - ** contact with the refinery and then await docking orders. - */ - if (nearest && Transmit_Message(RADIO_HELLO, nearest) == RADIO_ROGER) { - Status = HEADINGHOME; - } else { - ScenarioInit++; - nearest = Find_Docking_Bay(STRUCT_REFINERY, false); - ScenarioInit--; - if (nearest) { + BuildingClass * nearest = Find_Best_Refinery(); + if (nearest) { + TiberiumUnloadRefinery = nearest; + if (Transmit_Message(RADIO_HELLO, nearest) == RADIO_ROGER) { + Status = HEADINGHOME; + } else { Assign_Destination(::As_Target(nearest->Nearby_Location(this))); } } @@ -2770,6 +2889,7 @@ int UnitClass::Mission_Harvest(void) return(1); case GOINGTOIDLE: + TiberiumUnloadRefinery = NULL; Assign_Mission(MISSION_GUARD); break; diff --git a/TIBERIANDAWN/UNIT.H b/TIBERIANDAWN/UNIT.H index e16e56e..32f433d 100644 --- a/TIBERIANDAWN/UNIT.H +++ b/TIBERIANDAWN/UNIT.H @@ -84,6 +84,8 @@ class UnitClass : public TarComClass bool Harvesting(void); void APC_Close_Door(void); void APC_Open_Door(void); + BuildingClass* Tiberium_Unload_Refinery(void) const {return TiberiumUnloadRefinery;} + BuildingClass* Find_Best_Refinery(void) const; /* ** Query functions. @@ -199,10 +201,15 @@ class UnitClass : public TarComClass */ TCountDownTimerClass HarvestTimer; + /* + ** This is the refinery a harvester is interested in unloading at. + */ + BuildingClass* TiberiumUnloadRefinery; + /* ** 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]; /* ** This contains the value of the Virtual Function Table Pointer diff --git a/TIBERIANDAWN/WIN32LIB/DrawMisc.cpp b/TIBERIANDAWN/WIN32LIB/DrawMisc.cpp index c0be513..40d7d95 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#97 $ +; $Header: //depot/Projects/Mobius/QA/Project/Run/SOURCECODE/TIBERIANDAWN/WIN32LIB/DrawMisc.cpp#131 $ ;*************************************************************************** ;** 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 68b39d8..8b8c802 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#97 $ + ; $Header: //depot/Projects/Mobius/QA/Project/Run/SOURCECODE/TIBERIANDAWN/WIN32LIB/FACINGFF.h#131 $ ;*************************************************************************** ;** 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 ** ;***************************************************************************