September 16th patch update

DLL version incremented
Beacon functionality added
Support for loading screen match preview display
Placeholder handling of new key-bindable mod commands
This commit is contained in:
PG-SteveT 2020-09-16 10:03:04 -07:00
parent e37e174be1
commit fd05be35c1
68 changed files with 1313 additions and 267 deletions

28
CnCTDRAMapEditor.sln Normal file
View file

@ -0,0 +1,28 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.28307.1022
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CnCTDRAMapEditor", "CnCTDRAMapEditor\CnCTDRAMapEditor.csproj", "{397CEF00-8930-4EC8-B15F-F7CF7193FB22}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Gold|Any CPU = Gold|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{397CEF00-8930-4EC8-B15F-F7CF7193FB22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{397CEF00-8930-4EC8-B15F-F7CF7193FB22}.Debug|Any CPU.Build.0 = Debug|Any CPU
{397CEF00-8930-4EC8-B15F-F7CF7193FB22}.Gold|Any CPU.ActiveCfg = Gold|Any CPU
{397CEF00-8930-4EC8-B15F-F7CF7193FB22}.Gold|Any CPU.Build.0 = Gold|Any CPU
{397CEF00-8930-4EC8-B15F-F7CF7193FB22}.Release|Any CPU.ActiveCfg = Release|Any CPU
{397CEF00-8930-4EC8-B15F-F7CF7193FB22}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3A313796-A0F0-4740-98D6-037699DBC902}
EndGlobalSection
EndGlobal

View file

