CnC_Remastered_Collection/REDALERT/DLLInterface.cpp

8732 lines
248 KiB
C++
Raw Normal View History

//
// 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
/*
** DLLInterfac.cpp
**
** This is where we implement the API expected by the Instance Server.
**
** The Instance Server will pass in requests for loading and starting maps, control input from players,
** and requests for game simulation and rendering states.
**
**
*/
// Exception handling isn't enabled
#pragma warning (disable : 4530) //warning C4530: C++ exception handler used, but unwind semantics are not enabled.
#include <string>
#include <vector>
#include <set>
#include "function.h"
#include "externs.h"
#include "DLLInterface.h"
#include "Gadget.h"
#include "defines.h" // VOC_COUNT, VOX_COUNT
#include "SidebarGlyphx.h"
#include <chrono>
/*
** Externs
*/
extern int DLL_Startup(const char * command_line);
extern void Reallocate_Big_Shape_Buffer(void);
extern bool ProgEndCalled;
extern int Write_PCX_File(char* name, GraphicViewPortClass& pic, unsigned char* palette );
extern void Color_Cycle(void);
bool Debug_Write_Shape_Type(const ObjectTypeClass *type, int shapenum);
bool Debug_Write_Shape(const char *file_name, void const * shapefile, int shapenum, int flags = 0, void const * ghostdata = NULL);
typedef void (__cdecl* CNC_Event_Callback_Type)(const EventCallbackStruct &event);
typedef unsigned __int64 uint64;
typedef __int64 int64;
/*
** Audio defines
**
**
**
**
**
*/
// For compatibility with Watcom in audio enums
#pragma warning (disable : 4091)
// From RedAlert\Audio.cpp
enum ContextType;
extern struct SoundEffectNameStruct {
char const *Name; // Digitized voice file name.
int Priority; // Playback priority of this sample.
ContextType Where; // In what game context does this sample exist.
} SoundEffectName[VOC_COUNT];
// From RedAlert\Audio.cpp
extern char const* Speech[VOX_COUNT];
// From RedAlert\Audio.cpp
typedef enum {
IN_NOVAR, // No variation or alterations allowed.
IN_VAR // Infantry variance response modification.
};
/*
** Misc defines
**
**
**
**
**
*/
#define GAME_TO_PLAY Session.Type
#define MULTIPLAYER_COUNT Session.Players.Count()
#define KEYBOARD Keyboard
#define RANDOM_START_POSITION 0x7f
Command & Conquer Remastered post-launch patch Improvements to harvester resource finding logic. Don't allow the Advanced Comm Center to be capturable in skirmish or multiplayer. Increased failed pathfinding fudge factor. Buildings accept the Guard command if they can attack. Don't allow force capturing of ally structures. Fixes for laser Orcas in S3cr3t M1ss10n. Properly restore them after save. Reset Orcas after loads. Fixed flag animation rendering in CTF. Potentially fix a crash if aircraft are destroyed outside the map bounds. Fixed legacy Obelisk line rendering. Fix out-of-bounds crash in TD; issue was already fixed in RA. Disable capture flag on Commandos. Drop the flag when entering the limbo state. Fixed end game race condition, winning team ID is always sent before individual player win/lose messages. Fixed Chan spawn on SCB10EA. Don't show enter cursor for enemy units on refineries and repair pads. Changing right-click support for first put building on hold, and then subsequenct right-clicks to decrement that queue count for 1x or 5x; Then, 1x or 5x Left click will resume from hold. Don't debug reveal legacy rendering when a player is defeated. Fixed crash when loading saves of custom campaign maps. Reset harvester archived target when given a direct harvest order. Prevent NOD cargo planes from being force attacked. Fixed unit selection on load. Migrated queued repair pad functionality from RA to TD. Randomly animate infantry in area guard mode. Fixed crash accessing inactive objects. Added some walls in SCG08EB to prevent civilians from killing themselves. TD + RA: Audio: Overiding "Our base is under attack" cooldown timing from legacy from 2 minutes to 30 seconds, so it will be heard 4x as often. Fixed adjacent cell out-of-bounds crash issues. Kill player on disconnect Fixed and improved build time calculations to be consistent between TD and RA. Don't show health bars for cloaked Stealth Tanks in the legacy renderer. Fix selection of individual control groups after mixed selection. More adjustments to SCG08EB; switch C7 to C5, and add civilian AI to avoid Tiberium. Extra safety checks for units that have no weapons and aircraft that can't hunt. Fix loading of multiple infantry onto an APC. Additional safety checks for invalid coordinates. Prevent units from being instantly repaired. Fix map passability. Fail Allied mission 5B if the spy re-boards the starting transport (matches 5A and 5C behavior). Fixed multiplayer formation move causing units to move at light speed. Ignore movement destination checks if a unit is part of a mission-driven team. Fix buffer overrun crash. Ignore mines when determining win conditions. Fixed river passability in Blue Lakes.
2020-06-23 04:43:21 +12:00
#define KILL_PLAYER_ON_DISCONNECT 1
/*
** DLL Interface
**
**
**
**
**
*/
extern "C" __declspec(dllexport) unsigned int __cdecl CNC_Version(unsigned int version_in);
extern "C" __declspec(dllexport) void __cdecl CNC_Init(const char *command_line, CNC_Event_Callback_Type event_callback);
extern "C" __declspec(dllexport) void __cdecl CNC_Config(const CNCRulesDataStruct& rules);
extern "C" __declspec(dllexport) void __cdecl CNC_Add_Mod_Path(const char *mod_path);
extern "C" __declspec(dllexport) bool __cdecl CNC_Get_Visible_Page(unsigned char *buffer_in, unsigned int &width, unsigned int &height);
extern "C" __declspec(dllexport) bool __cdecl CNC_Get_Palette(unsigned char(&palette_in)[256][3]);
extern "C" __declspec(dllexport) bool __cdecl CNC_Start_Instance(int scenario_index, int build_level, const char *faction, const char *game_type, const char *content_directory, int sabotaged_structure, const char *override_map_name);
extern "C" __declspec(dllexport) bool __cdecl CNC_Start_Instance_Variation(int scenario_index, int scenario_variation, int scenario_direction, int build_level, const char *faction, const char *game_type, const char *content_directory, int sabotaged_structure, const char *override_map_name);
extern "C" __declspec(dllexport) bool __cdecl CNC_Start_Custom_Instance(const char* content_directory, const char* directory_path, const char* scenario_name, int build_level, bool multiplayer);
extern "C" __declspec(dllexport) bool __cdecl CNC_Advance_Instance(uint64 player_id);
extern "C" __declspec(dllexport) bool __cdecl CNC_Get_Game_State(GameStateRequestEnum state_type, uint64 player_id, unsigned char *buffer_in, unsigned int buffer_size);
extern "C" __declspec(dllexport) bool __cdecl CNC_Read_INI(int scenario_index, int scenario_variation, int scenario_direction, const char *content_directory, const char *override_map_name, char *ini_buffer, int _ini_buffer_size);
extern "C" __declspec(dllexport) void __cdecl CNC_Set_Home_Cell(int x, int y, uint64 player_id);
extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Game_Request(GameRequestEnum request_type);
extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Game_Settings_Request(int health_bar_display_mode, int resource_bar_display_mode);
extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Input(InputRequestEnum mouse_event, unsigned char special_key_flags, uint64 player_id, int x1, int y1, int x2, int y2);
extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Structure_Request(StructureRequestEnum request_type, uint64 player_id, int object_id);
extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Unit_Request(UnitRequestEnum request_type, uint64 player_id);
extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Sidebar_Request(SidebarRequestEnum request_type, uint64 player_id, int buildable_type, int buildable_id, short cell_x, short cell_y);
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) 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);
extern "C" __declspec(dllexport) bool __cdecl CNC_Save_Load(bool save, const char *file_path_and_name, const char *game_type);
extern "C" __declspec(dllexport) void __cdecl CNC_Set_Difficulty(int difficulty);
extern "C" __declspec(dllexport) void __cdecl CNC_Restore_Carryover_Objects(const CarryoverObjectStruct* objects);
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);
/*
** Class to implement the interface, and contain additional game state required by the conversion from peer/peer to client/server
**
**
**
**
**
*/
class DLLExportClass {
public:
static void Init(void);
static void Shutdown(void);
static void Config(const CNCRulesDataStruct& rules);
static void Add_Mod_Path(const char *mod_path);
static void Set_Home_Cell(int x, int y, uint64 player_id);
static void Set_Content_Directory(const char *dir);
static bool Get_Layer_State(uint64 player_id, unsigned char *buffer_in, unsigned int buffer_size);
static bool Get_Sidebar_State(uint64 player_id, unsigned char *buffer_in, unsigned int buffer_size);
static bool Start_Construction(uint64 player_id, int buildable_type, int buildable_id);
static bool Hold_Construction(uint64 player_id, int buildable_type, int buildable_id);
static bool Cancel_Construction(uint64 player_id, int buildable_type, int buildable_id);
static bool Start_Placement(uint64 player_id, int buildable_type, int buildable_id);
static BuildingClass *Get_Pending_Placement_Object(uint64 player_id, int buildable_type, int buildable_id);
static bool Get_Placement_State(uint64 player_id, unsigned char *buffer_in, unsigned int buffer_size);
static void Convert_Type(const ObjectClass *object, CNCObjectStruct &object_out);
static void DLL_Draw_Intercept(int shape_number, int x, int y, int width, int height, int flags, const ObjectClass *object, DirType rotation, long scale, const char *shape_file_name = NULL, char override_owner = HOUSE_NONE);
static void DLL_Draw_Pip_Intercept(const ObjectClass* object, int pip);
static bool Place(uint64 player_id, int buildable_type, int buildable_id, short cell_x, short cell_y);
static bool Cancel_Placement(uint64 player_id, int buildable_type, int buildable_id);
static bool Place_Super_Weapon(uint64 player_id, int buildable_type, int buildable_id, int x, int y);
static bool Create_Control_Group(unsigned char control_group_index);
static bool Add_To_Control_Group(unsigned char control_group_index);
static bool Toggle_Control_Group_Selection(unsigned char control_group_index);
static bool Construction_Action(SidebarRequestEnum construction_action, uint64 player_id, int buildable_type, int buildable_id);
static bool MP_Construction_Action(SidebarRequestEnum construction_action, uint64 player_id, int buildable_type, int buildable_id);
static bool Passes_Proximity_Check(CELL cell_in, BuildingTypeClass *placement_type, unsigned char* placement_distance);
static void Calculate_Start_Positions(void);
static void Computer_Message(bool last_player_taunt);
static void Repair_Mode(uint64 player_id);
static void Repair(uint64 player_id, int object_id);
static void Sell_Mode(uint64 player_id);
static void Sell(uint64 player_id, int object_id);
static void Repair_Sell_Cancel(uint64 player_id);
static void Scatter_Selected(uint64 player_id);
static void Select_Next_Unit(uint64 player_id);
static void Select_Previous_Unit(uint64 player_id);
static void Selected_Guard_Mode(uint64 player_id);
static void Selected_Stop(uint64 player_id);
static void Team_Units_Formation_Toggle_On(uint64 player_id);
static void Units_Queued_Movement_Toggle(uint64 player_id, bool toggle);
static void Cell_Class_Draw_It(CNCDynamicMapStruct *dynamic_map, int &entry_index, CellClass *cell_ptr, int xpixel, int ypixel, bool debug_output);
static bool Get_Dynamic_Map_State(uint64 player_id, unsigned char *buffer_in, unsigned int buffer_size);
static bool Get_Shroud_State(uint64 player_id, unsigned char *buffer_in, unsigned int buffer_size);
static bool Get_Occupier_State(uint64 player_id, unsigned char *buffer_in, unsigned int buffer_size);
static bool Get_Player_Info_State(uint64 player_id, unsigned char *buffer_in, unsigned int buffer_size);
static void Set_Event_Callback(CNC_Event_Callback_Type event_callback) {EventCallback = event_callback;}
static void Debug_Spawn_Unit(const char *object_name, int x, int y, bool enemy = false);
static void Debug_Spawn_All(int x, int y);
static bool Try_Debug_Spawn_Unlimbo(TechnoClass *techno, int &cell_x, int &cell_y);
static void Debug_Kill_Unit(int x, int y);
static void Debug_Heal_Unit(int x, int y);
static void On_Play_Movie(const char * movie_name, ThemeType theme, bool immediate);
static void On_Display_Briefing_Text();
static void On_Sound_Effect(const HouseClass* player_ptr, int sound_effect_index, const char* extension, int variation, COORDINATE coord);
static void On_Speech(const HouseClass* player_ptr, int speech_index);
static void On_Message(const HouseClass* player_ptr, const char* message, float timeout_seconds, EventCallbackMessageEnum message_type, int64 message_id);
static void On_Update_Map_Cell(int cell_x, int cell_y, const char* template_type_name);
static void On_Special_Weapon_Targetting(const HouseClass* player_ptr, SpecialWeaponType weapon_type);
static void On_Ping(const HouseClass* player_ptr, COORDINATE coord);
static void On_Game_Over(uint64 glyphx_player_id, bool player_wins);
static void On_Multiplayer_Game_Over(void);
static void On_Debug_Output(const char *debug_text);
static void On_Achievement(const HouseClass* player_ptr, const char *achievement_type, const char *achievement_reason);
static void On_Center_Camera(const HouseClass* player_ptr, int coord_x, int coord_y);
static void Glyphx_Queue_AI();
static void Store_Carryover_Objects();
static void Force_Human_Team_Wins(uint64 quitting_player_id);
/*
** Player context switching for input/output
*/
static bool Set_Player_Context(uint64 glyphx_player, bool force = false);
static void Reset_Player_Context(void);
static void Adjust_Internal_View(bool force_ignore_view_constraints = false);
static void Logic_Switch_Player_Context(ObjectClass *object);
static void Logic_Switch_Player_Context(HouseClass *house);
static void Refresh_Player_Control_Flags(void);
static __int64 Get_GlyphX_Player_ID(const HouseClass *house);
static void Recalculate_Placement_Distances();
static void Reset_Sidebars(void);
static SidebarGlyphxClass *Get_Current_Context_Sidebar(HouseClass *player_ptr = NULL);
static uint64 GlyphxPlayerIDs[MAX_PLAYERS];
static const void *Get_Shadow_Shapes(void) {return Map.ShadowShapes;}
static const unsigned char *Get_Shadow_Trans(void) {return &Map.ShadowTrans[0];}
static bool Legacy_Render_Enabled(void);
static bool Get_Input_Key_State(KeyNumType key);
static void Set_Special_Key_Flags(unsigned char special_key_flags);
static void Clear_Special_Key_Flags();
static bool Load(Straw &file);
static bool Save(Pipe &file);
static void Code_Pointers(void);
static void Decode_Pointers(void);
static bool Get_Game_Over() { return GameOver; }
private:
static void Calculate_Single_Player_Score(EventCallbackStruct&);
static int RA_Calculate_Leadership( HousesType player_house, int units_lost, int buildings_lost );
static int RA_Calculate_Economy( long available_money, int stolen_buildings_credits, unsigned harvested_credits, long initial_credits );
static int RA_Calculate_Score( int uspoints, int leadership, int economy );
static void Convert_Action_Type(ActionType type, ObjectClass* object, TARGET target, DllActionTypeEnum& dll_type);
static void Convert_Special_Weapon_Type(SpecialWeaponType weapon_type, DllSuperweaponTypeEnum& dll_weapon_type, char* weapon_name);
static void Fill_Sidebar_Entry_From_Special_Weapon(CNCSidebarEntryStruct& sidebar_entry_out, SuperClass*& super_weapon_out, SpecialWeaponType weapon_type);
static void Calculate_Placement_Distances(BuildingTypeClass* placement_type, unsigned char* placement_distance);
static int CurrentDrawCount;
static int TotalObjectCount;
static int SortOrder;
static int ExportLayer;
static CNCObjectListStruct *ObjectList;
static CNC_Event_Callback_Type EventCallback;
static int CurrentLocalPlayerIndex;
static bool GameOver;
static std::set<int64> MessagesSent;
/*
** Pseudo sidebars for players in multiplayer
*/
static SidebarGlyphxClass MultiplayerSidebars[MAX_PLAYERS];
static CELL MultiplayerStartPositions[MAX_PLAYERS];
static BuildingTypeClass *PlacementType[MAX_PLAYERS];
static unsigned char PlacementDistance[MAX_PLAYERS][MAP_CELL_TOTAL];
static unsigned char SpecialKeyFlags[MAX_PLAYERS];
/*
** Mod directories
*/
static DynamicVectorClass<char *> ModSearchPaths;
};
/*
** DLLExportClass static data
**
**
**
**
**
*/
int DLLExportClass::CurrentDrawCount = 0;
int DLLExportClass::TotalObjectCount = 0;
int DLLExportClass::SortOrder = 0;
int DLLExportClass::ExportLayer = 0;
CNCObjectListStruct *DLLExportClass::ObjectList = NULL;
SidebarGlyphxClass DLLExportClass::MultiplayerSidebars [MAX_PLAYERS];
uint64 DLLExportClass::GlyphxPlayerIDs[MAX_PLAYERS] = {0xffffffffl};
int DLLExportClass::CurrentLocalPlayerIndex = -1;
CELL DLLExportClass::MultiplayerStartPositions[MAX_PLAYERS];
BuildingTypeClass *DLLExportClass::PlacementType[MAX_PLAYERS];
unsigned char DLLExportClass::PlacementDistance[MAX_PLAYERS][MAP_CELL_TOTAL];
unsigned char DLLExportClass::SpecialKeyFlags[MAX_PLAYERS] = { 0U };
DynamicVectorClass<char *> DLLExportClass::ModSearchPaths;
std::set<int64> DLLExportClass::MessagesSent;
bool DLLExportClass::GameOver = false;
/*
** Global variables
**
**
**
**
**
*/
int DLLForceMouseX = 0;
int DLLForceMouseY = 0;
CNC_Event_Callback_Type DLLExportClass::EventCallback = NULL;
// Needed to accomodate Glyphx client sidebar. ST - 4/12/2019 5:29PM
int GlyphXClientSidebarWidthInLeptons = 0;
bool MPlayerIsHuman[MAX_PLAYERS];
int MPlayerTeamIDs[MAX_PLAYERS];
int MPlayerStartLocations[MAX_PLAYERS];
bool MPSuperWeaponDisable = false;
bool ShareAllyVisibility = true;
bool UseGlyphXStartLocations = true;
SpecialClass* SpecialBackup = NULL;
int GetRandSeed()
{
using namespace std::chrono;
time_point<system_clock> time_since_epoch = system_clock::now();
auto microseconds_since_epoch = floor<std::chrono::microseconds>(time_since_epoch);
return abs( static_cast<int>(microseconds_since_epoch.time_since_epoch().count()) );
}
void Play_Movie_GlyphX(const char * movie_name, ThemeType theme, bool immediate = false)
{
if ((movie_name[0] == 'x' || movie_name[0] == 'X') && movie_name[1] == 0) {
return;
}
DLLExportClass::On_Play_Movie(movie_name, theme, immediate);
}
void Display_Briefing_Text_GlyphX()
{
DLLExportClass::On_Display_Briefing_Text();
}
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()
//
#if 1
/*
** Fetch a pointer to the sound effect data. Modify the sound as appropriate and desired.
*/
char const * ext = ""; // ".AUD";
if (SoundEffectName[voc].Where == IN_VAR) {
/*
** If there is no forced house, then use the current player
** act like house.
*/
if (house == HOUSE_NONE) {
house = PlayerPtr->ActLike;
}
/*
** Change the extension based on the variation and house accent requested.
*/
if (((1 << house) & HOUSEF_ALLIES) != 0) {
/*
** For infantry, use a variation on the response. For vehicles, always
** use the vehicle response table.
*/
if (variation < 0) {
if (ABS(variation) % 2) {
ext = ".V00";
} else {
ext = ".V02";
}
} else {
if (variation % 2) {
ext = ".V01";
} else {
ext = ".V03";
}
}
} else {
if (variation < 0) {
if (ABS(variation) % 2) {
ext = ".R00";
} else {
ext = ".R02";
}
} else {
if (variation % 2) {
ext = ".R01";
} else {
ext = ".R03";
}
}
}
}
#endif
// END MBL
DLLExportClass::On_Sound_Effect(PlayerPtr, sound_index, ext, variation, coord);
#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()
//
char const * ext = ""; // ".AUD";
#ifdef TIBERIAN_DAWN
if (Special.IsJuvenile && SoundEffectName[voc].Where == IN_JUV) {
ext = ".JUV";
} else {
#endif
if (SoundEffectName[voc].Where == IN_VAR) {
/*
** For infantry, use a variation on the response. For vehicles, always
** use the vehicle response table.
*/
if (variation < 0) {
if (ABS(variation) % 2) {
ext = ".V00";
} else {
ext = ".V02";
}
} else {
if (variation % 2) {
ext = ".V01";
} else {
ext = ".V03";
}
}
}
#ifdef TIBERIAN_DAWN
}
#endif
// END MBL
DLLExportClass::On_Sound_Effect(PlayerPtr, sound_index, ext, variation, coord);
#endif
}
// 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);
}
else
{
DLLExportClass::On_Speech(house, speech_index);
}
}
void On_Message(const char* message, float timeout_seconds, int64 id)
{
DLLExportClass::On_Message(PlayerPtr, message, timeout_seconds, MESSAGE_TYPE_DIRECT, id);
}
void On_Update_Map_Cell(int cell_x, int cell_y, const char* template_type_name)
{
DLLExportClass::On_Update_Map_Cell(cell_x, cell_y, template_type_name);
}
void On_Special_Weapon_Targetting(const HouseClass* player_ptr, SpecialWeaponType weapon_type)
{
DLLExportClass::On_Special_Weapon_Targetting(player_ptr, weapon_type);
}
void On_Ping(const HouseClass* player_ptr, COORDINATE coord)
{
DLLExportClass::On_Ping(player_ptr, coord);
}
void On_Defeated_Message(const char* message, float timeout_seconds)
{
DLLExportClass::On_Message(PlayerPtr, message, timeout_seconds, MESSAGE_TYPE_PLAYER_DEFEATED, -1);
}
void GlyphX_Debug_Print(const char *debug_text)
{
DLLExportClass::On_Debug_Output(debug_text);
}
void On_Achievement_Event(const HouseClass* player_ptr, const char *achievement_type, const char *achievement_reason)
{
DLLExportClass::On_Achievement(player_ptr, achievement_type, achievement_reason);
}
/**************************************************************************************************
* CNC_Version -- Check DLL/Server version
*
* In: Version expected
*
* Out: Actual version
*
*
*
* History: 4/9/2020 2:12PM - ST
**************************************************************************************************/
extern "C" __declspec(dllexport) unsigned int __cdecl CNC_Version(unsigned int version_in)
{
// Unreferenced, but potentially useful to know which version the server is expecting
version_in;
return CNC_DLL_API_VERSION;
}
/**************************************************************************************************
* CNC_Init -- Initialize the .DLL
*
* In: Command line
*
* Out:
*
*
*
* History: 1/3/2019 11:33AM - ST
**************************************************************************************************/
extern "C" __declspec(dllexport) void __cdecl CNC_Init(const char *command_line, CNC_Event_Callback_Type event_callback)
{
DLLExportClass::Set_Content_Directory(NULL);
DLL_Startup(command_line);
// MBL
DLLExportClass::Set_Event_Callback( event_callback );
DLLExportClass::Init();
}
/**************************************************************************************************
* DLL_Shutdown -- Shutdown the .DLL
*
* In:
*
* Out:
*
*
*
* History: 2/20/2020 1:58PM - ST
**************************************************************************************************/
void DLL_Shutdown(void)
{
DLLExportClass::Shutdown();
}
/**************************************************************************************************
* CNC_Config -- Configure the plugin
*
* In: Configuration data
*
* Out:
*
*
*
* History: 10/03/2019 - SKY
**************************************************************************************************/
extern "C" __declspec(dllexport) void __cdecl CNC_Config(const CNCRulesDataStruct& rules)
{
DLLExportClass::Config(rules);
}
/**************************************************************************************************
* CNC_Add_Mod_Path -- Add a path to load mod files from
*
* In: Path to load mods from
*
* Out:
*
*
*
* History: 2/20/2020 2:04PM - ST
**************************************************************************************************/
extern "C" __declspec(dllexport) void __cdecl CNC_Add_Mod_Path(const char *mod_path)
{
DLLExportClass::Add_Mod_Path(mod_path);
}
/**************************************************************************************************
* CNC_Get_Visible_Page -- Get the screen buffer 'SeenBuff' from the game
*
* In: If buffer_in is null, just return info about page
*
* Out: false if not changed since last call
*
*
*
* History: 1/3/2019 11:33AM - ST
**************************************************************************************************/
extern "C" __declspec(dllexport) bool __cdecl CNC_Get_Visible_Page(unsigned char *buffer_in, unsigned int &width, unsigned int &height)
{
if (!DLLExportClass::Legacy_Render_Enabled() || (buffer_in == NULL)) {
return false;
}
/*
** Assume the seen page viewport is the same size as the page
*/
GraphicBufferClass *gbuffer = HidPage.Get_Graphic_Buffer();
if (gbuffer == NULL) {
return false;
}
int view_port_width = Map.MapCellWidth * CELL_PIXEL_W;
int view_port_height = Map.MapCellHeight * CELL_PIXEL_H;
if (view_port_width == 0 || view_port_height == 0) {
return false;
}
unsigned char *raw_buffer = (unsigned char*) gbuffer->Get_Buffer();
long raw_size = gbuffer->Get_Size();
if (raw_buffer == NULL || gbuffer->Get_Width() < view_port_width || gbuffer->Get_Height() < view_port_height) {
return false;
}
width = view_port_width;
height = view_port_height;
int pitch = gbuffer->Get_Width();
for (int i = 0; i < view_port_height; ++i, buffer_in += view_port_width, raw_buffer += pitch) {
memcpy(buffer_in, raw_buffer, view_port_width);
}
return true;
}
extern "C" __declspec(dllexport) bool __cdecl CNC_Get_Palette(unsigned char(&palette_in)[256][3])
{
memcpy(palette_in, CurrentPalette, sizeof(palette_in));
return true;
}
/**************************************************************************************************
* CNC_Set_Multiplayer_Data -- Set up for a multiplayer match
*
* In: Multiplayer data
*
* Out: false if data is bad
*
*
*
* History: 1/7/2019 5:20PM - ST
**************************************************************************************************/
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)
{
if (num_players <= 0) {
return false;
}
if (num_players > min(MAX_PLAYERS, max_players)) {
return false;
}
DLLExportClass::Init();
Session.Options.Bases = game_options.MPlayerBases; // 1 = bases are on for this scenario
Session.Options.Credits = game_options.MPlayerCredits; // # credits everyone gets
Session.Options.Tiberium = game_options.MPlayerTiberium; // 1 = tiberium enabled for this scenario
Session.Options.Goodies = game_options.MPlayerGoodies; // 1 = goodies enabled for this scenario
Session.Options.Ghosts = game_options.MPlayerGhosts; // 1 = houses with no players will still play
//MPlayerSolo = game_options.MPlayerSolo; // 1 = allows a single-player net game
Session.Options.UnitCount = game_options.MPlayerUnitCount; // # units for non-base multiplayer scenarios
Special.IsShadowGrow = game_options.MPlayerShadowRegrow;
Special.IsCaptureTheFlag = game_options.CaptureTheFlag;
if (Session.Options.Tiberium) {
Special.IsTGrowth = 1;
Special.IsTSpread = 1;
} else {
Special.IsTGrowth = 0;
Special.IsTSpread = 0;
}
Session.Options.ScenarioIndex = scenario_index;
Special.IsMCVDeploy = game_options.IsMCVDeploy;
Special.UseMCVDeploy = true;
MPSuperWeaponDisable = !game_options.EnableSuperweapons; // Are superweapons available
//Session.Options.AIPlayers = WWGetPrivateProfileInt("Options", "AI", 0, buffer); //Number of AI players
Special.IsEarlyWin = game_options.DestroyStructures;
Special.ModernBalance = game_options.ModernBalance;
/*
** Enable Counterstrike/Aftermath units
*/
OverrideNewUnitsEnabled = game_options.MPlayerAftermathUnits;
while (Session.Players.Count() > 0) {
delete Session.Players[0];
Session.Players.Delete(Session.Players[0]);
}
for (int i=0 ; i<num_players ; i++) {
CNCPlayerInfoStruct &player_info = player_list[i];
NodeNameType *who = new NodeNameType;
strncpy(who->Name, player_info.Name, MPLAYER_NAME_MAX);
who->Name[MPLAYER_NAME_MAX - 1] = 0; // Make sure it's terminated
who->Player.House = (HousesType)player_info.House;
who->Player.Color = (PlayerColorType) player_info.ColorIndex;
Session.Players.Add (who);
/*
** Player IDs are done differently in Red Alert vs. TD.
** In TD, the ID is created from the house/color combination
** In RA, the ID is HOUSE_MULTI1 + the index into the Session.Players vector
*/
DLLExportClass::GlyphxPlayerIDs[i] = player_info.GlyphxPlayerID;
MPlayerIsHuman[i] = !player_info.IsAI;
MPlayerTeamIDs[i] = player_info.Team;
MPlayerStartLocations[i] = player_info.StartLocationIndex;
/*
** Temp fix for custom maps that don't have valid start positions set from matchmaking
*/
if (i > 0 && MPlayerStartLocations[i] == 0 && MPlayerStartLocations[0] == 0) {
MPlayerStartLocations[i] = i;
}
}
/*
** Force smart defense always on for multiplayer/skirmish
*/
Rule.IsSmartDefense = true;
/*
** Backup special
*/
if (SpecialBackup != NULL) {
memcpy(SpecialBackup, &Special, sizeof(SpecialClass));
}
return true;
}
extern "C" __declspec(dllexport) bool __cdecl CNC_Clear_Object_Selection(uint64 player_id)
{
if (!DLLExportClass::Set_Player_Context(player_id)) {
return false;
}
Unselect_All();
return true;
}
extern "C" __declspec(dllexport) bool __cdecl CNC_Select_Object(uint64 player_id, int object_type_id, int object_to_select_id)
{
if (!DLLExportClass::Set_Player_Context(player_id)) {
return false;
}
switch (object_type_id)
{
case INFANTRY:
{
for (int index = 0; index < Infantry.Count(); index++) {
InfantryClass * obj = Infantry.Ptr(index);
if (obj
&& !obj->IsInLimbo
&& obj->House->IsPlayerControl
&& Infantry.ID((InfantryClass*)obj) == object_to_select_id)
{
if (!obj->Is_Selected_By_Player())
{
obj->Select();
AllowVoice = false;
}
return true;
}
}
}
break;
case UNIT:
{
for (int index = 0; index < Units.Count(); index++) {
UnitClass * obj = Units.Ptr(index);
if (obj
&& !obj->IsInLimbo
&& obj->House->IsPlayerControl
&& Units.ID((UnitClass*)obj) == object_to_select_id)
{
if (!obj->Is_Selected_By_Player())
{
obj->Select();
AllowVoice = false;
}
return true;
}
}
}
break;
case AIRCRAFT:
{
for (int index = 0; index < Aircraft.Count(); index++) {
AircraftClass * obj = Aircraft.Ptr(index);
if (obj
&& !obj->IsInLimbo
&& obj->House->IsPlayerControl
&& Aircraft.ID((AircraftClass*)obj) == object_to_select_id)
{
if (!obj->Is_Selected_By_Player())
{
obj->Select();
AllowVoice = false;
}
return true;
}
}
}
break;
case BUILDING:
{
for (int index = 0; index < Buildings.Count(); index++) {
BuildingClass * obj = Buildings.Ptr(index);
if (obj
&& !obj->IsInLimbo
&& obj->House->IsPlayerControl
&& Buildings.ID((BuildingClass*)obj) == object_to_select_id)
{
if (!obj->Is_Selected_By_Player())
{
obj->Select();
AllowVoice = false;
}
return true;
}
}
}
break;
case VESSEL:
{
for (int index = 0; index < Vessels.Count(); index++) {
VesselClass * obj = Vessels.Ptr(index);
if (obj
&& !obj->IsInLimbo
&& obj->House->IsPlayerControl
&& Vessels.ID((VesselClass*)obj) == object_to_select_id)
{
if (!obj->Is_Selected_By_Player())
{
obj->Select();
AllowVoice = false;
}
return true;
}
}
}
break;
}
return false;
}
/**************************************************************************************************
* GlyphX_Assign_Houses -- Replacement for Assign_Houses in INI.CPP / SCNEARIO.CPP
*
* In:
*
* Out:
*
*
*
* History: 8/8/2019 12:37PM - ST
**************************************************************************************************/
void GlyphX_Assign_Houses(void)
{
/*
** RA version
*/
extern int _build_tech[11];
int assigned[MAX_PLAYERS];
bool preassigned;
int i,j,random_start_location;
HousesType house, house2;
HouseClass * housep, *housep2;
int lowest_color;
int index;
srand(timeGetTime());
/*
** Use pre-selected start locations as long as at least one has been defined, otherwise let the original code
** in SCENARIO.CPP figure it out
*/
UseGlyphXStartLocations = false;
/*
** Assign random start positions if needed.
*/
int random_start_locations[26];
int num_start_locations = 0;
int num_random_start_locations = 0;
for (i = 0; i < 26; i++) {
if (Scen.Waypoint[i] != -1) {
preassigned = false;
for (j = 0; !preassigned && (j < Session.Players.Count()); j++) {
if (MPlayerStartLocations[j] == num_start_locations) {
preassigned = true;
UseGlyphXStartLocations = true;
}
}
if (!preassigned) {
random_start_locations[num_random_start_locations] = num_start_locations;
num_random_start_locations++;
}
num_start_locations++;
}
}
if (num_random_start_locations > 1) {
for (i = 0; i < num_random_start_locations - 1; i++) {
j = i + rand() / (RAND_MAX / (num_random_start_locations - i) + 1);
int t = random_start_locations[j];
random_start_locations[j] = random_start_locations[i];
random_start_locations[i] = t;
}
}
//------------------------------------------------------------------------
// Initialize
//------------------------------------------------------------------------
for (i = 0; i < MAX_PLAYERS; i++) {
assigned[i] = 0;
}
random_start_location = 0;
// debugprint( "Assign_Houses()\n" );
//------------------------------------------------------------------------
// Assign each player in 'Players' to a multiplayer house. Players will
// be sorted by their chosen color value (this value must be unique among
// all the players).
//------------------------------------------------------------------------
for (i = 0; i < Session.Players.Count(); i++) {
//.....................................................................
// Find the player with the lowest color index
//.....................................................................
index = 0;
lowest_color = 255;
for (j = 0; j < Session.Players.Count(); j++) {
//..................................................................
// If we've already assigned this house, skip it.
//..................................................................
if (assigned[j]) {
continue;
}
if (Session.Players[j]->Player.Color < lowest_color) {
lowest_color = Session.Players[j]->Player.Color;
index = j;
}
}
//.....................................................................
// Mark this player as having been assigned.
//.....................................................................
assigned[index] = 1;
//.....................................................................
// Assign the lowest-color'd player to the next available slot in the
// HouseClass array.
//.....................................................................
house = (HousesType)(i + HOUSE_MULTI1);
housep = HouseClass::As_Pointer(house);
memset((char *)housep->IniName, 0, MPLAYER_NAME_MAX);
strncpy((char *)housep->IniName, Session.Players[index]->Name, MPLAYER_NAME_MAX - 1);
#ifdef WOLAPI_INTEGRATION
// Make another copy of name, permanent throughout entire game.
strncpy((char *)housep->InitialName, Session.Players[index]->Name, MPLAYER_NAME_MAX - 1);
#endif
housep->IsHuman = MPlayerIsHuman[index];
housep->IsPlayerControl = housep->IsHuman;
if (!housep->IsHuman) {
housep->IsStarted = true;
strncpy(housep->IniName, Text_String(TXT_COMPUTER), HOUSE_NAME_MAX);
housep->IQ = Rule.MaxIQ;
//housep->Control.TechLevel = _build_tech[BuildLevel];
} else {
housep->IQ = 0;
}
housep->Init_Data((PlayerColorType)(Session.Players[index]->Player.Color),
Session.Players[index]->Player.House, Session.Options.Credits);
/*
** Set the start location override
*/
if (MPlayerStartLocations[index] != RANDOM_START_POSITION) {
housep->StartLocationOverride = MPlayerStartLocations[index];
} else {
if (random_start_location < num_random_start_locations) {
housep->StartLocationOverride = random_start_locations[random_start_location++];
} else {
housep->StartLocationOverride = -1;
}
}
if (index == 0) {
PlayerPtr = housep;
}
/*
** Convert the build level into an actual tech level to assign to the house.
** There isn't a one-to-one correspondence.
*/
housep->Control.TechLevel = _build_tech[BuildLevel];
housep->Assign_Handicap(Scen.Difficulty);
//.....................................................................
// Record where we placed this player
//.....................................................................
Session.Players[index]->Player.ID = house;
// debugprint( "Assigned ID of %i to %s\n", house, Session.Players[index]->Name );
}
for (i = Session.Players.Count(); i < Rule.MaxPlayers; i++) {
house = (HousesType)(i + HOUSE_MULTI1);
housep = HouseClass::As_Pointer(house);
if (housep != NULL) {
housep->IsDefeated = true;
}
}
for (i = 0; i < Session.Players.Count(); i++) {
house = Session.Players[i]->Player.ID;
housep = HouseClass::As_Pointer(house);
if (housep) {
int team = MPlayerTeamIDs[i];
for (int j=0 ; j < Session.Players.Count(); j++) {
if (i != j) {
if (team == MPlayerTeamIDs[j]) {
house2 = Session.Players[j]->Player.ID;
housep2 = HouseClass::As_Pointer(house2);
if (housep2) {
housep->Make_Ally(house2);
}
}
}
}
}
}
}
/**************************************************************************************************
* CNC_Set_Home_Cell -- Allows overriding the start position for the camera
*
*
* History: 2/14/2020 - LLL
**************************************************************************************************/
extern "C" __declspec(dllexport) void __cdecl CNC_Set_Home_Cell(int x, int y, uint64 player_id)
{
DLLExportClass::Set_Home_Cell(x, y, player_id);
}
/**************************************************************************************************
* CNC_Start_Instance -- Load and start a cnc map -> WITHOUT SPECIFYING A SCENARIO VARIATION (SCEN_VAR)
*
* In: Map initialization parameters
*
* Out: false if map load failed
*
*
*
* History: 7/10/2019 - LLL
**************************************************************************************************/
extern "C" __declspec(dllexport) bool __cdecl CNC_Start_Instance(int scenario_index, int build_level, const char *faction, const char *game_type, const char *content_directory, int sabotaged_structure, const char *override_map_name)
{
return CNC_Start_Instance_Variation(scenario_index, (int)SCEN_VAR_NONE, (int)SCEN_DIR_EAST, build_level, faction, game_type, content_directory, sabotaged_structure, override_map_name);
}
/**************************************************************************************************
* CNC_Start_Instance -- Load and start a cnc map
*
* In: Map initialization parameters
*
* Out: false if map load failed
*
*
* Renamed and modified to accept a scenario variation 7/10/2019 - LLL
*
* History: 1/7/2019 5:20PM - ST
**************************************************************************************************/
extern "C" __declspec(dllexport) bool __cdecl CNC_Start_Instance_Variation(int scenario_index, int scenario_variation, int scenario_direction, int build_level, const char *faction, const char *game_type, const char *content_directory, int sabotaged_structure, const char *override_map_name)
{
if (game_type == NULL) {
return false;
}
if (faction == NULL) {
return false;
}
if (content_directory == NULL) {
return false;
}
ScenarioPlayerType scen_player = SCEN_PLAYER_NONE;
if (stricmp(faction, "SPAIN") == 0) {
scen_player = SCEN_PLAYER_SPAIN;
Whom = HOUSE_GOOD;
}
if (stricmp(faction, "GREECE") == 0 || stricmp(faction, "ALLY") == 0) {
scen_player = SCEN_PLAYER_GREECE;
Whom = HOUSE_GOOD;
}
if (stricmp(faction, "USSR") == 0) {
scen_player = SCEN_PLAYER_USSR;
Whom = HOUSE_BAD;
}
DLLExportClass::Set_Content_Directory(content_directory);
BuildLevel = build_level;
Scen.Scenario = scenario_index;
ScenarioDirType scen_dir = (ScenarioDirType)scenario_direction;
if (stricmp(game_type, "GAME_NORMAL") == 0) {
GAME_TO_PLAY = GAME_NORMAL;
if (scen_player == SCEN_PLAYER_NONE) {
return false;
}
} else {
if (stricmp(game_type, "GAME_GLYPHX_MULTIPLAYER") == 0) {
GAME_TO_PLAY = GAME_GLYPHX_MULTIPLAYER;
scen_player = SCEN_PLAYER_MPLAYER;
} else {
return false;
}
}
/*
** Load the scenario. Specify variation 'A' for the editor; for the game,
** don't specify a variation, to make 'Set_Scenario_Name()' pick a random one.
** Skip this if we've already loaded a save-game.
*/
Force_CD_Available(ALWAYS_RELOAD_CD);
if (override_map_name && strlen(override_map_name)) {
strcpy(Scen.ScenarioName, override_map_name);
} else {
Scen.Set_Scenario_Name(Scen.Scenario, scen_player, scen_dir, (ScenarioVarType)scenario_variation);
}
HiddenPage.Clear();
VisiblePage.Clear();
/*
** Set the mouse to some position where it's not going to scroll, or do something else wierd.
*/
DLLForceMouseX = 100;
DLLForceMouseY = 100;
KEYBOARD->MouseQX = 100;
KEYBOARD->MouseQY = 100;
GlyphXClientSidebarWidthInLeptons = 0;
Seed = GetRandSeed();
Scen.RandomNumber = Seed;
if (!Start_Scenario(Scen.ScenarioName)) {
return(false);
}
DLLExportClass::Reset_Sidebars();
DLLExportClass::Reset_Player_Context();
DLLExportClass::Calculate_Start_Positions();
/*
** Make sure the scroll constraints are applied. This is important for GDI 1 where the map isn't wide enough for the screen
*/
COORDINATE origin_coord = Coord_Add(Map.TacticalCoord, XY_Coord(1, 0));
Map.Set_Tactical_Position(origin_coord);
origin_coord = Coord_Add(Map.TacticalCoord, XY_Coord(-1, 0));
Map.Set_Tactical_Position(origin_coord);
if (GAME_TO_PLAY == GAME_NORMAL) {
MPSuperWeaponDisable = false;
} else {
if (MPSuperWeaponDisable) {
/*
** Write over the tecb level settings we just loaded from the Rules ini
*/
Rule.GPSTechLevel = 100;
Rule.ParaInfantryTechLevel = 100;
Rule.SpyPlaneTechLevel = 100;
Rule.ParaBombTechLevel = 100;
Rule.ChronoTechLevel = 100;
}
}
/*
** Hide the SeenBuff; force the map to render one frame. The caller can
** then fade the palette in.
** (If we loaded a game, this step will fade out the title screen. If we
** started a scenario, Start_Scenario() will have played a couple of VQ
** movies, which will have cleared the screen to black already.)
*/
//Fade_Palette_To(BlackPalette, FADE_PALETTE_MEDIUM, Call_Back);
HiddenPage.Clear();
VisiblePage.Clear();
Set_Logic_Page(SeenBuff);
/*
** Sidebar is always active in hi-res.
*/
if (!Debug_Map) {
Map.SidebarClass::Activate(1);
}
Map.Flag_To_Redraw(true);
Set_Palette(GamePalette.Get_Data());
Map.Render();
Set_Palette(GamePalette.Get_Data());
return true;
}
/**************************************************************************************************
* CNC_Read_INI -- Load an ini file into the supplied buffer
*
* In: Map initialization parameters
*
* Out: false if ini load failed
*
*
* History: 12/16/2019 11:44AM - ST
**************************************************************************************************/
extern "C" __declspec(dllexport) bool __cdecl CNC_Read_INI(int scenario_index, int scenario_variation, int scenario_direction, const char *content_directory, const char *override_map_name, char *ini_buffer, int _ini_buffer_size)
{
if (content_directory == NULL) {
return false;
}
DLLExportClass::Set_Content_Directory(content_directory);
Scen.Scenario = scenario_index;
ScenarioDirType scen_dir = (ScenarioDirType)scenario_direction;
GAME_TO_PLAY = GAME_GLYPHX_MULTIPLAYER;
ScenarioPlayerType scen_player = SCEN_PLAYER_MPLAYER;
Force_CD_Available(ALWAYS_RELOAD_CD);
if (override_map_name && strlen(override_map_name)) {
strcpy(Scen.ScenarioName, override_map_name);
} else {
Scen.Set_Scenario_Name(Scen.Scenario, scen_player, scen_dir, (ScenarioVarType)scenario_variation);
}
if (_ini_buffer_size < _ShapeBufferSize) {
GlyphX_Debug_Print("INI file buffer may be too small");
return false;
}
if (!ini_buffer) {
GlyphX_Debug_Print("No INI file buffer");
return false;
}
memset(ini_buffer, _ini_buffer_size, 0);
CCFileClass file(Scen.ScenarioName);
if (!file.Is_Available()) {
GlyphX_Debug_Print("Failed to find scenario file");
GlyphX_Debug_Print(Scen.ScenarioName);
return(false);
} else {
GlyphX_Debug_Print("Opened scenario file");
GlyphX_Debug_Print(Scen.ScenarioName);
int bytes_read = file.Read(ini_buffer, _ini_buffer_size-1);
if (bytes_read == _ini_buffer_size - 1) {
GlyphX_Debug_Print("INI file buffer is too small");
return false;
}
}
/*
** Ini buffer should be zero terminated
*/
if ((int) strlen(ini_buffer) >= _ini_buffer_size) {
GlyphX_Debug_Print("INI file buffer overrun");
return false;
}
return true;
}
/**************************************************************************************************
* CNC_Start_Custom_Instance -- Load and start a custom map
*
* In: Map initialization parameters
*
* Out: false if map load failed
*
*
*
* History: 2019/10/28 - JAS
**************************************************************************************************/
extern "C" __declspec(dllexport) bool __cdecl CNC_Start_Custom_Instance(const char* content_directory, const char* directory_path, const char* scenario_name, int build_level, bool multiplayer)
{
DLLExportClass::Set_Content_Directory(content_directory);
char fullname[_MAX_FNAME + _MAX_EXT];
snprintf(fullname, _MAX_FNAME + _MAX_EXT, "%s%s.mpr", directory_path, scenario_name);
char name_buffer[128];
char digest_buffer[32];
CCFileClass file(fullname);
INIClass ini;
ini.Load(file);
ini.Get_String("Basic", "Name", "No Name", name_buffer, sizeof(name_buffer));
ini.Get_String("Digest", "1", "No Digest", digest_buffer, sizeof(digest_buffer));
Session.Scenarios.Add(new MultiMission(fullname, name_buffer, digest_buffer, ini.Get_Bool("Basic", "Official", false), false));
BuildLevel = build_level;
ScenarioPlayerType scen_player;
strncpy(Scen.ScenarioName, fullname, _MAX_FNAME + _MAX_EXT);
if (multiplayer) {
GAME_TO_PLAY = GAME_GLYPHX_MULTIPLAYER;
} else {
GAME_TO_PLAY = GAME_NORMAL;
}
scen_player = SCEN_PLAYER_MPLAYER;
//Scen.Scenario = scenario_index;
//ScenarioDirType scen_dir = (ScenarioDirType)scenario_direction;
//if (stricmp(game_type, "GAME_NORMAL") == 0) {
// GAME_TO_PLAY = GAME_NORMAL;
//}
//else {
// if (stricmp(game_type, "GAME_GLYPHX_MULTIPLAYER") == 0) {
// GAME_TO_PLAY = GAME_GLYPHX_MULTIPLAYER;
// scen_player = SCEN_PLAYER_MPLAYER;
// }
// else {
// return false;
// }
//}
///*
//** Load the scenario. Specify variation 'A' for the editor; for the game,
//** don't specify a variation, to make 'Set_Scenario_Name()' pick a random one.
//** Skip this if we've already loaded a save-game.
//*/
//Force_CD_Available(ALWAYS_RELOAD_CD);
//if (override_map_name && strlen(override_map_name)) {
// strcpy(Scen.ScenarioName, override_map_name);
//}
//else {
// Scen.Set_Scenario_Name(Scen.Scenario, scen_player, scen_dir, (ScenarioVarType)scenario_variation);
//}
HiddenPage.Clear();
VisiblePage.Clear();
/*
** Set the mouse to some position where it's not going to scroll, or do something else wierd.
*/
DLLForceMouseX = 100;
DLLForceMouseY = 100;
KEYBOARD->MouseQX = 100;
KEYBOARD->MouseQY = 100;
GlyphXClientSidebarWidthInLeptons = 0;
Seed = GetRandSeed();
Scen.RandomNumber = Seed;
if (!Start_Scenario(Scen.ScenarioName)) {
return(false);
}
DLLExportClass::Reset_Sidebars();
DLLExportClass::Reset_Player_Context();
DLLExportClass::Calculate_Start_Positions();
/*
** Make sure the scroll constraints are applied. This is important for GDI 1 where the map isn't wide enough for the screen
*/
COORDINATE origin_coord = Coord_Add(Map.TacticalCoord, XY_Coord(1, 0));
Map.Set_Tactical_Position(origin_coord);
origin_coord = Coord_Add(Map.TacticalCoord, XY_Coord(-1, 0));
Map.Set_Tactical_Position(origin_coord);
if (GAME_TO_PLAY == GAME_NORMAL) {
MPSuperWeaponDisable = false;
}
else {
if (MPSuperWeaponDisable) {
/*
** Write over the tecb level settings we just loaded from the Rules ini
*/
Rule.GPSTechLevel = 100;
Rule.ParaInfantryTechLevel = 100;
Rule.SpyPlaneTechLevel = 100;
Rule.ParaBombTechLevel = 100;
Rule.ChronoTechLevel = 100;
}
}
/*
** Hide the SeenBuff; force the map to render one frame. The caller can
** then fade the palette in.
** (If we loaded a game, this step will fade out the title screen. If we
** started a scenario, Start_Scenario() will have played a couple of VQ
** movies, which will have cleared the screen to black already.)
*/
//Fade_Palette_To(BlackPalette, FADE_PALETTE_MEDIUM, Call_Back);
HiddenPage.Clear();
VisiblePage.Clear();
Set_Logic_Page(SeenBuff);
/*
** Sidebar is always active in hi-res.
*/
if (!Debug_Map) {
Map.SidebarClass::Activate(1);
}
Map.Flag_To_Redraw(true);
Set_Palette(GamePalette.Get_Data());
Map.Render();
Set_Palette(GamePalette.Get_Data());
return true;
}
bool Debug_Write_Shape_Type(const ObjectTypeClass *type, int shapenum)
{
char fullname[_MAX_FNAME+_MAX_EXT];
char buffer[_MAX_FNAME];
CCFileClass file;
if (type->ImageData != NULL) {
sprintf(buffer, "%s_%d", type->IniName, shapenum);
_makepath(fullname, NULL, NULL, buffer, ".PCX");
return Debug_Write_Shape(fullname, type->ImageData, shapenum);
}
return false;
}
bool Debug_Write_Shape(const char *file_name, void const * shapefile, int shapenum, int flags, void const * ghostdata)
{
/*
** Build frame returns a pointer now instead of the shapes length
*/
char *shape_pointer = (char*) Build_Frame(shapefile , shapenum , _ShapeBuffer);
if (shape_pointer == NULL) {
return false;;
}
if (Get_Last_Frame_Length() > _ShapeBufferSize) {
return false;;
}
int width = Get_Build_Frame_Width(shapefile);
int height = Get_Build_Frame_Height(shapefile);
GraphicBufferClass temp_gbuffer(width, height);
GraphicViewPortClass temp_viewport(&temp_gbuffer, 0, 0, width, height);
WindowList[WINDOW_CUSTOM][WINDOWX] = 0;
WindowList[WINDOW_CUSTOM][WINDOWY] = 0;
WindowList[WINDOW_CUSTOM][WINDOWWIDTH] = width;
WindowList[WINDOW_CUSTOM][WINDOWHEIGHT] = height;
static const char _shape_trans = 0x40;
if (flags == 0) {
Buffer_Frame_To_Page(0, 0, width, height, shape_pointer, temp_viewport, SHAPE_NORMAL|SHAPE_WIN_REL|_shape_trans); //, ghostdata, predoffset);
} else {
Buffer_Frame_To_Page(0, 0, width, height, shape_pointer, temp_viewport, flags, ghostdata);
}
Write_PCX_File((char*)file_name, temp_viewport, (unsigned char*)GamePalette.Get_Data());
return true;
}
/**************************************************************************************************
* CNC_Advance_Instance -- Process one logic frame
*
* In:
*
* Out: Is game still playing?
*
*
*
* History: 1/7/2019 5:20PM - ST
**************************************************************************************************/
extern "C" __declspec(dllexport) bool __cdecl CNC_Advance_Instance(uint64 player_id)
{
//DLLExportClass::Set_Event_Callback(event_callback);
if (Frame <= 10) { // Don't spam forever, but useful to know that we actually started advancing
GlyphX_Debug_Print("CNC_Advance_Instance - RA");
}
/*
** Shouldn't really need to do this, but I like the idea of always running the main loop in the context of the same player.
** Might make tbe bugs more repeatable and consistent. ST - 3/15/2019 11:58AM
*/
if (player_id != 0) {
DLLExportClass::Set_Player_Context(player_id);
} else {
DLLExportClass::Set_Player_Context(DLLExportClass::GlyphxPlayerIDs[0]);
}
/*
** Restore special from backup
*/
if (SpecialBackup != NULL) {
memcpy(&Special, SpecialBackup, sizeof(SpecialClass));
}
#ifdef FIXIT_CSII // checked - ajw 9/28/98
TimeQuake = PendingTimeQuake;
PendingTimeQuake = false;
#else
TimeQuake = false;
#endif
/*
** Allocate extra memory for uncompressed shapes as needed
*/
Reallocate_Big_Shape_Buffer();
/*
** If there is no theme playing, but it looks like one is required, then start one
** playing. This is usually the symptom of there being no transition score.
*/
//if (SampleType && Theme.What_Is_Playing() == THEME_NONE) {
// Theme.Queue_Song(THEME_PICK_ANOTHER);
//}
/*
** Update the display, unless we're inside a dialog.
*/
//if (SpecialDialog == SDLG_NONE && GameInFocus) {
//WWMouse->Erase_Mouse(&HidPage, TRUE);
//Map.Input(input, x, y);
//if (input) {
// Keyboard_Process(input);
//}
/*
** The main loop passes these in uninitialized. ST - 2/7/2019 4:36PM
*/
KeyNumType input = KN_NONE; // Player input.
int x = 0;
int y = 0;
Map.Input(input, x, y);
//if (input) {
// Keyboard_Process(input);
//}
if (GAME_TO_PLAY == GAME_GLYPHX_MULTIPLAYER) {
/*
** Process the sidebar. ST - 4/18/2019 11:59AM
*/
HouseClass *old_player_ptr = PlayerPtr;
for (int i=0 ; i<MULTIPLAYER_COUNT ; i++) {
HouseClass *player_ptr = HouseClass::As_Pointer(Session.Players[i]->Player.ID);
DLLExportClass::Logic_Switch_Player_Context(player_ptr);
Sidebar_Glyphx_AI(player_ptr, input);
}
DLLExportClass::Logic_Switch_Player_Context(old_player_ptr);
}
//}
/*
** Sort the map's ground layer by y-coordinate value. This is done
** outside the IsToRedraw check, for the purposes of game sync'ing
** between machines; this way, all machines will sort the Map's
** layer in the same way, and any processing done that's based on
** the order of this layer will sync on different machines.
*/
Map.Layer[LAYER_GROUND].Sort();
/*
** AI logic operations are performed here.
*/
//Skip this block of code on first update of single-player games. This helps prevents trigger generated messages on the first update from being lost during loading screen or movie. - LLL
static bool FirstUpdate = GAME_TO_PLAY != GAME_GLYPHX_MULTIPLAYER;;
if (!FirstUpdate) {
HouseClass *old_player_ptr = PlayerPtr;
Logic.Clear_Recently_Created_Bits();
Logic.AI();
DLLExportClass::Logic_Switch_Player_Context(old_player_ptr);
}
FirstUpdate = false;
TimeQuake = false;
#ifdef FIXIT_CSII // checked - ajw 9/28/98
if (!PendingTimeQuake) {
TimeQuakeCenter = 0;
}
#endif
/*
** Manage the inter-player message list. If Manage() returns true, it means
** a message has expired & been removed, and the entire map must be updated.
*/
if (Session.Messages.Manage()) {
#ifdef WIN32
HiddenPage.Clear();
#else //WIN32
HidPage.Clear();
#endif //WIN32
Map.Flag_To_Redraw(true);
}
/*
** Process all commands that are ready to be processed.
*/
if (GAME_TO_PLAY == GAME_NORMAL) {
Queue_AI();
} else {
if (GAME_TO_PLAY == GAME_GLYPHX_MULTIPLAYER) {
DLLExportClass::Glyphx_Queue_AI();
/*
** Process the sidebar. ST - 3/22/2019 2:07PM
*/
for (int i=0 ; i<MULTIPLAYER_COUNT ; i++) {
HouseClass *player_ptr = HouseClass::As_Pointer(Session.Players[i]->Player.ID);
Sidebar_Glyphx_Recalc(player_ptr);
}
}
}
/*
** Keep track of elapsed time in the game.
*/
//Score.ElapsedTime += TIMER_SECOND / TICKS_PER_SECOND;
/*
** Perform any win/lose code as indicated by the global control flags.
*/
/*
** Check for player wins or loses according to global event flag.
*/
if (PlayerWins) {
//WWMouse->Erase_Mouse(&HidPage, TRUE);
PlayerLoses = false;
PlayerWins = false;
PlayerRestarts = false;
Map.Help_Text(TXT_NONE);
GlyphX_Debug_Print("PlayerWins = true");
if (GAME_TO_PLAY == GAME_GLYPHX_MULTIPLAYER) {
DLLExportClass::On_Multiplayer_Game_Over();
} else {
DLLExportClass::On_Game_Over(player_id, true);
}
//DLLExportClass::Set_Event_Callback(NULL);
return false;
}
if (PlayerLoses) {
//WWMouse->Erase_Mouse(&HidPage, TRUE);
PlayerWins = false;
PlayerLoses = false;
PlayerRestarts = false;
Map.Help_Text(TXT_NONE);
GlyphX_Debug_Print("PlayerLoses = true");
if (GAME_TO_PLAY == GAME_GLYPHX_MULTIPLAYER) {
DLLExportClass::On_Multiplayer_Game_Over();
} else {
DLLExportClass::On_Game_Over(player_id, false);
}
//DLLExportClass::Set_Event_Callback(NULL);
return false;
}
/*
** The frame logic has been completed. Increment the frame
** counter.
*/
Frame++;
/*
** Very rarely, the human players will get a message from the computer.
**
** This was disabled in the RA source code, so copying over the functionality from TD
*/
if (GAME_TO_PLAY != GAME_NORMAL && Session.Options.Ghosts && IRandom(0,10000) == 1) {
DLLExportClass::Computer_Message(false);
}
if (ProgEndCalled) {
GlyphX_Debug_Print("ProgEndCalled - GameActive = false");
GameActive = false;
}
if (DLLExportClass::Legacy_Render_Enabled()) {
Map.Render();
}
//Sync_Delay();
//DLLExportClass::Set_Event_Callback(NULL);
Color_Cycle();
/*
** Don't respect GameActive. Game will end in multiplayer on win/loss
*/
if (GAME_TO_PLAY == GAME_GLYPHX_MULTIPLAYER) {
return true;
}
return(GameActive);
}
/**************************************************************************************************
* CNC_Save_Load -- Process a save or load game action
*
* In:
*
* Out: Success?
*
*
*
* History: 1/7/2019 5:20PM - ST
**************************************************************************************************/
extern "C" __declspec(dllexport) bool __cdecl CNC_Save_Load(bool save, const char *file_path_and_name, const char *game_type)
{
bool result = false;
if (save) {
result = Save_Game(file_path_and_name, "internal");
} else {
if (game_type == NULL) {
return false;
}
if (stricmp(game_type, "GAME_NORMAL") == 0) {
GAME_TO_PLAY = GAME_NORMAL;
} else {
if (stricmp(game_type, "GAME_GLYPHX_MULTIPLAYER") == 0) {
GAME_TO_PLAY = GAME_GLYPHX_MULTIPLAYER;
//ScenPlayer = SCEN_PLAYER_MPLAYER;
} else {
return false;
}
}
while (Session.Players.Count() > 0) {
delete Session.Players[0];
Session.Players.Delete(Session.Players[0]);
}
result = Load_Game(file_path_and_name);
// MBL 07.21.2020
if (result == false)
{
return false;
}
DLLExportClass::Set_Player_Context(DLLExportClass::GlyphxPlayerIDs[0], true);
Set_Logic_Page(SeenBuff);
VisiblePage.Clear();
Map.Flag_To_Redraw(true);
if (DLLExportClass::Legacy_Render_Enabled()) {
Map.Render();
}
Set_Palette(GamePalette.Get_Data());
}
return result;
}
/**************************************************************************************************
* CNC_Set_Difficulty -- Set game difficulty
*
* In:
*
* Out:
*
*
*
* History: 10/02/2019 - SKY
**************************************************************************************************/
extern "C" __declspec(dllexport) void __cdecl CNC_Set_Difficulty(int difficulty)
{
if (GAME_TO_PLAY == GAME_NORMAL) {
Set_Scenario_Difficulty(difficulty);
}
}
/**************************************************************************************************
* CNC_Restore_Carryover_Objects
*
* History: 11/15/2019 - LLL
**************************************************************************************************/
extern "C" __declspec(dllexport) void __cdecl CNC_Restore_Carryover_Objects(const CarryoverObjectStruct* objects)
{
//Delete the list
while (Carryover) {
CarryoverClass* cptr = (CarryoverClass*)Carryover->Get_Next();
delete Carryover;
Carryover = cptr;
}
//Populate the list
const CarryoverObjectStruct* next_object = objects;
while (next_object) {
CarryoverClass* cptr = new CarryoverClass();
cptr->RTTI = (RTTIType)next_object->RTTI;
cptr->Type.Building = (StructType)next_object->Type; //This works regardless of what the RTTI-type and the enum type, because they're all enums. - LLL
cptr->Cell = (CELL)next_object->Cell;
cptr->House = (HousesType)next_object->House;
cptr->Strength = next_object->Strength;
next_object = next_object->Next;
if (Carryover == NULL) {
Carryover = cptr;
}
else {
cptr->Add_Tail(*Carryover);
}
}
}
/**************************************************************************************************
* CNC_Handle_Player_Switch_To_AI -- Renamed 3/9/2020 - LLL
* CNC_Handle_Player_Disconnect -- Handle player disconnected during multiplayer game
*
* In:
*
* Out:
*
*
*
* History: 12/3/2019 1:46PM - ST
**************************************************************************************************/
extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Player_Switch_To_AI(uint64 player_id)
{
if (PlayerWins || PlayerLoses || DLLExportClass::Get_Game_Over()) {
return;
}
GlyphX_Debug_Print("CNC_Handle_Player_Switch_To_AI");
if (GAME_TO_PLAY == GAME_NORMAL) {
return;
}
Command & Conquer Remastered post-launch patch Improvements to harvester resource finding logic. Don't allow the Advanced Comm Center to be capturable in skirmish or multiplayer. Increased failed pathfinding fudge factor. Buildings accept the Guard command if they can attack. Don't allow force capturing of ally structures. Fixes for laser Orcas in S3cr3t M1ss10n. Properly restore them after save. Reset Orcas after loads. Fixed flag animation rendering in CTF. Potentially fix a crash if aircraft are destroyed outside the map bounds. Fixed legacy Obelisk line rendering. Fix out-of-bounds crash in TD; issue was already fixed in RA. Disable capture flag on Commandos. Drop the flag when entering the limbo state. Fixed end game race condition, winning team ID is always sent before individual player win/lose messages. Fixed Chan spawn on SCB10EA. Don't show enter cursor for enemy units on refineries and repair pads. Changing right-click support for first put building on hold, and then subsequenct right-clicks to decrement that queue count for 1x or 5x; Then, 1x or 5x Left click will resume from hold. Don't debug reveal legacy rendering when a player is defeated. Fixed crash when loading saves of custom campaign maps. Reset harvester archived target when given a direct harvest order. Prevent NOD cargo planes from being force attacked. Fixed unit selection on load. Migrated queued repair pad functionality from RA to TD. Randomly animate infantry in area guard mode. Fixed crash accessing inactive objects. Added some walls in SCG08EB to prevent civilians from killing themselves. TD + RA: Audio: Overiding "Our base is under attack" cooldown timing from legacy from 2 minutes to 30 seconds, so it will be heard 4x as often. Fixed adjacent cell out-of-bounds crash issues. Kill player on disconnect Fixed and improved build time calculations to be consistent between TD and RA. Don't show health bars for cloaked Stealth Tanks in the legacy renderer. Fix selection of individual control groups after mixed selection. More adjustments to SCG08EB; switch C7 to C5, and add civilian AI to avoid Tiberium. Extra safety checks for units that have no weapons and aircraft that can't hunt. Fix loading of multiple infantry onto an APC. Additional safety checks for invalid coordinates. Prevent units from being instantly repaired. Fix map passability. Fail Allied mission 5B if the spy re-boards the starting transport (matches 5A and 5C behavior). Fixed multiplayer formation move causing units to move at light speed. Ignore movement destination checks if a unit is part of a mission-driven team. Fix buffer overrun crash. Ignore mines when determining win conditions. Fixed river passability in Blue Lakes.
2020-06-23 04:43:21 +12:00
#ifdef KILL_PLAYER_ON_DISCONNECT
/*
** Kill player's units on disconnect.
*/
if (player_id != 0) {
DLLExportClass::Set_Player_Context(player_id);
if (PlayerPtr) {
PlayerPtr->Flag_To_Die();
}
}
#else //KILL_PLAYER_ON_DISCONNECT
if (player_id != 0) {
Command & Conquer Remastered post-launch patch Improvements to harvester resource finding logic. Don't allow the Advanced Comm Center to be capturable in skirmish or multiplayer. Increased failed pathfinding fudge factor. Buildings accept the Guard command if they can attack. Don't allow force capturing of ally structures. Fixes for laser Orcas in S3cr3t M1ss10n. Properly restore them after save. Reset Orcas after loads. Fixed flag animation rendering in CTF. Potentially fix a crash if aircraft are destroyed outside the map bounds. Fixed legacy Obelisk line rendering. Fix out-of-bounds crash in TD; issue was already fixed in RA. Disable capture flag on Commandos. Drop the flag when entering the limbo state. Fixed end game race condition, winning team ID is always sent before individual player win/lose messages. Fixed Chan spawn on SCB10EA. Don't show enter cursor for enemy units on refineries and repair pads. Changing right-click support for first put building on hold, and then subsequenct right-clicks to decrement that queue count for 1x or 5x; Then, 1x or 5x Left click will resume from hold. Don't debug reveal legacy rendering when a player is defeated. Fixed crash when loading saves of custom campaign maps. Reset harvester archived target when given a direct harvest order. Prevent NOD cargo planes from being force attacked. Fixed unit selection on load. Migrated queued repair pad functionality from RA to TD. Randomly animate infantry in area guard mode. Fixed crash accessing inactive objects. Added some walls in SCG08EB to prevent civilians from killing themselves. TD + RA: Audio: Overiding "Our base is under attack" cooldown timing from legacy from 2 minutes to 30 seconds, so it will be heard 4x as often. Fixed adjacent cell out-of-bounds crash issues. Kill player on disconnect Fixed and improved build time calculations to be consistent between TD and RA. Don't show health bars for cloaked Stealth Tanks in the legacy renderer. Fix selection of individual control groups after mixed selection. More adjustments to SCG08EB; switch C7 to C5, and add civilian AI to avoid Tiberium. Extra safety checks for units that have no weapons and aircraft that can't hunt. Fix loading of multiple infantry onto an APC. Additional safety checks for invalid coordinates. Prevent units from being instantly repaired. Fix map passability. Fail Allied mission 5B if the spy re-boards the starting transport (matches 5A and 5C behavior). Fixed multiplayer formation move causing units to move at light speed. Ignore movement destination checks if a unit is part of a mission-driven team. Fix buffer overrun crash. Ignore mines when determining win conditions. Fixed river passability in Blue Lakes.
2020-06-23 04:43:21 +12:00
HousesType house;
HouseClass *ptr;
DLLExportClass::Set_Player_Context(player_id);
if (PlayerPtr) {
PlayerPtr->WasHuman = true;
PlayerPtr->IsHuman = false;
PlayerPtr->IQ = Rule.MaxIQ;
strcpy (PlayerPtr->IniName, Text_String(TXT_COMPUTER));
PlayerPtr->IsBaseBuilding = true;
/*
** Start the unload mission for MCVs
*/
for (int index = 0; index < Units.Count(); index++) {
UnitClass * obj = Units.Ptr(index);
if (obj && !obj->IsInLimbo && obj->House == PlayerPtr) {
if (*obj == UNIT_MCV) {
obj->Assign_Mission(MISSION_GUARD);
obj->Assign_Target(TARGET_NONE);
obj->Assign_Destination(TARGET_NONE);
obj->Assign_Mission(MISSION_UNLOAD);
obj->Commence();
}
}
}
DLLExportClass::On_Message(PlayerPtr, "", 60.0f, MESSAGE_TYPE_PLAYER_DISCONNECTED, -1);
/*
** Send the disconnect taunt message
*/
int human_count = 0;
for (house = HOUSE_MULTI1; house < (HOUSE_MULTI1 + MULTIPLAYER_COUNT); house++) {
ptr = HouseClass::As_Pointer(house);
if (ptr && ptr->IsHuman && !ptr->IsDefeated) {
human_count++;
}
}
if (human_count == 1) {
DLLExportClass::Computer_Message(true);
}
}
}
Command & Conquer Remastered post-launch patch Improvements to harvester resource finding logic. Don't allow the Advanced Comm Center to be capturable in skirmish or multiplayer. Increased failed pathfinding fudge factor. Buildings accept the Guard command if they can attack. Don't allow force capturing of ally structures. Fixes for laser Orcas in S3cr3t M1ss10n. Properly restore them after save. Reset Orcas after loads. Fixed flag animation rendering in CTF. Potentially fix a crash if aircraft are destroyed outside the map bounds. Fixed legacy Obelisk line rendering. Fix out-of-bounds crash in TD; issue was already fixed in RA. Disable capture flag on Commandos. Drop the flag when entering the limbo state. Fixed end game race condition, winning team ID is always sent before individual player win/lose messages. Fixed Chan spawn on SCB10EA. Don't show enter cursor for enemy units on refineries and repair pads. Changing right-click support for first put building on hold, and then subsequenct right-clicks to decrement that queue count for 1x or 5x; Then, 1x or 5x Left click will resume from hold. Don't debug reveal legacy rendering when a player is defeated. Fixed crash when loading saves of custom campaign maps. Reset harvester archived target when given a direct harvest order. Prevent NOD cargo planes from being force attacked. Fixed unit selection on load. Migrated queued repair pad functionality from RA to TD. Randomly animate infantry in area guard mode. Fixed crash accessing inactive objects. Added some walls in SCG08EB to prevent civilians from killing themselves. TD + RA: Audio: Overiding "Our base is under attack" cooldown timing from legacy from 2 minutes to 30 seconds, so it will be heard 4x as often. Fixed adjacent cell out-of-bounds crash issues. Kill player on disconnect Fixed and improved build time calculations to be consistent between TD and RA. Don't show health bars for cloaked Stealth Tanks in the legacy renderer. Fix selection of individual control groups after mixed selection. More adjustments to SCG08EB; switch C7 to C5, and add civilian AI to avoid Tiberium. Extra safety checks for units that have no weapons and aircraft that can't hunt. Fix loading of multiple infantry onto an APC. Additional safety checks for invalid coordinates. Prevent units from being instantly repaired. Fix map passability. Fail Allied mission 5B if the spy re-boards the starting transport (matches 5A and 5C behavior). Fixed multiplayer formation move causing units to move at light speed. Ignore movement destination checks if a unit is part of a mission-driven team. Fix buffer overrun crash. Ignore mines when determining win conditions. Fixed river passability in Blue Lakes.
2020-06-23 04:43:21 +12:00
#endif //KILL_PLAYER_ON_DISCONNECT
}
/**************************************************************************************************
* CNC_Handle_Human_Team_Wins
*
* History: 3/10/2020 - LLL
**************************************************************************************************/
extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Human_Team_Wins(uint64 quitting_player_id)
{
GlyphX_Debug_Print("CNC_Handle_Human_Team_Wins");
DLLExportClass::Force_Human_Team_Wins(quitting_player_id);
}
/**************************************************************************************************
* CNC_Start_Mission_Timer
*
* History: 11/25/2019 - LLL
**************************************************************************************************/
extern "C" __declspec(dllexport) void __cdecl CNC_Start_Mission_Timer(int time)
{
if (GameActive)
{
Scen.MissionTimer = time;
if (!Scen.MissionTimer.Is_Active()) {
Scen.MissionTimer.Start();
}
Map.Redraw_Tab();
}
}
/**************************************************************************************************
* DLLExportClass::Init -- Init the class
*
* In:
*
* Out:
*
*
*
* History: 3/12/2019 10:52AM - ST
**************************************************************************************************/
void DLLExportClass::Init(void)
{
for (int i=0 ; i<MAX_PLAYERS ; i++) {
GlyphxPlayerIDs[i] = 0xffffffffull;
}
CurrentLocalPlayerIndex = 0;
MessagesSent.clear();
if (SpecialBackup == NULL) {
SpecialBackup = new SpecialClass;
}
memcpy(SpecialBackup, &Special, sizeof(SpecialClass));
}
/**************************************************************************************************
* DLLExportClass::Shutdown -- Shutdown
*
* In:
*
* Out:
*
*
*
* History: 2/20/2020 1:59PM - ST
**************************************************************************************************/
void DLLExportClass::Shutdown(void)
{
delete SpecialBackup;
SpecialBackup = NULL;
for (int i=0 ; i<ModSearchPaths.Count() ; i++) {
delete [] ModSearchPaths[i];
}
ModSearchPaths.Clear();
}
/**************************************************************************************************
* DLLExportClass::Add_Mod_Path -- Add a path to load mod files from
*
* In: Mod path
*
* Out:
*
*
*
* History: 2/20/2020 2:03PM - ST
**************************************************************************************************/
void DLLExportClass::Add_Mod_Path(const char *mod_path)
{
char *copy_path = strdup(mod_path);
ModSearchPaths.Add(copy_path);
}
/**************************************************************************************************
* DLLExportClass::Set_Content_Directory -- Update the locations that the original code will load from
*
* In: Main (official) content directory
*
* Out:
*
*
*
* History: 2/20/2020 2:03PM - ST
**************************************************************************************************/
void DLLExportClass::Set_Content_Directory(const char *content_directory)
{
CCFileClass::Clear_Search_Drives();
CCFileClass::Reset_Raw_Path();
if ((content_directory == NULL || strlen(content_directory) == 0) && ModSearchPaths.Count() == 0) {
return;
}
char *all_paths = new char [_MAX_PATH * 100];
*all_paths = 0;
for (int i=0 ; i<ModSearchPaths.Count() ; i++) {
if (i != 0) {
strcat(all_paths, ";");
}
strcat(all_paths, ModSearchPaths[i]);
}
if (ModSearchPaths.Count() && content_directory && strlen(content_directory)) {
strcat(all_paths, ";");
}
if (content_directory) {
strcat(all_paths, content_directory);
}
CCFileClass::Set_Search_Drives(all_paths);
delete [] all_paths;
}
/**************************************************************************************************
* DLLExportClass::Config
*
* History: 1/16/2020 - SKY
**************************************************************************************************/
void DLLExportClass::Config(const CNCRulesDataStruct& rules)
{
for (int i = 0; i < 3; ++i)
{
Rule.Diff[i].FirepowerBias = rules.Difficulties[i].FirepowerBias;
Rule.Diff[i].GroundspeedBias = rules.Difficulties[i].GroundspeedBias;
Rule.Diff[i].AirspeedBias = rules.Difficulties[i].AirspeedBias;
Rule.Diff[i].ArmorBias = rules.Difficulties[i].ArmorBias;
Rule.Diff[i].ROFBias = rules.Difficulties[i].ROFBias;
Rule.Diff[i].CostBias = rules.Difficulties[i].CostBias;
Rule.Diff[i].BuildSpeedBias = rules.Difficulties[i].BuildSpeedBias;
Rule.Diff[i].RepairDelay = rules.Difficulties[i].RepairDelay;
Rule.Diff[i].BuildDelay = rules.Difficulties[i].BuildDelay;
Rule.Diff[i].IsBuildSlowdown = rules.Difficulties[i].IsBuildSlowdown ? 1 : 0;
Rule.Diff[i].IsWallDestroyer = rules.Difficulties[i].IsWallDestroyer ? 1 : 0;
Rule.Diff[i].IsContentScan = rules.Difficulties[i].IsContentScan ? 1 : 0;
}
}
/**************************************************************************************************
* DLLExportClass::Set_Home_Cell
*
* History: 2/14/2020 - LLL
**************************************************************************************************/
void DLLExportClass::Set_Home_Cell(int x, int y, uint64 player_id)
{
if (GAME_TO_PLAY == GAME_NORMAL) {
MultiplayerStartPositions[0] = XY_Cell(x, y);
}
else {
for (int i = 0; i < MAX_PLAYERS && i < 4; i++) {
if (GlyphxPlayerIDs[i] == player_id) {
MultiplayerStartPositions[i] = XY_Cell(x, y);
}
}
}
}
/**************************************************************************************************
* DLLExportClass::On_Play_Movie
*
* History: 7/23/2019 - LLL
**************************************************************************************************/
void DLLExportClass::On_Play_Movie(const char * movie_name, ThemeType theme, bool immediate)
{
if (EventCallback == NULL) {
return;
}
EventCallbackStruct new_event;
new_event.EventType = CALLBACK_EVENT_MOVIE;
new_event.Movie.MovieName = movie_name;
new_event.Movie.Theme = (int)theme;
new_event.Movie.Immediate = immediate;
EventCallback(new_event);
}
/**************************************************************************************************
* DLLExportClass::On_Display_Briefing_Text
*
* Called when Red Alert wants to display the mission breifing screen before a mission.
*
* History: 12/04/2019 - LLL
**************************************************************************************************/
void DLLExportClass::On_Display_Briefing_Text()
{
if (EventCallback == NULL) {
return;
}
EventCallbackStruct new_event;
new_event.EventType = CALLBACK_EVENT_BRIEFING_SCREEN;
EventCallback(new_event);
}
/**************************************************************************************************
* DLLExportClass::On_Sound_Effect -- Called when C&C wants to play a sound effect
*
* In:
*
* Out:
*
*
*
* History: 2/20/2019 2:39PM - ST
**************************************************************************************************/
void DLLExportClass::On_Sound_Effect(const HouseClass* player_ptr, int sound_effect_index, const char* extension, int variation, COORDINATE coord)
{
// player_ptr could be NULL
if (EventCallback == NULL) {
return;
}
EventCallbackStruct new_event;
new_event.EventType = CALLBACK_EVENT_SOUND_EFFECT;
new_event.SoundEffect.SFXIndex = sound_effect_index;
new_event.SoundEffect.Variation = variation;
new_event.GlyphXPlayerID = 0;
if ( player_ptr != NULL )
{
new_event.GlyphXPlayerID = Get_GlyphX_Player_ID(player_ptr);
}
if ( coord == 0 )
{
new_event.SoundEffect.PixelX = -1;
new_event.SoundEffect.PixelY = -1;
}
else
{
// Use world pixel coordinates
new_event.SoundEffect.PixelX = Lepton_To_Pixel(Coord_X(coord));
new_event.SoundEffect.PixelY = Lepton_To_Pixel(Coord_Y(coord));
}
if ( sound_effect_index >= VOC_FIRST && sound_effect_index < VOC_COUNT )
{
strncpy( new_event.SoundEffect.SoundEffectName, SoundEffectName[ sound_effect_index ].Name , 16);
if ( extension != NULL )
{
strncat( new_event.SoundEffect.SoundEffectName, extension , 16);
}
new_event.SoundEffect.SoundEffectPriority = SoundEffectName[ sound_effect_index ].Priority;
new_event.SoundEffect.SoundEffectContext = SoundEffectName[ sound_effect_index ].Where;
}
else
{
strncpy( new_event.SoundEffect.SoundEffectName, "BADINDEX", 16 );
new_event.SoundEffect.SoundEffectPriority = -1;
new_event.SoundEffect.SoundEffectContext = -1;
}
EventCallback(new_event);
}
/**************************************************************************************************
* DLLExportClass::On_Speech -- Called when C&C wants to play a speech line
*
* In:
*
* Out:
*
*
*
* History: 2/20/2019 2:39PM - ST
**************************************************************************************************/
void DLLExportClass::On_Speech(const HouseClass* player_ptr, int speech_index)
{
// player_ptr could be NULL
if (EventCallback == NULL) {
return;
}
EventCallbackStruct new_event;
new_event.EventType = CALLBACK_EVENT_SPEECH;
new_event.Speech.SpeechIndex = speech_index;
new_event.GlyphXPlayerID = 0;
if (player_ptr != NULL)
{
new_event.GlyphXPlayerID = Get_GlyphX_Player_ID(player_ptr);
}
if (speech_index >= VOX_FIRST && speech_index < VOX_COUNT)
{
strncpy(new_event.Speech.SpeechName, Speech[speech_index], 16);
}
else
{
strncpy(new_event.Speech.SpeechName, "BAD_SPEECH_INDEX", 16);
}
EventCallback(new_event);
}
/**************************************************************************************************
* DLLExportClass::RA_Calculate_Leadership --
*
* History: 10.30.2019 MBL (Based on LLL's Calculate_Single_Player_Score())
**************************************************************************************************/
int DLLExportClass::RA_Calculate_Leadership( HousesType player_house, int units_lost, int buildings_lost )
{
int house = (player_house == HOUSE_USSR || player_house == HOUSE_UKRAINE); // 0 or 1
int leadership = 0;
for (int index = 0; index < Logic.Count(); index++) {
ObjectClass* object = Logic[index];
HousesType owner = object->Owner();
if ((house) && (owner == HOUSE_USSR || owner == HOUSE_BAD || owner == HOUSE_UKRAINE)) {
leadership++;
}
else {
if ((!house) && (object->Owner() == HOUSE_GREECE)) {
leadership++;
}
}
}
if (!leadership) leadership++;
leadership = 100 * fixed(leadership, (units_lost + buildings_lost + leadership));
leadership = min(150, leadership);
return leadership;
}
/**************************************************************************************************
* DLLExportClass::RA_Calculate_Economy --
*
* History: 10.30.2019 MBL (Based on LLL's Calculate_Single_Player_Score())
**************************************************************************************************/
int DLLExportClass::RA_Calculate_Economy( long available_money, int stolen_buildings_credits, unsigned harvested_credits, long initial_credits )
{
int economy = 100 * fixed((unsigned)available_money + 1 + stolen_buildings_credits, harvested_credits + (unsigned)initial_credits + 1);
economy = min(economy, 150);
return economy;
}
/**************************************************************************************************
* DLLExportClass::RA_Calculate_Score --
*
* History: 10.30.2019 MBL (Based on LLL's Calculate_Single_Player_Score())
**************************************************************************************************/
int DLLExportClass::RA_Calculate_Score( int uspoints, int leadership, int economy )
{
int score_total = ((uspoints * leadership) / 100) + ((uspoints * economy) / 100);
if (score_total < -9999) score_total = -9999;
score_total = min(score_total, 99999);
return score_total;
}
/**************************************************************************************************
* DLLExportClass::CalculateScore*
*
* History: 10/16/2019 - LLL
**************************************************************************************************/
void DLLExportClass::Calculate_Single_Player_Score(EventCallbackStruct& event)
{
//Adapted from Red Alert SCORE.CPP Presentation() - LLL
int house = (PlayerPtr->Class->House == HOUSE_USSR || PlayerPtr->Class->House == HOUSE_UKRAINE); // 0 or 1
int good_units_lost = (HouseClass::As_Pointer(HOUSE_GOOD))->UnitsLost;
int bad_units_lost = (HouseClass::As_Pointer(HOUSE_BAD))->UnitsLost;
int neutral_units_lost = (HouseClass::As_Pointer(HOUSE_NEUTRAL))->UnitsLost;
int good_buildings_lost = (HouseClass::As_Pointer(HOUSE_GOOD))->BuildingsLost;
int bad_buildings_lost = (HouseClass::As_Pointer(HOUSE_BAD))->BuildingsLost;
int neutral_buildings_lost = (HouseClass::As_Pointer(HOUSE_NEUTRAL))->BuildingsLost;
int good_credits_harvested = (HouseClass::As_Pointer(HOUSE_GOOD))->HarvestedCredits;
int bad_credits_harvested = (HouseClass::As_Pointer(HOUSE_BAD))->HarvestedCredits;
int uspoints = 0;
for (HousesType hous = HOUSE_SPAIN; hous <= HOUSE_BAD; hous++) {
HouseClass *hows = HouseClass::As_Pointer(hous);
if (hous == HOUSE_USSR || hous == HOUSE_BAD || hous == HOUSE_UKRAINE) {
bad_units_lost += hows->UnitsLost;
bad_buildings_lost += hows->BuildingsLost;
}
else {
good_units_lost += hows->UnitsLost;
good_buildings_lost += hows->BuildingsLost;
}
if (PlayerPtr->Is_Ally(hous)) {
uspoints += hows->PointTotal;
}
}
// if(uspoints < 0) uspoints = 0;
// uspoints += 1000; //BG 1000 bonus points for winning mission
/*
** Bias the base score upward according to the difficulty level.
*/
switch (PlayerPtr->Difficulty) {
case DIFF_EASY:
uspoints += 500;
break;
case DIFF_NORMAL:
uspoints += 1500;
break;
case DIFF_HARD:
uspoints += 3500;
break;
}
/*
** Determine leadership rating.
*/
int leadership = 0;
for (int index = 0; index < Logic.Count(); index++) {
ObjectClass * object = Logic[index];
HousesType owner = object->Owner();
if ((house) && (owner == HOUSE_USSR || owner == HOUSE_BAD || owner == HOUSE_UKRAINE)) {
leadership++;
}
else {
if ((!house) && (object->Owner() == HOUSE_GREECE)) {
leadership++;
}
}
}
if (!leadership) leadership++;
leadership = 100 * fixed(leadership, (house ? bad_units_lost + bad_buildings_lost + leadership : good_units_lost + good_buildings_lost + leadership));
leadership = min(150, leadership);
/*
** Determine economy rating.
*/
// int init = PlayerPtr->Control.InitialCredits;
// int cred = PlayerPtr->Available_Money();
int economy = 100 * fixed((unsigned)PlayerPtr->Available_Money() + 1 + PlayerPtr->StolenBuildingsCredits, PlayerPtr->HarvestedCredits + (unsigned)PlayerPtr->Control.InitialCredits + 1);
economy = min(economy, 150);
int score_total = ((uspoints * leadership) / 100) + ((uspoints * economy) / 100);
if (score_total < -9999) score_total = -9999;
score_total = min(score_total, 99999);
//Score Stats
event.GameOver.Leadership = leadership;
event.GameOver.Efficiency = economy;
event.GameOver.Score = score_total;
event.GameOver.CategoryTotal = uspoints;
event.GameOver.NODKilled = bad_units_lost;
event.GameOver.GDIKilled = good_units_lost;
event.GameOver.CiviliansKilled = neutral_units_lost;
event.GameOver.NODBuildingsDestroyed = bad_buildings_lost;
event.GameOver.GDIBuildingsDestroyed = good_buildings_lost;
event.GameOver.CiviliansBuildingsDestroyed = neutral_buildings_lost;
event.GameOver.RemainingCredits = PlayerPtr->Available_Money();
if (Scen.IsSkipScore) {
event.GameOver.Score = -1;
}
}
/**************************************************************************************************
* DLLExportClass::On_Message -- Called when the game wants to display a message (ex. tutorial text)
*
* In:
*
* Out:
*
*
*
* History: 10/16/2019 - SKY
**************************************************************************************************/
void DLLExportClass::On_Message(const HouseClass* player_ptr, const char* message, float timeout_seconds, EventCallbackMessageEnum message_type, int64 message_id)
{
if (EventCallback == NULL)
{
return;
}
const char* p_msg = message;
std::string localized_text_name;
if (message_id != -1) {
if (message_id == TXT_INSUFFICIENT_FUNDS) {
localized_text_name = "TEXT_INSUFFICIENT_FUNDS_MESSAGE";
}
else if (message_id == TXT_LOW_POWER) {
localized_text_name = "TEXT_LOW_POWER_MESSAGE_001";
}
else if (message_id == TXT_POWER_TESLA) {
localized_text_name = "TEXT_LOW_POWER_MESSAGE_002";
}
else if (message_id == TXT_POWER_AAGUN) {
localized_text_name = "TEXT_LOW_POWER_MESSAGE_003";
}
else {
if (MessagesSent.find(message_id) != MessagesSent.end()) {
return;
}
MessagesSent.insert(message_id);
localized_text_name = "TEXT_RA_TUTORIAL_MESSAGE_";
localized_text_name += std::to_string(message_id);
}
p_msg = localized_text_name.c_str();
Sound_Effect(VOC_INCOMING_MESSAGE);
}
EventCallbackStruct new_event;
new_event.EventType = CALLBACK_EVENT_MESSAGE;
new_event.Message.Message = p_msg;
new_event.Message.TimeoutSeconds = timeout_seconds;
new_event.Message.MessageType = message_type;
new_event.Message.MessageParam1 = message_id;
new_event.GlyphXPlayerID = 0;
if (player_ptr != NULL)
{
new_event.GlyphXPlayerID = Get_GlyphX_Player_ID(player_ptr);
}
EventCallback(new_event);
}
/**************************************************************************************************
* DLLExportClass::On_Update_Map_Cell -- Called when an individual map cell template is updated
*
*
* History: 11/7/2019 - SKY
**************************************************************************************************/
void DLLExportClass::On_Update_Map_Cell(int cell_x, int cell_y, const char* template_type_name)
{
if (EventCallback == NULL)
{
return;
}
EventCallbackStruct new_event;
new_event.EventType = CALLBACK_EVENT_UPDATE_MAP_CELL;
new_event.UpdateMapCell.CellX = cell_x;
new_event.UpdateMapCell.CellY = cell_y;
strncpy(new_event.UpdateMapCell.TemplateTypeName, template_type_name, 32);
new_event.UpdateMapCell.TemplateTypeName[31] = '\0';
EventCallback(new_event);
}
/**************************************************************************************************
* DLLExportClass::On_Special_Weapon_Targetting -- Called when the server initiates targetting
*
*
* History: 11/19/2019 - SKY
**************************************************************************************************/
void DLLExportClass::On_Special_Weapon_Targetting(const HouseClass* player_ptr, SpecialWeaponType weapon_type)
{
if (EventCallback == NULL)
{
return;
}
EventCallbackStruct new_event;
new_event.EventType = CALLBACK_EVENT_SPECIAL_WEAPON_TARGETTING;
new_event.SpecialWeaponTargetting.Type = RTTI_SPECIAL;
new_event.SpecialWeaponTargetting.ID = weapon_type;
Convert_Special_Weapon_Type(weapon_type, new_event.SpecialWeaponTargetting.WeaponType, new_event.SpecialWeaponTargetting.Name);
new_event.GlyphXPlayerID = 0;
if (player_ptr != NULL)
{
new_event.GlyphXPlayerID = Get_GlyphX_Player_ID(player_ptr);
}
EventCallback(new_event);
}
/**************************************************************************************************
* DLLExportClass::On_Ping -- Called when a radar ping is needed
*
*
* History: 05/15/2019 - SKY
**************************************************************************************************/
void DLLExportClass::On_Ping(const HouseClass* player_ptr, COORDINATE coord)
{
if (EventCallback == NULL) {
return;
}
EventCallbackStruct new_event;
new_event.EventType = CALLBACK_EVENT_PING;
new_event.Ping.CoordX = Coord_X(coord);
new_event.Ping.CoordY = Coord_Y(coord);
new_event.GlyphXPlayerID = 0;
if (player_ptr != NULL)
{
new_event.GlyphXPlayerID = Get_GlyphX_Player_ID(player_ptr);
}
EventCallback(new_event);
}
/**************************************************************************************************
* DLLExportClass::On_Game_Over -- Called when the C&C campaign game finishes
*
*
* History: 6/19/2019 - LLL
**************************************************************************************************/
void DLLExportClass::On_Game_Over(uint64 glyphx_Player_id, bool player_wins)
{
if (EventCallback == NULL) {
return;
}
GameOver = true;
SaveTanya = IsTanyaDead;
Scen.CarryOverTimer = Scen.MissionTimer;
// int timer = Scen.MissionTimer;
Scen.CarryOverMoney = PlayerPtr->Credits;
DLLExportClass::Store_Carryover_Objects();
EventCallbackStruct new_event;
new_event.EventType = CALLBACK_EVENT_GAME_OVER;
new_event.GlyphXPlayerID = glyphx_Player_id;
new_event.GameOver.PlayerWins = player_wins;
new_event.GameOver.AfterScoreMovieName = "";
new_event.GameOver.Multiplayer = false;
new_event.GameOver.MultiPlayerTotalPlayers = 0;
Calculate_Single_Player_Score(new_event);
VQType movie = player_wins ? Scen.WinMovie : Scen.LoseMovie;
if (movie > VQ_NONE && movie < VQ_COUNT) {
new_event.GameOver.MovieName = VQName[movie];
} else {
new_event.GameOver.MovieName = "";
}
movie = player_wins ? Scen.WinMovie2 : VQ_NONE;
if (movie > VQ_NONE && movie < VQ_COUNT) {
new_event.GameOver.MovieName2 = VQName[movie];
}
else {
new_event.GameOver.MovieName2 = "";
}
movie = player_wins ? Scen.WinMovie3 : VQ_NONE;
if (movie > VQ_NONE && movie < VQ_COUNT) {
new_event.GameOver.MovieName3 = VQName[movie];
}
else {
new_event.GameOver.MovieName3 = "";
}
movie = player_wins ? Scen.WinMovie4 : VQ_NONE;
if (movie > VQ_NONE && movie < VQ_COUNT) {
new_event.GameOver.MovieName4 = VQName[movie];
}
else {
new_event.GameOver.MovieName4 = "";
}
//Campaign win & credits
if (Scen.IsEndOfGame) {
if (PlayerPtr->ActLike == HOUSE_USSR) {
new_event.GameOver.AfterScoreMovieName = VQName[VQ_SOVFINAL];
}
else {
new_event.GameOver.AfterScoreMovieName = VQName[VQ_ALLYEND];
}
}
new_event.GameOver.SabotagedStructureType = -1;
new_event.GameOver.TimerRemaining = Scen.MissionTimer.Was_Started() ? Scen.MissionTimer.Value() : -1;
EventCallback(new_event);
}
/**************************************************************************************************
* DLLExportClass::On_Multiplayer_Game_Over -- Called when the C&C multiplayer game finishes
*
*
* History: 6/19/2019 - LLL
* History: 10/31/2019 - MBL - Adding the multi-player score stats support for debrief
**************************************************************************************************/
void DLLExportClass::On_Multiplayer_Game_Over(void)
{
if (EventCallback == NULL) {
return;
}
GameOver = true;
EventCallbackStruct event;
event.EventType = CALLBACK_EVENT_GAME_OVER;
// Includes AI's for skirmish
int player_count = Session.Players.Count();
// Multiplayer players data for debrief stats
event.GameOver.Multiplayer = true;
event.GameOver.MultiPlayerTotalPlayers = player_count;
for ( int player_index = 0; player_index < player_count; player_index ++ )
{
HouseClass* player_ptr = HouseClass::As_Pointer(Session.Players[ player_index ]->Player.ID);
if ( player_ptr != NULL )
{
HousesType player_house = PlayerPtr->Class->House;
int uspoints = 0;
for (HousesType hous = HOUSE_SPAIN; hous <= HOUSE_BAD; hous++) {
HouseClass* hows = HouseClass::As_Pointer(hous);
if (player_ptr->Is_Ally(hous)) {
uspoints += hows->PointTotal;
}
}
// if(uspoints < 0) uspoints = 0;
// uspoints += 1000; //BG 1000 bonus points for winning mission
// N/A for multi-player
#if 0
// Bias the base score upward according to the difficulty level.
switch (PlayerPtr->Difficulty) {
case DIFF_EASY:
uspoints += 500;
break;
case DIFF_NORMAL:
uspoints += 1500;
break;
case DIFF_HARD:
uspoints += 3500;
break;
}
#endif
int leadership = RA_Calculate_Leadership( player_ptr->Class->House, player_ptr->UnitsLost, player_ptr->BuildingsLost );
int economy = RA_Calculate_Economy( player_ptr->Available_Money(), player_ptr->StolenBuildingsCredits, player_ptr->HarvestedCredits, player_ptr->Control.InitialCredits );
int total_score = RA_Calculate_Score( uspoints, leadership, economy );
int units_killed = 0;
int structures_killed = 0;
for ( unsigned int house_index = 0; house_index < HOUSE_COUNT; house_index ++ )
{
units_killed += player_ptr->UnitsKilled[ house_index ];
structures_killed += player_ptr->BuildingsKilled[ house_index ];
}
// Populate and copy the multiplayer player data structure
GameOverMultiPlayerStatsStruct multi_player_data;
multi_player_data.GlyphXPlayerID = Get_GlyphX_Player_ID( player_ptr );
multi_player_data.IsHuman = (player_ptr->IsHuman || player_ptr->WasHuman);
multi_player_data.WasHuman = player_ptr->WasHuman;
multi_player_data.IsWinner = !player_ptr->IsDefeated;
multi_player_data.Efficiency = economy;
multi_player_data.Score = total_score;
multi_player_data.ResourcesGathered = player_ptr->HarvestedCredits;
multi_player_data.TotalUnitsKilled = units_killed;
multi_player_data.TotalStructuresKilled = structures_killed;
if ( player_index < GAME_OVER_MULTIPLAYER_MAX_PLAYERS_TRACKED )
{
event.GameOver.MultiPlayerPlayersData[ player_index ] = multi_player_data;
}
}
}
for ( int player_index = player_count; player_index < GAME_OVER_MULTIPLAYER_MAX_PLAYERS_TRACKED; player_index ++ )
{
memset( &event.GameOver.MultiPlayerPlayersData[ player_index ], 0, sizeof( GameOverMultiPlayerStatsStruct ) );
}
// Single-player N/A stuff
event.GameOver.MovieName = "";
event.GameOver.MovieName2 = "";
event.GameOver.MovieName3 = "";
event.GameOver.MovieName4 = "";
event.GameOver.AfterScoreMovieName = "";
event.GameOver.Leadership = 0;
event.GameOver.Efficiency = 0;
event.GameOver.Score = 0;
event.GameOver.NODKilled = 0;
event.GameOver.GDIKilled = 0;
event.GameOver.CiviliansKilled = 0;
event.GameOver.NODBuildingsDestroyed = 0;
event.GameOver.GDIBuildingsDestroyed = 0;
event.GameOver.CiviliansBuildingsDestroyed = 0;
event.GameOver.RemainingCredits = 0;
event.GameOver.SabotagedStructureType = 0;
event.GameOver.TimerRemaining = -1;
Command & Conquer Remastered post-launch patch Improvements to harvester resource finding logic. Don't allow the Advanced Comm Center to be capturable in skirmish or multiplayer. Increased failed pathfinding fudge factor. Buildings accept the Guard command if they can attack. Don't allow force capturing of ally structures. Fixes for laser Orcas in S3cr3t M1ss10n. Properly restore them after save. Reset Orcas after loads. Fixed flag animation rendering in CTF. Potentially fix a crash if aircraft are destroyed outside the map bounds. Fixed legacy Obelisk line rendering. Fix out-of-bounds crash in TD; issue was already fixed in RA. Disable capture flag on Commandos. Drop the flag when entering the limbo state. Fixed end game race condition, winning team ID is always sent before individual player win/lose messages. Fixed Chan spawn on SCB10EA. Don't show enter cursor for enemy units on refineries and repair pads. Changing right-click support for first put building on hold, and then subsequenct right-clicks to decrement that queue count for 1x or 5x; Then, 1x or 5x Left click will resume from hold. Don't debug reveal legacy rendering when a player is defeated. Fixed crash when loading saves of custom campaign maps. Reset harvester archived target when given a direct harvest order. Prevent NOD cargo planes from being force attacked. Fixed unit selection on load. Migrated queued repair pad functionality from RA to TD. Randomly animate infantry in area guard mode. Fixed crash accessing inactive objects. Added some walls in SCG08EB to prevent civilians from killing themselves. TD + RA: Audio: Overiding "Our base is under attack" cooldown timing from legacy from 2 minutes to 30 seconds, so it will be heard 4x as often. Fixed adjacent cell out-of-bounds crash issues. Kill player on disconnect Fixed and improved build time calculations to be consistent between TD and RA. Don't show health bars for cloaked Stealth Tanks in the legacy renderer. Fix selection of individual control groups after mixed selection. More adjustments to SCG08EB; switch C7 to C5, and add civilian AI to avoid Tiberium. Extra safety checks for units that have no weapons and aircraft that can't hunt. Fix loading of multiple infantry onto an APC. Additional safety checks for invalid coordinates. Prevent units from being instantly repaired. Fix map passability. Fail Allied mission 5B if the spy re-boards the starting transport (matches 5A and 5C behavior). Fixed multiplayer formation move causing units to move at light speed. Ignore movement destination checks if a unit is part of a mission-driven team. Fix buffer overrun crash. Ignore mines when determining win conditions. Fixed river passability in Blue Lakes.
2020-06-23 04:43:21 +12:00
// Trigger an event for each human player, winner first (even if it's an AI)
for (int i = 0; i < player_count; i++) {
HouseClass *player_ptr = HouseClass::As_Pointer(Session.Players[i]->Player.ID);
if (!player_ptr->IsDefeated) {
event.GlyphXPlayerID = Get_GlyphX_Player_ID(player_ptr);
event.GameOver.IsHuman = player_ptr->IsHuman;
event.GameOver.PlayerWins = true;
event.GameOver.RemainingCredits = player_ptr->Available_Money();
EventCallback(event);
}
}
for (int i = 0; i < player_count; i++) {
HouseClass *player_ptr = HouseClass::As_Pointer(Session.Players[i]->Player.ID);
Command & Conquer Remastered post-launch patch Improvements to harvester resource finding logic. Don't allow the Advanced Comm Center to be capturable in skirmish or multiplayer. Increased failed pathfinding fudge factor. Buildings accept the Guard command if they can attack. Don't allow force capturing of ally structures. Fixes for laser Orcas in S3cr3t M1ss10n. Properly restore them after save. Reset Orcas after loads. Fixed flag animation rendering in CTF. Potentially fix a crash if aircraft are destroyed outside the map bounds. Fixed legacy Obelisk line rendering. Fix out-of-bounds crash in TD; issue was already fixed in RA. Disable capture flag on Commandos. Drop the flag when entering the limbo state. Fixed end game race condition, winning team ID is always sent before individual player win/lose messages. Fixed Chan spawn on SCB10EA. Don't show enter cursor for enemy units on refineries and repair pads. Changing right-click support for first put building on hold, and then subsequenct right-clicks to decrement that queue count for 1x or 5x; Then, 1x or 5x Left click will resume from hold. Don't debug reveal legacy rendering when a player is defeated. Fixed crash when loading saves of custom campaign maps. Reset harvester archived target when given a direct harvest order. Prevent NOD cargo planes from being force attacked. Fixed unit selection on load. Migrated queued repair pad functionality from RA to TD. Randomly animate infantry in area guard mode. Fixed crash accessing inactive objects. Added some walls in SCG08EB to prevent civilians from killing themselves. TD + RA: Audio: Overiding "Our base is under attack" cooldown timing from legacy from 2 minutes to 30 seconds, so it will be heard 4x as often. Fixed adjacent cell out-of-bounds crash issues. Kill player on disconnect Fixed and improved build time calculations to be consistent between TD and RA. Don't show health bars for cloaked Stealth Tanks in the legacy renderer. Fix selection of individual control groups after mixed selection. More adjustments to SCG08EB; switch C7 to C5, and add civilian AI to avoid Tiberium. Extra safety checks for units that have no weapons and aircraft that can't hunt. Fix loading of multiple infantry onto an APC. Additional safety checks for invalid coordinates. Prevent units from being instantly repaired. Fix map passability. Fail Allied mission 5B if the spy re-boards the starting transport (matches 5A and 5C behavior). Fixed multiplayer formation move causing units to move at light speed. Ignore movement destination checks if a unit is part of a mission-driven team. Fix buffer overrun crash. Ignore mines when determining win conditions. Fixed river passability in Blue Lakes.
2020-06-23 04:43:21 +12:00
if (player_ptr->IsHuman && player_ptr->IsDefeated) {
event.GlyphXPlayerID = Get_GlyphX_Player_ID(player_ptr);
Command & Conquer Remastered post-launch patch Improvements to harvester resource finding logic. Don't allow the Advanced Comm Center to be capturable in skirmish or multiplayer. Increased failed pathfinding fudge factor. Buildings accept the Guard command if they can attack. Don't allow force capturing of ally structures. Fixes for laser Orcas in S3cr3t M1ss10n. Properly restore them after save. Reset Orcas after loads. Fixed flag animation rendering in CTF. Potentially fix a crash if aircraft are destroyed outside the map bounds. Fixed legacy Obelisk line rendering. Fix out-of-bounds crash in TD; issue was already fixed in RA. Disable capture flag on Commandos. Drop the flag when entering the limbo state. Fixed end game race condition, winning team ID is always sent before individual player win/lose messages. Fixed Chan spawn on SCB10EA. Don't show enter cursor for enemy units on refineries and repair pads. Changing right-click support for first put building on hold, and then subsequenct right-clicks to decrement that queue count for 1x or 5x; Then, 1x or 5x Left click will resume from hold. Don't debug reveal legacy rendering when a player is defeated. Fixed crash when loading saves of custom campaign maps. Reset harvester archived target when given a direct harvest order. Prevent NOD cargo planes from being force attacked. Fixed unit selection on load. Migrated queued repair pad functionality from RA to TD. Randomly animate infantry in area guard mode. Fixed crash accessing inactive objects. Added some walls in SCG08EB to prevent civilians from killing themselves. TD + RA: Audio: Overiding "Our base is under attack" cooldown timing from legacy from 2 minutes to 30 seconds, so it will be heard 4x as often. Fixed adjacent cell out-of-bounds crash issues. Kill player on disconnect Fixed and improved build time calculations to be consistent between TD and RA. Don't show health bars for cloaked Stealth Tanks in the legacy renderer. Fix selection of individual control groups after mixed selection. More adjustments to SCG08EB; switch C7 to C5, and add civilian AI to avoid Tiberium. Extra safety checks for units that have no weapons and aircraft that can't hunt. Fix loading of multiple infantry onto an APC. Additional safety checks for invalid coordinates. Prevent units from being instantly repaired. Fix map passability. Fail Allied mission 5B if the spy re-boards the starting transport (matches 5A and 5C behavior). Fixed multiplayer formation move causing units to move at light speed. Ignore movement destination checks if a unit is part of a mission-driven team. Fix buffer overrun crash. Ignore mines when determining win conditions. Fixed river passability in Blue Lakes.
2020-06-23 04:43:21 +12:00
event.GameOver.IsHuman = true;
event.GameOver.PlayerWins = false;
event.GameOver.RemainingCredits = player_ptr->Available_Money();
EventCallback(event);
}
}
}
/**************************************************************************************************
* DLLExportClass::On_Achievement -- Called when something achievement-related happens
*
* In: Type of achievement, reason this happened
*
* Out:
*
*
*
* History: 11/11/2019 11:37AM - ST
**************************************************************************************************/
void DLLExportClass::On_Achievement(const HouseClass* player_ptr, const char *achievement_type, const char *achievement_reason)
{
if (EventCallback == NULL) {
return;
}
EventCallbackStruct new_event;
new_event.EventType = CALLBACK_EVENT_ACHIEVEMENT;
new_event.Achievement.AchievementType = achievement_type;
new_event.Achievement.AchievementReason = achievement_reason;
new_event.GlyphXPlayerID = 0;
if (player_ptr != NULL) {
new_event.GlyphXPlayerID = Get_GlyphX_Player_ID(player_ptr);
}
EventCallback(new_event);
}
void DLLExportClass::On_Center_Camera(const HouseClass* player_ptr, int coord_x, int coord_y)
{
if (EventCallback == NULL) {
return;
}
EventCallbackStruct new_event;
new_event.EventType = CALLBACK_EVENT_CENTER_CAMERA;
new_event.CenterCamera.CoordX = coord_x;
new_event.CenterCamera.CoordY = coord_y;
new_event.GlyphXPlayerID = 0;
if (player_ptr != NULL) {
new_event.GlyphXPlayerID = Get_GlyphX_Player_ID(player_ptr);
}
EventCallback(new_event);
}
/**************************************************************************************************
* DLLExportClass::On_Debug_Output -- Called when C&C wants to print debug output
*
* In: String to print to GlyphX log system
*
* Out:
*
*
*
* History: 2/20/2019 2:39PM - ST
**************************************************************************************************/
void DLLExportClass::On_Debug_Output(const char *debug_text)
{
if (EventCallback == NULL) {
return;
}
EventCallbackStruct new_event;
new_event.EventType = CALLBACK_EVENT_DEBUG_PRINT;
new_event.DebugPrint.PrintString = debug_text;
EventCallback(new_event);
}
/**************************************************************************************************
* DLLExportClass::Force_Human_Team_Wins
*
* History: 3/10/2020 - LL
**************************************************************************************************/
void DLLExportClass::Force_Human_Team_Wins(uint64 quitting_player_id)
{
int winning_team = -1;
//Find the first human's multiplayer team.
for (int i = 0; i < Session.Players.Count(); i++)
{
if (GlyphxPlayerIDs[i] != quitting_player_id) {
HousesType house_type = Session.Players[i]->Player.ID;
HouseClass* house_class = HouseClass::As_Pointer(house_type);
if (house_class && house_class->IsHuman && !house_class->IsDefeated) {
winning_team = MPlayerTeamIDs[i];
break;
}
}
}
//Mark all players not on that team as defeated.
for (int i = 0; i < Session.Players.Count(); i++)
{
HousesType house_type = Session.Players[i]->Player.ID;
HouseClass* house_class = HouseClass::As_Pointer(house_type);
if (house_class) {
house_class->IsDefeated = MPlayerTeamIDs[i] != winning_team;
}
}
PlayerWins = true;
}
/**************************************************************************************************
* CNC_Get_Game_State -- Get game state
*
* In: Type of state requested
* Player perspective
* Buffer to contain game state
* Size of buffer
*
* Out: Game state returned in buffer
*
*
*
* History: 1/7/2019 5:20PM - ST
**************************************************************************************************/
extern "C" __declspec(dllexport) bool __cdecl CNC_Get_Game_State(GameStateRequestEnum state_type, uint64 player_id, unsigned char *buffer_in, unsigned int buffer_size)
{
bool got_state = false;
switch (state_type) {
case GAME_STATE_LAYERS:
{
got_state = DLLExportClass::Get_Layer_State(player_id, buffer_in, buffer_size);
break;
}
case GAME_STATE_SIDEBAR:
{
got_state = DLLExportClass::Get_Sidebar_State(player_id, buffer_in, buffer_size);
break;
}
case GAME_STATE_PLACEMENT:
{
got_state = DLLExportClass::Get_Placement_State(player_id, buffer_in, buffer_size);
break;
}
case GAME_STATE_DYNAMIC_MAP:
got_state = DLLExportClass::Get_Dynamic_Map_State(player_id, buffer_in, buffer_size);
break;
case GAME_STATE_SHROUD:
got_state = DLLExportClass::Get_Shroud_State(player_id, buffer_in, buffer_size);
break;
case GAME_STATE_OCCUPIER:
got_state = DLLExportClass::Get_Occupier_State(player_id, buffer_in, buffer_size);
break;
case GAME_STATE_PLAYER_INFO:
got_state = DLLExportClass::Get_Player_Info_State(player_id, buffer_in, buffer_size);
break;
case GAME_STATE_STATIC_MAP:
{
if (buffer_size < sizeof(CNCMapDataStruct)) {
got_state = false;
break;
}
int map_cell_x = Map.MapCellX;
int map_cell_y = Map.MapCellY;
int map_cell_width = Map.MapCellWidth;
int map_cell_height = Map.MapCellHeight;
CNCMapDataStruct *map_data = (CNCMapDataStruct *)buffer_in;
map_data->OriginalMapCellX = map_cell_x;
map_data->OriginalMapCellY = map_cell_y;
map_data->OriginalMapCellWidth = map_cell_width;
map_data->OriginalMapCellHeight = map_cell_height;
if (map_cell_x > 0) {
map_cell_x--;
map_cell_width++;
}
if (map_cell_width < MAP_MAX_CELL_WIDTH) {
map_cell_width++;
}
if (map_cell_y > 0) {
map_cell_y--;
map_cell_height++;
}
if (map_cell_height < MAP_MAX_CELL_HEIGHT) {
map_cell_height++;
}
map_data->MapCellX = map_cell_x;
map_data->MapCellY = map_cell_y;
map_data->MapCellWidth = map_cell_width;
map_data->MapCellHeight = map_cell_height;
map_data->Theater = (CnCTheaterType) Scen.Theater;
char* dot_ptr = strchr(Scen.ScenarioName, '.');
const int count = (dot_ptr != nullptr) ? (dot_ptr - Scen.ScenarioName) : sizeof(Scen.ScenarioName);
memset(map_data->ScenarioName, 0, sizeof(map_data->ScenarioName));
strncpy(map_data->ScenarioName, Scen.ScenarioName, min(sizeof(map_data->ScenarioName) - 1, count));
int cell_index = 0;
char cell_name[_MAX_PATH];
char icon_number[32];
for (int y = 0 ; y < map_cell_height ; y++) {
for (int x = 0 ; x < map_cell_width ; x++) {
CELL cell = XY_Cell(map_cell_x+x, map_cell_y+y);
CellClass * cellptr = &Map[cell];
cell_name[0] = 0;
int icon = 0;
void *image_data = 0;
if (cellptr->Get_Template_Info(cell_name, icon, image_data)) {
itoa(icon, icon_number, 10);
strncat(cell_name, "_i", 32);
strncat(cell_name, icon_number, 32);
strncat(cell_name, ".tga", 32);
CNCStaticCellStruct &cell_info = map_data->StaticCells[cell_index++];
strncpy(cell_info.TemplateTypeName, cell_name, 32);
cell_info.IconNumber = icon;
}
}
}
got_state = true;
break;
}
default:
{
got_state = false;
break;
}
}
return got_state;
}
/**************************************************************************************************
* CNC_Handle_Game_Request
*
* Callback for when the requested movie is done playing.
*
* 7/23/2019 - LLL
**************************************************************************************************/
extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Game_Request(GameRequestEnum request_type)
{
switch (request_type)
{
case INPUT_GAME_MOVIE_DONE:
break;
}
}
extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Game_Settings_Request(int health_bar_display_mode, int resource_bar_display_mode)
{
if (!DLLExportClass::Legacy_Render_Enabled()) {
return;
}
RulesClass::eHealthBarDisplayMode new_hb_mode = (RulesClass::eHealthBarDisplayMode)health_bar_display_mode;
if (new_hb_mode != Rule.HealthBarDisplayMode) {
Rule.HealthBarDisplayMode = new_hb_mode;
Map.Flag_To_Redraw(true);
}
RulesClass::eResourceBarDisplayMode new_rb_mode = (RulesClass::eResourceBarDisplayMode)resource_bar_display_mode;
if (new_rb_mode != Rule.ResourceBarDisplayMode) {
Rule.ResourceBarDisplayMode = new_rb_mode;
Map.Flag_To_Redraw(true);
}
}
void DLL_Draw_Intercept(int shape_number, int x, int y, int width, int height, int flags, const ObjectClass *object, DirType rotation, long scale, const char *shape_file_name, char override_owner)
{
DLLExportClass::DLL_Draw_Intercept(shape_number, x, y, width, height, flags, object, rotation, scale, shape_file_name, override_owner);
}
void DLL_Draw_Pip_Intercept(const ObjectClass* object, int pip)
{
DLLExportClass::DLL_Draw_Pip_Intercept(object, pip);
}
void DLLExportClass::DLL_Draw_Intercept(int shape_number, int x, int y, int width, int height, int flags, const ObjectClass *object, DirType rotation, long scale, const char *shape_file_name, char override_owner)
{
CNCObjectStruct& new_object = ObjectList->Objects[TotalObjectCount + CurrentDrawCount];
memset(&new_object, 0, sizeof(new_object));
Convert_Type(object, new_object);
if (new_object.Type == UNKNOWN) {
return;
}
CNCObjectStruct* base_object = NULL;
char sub_object = 0;
for (int i = 0; i < CurrentDrawCount; ++i) {
CNCObjectStruct& draw_object = ObjectList->Objects[TotalObjectCount + i];
if (draw_object.CNCInternalObjectPointer == object) {
if (base_object == NULL) {
base_object = &draw_object;
}
sub_object++;
}
}
new_object.CNCInternalObjectPointer = (void*)object;
new_object.OccupyListLength = 0;
if (CurrentDrawCount == 0) {
new_object.SortOrder = (ExportLayer << 29) + (object->Sort_Y() >> 3);
} else {
new_object.SortOrder = ObjectList->Objects[TotalObjectCount].SortOrder + CurrentDrawCount;
}
strncpy(new_object.TypeName, object->Class_Of().IniName, CNC_OBJECT_ASSET_NAME_LENGTH);
if (shape_file_name != NULL) {
strncpy(new_object.AssetName, shape_file_name, CNC_OBJECT_ASSET_NAME_LENGTH);
}
else {
strncpy(new_object.AssetName, object->Class_Of().Graphic_Name(), CNC_OBJECT_ASSET_NAME_LENGTH);
}
new_object.Owner = (base_object != NULL) ? ((override_owner != HOUSE_NONE) ? override_owner : base_object->Owner) : (char)object->Owner();
HouseClass* owner_house = nullptr;
for (int i = 0; i < Houses.Count(); ++i) {
HouseClass* hptr = Houses.Ptr(i);
if ((hptr != nullptr) && (hptr->Class->House == new_object.Owner)) {
owner_house = hptr;
break;
}
}
new_object.RemapColor = (owner_house != nullptr) ? owner_house->RemapColor : -1;
if (base_object == NULL) {
CNCObjectStruct& root_object = ObjectList->Objects[TotalObjectCount];
if (new_object.Type == BUILDING) {
BuildingClass *building = (BuildingClass*)object;
if (building->BState == BSTATE_CONSTRUCTION) {
strncat(new_object.AssetName, "MAKE", CNC_OBJECT_ASSET_NAME_LENGTH);
}
const BuildingTypeClass *building_type = building->Class;
short const *occupy_list = building_type->Occupy_List();
if (occupy_list) {
while (*occupy_list != REFRESH_EOL && new_object.OccupyListLength < MAX_OCCUPY_CELLS) {
new_object.OccupyList[new_object.OccupyListLength] = *occupy_list;
new_object.OccupyListLength++;
occupy_list++;
}
}
}
COORDINATE coord = object->Render_Coord();
CELL cell = Coord_Cell(coord);
int dimx, dimy;
object->Class_Of().Dimensions(dimx, dimy);
new_object.PositionX = x;
new_object.PositionY = y;
new_object.Width = width;
new_object.Height = height;
new_object.Altitude = object->Height;
new_object.DrawFlags = flags;
new_object.SubObject = 0;
new_object.ShapeIndex = (unsigned short)shape_number;
new_object.IsTheaterSpecific = IsTheaterShape;
new_object.Rotation = (unsigned char)rotation;
new_object.Scale = scale;
new_object.FlashingFlags = 0;
new_object.Cloak = (CurrentDrawCount > 0) ? root_object.Cloak : UNCLOAKED;
new_object.VisibleFlags = CNCObjectStruct::VISIBLE_FLAGS_ALL;
new_object.SpiedByFlags = 0U;
new_object.IsSelectable = object->Class_Of().IsSelectable;
new_object.IsSelectedMask = object->IsSelectedMask;
new_object.MaxStrength = object->Class_Of().MaxStrength;
new_object.Strength = object->Strength;
new_object.CellX = (CurrentDrawCount > 0) ? root_object.CellX : Cell_X(cell);
new_object.CellY = (CurrentDrawCount > 0) ? root_object.CellY : Cell_Y(cell);
new_object.CenterCoordX = Coord_X(object->Center_Coord());
new_object.CenterCoordY = Coord_Y(object->Center_Coord());
new_object.DimensionX = dimx;
new_object.DimensionY = dimy;
new_object.SimLeptonX = (CurrentDrawCount > 0) ? root_object.SimLeptonX : 0;
new_object.SimLeptonY = (CurrentDrawCount > 0) ? root_object.SimLeptonY : 0;
new_object.BaseObjectID = ((CurrentDrawCount > 0) && (root_object.Type != BUILDING) && (root_object.Type != VESSEL)) ? root_object.ID : 0;
new_object.BaseObjectType = ((CurrentDrawCount > 0) && (root_object.Type != BUILDING) && (root_object.Type != VESSEL)) ? root_object.Type : UNKNOWN;
new_object.NumLines = 0;
new_object.RecentlyCreated = object->IsRecentlyCreated;
new_object.NumPips = 0;
new_object.MaxPips = 0;
new_object.CanDemolish = object->Can_Demolish();
new_object.CanDemolishUnit = object->Can_Demolish_Unit();
new_object.CanRepair = object->Can_Repair();
memset(new_object.CanMove, false, sizeof(new_object.CanMove));
memset(new_object.CanFire, false, sizeof(new_object.CanFire));
memset(new_object.ActionWithSelected, DAT_NONE, sizeof(new_object.ActionWithSelected));
HouseClass* old_player_ptr = PlayerPtr;
for (int i = 0; i < Houses.Count(); ++i) {
HouseClass* hptr = Houses.Ptr(i);
if ((hptr != nullptr) && hptr->IsActive && hptr->IsHuman) {
HousesType house = hptr->Class->House;
DynamicVectorClass<ObjectClass*>& selected_objects = CurrentObject.Raw(house);
if (selected_objects.Count() > 0) {
Logic_Switch_Player_Context(hptr);
Convert_Action_Type(Best_Object_Action(selected_objects, object), (selected_objects.Count() == 1) ? selected_objects[0] : NULL, object->As_Target(), new_object.ActionWithSelected[house]);
}
}
}
Logic_Switch_Player_Context(old_player_ptr);
RTTIType what_is_object = object->What_Am_I();
new_object.IsRepairing = false;
new_object.IsDumping = false;
new_object.IsALoaner = false;
new_object.IsFactory = false;
new_object.IsPrimaryFactory = false;
new_object.IsNominal = false;
new_object.IsDog = false;
new_object.IsIronCurtain = false;
new_object.IsAntiGround = false;
new_object.IsAntiAircraft = false;
new_object.IsSubSurface = false;
new_object.IsFake = false;
new_object.ProductionAssetName[0] = '\0';
new_object.OverrideDisplayName = "\0";
bool is_building = what_is_object == RTTI_BUILDING;
if (is_building) {
const BuildingClass* building = static_cast<const BuildingClass*>(object);
new_object.IsRepairing = building->IsRepairing;
new_object.IsFactory = building->Class->Is_Factory();
new_object.IsPrimaryFactory = building->IsLeader;
new_object.IsFake = building->Class->IsFake;
}
if (object->Is_Techno()) {
const TechnoClass* techno_object = static_cast<const TechnoClass*>(object);
const TechnoTypeClass *ttype = techno_object->Techno_Type_Class();
new_object.MaxSpeed = (unsigned char)ttype->MaxSpeed;
new_object.IsALoaner = techno_object->IsALoaner;
new_object.IsNominal = ttype->IsNominal;
new_object.MaxPips = ttype->Max_Pips();
new_object.IsAntiGround = (ttype->PrimaryWeapon != NULL) && (ttype->PrimaryWeapon->Bullet != NULL) && ttype->PrimaryWeapon->Bullet->IsAntiGround;
new_object.IsAntiAircraft = (ttype->PrimaryWeapon != NULL) && (ttype->PrimaryWeapon->Bullet != NULL) && ttype->PrimaryWeapon->Bullet->IsAntiAircraft;
new_object.IsSubSurface = (ttype->PrimaryWeapon != NULL) && (ttype->PrimaryWeapon->Bullet != NULL) && ttype->PrimaryWeapon->Bullet->IsSubSurface;
new_object.IsIronCurtain = techno_object->IronCurtainCountDown > 0;
int full_name = techno_object->Full_Name();
if (full_name < 0)
{
new_object.OverrideDisplayName = Text_String(full_name);
}
HouseClass* old_player_ptr = PlayerPtr;
for (int i = 0; i < Houses.Count(); ++i) {
HouseClass* hptr = Houses.Ptr(i);
if ((hptr != nullptr) && hptr->IsActive && hptr->IsHuman) {
Logic_Switch_Player_Context(hptr);
HousesType house = hptr->Class->House;
new_object.CanMove[house] = techno_object->Can_Player_Move();
new_object.CanFire[house] = techno_object->Can_Player_Fire();
}
}
Logic_Switch_Player_Context(old_player_ptr);
}
new_object.ControlGroup = (unsigned char)(-1);
new_object.IsInFormation = false;
new_object.CanPlaceBombs = false;
if (object->Is_Foot()) {
const FootClass* foot = static_cast<const FootClass*>(object);
new_object.ControlGroup = foot->Group;
new_object.IsInFormation = foot->XFormOffset != 0x80000000UL;
}
bool is_infantry = what_is_object == RTTI_INFANTRY;
if (is_infantry) {
const InfantryClass* infantry = static_cast<const InfantryClass*>(object);
new_object.IsDog = infantry->Class->IsDog;
new_object.CanPlaceBombs = infantry->Class->IsBomber;
}
new_object.CanHarvest = false;
bool is_unit = what_is_object == RTTI_UNIT;
if (is_unit) {
const UnitClass* unit = static_cast<const UnitClass*>(object);
if (unit->Class->Type == UNIT_HARVESTER)
{
new_object.CanHarvest = true;
}
new_object.IsDumping = unit->IsDumping;
}
new_object.IsFixedWingedAircraft = false;
bool is_aircraft = what_is_object == RTTI_AIRCRAFT;
if (is_aircraft) {
const AircraftClass* aircraft = static_cast<const AircraftClass*>(object);;
new_object.IsFixedWingedAircraft = aircraft->Class->IsFixedWing;
}
switch (what_is_object)
{
case RTTI_INFANTRY:
case RTTI_INFANTRYTYPE:
case RTTI_UNIT:
case RTTI_UNITTYPE:
case RTTI_AIRCRAFT:
case RTTI_AIRCRAFTTYPE:
case RTTI_BUILDING:
case RTTI_BUILDINGTYPE:
case RTTI_VESSEL:
case RTTI_VESSELTYPE:
{
const TechnoClass* techno_object = static_cast<const TechnoClass*>(object);
new_object.FlashingFlags = techno_object->Get_Flashing_Flags();
new_object.Cloak = techno_object->Cloak;
new_object.SpiedByFlags = techno_object->Spied_By();
if (techno_object->Techno_Type_Class()->IsInvisible) {
// Hide for enemy players
HouseClass* owner = HouseClass::As_Pointer(object->Owner());
if (owner != nullptr) {
for (int i = 0; i < Houses.Count(); ++i) {
HouseClass* hptr = Houses.Ptr(i);
if ((hptr != nullptr) && hptr->IsActive && !owner->Is_Ally(hptr)) {
new_object.VisibleFlags &= ~(1 << hptr->Class->House);
}
}
}
}
}
break;
case RTTI_ANIM:
case RTTI_ANIMTYPE:
{
const AnimClass* anim_object = static_cast<const AnimClass*>(object);
new_object.Owner = anim_object->OwnerHouse;
new_object.RemapColor = -1;
new_object.VisibleFlags = anim_object->Get_Visible_Flags();
const AnimTypeClass& anim_type = static_cast<const AnimTypeClass&>(anim_object->Class_Of());
if (anim_type.VirtualName != NULL) {
strncpy(new_object.AssetName, anim_type.VirtualName, CNC_OBJECT_ASSET_NAME_LENGTH);
}
}
break;
case RTTI_TERRAIN:
case RTTI_TERRAINTYPE:
{
if (strncmp(new_object.AssetName, "MINE", CNC_OBJECT_ASSET_NAME_LENGTH) == 0) {
strncpy(new_object.AssetName, "OREMINE", CNC_OBJECT_ASSET_NAME_LENGTH);
}
}
break;
}
}
else {
new_object.MaxStrength = 0;
new_object.MaxSpeed = 0;
new_object.Strength = 0;
new_object.CellX = base_object->CellX;
new_object.CellY = base_object->CellY;
new_object.CenterCoordX = base_object->CenterCoordX;
new_object.CenterCoordY = base_object->CenterCoordY;
new_object.DimensionX = base_object->DimensionX;
new_object.DimensionY = base_object->DimensionY;
new_object.IsSelectable = false;
new_object.IsSelectedMask = 0U;
new_object.SimLeptonX = base_object->SimLeptonX;
new_object.SimLeptonY = base_object->SimLeptonY;
new_object.PositionX = x;
new_object.PositionY = y;
new_object.Width = width;
new_object.Height = height;
new_object.Altitude = base_object->Altitude;
new_object.DrawFlags = flags;
new_object.ShapeIndex = (unsigned short)shape_number;
new_object.IsTheaterSpecific = IsTheaterShape;
new_object.Rotation = (unsigned char)rotation;
new_object.Scale = scale;
new_object.SubObject = sub_object;
new_object.BaseObjectID = base_object->ID;
new_object.BaseObjectType = base_object->Type;
new_object.FlashingFlags = base_object->FlashingFlags;
new_object.Cloak = base_object->Cloak;
new_object.OccupyListLength = 0;
new_object.NumPips = 0;
new_object.MaxPips = 0;
new_object.NumLines = 0;
new_object.IsRepairing = false;
new_object.IsDumping = false;
new_object.IsALoaner = base_object->IsALoaner;
new_object.CanDemolish = base_object->CanDemolish;
new_object.CanDemolishUnit = base_object->CanDemolishUnit;
new_object.CanRepair = base_object->CanRepair;
new_object.RecentlyCreated = base_object->RecentlyCreated;
new_object.IsFactory = base_object->IsFactory;
new_object.IsPrimaryFactory = base_object->IsPrimaryFactory;
new_object.IsAntiGround = base_object->IsAntiGround;
new_object.IsAntiAircraft = base_object->IsAntiAircraft;
new_object.IsSubSurface = base_object->IsSubSurface;
new_object.IsNominal = base_object->IsNominal;
new_object.IsDog = base_object->IsDog;
new_object.IsIronCurtain = base_object->IsIronCurtain;
new_object.IsInFormation = false;
new_object.CanHarvest = base_object->CanHarvest;
new_object.CanPlaceBombs = base_object->CanPlaceBombs;
new_object.ControlGroup = base_object->ControlGroup;
new_object.VisibleFlags = base_object->VisibleFlags;
new_object.SpiedByFlags = base_object->SpiedByFlags;
new_object.IsFixedWingedAircraft = base_object->IsFixedWingedAircraft;
new_object.IsFake = base_object->IsFake;
new_object.ProductionAssetName[0] = '\0';
new_object.OverrideDisplayName = "\0";
memset(new_object.CanMove, false, sizeof(new_object.CanMove));
memset(new_object.CanFire, false, sizeof(new_object.CanFire));
memset(new_object.ActionWithSelected, DAT_NONE, sizeof(new_object.ActionWithSelected));
}
CurrentDrawCount++;
}
void DLLExportClass::DLL_Draw_Pip_Intercept(const ObjectClass* object, int pip)
{
CNCObjectStruct* base_object = NULL;
for (int i = 0; i < CurrentDrawCount; ++i) {
CNCObjectStruct& draw_object = ObjectList->Objects[TotalObjectCount + i];
if (draw_object.CNCInternalObjectPointer == object) {
base_object = &draw_object;
break;
}
}
if ((base_object != NULL) && (base_object->NumPips < MAX_OBJECT_PIPS)) {
base_object->Pips[base_object->NumPips] = pip;
base_object->NumPips++;
base_object->MaxPips = max(base_object->MaxPips, base_object->NumPips);
}
}
/**************************************************************************************************
* DLLExportClass::Get_Layer_State -- Get game objects from the layers
*
* In:
*
* Out:
*
*
*
* History: 1/29/2019 11:37AM - ST
**************************************************************************************************/
bool DLLExportClass::Get_Layer_State(uint64 player_id, unsigned char *buffer_in, unsigned int buffer_size)
{
player_id;
static int _export_count = 0;
bool got_state = false;
ObjectList = (CNCObjectListStruct*) buffer_in;
TotalObjectCount = 0;
/*
** Get a reference draw coordinate for cells
*/
int map_cell_x = Map.MapCellX;
int map_cell_y = Map.MapCellY;
if (map_cell_x > 0) {
map_cell_x--;
}
if (map_cell_y > 0) {
map_cell_y--;
}
SortOrder = 0;
/*
** Get the ground layer first and then followed by all the layers in increasing altitude.
*/
for (int layer = 0; layer < DLL_LAYER_COUNT; layer++) {
ExportLayer = layer;
for (int index = 0; index < Map.Layer[layer].Count(); index++) {
ObjectClass *object = Map.Layer[layer][index];
if (object->IsActive) {
unsigned int memory_needed = sizeof(CNCObjectListStruct);
memory_needed += (TotalObjectCount + 10) * sizeof(CNCObjectStruct);
if (memory_needed >= buffer_size) {
return false;
}
if (object->Is_Techno()) {
/*
** Skip units tethered to buildings, since the building will draw them itself
*/
TechnoClass* techno_object = static_cast<TechnoClass*>(object);
TechnoClass* contact_object = techno_object->In_Radio_Contact() ? techno_object->Contact_With_Whom() : nullptr;
if ((object->What_Am_I() != RTTI_BUILDING) && (contact_object != nullptr) && (contact_object->What_Am_I() == RTTI_BUILDING) && contact_object->IsTethered && *((BuildingClass*)contact_object) == STRUCT_WEAP) {
continue;
}
/*
** Skip units tethered to vessels, since the vessel will draw them itself
*/
if ((contact_object != nullptr) && (contact_object->What_Am_I() == RTTI_VESSEL) && !contact_object->Is_Door_Closed() && contact_object->IsTethered && !techno_object->IsInLimbo) {
continue;
}
}
if (Debug_Map || Debug_Unshroud || (object->IsDown && !object->IsInLimbo)) {
int x, y;
Map.Coord_To_Pixel(object->Render_Coord(), x, y);
/*
** Call to Draw_It can result in multiple callbacks to the draw intercept
*/
CurrentDrawCount = 0;
object->Draw_It(x, y, WINDOW_VIRTUAL);
/*
** If the root object is a factory, then the last base object is the object in production (rendered after infiltrated buildings when selected).
** The root object is updated with the production asset name, but otherwise a separate object isn't created.
** This only occurs in skirmish and multiplayer.
*/
if ((GAME_TO_PLAY != GAME_NORMAL) && (CurrentDrawCount > 0)) {
CNCObjectStruct& root_object = ObjectList->Objects[TotalObjectCount];
if (root_object.IsFactory) {
BuildingClass* building = (BuildingClass*)root_object.CNCInternalObjectPointer;
FactoryClass* factory = building->House->IsHuman ? building->House->Fetch_Factory(building->Class->ToBuild) : building->Factory;
if (factory != nullptr) {
for (int i = CurrentDrawCount - 1; i > 0; --i) {
CNCObjectStruct& base_object = ObjectList->Objects[TotalObjectCount + i];
if (base_object.SubObject) {
continue;
}
strncpy(root_object.ProductionAssetName, base_object.TypeName, CNC_OBJECT_ASSET_NAME_LENGTH);
void* production_object = base_object.CNCInternalObjectPointer;
int new_draw_count = i;
for (int j = i + 1; j < CurrentDrawCount; ++j) {
CNCObjectStruct& cnc_object = ObjectList->Objects[TotalObjectCount + j];
if (cnc_object.CNCInternalObjectPointer != production_object) {
memcpy(ObjectList->Objects + TotalObjectCount + new_draw_count, &cnc_object, sizeof(CNCObjectStruct));
new_draw_count++;
}
}
memset(ObjectList->Objects + TotalObjectCount + new_draw_count, 0, (CurrentDrawCount - new_draw_count) * sizeof(CNCObjectStruct));
CurrentDrawCount = new_draw_count;
break;
}
}
}
}
/*
** Shadows need to be rendered before the base object so they appear underneath,
** even though they get drawn as sub-objects (after the base object)
*/
for (int i = 1; i < CurrentDrawCount; ++i) {
CNCObjectStruct& sub_object = ObjectList->Objects[TotalObjectCount + i];
if (!sub_object.SubObject) {
continue;
}
static const int shadow_flags = SHAPE_PREDATOR | SHAPE_FADING;
if (((sub_object.DrawFlags & shadow_flags) == shadow_flags) || (strncmp(sub_object.AssetName, "WAKE", CNC_OBJECT_ASSET_NAME_LENGTH) == 0)) {
if ((strncmp(sub_object.AssetName, "RROTOR", CNC_OBJECT_ASSET_NAME_LENGTH) != 0) &&
(strncmp(sub_object.AssetName, "LROTOR", CNC_OBJECT_ASSET_NAME_LENGTH) != 0)) {
for (int j = i - 1; j >= 0; --j) {
CNCObjectStruct& base_object = ObjectList->Objects[TotalObjectCount + j];
if (!base_object.SubObject && (base_object.CNCInternalObjectPointer == sub_object.CNCInternalObjectPointer)) {
int sort_order = base_object.SortOrder;
base_object.SortOrder = sub_object.SortOrder;
sub_object.SortOrder = sort_order;
break;
}
}
}
}
}
TotalObjectCount += CurrentDrawCount;
}
}
}
}
ObjectList->Count = TotalObjectCount;
if (ObjectList->Count) {
_export_count++;
return true;
}
return false;
}
void DLLExportClass::Convert_Type(const ObjectClass *object, CNCObjectStruct &object_out)
{
object_out.Type = UNKNOWN;
object_out.ID = -1;
if (object == NULL) {
return;
}
RTTIType type = object->What_Am_I();
switch (type) {
default:
break;
case RTTI_VESSEL:
object_out.Type = VESSEL;
object_out.ID = Vessels.ID((VesselClass*)object);
break;
case RTTI_INFANTRY:
object_out.Type = INFANTRY;
object_out.ID = Infantry.ID((InfantryClass*)object);
break;
case RTTI_UNIT:
object_out.Type = UNIT;
object_out.ID = Units.ID((UnitClass*)object);
break;
case RTTI_AIRCRAFT:
object_out.Type = AIRCRAFT;
object_out.ID = Aircraft.ID((AircraftClass*)object);
break;
case RTTI_BUILDING:
object_out.Type = BUILDING;
object_out.ID = Buildings.ID((BuildingClass*)object);
break;
case RTTI_BULLET:
object_out.Type = BULLET;
object_out.ID = Bullets.ID((BulletClass*)object);
break;
case RTTI_ANIM:
object_out.Type = ANIM;
object_out.ID = Anims.ID((AnimClass*)object);
break;
case RTTI_SMUDGE:
object_out.Type = SMUDGE;
object_out.ID = Smudges.ID((SmudgeClass*)object);
break;
case RTTI_TERRAIN:
object_out.Type = TERRAIN;
object_out.ID = Terrains.ID((TerrainClass*)object);
break;
}
}
/**************************************************************************************************
* CNC_Handle_Input -- Process input to the game
*
* In:
*
*
*
*
* Out: Game state returned in buffer
*
*
*
* History: 1/7/2019 5:20PM - ST
**************************************************************************************************/
extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Input(InputRequestEnum input_event, unsigned char special_key_flags, uint64 player_id, int x1, int y1, int x2, int y2)
{
if (!DLLExportClass::Set_Player_Context(player_id)) {
return;
}
switch (input_event) {
/*
** Special keys have changed
*/
case INPUT_REQUEST_SPECIAL_KEYS:
{
DLLExportClass::Set_Special_Key_Flags(special_key_flags);
break;
}
/*
** The mouse is moving
*/
case INPUT_REQUEST_MOUSE_MOVE:
{
if (!DLLExportClass::Legacy_Render_Enabled()) {
break;
}
DLLForceMouseX = x1;
DLLForceMouseY = y1;
KEYBOARD->MouseQX = x1;
KEYBOARD->MouseQY = y1;
COORDINATE coord = Map.Pixel_To_Coord(x1, y1);
CELL cell = Coord_Cell(coord);
if (coord) {
//x -= Map.TacPixelX;
//y -= Map.TacPixelY;
/*
** Cause any displayed cursor to move along with the mouse cursor.
*/
if (cell != Map.ZoneCell) {
Map.Set_Cursor_Pos(cell);
}
}
break;
}
/*
** Player left-clicked
*/
case INPUT_REQUEST_MOUSE_LEFT_CLICK:
{
DLLExportClass::Adjust_Internal_View();
DLLForceMouseX = x1;
DLLForceMouseY = y1;
KEYBOARD->MouseQX = x1;
KEYBOARD->MouseQY = y1;
KeyNumType key = (KeyNumType)(KN_LMOUSE | KN_RLSE_BIT);
if (Map.Pixel_To_Coord(x1, y1)) {
//DisplayClass::TacButton.Clicked_On(key, GadgetClass::LEFTRELEASE, x1, y1);
DisplayClass::TacButton.Clicked_On(key, GadgetClass::LEFTRELEASE, 100, 100);
}
break;
}
/*
** Player right-clicked (on up)
*/
case INPUT_REQUEST_MOUSE_RIGHT_CLICK:
{
DLLExportClass::Adjust_Internal_View();
DLLForceMouseX = x1;
DLLForceMouseY = y1;
KEYBOARD->MouseQX = x1;
KEYBOARD->MouseQY = y1;
KeyNumType key = (KeyNumType)(KN_RMOUSE | KN_RLSE_BIT);
if (Map.Pixel_To_Coord(x1, y1)) {
//DisplayClass::TacButton.Clicked_On(key, GadgetClass::RIGHTRELEASE, x1, y1);
DisplayClass::TacButton.Clicked_On(key, GadgetClass::RIGHTRELEASE, 100, 100);
}
break;
}
/*
** Player right button down
*/
case INPUT_REQUEST_MOUSE_RIGHT_DOWN:
{
DLLExportClass::Adjust_Internal_View();
DLLForceMouseX = x1;
DLLForceMouseY = y1;
KEYBOARD->MouseQX = x1;
KEYBOARD->MouseQY = y1;
KeyNumType key = (KeyNumType)(KN_RMOUSE);
if (Map.Pixel_To_Coord(x1, y1)) {
//DisplayClass::TacButton.Clicked_On(key, GadgetClass::RIGHTPRESS, x1, y1);
DisplayClass::TacButton.Clicked_On(key, GadgetClass::RIGHTPRESS, 100, 100);
}
break;
}
/*
** Player drag selected
*/
case INPUT_REQUEST_MOUSE_AREA:
{
DLLExportClass::Adjust_Internal_View();
Map.Select_These(XYP_Coord(x1, y1), XYP_Coord(x2, y2), false);
break;
}
case INPUT_REQUEST_MOUSE_AREA_ADDITIVE:
{
DLLExportClass::Adjust_Internal_View();
Map.Select_These(XYP_Coord(x1, y1), XYP_Coord(x2, y2), true);
break;
}
case INPUT_REQUEST_SELL_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);
PlayerPtr->Sell_Wall(cell);
break;
}
case INPUT_REQUEST_SELECT_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))
{
KeyNumType key = (KeyNumType)(KN_LMOUSE | KN_RLSE_BIT);
DisplayClass::TacButton.Selection_At_Mouse(GadgetClass::LEFTRELEASE, key);
}
break;
}
case INPUT_REQUEST_COMMAND_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))
{
KeyNumType key = (KeyNumType)(KN_LMOUSE | KN_RLSE_BIT);
DisplayClass::TacButton.Command_Object(GadgetClass::LEFTRELEASE, key);
}
break;
}
default:
break;
}
}
/**************************************************************************************************
* CNC_Handle_Structure_Request -- Process requests to repair and sell structures.
*
* In:
*
*
* Out:
*
*
*
* History: 4/29/2019 - LLL
**************************************************************************************************/
extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Structure_Request(StructureRequestEnum request_type, uint64 player_id, int object_id)
{
if (!DLLExportClass::Set_Player_Context(player_id)) {
return;
}
switch (request_type)
{
case INPUT_STRUCTURE_REPAIR_START:
DLLExportClass::Repair_Mode(player_id);
break;
case INPUT_STRUCTURE_REPAIR:
DLLExportClass::Repair(player_id, object_id);
break;
case INPUT_STRUCTURE_SELL_START:
DLLExportClass::Sell_Mode(player_id);
break;
case INPUT_STRUCTURE_SELL:
DLLExportClass::Sell(player_id, object_id);
break;
case INPUT_STRUCTURE_CANCEL:
DLLExportClass::Repair_Sell_Cancel(player_id);
break;
default:
break;
}
}
/**************************************************************************************************
* CNC_Handle_Unit_Request -- Process requests on selected units.
*
* In:
*
*
* Out:
*
*
*
* History: 10/15/2019 - SKY
**************************************************************************************************/
extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Unit_Request(UnitRequestEnum request_type, uint64 player_id)
{
if (!DLLExportClass::Set_Player_Context(player_id)) {
return;
}
switch (request_type)
{
case INPUT_UNIT_SCATTER:
DLLExportClass::Scatter_Selected(player_id);
break;
case INPUT_UNIT_SELECT_NEXT:
DLLExportClass::Select_Next_Unit(player_id);
break;
case INPUT_UNIT_SELECT_PREVIOUS:
DLLExportClass::Select_Previous_Unit(player_id);
break;
case INPUT_UNIT_GUARD_MODE:
DLLExportClass::Selected_Guard_Mode(player_id);
break;
case INPUT_UNIT_STOP:
DLLExportClass::Selected_Stop(player_id);
break;
case INPUT_UNIT_FORMATION_TOGGLE:
DLLExportClass::Team_Units_Formation_Toggle_On(player_id);
break;
case INPUT_UNIT_QUEUED_MOVEMENT_ON:
// Red Alert Only
DLLExportClass::Units_Queued_Movement_Toggle(player_id, true);
break;
case INPUT_UNIT_QUEUED_MOVEMENT_OFF:
// Red Alert Only
DLLExportClass::Units_Queued_Movement_Toggle(player_id, false);
break;
default:
break;
}
}
/**************************************************************************************************
* CNC_Handle_Sidebar_Request -- Process an input request to the sidebar
*
* In:
*
*
* Out:
*
*
*
* History: 1/7/2019 5:20PM - ST
**************************************************************************************************/
extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Sidebar_Request(SidebarRequestEnum request_type, uint64 player_id, int buildable_type, int buildable_id, short cell_x, short cell_y)
{
if (!DLLExportClass::Set_Player_Context(player_id)) {
return;
}
switch (request_type) {
Command & Conquer Remastered post-launch patch Improvements to harvester resource finding logic. Don't allow the Advanced Comm Center to be capturable in skirmish or multiplayer. Increased failed pathfinding fudge factor. Buildings accept the Guard command if they can attack. Don't allow force capturing of ally structures. Fixes for laser Orcas in S3cr3t M1ss10n. Properly restore them after save. Reset Orcas after loads. Fixed flag animation rendering in CTF. Potentially fix a crash if aircraft are destroyed outside the map bounds. Fixed legacy Obelisk line rendering. Fix out-of-bounds crash in TD; issue was already fixed in RA. Disable capture flag on Commandos. Drop the flag when entering the limbo state. Fixed end game race condition, winning team ID is always sent before individual player win/lose messages. Fixed Chan spawn on SCB10EA. Don't show enter cursor for enemy units on refineries and repair pads. Changing right-click support for first put building on hold, and then subsequenct right-clicks to decrement that queue count for 1x or 5x; Then, 1x or 5x Left click will resume from hold. Don't debug reveal legacy rendering when a player is defeated. Fixed crash when loading saves of custom campaign maps. Reset harvester archived target when given a direct harvest order. Prevent NOD cargo planes from being force attacked. Fixed unit selection on load. Migrated queued repair pad functionality from RA to TD. Randomly animate infantry in area guard mode. Fixed crash accessing inactive objects. Added some walls in SCG08EB to prevent civilians from killing themselves. TD + RA: Audio: Overiding "Our base is under attack" cooldown timing from legacy from 2 minutes to 30 seconds, so it will be heard 4x as often. Fixed adjacent cell out-of-bounds crash issues. Kill player on disconnect Fixed and improved build time calculations to be consistent between TD and RA. Don't show health bars for cloaked Stealth Tanks in the legacy renderer. Fix selection of individual control groups after mixed selection. More adjustments to SCG08EB; switch C7 to C5, and add civilian AI to avoid Tiberium. Extra safety checks for units that have no weapons and aircraft that can't hunt. Fix loading of multiple infantry onto an APC. Additional safety checks for invalid coordinates. Prevent units from being instantly repaired. Fix map passability. Fail Allied mission 5B if the spy re-boards the starting transport (matches 5A and 5C behavior). Fixed multiplayer formation move causing units to move at light speed. Ignore movement destination checks if a unit is part of a mission-driven team. Fix buffer overrun crash. Ignore mines when determining win conditions. Fixed river passability in Blue Lakes.
2020-06-23 04:43:21 +12:00
// MBL 06.02.2020 - Changing right-click support for first put building on hold, and then subsequenct right-clicks to decrement that queue count for 1x or 5x; Then, 1x or 5x Left click will resume from hold
// Handle and fall through to start construction (from hold state) below
case SIDEBAR_REQUEST_START_CONSTRUCTION_MULTI:
case SIDEBAR_REQUEST_START_CONSTRUCTION:
DLLExportClass::Start_Construction(player_id, buildable_type, buildable_id);
break;
case SIDEBAR_REQUEST_HOLD_CONSTRUCTION:
DLLExportClass::Hold_Construction(player_id, buildable_type, buildable_id);
break;
case SIDEBAR_REQUEST_CANCEL_CONSTRUCTION:
DLLExportClass::Cancel_Construction(player_id, buildable_type, buildable_id);
break;
case SIDEBAR_REQUEST_START_PLACEMENT:
DLLExportClass::Start_Placement(player_id, buildable_type, buildable_id);
break;
case SIDEBAR_REQUEST_PLACE:
DLLExportClass::Place(player_id, buildable_type, buildable_id, cell_x, cell_y);
break;
case SIDEBAR_CANCEL_PLACE:
DLLExportClass::Cancel_Placement(player_id, buildable_type, buildable_id);
break;
default:
break;
}
}
/**************************************************************************************************
* CNC_Handle_SuperWeapon_Request
*
* In:
*
*
* Out:
*
*
*
* History:
**************************************************************************************************/
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)
{
if (!DLLExportClass::Set_Player_Context(player_id)) {
return;
}
switch (request_type)
{
case SUPERWEAPON_REQUEST_PLACE_SUPER_WEAPON:
DLLExportClass::Place_Super_Weapon(player_id, buildable_type, buildable_id, x1, y1);
break;
}
}
/**************************************************************************************************
* CNC_Handle_ControlGroup_Request
*
* In:
*
*
* Out:
*
*
*
* History:
**************************************************************************************************/
extern "C" __declspec(dllexport) void __cdecl CNC_Handle_ControlGroup_Request(ControlGroupRequestEnum request_type, uint64 player_id, unsigned char control_group_index)
{
if (!DLLExportClass::Set_Player_Context(player_id)) {
return;
}
switch (request_type)
{
case CONTROL_GROUP_REQUEST_CREATE:
DLLExportClass::Create_Control_Group(control_group_index);
break;
case CONTROL_GROUP_REQUEST_TOGGLE:
DLLExportClass::Toggle_Control_Group_Selection(control_group_index);
break;
case CONTROL_GROUP_REQUEST_ADDITIVE_SELECTION:
DLLExportClass::Add_To_Control_Group(control_group_index);
break;
}
}
/**************************************************************************************************
* DLLExportClass::Get_Layer_State -- Get a snapshot of the sidebar state
*
* In:
*
* Out:
*
*
*
* History: 1/29/2019 11:37AM - ST
**************************************************************************************************/
bool DLLExportClass::Get_Sidebar_State(uint64 player_id, unsigned char *buffer_in, unsigned int buffer_size)
{
/*
** Get the player for this...
*/
if (!DLLExportClass::Set_Player_Context(player_id)) {
return false;
}
CNCSidebarStruct *sidebar = (CNCSidebarStruct*) buffer_in;
unsigned int memory_needed = sizeof(*sidebar); // Base amount needed. Will need more depending on how many entries there are
int entry_index = 0;
sidebar->Credits = 0;
sidebar->CreditsCounter = 0;
sidebar->Tiberium = 0;
sidebar->MaxTiberium = 0;
sidebar->PowerProduced = 0;
sidebar->PowerDrained = 0;
sidebar->RepairBtnEnabled = false;
sidebar->SellBtnEnabled = false;
sidebar->RadarMapActive = false;
sidebar->MissionTimer = Scen.MissionTimer.Is_Active() ? (Scen.MissionTimer / TICKS_PER_SECOND) : -1;
sidebar->UnitsKilled = 0;
sidebar->BuildingsKilled = 0;
sidebar->UnitsLost = 0;
sidebar->BuildingsLost = 0;
sidebar->TotalHarvestedCredits = 0;
if (PlayerPtr) {
sidebar->Credits = PlayerPtr->Credits;
sidebar->CreditsCounter = PlayerPtr->VisibleCredits.Current; // Timed display
// sidebar->CreditsCounter = PlayerPtr->VisibleCredits.Credits; // Actual
sidebar->Tiberium = PlayerPtr->Tiberium;
sidebar->MaxTiberium = PlayerPtr->Capacity;
sidebar->PowerProduced = PlayerPtr->Power;
sidebar->PowerDrained = PlayerPtr->Drain;
sidebar->RepairBtnEnabled = PlayerPtr->BScan > 0;
sidebar->SellBtnEnabled = PlayerPtr->BScan > 0;
sidebar->RadarMapActive = PlayerPtr->Radar == RADAR_ON;
// A. Get the DestroyedBuildings and DestroyedInfantry stats if they are available at this point
if (PlayerPtr->DestroyedBuildings) {
for ( int index = 0; index < PlayerPtr->DestroyedBuildings->Get_Unit_Count(); index ++ )
{
unsigned int count = (unsigned int) PlayerPtr->DestroyedBuildings->Get_Unit_Total( index );
sidebar->BuildingsKilled += count;
}
}
if (PlayerPtr->DestroyedInfantry) {
for ( int index = 0; index < PlayerPtr->DestroyedInfantry->Get_Unit_Count(); index ++ )
{
unsigned int count = (unsigned int) PlayerPtr->DestroyedInfantry->Get_Unit_Total( index );
sidebar->UnitsKilled += count; // Includes Infantry, Vehicles, Aircraft
}
}
if (PlayerPtr->DestroyedUnits) {
for ( int index = 0; index < PlayerPtr->DestroyedUnits->Get_Unit_Count(); index ++ )
{
unsigned int count = (unsigned int) PlayerPtr->DestroyedUnits->Get_Unit_Total( index );
sidebar->UnitsKilled += count; // Includes Infantry, Vehicles, Aircraft
}
}
if (PlayerPtr->DestroyedAircraft) {
for ( int index = 0; index < PlayerPtr->DestroyedAircraft->Get_Unit_Count(); index ++ )
{
unsigned int count = (unsigned int) PlayerPtr->DestroyedAircraft->Get_Unit_Total( index );
sidebar->UnitsKilled += count; // Includes Infantry, Vehicles, Aircraft
}
}
// B. If the DestroyedBuildings and DestroyedInfantry stats seemed to be unvailable, this is another way to do it
// Note that we need to do both of these depending on which type of match we are running, as well as for Replays/Observer and live stats reporting
// We can't just do it this way for everything, as it does not work for all cases
if (sidebar->BuildingsKilled == 0)
{
for (unsigned int house_index = 0; house_index < HOUSE_COUNT; house_index ++)
{
sidebar->BuildingsKilled += PlayerPtr->BuildingsKilled[ house_index ];
}
}
if (sidebar->UnitsKilled == 0)
{
for (unsigned int house_index = 0; house_index < HOUSE_COUNT; house_index ++)
{
sidebar->UnitsKilled += PlayerPtr->UnitsKilled[ house_index ]; // Includes Infantry, Vehicles, Aircraft
}
}
sidebar->UnitsLost = PlayerPtr->UnitsLost;
sidebar->BuildingsLost = PlayerPtr->BuildingsLost;
sidebar->TotalHarvestedCredits = PlayerPtr->HarvestedCredits;
}
if (GAME_TO_PLAY == GAME_NORMAL) {
/*
** Get each sidebar column
*/
for (int c = 0 ; c < 2 ; c++) {
sidebar->EntryCount[c] = Map.Column[c].BuildableCount;
/*
** Each production slot in the column
*/
for (int b=0 ; b < Map.Column[c].BuildableCount ; b++) {
CNCSidebarEntryStruct &sidebar_entry = sidebar->Entries[entry_index++];
if ((entry_index + 1) * sizeof(CNCSidebarEntryStruct) + memory_needed > buffer_size) {
return false;
}
memset(&sidebar_entry, 0, sizeof(sidebar_entry));
sidebar_entry.AssetName[0] = 0;
sidebar_entry.Type = UNKNOWN;
sidebar_entry.BuildableID = Map.Column[c].Buildables[b].BuildableID;
sidebar_entry.BuildableType = Map.Column[c].Buildables[b].BuildableType;
sidebar_entry.BuildableViaCapture = Map.Column[c].Buildables[b].BuildableViaCapture;
sidebar_entry.Fake = false;
TechnoTypeClass const * tech = Fetch_Techno_Type(Map.Column[c].Buildables[b].BuildableType, Map.Column[c].Buildables[b].BuildableID);
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.PowerProvided = 0;
Command & Conquer Remastered post-launch patch Improvements to harvester resource finding logic. Don't allow the Advanced Comm Center to be capturable in skirmish or multiplayer. Increased failed pathfinding fudge factor. Buildings accept the Guard command if they can attack. Don't allow force capturing of ally structures. Fixes for laser Orcas in S3cr3t M1ss10n. Properly restore them after save. Reset Orcas after loads. Fixed flag animation rendering in CTF. Potentially fix a crash if aircraft are destroyed outside the map bounds. Fixed legacy Obelisk line rendering. Fix out-of-bounds crash in TD; issue was already fixed in RA. Disable capture flag on Commandos. Drop the flag when entering the limbo state. Fixed end game race condition, winning team ID is always sent before individual player win/lose messages. Fixed Chan spawn on SCB10EA. Don't show enter cursor for enemy units on refineries and repair pads. Changing right-click support for first put building on hold, and then subsequenct right-clicks to decrement that queue count for 1x or 5x; Then, 1x or 5x Left click will resume from hold. Don't debug reveal legacy rendering when a player is defeated. Fixed crash when loading saves of custom campaign maps. Reset harvester archived target when given a direct harvest order. Prevent NOD cargo planes from being force attacked. Fixed unit selection on load. Migrated queued repair pad functionality from RA to TD. Randomly animate infantry in area guard mode. Fixed crash accessing inactive objects. Added some walls in SCG08EB to prevent civilians from killing themselves. TD + RA: Audio: Overiding "Our base is under attack" cooldown timing from legacy from 2 minutes to 30 seconds, so it will be heard 4x as often. Fixed adjacent cell out-of-bounds crash issues. Kill player on disconnect Fixed and improved build time calculations to be consistent between TD and RA. Don't show health bars for cloaked Stealth Tanks in the legacy renderer. Fix selection of individual control groups after mixed selection. More adjustments to SCG08EB; switch C7 to C5, and add civilian AI to avoid Tiberium. Extra safety checks for units that have no weapons and aircraft that can't hunt. Fix loading of multiple infantry onto an APC. Additional safety checks for invalid coordinates. Prevent units from being instantly repaired. Fix map passability. Fail Allied mission 5B if the spy re-boards the starting transport (matches 5A and 5C behavior). Fixed multiplayer formation move causing units to move at light speed. Ignore movement destination checks if a unit is part of a mission-driven team. Fix buffer overrun crash. Ignore mines when determining win conditions. Fixed river passability in Blue Lakes.
2020-06-23 04:43:21 +12:00
sidebar_entry.BuildTime = tech->Time_To_Build(PlayerPtr->Class->House); // sidebar_entry.BuildTime = tech->Time_To_Build() / 60;
strncpy(sidebar_entry.AssetName, tech->IniName, CNC_OBJECT_ASSET_NAME_LENGTH);
} else {
sidebar_entry.Cost = 0;
sidebar_entry.AssetName[0] = 0;
}
SuperClass* super_weapon = nullptr;
bool isbusy = false;
switch (Map.Column[c].Buildables[b].BuildableType) {
case RTTI_INFANTRYTYPE:
sidebar_entry.Type = INFANTRY_TYPE;
isbusy = (PlayerPtr->InfantryFactory != -1);
isbusy |= Infantry.Avail() <= 0;
break;
case RTTI_UNITTYPE:
isbusy = (PlayerPtr->UnitFactory != -1);
isbusy |= Units.Avail() <= 0;
sidebar_entry.Type = UNIT_TYPE;
break;
case RTTI_AIRCRAFTTYPE:
isbusy = (PlayerPtr->AircraftFactory != -1);
isbusy |= Aircraft.Avail() <= 0;
sidebar_entry.Type = AIRCRAFT_TYPE;
break;
case RTTI_BUILDINGTYPE:
{
isbusy = (PlayerPtr->BuildingFactory != -1);
isbusy |= Buildings.Avail() <= 0;
sidebar_entry.Type = BUILDING_TYPE;
const BuildingTypeClass* build_type = static_cast<const BuildingTypeClass*>(tech);
sidebar_entry.PowerProvided = build_type->Power - build_type->Drain;
sidebar_entry.Fake = build_type->IsFake;
}
break;
case RTTI_VESSELTYPE:
sidebar_entry.Type = VESSEL_TYPE;
isbusy = (PlayerPtr->VesselFactory != -1);
isbusy |= Vessels.Avail() <= 0;
break;
default:
sidebar_entry.Type = UNKNOWN;
break;
case RTTI_SPECIAL:
Fill_Sidebar_Entry_From_Special_Weapon(sidebar_entry, super_weapon, (SpecialWeaponType)Map.Column[c].Buildables[b].BuildableID);
break;
}
if (super_weapon != nullptr)
{
sidebar_entry.Progress = (float)super_weapon->Anim_Stage() / (float)SuperClass::ANIMATION_STAGES;
sidebar_entry.Completed = super_weapon->Is_Ready();
sidebar_entry.Constructing = super_weapon->Anim_Stage() != SuperClass::ANIMATION_STAGES;
sidebar_entry.ConstructionOnHold = false;
sidebar_entry.PlacementListLength = 0;
sidebar_entry.PowerProvided = 0;
sidebar_entry.BuildTime = super_weapon->Get_Recharge_Time();
}
else
{
int fnumber = Map.Column[c].Buildables[b].Factory;
FactoryClass * factory = NULL;
if (tech && fnumber != -1) {
factory = Factories.Raw_Ptr(fnumber);
}
sidebar_entry.Completed = false;
sidebar_entry.Constructing = false;
sidebar_entry.ConstructionOnHold = false;
sidebar_entry.Progress = 0.0f;
sidebar_entry.Busy = isbusy;
sidebar_entry.PlacementListLength = 0;
if (factory) {
if (factory->Is_Building()) {
sidebar_entry.Constructing = true;
sidebar_entry.Progress = (float)factory->Completion() / (float)FactoryClass::STEP_COUNT;
sidebar_entry.Completed = factory->Has_Completed();
}
else {
sidebar_entry.Completed = factory->Has_Completed();
if (!sidebar_entry.Completed)
{
sidebar_entry.ConstructionOnHold = true;
sidebar_entry.Progress = (float)factory->Completion() / (float)FactoryClass::STEP_COUNT;
}
if (sidebar_entry.Completed && sidebar_entry.Type == BUILDING_TYPE) {
if (tech) {
BuildingTypeClass *building_type = (BuildingTypeClass*)tech;
short const *occupy_list = building_type->Occupy_List(true);
if (occupy_list) {
while (*occupy_list != REFRESH_EOL && sidebar_entry.PlacementListLength < MAX_OCCUPY_CELLS) {
sidebar_entry.PlacementList[sidebar_entry.PlacementListLength] = *occupy_list;
sidebar_entry.PlacementListLength++;
occupy_list++;
}
}
}
}
}
}
}
}
}
} else {
if (GAME_TO_PLAY == GAME_GLYPHX_MULTIPLAYER) {
SidebarGlyphxClass *context_sidebar = DLLExportClass::Get_Current_Context_Sidebar();
/*
** Get each sidebar column
*/
for (int c = 0 ; c < 2 ; c++) {
sidebar->EntryCount[c] = context_sidebar->Column[c].BuildableCount;
/*
** Each production slot in the column
*/
for (int b=0 ; b < context_sidebar->Column[c].BuildableCount ; b++) {
CNCSidebarEntryStruct &sidebar_entry = sidebar->Entries[entry_index++];
if ((entry_index + 1) * sizeof(CNCSidebarEntryStruct) + memory_needed > buffer_size) {
return false;
}
memset(&sidebar_entry, 0, sizeof(sidebar_entry));
sidebar_entry.AssetName[0] = 0;
sidebar_entry.Type = UNKNOWN;
sidebar_entry.BuildableID = context_sidebar->Column[c].Buildables[b].BuildableID;
sidebar_entry.BuildableType = context_sidebar->Column[c].Buildables[b].BuildableType;
sidebar_entry.BuildableViaCapture = context_sidebar->Column[c].Buildables[b].BuildableViaCapture;
sidebar_entry.Fake = false;
TechnoTypeClass const * tech = Fetch_Techno_Type(context_sidebar->Column[c].Buildables[b].BuildableType, context_sidebar->Column[c].Buildables[b].BuildableID);
sidebar_entry.SuperWeaponType = SW_NONE;
if (tech) {
// MBL 06.22.2020 - 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;
sidebar_entry.Cost = tech->Cost * PlayerPtr->CostBias;
sidebar_entry.PowerProvided = 0;
Command & Conquer Remastered post-launch patch Improvements to harvester resource finding logic. Don't allow the Advanced Comm Center to be capturable in skirmish or multiplayer. Increased failed pathfinding fudge factor. Buildings accept the Guard command if they can attack. Don't allow force capturing of ally structures. Fixes for laser Orcas in S3cr3t M1ss10n. Properly restore them after save. Reset Orcas after loads. Fixed flag animation rendering in CTF. Potentially fix a crash if aircraft are destroyed outside the map bounds. Fixed legacy Obelisk line rendering. Fix out-of-bounds crash in TD; issue was already fixed in RA. Disable capture flag on Commandos. Drop the flag when entering the limbo state. Fixed end game race condition, winning team ID is always sent before individual player win/lose messages. Fixed Chan spawn on SCB10EA. Don't show enter cursor for enemy units on refineries and repair pads. Changing right-click support for first put building on hold, and then subsequenct right-clicks to decrement that queue count for 1x or 5x; Then, 1x or 5x Left click will resume from hold. Don't debug reveal legacy rendering when a player is defeated. Fixed crash when loading saves of custom campaign maps. Reset harvester archived target when given a direct harvest order. Prevent NOD cargo planes from being force attacked. Fixed unit selection on load. Migrated queued repair pad functionality from RA to TD. Randomly animate infantry in area guard mode. Fixed crash accessing inactive objects. Added some walls in SCG08EB to prevent civilians from killing themselves. TD + RA: Audio: Overiding "Our base is under attack" cooldown timing from legacy from 2 minutes to 30 seconds, so it will be heard 4x as often. Fixed adjacent cell out-of-bounds crash issues. Kill player on disconnect Fixed and improved build time calculations to be consistent between TD and RA. Don't show health bars for cloaked Stealth Tanks in the legacy renderer. Fix selection of individual control groups after mixed selection. More adjustments to SCG08EB; switch C7 to C5, and add civilian AI to avoid Tiberium. Extra safety checks for units that have no weapons and aircraft that can't hunt. Fix loading of multiple infantry onto an APC. Additional safety checks for invalid coordinates. Prevent units from being instantly repaired. Fix map passability. Fail Allied mission 5B if the spy re-boards the starting transport (matches 5A and 5C behavior). Fixed multiplayer formation move causing units to move at light speed. Ignore movement destination checks if a unit is part of a mission-driven team. Fix buffer overrun crash. Ignore mines when determining win conditions. Fixed river passability in Blue Lakes.
2020-06-23 04:43:21 +12:00
sidebar_entry.BuildTime = tech->Time_To_Build(PlayerPtr->Class->House); // sidebar_entry.BuildTime = tech->Time_To_Build() / 60;
strncpy(sidebar_entry.AssetName, tech->IniName, CNC_OBJECT_ASSET_NAME_LENGTH);
} else {
sidebar_entry.Cost = 0;
sidebar_entry.AssetName[0] = 0;
}
SuperClass* super_weapon = nullptr;
bool isbusy = false;
switch (context_sidebar->Column[c].Buildables[b].BuildableType) {
case RTTI_INFANTRYTYPE:
sidebar_entry.Type = INFANTRY_TYPE;
isbusy = (PlayerPtr->InfantryFactory != -1);
isbusy |= Infantry.Avail() <= 0;
break;
case RTTI_UNITTYPE:
isbusy = (PlayerPtr->UnitFactory != -1);
isbusy |= Units.Avail() <= 0;
sidebar_entry.Type = UNIT_TYPE;
break;
case RTTI_AIRCRAFTTYPE:
isbusy = (PlayerPtr->AircraftFactory != -1);
isbusy |= Aircraft.Avail() <= 0;
sidebar_entry.Type = AIRCRAFT_TYPE;
break;
case RTTI_BUILDINGTYPE:
{
isbusy = (PlayerPtr->BuildingFactory != -1);
isbusy |= Buildings.Avail() <= 0;
sidebar_entry.Type = BUILDING_TYPE;
const BuildingTypeClass* build_type = static_cast<const BuildingTypeClass*>(tech);
sidebar_entry.PowerProvided = build_type->Power - build_type->Drain;
sidebar_entry.Fake = build_type->IsFake;
break;
}
case RTTI_VESSELTYPE:
isbusy = (PlayerPtr->VesselFactory != -1);
isbusy |= Vessels.Avail() <= 0;
sidebar_entry.Type = VESSEL_TYPE;
break;
default:
sidebar_entry.Type = UNKNOWN;
break;
case RTTI_SPECIAL:
Fill_Sidebar_Entry_From_Special_Weapon(sidebar_entry, super_weapon, (SpecialWeaponType)sidebar_entry.BuildableID);
break;
}
if (super_weapon != nullptr)
{
sidebar_entry.Progress = (float)super_weapon->Anim_Stage() / (float)SuperClass::ANIMATION_STAGES;
sidebar_entry.Completed = super_weapon->Is_Ready();
sidebar_entry.Constructing = super_weapon->Anim_Stage() != SuperClass::ANIMATION_STAGES;
sidebar_entry.ConstructionOnHold = false;
sidebar_entry.PlacementListLength = 0;
sidebar_entry.PowerProvided = 0;
sidebar_entry.BuildTime = super_weapon->Get_Recharge_Time();
}
else
{
int fnumber = context_sidebar->Column[c].Buildables[b].Factory;
FactoryClass * factory = NULL;
if (tech && fnumber != -1) {
factory = Factories.Raw_Ptr(fnumber);
}
sidebar_entry.Completed = false;
sidebar_entry.Constructing = false;
sidebar_entry.ConstructionOnHold = false;
sidebar_entry.Progress = 0.0f;
sidebar_entry.Busy = isbusy;
sidebar_entry.PlacementListLength = 0;
if (factory) {
if (factory->Is_Building()) {
sidebar_entry.Constructing = true;
sidebar_entry.Progress = (float)factory->Completion() / (float)FactoryClass::STEP_COUNT;
sidebar_entry.Completed = factory->Has_Completed();
}
else {
sidebar_entry.Completed = factory->Has_Completed();
if (!sidebar_entry.Completed)
{
sidebar_entry.ConstructionOnHold = true;
sidebar_entry.Progress = (float)factory->Completion() / (float)FactoryClass::STEP_COUNT;
}
if (sidebar_entry.Completed && sidebar_entry.Type == BUILDING_TYPE) {
if (tech) {
BuildingTypeClass *building_type = (BuildingTypeClass*)tech;
short const *occupy_list = building_type->Occupy_List(true);
if (occupy_list) {
while (*occupy_list != REFRESH_EOL && sidebar_entry.PlacementListLength < MAX_OCCUPY_CELLS) {
sidebar_entry.PlacementList[sidebar_entry.PlacementListLength] = *occupy_list;
sidebar_entry.PlacementListLength++;
occupy_list++;
}
}
}
}
}
}
}
}
}
}
}
return true;
}
void DLLExportClass::Convert_Action_Type(ActionType type, ObjectClass* object, TARGET target, DllActionTypeEnum& dll_type)
{
switch (type)
{
case ACTION_NONE:
default:
dll_type = DAT_NONE;
break;
case ACTION_MOVE:
dll_type = DAT_MOVE;
break;
case ACTION_NOMOVE:
dll_type = DAT_NOMOVE;
break;
case ACTION_ENTER:
dll_type = DAT_ENTER;
break;
case ACTION_SELF:
dll_type = DAT_SELF;
break;
case ACTION_ATTACK:
if (Target_Legal(target) && (object != NULL) && object->Is_Techno() && ((TechnoClass*)object)->In_Range(target, 0)) {
dll_type = DAT_ATTACK;
}
else {
dll_type = DAT_ATTACK_OUT_OF_RANGE;
}
break;
case ACTION_GUARD_AREA:
dll_type = DAT_GUARD;
break;
case ACTION_HARVEST:
dll_type = DAT_ATTACK;
break;
case ACTION_SELECT:
case ACTION_TOGGLE_SELECT:
dll_type = DAT_SELECT;
break;
case ACTION_CAPTURE:
dll_type = DAT_CAPTURE;
break;
case ACTION_DAMAGE:
dll_type = DAT_DAMAGE;
break;
case ACTION_SABOTAGE:
dll_type = DAT_SABOTAGE;
break;
case ACTION_HEAL:
dll_type = DAT_HEAL;
break;
case ACTION_TOGGLE_PRIMARY:
dll_type = DAT_TOGGLE_PRIMARY;
break;
case ACTION_NO_DEPLOY:
dll_type = DAT_CANT_DEPLOY;
break;
case ACTION_GREPAIR:
dll_type = DAT_REPAIR;
break;
case ACTION_NO_GREPAIR:
dll_type = DAT_CANT_REPAIR;
break;
}
}
void DLLExportClass::Convert_Special_Weapon_Type(SpecialWeaponType weapon_type, DllSuperweaponTypeEnum& dll_weapon_type, char* weapon_name)
{
switch (weapon_type)
{
case SPC_SONAR_PULSE:
dll_weapon_type = SW_SONAR_PULSE;
if (weapon_name != NULL)
{
strncpy(weapon_name, "SW_SonarPulse", 16);
}
break;
case SPC_NUCLEAR_BOMB:
dll_weapon_type = SW_NUKE;
if (weapon_name != NULL)
{
strncpy(weapon_name, "SW_Nuke", 16);
}
break;
case SPC_CHRONOSPHERE:
dll_weapon_type = SW_CHRONOSPHERE;
if (weapon_name != NULL)
{
strncpy(weapon_name, "SW_Chrono", 16);
}
break;
case SPC_PARA_BOMB:
dll_weapon_type = SW_PARA_BOMB;
if (weapon_name != NULL)
{
strncpy(weapon_name, "SW_ParaBomb", 16);
}
break;
case SPC_PARA_INFANTRY:
dll_weapon_type = SW_PARA_INFANTRY;
if (weapon_name != NULL)
{
strncpy(weapon_name, "SW_ParaInfantry", 16);
}
break;
case SPC_SPY_MISSION:
dll_weapon_type = SW_SPY_MISSION;
if (weapon_name != NULL)
{
strncpy(weapon_name, "SW_SpyMission", 16);
}
break;
case SPC_IRON_CURTAIN:
dll_weapon_type = SW_IRON_CURTAIN;
if (weapon_name != NULL)
{
strncpy(weapon_name, "SW_IronCurtain", 16);
}
break;
case SPC_GPS:
dll_weapon_type = SW_GPS;
if (weapon_name != NULL)
{
strncpy(weapon_name, "SW_GPS", 16);
}
break;
case SPC_CHRONO2:
dll_weapon_type = SW_CHRONOSPHERE_DESTINATION;
if (weapon_name != NULL)
{
strncpy(weapon_name, "SW_Chrono2", 16);
}
break;
default:
dll_weapon_type = SW_UNKNOWN;
if (weapon_name != NULL)
{
weapon_name[0] = '\0';
}
break;
}
}
void DLLExportClass::Fill_Sidebar_Entry_From_Special_Weapon(CNCSidebarEntryStruct& sidebar_entry_out, SuperClass*& super_weapon_out, SpecialWeaponType weapon_type)
{
sidebar_entry_out.Type = SPECIAL;
switch (weapon_type)
{
case SPC_SONAR_PULSE:
case SPC_NUCLEAR_BOMB:
case SPC_CHRONOSPHERE:
case SPC_PARA_BOMB:
case SPC_PARA_INFANTRY:
case SPC_SPY_MISSION:
case SPC_IRON_CURTAIN:
case SPC_GPS:
case SPC_CHRONO2:
Convert_Special_Weapon_Type(weapon_type, sidebar_entry_out.SuperWeaponType, sidebar_entry_out.AssetName);
break;
default:
sidebar_entry_out.SuperWeaponType = SW_UNKNOWN;
sidebar_entry_out.Type = UNKNOWN;
super_weapon_out = nullptr;
return;
}
super_weapon_out = &(PlayerPtr->SuperWeapon[weapon_type]);
}
static const int _map_width_shift_bits = 7;
void DLLExportClass::Calculate_Placement_Distances(BuildingTypeClass* placement_type, unsigned char* placement_distance)
{
int map_cell_x = Map.MapCellX;
int map_cell_y = Map.MapCellY;
int map_cell_width = Map.MapCellWidth;
int map_cell_height = Map.MapCellHeight;
if (map_cell_x > 0) {
map_cell_x--;
map_cell_width++;
}
if (map_cell_width < MAP_MAX_CELL_WIDTH) {
map_cell_width++;
}
if (map_cell_y > 0) {
map_cell_y--;
map_cell_height++;
}
if (map_cell_height < MAP_MAX_CELL_WIDTH) {
map_cell_height++;
}
static const FacingType _scan_facings[] = {
FACING_E,
FACING_S,
FACING_W,
FACING_N
};
memset(placement_distance, 255U, MAP_CELL_TOTAL);
for (int y = 0; y < map_cell_height; y++) {
for (int x = 0; x < map_cell_width; x++) {
CELL cell = (CELL)map_cell_x + x + ((map_cell_y + y) << _map_width_shift_bits);
BuildingClass* base = (BuildingClass*)Map[cell].Cell_Find_Object(RTTI_BUILDING);
if ((base && base->House->Class->House == PlayerPtr->Class->House && base->Class->IsBase) ||
((placement_type->IsWall || ((Map[cell].Smudge != SMUDGE_NONE) && SmudgeTypeClass::As_Reference(Map[cell].Smudge).IsBib)) &&
Map[cell].Owner == PlayerPtr->Class->House)) {
placement_distance[cell] = 0U;
CELL startcell = cell;
for (unsigned char distance = 1U; distance <= (placement_type->Adjacent + 1U); distance++) {
startcell = Adjacent_Cell(startcell, FACING_NW);
CELL scancell = startcell;
for (int i = 0; i < ARRAY_SIZE(_scan_facings); i++) {
CELL nextcell = scancell;
for (unsigned char scan = 0U; scan <= (distance * 2U); scan++) {
scancell = nextcell;
if (Map.In_Radar(scancell)) {
placement_distance[scancell] = min(placement_distance[scancell], distance);
}
nextcell = Adjacent_Cell(scancell, _scan_facings[i]);
}
}
}
}
}
}
}
void Recalculate_Placement_Distances()
{
DLLExportClass::Recalculate_Placement_Distances();
}
void DLLExportClass::Recalculate_Placement_Distances()
{
if (PlacementType[CurrentLocalPlayerIndex] != NULL) {
Calculate_Placement_Distances(PlacementType[CurrentLocalPlayerIndex], PlacementDistance[CurrentLocalPlayerIndex]);
}
}
/**************************************************************************************************
* DLLExportClass::Get_Placement_State -- Get a snapshot of legal validity of placing a structure on all map cells
*
* In:
*
* Out:
*
*
*
* History: 2/4/2019 3:11PM - ST
**************************************************************************************************/
bool DLLExportClass::Get_Placement_State(uint64 player_id, unsigned char *buffer_in, unsigned int buffer_size)
{
/*
** Get the player for this...
*/
if (!DLLExportClass::Set_Player_Context(player_id)) {
return false;
}
if (PlacementType[CurrentLocalPlayerIndex] == NULL) {
return false;
}
CNCPlacementInfoStruct *placement_info = (CNCPlacementInfoStruct*) buffer_in;
unsigned int memory_needed = sizeof(*placement_info); // Base amount needed. Will need more depending on how many entries there are
int map_cell_x = Map.MapCellX;
int map_cell_y = Map.MapCellY;
int map_cell_width = Map.MapCellWidth;
int map_cell_height = Map.MapCellHeight;
if (map_cell_x > 0) {
map_cell_x--;
map_cell_width++;
}
if (map_cell_width < MAP_MAX_CELL_WIDTH) {
map_cell_width++;
}
if (map_cell_y > 0) {
map_cell_y--;
map_cell_height++;
}
if (map_cell_height < MAP_MAX_CELL_WIDTH) {
map_cell_height++;
}
memory_needed += map_cell_width * map_cell_height * sizeof(CNCPlacementCellInfoStruct);
if (memory_needed + 128 >= buffer_size) {
return false;
}
placement_info->Count = map_cell_width * map_cell_height;
int index = 0;
for (int y=0 ; y < map_cell_height ; y++) {
for (int x=0 ; x < map_cell_width ; x++) {
CELL cell = (CELL) map_cell_x + x + ((map_cell_y + y) << _map_width_shift_bits);
bool pass = Passes_Proximity_Check(cell, PlacementType[CurrentLocalPlayerIndex], PlacementDistance[CurrentLocalPlayerIndex]);
CellClass * cellptr = &Map[cell];
bool clear = cellptr->Is_Clear_To_Build(PlacementType[CurrentLocalPlayerIndex]->Speed);
CNCPlacementCellInfoStruct &placement_cell_info = placement_info->CellInfo[index++];
placement_cell_info.PassesProximityCheck = pass;
placement_cell_info.GenerallyClear = clear;
}
}
Map.ZoneOffset = 0;
return true;
}
bool DLLExportClass::Passes_Proximity_Check(CELL cell_in, BuildingTypeClass *placement_type, unsigned char* placement_distance)
{
/*
** Scan through all cells that the building foundation would cover. If any adjacent
** cells to these are of friendly persuasion, then consider the proximity check to
** have been a success.
*/
short const *occupy_list = placement_type->Occupy_List(true);
while (*occupy_list != REFRESH_EOL) {
CELL center_cell = cell_in + *occupy_list++;
if (!Map.In_Radar(center_cell)) {
return false;
}
if (placement_distance[center_cell] <= (placement_type->Adjacent + 1)) {
return true;
}
}
return false;
}
/**************************************************************************************************
* DLLExportClass::Start_Construction -- Start sidebar construction
*
* In:
*
* Out:
*
*
*
* History: 1/29/2019 11:37AM - ST
**************************************************************************************************/
bool DLLExportClass::Start_Construction(uint64 player_id, int buildable_type, int buildable_id)
{
/*
** Get the player for this...
*/
if (!DLLExportClass::Set_Player_Context(player_id)) {
return false;
}
if (GAME_TO_PLAY == GAME_NORMAL) {
return Construction_Action(SIDEBAR_REQUEST_START_CONSTRUCTION, player_id, buildable_type, buildable_id);
}
return MP_Construction_Action(SIDEBAR_REQUEST_START_CONSTRUCTION, player_id, buildable_type, buildable_id);
}
/**************************************************************************************************
* DLLExportClass::Hold_Construction -- Pause sidebar construction
*
* In:
*
* Out:
*
*
*
* History: 6/12/2019 JAS
**************************************************************************************************/
bool DLLExportClass::Hold_Construction(uint64 player_id, int buildable_type, int buildable_id)
{
if (!DLLExportClass::Set_Player_Context(player_id))
{
return false;
}
if (GAME_TO_PLAY == GAME_NORMAL) {
return Construction_Action(SIDEBAR_REQUEST_HOLD_CONSTRUCTION, player_id, buildable_type, buildable_id);
}
return MP_Construction_Action(SIDEBAR_REQUEST_HOLD_CONSTRUCTION, player_id, buildable_type, buildable_id);
}
/**************************************************************************************************
* DLLExportClass::Cancel_Construction -- Stop sidebar construction
*
* In:
*
* Out:
*
*
*
* History: 6/12/2019 JAS
**************************************************************************************************/
bool DLLExportClass::Cancel_Construction(uint64 player_id, int buildable_type, int buildable_id)
{
if (!DLLExportClass::Set_Player_Context(player_id))
{
return false;
}
return Cancel_Placement(player_id, buildable_type, buildable_id) &&
((GAME_TO_PLAY == GAME_NORMAL) ?
Construction_Action(SIDEBAR_REQUEST_CANCEL_CONSTRUCTION, player_id, buildable_type, buildable_id) :
MP_Construction_Action(SIDEBAR_REQUEST_CANCEL_CONSTRUCTION, player_id, buildable_type, buildable_id));
}
/**************************************************************************************************
* DLLExportClass::Construction_Action -- Reproduce actions on the sidebar
*
* In:
*
* Out:
*
*
*
* History: 1/29/2019 11:37AM - ST
**************************************************************************************************/
bool DLLExportClass::Construction_Action(SidebarRequestEnum construction_action, uint64 player_id, int buildable_type, int buildable_id)
{
/*
**
** Based on SidebarClass::StripClass::SelectClass::Action
**
** Most of this code is validating that the game is in the correct state to be able to act on a sidebar icon
**
*/
for (int c = 0 ; c < 2 ; c++) {
/*
** Each production slot in the column
*/
for (int b=0 ; b < Map.Column[c].BuildableCount ; b++) {
if (Map.Column[c].Buildables[b].BuildableID == buildable_id) {
if (Map.Column[c].Buildables[b].BuildableType == buildable_type) {
int fnumber = Map.Column[c].Buildables[b].Factory;
int spc = 0;
ObjectTypeClass const * choice = NULL;
if (buildable_type != RTTI_SPECIAL) {
choice = Fetch_Techno_Type((RTTIType)buildable_type, buildable_id);
} else {
spc = buildable_id;
}
FactoryClass * factory = PlayerPtr->Fetch_Factory((RTTIType)buildable_type);
if (fnumber != -1) {
factory = Factories.Raw_Ptr(fnumber);
}
if (spc == 0 && choice) {
if (fnumber == -1 && factory != NULL) {
return(false);
}
if (factory) {
switch (construction_action)
{
case SIDEBAR_REQUEST_CANCEL_CONSTRUCTION:
On_Speech(PlayerPtr, VOX_CANCELED); // Speak(VOX_CANCELED);
OutList.Add(EventClass(EventClass::ABANDON, (RTTIType)buildable_type, buildable_id));
break;
case SIDEBAR_REQUEST_HOLD_CONSTRUCTION:
if (factory->Is_Building())
{
On_Speech(PlayerPtr, VOX_SUSPENDED); // Speak(VOX_SUSPENDED);
OutList.Add(EventClass(EventClass::SUSPEND, (RTTIType)buildable_type, buildable_id));
}
break;
default:
/*
** If this object is currently being built, then give a scold sound and text and then
** bail.
*/
if (factory->Is_Building()) {
On_Speech(PlayerPtr, VOX_NO_FACTORY); //Speak(VOX_NO_FACTORY); // "Cannot Comply"
return false;
}
else {
/*
** If production has completed, then attempt to have the object exit
** the factory or go into placement mode.
*/
if (factory->Has_Completed()) {
TechnoClass * pending = factory->Get_Object();
if (!pending && factory->Get_Special_Item()) {
// TO_DO
//Map.IsTargettingMode = true;
}
else {
BuildingClass * builder = pending->Who_Can_Build_Me(false, false);
if (!builder) {
OutList.Add(EventClass(EventClass::ABANDON, (RTTIType)buildable_type, buildable_id));
On_Speech(PlayerPtr, VOX_NO_FACTORY); //Speak(VOX_NO_FACTORY); // "Cannot Comply"
}
else {
/*
** If the completed object is a building, then change the
** game state into building placement mode. This fact is
** not transmitted to any linked computers until the moment
** the building is actually placed down.
*/
if (pending->What_Am_I() == RTTI_BUILDING) {
if (construction_action == SIDEBAR_REQUEST_START_PLACEMENT) {
PlayerPtr->Manual_Place(builder, (BuildingClass *)pending);
}
}
else {
/*
** For objects that can leave the factory under their own
** power, queue this event and process through normal house
** production channels.
*/
//OutList.Add(EventClass(EventClass::PLACE, otype, -1));
}
}
}
}
else {
/*
** The factory must have been in a suspended state. Resume construction
** normally.
*/
if (construction_action == SIDEBAR_REQUEST_START_CONSTRUCTION) {
if ((RTTIType)buildable_type == RTTI_INFANTRYTYPE)
{
On_Speech(PlayerPtr, VOX_TRAINING); // Speak(VOX_TRAINING);
}
else
{
On_Speech(PlayerPtr, VOX_BUILDING); // Speak(VOX_BUILDING);
}
OutList.Add(EventClass(EventClass::PRODUCE, (RTTIType)buildable_type, buildable_id));
return true;
}
}
}
}
} else {
switch (construction_action)
{
case SIDEBAR_REQUEST_CANCEL_CONSTRUCTION:
case SIDEBAR_REQUEST_HOLD_CONSTRUCTION:
break;
default:
/*
** If this side strip is already busy with production, then ignore the
** input and announce this fact.
*/
if ((RTTIType)buildable_type == RTTI_INFANTRYTYPE)
{
On_Speech(PlayerPtr, VOX_TRAINING); // Speak(VOX_TRAINING);
}
else
{
On_Speech(PlayerPtr, VOX_BUILDING); // Speak(VOX_BUILDING);
}
OutList.Add(EventClass(EventClass::PRODUCE, (RTTIType)buildable_type, buildable_id));
/*
** Execute immediately so we get the sidebar feedback
*/
Queue_AI();
return true;
}
}
}
}
}
}
}
return false;
}
/**************************************************************************************************
* DLLExportClass::MP_Construction_Action -- Reproduce actions on the sidebar
*
* In:
*
* Out:
*
*
*
* History: 3/26/2019 1:02PM - ST
**************************************************************************************************/
bool DLLExportClass::MP_Construction_Action(SidebarRequestEnum construction_action, uint64 player_id, int buildable_type, int buildable_id)
{
/*
**
** Based on SidebarClass::StripClass::SelectClass::Action
**
** Most of this code is validating that the game is in the correct state to be able to act on a sidebar icon
**
*/
SidebarGlyphxClass *context_sidebar = DLLExportClass::Get_Current_Context_Sidebar();
for (int c = 0 ; c < 2 ; c++) {
/*
** Each production slot in the column
*/
for (int b=0 ; b < context_sidebar->Column[c].BuildableCount ; b++) {
if (context_sidebar->Column[c].Buildables[b].BuildableID == buildable_id) {
if (context_sidebar->Column[c].Buildables[b].BuildableType == buildable_type) {
int genfactory = -1;
switch (buildable_type) {
case RTTI_INFANTRYTYPE:
genfactory = PlayerPtr->InfantryFactory;
break;
case RTTI_UNITTYPE:
genfactory = PlayerPtr->UnitFactory;
break;
case RTTI_AIRCRAFTTYPE:
genfactory = PlayerPtr->AircraftFactory;
break;
case RTTI_BUILDINGTYPE:
genfactory = PlayerPtr->BuildingFactory;
break;
default:
genfactory = -1;
break;
}
int fnumber = context_sidebar->Column[c].Buildables[b].Factory;
int spc = 0;
ObjectTypeClass const * choice = NULL;
if (buildable_type != RTTI_SPECIAL) {
choice = Fetch_Techno_Type((RTTIType)buildable_type, buildable_id);
} else {
spc = buildable_id;
}
FactoryClass * factory = NULL;
if (fnumber != -1) {
factory = Factories.Raw_Ptr(fnumber);
}
if (spc == 0 && choice) {
/*
** If there is already a factory attached to this strip but the player didn't click
** on the icon that has the attached factory, then say that the factory is busy and
** ignore the click.
*/
if (fnumber == -1 && genfactory != -1) {
On_Speech(PlayerPtr, VOX_NO_FACTORY); //Speak(VOX_NO_FACTORY); // "Cannot Comply"
return(false);
}
if (factory) {
switch (construction_action)
{
case SIDEBAR_REQUEST_CANCEL_CONSTRUCTION:
On_Speech(PlayerPtr, VOX_CANCELED); // Speak(VOX_CANCELED);
OutList.Add(EventClass(EventClass::ABANDON, (RTTIType)buildable_type, buildable_id));
break;
case SIDEBAR_REQUEST_HOLD_CONSTRUCTION:
if (factory->Is_Building())
{
On_Speech(PlayerPtr, VOX_SUSPENDED); // Speak(VOX_SUSPENDED);
OutList.Add(EventClass(EventClass::SUSPEND, (RTTIType)buildable_type, buildable_id));
}
break;
default:
/*
** If this object is currently being built, then give a scold sound and text and then
** bail.
*/
if (factory->Is_Building()) {
On_Speech(PlayerPtr, VOX_NO_FACTORY); //Speak(VOX_NO_FACTORY); // "Cannot Comply"
return false;
}
else {
/*
** If production has completed, then attempt to have the object exit
** the factory or go into placement mode.
*/
if (factory->Has_Completed()) {
TechnoClass * pending = factory->Get_Object();
if (!pending && factory->Get_Special_Item()) {
// TO_DO
//Map.IsTargettingMode = true;
}
else {
BuildingClass * builder = pending->Who_Can_Build_Me(false, false);
if (!builder) {
On_Speech(PlayerPtr, VOX_NO_FACTORY); //Speak(VOX_NO_FACTORY); // "Cannot Comply"
OutList.Add(EventClass(EventClass::ABANDON, (RTTIType)buildable_type, buildable_id));
//Speak(VOX_NO_FACTORY);
}
else {
/*
** If the completed object is a building, then change the
** game state into building placement mode. This fact is
** not transmitted to any linked computers until the moment
** the building is actually placed down.
*/
if (pending->What_Am_I() == RTTI_BUILDING) {
if (construction_action == SIDEBAR_REQUEST_START_PLACEMENT) {
if (DLLExportClass::Legacy_Render_Enabled()) {
PlayerPtr->Manual_Place(builder, (BuildingClass *)pending);
} else {
Unselect_All();
}
}
}
else {
/*
** For objects that can leave the factory under their own
** power, queue this event and process through normal house
** production channels.
*/
//OutList.Add(EventClass(EventClass::PLACE, otype, -1));
}
}
}
}
else {
/*
** The factory must have been in a suspended state. Resume construction
** normally.
*/
if (construction_action == SIDEBAR_REQUEST_START_CONSTRUCTION) {
if ((RTTIType)buildable_type == RTTI_INFANTRYTYPE)
{
On_Speech(PlayerPtr, VOX_TRAINING); // Speak(VOX_TRAINING);
}
else
{
On_Speech(PlayerPtr, VOX_BUILDING); // Speak(VOX_BUILDING);
}
OutList.Add(EventClass(EventClass::PRODUCE, (RTTIType)buildable_type, buildable_id));
return true;
}
}
}
break;
}
} else {
switch (construction_action)
{
case SIDEBAR_REQUEST_CANCEL_CONSTRUCTION:
case SIDEBAR_REQUEST_HOLD_CONSTRUCTION:
break;
default:
/*
**
*/
if ((RTTIType)buildable_type == RTTI_INFANTRYTYPE)
{
On_Speech(PlayerPtr, VOX_TRAINING); // Speak(VOX_TRAINING);
}
else
{
On_Speech(PlayerPtr, VOX_BUILDING); // Speak(VOX_BUILDING);
}
OutList.Add(EventClass(EventClass::PRODUCE, (RTTIType)buildable_type, buildable_id));
/*
** Execute immediately so we get the sidebar feedback
*/
DLLExportClass::Glyphx_Queue_AI();
return true;
}
}
}
}
}
}
}
return false;
}
/**************************************************************************************************
* DLLExportClass::Start_Placement -- Start placing a completed structure
*
* In:
*
* Out:
*
*
*
* History: 1/29/2019 11:37AM - ST
**************************************************************************************************/
bool DLLExportClass::Start_Placement(uint64 player_id, int buildable_type, int buildable_id)
{
/*
** Get the player for this...
*/
if (!DLLExportClass::Set_Player_Context(player_id)) {
return false;
}
BuildingClass *building = Get_Pending_Placement_Object(player_id, buildable_type, buildable_id);
if (building) {
TechnoTypeClass const * tech = Fetch_Techno_Type((RTTIType)buildable_type, buildable_id);
if (tech) {
BuildingTypeClass *building_type = (BuildingTypeClass*) tech;
//short const *occupy_list = building_type->Get_Occupy_List(true);
PlacementType[CurrentLocalPlayerIndex] = building_type;
Recalculate_Placement_Distances();
if (GAME_TO_PLAY == GAME_NORMAL) {
return Construction_Action(SIDEBAR_REQUEST_START_PLACEMENT, player_id, buildable_type, buildable_id);
}
return MP_Construction_Action(SIDEBAR_REQUEST_START_PLACEMENT, player_id, buildable_type, buildable_id);
}
}
return true;
}
/**************************************************************************************************
* DLLExportClass::Cancel_Placement -- Cancel placing a completed structure
*
* In:
*
* Out:
*
*
*
* History: 2/7/2019 10:52AM - ST
**************************************************************************************************/
bool DLLExportClass::Cancel_Placement(uint64 player_id, int buildable_type, int buildable_id)
{
/*
** Get the player for this...
*/
if (!DLLExportClass::Set_Player_Context(player_id)) {
return false;
}
PlacementType[CurrentLocalPlayerIndex] = NULL;
Map.PendingObjectPtr = 0;
Map.PendingObject = 0;
Map.PendingHouse = HOUSE_NONE;
Map.IsTargettingMode = SPC_NONE;
Map.Set_Cursor_Shape(0);
return true;
}
/**************************************************************************************************
* DLLExportClass::Place -- Place a completed structure down
*
* In:
*
* Out:
*
*
*
* History: 2/6/2019 11:51AM - ST
**************************************************************************************************/
bool DLLExportClass::Place(uint64 player_id, int buildable_type, int buildable_id, short cell_x, short cell_y)
{
/*
** Get the player for this...
*/
if (!DLLExportClass::Set_Player_Context(player_id)) {
return false;
}
static const int _map_width_shift_bits = 7;
BuildingClass *building = Get_Pending_Placement_Object(player_id, buildable_type, buildable_id);
if (building) {
TechnoTypeClass const * tech = Fetch_Techno_Type((RTTIType)buildable_type, buildable_id);
if (tech) {
BuildingTypeClass *building_type = (BuildingTypeClass*) tech;
//short const *occupy_list = building_type->Get_Occupy_List(true);
PlacementType[CurrentLocalPlayerIndex] = building_type;
/*
** The cell coordinates passed in will be relative to the playable area that the client knows about
*/
int map_cell_x = Map.MapCellX;
int map_cell_y = Map.MapCellY;
int map_cell_width = Map.MapCellWidth;
int map_cell_height = Map.MapCellHeight;
if (map_cell_x > 0) {
map_cell_x--;
map_cell_width++;
}
if (map_cell_y > 0) {
map_cell_y--;
map_cell_height++;
}
CELL cell = (CELL) (map_cell_x + cell_x) + ( (map_cell_y + cell_y) << _map_width_shift_bits);
/*
** Call the place directly instead of queueing it, so we can evaluate the return code.
*/
if (PlayerPtr->Place_Object(building->What_Am_I(), cell + Map.ZoneOffset)) {
PlacementType[CurrentLocalPlayerIndex] = NULL;
}
}
}
return true;
}
BuildingClass *DLLExportClass::Get_Pending_Placement_Object(uint64 player_id, int buildable_type, int buildable_id)
{
/*
**
** Based on SidebarClass::StripClass::SelectClass::Action
**
**
*/
if (GAME_TO_PLAY == GAME_NORMAL) {
for (int c = 0 ; c < 2 ; c++) {
/*
** Each production slot in the column
*/
for (int b=0 ; b < Map.Column[c].BuildableCount ; b++) {
if (Map.Column[c].Buildables[b].BuildableID == buildable_id) {
if (Map.Column[c].Buildables[b].BuildableType == buildable_type) {
int genfactory = -1;
switch (buildable_type) {
case RTTI_INFANTRYTYPE:
genfactory = PlayerPtr->InfantryFactory;
break;
case RTTI_UNITTYPE:
genfactory = PlayerPtr->UnitFactory;
break;
case RTTI_AIRCRAFTTYPE:
genfactory = PlayerPtr->AircraftFactory;
break;
case RTTI_BUILDINGTYPE:
genfactory = PlayerPtr->BuildingFactory;
break;
default:
genfactory = -1;
break;
}
int fnumber = Map.Column[c].Buildables[b].Factory;
int spc = 0;
ObjectTypeClass const * choice = NULL;
if (buildable_type != RTTI_SPECIAL) {
choice = Fetch_Techno_Type((RTTIType)buildable_type, buildable_id);
} else {
spc = buildable_id;
}
FactoryClass * factory = NULL;
if (fnumber != -1) {
factory = Factories.Raw_Ptr(fnumber);
}
if (spc == 0 && choice) {
if (fnumber == -1 && genfactory != -1) {
return(NULL);
}
if (factory) {
/*
** If production has completed, then attempt to have the object exit
** the factory or go into placement mode.
*/
if (factory->Has_Completed()) {
TechnoClass * pending = factory->Get_Object();
if (!pending && factory->Get_Special_Item()) {
//Map.IsTargettingMode = true;
} else {
BuildingClass * builder = pending->Who_Can_Build_Me(false, false);
if (!builder) {
OutList.Add(EventClass(EventClass::ABANDON, buildable_type, buildable_id));
On_Speech(PlayerPtr, VOX_NO_FACTORY); // Speak(VOX_NO_FACTORY);
} else {
/*
** If the completed object is a building, then change the
** game state into building placement mode. This fact is
** not transmitted to any linked computers until the moment
** the building is actually placed down.
*/
if (pending->What_Am_I() == RTTI_BUILDING) {
return (BuildingClass*)pending;
//PlayerPtr->Manual_Place(builder, (BuildingClass *)pending);
}
}
}
}
}
}
}
}
}
}
} else {
if (GAME_TO_PLAY == GAME_GLYPHX_MULTIPLAYER) {
SidebarGlyphxClass *context_sidebar = DLLExportClass::Get_Current_Context_Sidebar();
for (int c = 0 ; c < 2 ; c++) {
/*
** Each production slot in the column
*/
for (int b=0 ; b < context_sidebar->Column[c].BuildableCount ; b++) {
if (context_sidebar->Column[c].Buildables[b].BuildableID == buildable_id) {
if (context_sidebar->Column[c].Buildables[b].BuildableType == buildable_type) {
int genfactory = -1;
switch (buildable_type) {
case RTTI_INFANTRYTYPE:
genfactory = PlayerPtr->InfantryFactory;
break;
case RTTI_UNITTYPE:
genfactory = PlayerPtr->UnitFactory;
break;
case RTTI_AIRCRAFTTYPE:
genfactory = PlayerPtr->AircraftFactory;
break;
case RTTI_BUILDINGTYPE:
genfactory = PlayerPtr->BuildingFactory;
break;
default:
genfactory = -1;
break;
}
int fnumber = context_sidebar->Column[c].Buildables[b].Factory;
int spc = 0;
ObjectTypeClass const * choice = NULL;
if (buildable_type != RTTI_SPECIAL) {
choice = Fetch_Techno_Type((RTTIType)buildable_type, buildable_id);
} else {
spc = buildable_id;
}
FactoryClass * factory = NULL;
if (fnumber != -1) {
factory = Factories.Raw_Ptr(fnumber);
}
if (spc == 0 && choice) {
if (fnumber == -1 && genfactory != -1) {
return(NULL);
}
if (factory) {
/*
** If production has completed, then attempt to have the object exit
** the factory or go into placement mode.
*/
if (factory->Has_Completed()) {
TechnoClass * pending = factory->Get_Object();
if (!pending && factory->Get_Special_Item()) {
//Map.IsTargettingMode = true;
} else {
BuildingClass * builder = pending->Who_Can_Build_Me(false, false);
if (!builder) {
OutList.Add(EventClass(EventClass::ABANDON, buildable_type, buildable_id));
On_Speech(PlayerPtr, VOX_NO_FACTORY); // Speak(VOX_NO_FACTORY);
} else {
/*
** If the completed object is a building, then change the
** game state into building placement mode. This fact is
** not transmitted to any linked computers until the moment
** the building is actually placed down.
*/
if (pending->What_Am_I() == RTTI_BUILDING) {
return (BuildingClass*)pending;
//PlayerPtr->Manual_Place(builder, (BuildingClass *)pending);
}
}
}
}
}
}
}
}
}
}
}
}
return NULL;
}
/**************************************************************************************************
* DLLExportClass::Place_Super_Weapon
*
* History:
**************************************************************************************************/
bool DLLExportClass::Place_Super_Weapon(uint64 player_id, int buildable_type, int buildable_id, int x, int y)
{
if (buildable_type != RTTI_SPECIAL)
{
return false;
}
COORDINATE coord = Map.Pixel_To_Coord(x, y);
CELL cell = Coord_Cell(coord);
SpecialWeaponType weapon_type = (SpecialWeaponType)buildable_id;
OutList.Add(EventClass(EventClass::SPECIAL_PLACE, weapon_type, cell));
return true;
}
/**************************************************************************************************
* DLLExportClass::Create_Control_Group
*
* History:
**************************************************************************************************/
bool DLLExportClass::Create_Control_Group(unsigned char control_group_index)
{
Handle_Team(control_group_index, 2);
return true;
}
/**************************************************************************************************
* DLLExportClass::Add_To_Control_Group
*
* History:
**************************************************************************************************/
bool DLLExportClass::Add_To_Control_Group(unsigned char control_group_index)
{
Handle_Team(control_group_index, 1);
return true;
}
/**************************************************************************************************
* DLLExportClass::Toggle_Control_Group_Selection
*
* History:
**************************************************************************************************/
bool DLLExportClass::Toggle_Control_Group_Selection(unsigned char control_group_index)
{
Handle_Team(control_group_index, 0);
return true;
}
/**************************************************************************************************
* DLLExportClass::Get_Shroud_State -- Get a snapshot of the shroud for the given player
*
* In:
*
* Out:
*
*
*
* History: 4/12/2019 3:44PM - ST
**************************************************************************************************/
bool DLLExportClass::Get_Shroud_State(uint64 player_id, unsigned char *buffer_in, unsigned int buffer_size)
{
if (!DLLExportClass::Set_Player_Context(player_id)) {
return false;
}
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
int entry_index = 0;
/*
**
** Based loosely on DisplayClass::Redraw_Icons
**
**
*/
int map_cell_x = Map.MapCellX;
int map_cell_y = Map.MapCellY;
int map_cell_width = Map.MapCellWidth;
int map_cell_height = Map.MapCellHeight;
if (map_cell_x > 0) {
map_cell_x--;
map_cell_width++;
}
if (map_cell_width < MAP_MAX_CELL_WIDTH) {
map_cell_width++;
}
if (map_cell_y > 0) {
map_cell_y--;
map_cell_height++;
}
if (map_cell_height < MAP_MAX_CELL_HEIGHT) {
map_cell_height++;
}
for (int y = 0; y < map_cell_height; y++) {
for (int x = 0; x < map_cell_width; x++) {
CELL cell = XY_Cell(map_cell_x + x, map_cell_y + y);
COORDINATE coord = Cell_Coord(cell) & 0xFF00FF00L;
memory_needed += sizeof(CNCShroudEntryStruct);
if (memory_needed >= buffer_size) {
return false;
}
int xpixel;
int ypixel;
Map.Coord_To_Pixel(coord, xpixel, ypixel);
CellClass * cellptr = &Map[Coord_Cell(coord)];
CNCShroudEntryStruct &shroud_entry = shroud->Entries[entry_index];
shroud_entry.IsVisible = cellptr->Is_Visible(PlayerPtr);
shroud_entry.IsMapped = cellptr->Is_Mapped(PlayerPtr);
shroud_entry.IsJamming = cellptr->Is_Jamming(PlayerPtr);
//shroud_entry.IsVisible = cellptr->IsVisible;
//shroud_entry.IsMapped = cellptr->IsMapped;
shroud_entry.ShadowIndex = -1;
if (shroud_entry.IsMapped) {
if (!shroud_entry.IsVisible) {
shroud_entry.ShadowIndex = (char)Map.Cell_Shadow(cell, PlayerPtr);
}
}
entry_index++;
}
}
shroud->Count = entry_index;
return true;
}
/**************************************************************************************************
* DLLExportClass::Get_Occupier_State -- Get the occupier state for this player
*
* In:
*
* Out:
*
*
*
* History: 10/25/2019 - SKY
**************************************************************************************************/
bool DLLExportClass::Get_Occupier_State(uint64 player_id, unsigned char *buffer_in, unsigned int buffer_size)
{
UNREFERENCED_PARAMETER(player_id);
CNCOccupierHeaderStruct* occupiers = (CNCOccupierHeaderStruct*)buffer_in;
CNCOccupierEntryHeaderStruct* entry = reinterpret_cast<CNCOccupierEntryHeaderStruct*>(occupiers + 1U);
occupiers->Count = 0;
unsigned int memory_needed = sizeof(CNCOccupierHeaderStruct);
int map_cell_x = Map.MapCellX;
int map_cell_y = Map.MapCellY;
int map_cell_width = Map.MapCellWidth;
int map_cell_height = Map.MapCellHeight;
if (map_cell_x > 0) {
map_cell_x--;
map_cell_width++;
}
if (map_cell_width < MAP_MAX_CELL_WIDTH) {
map_cell_width++;
}
if (map_cell_y > 0) {
map_cell_y--;
map_cell_height++;
}
if (map_cell_height < MAP_MAX_CELL_HEIGHT) {
map_cell_height++;
}
for (int y = 0; y < map_cell_height; y++) {
for (int x = 0; x < map_cell_width; x++, occupiers->Count++) {
CELL cell = XY_Cell(map_cell_x + x, map_cell_y + y);
CellClass * cellptr = &Map[cell];
int occupier_count = 0;
ObjectClass* optr = cellptr->Cell_Occupier();
while (optr != NULL) {
occupier_count++;
optr = optr->Next;
}
memory_needed += sizeof(CNCOccupierEntryHeaderStruct) + (sizeof(CNCOccupierObjectStruct) * occupier_count);
if (memory_needed >= buffer_size) {
return false;
}
CNCOccupierObjectStruct* occupier = reinterpret_cast<CNCOccupierObjectStruct*>(entry + 1U);
entry->Count = 0;
optr = cellptr->Cell_Occupier();
for (int i = 0; i < occupier_count; i++, occupier++, entry->Count++) {
CNCObjectStruct object;
Convert_Type(optr, object);
occupier->Type = object.Type;
occupier->ID = object.ID;
optr = optr->Next;
}
entry = reinterpret_cast<CNCOccupierEntryHeaderStruct*>(occupier + 1U);
}
}
return true;
}
/**************************************************************************************************
* DLLExportClass::Get_Player_Info_State -- Get the multiplayer info for this player
*
* In:
*
* Out:
*
*
*
* History: 4/22/2019 10:33AM - ST
**************************************************************************************************/
bool DLLExportClass::Get_Player_Info_State(uint64 player_id, unsigned char *buffer_in, unsigned int buffer_size)
{
if (!DLLExportClass::Set_Player_Context(player_id)) {
return false;
}
CNCPlayerInfoStruct *player_info = (CNCPlayerInfoStruct*) buffer_in;
unsigned int memory_needed = sizeof(*player_info) + 32; // A little extra for no reason
if (memory_needed >= buffer_size) {
return false;
}
player_info->GlyphxPlayerID = 0;
if (PlayerPtr == NULL) {
return false;;
}
if (Session.Players.Count() > CurrentLocalPlayerIndex) {
strncpy(&player_info->Name[0], Session.Players[CurrentLocalPlayerIndex]->Name, MPLAYER_NAME_MAX);
}
player_info->Name[MPLAYER_NAME_MAX - 1] = 0; // Make sure it's terminated
player_info->House = PlayerPtr->Class->House;
player_info->AllyFlags = PlayerPtr->Get_Ally_Flags();
if (Session.Players.Count() > CurrentLocalPlayerIndex) {
player_info->ColorIndex = Session.Players[CurrentLocalPlayerIndex]->Player.Color;
} else {
player_info->ColorIndex = (PlayerPtr->Class->House == HOUSE_USSR) ? 2 : 1; // Fudge to a sensible color in campaign; 2 = red, 1 = blue
}
player_info->GlyphxPlayerID = player_id;
player_info->HomeCellX = Cell_X(MultiplayerStartPositions[CurrentLocalPlayerIndex]);
player_info->HomeCellY = Cell_Y(MultiplayerStartPositions[CurrentLocalPlayerIndex]);
player_info->IsDefeated = PlayerPtr->IsDefeated;
// Can see other players' power if ally (except for the player themself) or spying on a power plant
// Can see other players' money if spying on a resource building
player_info->SpiedPowerFlags = 0U;
player_info->SpiedMoneyFlags = 0U;
for (int i = 0; i < Houses.Count(); ++i) {
HouseClass* house = Houses.Ptr(i);
if ((house != nullptr) && house->IsActive && (house != PlayerPtr) && house->Is_Ally(PlayerPtr)) {
player_info->SpiedPowerFlags |= 1U << house->Class->House;
}
}
for (int i = 0; i < Buildings.Count(); ++i) {
BuildingClass* building = Buildings.Ptr(i);
if ((building != nullptr) && building->IsActive && (building->Spied_By() & (1U << PlayerPtr->Class->House))) {
if ((*building == STRUCT_POWER) || (*building == STRUCT_ADVANCED_POWER)) {
player_info->SpiedPowerFlags |= 1U << building->House->Class->House;
} else if ((*building == STRUCT_REFINERY) || (*building == STRUCT_STORAGE)) {
player_info->SpiedMoneyFlags |= 1U << building->House->Class->House;
}
}
}
// Populate spied data
for (char house = 0; house < MAX_HOUSES; ++house) {
HouseClass* hptr = HouseClass::As_Pointer((HousesType)house);
if ((hptr != nullptr) && hptr->IsActive) {
if (player_info->SpiedPowerFlags & (1U << house)) {
player_info->SpiedInfo[house].Power = hptr->Power;
player_info->SpiedInfo[house].Drain = hptr->Drain;
}
if (player_info->SpiedMoneyFlags & (1U << house)) {
player_info->SpiedInfo[house].Money = hptr->Available_Money();
}
}
}
// Populate selection info
if (CurrentObject.Count() > 0) {
CNCObjectStruct object;
Convert_Type(CurrentObject[0], object);
player_info->SelectedID = object.ID;
player_info->SelectedType = object.Type;
const int left = Map.MapCellX;
const int right = Map.MapCellX + Map.MapCellWidth - 1;
const int top = Map.MapCellY;
const int bottom = Map.MapCellY + Map.MapCellHeight - 1;
// Use first object with a weapon, or first object if none
ObjectClass* action_object = nullptr;
for (int i = 0; i < CurrentObject.Count(); ++i) {
ObjectClass* object = CurrentObject[i];
if (object->Is_Techno()) {
TechnoClass* techno = (TechnoClass*)object;
if (techno->Techno_Type_Class()->PrimaryWeapon != NULL || techno->Techno_Type_Class()->SecondaryWeapon != NULL) {
action_object = object;
break;
}
}
}
if (action_object == nullptr) {
action_object = CurrentObject[0];
}
int index = 0;
for (int y = top; y <= bottom; ++y) {
for (int x = left; x <= right; ++x, ++index) {
Convert_Action_Type(action_object->What_Action(XY_Cell(x, y)), (CurrentObject.Count() == 1) ? action_object : NULL, As_Target(XY_Cell(x, y)), player_info->ActionWithSelected[index]);
}
}
player_info->ActionWithSelectedCount = Map.MapCellWidth * Map.MapCellHeight;
}
else {
player_info->SelectedID = -1;
player_info->SelectedType = UNKNOWN;
player_info->ActionWithSelectedCount = 0U;
}
// Screen shake
player_info->ScreenShake = PlayerPtr->ScreenShakeTime;
// Radar jammed
player_info->IsRadarJammed = Map.Get_Jammed(PlayerPtr);
return true;
};
/**************************************************************************************************
* DLLExportClass::Get_Dynamic_Map_State -- Get a snapshot of the smudges and overlays on the terrain
*
* In:
*
* Out:
*
*
*
* History: 2/8/2019 10:45AM - ST
**************************************************************************************************/
bool DLLExportClass::Get_Dynamic_Map_State(uint64 player_id, unsigned char *buffer_in, unsigned int buffer_size)
{
/*
** Get the player for this...
*/
player_id;
static int _call_count = 0;
CNCDynamicMapStruct *dynamic_map = (CNCDynamicMapStruct*) buffer_in;
unsigned int memory_needed = sizeof(*dynamic_map) + 256; // Base amount needed. Will need more depending on how many entries there are
int entry_index = 0;
/*
**
** Based loosely on DisplayClass::Redraw_Icons
**
**
*/
int map_cell_x = Map.MapCellX;
int map_cell_y = Map.MapCellY;
int map_cell_width = Map.MapCellWidth;
int map_cell_height = Map.MapCellHeight;
if (map_cell_x > 0) {
map_cell_x--;
map_cell_width++;
}
if (map_cell_width < MAP_MAX_CELL_WIDTH) {
map_cell_width++;
}
if (map_cell_y > 0) {
map_cell_y--;
map_cell_height++;
}
if (map_cell_height < MAP_MAX_CELL_WIDTH) {
map_cell_height++;
}
int cell_index = 0;
bool debug_output = false;
//if (_call_count == 20) {
//debug_output = true;
//}
// Need to ignore view constraints for dynamic map updates, so the radar map
// has the latest tiberium state for cells outside the tactical view
DLLExportClass::Adjust_Internal_View(true);
for (int y = 0 ; y < map_cell_height ; y++) {
for (int x = 0 ; x < map_cell_width ; x++) {
CELL cell = XY_Cell(map_cell_x+x, map_cell_y+y);
COORDINATE coord = Cell_Coord(cell) & 0xFF00FF00L;
memory_needed += sizeof(CNCDynamicMapEntryStruct) * 2;
if (memory_needed >= buffer_size) {
return false;
}
/*
** Only cells flagged to be redraw are examined.
*/
//if (In_View(cell) && Is_Cell_Flagged(cell)) {
int xpixel;
int ypixel;
if (Map.Coord_To_Pixel(coord, xpixel, ypixel)) {
CellClass * cellptr = &Map[Coord_Cell(coord)];
/*
** If there is a portion of the underlying icon that could be visible,
** then draw it. Also draw the cell if the shroud is off.
*/
if (GAME_TO_PLAY == GAME_GLYPHX_MULTIPLAYER || cellptr->IsMapped || Debug_Unshroud) {
Cell_Class_Draw_It(dynamic_map, entry_index, cellptr, xpixel, ypixel, debug_output);
}
/*
** If any cell is not fully mapped, then flag it so that the shadow drawing
** process will occur. Only draw the shadow if Debug_Unshroud is false.
*/
//if (!cellptr->IsMapped && !Debug_Unshroud) {
// IsShadowPresent = true;
//}
}
//}
}
}
if (entry_index) {
_call_count++;
}
dynamic_map->Count = entry_index;
dynamic_map->VortexActive = ChronalVortex.Is_Active();
dynamic_map->VortexX = Coord_X(ChronalVortex.Get_Position());
dynamic_map->VortexY = Coord_Y(ChronalVortex.Get_Position());
dynamic_map->VortexWidth = Pixel_To_Lepton(64);
dynamic_map->VortexHeight = Pixel_To_Lepton(64);
return true;
}
/**************************************************************************************************
* DLLExportClass::Cell_Class_Draw_It -- Go through the motions of drawing a cell to get the smudge and overlay info
*
* In:
*
* Out:
*
*
*
* History: 2/8/2019 11:09AM - ST
**************************************************************************************************/
void DLLExportClass::Cell_Class_Draw_It(CNCDynamicMapStruct *dynamic_map, int &entry_index, CellClass *cell_ptr, int xpixel, int ypixel, bool debug_output)
{
/*
**
** Based on CellClass::Draw_It and SmudgeTypeClass::Draw_It
**
**
*/
CELL cell = cell_ptr->Cell_Number();
/*
** Redraw any smudge.
*/
if (cell_ptr->Smudge != SMUDGE_NONE) {
//SmudgeTypeClass::As_Reference(Smudge).Draw_It(x, y, SmudgeData);
const SmudgeTypeClass &smudge_type = SmudgeTypeClass::As_Reference(cell_ptr->Smudge);
if (smudge_type.Get_Image_Data() != NULL) {
if (debug_output) {
IsTheaterShape = true;
Debug_Write_Shape_Type(&smudge_type, 0);
IsTheaterShape = false;
}
CNCDynamicMapEntryStruct &smudge_entry = dynamic_map->Entries[entry_index++];
strncpy(smudge_entry.AssetName, smudge_type.IniName, CNC_OBJECT_ASSET_NAME_LENGTH);
smudge_entry.Type = (short) cell_ptr->Smudge;
smudge_entry.Owner = (char)cell_ptr->Owner;
smudge_entry.DrawFlags = SHAPE_WIN_REL; // Looks like smudges are drawn top left
smudge_entry.PositionX = xpixel;
smudge_entry.PositionY = ypixel;
smudge_entry.Width = Get_Build_Frame_Width(smudge_type.Get_Image_Data());
smudge_entry.Height = Get_Build_Frame_Height(smudge_type.Get_Image_Data());
smudge_entry.CellX = Cell_X(cell);
smudge_entry.CellY = Cell_Y(cell);
smudge_entry.ShapeIndex = cell_ptr->SmudgeData;
smudge_entry.IsSmudge = true;
smudge_entry.IsOverlay = false;
smudge_entry.IsResource = false;
smudge_entry.IsSellable = false;
smudge_entry.IsTheaterShape = true; // Smudges are always theater-specific
smudge_entry.IsFlag = false;
}
}
/*
** Draw the overlay object.
*/
if (cell_ptr->Overlay != OVERLAY_NONE) {
//OverlayTypeClass const & otype = OverlayTypeClass::As_Reference(Overlay);
//IsTheaterShape = (bool)otype.IsTheater;
//CC_Draw_Shape(otype.Get_Image_Data(), OverlayData, (x+(CELL_PIXEL_W>>1)), (y+(CELL_PIXEL_H>>1)), WINDOW_TACTICAL, SHAPE_CENTER|SHAPE_WIN_REL|SHAPE_GHOST, NULL, Map.UnitShadow);
//IsTheaterShape = false;
const OverlayTypeClass &overlay_type = OverlayTypeClass::As_Reference(cell_ptr->Overlay);
if (overlay_type.Get_Image_Data() != NULL) {
CNCDynamicMapEntryStruct &overlay_entry = dynamic_map->Entries[entry_index++];
if (debug_output) {
IsTheaterShape = (bool)overlay_type.IsTheater;
Debug_Write_Shape_Type(&overlay_type, 0);
IsTheaterShape = false;
}
strncpy(overlay_entry.AssetName, overlay_type.IniName, CNC_OBJECT_ASSET_NAME_LENGTH);
overlay_entry.Type = (short)cell_ptr->Overlay;
overlay_entry.Owner = (char)cell_ptr->Owner;
overlay_entry.DrawFlags = SHAPE_CENTER|SHAPE_WIN_REL|SHAPE_GHOST; // Looks like overlays are drawn centered and translucent
overlay_entry.PositionX = xpixel + (CELL_PIXEL_W>>1);
overlay_entry.PositionY = ypixel + (CELL_PIXEL_H>>1);
overlay_entry.Width = Get_Build_Frame_Width(overlay_type.Get_Image_Data());
overlay_entry.Height = Get_Build_Frame_Height(overlay_type.Get_Image_Data());
overlay_entry.CellX = Cell_X(cell);
overlay_entry.CellY = Cell_Y(cell);
overlay_entry.ShapeIndex = cell_ptr->OverlayData;
overlay_entry.IsSmudge = false;
overlay_entry.IsOverlay = true;
overlay_entry.IsResource = overlay_entry.Type >= OVERLAY_GOLD1 && overlay_entry.Type <= OVERLAY_GEMS4;
overlay_entry.IsSellable = (overlay_entry.Type >= OVERLAY_SANDBAG_WALL && overlay_entry.Type <= OVERLAY_WOOD_WALL) || overlay_entry.Type == OVERLAY_FENCE;
overlay_entry.IsTheaterShape = (bool)overlay_type.IsTheater;
overlay_entry.IsFlag = false;
}
}
if (cell_ptr->IsFlagged) {
const void* image_data = MFCD::Retrieve("FLAGFLY.SHP");
if (image_data != NULL) {
CNCDynamicMapEntryStruct &flag_entry = dynamic_map->Entries[entry_index++];
strncpy(flag_entry.AssetName, "FLAGFLY", CNC_OBJECT_ASSET_NAME_LENGTH);
flag_entry.AssetName[CNC_OBJECT_ASSET_NAME_LENGTH - 1] = 0;
flag_entry.Type = -1;
flag_entry.Owner = cell_ptr->Owner;
flag_entry.DrawFlags = SHAPE_CENTER|SHAPE_GHOST|SHAPE_FADING;
flag_entry.PositionX = xpixel + (ICON_PIXEL_W / 2);
flag_entry.PositionY = ypixel + (ICON_PIXEL_H / 2);
flag_entry.Width = Get_Build_Frame_Width(image_data);
flag_entry.Height = Get_Build_Frame_Height(image_data);
flag_entry.CellX = Cell_X(cell);
flag_entry.CellY = Cell_Y(cell);
flag_entry.ShapeIndex = Frame % 14;
flag_entry.IsSmudge = false;
flag_entry.IsOverlay = false;
flag_entry.IsResource = false;
flag_entry.IsSellable = false;
flag_entry.IsTheaterShape = false;
flag_entry.IsFlag = true;
}
}
}
/**************************************************************************************************
* DLLExportClass::Glyphx_Queue_AI -- Special queue processing for Glyphx multiplayer mode
*
* In:
*
* Out:
*
*
*
* History: 3/12/2019 10:52AM - ST
**************************************************************************************************/
void DLLExportClass::Glyphx_Queue_AI(void)
{
//------------------------------------------------------------------------
// Move events from the OutList (events generated by this player) into the
// DoList (the list of events to execute).
//------------------------------------------------------------------------
while (OutList.Count) {
OutList.First().IsExecuted = false;
if (!DoList.Add(OutList.First())) {
;
}
OutList.Next();
}
/*
** Based on Execute_DoList in queue.cpp
**
** The events have the ID of the player encoded in them, so no special per-player processing should be needed.
** When the event is created, the 'local player' is assumed to be the originator of the event, so PlayerPtr will need
** to be swapped out to represent the real originating player prior to any events being created as a result of GlyphX input
**
** ST - 3/12/2019 10:51AM
*/
for (int i = 0; i < MULTIPLAYER_COUNT; i++) {
HousesType house;
HouseClass *housep;
house = Session.Players[i]->Player.ID;
housep= HouseClass::As_Pointer (house);
//.....................................................................
// If for some reason this house doesn't exist, skip it.
// Also, if this house has exited the game, skip it. (The user can
// generate events after he exits, because the exit event is scheduled
// at least FrameSendRate*3 frames ahead. If one system gets these
// packets & another system doesn't, they'll go out of sync because
// they aren't checking the CommandCount for that house, since that
// house isn't connected any more.)
//.....................................................................
if (!housep){
continue;
}
if (!housep->IsHuman){
continue;
}
//.....................................................................
// Loop through all events
//.....................................................................
for (int j = 0; j < DoList.Count; j++) {
if (!DoList[j].IsExecuted && (unsigned)Frame >= DoList[j].Frame) {
DoList[j].Execute();
//...............................................................
// Mark this event as executed.
//...............................................................
DoList[j].IsExecuted = 1;
}
}
}
//------------------------------------------------------------------------
// Clean out the DoList
//------------------------------------------------------------------------
while (DoList.Count) {
//.....................................................................
// Discard events that have been executed, OR it's too late to execute.
// (This happens if another player exits the game; he'll leave FRAMEINFO
// events lying around in my queue. They won't have been "executed",
// because his IPX connection was destroyed.)
//.....................................................................
if ( (DoList.First().IsExecuted) || ((unsigned)Frame > DoList.First().Frame) ) {
DoList.Next();
}
else {
break;
}
}
}
CarryoverClass Test_CC(CarryoverObjectStruct* cptr)
{
CarryoverClass cc;
cc.RTTI = (RTTIType)cptr->RTTI;
cc.Type.Building = (StructType)cptr->Type; //This works regardless of what the RTTI-type and the enum type, because they're all enums. - LLL
cc.Cell = (CELL)cptr->Cell;
cc.House = (HousesType)cptr->House;
cc.Strength = cptr->Strength;
return cc;
}
/**************************************************************************************************
* DLLExportClass::Reset_Sidebars -- Init the multiplayer sidebars
*
*
*
* History: 11/8/2019 - LLL
**************************************************************************************************/
void DLLExportClass::Store_Carryover_Objects()
{
if (EventCallback == NULL || Scen.IsToCarryOver == false) {
return;
}
CarryoverObjectStruct* carryover_list = 0;
CarryoverObjectStruct* carryover_list_tail = 0;
/*
** Record all objects, that are to be part of the carry over set, into the carry over list.
*/
for (int building_index = 0; building_index < Buildings.Count(); building_index++) {
BuildingClass * building = Buildings.Ptr(building_index);
if (building && !building->IsInLimbo && building->Strength > 0) {
CarryoverClass carryover = CarryoverClass(building);
CarryoverObjectStruct* cptr = new CarryoverObjectStruct();
if (cptr) {
cptr->RTTI = carryover.RTTI;
cptr->Type = (int)carryover.Type.Building;
cptr->House = carryover.House;
cptr->Cell = carryover.Cell;
cptr->Strength = carryover.Strength;
CarryoverClass cc = Test_CC(cptr);
cc;
if (!carryover_list_tail) {
carryover_list = cptr;
carryover_list_tail = cptr;
}
else {
carryover_list_tail->Next = cptr;
carryover_list_tail = cptr;
}
}
}
}
for (int unit_index = 0; unit_index < Units.Count(); unit_index++) {
UnitClass * unit = Units.Ptr(unit_index);
if (unit && !unit->IsInLimbo && unit->Strength > 0) {
CarryoverClass carryover = CarryoverClass(unit);
CarryoverObjectStruct* cptr = new CarryoverObjectStruct();
if (cptr) {
cptr->RTTI = carryover.RTTI;
cptr->Type = (int)carryover.Type.Unit;
cptr->House = carryover.House;
cptr->Cell = carryover.Cell;
cptr->Strength = carryover.Strength;
CarryoverClass cc = Test_CC(cptr);
cc;
if (!carryover_list_tail) {
carryover_list = cptr;
carryover_list_tail = cptr;
}
else {
carryover_list_tail->Next = cptr;
carryover_list_tail = cptr;
}
}
}
}
for (int infantry_index = 0; infantry_index < Infantry.Count(); infantry_index++) {
InfantryClass * infantry = Infantry.Ptr(infantry_index);
if (infantry && !infantry->IsInLimbo && infantry->Strength > 0) {
CarryoverClass carryover = CarryoverClass(infantry);
CarryoverObjectStruct* cptr = new CarryoverObjectStruct();
if (cptr) {
cptr->RTTI = carryover.RTTI;
cptr->Type = (int)carryover.Type.Building;
cptr->House = carryover.House;
cptr->Cell = carryover.Cell;
cptr->Strength = carryover.Strength;
CarryoverClass cc = Test_CC(cptr);
cc;
if (!carryover_list_tail) {
carryover_list = cptr;
carryover_list_tail = cptr;
}
else {
carryover_list_tail->Next = cptr;
carryover_list_tail = cptr;
}
}
}
}
for (int vessel_index = 0; vessel_index < Vessels.Count(); vessel_index++) {
VesselClass * vessel = Vessels.Ptr(vessel_index);
if (vessel && !vessel->IsInLimbo && vessel->Strength > 0) {
CarryoverClass carryover = CarryoverClass(vessel);
CarryoverObjectStruct* cptr = new CarryoverObjectStruct();
if (cptr) {
cptr->RTTI = carryover.RTTI;
cptr->Type = (int)carryover.Type.Building;
cptr->House = carryover.House;
cptr->Cell = carryover.Cell;
cptr->Strength = carryover.Strength;
CarryoverClass cc = Test_CC(cptr);
cc;
if (!carryover_list_tail) {
carryover_list = cptr;
carryover_list_tail = cptr;
}
else {
carryover_list_tail->Next = cptr;
carryover_list_tail = cptr;
}
}
}
}
//Make & Send Event
EventCallbackStruct event;
event.EventType = CALLBACK_EVENT_STORE_CARRYOVER_OBJECTS;
event.CarryoverObjects.CarryoverList = carryover_list;
EventCallback(event);
//Delete the list
while (carryover_list) {
CarryoverObjectStruct* cptr = (CarryoverObjectStruct*)carryover_list->Next;
delete carryover_list;
carryover_list = cptr;
}
}
/**************************************************************************************************
* DLLExportClass::Reset_Sidebars -- Init the multiplayer sidebars
*
* In:
*
* Out:
*
*
*
* History: 3/14/2019 3:10PM - ST
**************************************************************************************************/
void DLLExportClass::Reset_Sidebars(void)
{
for (int i=0 ; i<MULTIPLAYER_COUNT ; i++) {
if (i >= Session.Players.Count()) {
continue;
}
if (Session.Players[i] == NULL) {
continue;
}
HouseClass *player_ptr = HouseClass::As_Pointer(Session.Players[i]->Player.ID);
MultiplayerSidebars[i].Init_Clear(player_ptr);
}
}
/**************************************************************************************************
* DLLExportClass::Set_Player_Context -- Switch the C&C local player context
*
* In:
*
* Out:
*
*
*
* History: 3/14/2019 3:20PM - ST
**************************************************************************************************/
bool DLLExportClass::Set_Player_Context(uint64 glyphx_player_id, bool force)
{
/*
** Context never needs to change in single player
*/
if (GAME_TO_PLAY == GAME_NORMAL) {
if (PlayerPtr) {
CurrentObject.Set_Active_Context(PlayerPtr->Class->House);
}
return true;
}
/*
** C&C relies a lot on PlayerPtr, which is a pointer to the 'local' player's house. Historically, in a peer-to-peer
** multiplayer game, each player's PlayerPtr pointed to their own local player.
**
** Since much of the IO logic depends on PlayerPtr being the player performing the action, we need to set PlayerPtr
** correctly depending on which player generated input or needs output
*/
for (int i=0 ; i<MULTIPLAYER_COUNT ; i++) {
if (GlyphxPlayerIDs[i] == glyphx_player_id) {
if (!force && i == CurrentLocalPlayerIndex) {
return true;
}
PlayerPtr = HouseClass::As_Pointer(Session.Players[i]->Player.ID);
CurrentObject.Set_Active_Context(PlayerPtr->Class->House);
CurrentLocalPlayerIndex = i;
Refresh_Player_Control_Flags();
return true;
}
}
return false;
}
/**************************************************************************************************
* DLLExportClass::Reset_Player_Context -- Clear out old player context data
*
* In:
*
* Out:
*
*
*
* History: 4/16/2019 10:36AM - ST
**************************************************************************************************/
void DLLExportClass::Reset_Player_Context(void)
{
CurrentLocalPlayerIndex = 0;
CurrentObject.Clear_All();
}
/**************************************************************************************************
* DLLExportClass::Refresh_Player_Control_Flags -- Set the IsPlayerControl flags so that the player
* in context has IsPlayerControl
*
* In:
*
* Out:
*
*
*
* History: 4/16/2019 10:36AM - ST
**************************************************************************************************/
void DLLExportClass::Refresh_Player_Control_Flags(void)
{
for (int i=0 ; i<MULTIPLAYER_COUNT ; i++) {
HouseClass *player_ptr = HouseClass::As_Pointer(Session.Players[i]->Player.ID);
if (player_ptr) {
if (i == CurrentLocalPlayerIndex && player_ptr->IsHuman) {
player_ptr->IsPlayerControl = true;
} else {
player_ptr->IsPlayerControl = false;
}
}
}
}
/**************************************************************************************************
* Logic_Switch_Player_Context -- Called when the internal game locic needs to switch player context
*
* In:
*
* Out:
*
*
*
* History: 4/17/2019 9:45AM - ST
**************************************************************************************************/
void Logic_Switch_Player_Context(ObjectClass *object)
{
DLLExportClass::Logic_Switch_Player_Context(object);
}
/**************************************************************************************************
* DLLExportClass::Logic_Switch_Player_Context -- Called when the internal game locic needs to switch player context
*
* In:
*
* Out:
*
*
*
* History: 4/17/2019 9:45AM - ST
**************************************************************************************************/
void DLLExportClass::Logic_Switch_Player_Context(ObjectClass *object)
{
if (object == NULL) {
return;
}
/*
** If it's not a techno, it can't be owned.
*/
if (!object->Is_Techno()) {
return;
}
TechnoClass *tech = static_cast<TechnoClass*>(object);
//HousesType house = tech->House->Class->House;
DLLExportClass::Logic_Switch_Player_Context(tech->House);
}
/**************************************************************************************************
* Logic_Switch_Player_Context -- Called when the internal game locic needs to switch player context
*
* In:
*
* Out:
*
*
*
* History: 4/17/2019 9:45AM - ST
**************************************************************************************************/
void Logic_Switch_Player_Context(HouseClass *object)
{
DLLExportClass::Logic_Switch_Player_Context(object);
}
/**************************************************************************************************
* DLLExportClass::Logic_Switch_Player_Context -- Called when the internal game locic needs to switch player context
*
* In:
*
* Out:
*
*
*
* History: 4/17/2019 9:45AM - ST
**************************************************************************************************/
void DLLExportClass::Logic_Switch_Player_Context(HouseClass *house)
{
if (GAME_TO_PLAY == GAME_NORMAL) {
CurrentObject.Set_Active_Context(PlayerPtr->Class->House);
return;
}
if (house == NULL) {
return;
}
/*
** C&C relies a lot on PlayerPtr, which is a pointer to the 'local' player's house. Historically, in a peer-to-peer
** multiplayer game, each player's PlayerPtr pointed to their own local player.
**
** Since much of the IO logic depends on PlayerPtr being the player performing the action, we need to set PlayerPtr
** correctly depending on which player generated input or needs output
*/
HousesType house_type = house->Class->House;
for (int i=0 ; i<MULTIPLAYER_COUNT ; i++) {
if (house_type == Session.Players[i]->Player.ID) {
if (i == CurrentLocalPlayerIndex) {
return;
}
PlayerPtr = HouseClass::As_Pointer(Session.Players[i]->Player.ID);
CurrentObject.Set_Active_Context(PlayerPtr->Class->House);
CurrentLocalPlayerIndex = i;
Refresh_Player_Control_Flags();
return;
}
}
}
/**************************************************************************************************
* DLLExportClass::Calculate_Start_Positions -- Calculate the initial view positions for the players
*
* In:
*
* Out:
*
*
*
* History: 4/16/2019 6/12/2019 3:00PM - ST
**************************************************************************************************/
void DLLExportClass::Calculate_Start_Positions(void)
{
if (GAME_TO_PLAY == GAME_NORMAL) {
MultiplayerStartPositions[0] = Scen.Views[0];
return;
}
HouseClass *player_ptr = PlayerPtr;
ScenarioInit++;
COORDINATE old_tac = Map.TacticalCoord;
for (int i=0 ; i< MULTIPLAYER_COUNT; i++) {
PlayerPtr = HouseClass::As_Pointer(Session.Players[i]->Player.ID);
if (PlayerPtr) {
long x, y;
Map.Compute_Start_Pos(x, y);
MultiplayerStartPositions[i] = XY_Cell(x, y);
}
}
Map.TacticalCoord = old_tac;
ScenarioInit--;
PlayerPtr = player_ptr;
}
/**************************************************************************************************
* DLLExportClass::Get_GlyphX_Player_ID -- Get the external GlyphX player ID from the C&C house/player pointer
* Returns 0 in single player or if player ID isn't found
*
* In:
*
* Out:
*
*
*
* History: 4/22/2019 6:23PM - ST
**************************************************************************************************/
__int64 DLLExportClass::Get_GlyphX_Player_ID(const HouseClass *house)
{
/*
** C&C relies a lot on PlayerPtr, which is a pointer to the 'local' player's house. Historically, in a peer-to-peer
** multiplayer game, each player's PlayerPtr pointed to their own local player.
**
** Since much of the IO logic depends on PlayerPtr being the player performing the action, we need to set PlayerPtr
** correctly depending on which player generated input or needs output
*/
if (GAME_TO_PLAY == GAME_NORMAL) {
return 0;
}
if (house == NULL) {
return 0;
}
HousesType house_type = house->Class->House;
for (int i=0 ; i<MULTIPLAYER_COUNT ; i++) {
if (house_type == Session.Players[i]->Player.ID) {
return GlyphxPlayerIDs[i];
}
}
/*
** Failure case.
*/
return 0;
}
/**************************************************************************************************
* DLLExportClass::Adjust_Internal_View -- Set the internal tactical view to encompass the input coordinates
*
* In:
*
* Out:
*
*
*
* History: 4/16/2019 3:00PM - ST
**************************************************************************************************/
void DLLExportClass::Adjust_Internal_View(bool force_ignore_view_constraints)
{
/*
** When legacy rendering is disabled (especially in multiplayer) we can get input coordinates that
** fall outside the engine's tactical view. In this case, we need to adjust the tactical view before the
** input will behave as expected.
*/
if (!force_ignore_view_constraints && Legacy_Render_Enabled()) {
/*
** Render view should already be tracking the player's local view
*/
DisplayClass::IgnoreViewConstraints = false;
return;
}
DisplayClass::IgnoreViewConstraints = true;
}
/**************************************************************************************************
* DLLExportClass::Get_Current_Context_Sidebar -- Get the sidebar data for the current player context
*
* In:
*
* Out:
*
*
*
* History: 3/14/2019 3:20PM - ST
**************************************************************************************************/
SidebarGlyphxClass *DLLExportClass::Get_Current_Context_Sidebar(HouseClass *player_ptr)
{
if (player_ptr) {
for (int i=0 ; i<MULTIPLAYER_COUNT ; i++) {
if (player_ptr == HouseClass::As_Pointer(Session.Players[i]->Player.ID)) {
return &MultiplayerSidebars[i];
}
}
}
return &MultiplayerSidebars[CurrentLocalPlayerIndex];
}
SidebarGlyphxClass *Get_Current_Context_Sidebar(HouseClass *player_ptr)
{
return DLLExportClass::Get_Current_Context_Sidebar(player_ptr);
}
/**************************************************************************************************
* DLLExportClass::Repair_Mode -- Starts the player's repair mode. All it does here is unselect all units.
*
* In:
*
* Out:
*
*
*
* History: 5/1/2019 - LLL
**************************************************************************************************/
void DLLExportClass::Repair_Mode(uint64 player_id)
{
/*
** Get the player for this...
*/
if (!DLLExportClass::Set_Player_Context(player_id)) {
return;
}
Unselect_All();
}
/**************************************************************************************************
* DLLExportClass::Repair -- Repairs a specific building
*
* In:
*
* Out:
*
*
*
* History: 5/1/2019 - LLL
**************************************************************************************************/
void DLLExportClass::Repair(uint64 player_id, int object_id)
{
/*
** Get the player for this...
*/
if (!DLLExportClass::Set_Player_Context(player_id)) {
return;
}
TARGET target = Build_Target(RTTI_BUILDING, object_id);
if (target != TARGET_NONE)
{
BuildingClass* building = As_Building(target);
if (building) {
if (!building->IsActive) {
GlyphX_Debug_Print("DLLExportClass::Repair -- trying to repair a non-active building");
} else {
if (building->Can_Repair() && building->House.Is_Valid() && building->House->Class->House == PlayerPtr->Class->House)
{
building->Repair(-1);
}
}
}
}
}
/**************************************************************************************************
* DLLExportClass::Sell_Mode -- Starts the player's sell mode. All it does here is unselect all units.
*
* In:
*
* Out:
*
*
*
* History: 5/1/2019 - LLL
**************************************************************************************************/
void DLLExportClass::Sell_Mode(uint64 player_id)
{
/*
** Get the player for this...
*/
if (!DLLExportClass::Set_Player_Context(player_id)) {
return;
}
Unselect_All();
}
/**************************************************************************************************
* DLLExportClass::Sell -- Sell's a player's speceific building.
*
* In:
*
* Out:
*
*
*
* History: 5/1/2019 - LLL
**************************************************************************************************/
void DLLExportClass::Sell(uint64 player_id, int object_id)
{
/*
** Get the player for this...
*/
if (!DLLExportClass::Set_Player_Context(player_id)) {
return;
}
TARGET target = Build_Target(RTTI_BUILDING, object_id);
if (target != TARGET_NONE)
{
BuildingClass* building = As_Building(target);
if (building) {
if (!building->IsActive) {
GlyphX_Debug_Print("DLLExportClass::Sell -- trying to sell a non-active building");
} else {
if (building->Can_Demolish() && building->House.Is_Valid() && building->House->Class->House == PlayerPtr->Class->House)
{
building->Sell_Back(1);
}
}
}
}
}
/**************************************************************************************************
* DLLExportClass::Repair_Sell_Cancel -- Ends the player's repair or sell mode. Doesn't do anything right now.
*
* In:
*
* Out:
*
*
*
* History: 5/1/2019 - LLL
**************************************************************************************************/
void DLLExportClass::Repair_Sell_Cancel(uint64 player_id)
{
//OutputDebugString("Repair_Sell_Cancel\n");
}
/**************************************************************************************************
* DLLExportClass::Scatter_Selected -- Scatter the selected units
*
* In:
*
* Out:
*
*
*
* History: 10/15/2019 - SKY
**************************************************************************************************/
void DLLExportClass::Scatter_Selected(uint64 player_id)
{
/*
** Get the player for this...
*/
if (!DLLExportClass::Set_Player_Context(player_id)) {
return;
}
if (CurrentObject.Count()) {
for (int index = 0; index < CurrentObject.Count(); index++) {
ObjectClass const * tech = CurrentObject[index];
if (tech != NULL && tech->Can_Player_Move()) {
OutList.Add(EventClass(EventClass::SCATTER, TargetClass(tech)));
}
}
}
}
/**************************************************************************************************
* DLLExportClass::Select_Next_Unit
*
* History: 03.02.2020 MBL
**************************************************************************************************/
void DLLExportClass::Select_Next_Unit(uint64 player_id)
{
/*
** Get the player for this...
*/
if (!DLLExportClass::Set_Player_Context(player_id)) {
return;
}
ObjectClass* obj = Map.Next_Object(CurrentObject.Count() ? CurrentObject[0] : NULL);
if (obj) {
Unselect_All();
obj->Select();
COORDINATE center = Map.Center_Map();
Map.Flag_To_Redraw(true);
if (center) {
On_Center_Camera(PlayerPtr, Coord_X(center), Coord_Y(center));
}
}
}
/**************************************************************************************************
* DLLExportClass::Select_Previous_Unit
*
* History: 03.02.2020 MBL
**************************************************************************************************/
void DLLExportClass::Select_Previous_Unit(uint64 player_id)
{
/*
** Get the player for this...
*/
if (!DLLExportClass::Set_Player_Context(player_id)) {
return;
}
ObjectClass* obj = Map.Prev_Object(CurrentObject.Count() ? CurrentObject[0] : NULL);
if (obj) {
Unselect_All();
obj->Select();
COORDINATE center = Map.Center_Map();
Map.Flag_To_Redraw(true);
if (center) {
On_Center_Camera(PlayerPtr, Coord_X(center), Coord_Y(center));
}
}
}
/**************************************************************************************************
* DLLExportClass::Selected_Guard_Mode
*
* History: 03.03.2020 MBL
**************************************************************************************************/
void DLLExportClass::Selected_Guard_Mode(uint64 player_id)
{
/*
** Get the player for this...
*/
if (!DLLExportClass::Set_Player_Context(player_id)) {
return;
}
if (CurrentObject.Count()) {
for (int index = 0; index < CurrentObject.Count(); index++) {
ObjectClass const * tech = CurrentObject[index];
Command & Conquer Remastered post-launch patch Improvements to harvester resource finding logic. Don't allow the Advanced Comm Center to be capturable in skirmish or multiplayer. Increased failed pathfinding fudge factor. Buildings accept the Guard command if they can attack. Don't allow force capturing of ally structures. Fixes for laser Orcas in S3cr3t M1ss10n. Properly restore them after save. Reset Orcas after loads. Fixed flag animation rendering in CTF. Potentially fix a crash if aircraft are destroyed outside the map bounds. Fixed legacy Obelisk line rendering. Fix out-of-bounds crash in TD; issue was already fixed in RA. Disable capture flag on Commandos. Drop the flag when entering the limbo state. Fixed end game race condition, winning team ID is always sent before individual player win/lose messages. Fixed Chan spawn on SCB10EA. Don't show enter cursor for enemy units on refineries and repair pads. Changing right-click support for first put building on hold, and then subsequenct right-clicks to decrement that queue count for 1x or 5x; Then, 1x or 5x Left click will resume from hold. Don't debug reveal legacy rendering when a player is defeated. Fixed crash when loading saves of custom campaign maps. Reset harvester archived target when given a direct harvest order. Prevent NOD cargo planes from being force attacked. Fixed unit selection on load. Migrated queued repair pad functionality from RA to TD. Randomly animate infantry in area guard mode. Fixed crash accessing inactive objects. Added some walls in SCG08EB to prevent civilians from killing themselves. TD + RA: Audio: Overiding "Our base is under attack" cooldown timing from legacy from 2 minutes to 30 seconds, so it will be heard 4x as often. Fixed adjacent cell out-of-bounds crash issues. Kill player on disconnect Fixed and improved build time calculations to be consistent between TD and RA. Don't show health bars for cloaked Stealth Tanks in the legacy renderer. Fix selection of individual control groups after mixed selection. More adjustments to SCG08EB; switch C7 to C5, and add civilian AI to avoid Tiberium. Extra safety checks for units that have no weapons and aircraft that can't hunt. Fix loading of multiple infantry onto an APC. Additional safety checks for invalid coordinates. Prevent units from being instantly repaired. Fix map passability. Fail Allied mission 5B if the spy re-boards the starting transport (matches 5A and 5C behavior). Fixed multiplayer formation move causing units to move at light speed. Ignore movement destination checks if a unit is part of a mission-driven team. Fix buffer overrun crash. Ignore mines when determining win conditions. Fixed river passability in Blue Lakes.
2020-06-23 04:43:21 +12:00
if (tech != NULL && tech->Can_Player_Fire()) {
if (tech->Can_Player_Move()) {
OutList.Add(EventClass(TargetClass(tech), MISSION_GUARD_AREA));
} else {
OutList.Add(EventClass(TargetClass(tech), MISSION_GUARD));
}
}
}
}
}
/**************************************************************************************************
* DLLExportClass::Selected_Stop
*
* History: 03.03.2020 MBL
**************************************************************************************************/
void DLLExportClass::Selected_Stop(uint64 player_id)
{
/*
** Get the player for this...
*/
if (!DLLExportClass::Set_Player_Context(player_id)) {
return;
}
// Copied from RedAlert/Conquer.cpp - Keyboard_Process() with Options.KeyStop (VK_S)
if (CurrentObject.Count()) {
for (int index = 0; index < CurrentObject.Count(); index++) {
ObjectClass const * tech = CurrentObject[index];
if (tech != NULL && (tech->Can_Player_Move() || (tech->Can_Player_Fire() && tech->What_Am_I() != RTTI_BUILDING))) {
OutList.Add(EventClass(EventClass::IDLE, TargetClass(tech)));
}
}
}
}
/**************************************************************************************************
* DLLExportClass::Units_Queued_Movement_Toggle
*
* History: 03.03.2020 MBL
**************************************************************************************************/
void DLLExportClass::Units_Queued_Movement_Toggle(uint64 player_id, bool toggle)
{
// Currently Red Alert only
/*
** Get the player for this...
*/
if (!DLLExportClass::Set_Player_Context(player_id)) {
return;
}
if (PlayerPtr != NULL)
{
PlayerPtr->IsQueuedMovementToggle = toggle;
}
}
/**************************************************************************************************
* DLLExportClass::Team_Units_Formation_Toggle_On
*
* History: 03.03.2020 MBL
**************************************************************************************************/
// extern void Toggle_Formation(void); // Code\RedAlert\Conquer.cpp
extern char FormationEvent; // Code\RedAlert\Conquer.cpp
void DLLExportClass::Team_Units_Formation_Toggle_On(uint64 player_id)
{
/*
** Get the player for this...
*/
if (!DLLExportClass::Set_Player_Context(player_id)) {
return;
}
//
// MBL 03.23.2020: Code here copied and modified from Toggle_Formation(), since obj->IsSelected is not supported
// Replacing with ObjectClass::Is_Selected_By_Player(HouseClass *player);
//
int team = MAX_TEAMS;
long minx = 0x7FFFFFFFL, miny = 0x7FFFFFFFL;
long maxx = 0, maxy = 0;
int index;
bool setform = 0;
Command & Conquer Remastered post-launch patch Improvements to harvester resource finding logic. Don't allow the Advanced Comm Center to be capturable in skirmish or multiplayer. Increased failed pathfinding fudge factor. Buildings accept the Guard command if they can attack. Don't allow force capturing of ally structures. Fixes for laser Orcas in S3cr3t M1ss10n. Properly restore them after save. Reset Orcas after loads. Fixed flag animation rendering in CTF. Potentially fix a crash if aircraft are destroyed outside the map bounds. Fixed legacy Obelisk line rendering. Fix out-of-bounds crash in TD; issue was already fixed in RA. Disable capture flag on Commandos. Drop the flag when entering the limbo state. Fixed end game race condition, winning team ID is always sent before individual player win/lose messages. Fixed Chan spawn on SCB10EA. Don't show enter cursor for enemy units on refineries and repair pads. Changing right-click support for first put building on hold, and then subsequenct right-clicks to decrement that queue count for 1x or 5x; Then, 1x or 5x Left click will resume from hold. Don't debug reveal legacy rendering when a player is defeated. Fixed crash when loading saves of custom campaign maps. Reset harvester archived target when given a direct harvest order. Prevent NOD cargo planes from being force attacked. Fixed unit selection on load. Migrated queued repair pad functionality from RA to TD. Randomly animate infantry in area guard mode. Fixed crash accessing inactive objects. Added some walls in SCG08EB to prevent civilians from killing themselves. TD + RA: Audio: Overiding "Our base is under attack" cooldown timing from legacy from 2 minutes to 30 seconds, so it will be heard 4x as often. Fixed adjacent cell out-of-bounds crash issues. Kill player on disconnect Fixed and improved build time calculations to be consistent between TD and RA. Don't show health bars for cloaked Stealth Tanks in the legacy renderer. Fix selection of individual control groups after mixed selection. More adjustments to SCG08EB; switch C7 to C5, and add civilian AI to avoid Tiberium. Extra safety checks for units that have no weapons and aircraft that can't hunt. Fix loading of multiple infantry onto an APC. Additional safety checks for invalid coordinates. Prevent units from being instantly repaired. Fix map passability. Fail Allied mission 5B if the spy re-boards the starting transport (matches 5A and 5C behavior). Fixed multiplayer formation move causing units to move at light speed. Ignore movement destination checks if a unit is part of a mission-driven team. Fix buffer overrun crash. Ignore mines when determining win conditions. Fixed river passability in Blue Lakes.
2020-06-23 04:43:21 +12:00
TeamFormDataStruct& team_form_data = TeamFormData[PlayerPtr->Class->House];
//
// Recording support
//
if (Session.Record) {
FormationEvent = 1;
}
/*
** Find the first selected object that is a member of a team, and
** register his group as the team we're using. Once we find the team
** number, update the 'setform' flag to know whether we should be setting
** the formation's offsets, or clearing them. If they currently have
** illegal offsets (as in 0x80000000), then we're setting.
*/
for (index = 0; index < Units.Count(); index++) {
UnitClass * obj = Units.Ptr(index);
if (obj) {
if (obj->House == PlayerPtr) {
if (!obj->IsInLimbo) {
if (obj->Is_Selected_By_Player(PlayerPtr)) {
team = obj->Group;
if (team < MAX_TEAMS) {
setform = obj->XFormOffset == (int)0x80000000;
Command & Conquer Remastered post-launch patch Improvements to harvester resource finding logic. Don't allow the Advanced Comm Center to be capturable in skirmish or multiplayer. Increased failed pathfinding fudge factor. Buildings accept the Guard command if they can attack. Don't allow force capturing of ally structures. Fixes for laser Orcas in S3cr3t M1ss10n. Properly restore them after save. Reset Orcas after loads. Fixed flag animation rendering in CTF. Potentially fix a crash if aircraft are destroyed outside the map bounds. Fixed legacy Obelisk line rendering. Fix out-of-bounds crash in TD; issue was already fixed in RA. Disable capture flag on Commandos. Drop the flag when entering the limbo state. Fixed end game race condition, winning team ID is always sent before individual player win/lose messages. Fixed Chan spawn on SCB10EA. Don't show enter cursor for enemy units on refineries and repair pads. Changing right-click support for first put building on hold, and then subsequenct right-clicks to decrement that queue count for 1x or 5x; Then, 1x or 5x Left click will resume from hold. Don't debug reveal legacy rendering when a player is defeated. Fixed crash when loading saves of custom campaign maps. Reset harvester archived target when given a direct harvest order. Prevent NOD cargo planes from being force attacked. Fixed unit selection on load. Migrated queued repair pad functionality from RA to TD. Randomly animate infantry in area guard mode. Fixed crash accessing inactive objects. Added some walls in SCG08EB to prevent civilians from killing themselves. TD + RA: Audio: Overiding "Our base is under attack" cooldown timing from legacy from 2 minutes to 30 seconds, so it will be heard 4x as often. Fixed adjacent cell out-of-bounds crash issues. Kill player on disconnect Fixed and improved build time calculations to be consistent between TD and RA. Don't show health bars for cloaked Stealth Tanks in the legacy renderer. Fix selection of individual control groups after mixed selection. More adjustments to SCG08EB; switch C7 to C5, and add civilian AI to avoid Tiberium. Extra safety checks for units that have no weapons and aircraft that can't hunt. Fix loading of multiple infantry onto an APC. Additional safety checks for invalid coordinates. Prevent units from being instantly repaired. Fix map passability. Fail Allied mission 5B if the spy re-boards the starting transport (matches 5A and 5C behavior). Fixed multiplayer formation move causing units to move at light speed. Ignore movement destination checks if a unit is part of a mission-driven team. Fix buffer overrun crash. Ignore mines when determining win conditions. Fixed river passability in Blue Lakes.
2020-06-23 04:43:21 +12:00
team_form_data.TeamSpeed[team] = SPEED_WHEEL;
team_form_data.TeamMaxSpeed[team] = MPH_LIGHT_SPEED;
break;
}
}
}
}
}
}
if (team >= MAX_TEAMS) {
for (index = 0; index < Infantry.Count(); index++) {
InfantryClass * obj = Infantry.Ptr(index);
if (obj) {
if (obj->House == PlayerPtr) {
if (!obj->IsInLimbo) {
if (obj->Is_Selected_By_Player(PlayerPtr)) {
team = obj->Group;
if (team < MAX_TEAMS) {
setform = obj->XFormOffset == (int)0x80000000;
Command & Conquer Remastered post-launch patch Improvements to harvester resource finding logic. Don't allow the Advanced Comm Center to be capturable in skirmish or multiplayer. Increased failed pathfinding fudge factor. Buildings accept the Guard command if they can attack. Don't allow force capturing of ally structures. Fixes for laser Orcas in S3cr3t M1ss10n. Properly restore them after save. Reset Orcas after loads. Fixed flag animation rendering in CTF. Potentially fix a crash if aircraft are destroyed outside the map bounds. Fixed legacy Obelisk line rendering. Fix out-of-bounds crash in TD; issue was already fixed in RA. Disable capture flag on Commandos. Drop the flag when entering the limbo state. Fixed end game race condition, winning team ID is always sent before individual player win/lose messages. Fixed Chan spawn on SCB10EA. Don't show enter cursor for enemy units on refineries and repair pads. Changing right-click support for first put building on hold, and then subsequenct right-clicks to decrement that queue count for 1x or 5x; Then, 1x or 5x Left click will resume from hold. Don't debug reveal legacy rendering when a player is defeated. Fixed crash when loading saves of custom campaign maps. Reset harvester archived target when given a direct harvest order. Prevent NOD cargo planes from being force attacked. Fixed unit selection on load. Migrated queued repair pad functionality from RA to TD. Randomly animate infantry in area guard mode. Fixed crash accessing inactive objects. Added some walls in SCG08EB to prevent civilians from killing themselves. TD + RA: Audio: Overiding "Our base is under attack" cooldown timing from legacy from 2 minutes to 30 seconds, so it will be heard 4x as often. Fixed adjacent cell out-of-bounds crash issues. Kill player on disconnect Fixed and improved build time calculations to be consistent between TD and RA. Don't show health bars for cloaked Stealth Tanks in the legacy renderer. Fix selection of individual control groups after mixed selection. More adjustments to SCG08EB; switch C7 to C5, and add civilian AI to avoid Tiberium. Extra safety checks for units that have no weapons and aircraft that can't hunt. Fix loading of multiple infantry onto an APC. Additional safety checks for invalid coordinates. Prevent units from being instantly repaired. Fix map passability. Fail Allied mission 5B if the spy re-boards the starting transport (matches 5A and 5C behavior). Fixed multiplayer formation move causing units to move at light speed. Ignore movement destination checks if a unit is part of a mission-driven team. Fix buffer overrun crash. Ignore mines when determining win conditions. Fixed river passability in Blue Lakes.
2020-06-23 04:43:21 +12:00
team_form_data.TeamSpeed[team] = SPEED_WHEEL;
team_form_data.TeamMaxSpeed[team] = MPH_LIGHT_SPEED;
break;
}
}
}
}
}
}
}
if (team >= MAX_TEAMS) {
for (index = 0; index < Vessels.Count(); index++) {
VesselClass * obj = Vessels.Ptr(index);
if (obj) {
if (obj->House == PlayerPtr) {
if (!obj->IsInLimbo) {
if (obj->Is_Selected_By_Player(PlayerPtr)) {
team = obj->Group;
if (team < MAX_TEAMS) {
setform = obj->XFormOffset == 0x80000000UL;
Command & Conquer Remastered post-launch patch Improvements to harvester resource finding logic. Don't allow the Advanced Comm Center to be capturable in skirmish or multiplayer. Increased failed pathfinding fudge factor. Buildings accept the Guard command if they can attack. Don't allow force capturing of ally structures. Fixes for laser Orcas in S3cr3t M1ss10n. Properly restore them after save. Reset Orcas after loads. Fixed flag animation rendering in CTF. Potentially fix a crash if aircraft are destroyed outside the map bounds. Fixed legacy Obelisk line rendering. Fix out-of-bounds crash in TD; issue was already fixed in RA. Disable capture flag on Commandos. Drop the flag when entering the limbo state. Fixed end game race condition, winning team ID is always sent before individual player win/lose messages. Fixed Chan spawn on SCB10EA. Don't show enter cursor for enemy units on refineries and repair pads. Changing right-click support for first put building on hold, and then subsequenct right-clicks to decrement that queue count for 1x or 5x; Then, 1x or 5x Left click will resume from hold. Don't debug reveal legacy rendering when a player is defeated. Fixed crash when loading saves of custom campaign maps. Reset harvester archived target when given a direct harvest order. Prevent NOD cargo planes from being force attacked. Fixed unit selection on load. Migrated queued repair pad functionality from RA to TD. Randomly animate infantry in area guard mode. Fixed crash accessing inactive objects. Added some walls in SCG08EB to prevent civilians from killing themselves. TD + RA: Audio: Overiding "Our base is under attack" cooldown timing from legacy from 2 minutes to 30 seconds, so it will be heard 4x as often. Fixed adjacent cell out-of-bounds crash issues. Kill player on disconnect Fixed and improved build time calculations to be consistent between TD and RA. Don't show health bars for cloaked Stealth Tanks in the legacy renderer. Fix selection of individual control groups after mixed selection. More adjustments to SCG08EB; switch C7 to C5, and add civilian AI to avoid Tiberium. Extra safety checks for units that have no weapons and aircraft that can't hunt. Fix loading of multiple infantry onto an APC. Additional safety checks for invalid coordinates. Prevent units from being instantly repaired. Fix map passability. Fail Allied mission 5B if the spy re-boards the starting transport (matches 5A and 5C behavior). Fixed multiplayer formation move causing units to move at light speed. Ignore movement destination checks if a unit is part of a mission-driven team. Fix buffer overrun crash. Ignore mines when determining win conditions. Fixed river passability in Blue Lakes.
2020-06-23 04:43:21 +12:00
team_form_data.TeamSpeed[team] = SPEED_WHEEL;
team_form_data.TeamMaxSpeed[team] = MPH_LIGHT_SPEED;
break;
}
}
}
}
}
}
}
if (team >= MAX_TEAMS) return;
/*
** Now that we have a team, let's go set (or clear) the formation offsets.
*/
for (index = 0; index < Units.Count(); index++) {
UnitClass * obj = Units.Ptr(index);
if (obj && !obj->IsInLimbo && obj->House == PlayerPtr && obj->Group == team) {
obj->Mark(MARK_CHANGE);
if (setform) {
long xc = Cell_X(Coord_Cell(obj->Center_Coord()));
long yc = Cell_Y(Coord_Cell(obj->Center_Coord()));
if (xc < minx) minx = xc;
if (xc > maxx) maxx = xc;
if (yc < miny) miny = yc;
if (yc > maxy) maxy = yc;
Command & Conquer Remastered post-launch patch Improvements to harvester resource finding logic. Don't allow the Advanced Comm Center to be capturable in skirmish or multiplayer. Increased failed pathfinding fudge factor. Buildings accept the Guard command if they can attack. Don't allow force capturing of ally structures. Fixes for laser Orcas in S3cr3t M1ss10n. Properly restore them after save. Reset Orcas after loads. Fixed flag animation rendering in CTF. Potentially fix a crash if aircraft are destroyed outside the map bounds. Fixed legacy Obelisk line rendering. Fix out-of-bounds crash in TD; issue was already fixed in RA. Disable capture flag on Commandos. Drop the flag when entering the limbo state. Fixed end game race condition, winning team ID is always sent before individual player win/lose messages. Fixed Chan spawn on SCB10EA. Don't show enter cursor for enemy units on refineries and repair pads. Changing right-click support for first put building on hold, and then subsequenct right-clicks to decrement that queue count for 1x or 5x; Then, 1x or 5x Left click will resume from hold. Don't debug reveal legacy rendering when a player is defeated. Fixed crash when loading saves of custom campaign maps. Reset harvester archived target when given a direct harvest order. Prevent NOD cargo planes from being force attacked. Fixed unit selection on load. Migrated queued repair pad functionality from RA to TD. Randomly animate infantry in area guard mode. Fixed crash accessing inactive objects. Added some walls in SCG08EB to prevent civilians from killing themselves. TD + RA: Audio: Overiding "Our base is under attack" cooldown timing from legacy from 2 minutes to 30 seconds, so it will be heard 4x as often. Fixed adjacent cell out-of-bounds crash issues. Kill player on disconnect Fixed and improved build time calculations to be consistent between TD and RA. Don't show health bars for cloaked Stealth Tanks in the legacy renderer. Fix selection of individual control groups after mixed selection. More adjustments to SCG08EB; switch C7 to C5, and add civilian AI to avoid Tiberium. Extra safety checks for units that have no weapons and aircraft that can't hunt. Fix loading of multiple infantry onto an APC. Additional safety checks for invalid coordinates. Prevent units from being instantly repaired. Fix map passability. Fail Allied mission 5B if the spy re-boards the starting transport (matches 5A and 5C behavior). Fixed multiplayer formation move causing units to move at light speed. Ignore movement destination checks if a unit is part of a mission-driven team. Fix buffer overrun crash. Ignore mines when determining win conditions. Fixed river passability in Blue Lakes.
2020-06-23 04:43:21 +12:00
if (obj->Class->MaxSpeed < team_form_data.TeamMaxSpeed[team]) {
team_form_data.TeamMaxSpeed[team] = obj->Class->MaxSpeed;
team_form_data.TeamSpeed[team] = obj->Class->Speed;
}
} else {
obj->XFormOffset = obj->YFormOffset = (int)0x80000000;
}
}
}
for (index = 0; index < Infantry.Count(); index++) {
InfantryClass * obj = Infantry.Ptr(index);
if (obj && !obj->IsInLimbo && obj->House == PlayerPtr && obj->Group == team) {
obj->Mark(MARK_CHANGE);
if (setform) {
long xc = Cell_X(Coord_Cell(obj->Center_Coord()));
long yc = Cell_Y(Coord_Cell(obj->Center_Coord()));
if (xc < minx) minx = xc;
if (xc > maxx) maxx = xc;
if (yc < miny) miny = yc;
if (yc > maxy) maxy = yc;
Command & Conquer Remastered post-launch patch Improvements to harvester resource finding logic. Don't allow the Advanced Comm Center to be capturable in skirmish or multiplayer. Increased failed pathfinding fudge factor. Buildings accept the Guard command if they can attack. Don't allow force capturing of ally structures. Fixes for laser Orcas in S3cr3t M1ss10n. Properly restore them after save. Reset Orcas after loads. Fixed flag animation rendering in CTF. Potentially fix a crash if aircraft are destroyed outside the map bounds. Fixed legacy Obelisk line rendering. Fix out-of-bounds crash in TD; issue was already fixed in RA. Disable capture flag on Commandos. Drop the flag when entering the limbo state. Fixed end game race condition, winning team ID is always sent before individual player win/lose messages. Fixed Chan spawn on SCB10EA. Don't show enter cursor for enemy units on refineries and repair pads. Changing right-click support for first put building on hold, and then subsequenct right-clicks to decrement that queue count for 1x or 5x; Then, 1x or 5x Left click will resume from hold. Don't debug reveal legacy rendering when a player is defeated. Fixed crash when loading saves of custom campaign maps. Reset harvester archived target when given a direct harvest order. Prevent NOD cargo planes from being force attacked. Fixed unit selection on load. Migrated queued repair pad functionality from RA to TD. Randomly animate infantry in area guard mode. Fixed crash accessing inactive objects. Added some walls in SCG08EB to prevent civilians from killing themselves. TD + RA: Audio: Overiding "Our base is under attack" cooldown timing from legacy from 2 minutes to 30 seconds, so it will be heard 4x as often. Fixed adjacent cell out-of-bounds crash issues. Kill player on disconnect Fixed and improved build time calculations to be consistent between TD and RA. Don't show health bars for cloaked Stealth Tanks in the legacy renderer. Fix selection of individual control groups after mixed selection. More adjustments to SCG08EB; switch C7 to C5, and add civilian AI to avoid Tiberium. Extra safety checks for units that have no weapons and aircraft that can't hunt. Fix loading of multiple infantry onto an APC. Additional safety checks for invalid coordinates. Prevent units from being instantly repaired. Fix map passability. Fail Allied mission 5B if the spy re-boards the starting transport (matches 5A and 5C behavior). Fixed multiplayer formation move causing units to move at light speed. Ignore movement destination checks if a unit is part of a mission-driven team. Fix buffer overrun crash. Ignore mines when determining win conditions. Fixed river passability in Blue Lakes.
2020-06-23 04:43:21 +12:00
if (obj->Class->MaxSpeed < team_form_data.TeamMaxSpeed[team]) {
team_form_data.TeamMaxSpeed[team] = obj->Class->MaxSpeed;
}
} else {
obj->XFormOffset = obj->YFormOffset = (int)0x80000000;
}
}
}
for (index = 0; index < Vessels.Count(); index++) {
VesselClass * obj = Vessels.Ptr(index);
if (obj && !obj->IsInLimbo && obj->House == PlayerPtr && obj->Group == team) {
obj->Mark(MARK_CHANGE);
if (setform) {
long xc = Cell_X(Coord_Cell(obj->Center_Coord()));
long yc = Cell_Y(Coord_Cell(obj->Center_Coord()));
if (xc < minx) minx = xc;
if (xc > maxx) maxx = xc;
if (yc < miny) miny = yc;
if (yc > maxy) maxy = yc;
Command & Conquer Remastered post-launch patch Improvements to harvester resource finding logic. Don't allow the Advanced Comm Center to be capturable in skirmish or multiplayer. Increased failed pathfinding fudge factor. Buildings accept the Guard command if they can attack. Don't allow force capturing of ally structures. Fixes for laser Orcas in S3cr3t M1ss10n. Properly restore them after save. Reset Orcas after loads. Fixed flag animation rendering in CTF. Potentially fix a crash if aircraft are destroyed outside the map bounds. Fixed legacy Obelisk line rendering. Fix out-of-bounds crash in TD; issue was already fixed in RA. Disable capture flag on Commandos. Drop the flag when entering the limbo state. Fixed end game race condition, winning team ID is always sent before individual player win/lose messages. Fixed Chan spawn on SCB10EA. Don't show enter cursor for enemy units on refineries and repair pads. Changing right-click support for first put building on hold, and then subsequenct right-clicks to decrement that queue count for 1x or 5x; Then, 1x or 5x Left click will resume from hold. Don't debug reveal legacy rendering when a player is defeated. Fixed crash when loading saves of custom campaign maps. Reset harvester archived target when given a direct harvest order. Prevent NOD cargo planes from being force attacked. Fixed unit selection on load. Migrated queued repair pad functionality from RA to TD. Randomly animate infantry in area guard mode. Fixed crash accessing inactive objects. Added some walls in SCG08EB to prevent civilians from killing themselves. TD + RA: Audio: Overiding "Our base is under attack" cooldown timing from legacy from 2 minutes to 30 seconds, so it will be heard 4x as often. Fixed adjacent cell out-of-bounds crash issues. Kill player on disconnect Fixed and improved build time calculations to be consistent between TD and RA. Don't show health bars for cloaked Stealth Tanks in the legacy renderer. Fix selection of individual control groups after mixed selection. More adjustments to SCG08EB; switch C7 to C5, and add civilian AI to avoid Tiberium. Extra safety checks for units that have no weapons and aircraft that can't hunt. Fix loading of multiple infantry onto an APC. Additional safety checks for invalid coordinates. Prevent units from being instantly repaired. Fix map passability. Fail Allied mission 5B if the spy re-boards the starting transport (matches 5A and 5C behavior). Fixed multiplayer formation move causing units to move at light speed. Ignore movement destination checks if a unit is part of a mission-driven team. Fix buffer overrun crash. Ignore mines when determining win conditions. Fixed river passability in Blue Lakes.
2020-06-23 04:43:21 +12:00
if (obj->Class->MaxSpeed < team_form_data.TeamMaxSpeed[team]) {
team_form_data.TeamMaxSpeed[team] = obj->Class->MaxSpeed;
}
} else {
obj->XFormOffset = obj->YFormOffset = 0x80000000UL;
}
}
}
/*
** All the units have been counted to find the bounding rectangle and
** center of the formation, or to clear their offsets. Now, if we're to
** set them into formation, proceed to do so. Otherwise, bail.
*/
if (setform) {
int centerx = (int)((maxx - minx)/2)+minx;
int centery = (int)((maxy - miny)/2)+miny;
for (index = 0; index < Units.Count(); index++) {
UnitClass * obj = Units.Ptr(index);
if (obj && !obj->IsInLimbo && obj->House == PlayerPtr && obj->Group == team) {
long xc = Cell_X(Coord_Cell(obj->Center_Coord()));
long yc = Cell_Y(Coord_Cell(obj->Center_Coord()));
obj->XFormOffset = xc - centerx;
obj->YFormOffset = yc - centery;
}
}
for (index = 0; index < Infantry.Count(); index++) {
InfantryClass * obj = Infantry.Ptr(index);
if (obj && !obj->IsInLimbo && obj->House == PlayerPtr && obj->Group == team ) {
long xc = Cell_X(Coord_Cell(obj->Center_Coord()));
long yc = Cell_Y(Coord_Cell(obj->Center_Coord()));
obj->XFormOffset = xc - centerx;
obj->YFormOffset = yc - centery;
}
}
for (index = 0; index < Vessels.Count(); index++) {
VesselClass * obj = Vessels.Ptr(index);
if (obj && !obj->IsInLimbo && obj->House == PlayerPtr && obj->Group == team ) {
long xc = Cell_X(Coord_Cell(obj->Center_Coord()));
long yc = Cell_Y(Coord_Cell(obj->Center_Coord()));
obj->XFormOffset = xc - centerx;
obj->YFormOffset = yc - centery;
}
}
}
}
/**************************************************************************************************
* CNC_Handle_Debug_Request -- Process a debug input request
*
* In:
*
*
* Out:
*
*
* History: 1/7/2019 5:20PM - ST
**************************************************************************************************/
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)
{
if (!DLLExportClass::Set_Player_Context(player_id)) {
return;
}
switch (debug_request_type) {
case DEBUG_REQUEST_SPAWN_OBJECT:
DLLExportClass::Debug_Spawn_Unit(object_name, x, y, enemy);
break;
case DEBUG_REQUEST_FORCE_CRASH:
Debug_Force_Crash = true;
break;
case DEBUG_REQUEST_KILL_OBJECT:
if (strcmp(object_name, "HEAL") == 0) {
DLLExportClass::Debug_Heal_Unit(x, y);
}
else {
DLLExportClass::Debug_Kill_Unit(x, y);
}
break;
case DEBUG_REQUEST_END_GAME:
{
bool win = true;
const char lose[] = "LOSE";
if (strcmp(lose, object_name) == 0) {
win = false;
}
PlayerWins = win;
PlayerLoses = !win;
}
break;
case DEBUG_REQUEST_UNSHROUD:
Debug_Unshroud = unshroud;
Map.Flag_To_Redraw(true);
break;
case DEBUG_REQUEST_SUPERWEAPON_RECHARGE:
for (int i = 0; i < SPC_COUNT; ++i)
{
PlayerPtr->SuperWeapon[i].Forced_Charge(true);
}
break;
case DEBUG_REQUEST_END_PRODUCTION:
{
for (int index = 0; index < Factories.Count(); index++) {
FactoryClass* factory = Factories.Ptr(index);
if (factory->Get_House()->IsHuman) {
Factories.Ptr(index)->Force_Complete();
}
}
}
break;
case DEBUG_REQUEST_ADD_RESOURCES:
{
if (object_name) {
int amount = atoi(object_name);
PlayerPtr->Credits += amount;
if (PlayerPtr->Credits < 0) {
PlayerPtr->Credits = 0;
}
}
}
break;
case DEBUG_REQUEST_UNLOCK_BUILDABLES:
PlayerPtr->DebugUnlockBuildables = !PlayerPtr->DebugUnlockBuildables;
PlayerPtr->IsRecalcNeeded = true;
break;
case DEBUG_REQUEST_SET_GLOBAL_FLAG:
Scen.Set_Global_To(x, true);
break;
default:
break;
}
}
/**************************************************************************************************
* DLLExportClass::Debug_Spawn_All -- Debug spawn all buildable units and structures
*
* In: Object to unlimbo , x & y cell positions
*
*
* Out: True if unlimbo succeeded
*
*
*
* History: 1/22/2020 2:57PM - ST
**************************************************************************************************/
bool DLLExportClass::Try_Debug_Spawn_Unlimbo(TechnoClass *techno, int &cell_x, int &cell_y)
{
if (techno) {
int map_cell_x = Map.MapCellX;
int map_cell_y = Map.MapCellY;
int map_cell_right = map_cell_x + Map.MapCellWidth;
int map_cell_bottom = map_cell_y + Map.MapCellHeight;
map_cell_right = min(map_cell_right, cell_x + 26); // Generally try to prevent the objects from spawing off the right of the screen
int try_x = cell_x;
int try_y = cell_y;
while (try_y < map_cell_bottom) {
CELL cell = XY_Cell(try_x, try_y);
if (techno->Unlimbo(Cell_Coord(cell))) {
try_x++;
if (try_x > map_cell_right - 2) {
try_x = cell_x; //map_cell_x + 2;
try_y++;
}
cell_x = try_x;
cell_y = try_y;
return true;
}
try_x++;
if (try_x > map_cell_right - 2) {
try_x = cell_x; //map_cell_x + 2;
try_y++;
}
}
cell_x = try_x;
cell_y = try_y;
}
return false;
}
/**************************************************************************************************
* DLLExportClass::Debug_Spawn_All -- Debug spawn all buildable units and structures
*
* In:
*
* Out:
*
*
*
* History: 1/22/2020 2:57PM - ST
**************************************************************************************************/
void DLLExportClass::Debug_Spawn_All(int x, int y)
{
int map_cell_x = Map.MapCellX;
int map_cell_y = Map.MapCellY;
int map_cell_bottom = map_cell_y + Map.MapCellHeight;
int origin_x = map_cell_x + 2;
int origin_y = map_cell_y + 2;
if (x != 0 || y != 0) {
CELL screen_cell = Coord_Cell(Map.Pixel_To_Coord(x, y));
origin_x = Cell_X(screen_cell);
origin_y = Cell_Y(screen_cell);
}
int try_x = origin_x;
int try_y = origin_y;
HousesType house = PlayerPtr->Class->House;
for (StructType sindex = STRUCT_FIRST; sindex < STRUCT_COUNT; sindex++) {
BuildingTypeClass const & building_type = BuildingTypeClass::As_Reference(sindex);
if (building_type.Get_Ownable() && building_type.Level != -1) {
BuildingClass * building = new BuildingClass(building_type, house);
if (building) {
try_x = origin_x;
try_y = origin_y;
while (try_y < map_cell_bottom) {
if (Try_Debug_Spawn_Unlimbo(building, try_x, try_y)) {
break;
}
}
}
}
}
for (UnitType index = UNIT_FIRST; index < UNIT_COUNT; index++) {
UnitTypeClass const & unit_type = UnitTypeClass::As_Reference(index);
/*
** Fetch the sidebar cameo image for this building.
*/
if (unit_type.Get_Ownable() && unit_type.Level != -1) {
UnitClass * unit = (UnitClass*) unit_type.Create_One_Of(PlayerPtr);
if (unit) {
try_x = origin_x;
try_y = origin_y;
while (try_y < map_cell_bottom) {
if (Try_Debug_Spawn_Unlimbo(unit, try_x, try_y)) {
break;
}
}
}
}
}
for (InfantryType index = INFANTRY_FIRST; index < INFANTRY_COUNT; index++) {
InfantryTypeClass const &infantry_type = InfantryTypeClass::As_Reference(index);
/*
** Fetch the sidebar cameo image for this building.
*/
if (infantry_type.Get_Ownable() && infantry_type.Level != -1) {
InfantryClass * inf = (InfantryClass*) infantry_type.Create_One_Of(PlayerPtr);
if (inf) {
try_x = origin_x;
try_y = origin_y;
while (try_y < map_cell_bottom) {
if (Try_Debug_Spawn_Unlimbo(inf, try_x, try_y)) {
break;
}
}
}
}
}
for (AircraftType index = AIRCRAFT_FIRST; index < AIRCRAFT_COUNT; index++) {
AircraftTypeClass const &aircraft_type = AircraftTypeClass::As_Reference(index);
/*
** Fetch the sidebar cameo image for this building.
*/
if (aircraft_type.Get_Ownable() && aircraft_type.Level != -1) {
AircraftClass * air = (AircraftClass*) aircraft_type.Create_One_Of(PlayerPtr);
if (air) {
try_x = origin_x;
try_y = origin_y;
while (try_y < map_cell_bottom) {
if (Try_Debug_Spawn_Unlimbo(air, try_x, try_y)) {
break;
}
}
}
}
}
for (VesselType index = VESSEL_FIRST; index < VESSEL_COUNT; index++) {
VesselTypeClass const &vessel_type = VesselTypeClass::As_Reference(index);
/*
** Fetch the sidebar cameo image for this building.
*/
if (vessel_type.Get_Ownable() && vessel_type.Level != -1) {
VesselClass * boat = (VesselClass*) vessel_type.Create_One_Of(PlayerPtr);
if (boat) {
try_x = origin_x;
try_y = origin_y;
while (try_y < map_cell_bottom) {
if (Try_Debug_Spawn_Unlimbo(boat, try_x, try_y)) {
break;
}
}
}
}
}
}
/**************************************************************************************************
* DLLExportClass::Debug_Spawn_Unit -- Debug spawn a unit at the specified location
*
* In:
*
* Out:
*
*
*
* History: 3/14/2019 3:20PM - ST
**************************************************************************************************/
void DLLExportClass::Debug_Spawn_Unit(const char *object_name, int x, int y, bool enemy)
{
if (object_name == NULL) {
return;
}
if (strlen(object_name) == 0) {
return;
}
COORDINATE coord = Map.Pixel_To_Coord(x, y);
CELL cell = Coord_Cell(coord);
HousesType house = PlayerPtr->Class->House;
/*
** Place all?
*/
if (stricmp(object_name, "ALLOBJECTS") == 0) {
Debug_Spawn_All(x, y);
return;
}
/*
** If this is for the enemy, find the enemy with the most stuff
*/
if (enemy) {
unsigned max_count = 0;
for (int i = 0; i < Houses.Count(); ++i) {
const HouseClass* player = Houses.Ptr(i);
const unsigned count = player->CurUnits + player->CurBuildings + player->CurInfantry + player->CurVessels + player->CurAircraft;
if (!PlayerPtr->Is_Ally(player) && (count >= max_count)) {
house = player->Class->House;
max_count = count;
}
}
}
/*
** What is this thing?
*/
StructType structure_type = BuildingTypeClass::From_Name(object_name);
if (structure_type != STRUCT_NONE) {
BuildingClass * building = new BuildingClass(structure_type, house);
if (building) {
if (!building->Unlimbo(Cell_Coord(cell))) {
delete building;
}
}
#if (0)
Map.PendingObject = &BuildingTypeClass::As_Reference(structure_type);
Map.PendingHouse = PlayerPtr->ActLike;
Map.PendingObjectPtr = Map.PendingObject->Create_One_Of(PlayerPtr);
if (Map.PendingObjectPtr) {
Map.Set_Cursor_Pos();
Map.Set_Cursor_Shape(Map.PendingObject->Occupy_List());
//OutList.Add(EventClass(EventClass::PLACE, RTTI_BUILDING, (CELL)(cell + Map.ZoneOffset)));
}
#endif
return;
}
UnitType unit_type = UnitTypeClass::From_Name(object_name);
if (unit_type != UNIT_NONE) {
UnitClass * unit = new UnitClass(unit_type, house);
if (unit) {
unit->Unlimbo(Map.Pixel_To_Coord(x, y), DIR_N);
}
return;
}
InfantryType infantry_type = InfantryTypeClass::From_Name(object_name);
if (infantry_type != INFANTRY_NONE) {
InfantryClass * inf = new InfantryClass(infantry_type, house);
if (inf) {
inf->Unlimbo(Map.Pixel_To_Coord(x, y), DIR_N);
}
return;
}
AircraftType aircraft_type = AircraftTypeClass::From_Name(object_name);
if (aircraft_type != AIRCRAFT_NONE) {
AircraftClass * air = new AircraftClass(aircraft_type, house);
if (air) {
air->Unlimbo(Map.Pixel_To_Coord(x, y), DIR_N);
}
return;
}
VesselType vessel_type = VesselTypeClass::From_Name(object_name);
if (vessel_type != VESSEL_NONE) {
VesselClass *boat = new VesselClass(vessel_type, house);
if (boat != NULL) {
if (boat->Unlimbo(Map.Pixel_To_Coord(x, y), DIR_N)) {
boat->Enter_Idle_Mode();
} else {
delete boat;
}
}
}
OverlayType overlay_type = OverlayTypeClass::From_Name(object_name);
if (overlay_type != OVERLAY_NONE)
{
new OverlayClass(overlay_type, cell);
return;
}
}
/**************************************************************************************************
* DLLExportClass::Debug_Kill_Unit -- Kill a unit at the specified location
*
* In:
*
* Out:
*
*
*
* History: 8/19/2019 3:09PM - ST
**************************************************************************************************/
void DLLExportClass::Debug_Kill_Unit(int x, int y)
{
COORDINATE coord = Map.Pixel_To_Coord(x, y);
CELL cell = Coord_Cell(coord);
CellClass * cellptr = &Map[cell];
if (cellptr) {
ObjectClass *obj = cellptr->Cell_Object();
static const int debug_damage = 100; // 100 = Incremental damage
if (obj) {
int damage = debug_damage;
obj->Take_Damage(damage, 0, WARHEAD_HE, 0, true);
} else {
if (cellptr->Overlay != OVERLAY_NONE) {
OverlayTypeClass const * optr = &OverlayTypeClass::As_Reference(cellptr->Overlay);
if (optr->IsTiberium) {
cellptr->Reduce_Tiberium(1);
}
if (optr->IsWall) {
Map[cell].Reduce_Wall(debug_damage);
}
}
if (cellptr->TType == TEMPLATE_BRIDGE1 || cellptr->TType == TEMPLATE_BRIDGE2 ||
cellptr->TType == TEMPLATE_BRIDGE1H || cellptr->TType == TEMPLATE_BRIDGE2H ||
cellptr->TType == TEMPLATE_BRIDGE_1A || cellptr->TType == TEMPLATE_BRIDGE_1B ||
cellptr->TType == TEMPLATE_BRIDGE_2A || cellptr->TType == TEMPLATE_BRIDGE_2B ||
cellptr->TType == TEMPLATE_BRIDGE_3A || cellptr->TType == TEMPLATE_BRIDGE_3B) {
Map.Destroy_Bridge_At(cell);
}
}
}
}
void DLLExportClass::Debug_Heal_Unit(int x, int y)
{
COORDINATE coord = Map.Pixel_To_Coord(x, y);
CELL cell = Coord_Cell(coord);
CellClass * cellptr = &Map[cell];
if (cellptr) {
ObjectClass *obj = cellptr->Cell_Object();
if (obj) {
obj->Strength = obj->Class_Of().MaxStrength;
}
else {
if (cellptr->Overlay != OVERLAY_NONE) {
OverlayTypeClass const * optr = &OverlayTypeClass::As_Reference(cellptr->Overlay);
if (optr->IsTiberium) {
const int cellcount = (int)FACING_COUNT + 1;
CellClass* cells[cellcount];
cells[0] = cellptr;
for (FacingType index = FACING_N; index < FACING_COUNT; index++) {
Command & Conquer Remastered post-launch patch Improvements to harvester resource finding logic. Don't allow the Advanced Comm Center to be capturable in skirmish or multiplayer. Increased failed pathfinding fudge factor. Buildings accept the Guard command if they can attack. Don't allow force capturing of ally structures. Fixes for laser Orcas in S3cr3t M1ss10n. Properly restore them after save. Reset Orcas after loads. Fixed flag animation rendering in CTF. Potentially fix a crash if aircraft are destroyed outside the map bounds. Fixed legacy Obelisk line rendering. Fix out-of-bounds crash in TD; issue was already fixed in RA. Disable capture flag on Commandos. Drop the flag when entering the limbo state. Fixed end game race condition, winning team ID is always sent before individual player win/lose messages. Fixed Chan spawn on SCB10EA. Don't show enter cursor for enemy units on refineries and repair pads. Changing right-click support for first put building on hold, and then subsequenct right-clicks to decrement that queue count for 1x or 5x; Then, 1x or 5x Left click will resume from hold. Don't debug reveal legacy rendering when a player is defeated. Fixed crash when loading saves of custom campaign maps. Reset harvester archived target when given a direct harvest order. Prevent NOD cargo planes from being force attacked. Fixed unit selection on load. Migrated queued repair pad functionality from RA to TD. Randomly animate infantry in area guard mode. Fixed crash accessing inactive objects. Added some walls in SCG08EB to prevent civilians from killing themselves. TD + RA: Audio: Overiding "Our base is under attack" cooldown timing from legacy from 2 minutes to 30 seconds, so it will be heard 4x as often. Fixed adjacent cell out-of-bounds crash issues. Kill player on disconnect Fixed and improved build time calculations to be consistent between TD and RA. Don't show health bars for cloaked Stealth Tanks in the legacy renderer. Fix selection of individual control groups after mixed selection. More adjustments to SCG08EB; switch C7 to C5, and add civilian AI to avoid Tiberium. Extra safety checks for units that have no weapons and aircraft that can't hunt. Fix loading of multiple infantry onto an APC. Additional safety checks for invalid coordinates. Prevent units from being instantly repaired. Fix map passability. Fail Allied mission 5B if the spy re-boards the starting transport (matches 5A and 5C behavior). Fixed multiplayer formation move causing units to move at light speed. Ignore movement destination checks if a unit is part of a mission-driven team. Fix buffer overrun crash. Ignore mines when determining win conditions. Fixed river passability in Blue Lakes.
2020-06-23 04:43:21 +12:00
cells[(int)index + 1] = cellptr->Adjacent_Cell(index);
}
for (int index = 0; index < cellcount; index++) {
CellClass * newcell = cells[index];
if (newcell != NULL) {
if (newcell->Can_Tiberium_Germinate()) {
switch (cellptr->Overlay) {
case OVERLAY_GOLD1:
case OVERLAY_GOLD2:
case OVERLAY_GOLD3:
case OVERLAY_GOLD4:
new OverlayClass(Random_Pick(OVERLAY_GOLD1, OVERLAY_GOLD4), newcell->Cell_Number());
newcell->OverlayData = 0;
break;
case OVERLAY_GEMS1:
case OVERLAY_GEMS2:
case OVERLAY_GEMS3:
case OVERLAY_GEMS4:
new OverlayClass(Random_Pick(OVERLAY_GEMS1, OVERLAY_GEMS4), newcell->Cell_Number());
newcell->OverlayData = 0;
break;
default:
break;
}
}
else if (newcell->Land_Type() == LAND_TIBERIUM) {
switch (newcell->Overlay) {
case OVERLAY_GOLD1:
case OVERLAY_GOLD2:
case OVERLAY_GOLD3:
case OVERLAY_GOLD4:
newcell->OverlayData = MIN(newcell->OverlayData + 1, 11);
newcell->Recalc_Attributes();
newcell->Redraw_Objects();
break;
case OVERLAY_GEMS1:
case OVERLAY_GEMS2:
case OVERLAY_GEMS3:
case OVERLAY_GEMS4:
newcell->OverlayData = MIN(newcell->OverlayData + 1, 2);
newcell->Recalc_Attributes();
newcell->Redraw_Objects();
break;
default:
break;
}
}
}
}
}
}
}
}
}
/**************************************************************************************************
* DLLExportClass::Legacy_Render_Enabled -- Is the legacy rendering enabled?
*
* In:
*
* Out:
*
*
*
* History: 4/15/2019 5:46PM - ST
**************************************************************************************************/
bool DLLExportClass::Legacy_Render_Enabled(void)
{
if (GAME_TO_PLAY == GAME_GLYPHX_MULTIPLAYER) {
unsigned int num_humans = 0U;
for (int i = 0; i < MULTIPLAYER_COUNT; ++i) {
HouseClass *player_ptr = HouseClass::As_Pointer(Session.Players[i]->Player.ID);
if (player_ptr && player_ptr->IsHuman) {
if (++num_humans > 1) break;
}
}
return num_humans < 2;
}
//return false;
return true;
}
/**************************************************************************************************
* DLLExportClass::Computer_Message -- Replacement for original Computer_Message function
*
* In:
*
* Out:
*
*
*
* History: 1/27/2020 1:42PM - ST
**************************************************************************************************/
void DLLExportClass::Computer_Message(bool last_player_taunt)
{
HousesType house;
HouseClass *ptr;
HouseClass *ai_players[MAX_PLAYERS];
int ai_player_count = 0;
/*------------------------------------------------------------------------
Find the computer house that the message will be from
------------------------------------------------------------------------*/
for (house = HOUSE_MULTI1; house < (HOUSE_MULTI1 + MULTIPLAYER_COUNT); house++) {
ptr = HouseClass::As_Pointer(house);
if (!ptr || ptr->IsHuman || ptr->IsDefeated) {
continue;
}
ai_players[ai_player_count++] = ptr;
}
if (ai_player_count) {
int ai_player_index = 0;
if (ai_player_count > 1) {
ai_player_index = IRandom(0, ai_player_count - 1);
}
int taunt_index;
if (last_player_taunt) {
taunt_index = 13;
} else {
taunt_index = IRandom(0,12);
}
On_Message(ai_players[ai_player_index], "", 15.0f, MESSAGE_TYPE_COMPUTER_TAUNT, taunt_index);
}
}
/**************************************************************************************************
* DLLExportClass::Set_Special_Key_Flags --
*
* In:
*
* Out:
*
*
*
* History: 6/27/2019 - JAS
**************************************************************************************************/
void DLLExportClass::Set_Special_Key_Flags(unsigned char special_key_flags)
{
SpecialKeyFlags[CurrentLocalPlayerIndex] = special_key_flags;
}
/**************************************************************************************************
* DLLExportClass::Clear_Special_Key_Flags --
*
* In:
*
* Out:
*
*
*
* History: 6/27/2019 - JAS
**************************************************************************************************/
void DLLExportClass::Clear_Special_Key_Flags()
{
SpecialKeyFlags[CurrentLocalPlayerIndex] = 0;
}
/**************************************************************************************************
* DLLExportClass::Get_Input_Key_State --
*
* In:
*
* Out:
*
*
*
* History: 6/27/2019 - JAS
**************************************************************************************************/
bool DLLExportClass::Get_Input_Key_State(KeyNumType key)
{
switch (key)
{
case KN_LCTRL:
return (SpecialKeyFlags[CurrentLocalPlayerIndex] & INPUT_SPECIAL_KEY_CTRL) != 0;
break;
case KN_LSHIFT:
return (SpecialKeyFlags[CurrentLocalPlayerIndex] & INPUT_SPECIAL_KEY_SHIFT) != 0;
break;
case KN_LALT:
return (SpecialKeyFlags[CurrentLocalPlayerIndex] & INPUT_SPECIAL_KEY_ALT) != 0;
break;
default:
break;
};
return false;
}
/**************************************************************************************************
* Get_Input_Key_State
*
* History: 6/27/2019 - JAS
**************************************************************************************************/
bool DLL_Export_Get_Input_Key_State(KeyNumType key)
{
return DLLExportClass::Get_Input_Key_State(key);
}
bool DLLSave(Pipe &file)
{
return DLLExportClass::Save(file);
}
bool DLLLoad(Straw &file)
{
return DLLExportClass::Load(file);
}
/**************************************************************************************************
* DLLExportClass::Save --
*
* In:
*
* Out:
*
*
*
* History: 9/10/2019 10:24AM - ST
**************************************************************************************************/
bool DLLExportClass::Save(Pipe & pipe)
{
/*
** Version first
*/
unsigned int version = CNC_DLL_API_VERSION;
pipe.Put(&version, sizeof(version));
pipe.Put(MultiplayerStartPositions, sizeof(MultiplayerStartPositions));
pipe.Put(GlyphxPlayerIDs, sizeof(GlyphxPlayerIDs));
pipe.Put(&GlyphXClientSidebarWidthInLeptons, sizeof(GlyphXClientSidebarWidthInLeptons));
pipe.Put(MPlayerIsHuman, sizeof(MPlayerIsHuman));
pipe.Put(MultiplayerStartPositions, sizeof(MultiplayerStartPositions));
pipe.Put(PlacementType, sizeof(PlacementType));
pipe.Put(&OverrideNewUnitsEnabled, sizeof(OverrideNewUnitsEnabled));
for (int i=0 ; i<MAX_PLAYERS ; i++) {
Sidebar_Glyphx_Save(pipe, &MultiplayerSidebars[i]);
int has_player = false;
if (i < Session.Players.Count() && Session.Players[i]) {
has_player = true;
pipe.Put(&has_player, sizeof(has_player));
pipe.Put(Session.Players[i], sizeof(NodeNameType));
} else {
pipe.Put(&has_player, sizeof(has_player));
}
}
pipe.Put(&Special, sizeof(Special));
/*
** Special case for MPSuperWeaponDisable - store negated value so it defaults to enabled
*/
bool not_allow_super_weapons = !MPSuperWeaponDisable;
pipe.Put(&not_allow_super_weapons, sizeof(not_allow_super_weapons));
/*
** Room for save game expansion
*/
unsigned char padding[4095];
memset(padding, 0, sizeof(padding));
pipe.Put(padding, sizeof(padding));
return true;
}
/**************************************************************************************************
* DLLExportClass::Load --
*
* In:
*
* Out:
*
*
*
* History: 9/10/2019 10:24AM - ST
**************************************************************************************************/
bool DLLExportClass::Load(Straw & file)
{
unsigned int version = 0;
if (file.Get(&version, sizeof(version)) != sizeof(version)) {
return false;
}
if (file.Get(MultiplayerStartPositions, sizeof(MultiplayerStartPositions)) != sizeof(MultiplayerStartPositions)) {
return false;
}
if (file.Get(GlyphxPlayerIDs, sizeof(GlyphxPlayerIDs)) != sizeof(GlyphxPlayerIDs)) {
return false;
}
if (file.Get(&GlyphXClientSidebarWidthInLeptons, sizeof(GlyphXClientSidebarWidthInLeptons)) != sizeof(GlyphXClientSidebarWidthInLeptons)) {
return false;
}
if (file.Get(MPlayerIsHuman, sizeof(MPlayerIsHuman)) != sizeof(MPlayerIsHuman)) {
return false;
}
if (file.Get(MultiplayerStartPositions, sizeof(MultiplayerStartPositions)) != sizeof(MultiplayerStartPositions)) {
return false;
}
if (file.Get(PlacementType, sizeof(PlacementType)) != sizeof(PlacementType)) {
return false;
}
if (file.Get(&OverrideNewUnitsEnabled, sizeof(OverrideNewUnitsEnabled)) != sizeof(OverrideNewUnitsEnabled)) {
return false;
}
if (Is_Aftermath_Installed()) {
if (Session.Type == GAME_SKIRMISH || Session.Type == GAME_GLYPHX_MULTIPLAYER) {
bAftermathMultiplayer = NewUnitsEnabled = OverrideNewUnitsEnabled;
}
}
Session.NumPlayers = 0;
for (int i=0 ; i<MAX_PLAYERS ; i++) {
Sidebar_Glyphx_Load(file, &MultiplayerSidebars[i]);
int has_player = false;
file.Get(&has_player, sizeof(has_player));
if (has_player) {
NodeNameType *who = new NodeNameType;
file.Get(who, sizeof(NodeNameType));
Session.Players.Add (who);
Session.NumPlayers++;
}
}
if (file.Get(&Special, sizeof(Special)) != sizeof(Special)) {
return false;
}
/*
** Restore backup
*/
if (SpecialBackup != NULL) {
memcpy(SpecialBackup, &Special, sizeof(SpecialClass));
}
/*
** Special case for MPSuperWeaponDisable - store negated value so it defaults to enabled
*/
bool not_allow_super_weapons = false;
if (file.Get(&not_allow_super_weapons, sizeof(not_allow_super_weapons)) != sizeof(not_allow_super_weapons)) {
return false;
}
MPSuperWeaponDisable = !not_allow_super_weapons;
unsigned char padding[4095];
if (file.Get(padding, sizeof(padding)) != sizeof(padding)) {
return false;
}
return true;
}
/**************************************************************************************************
* DLLExportClass::Code_Pointers --
*
* In:
*
* Out:
*
*
*
* History: 9/10/2019 10:24AM - ST
**************************************************************************************************/
void DLLExportClass::Code_Pointers(void)
{
for (int i=0 ; i<MAX_PLAYERS ; i++) {
Sidebar_Glyphx_Code_Pointers(&MultiplayerSidebars[i]);
if (PlacementType[i]) {
PlacementType[i] = (BuildingTypeClass *) PlacementType[i]->Type;
}
}
}
/**************************************************************************************************
* DLLExportClass::Decode_Pointers --
*
* In:
*
* Out:
*
*
*
* History: 9/10/2019 10:24AM - ST
**************************************************************************************************/
void DLLExportClass::Decode_Pointers(void)
{
for (int i=0 ; i<MAX_PLAYERS ; i++) {
Sidebar_Glyphx_Decode_Pointers(&MultiplayerSidebars[i]);
if (PlacementType[i]) {
StructType type = (StructType) reinterpret_cast<unsigned int>(PlacementType[i]);
PlacementType[i] = NULL;
if (type >= STRUCT_FIRST && type < STRUCT_COUNT) {
TechnoTypeClass const * tech = Fetch_Techno_Type(RTTI_BUILDINGTYPE, type);
if (tech) {
BuildingTypeClass* build_type = (BuildingTypeClass*)(tech);
if (build_type) {
PlacementType[i] = build_type;
}
}
}
}
}
}
void DLL_Code_Pointers(void)
{
DLLExportClass::Code_Pointers();
}
void DLL_Decode_Pointers(void)
{
DLLExportClass::Decode_Pointers();
}