@ -2132,7 +2132,61 @@ static AnimTypeClass const Flag(
0, // Loop start frame number.
-1, // Ending frame of loop back.
-1, // Number of animation stages.
-1, // Number of times the animation loops.
-1, // Number of times the animation loops.
VOC_NONE, // Sound effect to play.
ANIM_NONE
);
static AnimTypeClass const Beacon(
ANIM_BEACON, // Animation number.
"MOVEFLSH", // Data name of animation.
21, // Maximum dimension of animation.
0, // Biggest animation stage.
true, // Theater specific art imagery?
false, // Normalized animation rate?
false, // Uses white translucent table?
false, // Scorches the ground?
false, // Forms a crater?
false, // Sticks to unit in square?
false, // Ground level animation?
false, // Translucent colors in this animation?
false, // Is this a flame thrower animation?
0, // Damage to apply per tick (fixed point).
1, // Delay between frames.
0, // Starting frame number.
0, // Loop start frame number.
-1, // Ending frame of loop back.
1, // Number of animation stages.
-1, // Number of times the animation loops.
VOC_NONE, // Sound effect to play.
ANIM_NONE,
-1, // Virtual stages
0x100, // Virtual scale
NULL, // Virtual name
ANIM_BEACON_VIRTUAL // Virtual anim
);
static AnimTypeClass const BeaconVirtual(
ANIM_BEACON_VIRTUAL, // Animation number.
"BEACON", // Data name of animation.
21, // Maximum dimension of animation.
0, // Biggest animation stage.
false, // Theater specific art imagery?
false, // Normalized animation rate?
false, // Uses white translucent table?
false, // Scorches the ground?
false, // Forms a crater?
false, // Sticks to unit in square?
false, // Ground level animation?
false, // Translucent colors in this animation?
false, // Is this a flame thrower animation?
0, // Damage to apply per tick (fixed point).
1, // Delay between frames.
0, // Starting frame number.
0, // Loop start frame number.
-1, // Ending frame of loop back.
1, // Number of animation stages.
-1, // Number of times the animation loops.
VOC_NONE, // Sound effect to play.
ANIM_NONE
);
@ -2450,6 +2504,7 @@ void AnimTypeClass::Init_Heap(void)
new AnimTypeClass(ParaBomb);
new AnimTypeClass(MineExp1);
new AnimTypeClass(Flag);
new AnimTypeClass(Beacon);
#ifdef FIXIT_ANTS
new AnimTypeClass(Ant1Death);
new AnimTypeClass(Ant2Death);
@ -2459,6 +2514,7 @@ void AnimTypeClass::Init_Heap(void)
new AnimTypeClass(Fire2Virtual);
new AnimTypeClass(Fire1Virtual);
new AnimTypeClass(Fire4Virtual);
new AnimTypeClass(BeaconVirtual);
}
/***********************************************************************************************
@ -2529,6 +2585,9 @@ void AnimTypeClass::Init(TheaterType theater)
((void const *&)anim.ImageData) = MFCD::Retrieve(fullname);
}
}
// Set up beacon image data manually since they're new animations only available in the virtual renderer
((void const *&)As_Reference(ANIM_BEACON_VIRTUAL).ImageData) = As_Reference(ANIM_BEACON).ImageData;
}
}

View file

@ -262,6 +262,8 @@ void AnimClass::Draw_It(int x, int y, WindowNumberType window) const
void const * remap = NULL;
ShapeFlags_Type flags = SHAPE_CENTER|SHAPE_WIN_REL;
bool alt = false;
int width = 0;
int height = 0;
/*
** Some animations require special fixups.
@ -277,6 +279,12 @@ void AnimClass::Draw_It(int x, int y, WindowNumberType window) const
transtable = Map.UnitShadow;
alt = true;
break;
case ANIM_BEACON_VIRTUAL:
width = 29;
height = 39;
flags = flags | SHAPE_BOTTOM | SHAPE_COMPACT;
break;
}
/*
@ -305,7 +313,7 @@ void AnimClass::Draw_It(int x, int y, WindowNumberType window) const
*/
if ((window == WINDOW_VIRTUAL) || (Fetch_Stage() < Class->Stages)) {
// Add 'this' parameter to call new shape draw intercept. ST - 5/22/2019
CC_Draw_Shape(this, shapefile, shapenum, x, y, window, flags, remap, transtable, DIR_N, Class->VirtualScale);
CC_Draw_Shape(this, shapefile, shapenum, x, y, window, flags, remap, transtable, DIR_N, Class->VirtualScale, width, height);
}
}
IsTheaterShape = false;
@ -553,7 +561,8 @@ AnimClass::AnimClass(AnimType animnum, COORDINATE coord, unsigned char timedelay
IsInvisible(false),
Delay(timedelay),
Accum(0),
AttachLayer(LAYER_NONE)
AttachLayer(LAYER_NONE),
KillTime(0ULL)
{
#ifdef VIC
if (Class->Stages == -1) {
@ -741,6 +750,19 @@ void AnimClass::AI(void)
}
}
/*
** Check the kill time.
*/
if (KillTime > 0ULL) {
FILETIME ft;
GetSystemTimeAsFileTime(&ft);
unsigned long long now = (unsigned long long)ft.dwLowDateTime + ((unsigned long long)ft.dwHighDateTime << 32ULL);
if (now >= KillTime) {
IsToDelete = true;
}
}
/*
** Delete this animation and bail early if the animation is flagged to be deleted
** immediately.
@ -1255,4 +1277,20 @@ void AnimClass::Do_Atom_Damage(HousesType ownerhouse, CELL cell)
//GamePalette.Set(FADE_PALETTE_SLOW, Call_Back); //TO_FIX. ST 5/8/2019
}
#endif
}
}
void AnimClass::Set_Owner(HousesType owner)
{
OwnerHouse = owner;
if (Target_Legal(VirtualAnimTarget)) {
As_Animation(VirtualAnimTarget)->Set_Owner(owner);
}
}
void AnimClass::Set_Visible_Flags(unsigned flags)
{
VisibleFlags = flags;
if (Target_Legal(VirtualAnimTarget)) {
As_Animation(VirtualAnimTarget)->Set_Visible_Flags(flags);
}
}

View file

@ -70,15 +70,17 @@ class AnimClass : public ObjectClass, public StageClass {
void Sort_Above(TARGET target);
void Make_Invisible(void) {IsInvisible = true;};
void Make_Visible(void) {IsInvisible = false;};
void Kill_At(unsigned long long kill_time) {KillTime = kill_time;}
static void Do_Atom_Damage(HousesType ownerhouse, CELL cell);
/*
** 2019/09/19 JAS
** Added functions for accessing which players can see this anim
*/
void Set_Visible_Flags(unsigned flags) { VisibleFlags = flags; }
void Set_Visible_Flags(unsigned flags);
unsigned Get_Visible_Flags() const { return (Delay == 0) ? VisibleFlags : 0; }
virtual void Set_Owner(HousesType owner);
virtual HousesType Owner(void) const {return OwnerHouse;};
virtual bool Can_Place_Here(COORDINATE ) const {return true;}
virtual bool Mark(MarkType mark=MARK_CHANGE);
@ -179,10 +181,15 @@ class AnimClass : public ObjectClass, public StageClass {
*/
TARGET VirtualAnimTarget;
/*
** Real-time point to kill this animation.
*/
unsigned long long KillTime;
/*
** Some additional padding in case we need to add data to the class and maintain backwards compatibility for save/load
*/
unsigned char SaveLoadPadding[32];
unsigned char SaveLoadPadding[24];
};

View file

@ -281,6 +281,8 @@ struct SoundEffectNameStruct {
{"MADEXPLO", 20, IN_NOVAR}, // VOC_MAD_EXPLODE MAD tank explodes
{"SHKTROP1", 20, IN_NOVAR}, // VOC_SHOCK_TROOP1 Shock Trooper fires
{"BEACON", 10, IN_NOVAR}, // VOC_BEACON Beacon sound.
#endif
};

View file

@ -1850,7 +1850,7 @@ void BuildingClass::Active_Click_With(ActionType action, CELL cell)
OutList.Add(EventClass(EventClass::SELL, TargetClass(this)));
COORDINATE coord = Map.Pixel_To_Coord(Get_Mouse_X(), Get_Mouse_Y());
OutList.Add(EventClass(ANIM_MOVE_FLASH, PlayerPtr->Class->House, coord));
OutList.Add(EventClass(ANIM_MOVE_FLASH, PlayerPtr->Class->House, coord, 1 << PlayerPtr->Class->House));
}
}
@ -3152,7 +3152,7 @@ bool BuildingClass::Captured(HouseClass * newowner)
assert(Buildings.ID(this) == ID);
assert(IsActive);
if (Class->IsCaptureable && newowner != House) {
if (Can_Capture() && newowner != House) {
#ifdef TOFIX
switch (Owner()) {
case HOUSE_GOOD:
@ -3457,6 +3457,19 @@ bool BuildingClass::Can_Demolish_Unit(void) const
}
bool BuildingClass::Can_Capture(void) const
{
bool can_capture = Class->IsCaptureable && Mission != MISSION_DECONSTRUCTION;
// Only allow capturing of multiplayer-owned structures
if (Session.Type != GAME_NORMAL) {
can_capture &= House->Class->House >= HOUSE_MULTI1 && House->Class->House <= HOUSE_MULTI8;
}
return(can_capture);
}
/***********************************************************************************************
* BuildingClass::Mission_Guard -- Handles guard mission for combat buildings. *
* *
@ -3759,6 +3772,7 @@ int BuildingClass::Mission_Deconstruction(void)
Status = DURING;
Begin_Mode(BSTATE_CONSTRUCTION);
IsReadyToCommence = false;
IsSurvivorless = true;
break;
}
Transmit_Message(RADIO_RUN_AWAY);

View file

@ -246,6 +246,7 @@ class BuildingClass : public TechnoClass
virtual ActionType What_Action(CELL cell) const;
virtual bool Can_Demolish(void) const;
virtual bool Can_Demolish_Unit(void) const;
virtual bool Can_Capture(void) const;
virtual ObjectTypeClass const & Class_Of(void) const {return *Class;};
virtual DirType Fire_Direction(void) const;
virtual short const * Overlap_List(bool redraw=false) const;

View file

@ -1075,7 +1075,7 @@ void BulletClass::Bullet_Explodes(bool forced)
//
else if (aptr && anim == ANIM_ATOM_BLAST && aptr->OwnerHouse == HOUSE_NONE) {
if (Payback && Payback->House && Payback->House->Class) {
aptr->OwnerHouse = Payback->House->Class->House;
aptr->Set_Owner(Payback->House->Class->House);
}
}
}

View file

@ -2818,7 +2818,7 @@ void CellClass::Flag_Create(void)
CTFFlag = new AnimClass(ANIM_FLAG, Cell_Coord());
}
assert(CTFFlag != NULL);
CTFFlag->OwnerHouse = Owner;
CTFFlag->Set_Owner(Owner);
}
}

View file

@ -3430,11 +3430,11 @@ extern void DLL_Draw_Intercept(int shape_number, int x, int y, int width, int he
extern void DLL_Draw_Pip_Intercept(const ObjectClass* object, int pip);
void CC_Draw_Shape(void const * shapefile, int shapenum, int x, int y, WindowNumberType window, ShapeFlags_Type flags, void const * fadingdata, void const * ghostdata, DirType rotation);
void CC_Draw_Shape(const ObjectClass *object, void const * shapefile, int shapenum, int x, int y, WindowNumberType window, ShapeFlags_Type flags, void const * fadingdata, void const * ghostdata, DirType rotation, long virtualscale)
void CC_Draw_Shape(const ObjectClass *object, void const * shapefile, int shapenum, int x, int y, WindowNumberType window, ShapeFlags_Type flags, void const * fadingdata, void const * ghostdata, DirType rotation, long virtualscale, int width, int height)
{
if (window == WINDOW_VIRTUAL) {
int width = Get_Build_Frame_Width(shapefile);
int height = Get_Build_Frame_Height(shapefile);
if (width == 0) width = Get_Build_Frame_Width(shapefile);
if (height == 0) height = Get_Build_Frame_Height(shapefile);
DLL_Draw_Intercept(shapenum, x, y, width, height, (int)flags, object, rotation, virtualscale, NULL, HOUSE_NONE);
return;
}

View file

@ -2324,6 +2324,7 @@ typedef enum AnimType : char {
ANIM_PARA_BOMB,
ANIM_MINE_EXP1,
ANIM_FLAG,
ANIM_BEACON,
#ifdef FIXIT_ANTS
ANIM_ANT1_DEATH,
@ -2335,6 +2336,7 @@ typedef enum AnimType : char {
ANIM_FIRE_MED_VIRTUAL, // Medium flame animation.
ANIM_FIRE_MED2_VIRTUAL, // Medium flame animation (oranger).
ANIM_FIRE_TINY_VIRTUAL, // Very tiny flames.
ANIM_BEACON_VIRTUAL, // Beacon (virtual).
ANIM_COUNT,
ANIM_FIRST=0
@ -3333,6 +3335,8 @@ typedef enum VocType : short {
VOC_MAD_EXPLODE,
VOC_SHOCK_TROOP1,
VOC_BEACON,
#endif
VOC_COUNT,
VOC_FIRST=0

View file

@ -996,7 +996,7 @@ void DisplayClass::Cursor_Mark(CELL pos, bool on)
CELL const * ptr;
CellClass * cellptr;
if (pos == -1) return;
if ((unsigned)pos >= MAP_CELL_TOTAL) return;
/*
** For every cell in the CursorSize list, invoke its Redraw_Objects and
@ -2916,6 +2916,7 @@ void DisplayClass::Select_These(COORDINATE coord1, COORDINATE coord2, bool addit
HouseClass * hptr = HouseClass::As_Pointer(obj->Owner());
if ( obj->Class_Of().IsSelectable &&
obj->What_Am_I() != RTTI_BUILDING &&
(!obj->Is_Techno() || !((TechnoClass*)obj)->Is_Cloaked(PlayerPtr)) &&
x >= x1 && x <= x2 && y >= y1 && y <= y2) {
bool old_allow_voice = AllowVoice;
bool is_player_controlled = (hptr != NULL) && hptr->IsPlayerControl;
@ -2942,6 +2943,7 @@ void DisplayClass::Select_These(COORDINATE coord1, COORDINATE coord2, bool addit
** Only try to select objects that are allowed to be selected, and are within the bounding box.
*/
if ( aircraft->Class->IsSelectable &&
!aircraft->Is_Cloaked(PlayerPtr) &&
!aircraft->Is_Selected_By_Player() &&
x >= x1 && x <= x2 && y >= y1 && y <= y2) {
bool old_allow_voice = AllowVoice;

View file

@ -141,6 +141,7 @@ extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Sidebar_Request(Sidebar
extern "C" __declspec(dllexport) void __cdecl CNC_Handle_SuperWeapon_Request(SuperWeaponRequestEnum request_type, uint64 player_id, int buildable_type, int buildable_id, int x1, int y1);
extern "C" __declspec(dllexport) void __cdecl CNC_Handle_ControlGroup_Request(ControlGroupRequestEnum request_type, uint64 player_id, unsigned char control_group_index);
extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Debug_Request(DebugRequestEnum debug_request_type, uint64 player_id, const char *object_name, int x, int y, bool unshroud, bool enemy);
extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Beacon_Request(BeaconRequestEnum beacon_request_type, uint64 player_id, int pixel_x, int pixel_y);
extern "C" __declspec(dllexport) bool __cdecl CNC_Set_Multiplayer_Data(int scenario_index, CNCMultiplayerOptionsStruct &game_options, int num_players, CNCPlayerInfoStruct *player_list, int max_players);
extern "C" __declspec(dllexport) bool __cdecl CNC_Clear_Object_Selection(uint64 player_id);
extern "C" __declspec(dllexport) bool __cdecl CNC_Select_Object(uint64 player_id, int object_type_id, int object_to_select_id);
@ -150,6 +151,7 @@ extern "C" __declspec(dllexport) void __cdecl CNC_Restore_Carryover_Objects(cons
extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Player_Switch_To_AI(uint64 player_id);
extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Human_Team_Wins(uint64 player_id);
extern "C" __declspec(dllexport) void __cdecl CNC_Start_Mission_Timer(int time);
extern "C" __declspec(dllexport) bool __cdecl CNC_Get_Start_Game_Info(uint64 player_id, int &start_location_waypoint_index);
@ -418,14 +420,13 @@ void Display_Briefing_Text_GlyphX()
void On_Sound_Effect(int sound_index, int variation, COORDINATE coord, int house)
{
// MBL 06.17.2019
int voc = sound_index;
if (voc == VOC_NONE)
{
return;
}
// MBL 06.17.2019 - Borrowed from RedAlert\AUDIO.CPP Sound_Effect()
// Borrowed from RedAlert\AUDIO.CPP Sound_Effect()
//
#if 1
/*
@ -487,14 +488,13 @@ void On_Sound_Effect(int sound_index, int variation, COORDINATE coord, int house
#if 0
// MBL 02.26.2019
int voc = sound_index;
if (voc == VOC_NONE)
{
return;
}
// MBL 02.26.2019 - Borrowed from AUDIO.CPP Sound_Effect()
// Borrowed from AUDIO.CPP Sound_Effect()
//
char const * ext = ""; // ".AUD";
#ifdef TIBERIAN_DAWN
@ -531,10 +531,8 @@ void On_Sound_Effect(int sound_index, int variation, COORDINATE coord, int house
}
// void On_Speech(int speech_index) // MBL 02.06.2020
void On_Speech(int speech_index, HouseClass *house)
{
// DLLExportClass::On_Speech(PlayerPtr, speech_index); // MBL 02.06.2020
if (house == NULL) {
DLLExportClass::On_Speech(PlayerPtr, speech_index);
}
@ -622,7 +620,6 @@ extern "C" __declspec(dllexport) void __cdecl CNC_Init(const char *command_line,
DLL_Startup(command_line);
// MBL
DLLExportClass::Set_Event_Callback( event_callback );
DLLExportClass::Init();
@ -1034,7 +1031,7 @@ void GlyphX_Assign_Houses(void)
UseGlyphXStartLocations = true;
}
}
if (!preassigned) {
if (!preassigned && i < MAX_PLAYERS) {
random_start_locations[num_random_start_locations] = num_start_locations;
num_random_start_locations++;
}
@ -1943,13 +1940,13 @@ extern "C" __declspec(dllexport) bool __cdecl CNC_Save_Load(bool save, const cha
result = Load_Game(file_path_and_name);
// MBL 07.21.2020
if (result == false)
{
return false;
}
DLLExportClass::Set_Player_Context(DLLExportClass::GlyphxPlayerIDs[0], true);
DLLExportClass::Cancel_Placement(DLLExportClass::GlyphxPlayerIDs[0], -1, -1);
Set_Logic_Page(SeenBuff);
VisiblePage.Clear();
Map.Flag_To_Redraw(true);
@ -2148,6 +2145,40 @@ extern "C" __declspec(dllexport) void __cdecl CNC_Start_Mission_Timer(int time)
}
/**************************************************************************************************
* CNC_Get_Start_Game_Info
*
* History: 8/31/2020 11:37AM - ST
**************************************************************************************************/
extern "C" __declspec(dllexport) bool __cdecl CNC_Get_Start_Game_Info(uint64 player_id, int &start_location_waypoint_index)
{
start_location_waypoint_index = 0;
if (!DLLExportClass::Set_Player_Context(player_id)) {
return false;
}
start_location_waypoint_index = PlayerPtr->StartLocationOverride;
return true;
}
/**************************************************************************************************
* Is_Legacy_Render_Enabled -- Is the legacy rendering enabled?
*
* In:
*
* Out: True if only one human player
*
*
*
* History: 8/25/2020 5:55PM - ST
**************************************************************************************************/
bool Is_Legacy_Render_Enabled(void)
{
return DLLExportClass::Legacy_Render_Enabled();
}
/**************************************************************************************************
* DLLExportClass::Init -- Init the class
*
@ -4096,6 +4127,31 @@ extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Input(InputRequestEnum
break;
}
// MBL 09.08.2020 - Mod Support
case INPUT_REQUEST_MOD_GAME_COMMAND_1_AT_POSITION:
case INPUT_REQUEST_MOD_GAME_COMMAND_2_AT_POSITION:
case INPUT_REQUEST_MOD_GAME_COMMAND_3_AT_POSITION:
case INPUT_REQUEST_MOD_GAME_COMMAND_4_AT_POSITION:
{
DLLExportClass::Adjust_Internal_View();
DLLForceMouseX = x1;
DLLForceMouseY = y1;
Keyboard->MouseQX = x1;
Keyboard->MouseQY = y1;
COORDINATE coord = Map.Pixel_To_Coord(x1, y1);
CELL cell = Coord_Cell(coord);
if (Map.Pixel_To_Coord(x1, y1))
{
// TBD: For our ever-awesome Community Modders!
//
// PlayerPtr->Handle_Mod_Game_Command(cell, input_event - INPUT_REQUEST_MOD_GAME_COMMAND_1_AT_POSITION);
}
break;
}
default:
break;
}
@ -4220,7 +4276,7 @@ extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Sidebar_Request(Sidebar
switch (request_type) {
// MBL 06.02.2020 - Changing right-click support for first put building on hold, and then subsequenct right-clicks to decrement that queue count for 1x or 5x; Then, 1x or 5x Left click will resume from hold
// Changing right-click support for first put building on hold, and then subsequenct right-clicks to decrement that queue count for 1x or 5x; Then, 1x or 5x Left click will resume from hold
// Handle and fall through to start construction (from hold state) below
case SIDEBAR_REQUEST_START_CONSTRUCTION_MULTI:
@ -4461,7 +4517,7 @@ bool DLLExportClass::Get_Sidebar_State(uint64 player_id, unsigned char *buffer_i
sidebar_entry.SuperWeaponType = SW_NONE;
if (tech) {
sidebar_entry.Cost = tech->Cost * PlayerPtr->CostBias; // MBL: If this gets modified, also modify below for skirmish and multiplayer
sidebar_entry.Cost = tech->Cost * PlayerPtr->CostBias; // If this gets modified, also modify below for skirmish and multiplayer
sidebar_entry.PowerProvided = 0;
sidebar_entry.BuildTime = tech->Time_To_Build(PlayerPtr->Class->House); // sidebar_entry.BuildTime = tech->Time_To_Build() / 60;
strncpy(sidebar_entry.AssetName, tech->IniName, CNC_OBJECT_ASSET_NAME_LENGTH);
@ -4619,7 +4675,7 @@ bool DLLExportClass::Get_Sidebar_State(uint64 player_id, unsigned char *buffer_i
if (tech) {
// MBL 06.22.2020 - Updated to apply and difficulty abd/or faction price modifier; See https://jaas.ea.com/browse/TDRA-6864
// Updated to apply and difficulty abd/or faction price modifier; See https://jaas.ea.com/browse/TDRA-6864
// If this gets modified, also modify above for non-skirmish / non-multiplayer
//
// sidebar_entry.Cost = tech->Cost;
@ -6014,6 +6070,22 @@ bool DLLExportClass::Get_Shroud_State(uint64 player_id, unsigned char *buffer_in
return false;
}
/*
** Apply mobile gap generators
*/
static unsigned int _shroud_bits[UNIT_MAX];
if (GAME_TO_PLAY == GAME_GLYPHX_MULTIPLAYER) {
for (int index = 0; index < Units.Count(); index++) {
UnitClass * obj = Units.Ptr(index);
if (obj->Class->IsGapper && obj->IsActive && obj->Strength) {
if (!obj->House->Is_Ally(PlayerPtr)) {
_shroud_bits[index] = obj->Apply_Temporary_Jamming_Shroud(PlayerPtr);
}
}
}
}
CNCShroudStruct *shroud = (CNCShroudStruct*) buffer_in;
unsigned int memory_needed = sizeof(*shroud) + 256; // Base amount needed. Will need more depending on how many entries there are
@ -6087,6 +6159,17 @@ bool DLLExportClass::Get_Shroud_State(uint64 player_id, unsigned char *buffer_in
shroud->Count = entry_index;
if (GAME_TO_PLAY == GAME_GLYPHX_MULTIPLAYER) {
for (int index = 0; index < Units.Count(); index++) {
UnitClass * obj = Units.Ptr(index);
if (obj->Class->IsGapper && obj->IsActive && obj->Strength) {
if (!obj->House->Is_Ally(PlayerPtr)) {
obj->Unapply_Temporary_Jamming_Shroud(PlayerPtr, _shroud_bits[index]);
}
}
}
}
return true;
}
@ -7551,7 +7634,7 @@ void DLLExportClass::Team_Units_Formation_Toggle_On(uint64 player_id)
}
//
// MBL 03.23.2020: Code here copied and modified from Toggle_Formation(), since obj->IsSelected is not supported
// Code here copied and modified from Toggle_Formation(), since obj->IsSelected is not supported
// Replacing with ObjectClass::Is_Selected_By_Player(HouseClass *player);
//
@ -7847,7 +7930,37 @@ extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Debug_Request(DebugRequ
}
extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Beacon_Request(BeaconRequestEnum beacon_request_type, uint64 player_id, int pixel_x, int pixel_y)
{
if (!DLLExportClass::Set_Player_Context(player_id)) {
return;
}
// Beacons are only available if legacy rendering is disabled
if (DLLExportClass::Legacy_Render_Enabled()) {
return;
}
// Only allow one beacon per player
for (int index = 0; index < Anims.Count(); ++index) {
AnimClass* anim = Anims.Ptr(index);
if (anim != NULL &&
(*anim == ANIM_BEACON || *anim == ANIM_BEACON_VIRTUAL) &&
anim->OwnerHouse == PlayerPtr->Class->House) {
delete anim;
}
}
OutList.Add(EventClass(ANIM_BEACON, PlayerPtr->Class->House, Map.Pixel_To_Coord(pixel_x, pixel_y), PlayerPtr->Get_Allies()));
// Send sound effect to allies
for (int index = 0; index < Houses.Count(); ++index) {
HouseClass* hptr = Houses.Ptr(index);
if (hptr != NULL && hptr->IsActive && hptr->IsHuman && PlayerPtr->Is_Ally(hptr)) {
DLLExportClass::On_Sound_Effect(hptr, VOC_BEACON, "", 0, 0);
}
}
}

View file

@ -29,7 +29,7 @@ struct CarryoverObjectStruct;
**
**
*/
#define CNC_DLL_API_VERSION 0x101
#include "DLLInterfaceVersion.h"
@ -425,7 +425,11 @@ enum InputRequestEnum {
INPUT_REQUEST_SELL_AT_POSITION,
INPUT_REQUEST_SELECT_AT_POSITION,
INPUT_REQUEST_COMMAND_AT_POSITION,
INPUT_REQUEST_SPECIAL_KEYS
INPUT_REQUEST_SPECIAL_KEYS,
INPUT_REQUEST_MOD_GAME_COMMAND_1_AT_POSITION,
INPUT_REQUEST_MOD_GAME_COMMAND_2_AT_POSITION,
INPUT_REQUEST_MOD_GAME_COMMAND_3_AT_POSITION,
INPUT_REQUEST_MOD_GAME_COMMAND_4_AT_POSITION,
};
@ -476,6 +480,18 @@ enum GameRequestEnum {
};
/**************************************************************************************
**
** Beacon Requests
**
**
*/
enum BeaconRequestEnum {
INPUT_BEACON_NONE,
INPUT_BEACON_PLACE,
};
/**************************************************************************************
**
** Special Keys

View file

@ -0,0 +1,33 @@
//
// Copyright 2020 Electronic Arts Inc.
//
// TiberianDawn.DLL and RedAlert.dll and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// TiberianDawn.DLL and RedAlert.dll and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
#pragma once
#ifndef DLL_INTERFACE_VERSION_H
#define DLL_INTERFACE_VERSION_H
/*
** DLL Interface version
**
**
**
*/
#define CNC_DLL_API_VERSION 0x102
#endif //DLL_INTERFACE_VERSION_H

View file

@ -282,7 +282,7 @@ EventClass::EventClass(EventType type, TargetClass src, TargetClass dest)
* HISTORY: *
* 05/19/1995 JLB : Created. *
*=============================================================================================*/
EventClass::EventClass(AnimType anim, HousesType owner, COORDINATE coord)
EventClass::EventClass(AnimType anim, HousesType owner, COORDINATE coord, int visible)
{
ID = PlayerPtr->ID;
Type = ANIMATION;
@ -290,6 +290,7 @@ EventClass::EventClass(AnimType anim, HousesType owner, COORDINATE coord)
Data.Anim.What = anim;
Data.Anim.Owner = owner;
Data.Anim.Where = coord;
Data.Anim.Visible = visible;
}
@ -600,14 +601,17 @@ void EventClass::Execute(void)
case ANIMATION:
anim = new AnimClass(Data.Anim.What, Data.Anim.Where);
if (anim) {
//2019/09/19 JAS - Visibility needs to be determined per player
if (Data.Anim.Owner == HOUSE_NONE || Data.Anim.What != ANIM_MOVE_FLASH)
{
anim->Set_Visible_Flags(static_cast<unsigned int>(-1));
}
else
{
anim->Set_Visible_Flags(1 << Data.Anim.Owner);
anim->Set_Owner(Data.Anim.Owner);
anim->Set_Visible_Flags(static_cast<unsigned int>(Data.Anim.Visible));
/*
** Beacons have a 30-second kill time.
*/
if (Data.Anim.What == ANIM_BEACON) {
FILETIME ft;
GetSystemTimeAsFileTime(&ft);
unsigned long long kill_time = ((unsigned long long)ft.dwLowDateTime + ((unsigned long long)ft.dwHighDateTime << 32ULL)) + 300000000ULL;
anim->Kill_At(kill_time);
}
}
break;

View file

@ -136,6 +136,7 @@ class EventClass
AnimType What; // The animation to create.
HousesType Owner; // The owner of the animation (when it matters).
COORDINATE Where; // The location to place the animation.
int Visible; // Who this animation is visible to.
} Anim;
struct {
int Value; // general-purpose data
@ -239,7 +240,7 @@ class EventClass
EventClass(EventType type, RTTIType object, int id);
EventClass(EventType type, RTTIType object, CELL cell);
EventClass(EventType type, int id, CELL cell);
EventClass(AnimType anim, HousesType owner, COORDINATE coord);
EventClass(AnimType anim, HousesType owner, COORDINATE coord, int visible = -1);
EventClass(void *ptr, unsigned long size);
EventClass(EventType type, void *ptr, unsigned long size);

View file

@ -946,8 +946,12 @@ void FootClass::Approach_Target(void)
/*
** If a suitable intermediate location was found, then head toward it.
** Otherwise, head toward the enemy unit directly.
** Infantry always head towards the target since they can enter a cell
** in range, but still not be able to hit the target if the spot is out of range.
*/
if (found) {
if (What_Am_I() == RTTI_INFANTRY) {
Assign_Destination(TarCom);
} else if (found) {
Assign_Destination(::As_Target(trycell));
} else {
@ -1005,6 +1009,20 @@ int FootClass::Mission_Guard_Area(void)
ArchiveTarget = ::As_Target(Coord);
}
/*
** Ensure units aren't trying to guard cells off the map.
*/
if (Target_Legal(NavCom) && Is_Target_Cell(NavCom)) {
CELL cell = As_Cell(NavCom);
int x = Cell_X(cell);
int y = Cell_Y(cell);
if (x < Map.MapCellX || y < Map.MapCellY || x >= (Map.MapCellX + Map.MapCellWidth) || y >= (Map.MapCellY + Map.MapCellHeight)) {
Assign_Target(TARGET_NONE);
Assign_Destination(TARGET_NONE);
ArchiveTarget = ::As_Target(Coord);
}
}
/*
** If this is a bomber type infantry and the current target is a building, then go into
** sabotage mode if not already.
@ -1311,7 +1329,7 @@ void FootClass::Active_Click_With(ActionType action, CELL cell)
case ACTION_MOVE:
if (AllowVoice) {
COORDINATE coord = Map.Pixel_To_Coord(Get_Mouse_X(), Get_Mouse_Y());
OutList.Add(EventClass(ANIM_MOVE_FLASH, PlayerPtr->Class->House, coord));
OutList.Add(EventClass(ANIM_MOVE_FLASH, PlayerPtr->Class->House, coord, 1 << PlayerPtr->Class->House));
}
// Fall into next case.

View file

@ -547,7 +547,7 @@ void const *Get_Radar_Icon(void const * shapefile, int shapenum, int frames, int
void CC_Draw_Shape(void const * shapefile, int shapenum, int x, int y, WindowNumberType window, ShapeFlags_Type flags, void const * fadingdata = 0, void const * ghostdata = 0, DirType rotation = DIR_N);
// Added for draw intercept. ST - 1/17/2019 12:31PM
void CC_Draw_Shape(const ObjectClass *object, void const * shapefile, int shapenum, int x, int y, WindowNumberType window, ShapeFlags_Type flags, void const * fadingdata = 0, void const * ghostdata = 0, DirType rotation = DIR_N, long virtualscale = 0x0100);
void CC_Draw_Shape(const ObjectClass *object, void const * shapefile, int shapenum, int x, int y, WindowNumberType window, ShapeFlags_Type flags, void const * fadingdata = 0, void const * ghostdata = 0, DirType rotation = DIR_N, long virtualscale = 0x0100, int width = 0, int height = 0);
void CC_Draw_Shape(const ObjectClass *object, const char *shape_file_name, void const * shapefile, int shapenum, int x, int y, WindowNumberType window, ShapeFlags_Type flags, void const * fadingdata = 0, void const * ghostdata = 0, DirType rotation = DIR_N, long virtualscale = 0x0100, char override_owner = HOUSE_NONE);
// Added for pip draw intercept - SKY

View file

@ -1821,18 +1821,16 @@ void HouseClass::Attacked(BuildingClass* source)
{
assert(Houses.ID(this) == ID);
bool expired = SpeakAttackDelay == 0;
bool spoke = false;
#ifdef FIXIT_BASE_ANNOUNCE
// if (SpeakAttackDelay == 0 && ((Session.Type == GAME_NORMAL && IsPlayerControl) || PlayerPtr->Class->House == Class->House)) {
if (expired && ((Session.Type == GAME_NORMAL && IsPlayerControl) || PlayerPtr->Class->House == Class->House)) {
if (SpeakAttackDelay == 0 && ((Session.Type == GAME_NORMAL && IsPlayerControl) || PlayerPtr->Class->House == Class->House)) {
#else
// if (SpeakAttackDelay == 0 && PlayerPtr->Class->House == Class->House) {
if (expired && PlayerPtr->Class->House == Class->House) {
if (SpeakAttackDelay == 0 && PlayerPtr->Class->House == Class->House) {
#endif
Speak(VOX_BASE_UNDER_ATTACK, NULL, source ? source->Center_Coord() : 0);
spoke = true;
if (Session.Type == GAME_NORMAL) {
Speak(VOX_BASE_UNDER_ATTACK, NULL, source ? source->Center_Coord() : 0);
} else {
Speak(VOX_BASE_UNDER_ATTACK, this);
}
// MBL 06.13.2020 - Timing change from 2 minute cooldown, per https://jaas.ea.com/browse/TDRA-6784
// SpeakAttackDelay = Options.Normalize_Delay(TICKS_PER_MINUTE * Rule.SpeakDelay); // 2 minutes
@ -1847,22 +1845,6 @@ void HouseClass::Attacked(BuildingClass* source)
HouseTriggers[Class->House][index]->Spring(TEVENT_ATTACKED);
}
}
// MBL 07.07.2020 - CNC Patch 3, fix for not working for all players in MP, per https://jaas.ea.com/browse/TDRA-7249
// Separated to here as did not want to change any logic around the HouseTriggers[] Spring events
//
if (expired == true && spoke == false)
{
if (Session.Type != GAME_NORMAL) // Multiplayer
{
Speak(VOX_BASE_UNDER_ATTACK, this);
spoke = true;
// SpeakAttackDelay = Options.Normalize_Delay(TICKS_PER_MINUTE * Rule.SpeakDelay); // 2 minutes
// SpeakAttackDelay = Options.Normalize_Delay(TICKS_PER_MINUTE/2); // 30 seconds as requested
SpeakAttackDelay = Options.Normalize_Delay( (TICKS_PER_MINUTE/2)+(TICKS_PER_SECOND*5) ); // Tweaked for accuracy
}
}
}

View file

@ -703,6 +703,7 @@ class HouseClass {
bool Is_Ally(HousesType house) const;
bool Is_Ally(HouseClass const * house) const;
bool Is_Ally(ObjectClass const * object) const;
unsigned int Get_Allies(void) const {return Allies;}
#ifdef CHEAT_KEYS
void Debug_Dump(MonoClass *mono) const;
#endif
@ -782,6 +783,9 @@ class HouseClass {
void Init_Unit_Trackers(void);
void Free_Unit_Trackers(void);
// MBL 09.08.2020 Mod support stub
void Handle_Mod_Game_Command(CELL cell, int mod_command_index); // mod_command_index = 0-3
/*
** File I/O.
*/

View file

@ -622,8 +622,14 @@ void InfantryClass::Per_Cell_Process(PCPType why)
*/
if (Mission == MISSION_CAPTURE) {
TechnoClass * tech = cellptr->Cell_Building();
if (tech == NULL) tech = cellptr->Cell_Techno();
if (tech != NULL) {
if ((tech->As_Target() == NavCom || tech->As_Target() == TarCom) && !tech->Can_Capture()) {
tech = NULL;
Assign_Destination(TARGET_NONE);
}
} else {
tech = cellptr->Cell_Techno();
}
if (tech != NULL && (tech->As_Target() == NavCom || tech->As_Target() == TarCom)) {
if (*this == INFANTRY_RENOVATOR) {
@ -638,11 +644,14 @@ void InfantryClass::Per_Cell_Process(PCPType why)
#else
if (tech->House->Is_Ally(House)) {
#endif
if (tech->Trigger.Is_Valid()) {
tech->Trigger->Spring(TEVENT_PLAYER_ENTERED, this);
}
tech->Renovate();
} else {
bool iscapturable = false;
if (tech->What_Am_I() == RTTI_BUILDING) {
iscapturable = ((BuildingClass *)tech)->Class->IsCaptureable;
iscapturable = tech->Can_Capture();
}
#ifdef FIXIT_ENGINEER // checked - ajw 9/28/98
if (tech->Health_Ratio() <= EngineerCaptureLevel && iscapturable) {
@ -1207,7 +1216,7 @@ void InfantryClass::Assign_Target(TARGET target)
*/
if (!Target_Legal(NavCom) && Class->IsCapture && !Is_Weapon_Equipped()) {
BuildingClass const * building = As_Building(target);
if (building != NULL && building->Class->IsCaptureable) {
if (building != NULL && building->Can_Capture()) {
Assign_Destination(target);
}
}
@ -2918,7 +2927,7 @@ ActionType InfantryClass::What_Action(ObjectClass const * object) const
return(ACTION_GREPAIR);
} else {
if (bldg->Class->IsCaptureable) {
if (bldg->Can_Capture()) {
#ifdef FIXIT_ENGINEER // checked - ajw 9/28/98
if (bldg->Health_Ratio() <= EngineerCaptureLevel) {
#else
@ -2929,7 +2938,7 @@ ActionType InfantryClass::What_Action(ObjectClass const * object) const
return(ACTION_DAMAGE);
}
// if (bldg->Health_Ratio() <= Rule.ConditionRed && bldg->Class->IsCaptureable) {
// if (bldg->Health_Ratio() <= Rule.ConditionRed && bldg->Can_Capture()) {
}
}
}
@ -3050,7 +3059,7 @@ ActionType InfantryClass::What_Action(ObjectClass const * object) const
if (Class->IsCapture && action == ACTION_ATTACK) {
if (!House->Is_Ally(object) && (
//Disable capturing of helicopters (object->What_Am_I() == RTTI_AIRCRAFT && ((AircraftClass *)object)->Pip_Count() == 0 && *((AircraftClass *)object) == AIRCRAFT_TRANSPORT) ||
(object->What_Am_I() == RTTI_BUILDING && ((BuildingClass *)object)->Class->IsCaptureable) )
(object->What_Am_I() == RTTI_BUILDING && object->Can_Capture()) )
) {
if (*this == INFANTRY_THIEF && (object->What_Am_I() == RTTI_BUILDING && ((BuildingClass *)object)->Class->Capacity == 0)) {
@ -3284,7 +3293,7 @@ int InfantryClass::Mission_Attack(void)
return(1);
}
if (Class->IsCapture && As_Building(TarCom) != NULL && As_Building(TarCom)->Class->IsCaptureable) {
if (Class->IsCapture && As_Building(TarCom) != NULL && As_Building(TarCom)->Can_Capture()) {
Assign_Destination(TarCom);
Assign_Mission(MISSION_CAPTURE);
return(1);
@ -3914,7 +3923,7 @@ void InfantryClass::Doing_AI(void)
}
}
if (anim != NULL) {
anim->OwnerHouse = House->Class->House;
anim->Set_Owner(House->Class->House);
}
delete this;
return;

View file

@ -1,6 +1,4 @@
Electronic Arts Inc. released only TiberianDawn.dll, RedAlert.dll and
the Command & Conquer Map Editor and their corresponding source code
under the GPL V3 below, with additional terms at the bottom.
Electronic Arts Inc. released only TiberianDawn.dll and RedAlert.dll and their corresponding source code under the GPL V3 below, with additional terms at the bottom.
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
@ -711,4 +709,4 @@ PROVIDED BY ELECTRONIC ARTS OR ANY AUTHORIZED REPRESENTATIVE SHALL CREATE A
WARRANTY. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF OR LIMITATIONS ON
IMPLIED WARRANTIES OR THE LIMITATIONS ON THE APPLICABLE STATUTORY RIGHTS OF A
CONSUMER, SO SOME OR ALL OF THE ABOVE EXCLUSIONS AND LIMITATIONS MAY NOT APPLY
TO YOU.
TO YOU.

View file

@ -428,7 +428,7 @@ dxisbig:
#if (0)
/*
; $Header: //depot/Projects/Mobius/QA/Project/Run/SOURCECODE/REDALERT/MiscAsm.cpp#97 $
; $Header: //depot/Projects/Mobius/QA/Project/Run/SOURCECODE/REDALERT/MiscAsm.cpp#131 $
;***************************************************************************
;** C O N F I D E N T I A L --- W E S T W O O D A S S O C I A T E S **
;***************************************************************************

View file

@ -432,6 +432,15 @@ bool ObjectClass::Can_Demolish_Unit(void) const
}
bool ObjectClass::Can_Capture(void) const
{
assert(this != 0);
assert(IsActive);
return(false);
}
/***********************************************************************************************
* ObjectClass::Can_Player_Fire -- Can the player give this object an attack mission? *
* *

View file

@ -165,6 +165,7 @@ class ObjectClass : public AbstractClass
virtual bool Can_Repair(void) const;
virtual bool Can_Demolish(void) const;
virtual bool Can_Demolish_Unit(void) const;
virtual bool Can_Capture(void) const;
virtual bool Can_Player_Fire(void) const;
virtual bool Can_Player_Move(void) const;

View file

@ -1409,7 +1409,7 @@ bool RadarClass::Jam_Cell(CELL cell, HouseClass * house/*KO, bool shadeit*/)
for (int i = 0; i < Session.Players.Count(); i++) {
HouseClass *player_house = HouseClass::As_Pointer(Session.Players[i]->Player.ID);
if (player_house->IsHuman && player_house != house) {
if (player_house->IsHuman && player_house != house && !house->Is_Ally(player_house)) {
Shroud_Cell(cell, player_house);
}
}

View file

@ -514,6 +514,7 @@
<ClInclude Include="DIBUTIL.H" />
<ClInclude Include="DISPLAY.H" />
<ClInclude Include="DLLInterface.h" />
<ClInclude Include="DLLInterfaceVersion.h" />
<ClInclude Include="DOOR.H" />
<ClInclude Include="DPMI.H" />
<ClInclude Include="DRIVE.H" />

View file

@ -1766,6 +1766,9 @@
<ClInclude Include="Resource\resource.h">
<Filter>Source Files\Resource</Filter>
</ClInclude>
<ClInclude Include="DLLInterfaceVersion.h">
<Filter>Source Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="COORDA.ASM">

View file

@ -319,6 +319,14 @@ Theme.Stop();
return(false);
}
/* Swap Lt. Blue and Blue color remaps in skirmish/multiplayer */
if (Session.Type != GAME_NORMAL) {
RemapControlType temp;
memcpy(&temp, &ColorRemaps[PCOLOR_LTBLUE], sizeof(RemapControlType));
memcpy(&ColorRemaps[PCOLOR_LTBLUE], &ColorRemaps[PCOLOR_BLUE], sizeof(RemapControlType));
memcpy(&ColorRemaps[PCOLOR_BLUE], &temp, sizeof(RemapControlType));
}
/*
** Play the winning movie and then start the next scenario.
*/
@ -2293,11 +2301,15 @@ bool Read_Scenario_INI(char * fname, bool )
Rule.Difficulty(ini);
/*
** Fix a legacy bug with England and France country bonuses
** - Fix a legacy bug with England and France country bonuses.
** - Use ore growth and spread values from the special settings.
*/
if (Session.Type != GAME_NORMAL) {
HouseTypeClass::As_Reference(HOUSE_ENGLAND).ArmorBias = fixed(9, 10);
HouseTypeClass::As_Reference(HOUSE_FRANCE).ROFBias = fixed(9, 10);
Rule.IsTGrowth = Special.IsTGrowth;
Rule.IsTSpread = Special.IsTSpread;
}
/*
@ -2477,6 +2489,7 @@ bool Read_Scenario_INI(char * fname, bool )
** Blue Lakes multiplayer map cell 11937 - LAND_RIVER
** USSR mission 13 - fixup trigger action
** Allied mission 5B - fail mission if spy re-boards the transport at mission start
** Allied mission 8A - Germany is allied with Greece and itself
** Allied mission 9A - fail mission if tech center is destroyed before being spied
** Aftermath: Brother in Arms - have transports move to separate waypoints
** Aftermath: Let's Make a Steal - Make the pillboxes un-capturable
@ -2525,6 +2538,17 @@ bool Read_Scenario_INI(char * fname, bool )
frc1_trigger->Action1.Trigger = los3_trigger;
}
if (_stricmp(Scen.ScenarioName, "scg08ea.ini") == 0) {
for (int house = HOUSE_FIRST; house < HOUSE_COUNT; ++house) {
HouseClass* ptr = Houses.Ptr(house);
if (ptr != NULL && ptr->IsActive) {
if (ptr->Class->House == HOUSE_GREECE || ptr->Class->House == HOUSE_GERMANY) {
ptr->Make_Ally(HOUSE_GERMANY);
}
}
}
}
if (_stricmp(Scen.ScenarioName, "scg09ea.ini") == 0) {
TriggerTypeClass* spyd_trigger = TriggerTypeClass::From_Name("Spyd");
assert(spyd_trigger != NULL);
@ -3202,6 +3226,7 @@ static void Create_Units(bool official)
if (numtaken == 0) {
int pick = Random_Pick(0, num_waypts-1);
centroid = waypts[pick];
hptr->StartLocationOverride = pick;
taken[pick] = true;
numtaken++;
} else {
@ -3251,6 +3276,7 @@ static void Create_Units(bool official)
** Assign this best position to the house.
*/
centroid = waypts[best];
hptr->StartLocationOverride = best;
taken[best] = true;
numtaken++;
}

View file

@ -1647,7 +1647,7 @@ bool TechnoClass::Evaluate_Object(ThreatType method, int mask, int range, Techno
** If the scan is limited to capturable buildings only, then bail if the examined
** object isn't a capturable building.
*/
if ((method & THREAT_CAPTURE) && (otype != RTTI_BUILDING || !((BuildingTypeClass const *)tclass)->IsCaptureable)) {
if ((method & THREAT_CAPTURE) && (otype != RTTI_BUILDING || !object->Can_Capture())) {
BEnd(BENCH_EVAL_OBJECT);
return(false);
}
@ -3378,6 +3378,13 @@ BulletClass * TechnoClass::Fire_At(TARGET target, int which)
}
}
/*
** For electric zaps, immediately perform bullet logic.
*/
if (weapon->IsElectric) {
bullet->AI();
}
#endif
}
@ -3560,7 +3567,7 @@ ActionType TechnoClass::What_Action(ObjectClass const * object) const
((weapon != NULL) && weapon->Bullet->IsAntiAircraft) ||
(object->Is_Techno() && (((TechnoClass *)object)->Height == 0))) {
if (Can_Player_Move() || In_Range(object, primary)) {
if (In_Range(object, primary) || (What_Am_I() == RTTI_INFANTRY && ((InfantryClass *)this)->Class->IsCapture && object->What_Am_I() == RTTI_BUILDING && ((BuildingClass *)object)->Class->IsCaptureable)) {
if (In_Range(object, primary) || (What_Am_I() == RTTI_INFANTRY && ((InfantryClass *)this)->Class->IsCapture && object->What_Am_I() == RTTI_BUILDING && object->Can_Capture())) {
return(ACTION_ATTACK);
} else {
if (!Can_Player_Move()) {

View file

@ -100,6 +100,32 @@
#include "function.h"
#include "COORDA.h"
extern void Logic_Switch_Player_Context(ObjectClass *object);
extern void Logic_Switch_Player_Context(HouseClass *object);
extern void On_Special_Weapon_Targetting(const HouseClass* player_ptr, SpecialWeaponType weapon_type);
extern bool Is_Legacy_Render_Enabled(void);
static int _GapShroudXTable[]={
-1, 0, 1,
-2,-1, 0, 1, 2,
-2,-1, 0, 1, 2,
-2,-1, 0, 1, 2,
-2,-1, 0, 1, 2,
-2,-1, 0, 1, 2,
-1, 0, 1
};
static int _GapShroudYTable[]={
-3,-3,-3,
-2,-2,-2,-2,-2,
-1,-1,-1,-1,-1,
0, 0, 0, 0, 0,
1, 1, 1, 1, 1,
2, 2, 2, 2, 2,
3, 3, 3
};
/***********************************************************************************************
* Recoil_Adjust -- Adjust pixel values in direction specified. *
* *
@ -290,7 +316,8 @@ UnitClass::UnitClass(UnitType classid, HousesType house) :
ShroudBits(0xFFFFFFFFUL),
ShroudCenter(0),
Reload(0),
SecondaryFacing(PrimaryFacing)
SecondaryFacing(PrimaryFacing),
TiberiumUnloadRefinery(TARGET_NONE)
{
Reload = 0;
House->Tracking_Add(this);
@ -414,6 +441,20 @@ void UnitClass::AI(void)
IsHarvesting = false;
}
/*
** Clear the unload refinery if not haresting or entering a refinery.
*/
if (Class->IsToHarvest) {
if (Mission != MISSION_HARVEST) {
if (Mission != MISSION_ENTER ||
!In_Radio_Contact() ||
Contact_With_Whom()->What_Am_I() != RTTI_BUILDING ||
*((BuildingClass*)Contact_With_Whom()) != STRUCT_REFINERY) {
TiberiumUnloadRefinery = TARGET_NONE;
}
}
}
/*
** Handle combat logic for this unit. It will determine if it has a target and
** if so, if conditions are favorable for firing. When conditions permit, the
@ -2275,29 +2316,47 @@ bool UnitClass::Goto_Tiberium(int rad)
int tiberium = 0;
int besttiberium = 0;
for (int x = -radius; x <= radius; x++) {
/*
** Randomize the corners.
*/
int corner[2];
int corners[4][2] = {
{x, -radius},
{x, +radius},
{-radius, x},
{+radius, x}
};
for (int i = 0; i < 3; i++) {
int j = i + rand() / (RAND_MAX / (4 - i) + 1);
memcpy(&corner, &corners[j], sizeof(corner));
memcpy(&corners[j], &corners[i], sizeof(corner));
memcpy(&corners[i], corner, sizeof(corner));
}
cell = center;
tiberium = Tiberium_Check(cell, x, -radius);
tiberium = Tiberium_Check(cell, corners[0][0], corners[0][1]);
if (tiberium > besttiberium) {
bestcell = cell;
besttiberium = tiberium;
}
cell = center;
tiberium = Tiberium_Check(cell, x, +radius);
tiberium = Tiberium_Check(cell, corners[1][0], corners[1][1]);
if (tiberium > besttiberium) {
bestcell = cell;
besttiberium = tiberium;
}
cell = center;
tiberium = Tiberium_Check(cell, -radius, x);
tiberium = Tiberium_Check(cell, corners[2][0], corners[2][1]);
if (tiberium > besttiberium) {
bestcell = cell;
besttiberium = tiberium;
}
cell = center;
tiberium = Tiberium_Check(cell, +radius, x);
tiberium = Tiberium_Check(cell, corners[3][0], corners[3][1]);
if (tiberium > besttiberium) {
bestcell = cell;
besttiberium = tiberium;
@ -2411,10 +2470,6 @@ bool UnitClass::Harvesting(void)
* HISTORY: *
* 07/18/1994 JLB : Created. *
*=============================================================================================*/
extern void Logic_Switch_Player_Context(ObjectClass *object);
extern void Logic_Switch_Player_Context(HouseClass *object);
extern void On_Special_Weapon_Targetting(const HouseClass* player_ptr, SpecialWeaponType weapon_type);
int UnitClass::Mission_Unload(void)
{
assert(Units.ID(this) == ID);
@ -2871,6 +2926,7 @@ int UnitClass::Mission_Harvest(void)
/*
** Look for ore where we last found some - mine the same patch
*/
TiberiumUnloadRefinery = TARGET_NONE;
if (Target_Legal(ArchiveTarget)) {
Assign_Destination(ArchiveTarget);
ArchiveTarget = 0;
@ -2952,24 +3008,17 @@ int UnitClass::Mission_Harvest(void)
if (!Target_Legal(NavCom)) {
/*
** Find nearby refinery and head to it?
** Find nearby refinery and head to it.
*/
BuildingClass * nearest = Find_Docking_Bay(STRUCT_REFINERY, false);
/*
** Since the refinery said it was ok to load, establish radio
** contact with the refinery and then await docking orders.
*/
if (nearest != NULL && Transmit_Message(RADIO_HELLO, nearest) == RADIO_ROGER) {
Status = HEADINGHOME;
if (nearest->House == PlayerPtr && (PlayerPtr->Capacity - PlayerPtr->Tiberium) < 300 && PlayerPtr->Capacity > 500 && (PlayerPtr->ActiveBScan & (STRUCTF_REFINERY | STRUCTF_CONST))) {
Speak(VOX_NEED_MO_CAPACITY);
}
} else {
ScenarioInit++;
nearest = Find_Docking_Bay(STRUCT_REFINERY, false);
ScenarioInit--;
if (nearest != NULL) {
BuildingClass * nearest = Find_Best_Refinery();
if (nearest != NULL) {
TiberiumUnloadRefinery = nearest->As_Target();
if (Transmit_Message(RADIO_HELLO, nearest) == RADIO_ROGER) {
Status = HEADINGHOME;
if (nearest->House == PlayerPtr && (PlayerPtr->Capacity - PlayerPtr->Tiberium) < 300 && PlayerPtr->Capacity > 500 && (PlayerPtr->ActiveBScan & (STRUCTF_REFINERY | STRUCTF_CONST))) {
Speak(VOX_NEED_MO_CAPACITY);
}
} else {
Assign_Destination(::As_Target(Nearby_Location(nearest)));
}
}
@ -2990,6 +3039,7 @@ int UnitClass::Mission_Harvest(void)
** no where to go.
*/
case GOINGTOIDLE:
TiberiumUnloadRefinery = TARGET_NONE;
if (IsUseless) {
if (House->ActiveBScan & STRUCTF_REPAIR) {
Assign_Mission(MISSION_REPAIR);
@ -4363,6 +4413,103 @@ fixed UnitClass::Tiberium_Load(void) const
}
BuildingClass* UnitClass::Tiberium_Unload_Refinery(void) const
{
return Target_Legal(TiberiumUnloadRefinery) ? As_Building(TiberiumUnloadRefinery) : NULL;
}
struct RefineryData
{
BuildingClass* Refinery;
int Distance;
int Harvesters;
};
static bool operator==(const RefineryData& lhs, const RefineryData& rhs)
{
return lhs.Refinery == rhs.Refinery;
}
static bool operator!=(const RefineryData& lhs, const RefineryData& rhs)
{
return !(lhs == rhs);
}
static int _refinery_compare(const void * left, const void * right)
{
const RefineryData& lhs = *reinterpret_cast<const RefineryData*>(left);
const RefineryData& rhs = *reinterpret_cast<const RefineryData*>(right);
if (lhs.Distance < rhs.Distance) {
return -1;
} else if (rhs.Distance < lhs.Distance) {
return 1;
}
return 0;
}
BuildingClass* UnitClass::Find_Best_Refinery(void) const
{
static DynamicVectorClass<RefineryData> _refineries;
_refineries.Clear();
for (int i = 0; i < Buildings.Count(); ++i) {
BuildingClass* refinery = Buildings.Ptr(i);
if (refinery != NULL &&
refinery->House == House &&
!refinery->IsInLimbo &&
*refinery == STRUCT_REFINERY &&
Map[refinery->Center_Coord()].Zones[Techno_Type_Class()->MZone] == Map[Center_Coord()].Zones[Techno_Type_Class()->MZone]) {
_refineries.Add(RefineryData{ refinery, Distance(refinery), 0 });
}
}
// Base case for zero or one refineries.
if (_refineries.Count() == 0) {
return NULL;
} else if (_refineries.Count() == 1) {
return _refineries[0].Refinery;
}
// Count harvesters going to each refinery as well as the total.
int num_harvesters = 0;
for (int i = 0; i < Units.Count(); ++i) {
UnitClass* unit = Units.Ptr(i);
if (unit->IsActive && Class->IsToHarvest && unit->House == House) {
BuildingClass* refinery = unit->Tiberium_Unload_Refinery();
if (refinery != NULL) {
int index = _refineries.ID(RefineryData{ refinery });
assert(index >= 0);
_refineries[index].Harvesters++;
num_harvesters++;
}
}
}
// Sort by distance (special case for 2 refineries as that's a single swap).
if (_refineries.Count() == 2) {
if (_refineries[0].Distance > _refineries[1].Distance) {
RefineryData temp = _refineries[0];
_refineries[0] = _refineries[1];
_refineries[1] = temp;
}
} else {
qsort(&_refineries[0], _refineries.Count(), sizeof(RefineryData), _refinery_compare);
}
// Evenly distribute harvesters among refineries.
int harvesters_per_refinery = (num_harvesters + _refineries.Count() - 1) / _refineries.Count();
for (int i = 0; i < _refineries.Count(); ++i) {
if (_refineries[i].Harvesters < harvesters_per_refinery) {
return _refineries[i].Refinery;
}
}
// Fall back on closest refinery
return _refineries[0].Refinery;
}
/***********************************************************************************************
* UnitClass::Offload_Tiberium_Bail -- Offloads one Tiberium quantum from the object. *
* *
@ -4517,7 +4664,7 @@ void UnitClass::Overrun_Square(CELL cell, bool threaten)
if (object->Height == 0) {
AnimClass* anim = new AnimClass(ANIM_CORPSE1, object->Center_Coord());
if (anim != NULL) {
anim->OwnerHouse = object->Owner();
anim->Set_Owner(object->Owner());
}
}
object->Record_The_Kill(this);
@ -5022,64 +5169,143 @@ bool UnitClass::Limbo(void)
return(false);
}
/***********************************************************************************************
* UnitClass::Apply_Temporary_Jamming_Shroud -- Apply a temporary gap generator shroud effect *
* *
* This is intended as a temporary effect that is active only during export of the *
* shroud data *
* *
* INPUT: House to apply effect for *
* *
* OUTPUT: Bitmask of cells that effect was applied to *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 8/19/2020 12:13PM ST : Created. *
*=============================================================================================*/
unsigned int UnitClass::Apply_Temporary_Jamming_Shroud(HouseClass *house_to_apply_for)
{
unsigned int shroud_bits_applied = 0;
if (!IsActive || !Strength) {
return shroud_bits_applied;
}
if (!Class->IsGapper) {
return shroud_bits_applied;
}
CELL shroud_center = Coord_Cell(Center_Coord());
int centerx = Cell_X(shroud_center);
int centery = Cell_Y(shroud_center);
CELL trycell;
for (int index = 0; index < 31; index++) {
shroud_bits_applied <<= 1;
trycell = XY_Cell(centerx + _GapShroudXTable[index], centery + _GapShroudYTable[index]);
if (Map[trycell].Is_Mapped(house_to_apply_for)) {
Map.Jam_Cell(trycell, House);
shroud_bits_applied |= 1;
}
}
if (shroud_bits_applied) {
Map.Constrained_Look(Coord, 5 * CELL_LEPTON_W, house_to_apply_for);
}
return shroud_bits_applied;
}
/***********************************************************************************************
* UnitClass::Unapply_Temporary_Jamming_Shroud -- Remove temporary gap generator shroud effect *
* *
* Remove gap effect added by Apply_Temporary_Jamming_Shroud *
* *
* INPUT: House to unapply effect for *
* Bitmask of cells that effect was applied to *
* *
* OUTPUT: *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 8/19/2020 12:16PM ST : Created. *
*=============================================================================================*/
void UnitClass::Unapply_Temporary_Jamming_Shroud(HouseClass *house_to_unapply_for, unsigned int shroud_bits_applied)
{
if (!IsActive || !Strength) {
return;
}
if (!Class->IsGapper) {
return;
}
CELL shroud_center = Coord_Cell(Center_Coord());
int centerx = Cell_X(shroud_center);
int centery = Cell_Y(shroud_center);
CELL trycell;
for (int index = 30; index >= 0 && shroud_bits_applied; index--) {
if (shroud_bits_applied & 1) {
trycell = XY_Cell(centerx + _GapShroudXTable[index], centery + _GapShroudYTable[index]);
Map.UnJam_Cell(trycell, House);
Map.Map_Cell(trycell, house_to_unapply_for);
}
shroud_bits_applied >>= 1;
}
}
/*
** Updated for client/server multiplayer - ST 8/12/2019 11:46AM
*/
void UnitClass::Shroud_Regen(void)
{
if (Class->IsGapper/*KO && !House->IsPlayerControl*/) {
static int _xtab[]={
-1, 0, 1,
-2,-1, 0, 1, 2,
-2,-1, 0, 1, 2,
-2,-1, 0, 1, 2,
-2,-1, 0, 1, 2,
-2,-1, 0, 1, 2,
-1, 0, 1
};
static int _ytab[]={
-3,-3,-3,
-2,-2,-2,-2,-2,
-1,-1,-1,-1,-1,
0, 0, 0, 0, 0,
1, 1, 1, 1, 1,
2, 2, 2, 2, 2,
3, 3, 3
};
int index;
int centerx, centery;
CELL trycell;
// Only restore under the shroud if it's a valid field.
if (ShroudBits != (unsigned)-1L) {
centerx = Cell_X(ShroudCenter);
centery = Cell_Y(ShroudCenter);
for (index = 30; index >= 0 && ShroudBits; index--) {
if (ShroudBits & 1) {
trycell = XY_Cell(centerx + _xtab[index], centery + _ytab[index]);
#if(0)
Map.Map_Cell(trycell, PlayerPtr);
#else
Map.UnJam_Cell(trycell, House);
Map.Map_Cell(trycell, House);
#endif
if (Session.Type != GAME_GLYPHX_MULTIPLAYER || Is_Legacy_Render_Enabled()) {
// Only restore under the shroud if it's a valid field.
if (ShroudBits != (unsigned)-1L) {
centerx = Cell_X(ShroudCenter);
centery = Cell_Y(ShroudCenter);
for (index = 30; index >= 0 && ShroudBits; index--) {
if (ShroudBits & 1) {
trycell = XY_Cell(centerx + _GapShroudXTable[index], centery + _GapShroudYTable[index]);
#if(0)
Map.Map_Cell(trycell, PlayerPtr);
#else
Map.UnJam_Cell(trycell, House);
Map.Map_Cell(trycell, House);
#endif
}
ShroudBits >>= 1;
}
ShroudBits >>= 1;
}
}
if(IsActive && Strength) {
// Now shroud around the new center
ShroudBits = 0L;
ShroudCenter = Coord_Cell(Center_Coord());
centerx = Cell_X(ShroudCenter);
centery = Cell_Y(ShroudCenter);
for (index = 0; index < 31; index++) {
ShroudBits <<= 1;
trycell = XY_Cell(centerx + _xtab[index], centery + _ytab[index]);
if (Map[trycell].Is_Mapped(House)) {
Map.Jam_Cell(trycell, House);
ShroudBits |= 1;
if(IsActive && Strength) {
// Now shroud around the new center
ShroudBits = 0L;
ShroudCenter = Coord_Cell(Center_Coord());
centerx = Cell_X(ShroudCenter);
centery = Cell_Y(ShroudCenter);
for (index = 0; index < 31; index++) {
ShroudBits <<= 1;
trycell = XY_Cell(centerx + _GapShroudXTable[index], centery + _GapShroudYTable[index]);
if (Map[trycell].Is_Mapped(House)) {
Map.Jam_Cell(trycell, House);
ShroudBits |= 1;
}
}
}
}
@ -5094,14 +5320,15 @@ void UnitClass::Shroud_Regen(void)
} else {
for (int i = 0; i < Session.Players.Count(); i++) {
HouseClass *player_house = HouseClass::As_Pointer(Session.Players[i]->Player.ID);
if (player_house->IsHuman && player_house != House) {
Map.Constrained_Look(Coord, 5 * CELL_LEPTON_W, player_house);
if (Is_Legacy_Render_Enabled()) {
for (int i = 0; i < Session.Players.Count(); i++) {
HouseClass *player_house = HouseClass::As_Pointer(Session.Players[i]->Player.ID);
if (player_house->IsHuman && player_house != House) {
Map.Constrained_Look(Coord, 5 * CELL_LEPTON_W, player_house);
}
}
}
}
}
}

View file

@ -111,10 +111,15 @@ class UnitClass : public DriveClass
*/
FacingClass SecondaryFacing;
/*
** This is the refinery a harvester is interested in unloading at.
*/
TARGET TiberiumUnloadRefinery;
/*
** Some additional padding in case we need to add data to the class and maintain backwards compatibility for save/load
*/
unsigned char SaveLoadPadding[32];
unsigned char SaveLoadPadding[28];
/*---------------------------------------------------------------------
** Constructors, Destructors, and overloaded operators.
@ -145,6 +150,9 @@ class UnitClass : public DriveClass
void APC_Close_Door(void);
void APC_Open_Door(void);
unsigned int Apply_Temporary_Jamming_Shroud(HouseClass *house_to_apply_for);
void Unapply_Temporary_Jamming_Shroud(HouseClass *house_to_unapply_for, unsigned int shroud_bits_applied);
/*
** Query functions.
*/
@ -158,6 +166,8 @@ class UnitClass : public DriveClass
virtual bool Ok_To_Move(DirType facing) const;
virtual FireErrorType Can_Fire(TARGET target, int which) const;
virtual fixed Tiberium_Load(void) const;
virtual BuildingClass* Tiberium_Unload_Refinery(void) const;
virtual BuildingClass* Find_Best_Refinery(void) const;
/*
** Coordinate inquiry functions. These are used for both display and
@ -248,4 +258,4 @@ class UnitClass : public DriveClass
bool Save(Pipe & file) const;
};
#endif
#endif

View file

@ -4842,7 +4842,7 @@ extern "C" int __cdecl Confine_Rect ( int * x , int * y , int w , int h , int wi
/*
; $Header: //depot/Projects/Mobius/QA/Project/Run/SOURCECODE/REDALERT/WIN32LIB/DrawMisc.cpp#97 $
; $Header: //depot/Projects/Mobius/QA/Project/Run/SOURCECODE/REDALERT/WIN32LIB/DrawMisc.cpp#131 $
;***************************************************************************
;** C O N F I D E N T I A L --- W E S T W O O D A S S O C I A T E S **
;***************************************************************************

View file

@ -2379,7 +2379,58 @@ static AnimTypeClass const Flag(
0, // Loop start frame number.
-1, // Ending frame of loop back.
-1, // Number of animation stages.
-1, // Number of times the animation loops.
-1, // Number of times the animation loops.
VOC_NONE, // Sound effect to play.
ANIM_NONE
);
static AnimTypeClass const Beacon(
ANIM_BEACON, // Animation number.
"MOVEFLSH", // Data name of animation.
21, // Maximum dimension of animation.
0, // Biggest animation stage.
false, // Normalized animation rate?
false, // Uses white translucent table?
false, // Scorches the ground?
false, // Forms a crater?
false, // Sticks to unit in square?
false, // Ground level animation?
false, // Translucent colors in this animation?
false, // Is this a flame thrower animation?
0x0000, // Damage to apply per tick (fixed point).
1, // Delay between frames.
0, // Starting frame number.
0, // Loop start frame number.
-1, // Ending frame of loop back.
1, // Number of animation stages.
-1, // Number of times the animation loops.
VOC_NONE, // Sound effect to play.
ANIM_NONE,
-1, // Virtual stages
0x100, // Virtual scale
ANIM_BEACON_VIRTUAL // Virtual anim
);
static AnimTypeClass const BeaconVirtual(
ANIM_BEACON_VIRTUAL, // Animation number.
"BEACON", // Data name of animation.
21, // Maximum dimension of animation.
0, // Biggest animation stage.
false, // Normalized animation rate?
false, // Uses white translucent table?
false, // Scorches the ground?
false, // Forms a crater?
false, // Sticks to unit in square?
false, // Ground level animation?
false, // Translucent colors in this animation?
false, // Is this a flame thrower animation?
0x0000, // Damage to apply per tick (fixed point).
1, // Delay between frames.
0, // Starting frame number.
0, // Loop start frame number.
-1, // Ending frame of loop back.
1, // Number of animation stages.
-1, // Number of times the animation loops.
VOC_NONE, // Sound effect to play.
ANIM_NONE
);
@ -2484,10 +2535,12 @@ AnimTypeClass const * const AnimTypeClass::Pointers[ANIM_COUNT] = {
&RaptDie,
&ChemBall,
&Flag,
&Beacon,
&Fire3Virtual,
&Fire2Virtual,
&Fire1Virtual,
&Fire4Virtual
&Fire4Virtual,
&BeaconVirtual
};
@ -2570,6 +2623,9 @@ void AnimTypeClass::One_Time(void)
((void const *&)As_Reference(index).ImageData) = MixFileClass::Retrieve(fullname);
}
}
// Set up beacon image data manually since they're new animations only available in the virtual renderer
((void const *&)As_Reference(ANIM_BEACON_VIRTUAL).ImageData) = As_Reference(ANIM_BEACON).ImageData;
}

View file

@ -2368,6 +2368,11 @@ int AircraftClass::Mission_Attack(void)
return(1);
}
/*
** Clear second shot flag so fire burst works correctly.
*/
IsSecondShot = false;
PrimaryFacing.Set_Desired(Direction(TarCom));
SecondaryFacing.Set_Desired(Direction(TarCom));
switch (Can_Fire(TarCom, 0)) {

View file

@ -249,6 +249,8 @@ void AnimClass::Draw_It(int x, int y, WindowNumberType window)
int shapenum = Class->Start + Fetch_Stage();
void const * remap = NULL;
ShapeFlags_Type flags = SHAPE_CENTER|SHAPE_WIN_REL;
int width = 0;
int height = 0;
/*
** Some animations require special fixups.
@ -276,6 +278,12 @@ void AnimClass::Draw_It(int x, int y, WindowNumberType window)
y += (3 * ICON_PIXEL_H / 4) - Get_Build_Frame_Height(shapefile);
transtable = Map.UnitShadow;
break;
case ANIM_BEACON_VIRTUAL:
width = 29;
height = 39;
flags = flags | SHAPE_BOTTOM | SHAPE_COMPACT;
break;
}
/*
@ -303,7 +311,7 @@ void AnimClass::Draw_It(int x, int y, WindowNumberType window)
** Draw the animation shape, but ignore legacy if beyond normal stage count.
*/
if ((window == WINDOW_VIRTUAL) || (Fetch_Stage() < Class->Stages)) {
CC_Draw_Shape(this, shapefile, shapenum, x, y, window, flags, remap, transtable, Class->VirtualScale);
CC_Draw_Shape(this, shapefile, shapenum, x, y, window, flags, remap, transtable, Class->VirtualScale, width, height);
}
}
}
@ -571,6 +579,7 @@ AnimClass::AnimClass(AnimType animnum, COORDINATE coord, unsigned char timedelay
Object = 0;
SortTarget = TARGET_NONE;
OwnerHouse = HOUSE_NONE;
KillTime = 0ULL;
if (Class->Stages == -1) {
((int&)Class->Stages) = Get_Build_Frame_Count(Class->Get_Image_Data());
@ -732,6 +741,19 @@ void AnimClass::AI(void)
IsToDelete = true;
}
/*
** Check the kill time.
*/
if (KillTime > 0ULL) {
FILETIME ft;
GetSystemTimeAsFileTime(&ft);
unsigned long long now = (unsigned long long)ft.dwLowDateTime + ((unsigned long long)ft.dwHighDateTime << 32ULL);
if (now >= KillTime) {
IsToDelete = true;
}
}
/*
** Delete this animation and bail early if the animation is flagged to be deleted
** immediately.
@ -1292,4 +1314,20 @@ void AnimClass::Detach(TARGET target, bool all)
IsToDelete = true;
}
}
}
void AnimClass::Set_Owner(HousesType owner)
{
OwnerHouse = owner;
if (VirtualAnim != NULL) {
VirtualAnim->Set_Owner(owner);
}
}
void AnimClass::Set_Visible_Flags(unsigned flags)
{
VisibleFlags = flags;
if (VirtualAnim != NULL) {
VirtualAnim->Set_Visible_Flags(flags);
}
}

View file

@ -63,14 +63,16 @@ class AnimClass : public ObjectClass, private StageClass {
void Sort_Above(TARGET target);
void Make_Invisible(void) {IsInvisible = true;};
void Make_Visible(void) {IsInvisible = false;};
void Kill_At(unsigned long long kill_time) {KillTime = kill_time;}
/*
** 2019/09/19 JAS
** Added functions for accessing which players can see this anim
*/
void Set_Visible_Flags(unsigned flags) { VisibleFlags = flags; }
void Set_Visible_Flags(unsigned flags);
unsigned Get_Visible_Flags() const { return (Delay == 0) ? VisibleFlags : 0; }
virtual void Set_Owner(HousesType owner);
virtual bool Can_Place_Here(COORDINATE ) const {return true;}
virtual bool Mark(MarkType mark=MARK_CHANGE);
virtual bool Render(bool forced);
@ -194,10 +196,15 @@ class AnimClass : public ObjectClass, private StageClass {
*/
AnimClass * VirtualAnim;
/*
** Real-time point to kill this animation.
*/
unsigned long long KillTime;
/*
** Some additional padding in case we need to add data to the class and maintain backwards compatibility for save/load
*/
unsigned char SaveLoadPadding[32];
unsigned char SaveLoadPadding[24];
};

View file

@ -254,6 +254,7 @@ struct SoundEffectNameStruct {
{"DINOYES", 10, IN_NOVAR}, // VOC_DINOYES Yes Sir in dino-speak.
{"DINOATK1", 10, IN_NOVAR}, // VOC_DINOATK1 Dino attack sound.
{"DINODIE1", 10, IN_NOVAR}, // VOC_DINODIE1 Dino die sound.
{"BEACON", 10, IN_NOVAR }, // VOC_BEACON Beacon sound.
#ifdef PETROGLYPH_EXAMPLE_MOD
{"NUKE_LOB", 10, IN_NOVAR} // VOC_NUKE_LOB Mod expansion unit firing sound

View file

@ -2077,7 +2077,7 @@ void BuildingClass::Active_Click_With(ActionType action, CELL cell)
OutList.Add(EventClass(EventClass::SELL, As_Target()));
COORDINATE coord = Map.Pixel_To_Coord(Get_Mouse_X(), Get_Mouse_Y());
OutList.Add(EventClass(ANIM_MOVE_FLASH, PlayerPtr->Class->House, coord));
OutList.Add(EventClass(ANIM_MOVE_FLASH, PlayerPtr->Class->House, coord, 1 << PlayerPtr->Class->House));
}
}
@ -3872,13 +3872,16 @@ bool BuildingClass::Can_Demolish_Unit(void) const
bool BuildingClass::Can_Capture(void) const
{
bool can_capture = Class->IsCaptureable;
bool can_capture = Class->IsCaptureable && Mission != MISSION_DECONSTRUCTION;
// Override capturable state if this building has a capture win trigger
if (GameToPlay == GAME_NORMAL) {
if (Trigger != NULL && Trigger->Action == TriggerClass::ACTION_WINLOSE) {
if (!House->IsHuman && Trigger != NULL && Trigger->Action == TriggerClass::ACTION_WINLOSE) {
can_capture = true;
}
} else {
// Only allow capturing of multiplayer-owned structures
can_capture &= House->Class->House >= HOUSE_MULTI1 && House->Class->House <= HOUSE_MULTI6;
}
return(can_capture);
@ -4161,6 +4164,7 @@ int BuildingClass::Mission_Deconstruction(void)
Status = DURING;
Begin_Mode(BSTATE_CONSTRUCTION);
IsReadyToCommence = false;
IsSurvivorless = true;
break;
}
Transmit_Message(RADIO_RUN_AWAY);

View file

@ -473,7 +473,7 @@ void BulletClass::AI(void)
//
if (newanim && Class->Explosion == ANIM_ATOM_BLAST && newanim->OwnerHouse == HOUSE_NONE) {
if (Payback && Payback->House && Payback->House->Class) {
newanim->OwnerHouse = Payback->House->Class->House;
newanim->Set_Owner(Payback->House->Class->House);
}
}
}

View file

@ -389,7 +389,9 @@ void CellClass::Redraw_Objects(bool forced)
if (Cell_Occupier()) {
ObjectClass * optr = Cell_Occupier();
while (optr) {
optr->Mark(MARK_CHANGE);
if (optr->IsActive) {
optr->Mark(MARK_CHANGE);
}
optr = optr->Next;
}
}
@ -2448,7 +2450,7 @@ void CellClass::Flag_Create(void)
CTFFlag = new AnimClass(ANIM_FLAG, Cell_Coord(), 0, 1, true);
}
assert(CTFFlag != NULL);
CTFFlag->OwnerHouse = Owner;
CTFFlag->Set_Owner(Owner);
}
}

View file

@ -2730,11 +2730,11 @@ extern void DLL_Draw_Intercept(int shape_number, int x, int y, int width, int he
extern void DLL_Draw_Pip_Intercept(const ObjectClass* object, int pip);
extern void DLL_Draw_Line_Intercept(int x, int y, int x1, int y1, unsigned char color, int frame);
void CC_Draw_Shape(ObjectClass *object, void const * shapefile, int shapenum, int x, int y, WindowNumberType window, ShapeFlags_Type flags, void const * fadingdata, void const * ghostdata, int scale)
void CC_Draw_Shape(ObjectClass *object, void const * shapefile, int shapenum, int x, int y, WindowNumberType window, ShapeFlags_Type flags, void const * fadingdata, void const * ghostdata, int scale, int width, int height)
{
if (window == WINDOW_VIRTUAL) {
int width = Get_Build_Frame_Width(shapefile);
int height = Get_Build_Frame_Height(shapefile);
if (width == 0) width = Get_Build_Frame_Width(shapefile);
if (height == 0) height = Get_Build_Frame_Height(shapefile);
DLL_Draw_Intercept(shapenum, x, y, width, height, (int)flags, object, NULL, -1, scale);
return;
}

View file

@ -1510,11 +1510,13 @@ typedef enum AnimType : char {
ANIM_RAPT_DIE,
ANIM_CHEM_BALL, // Chemical warrior explosion.
ANIM_FLAG, // CTF flag.
ANIM_BEACON, // Beacon.
ANIM_FIRE_SMALL_VIRTUAL, // Small flame animation (virtual).
ANIM_FIRE_MED_VIRTUAL, // Medium flame animation (virtual).
ANIM_FIRE_MED2_VIRTUAL, // Medium flame animation (oranger) (virtual).
ANIM_FIRE_TINY_VIRTUAL, // Very tiny flames (virtual).
ANIM_FIRE_SMALL_VIRTUAL, // Small flame animation (virtual).
ANIM_FIRE_MED_VIRTUAL, // Medium flame animation (virtual).
ANIM_FIRE_MED2_VIRTUAL, // Medium flame animation (oranger) (virtual).
ANIM_FIRE_TINY_VIRTUAL, // Very tiny flames (virtual).
ANIM_BEACON_VIRTUAL, // Beacon (virtual).
ANIM_COUNT,
ANIM_FIRST=0
@ -2309,6 +2311,8 @@ typedef enum VocType : char{
VOC_DINOATK1, // Dino attack sound.
VOC_DINODIE1, // Dino die sound.
VOC_BEACON, // Beacon sound.
#ifdef PETROGLYPH_EXAMPLE_MOD
VOC_NUKE_LOB, // Modded unit firing sound
#endif

View file

@ -1107,7 +1107,7 @@ void DisplayClass::Cursor_Mark(CELL pos, bool on)
CELL const *ptr;
CellClass *cellptr;
if (pos == -1) return;
if ((unsigned)pos >= MAP_CELL_TOTAL) return;
/*
** For every cell in the CursorSize list, invoke its Redraw_Objects and
@ -2965,6 +2965,7 @@ void DisplayClass::Select_These(COORDINATE coord1, COORDINATE coord2, bool addit
*/
if ( obj->Class_Of().IsSelectable &&
obj->What_Am_I() != RTTI_BUILDING &&
(!obj->Is_Techno() || !((TechnoClass*)obj)->Is_Cloaked(PlayerPtr)) &&
x >= x1 && x <= x2 && y >= y1 && y <= y2) {
bool old_allow_voice = AllowVoice;
bool is_player_controlled = obj->Owner() == PlayerPtr->Class->House;
@ -2992,6 +2993,7 @@ void DisplayClass::Select_These(COORDINATE coord1, COORDINATE coord2, bool addit
** selected, and are within the bounding box.
*/
if ( aircraft->Class_Of().IsSelectable &&
!aircraft->Is_Cloaked(PlayerPtr) &&
!aircraft->Is_Selected_By_Player() &&
x >= x1 && x <= x2 && y >= y1 && y <= y2) {
bool old_allow_voice = AllowVoice;

View file

@ -140,6 +140,7 @@ extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Sidebar_Request(Sidebar
extern "C" __declspec(dllexport) void __cdecl CNC_Handle_SuperWeapon_Request(SuperWeaponRequestEnum request_type, uint64 player_id, int buildable_type, int buildable_id, int x1, int y1);
extern "C" __declspec(dllexport) void __cdecl CNC_Handle_ControlGroup_Request(ControlGroupRequestEnum request_type, uint64 player_id, unsigned char control_group_index);
extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Debug_Request(DebugRequestEnum debug_request_type, uint64 player_id, const char *object_name, int x, int y, bool unshroud, bool enemy);
extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Beacon_Request(BeaconRequestEnum beacon_request_type, uint64 player_id, int pixel_x, int pixel_y);
extern "C" __declspec(dllexport) bool __cdecl CNC_Set_Multiplayer_Data(int scenario_index, CNCMultiplayerOptionsStruct &game_options, int num_players, CNCPlayerInfoStruct *player_list, int max_players);
extern "C" __declspec(dllexport) bool __cdecl CNC_Clear_Object_Selection(uint64 player_id);
extern "C" __declspec(dllexport) bool __cdecl CNC_Select_Object(uint64 player_id, int object_type_id, int object_to_select_id);
@ -148,7 +149,7 @@ extern "C" __declspec(dllexport) void __cdecl CNC_Set_Difficulty(int difficulty)
extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Player_Switch_To_AI(uint64 player_id);
extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Human_Team_Wins(uint64 player_id);
extern "C" __declspec(dllexport) void __cdecl CNC_Start_Mission_Timer(int time);
extern "C" __declspec(dllexport) bool __cdecl CNC_Get_Start_Game_Info(uint64 player_id, int &start_location_waypoint_index);
/*
@ -390,14 +391,13 @@ void Play_Movie_GlyphX(const char * movie_name, ThemeType theme)
void On_Sound_Effect(int sound_index, int variation, COORDINATE coord)
{
// MBL 02.26.2019
int voc = sound_index;
if (voc == VOC_NONE)
{
return;
}
// MBL 02.26.2019 - Borrowed from AUDIO.CPP Sound_Effect()
// Borrowed from AUDIO.CPP Sound_Effect()
//
#if 1
char const * ext = ""; // ".AUD";
@ -425,16 +425,12 @@ void On_Sound_Effect(int sound_index, int variation, COORDINATE coord)
}
}
#endif
// END MBL
DLLExportClass::On_Sound_Effect(PlayerPtr, sound_index, ext, variation, coord);
}
// MBL 02.06.2020
// void On_Speech(int speech_index)
void On_Speech(int speech_index, HouseClass *house)
{
// DLLExportClass::On_Speech(PlayerPtr, speech_index); // MBL 02.06.2020
if (house == NULL) {
DLLExportClass::On_Speech(PlayerPtr, speech_index);
}
@ -504,7 +500,6 @@ extern "C" __declspec(dllexport) void __cdecl CNC_Init(const char *command_line,
DLL_Startup(command_line);
// MBL
DLLExportClass::Set_Event_Callback( event_callback );
DLLExportClass::Init();
@ -879,7 +874,7 @@ void GlyphX_Assign_Houses(void)
preassigned = true;
}
}
if (!preassigned) {
if (!preassigned && i < MAX_PLAYERS) {
random_start_locations[num_random_start_locations] = num_start_locations;
num_random_start_locations++;
}
@ -1334,6 +1329,10 @@ extern "C" __declspec(dllexport) bool __cdecl CNC_Start_Custom_Instance(const ch
GlyphXClientSidebarWidthInLeptons = 0;
Play_Movie(IntroMovie);
Play_Movie(BriefMovie);
Play_Movie(ActionMovie, TransitTheme);
/*
if (!Start_Scenario(ScenarioName)) {
return(false);
@ -1701,13 +1700,13 @@ extern "C" __declspec(dllexport) bool __cdecl CNC_Save_Load(bool save, const cha
result = Load_Game(file_path_and_name);
// MBL 07.21.2020
if (result == false)
{
return false;
}
DLLExportClass::Set_Player_Context(DLLExportClass::GlyphxPlayerIDs[0], true);
DLLExportClass::Cancel_Placement(DLLExportClass::GlyphxPlayerIDs[0], -1, -1);
Set_Logic_Page(SeenBuff);
VisiblePage.Clear();
Map.Flag_To_Redraw(true);
@ -1860,6 +1859,24 @@ extern "C" __declspec(dllexport) void __cdecl CNC_Start_Mission_Timer(int time)
/**************************************************************************************************
* CNC_Get_Start_Game_Info
*
* History: 8/31/2020 11:37AM - ST
**************************************************************************************************/
extern "C" __declspec(dllexport) bool __cdecl CNC_Get_Start_Game_Info(uint64 player_id, int &start_location_waypoint_index)
{
start_location_waypoint_index = 0;
if (!DLLExportClass::Set_Player_Context(player_id)) {
return false;
}
start_location_waypoint_index = PlayerPtr->StartLocationOverride;
return true;
}
/**************************************************************************************************
* DLLExportClass::Init -- Init the class
*
@ -3682,6 +3699,31 @@ extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Input(InputRequestEnum
break;
}
// MBL 09.08.2020 - Mod Support
case INPUT_REQUEST_MOD_GAME_COMMAND_1_AT_POSITION:
case INPUT_REQUEST_MOD_GAME_COMMAND_2_AT_POSITION:
case INPUT_REQUEST_MOD_GAME_COMMAND_3_AT_POSITION:
case INPUT_REQUEST_MOD_GAME_COMMAND_4_AT_POSITION:
{
DLLExportClass::Adjust_Internal_View();
DLLForceMouseX = x1;
DLLForceMouseY = y1;
_Kbd->MouseQX = x1;
_Kbd->MouseQY = y1;
COORDINATE coord = Map.Pixel_To_Coord(x1, y1);
CELL cell = Coord_Cell(coord);
if (Map.Pixel_To_Coord(x1, y1))
{
// TBD: For our ever-awesome Community Modders!
//
// PlayerPtr->Handle_Mod_Game_Command(cell, input_event - INPUT_REQUEST_MOD_GAME_COMMAND_1_AT_POSITION);
}
break;
}
default:
break;
}
@ -3806,7 +3848,7 @@ extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Sidebar_Request(Sidebar
switch (request_type) {
// MBL 06.02.2020 - Changing right-click support for first put building on hold, and then subsequenct right-clicks to decrement that queue count for 1x or 5x; Then, 1x or 5x Left click will resume from hold
// Changing right-click support for first put building on hold, and then subsequenct right-clicks to decrement that queue count for 1x or 5x; Then, 1x or 5x Left click will resume from hold
// Handle and fall through to start construction (from hold state) below
case SIDEBAR_REQUEST_START_CONSTRUCTION_MULTI:
@ -6851,6 +6893,39 @@ extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Debug_Request(DebugRequ
}
extern "C" __declspec(dllexport) void __cdecl CNC_Handle_Beacon_Request(BeaconRequestEnum beacon_request_type, uint64 player_id, int pixel_x, int pixel_y)
{
if (!DLLExportClass::Set_Player_Context(player_id)) {
return;
}
// Beacons are only available if legacy rendering is disabled
if (DLLExportClass::Legacy_Render_Enabled()) {
return;
}
// Only allow one beacon per player
for (int index = 0; index < Anims.Count(); ++index) {
AnimClass* anim = Anims.Ptr(index);
if (anim != NULL &&
(*anim == ANIM_BEACON || *anim == ANIM_BEACON_VIRTUAL) &&
anim->OwnerHouse == PlayerPtr->Class->House) {
delete anim;
}
}
OutList.Add(EventClass(ANIM_BEACON, PlayerPtr->Class->House, Map.Pixel_To_Coord(pixel_x, pixel_y), PlayerPtr->Get_Allies()));
// Send sound effect to allies
for (int index = 0; index < Houses.Count(); ++index) {
HouseClass* hptr = Houses.Ptr(index);
if (hptr != NULL && hptr->IsActive && hptr->IsHuman && PlayerPtr->Is_Ally(hptr)) {
DLLExportClass::On_Sound_Effect(hptr, VOC_BEACON, "", 0, 0);
}
}
}
/**************************************************************************************************
* DLLExportClass::Debug_Spawn_All -- Debug spawn all buildable units and structures
*

View file

@ -28,7 +28,7 @@ struct CarryoverObjectStruct;
**
**
*/
#define CNC_DLL_API_VERSION 0x101
#include "DLLInterfaceVersion.h"
@ -421,7 +421,11 @@ enum InputRequestEnum {
INPUT_REQUEST_SELL_AT_POSITION,
INPUT_REQUEST_SELECT_AT_POSITION,
INPUT_REQUEST_COMMAND_AT_POSITION,
INPUT_REQUEST_SPECIAL_KEYS
INPUT_REQUEST_SPECIAL_KEYS,
INPUT_REQUEST_MOD_GAME_COMMAND_1_AT_POSITION,
INPUT_REQUEST_MOD_GAME_COMMAND_2_AT_POSITION,
INPUT_REQUEST_MOD_GAME_COMMAND_3_AT_POSITION,
INPUT_REQUEST_MOD_GAME_COMMAND_4_AT_POSITION,
};
@ -472,6 +476,18 @@ enum GameRequestEnum {
};
/**************************************************************************************
**
** Beacon Requests
**
**
*/
enum BeaconRequestEnum {
INPUT_BEACON_NONE,
INPUT_BEACON_PLACE,
};
/**************************************************************************************
**

View file

@ -0,0 +1,33 @@
//
// Copyright 2020 Electronic Arts Inc.
//
// TiberianDawn.DLL and RedAlert.dll and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// TiberianDawn.DLL and RedAlert.dll and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
#pragma once
#ifndef DLL_INTERFACE_VERSION_H
#define DLL_INTERFACE_VERSION_H
/*
** DLL Interface version
**
**
**
*/
#define CNC_DLL_API_VERSION 0x102
#endif //DLL_INTERFACE_VERSION_H

View file

@ -250,7 +250,7 @@ EventClass::EventClass(EventType type, TARGET src, TARGET dest)
* HISTORY: *
* 05/19/1995 JLB : Created. *
*=============================================================================================*/
EventClass::EventClass(AnimType anim, HousesType owner, COORDINATE coord)
EventClass::EventClass(AnimType anim, HousesType owner, COORDINATE coord, int visible)
{
ID = Houses.ID(PlayerPtr);
Type = ANIMATION;
@ -258,6 +258,7 @@ EventClass::EventClass(AnimType anim, HousesType owner, COORDINATE coord)
Data.Anim.What = anim;
Data.Anim.Owner = owner;
Data.Anim.Where = coord;
Data.Anim.Visible = visible;
}
@ -506,14 +507,26 @@ CCDebugString ("C&C95 - Sell packet received\n");
case ANIMATION:
anim = new AnimClass(Data.Anim.What, Data.Anim.Where);
if (anim) {
//2019/09/19 JAS - Visibility needs to be determined per player
if (Data.Anim.What != ANIM_MOVE_FLASH || Data.Anim.Owner == HOUSE_NONE || Special.IsVisibleTarget)
anim->Set_Owner(Data.Anim.Owner);
if (Special.IsVisibleTarget)
{
anim->Set_Visible_Flags(static_cast<unsigned int>(-1));
}
else
{
anim->Set_Visible_Flags(1 << Data.Anim.Owner);
anim->Set_Visible_Flags(static_cast<unsigned int>(Data.Anim.Visible));
}
/*
** Beacons have a 30-second kill time.
*/
if (Data.Anim.What == ANIM_BEACON) {
FILETIME ft;
GetSystemTimeAsFileTime(&ft);
unsigned long long kill_time = ((unsigned long long)ft.dwLowDateTime + ((unsigned long long)ft.dwHighDateTime << 32ULL)) + 300000000ULL;
anim->Kill_At(kill_time);
}
}
break;

View file

@ -127,6 +127,7 @@ class EventClass
AnimType What; // The animation to create.
HousesType Owner; // The owner of the animation (when it matters).
COORDINATE Where; // The location to place the animation.
int Visible; // Who this animation is visible to.
} Anim;
struct {
int Value; // general-purpose data
@ -208,7 +209,7 @@ class EventClass
EventClass(EventType type, RTTIType object, int id);
EventClass(EventType type, RTTIType object, CELL cell);
EventClass(EventType type, int id, CELL cell);
EventClass(AnimType anim, HousesType owner, COORDINATE coord);
EventClass(AnimType anim, HousesType owner, COORDINATE coord, int visible = -1);
// Process the event.
void Execute(void);

View file

@ -305,14 +305,20 @@ bool FootClass::Mark(MarkType mark)
/*
** Inform the map of the refresh, occupation, and overlap
** request.
** Special case is fixed-wing aircraft, which are never placed
** or picked up since they can never land.
*/
switch (mark) {
case MARK_UP:
Map.Pick_Up(cell, this);
if (What_Am_I() != RTTI_AIRCRAFT || !((AircraftClass*)this)->Class->IsFixedWing) {
Map.Pick_Up(cell, this);
}
break;
case MARK_DOWN:
Map.Place_Down(cell, this);
if (What_Am_I() != RTTI_AIRCRAFT || !((AircraftClass*)this)->Class->IsFixedWing) {
Map.Place_Down(cell, this);
}
break;
default:
@ -947,8 +953,10 @@ void FootClass::Approach_Target(void)
/*
** If a suitable intermediate location was found, then head toward it.
** Otherwise, head toward the enemy unit directly.
** Infantry always head towards the target since they can enter a cell
** in range, but still not be able to hit the target if the spot is out of range.
*/
if (found) {
if (found && What_Am_I() != RTTI_INFANTRY) {
Assign_Destination(::As_Target(trycell));
} else {
Assign_Destination(TarCom);
@ -990,6 +998,20 @@ int FootClass::Mission_Guard_Area(void)
ArchiveTarget = ::As_Target(Coord_Cell(Coord));
}
/*
** Ensure units aren't trying to guard cells off the map.
*/
if (Target_Legal(NavCom) && Is_Target_Cell(NavCom)) {
CELL cell = As_Cell(NavCom);
int x = Cell_X(cell);
int y = Cell_Y(cell);
if (x < Map.MapCellX || y < Map.MapCellY || x >= (Map.MapCellX + Map.MapCellWidth) || y >= (Map.MapCellY + Map.MapCellHeight)) {
Assign_Target(TARGET_NONE);
Assign_Destination(TARGET_NONE);
ArchiveTarget = ::As_Target(Coord_Cell(Coord));
}
}
/*
** Make sure that the unit has not strayed too far from the home position.
** If it has, then race back to it.
@ -1320,7 +1342,7 @@ void FootClass::Active_Click_With(ActionType action, CELL cell)
case ACTION_MOVE:
if (AllowVoice) {
COORDINATE coord = Map.Pixel_To_Coord(Get_Mouse_X(), Get_Mouse_Y());
OutList.Add(EventClass(ANIM_MOVE_FLASH, PlayerPtr->Class->House, coord));
OutList.Add(EventClass(ANIM_MOVE_FLASH, PlayerPtr->Class->House, coord, 1 << PlayerPtr->Class->House));
}
// Fall into next case.

View file

@ -360,7 +360,7 @@ void const *Get_Radar_Icon(void const *shapefile, int shapenum, int frames, int
void CC_Draw_Shape(void const * shapefile, int shapenum, int x, int y, WindowNumberType window, ShapeFlags_Type flags, void const * fadingdata=0, void const * ghostdata=0);
// Added for draw intercept. ST - 1/17/2019 12:31PM
void CC_Draw_Shape(ObjectClass *object, void const * shapefile, int shapenum, int x, int y, WindowNumberType window, ShapeFlags_Type flags, void const * fadingdata=0, void const * ghostdata=0, int scale=0x100);
void CC_Draw_Shape(ObjectClass *object, void const * shapefile, int shapenum, int x, int y, WindowNumberType window, ShapeFlags_Type flags, void const * fadingdata=0, void const * ghostdata=0, int scale=0x100, int width=0, int height=0);
void CC_Draw_Shape(ObjectClass *object, const char *shape_file_name, void const * shapefile, int shapenum, int x, int y, WindowNumberType window, ShapeFlags_Type flags, void const * fadingdata = 0, void const * ghostdata = 0, char override_owner = HOUSE_NONE);
// Added for pip draw intercept - SKY

View file

@ -560,7 +560,7 @@ DynamicVectorClass <int> MPlayerFilenum;
/***************************************************************************
** This value determines the max allowable # of players.
*/
int MPlayerMax = 4;
int MPlayerMax = 6;
/***************************************************************************

View file

@ -1673,14 +1673,13 @@ void HouseClass::Attacked(BuildingClass* source)
{
Validate();
bool expired = SpeakAttackDelay.Expired();
bool spoke = false;
if (SpeakAttackDelay.Expired() && PlayerPtr->Class->House == Class->House) {
// if (SpeakAttackDelay.Expired() && PlayerPtr->Class->House == Class->House) {
if (expired && PlayerPtr->Class->House == Class->House) {
Speak(VOX_BASE_UNDER_ATTACK, NULL, source ? source->Center_Coord() : 0);
spoke = true;
if (GameToPlay == GAME_NORMAL) {
Speak(VOX_BASE_UNDER_ATTACK, NULL, source ? source->Center_Coord() : 0);
} else {
Speak(VOX_BASE_UNDER_ATTACK, this);
}
// MBL 06.13.2020 - Timing change from 2 minute cooldown, per https://jaas.ea.com/browse/TDRA-6784
// SpeakAttackDelay.Set(Options.Normalize_Delay(SPEAK_DELAY)); // 2 minutes
@ -1695,23 +1694,6 @@ void HouseClass::Attacked(BuildingClass* source)
HouseTriggers[Class->House][index]->Spring(EVENT_ATTACKED, Class->House);
}
}
// MBL 07.07.2020 - CNC Patch 3, fix for not working for all players in MP, per https://jaas.ea.com/browse/TDRA-7249
// Separated to here as did not want to change any logic around the HouseTriggers[] Spring events
//
if (expired == true && spoke == false)
{
if (GameToPlay != GAME_NORMAL) // Multiplayer
{
Speak(VOX_BASE_UNDER_ATTACK, this);
spoke = true;
// SpeakAttackDelay.Set(Options.Normalize_Delay(SPEAK_DELAY)); // 2 minutes
// SpeakAttackDelay.Set(Options.Normalize_Delay(TICKS_PER_MINUTE/2)); // 30 seconds as requested
SpeakAttackDelay.Set(Options.Normalize_Delay( (TICKS_PER_MINUTE/2)+(TICKS_PER_SECOND*5) )); // Tweaked for accuracy
}
}
// END MBL 07.07.2020
}
@ -2584,7 +2566,15 @@ ProdFailType HouseClass::Abandon_Production(RTTIType type)
if (GameToPlay == GAME_GLYPHX_MULTIPLAYER) {
if (IsHuman) {
Sidebar_Glyphx_Abandon_Production(type, *factory, this);
// Need to clear pending object here?
// Need to clear pending object here if legacy renderer enabled
if (type == RTTI_BUILDINGTYPE || type == RTTI_BUILDING && Map.PendingObjectPtr) {
Map.PendingObjectPtr = 0;
Map.PendingObject = 0;
Map.PendingHouse = HOUSE_NONE;
Map.Set_Cursor_Shape(0);
}
}
} else {
@ -2692,7 +2682,7 @@ bool HouseClass::Place_Special_Blast(SpecialWeaponType id, CELL cell)
case SPC_ION_CANNON:
if (IonCannon.Is_Ready()) {
anim = new AnimClass(ANIM_ION_CANNON, Cell_Coord(cell));
if (anim) anim->OwnerHouse = Class->House;
if (anim) anim->Set_Owner(Class->House);
if (this == PlayerPtr) {
Map.IsTargettingMode = false;
}

View file

@ -444,6 +444,7 @@ class HouseClass {
bool Is_Ally(HousesType house) const;
bool Is_Ally(HouseClass const * house) const;
bool Is_Ally(ObjectClass const * object) const;
unsigned int Get_Allies(void) const {return Allies;}
#ifdef CHEAT_KEYS
void Debug_Dump(MonoClass *mono) const;
#endif
@ -517,6 +518,9 @@ class HouseClass {
void Init_Unit_Trackers(void);
void Free_Unit_Trackers(void);
// MBL 09.08.2020 Mod support stub
void Handle_Mod_Game_Command(CELL cell, int mod_command_index); // mod_command_index = 0-3
#ifdef USE_RA_AI
/*
** AI Functions imported from RA

View file

@ -677,6 +677,10 @@ void InfantryClass::Per_Cell_Process(bool center)
*/
if (center && Mission == MISSION_CAPTURE) {
TechnoClass * tech = cellptr->Cell_Techno();
if (tech && tech->As_Target() == NavCom && tech->What_Am_I() == RTTI_BUILDING && !tech->Can_Capture()) {
tech = NULL;
Assign_Destination(TARGET_NONE);
}
if (tech && tech->As_Target() == NavCom) {
tech->Captured(House);
Delete_This();

View file

@ -491,11 +491,15 @@ bool Read_Scenario_Ini(char *root, bool fresh)
** Build the full text of the mission objective.
*/
for (;;) {
char buff[16];
int len = (sizeof(BriefingText)-strlen(BriefingText))-1;
if (len <= 0) {
break;
}
char buff[16];
sprintf(buff, "%d", index++);
*stage = '\0';
WWGetPrivateProfileString("Briefing", buff, "", stage, (sizeof(BriefingText)-strlen(BriefingText))-1, buffer);
WWGetPrivateProfileString("Briefing", buff, "", stage, len, buffer);
if (strlen(stage) == 0) break;
strcat(stage, " ");
stage += strlen(stage);
@ -541,6 +545,7 @@ bool Read_Scenario_Ini(char *root, bool fresh)
** NOD09A - delete airstrike trigger when radar destroyed
** NOD10B cell 2015 - LAND_ROCK
** NOD13B - trigger AI production when the player reaches the transports
- fix repeating airstrike trigger
** NOD13C - delete airstrike trigger when radar destroyed
*/
if (_stricmp(ScenarioName, "scb07ea") == 0) {
@ -572,6 +577,10 @@ bool Read_Scenario_Ini(char *root, bool fresh)
CellTriggers[340] = prod; prod->AttachCount++;
CellTriggers[404] = prod; prod->AttachCount++;
CellTriggers[468] = prod; prod->AttachCount++;
TriggerClass* xxxx = TriggerClass::As_Pointer("xxxx");
assert(xxxx != NULL);
xxxx->IsPersistant = TriggerClass::PERSISTANT;
}
if (_stricmp(ScenarioName, "scb13ec") == 0) {
for (int index = 0; index < Buildings.Count(); ++index) {
@ -920,11 +929,15 @@ bool Read_Scenario_Ini_File(char *scenario_file_name, char* bin_file_name, const
** Build the full text of the mission objective.
*/
for (;;) {
char buff[16];
int len = (sizeof(BriefingText) - strlen(BriefingText)) - 1;
if (len <= 0) {
break;
}
char buff[16];
sprintf(buff, "%d", index++);
*stage = '\0';
WWGetPrivateProfileString("Briefing", buff, "", stage, (sizeof(BriefingText) - strlen(BriefingText)) - 1, buffer);
WWGetPrivateProfileString("Briefing", buff, "", stage, len, buffer);
if (strlen(stage) == 0) break;
strcat(stage, " ");
stage += strlen(stage);

View file

@ -1560,6 +1560,10 @@ bool UnitClass::Save(FileClass & file)
*=============================================================================================*/
void UnitClass::Code_Pointers(void)
{
if (TiberiumUnloadRefinery) {
TiberiumUnloadRefinery = (BuildingClass *)TiberiumUnloadRefinery->As_Target();
}
TarComClass::Code_Pointers();
}
@ -1584,6 +1588,11 @@ void UnitClass::Code_Pointers(void)
*=============================================================================================*/
void UnitClass::Decode_Pointers(void)
{
if (TiberiumUnloadRefinery) {
TiberiumUnloadRefinery = As_Building((TARGET)TiberiumUnloadRefinery, false);
Check_Ptr((void *)TiberiumUnloadRefinery, __FILE__, __LINE__);
}
TarComClass::Decode_Pointers();
}

View file

@ -1,6 +1,4 @@
Electronic Arts Inc. released only TiberianDawn.dll, RedAlert.dll and
the Command & Conquer Map Editor and their corresponding source code
under the GPL V3 below, with additional terms at the bottom.
Electronic Arts Inc. released only TiberianDawn.dll and RedAlert.dll and their corresponding source code under the GPL V3 below, with additional terms at the bottom.
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
@ -711,4 +709,4 @@ PROVIDED BY ELECTRONIC ARTS OR ANY AUTHORIZED REPRESENTATIVE SHALL CREATE A
WARRANTY. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF OR LIMITATIONS ON
IMPLIED WARRANTIES OR THE LIMITATIONS ON THE APPLICABLE STATUTORY RIGHTS OF A
CONSUMER, SO SOME OR ALL OF THE ABOVE EXCLUSIONS AND LIMITATIONS MAY NOT APPLY
TO YOU.
TO YOU.

View file

@ -428,7 +428,7 @@ dxisbig:
#if (0)
/*
; $Header: //depot/Projects/Mobius/QA/Project/Run/SOURCECODE/TIBERIANDAWN/MiscAsm.cpp#97 $
; $Header: //depot/Projects/Mobius/QA/Project/Run/SOURCECODE/TIBERIANDAWN/MiscAsm.cpp#131 $
;***************************************************************************
;** C O N F I D E N T I A L --- W E S T W O O D A S S O C I A T E S **
;***************************************************************************

View file

@ -317,7 +317,7 @@ int DLL_Startup(const char * command_line_in)
if (Parse_Command_Line(argc, argv)) {
WindowsTimer = new WinTimerClass(60,FALSE);
#if (0)
int time_test = WindowsTimer->Get_System_Tick_Count();
Sleep (1000);
if (WindowsTimer->Get_System_Tick_Count() == time_test){
@ -332,6 +332,7 @@ int DLL_Startup(const char * command_line_in)
#endif //FRENCH
return(EXIT_FAILURE);
}
#endif
RawFileClass cfile("CONQUER.INI");

View file

@ -1402,7 +1402,7 @@ bool TechnoClass::Evaluate_Object(ThreatType method, int mask, int range, Techno
** If the scan is limited to capturable buildings only, then bail if the examined
** object isn't a capturable building.
*/
if ((method & THREAT_CAPTURE) && (otype != RTTI_BUILDING || !((BuildingTypeClass const *)tclass)->IsCaptureable)) {
if ((method & THREAT_CAPTURE) && (otype != RTTI_BUILDING || !object->Can_Capture())) {
return(false);
}

View file

@ -206,6 +206,7 @@
<ClInclude Include="DIAL8.H" />
<ClInclude Include="DISPLAY.H" />
<ClInclude Include="DLLInterface.h" />
<ClInclude Include="DLLInterfaceVersion.h" />
<ClInclude Include="DOOR.H" />
<ClInclude Include="DPMI.H" />
<ClInclude Include="DRIVE.H" />

View file

@ -627,6 +627,9 @@
<ClInclude Include="RULES.H">
<Filter>Source Files</Filter>
</ClInclude>
<ClInclude Include="DLLInterfaceVersion.h">
<Filter>Source Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="AADATA.CPP">

View file

@ -396,6 +396,20 @@ void UnitClass::AI(void)
return;
}
/*
** Clear the unload refinery if not haresting or entering a refinery.
*/
if (Class->IsToHarvest) {
if (Mission != MISSION_HARVEST) {
if (Mission != MISSION_ENTER ||
!In_Radio_Contact() ||
Contact_With_Whom()->What_Am_I() != RTTI_BUILDING ||
*((BuildingClass*)Contact_With_Whom()) != STRUCT_REFINERY) {
TiberiumUnloadRefinery = NULL;
}
}
}
/*
** Rocket launchers will reload every so often.
*/
@ -1198,6 +1212,7 @@ UnitClass::UnitClass(UnitType classid, HousesType house) :
Reload = 0;
Ammo = Class->MaxAmmo;
IsCloakable = Class->IsCloakable;
TiberiumUnloadRefinery = NULL;
if (Class->IsAnimating) Set_Rate(Options.Normalize_Delay(3));
/*
@ -2345,29 +2360,47 @@ bool UnitClass::Goto_Tiberium(void)
int tiberium = 0;
int besttiberium = 0;
for (int x = -radius; x <= radius; x++) {
/*
** Randomize the corners.
*/
int corner[2];
int corners[4][2] = {
{x, -radius},
{x, +radius},
{-radius, x},
{+radius, x}
};
for (int i = 0; i < 3; i++) {
int j = i + rand() / (RAND_MAX / (4 - i) + 1);
memcpy(&corner, &corners[j], sizeof(corner));
memcpy(&corners[j], &corners[i], sizeof(corner));
memcpy(&corners[i], corner, sizeof(corner));
}
cell = center;
tiberium = Tiberium_Check(cell, x, -radius);
tiberium = Tiberium_Check(cell, corners[0][0], corners[0][1]);
if (tiberium > besttiberium) {
bestcell = cell;
besttiberium = tiberium;
}
cell = center;
tiberium = Tiberium_Check(cell, x, +radius);
tiberium = Tiberium_Check(cell, corners[1][0], corners[1][1]);
if (tiberium > besttiberium) {
bestcell = cell;
besttiberium = tiberium;
}
cell = center;
tiberium = Tiberium_Check(cell, -radius, x);
tiberium = Tiberium_Check(cell, corners[2][0], corners[2][1]);
if (tiberium > besttiberium) {
bestcell = cell;
besttiberium = tiberium;
}
cell = center;
tiberium = Tiberium_Check(cell, +radius, x);
tiberium = Tiberium_Check(cell, corners[3][0], corners[3][1]);
if (tiberium > besttiberium) {
bestcell = cell;
besttiberium = tiberium;
@ -2385,6 +2418,96 @@ bool UnitClass::Goto_Tiberium(void)
}
struct RefineryData
{
BuildingClass* Refinery;
int Distance;
int Harvesters;
};
static bool operator==(const RefineryData& lhs, const RefineryData& rhs)
{
return lhs.Refinery == rhs.Refinery;
}
static bool operator!=(const RefineryData& lhs, const RefineryData& rhs)
{
return !(lhs == rhs);
}
static int _refinery_compare(const void * left, const void * right)
{
const RefineryData& lhs = *reinterpret_cast<const RefineryData*>(left);
const RefineryData& rhs = *reinterpret_cast<const RefineryData*>(right);
if (lhs.Distance < rhs.Distance) {
return -1;
} else if (rhs.Distance < lhs.Distance) {
return 1;
}
return 0;
}
BuildingClass* UnitClass::Find_Best_Refinery(void) const
{
static DynamicVectorClass<RefineryData> _refineries;
_refineries.Clear();
for (int i = 0; i < Buildings.Count(); ++i) {
BuildingClass* refinery = Buildings.Ptr(i);
if (refinery != NULL &&
refinery->House == House &&
!refinery->IsInLimbo &&
*refinery == STRUCT_REFINERY) {
_refineries.Add(RefineryData{ refinery, Distance(refinery), 0 });
}
}
// Base case for zero or one refineries.
if (_refineries.Count() == 0) {
return NULL;
} else if (_refineries.Count() == 1) {
return _refineries[0].Refinery;
}
// Count harvesters going to each refinery as well as the total.
int num_harvesters = 0;
for (int i = 0; i < Units.Count(); ++i) {
UnitClass* unit = Units.Ptr(i);
if (unit->IsActive && unit->Class->IsToHarvest && unit->House == House) {
BuildingClass* refinery = unit->Tiberium_Unload_Refinery();
if (refinery != NULL) {
int index = _refineries.ID(RefineryData{ refinery });
assert(index >= 0);
_refineries[index].Harvesters++;
num_harvesters++;
}
}
}
// Sort by distance (special case for 2 refineries as that's a single swap).
if (_refineries.Count() == 2) {
if (_refineries[0].Distance > _refineries[1].Distance) {
RefineryData temp = _refineries[0];
_refineries[0] = _refineries[1];
_refineries[1] = temp;
}
} else {
qsort(&_refineries[0], _refineries.Count(), sizeof(RefineryData), _refinery_compare);
}
// Evenly distribute harvesters among refineries.
int harvesters_per_refinery = (num_harvesters + _refineries.Count() - 1) / _refineries.Count();
for (int i = 0; i < _refineries.Count(); ++i) {
if (_refineries[i].Harvesters < harvesters_per_refinery) {
return _refineries[i].Refinery;
}
}
// Fall back on closest refinery
return _refineries[0].Refinery;
}
/***********************************************************************************************
* UnitClass::Harvesting -- Harvests tiberium at the current location. *
* *
@ -2677,7 +2800,10 @@ int UnitClass::Mission_Harvest(void)
Assign_Target(TARGET_NONE);
Status = FINDHOME;
return(1);
} else if (Goto_Tiberium()) {
}
TiberiumUnloadRefinery = NULL;
if (Goto_Tiberium()) {
IsHarvesting = true;
Set_Rate(2);
Set_Stage(0);
@ -2739,21 +2865,14 @@ int UnitClass::Mission_Harvest(void)
if (!Target_Legal(NavCom)) {
/*
** Find nearby refinery and head to it?
** Find nearby refinery and head to it.
*/
BuildingClass * nearest = Find_Docking_Bay(STRUCT_REFINERY, false);
/*
** Since the refinery said it was ok to load, establish radio
** contact with the refinery and then await docking orders.
*/
if (nearest && Transmit_Message(RADIO_HELLO, nearest) == RADIO_ROGER) {
Status = HEADINGHOME;
} else {
ScenarioInit++;
nearest = Find_Docking_Bay(STRUCT_REFINERY, false);
ScenarioInit--;
if (nearest) {
BuildingClass * nearest = Find_Best_Refinery();
if (nearest) {
TiberiumUnloadRefinery = nearest;
if (Transmit_Message(RADIO_HELLO, nearest) == RADIO_ROGER) {
Status = HEADINGHOME;
} else {
Assign_Destination(::As_Target(nearest->Nearby_Location(this)));
}
}
@ -2770,6 +2889,7 @@ int UnitClass::Mission_Harvest(void)
return(1);
case GOINGTOIDLE:
TiberiumUnloadRefinery = NULL;
Assign_Mission(MISSION_GUARD);
break;

View file

@ -84,6 +84,8 @@ class UnitClass : public TarComClass
bool Harvesting(void);
void APC_Close_Door(void);
void APC_Open_Door(void);
BuildingClass* Tiberium_Unload_Refinery(void) const {return TiberiumUnloadRefinery;}
BuildingClass* Find_Best_Refinery(void) const;
/*
** Query functions.
@ -199,10 +201,15 @@ class UnitClass : public TarComClass
*/
TCountDownTimerClass HarvestTimer;
/*
** This is the refinery a harvester is interested in unloading at.
*/
BuildingClass* TiberiumUnloadRefinery;
/*
** Some additional padding in case we need to add data to the class and maintain backwards compatibility for save/load
*/
unsigned char SaveLoadPadding[32];
unsigned char SaveLoadPadding[28];
/*
** This contains the value of the Virtual Function Table Pointer

View file

@ -4841,7 +4841,7 @@ extern "C" int __cdecl Confine_Rect ( int * x , int * y , int w , int h , int wi
/*
; $Header: //depot/Projects/Mobius/QA/Project/Run/SOURCECODE/TIBERIANDAWN/WIN32LIB/DrawMisc.cpp#97 $
; $Header: //depot/Projects/Mobius/QA/Project/Run/SOURCECODE/TIBERIANDAWN/WIN32LIB/DrawMisc.cpp#131 $
;***************************************************************************
;** C O N F I D E N T I A L --- W E S T W O O D A S S O C I A T E S **
;***************************************************************************

View file

@ -321,7 +321,7 @@ int __cdecl Desired_Facing8(long x1, long y1, long x2, long y2);
/*
; $Header: //depot/Projects/Mobius/QA/Project/Run/SOURCECODE/TIBERIANDAWN/WIN32LIB/FACINGFF.h#97 $
; $Header: //depot/Projects/Mobius/QA/Project/Run/SOURCECODE/TIBERIANDAWN/WIN32LIB/FACINGFF.h#131 $
;***************************************************************************
;** C O N F I D E N T I A L --- W E S T W O O D A S S O C I A T E S **
;***************************************************************************