CnC_Remastered_Collection/TIBERIANDAWN/EVENT.CPP
PG-SteveT fd05be35c1 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
2020-09-16 10:03:04 -07:00

799 lines
36 KiB
C++

//
// 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
/* $Header: F:\projects\c&c\vcs\code\event.cpv 2.17 16 Oct 1995 16:50:28 JOE_BOSTIC $ */
/***********************************************************************************************
*** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ***
***********************************************************************************************
* *
* Project Name : Command & Conquer *
* *
* File Name : EVENT.CPP *
* *
* Programmer : Joe L. Bostic *
* *
* Start Date : 12/09/94 *
* *
* Last Update : June 25, 1995 [JLB] *
* *
*---------------------------------------------------------------------------------------------*
* Functions: *
* EventClass::EventClass -- Construct an id and cell based event. *
* EventClass::EventClass -- Construct simple target type event. *
* EventClass::EventClass -- Constructor for mission change events. *
* EventClass::EventClass -- Constructor for navigation computer events. *
* EventClass::EventClass -- Constructor for object types affecting cells event. *
* EventClass::EventClass -- Constructor for sidebar build events. *
* EventClass::EventClass -- Constructs event to transfer special flags. *
* EventClass::EventClass -- Default constructor for event objects. *
* EventClass::EventClass -- Event for sequencing animations. *
* EventClass::EventClass -- Megamission assigned to unit. *
* EventClass::Execute -- Execute a queued command. *
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#include "function.h"
#include "ccdde.h"
/***************************************************************************
** Table of what data is really used in the EventClass struct for different
** events. This table must be kept current with the EventType enum.
*/
unsigned char EventClass::EventLength[EventClass::LAST_EVENT] = {
0, // EMPTY
size_of(EventClass, Data.General ), // ALLY
size_of(EventClass, Data.MegaMission ), // MEGAMISSION
size_of(EventClass, Data.Target ), // IDLE
size_of(EventClass, Data.Target ), // SCATTER
0, // DESTRUCT
0, // DEPLOY
size_of(EventClass, Data.Place ), // PLACE
0, // OPTIONS
size_of(EventClass, Data.General ), // GAMESPEED
size_of(EventClass, Data.Specific ), // PRODUCE
size_of(EventClass, Data.Specific.Type ), // SUSPEND
size_of(EventClass, Data.Specific.Type ), // ABANDON
size_of(EventClass, Data.Target ), // PRIMARY
size_of(EventClass, Data.Special ), // SPECIAL_PLACE
0, // EXIT
size_of(EventClass, Data.Anim ), // ANIMATION
size_of(EventClass, Data.Target ), // REPAIR
size_of(EventClass, Data.Target ), // SELL
size_of(EventClass, Data.Options ), // SPECIAL
0, // FRAMESYNC
0, // MESSAGE
size_of(EventClass, Data.FrameInfo.Delay ), // RESPONSE_TIME
size_of(EventClass, Data.FrameInfo ), // FRAMEINFO
size_of(EventClass, Data.NavCom ), // ARCHIVE
size_of(EventClass, Data.Timing ), // TIMING
size_of(EventClass, Data.ProcessTime ), // PROCESS_TIME
};
char * EventClass::EventNames[EventClass::LAST_EVENT] = {
"EMPTY",
"ALLY",
"MEGAMISSION",
"IDLE",
"SCATTER",
"DESTRUCT",
"DEPLOY",
"PLACE",
"OPTIONS",
"GAMESPEED",
"PRODUCE",
"SUSPEND",
"ABANDON",
"PRIMARY",
"SPECIAL_PLACE",
"EXIT",
"ANIMATION",
"REPAIR",
"SELL",
"SPECIAL",
"FRAMESYNC",
"MESSAGE",
"RESPONSE_TIME",
"FRAMEINFO",
"ARCHIVE",
"TIMING",
"PROCESS_TIME",
};
/***********************************************************************************************
* EventClass::EventClass -- Constructs event to transfer special flags. *
* *
* This constructs an event that will transfer the special flags. *
* *
* INPUT: data -- The special flags to be transported to all linked computers. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 06/25/1995 JLB : Created. *
*=============================================================================================*/
EventClass::EventClass(SpecialClass data)
{
ID = Houses.ID(PlayerPtr);
Type = SPECIAL;
Frame = ::Frame;
Data.Options.Data = data;
}
/***********************************************************************************************
* EventClass::EventClass -- Construct simple target type event. *
* *
* This will construct a generic event that needs only a target parameter. The actual *
* event and target values are specified as parameters. *
* *
* INPUT: type -- The event type to construct. *
* *
* target-- The target value that this event is to apply to. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 06/25/1995 JLB : Created. *
*=============================================================================================*/
EventClass::EventClass(EventType type, TARGET target)
{
ID = Houses.ID(PlayerPtr);
Type = type;
Frame = ::Frame;
Data.Target.Whom = target;
}
/***********************************************************************************************
* EventClass::EventClass -- Default constructor for event objects. *
* *
* This constructs a simple event object that requires no parameters other than the *
* type of event it is. *
* *
* INPUT: type -- The type of event to construct. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 12/27/1994 JLB : Created. *
*=============================================================================================*/
EventClass::EventClass(EventType type)
{
ID = Houses.ID(PlayerPtr);
Type = type;
Frame = ::Frame;
}
/***********************************************************************************************
* EventClass::EventClass -- Constructor for general-purpose-data events. *
* *
* INPUT: type -- The type of event to construct. *
* val -- data value *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 12/27/1994 JLB : Created. *
*=============================================================================================*/
EventClass::EventClass(EventType type, int val)
{
ID = Houses.ID(PlayerPtr);
Type = type;
Data.General.Value = val;
Frame = ::Frame;
}
/***********************************************************************************************
* EventClass::EventClass -- Constructor for navigation computer events. *
* *
* Constructor for events that are used to assign the navigation computer. *
* *
* INPUT: type -- The type of event (this constructor can be used by other navigation *
* type events). *
* *
* src -- The object that the event should apply to. *
* *
* dest -- The destination (or target) that the event needs to complete. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 12/27/1994 JLB : Created. *
*=============================================================================================*/
EventClass::EventClass(EventType type, TARGET src, TARGET dest)
{
ID = Houses.ID(PlayerPtr);
Type = type;
Frame = ::Frame;
Data.NavCom.Whom = src;
Data.NavCom.Where = dest;
}
/***********************************************************************************************
* EventClass::EventClass -- Event for sequencing animations. *
* *
* This constructor is used for animations that must be created through the event system. *
* *
* INPUT: anim -- The animation that will be created. *
* *
* coord -- The location where the animation is to be created. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 05/19/1995 JLB : Created. *
*=============================================================================================*/
EventClass::EventClass(AnimType anim, HousesType owner, COORDINATE coord, int visible)
{
ID = Houses.ID(PlayerPtr);
Type = ANIMATION;
Frame = ::Frame;
Data.Anim.What = anim;
Data.Anim.Owner = owner;
Data.Anim.Where = coord;
Data.Anim.Visible = visible;
}
/***********************************************************************************************
* EventClass::EventClass -- Megamission assigned to unit. *
* *
* This is the event that is used to assign most missions to units. It combines both the *
* mission and the target (navcom and tarcom). *
* *
* INPUT: src -- The object that this mission is to apply to. *
* *
* mission -- The mission to assign to this object. *
* *
* target -- The target to assign to this object's TarCom. *
* *
* destination -- The destination to assign to this object's NavCom. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 05/18/1995 JLB : Created. *
*=============================================================================================*/
EventClass::EventClass(TARGET src, MissionType mission, TARGET target, TARGET destination)
{
ID = Houses.ID(PlayerPtr);
Type = MEGAMISSION;
Frame = ::Frame;
Data.MegaMission.Whom = src;
Data.MegaMission.Mission = mission;
Data.MegaMission.Target = target;
Data.MegaMission.Destination = destination;
}
/***********************************************************************************************
* EventClass::EventClass -- Constructor for sidebar build events. *
* *
* This constructor is used for events that deal with an object type and an object ID. *
* Typically, this is used exclusively by the sidebar. *
* *
* INPUT: type -- The event type of this object. *
* *
* object -- The object type number. *
* *
* id -- The object sub-type number. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 05/18/1995 JLB : Created. *
*=============================================================================================*/
EventClass::EventClass(EventType type, RTTIType object, int id)
{
ID = Houses.ID(PlayerPtr);
Type = type;
Frame = ::Frame;
Data.Specific.Type = object;
Data.Specific.ID = id;
}
/***********************************************************************************************
* EventClass::EventClass -- Constructor for object types affecting cells event. *
* *
* This constructor is used for those events that have an object type and associated cell. *
* Typically, this is for building placement after construction has completed. *
* *
* INPUT: type -- The event type for this object. *
* *
* object -- The object type number (actual object is probably inferred from the *
* sidebar data). *
* *
* cell -- The cell location where this event is to occur. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 05/18/1995 JLB : Created. *
*=============================================================================================*/
EventClass::EventClass(EventType type, RTTIType object, CELL cell)
{
ID = Houses.ID(PlayerPtr);
Type = type;
Frame = ::Frame;
Data.Place.Type = object;
Data.Place.Cell = cell;
}
/***********************************************************************************************
* EventClass::EventClass -- Construct an id and cell based event. *
* *
* This constructor is used for those events that require an ID number and a cell location. *
* *
* INPUT: type -- The event type this will be. *
* *
* id -- The arbitrary id number to assign. *
* *
* cell -- The location for this event. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 05/18/1995 JLB : Created. *
*=============================================================================================*/
EventClass::EventClass(EventType type, int id, CELL cell)
{
ID = Houses.ID(PlayerPtr);
Type = type;
Frame = ::Frame;
Data.Special.ID = id;
Data.Special.Cell = cell;
}
/***********************************************************************************************
* EventClass::Execute -- Execute a queued command. *
* *
* This routine executes an event. The even must already have been confirmed by any *
* remote machine before calling this routine. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 12/27/1994 JLB : Created. *
*=============================================================================================*/
void EventClass::Execute(void)
{
TechnoClass * techno;
AnimClass * anim = 0;
HouseClass * house = 0;
char txt[80];
int i;
//#if (0)
if (Type < 0 || Type > PROCESS_TIME){
char tempbuf[128];
sprintf (tempbuf, "Packet type %d received\n", Type);
CCDebugString (tempbuf);
sprintf (tempbuf, " ID = %d\n", ID);
CCDebugString (tempbuf);
sprintf (tempbuf, " Frame = %d\n", Frame);
CCDebugString (tempbuf);
sprintf (tempbuf, " MPlayer ID = %d\n", MPlayerID);
CCDebugString (tempbuf);
}
//#endif //(0)
switch (Type) {
/*
** Update the archive target for this building.
*/
case ARCHIVE:
techno = As_Techno(Data.NavCom.Whom);
if (techno && techno->IsActive) {
techno->ArchiveTarget = Data.NavCom.Where;
}
break;
/*
** Make or break alliance.
*/
case ALLY:
house = Houses.Raw_Ptr(Data.General.Value);
if (Houses.Raw_Ptr(ID)->Is_Ally(house)) {
Houses.Raw_Ptr(ID)->Make_Enemy((HousesType)Data.General.Value);
} else {
Houses.Raw_Ptr(ID)->Make_Ally((HousesType)Data.General.Value);
}
break;
/*
** Special self destruct action requested. This is active in the multiplayer mode.
*/
case DESTRUCT:
CCDebugString ("C&C95 - Resignation packet received\n");
Houses.Raw_Ptr(ID)->Flag_To_Die();
Houses.Raw_Ptr(ID)->Resigned = true;
break;
/*
** Update the special control flags. This is necessary so that in a multiplay
** game, all machines will agree on the rules. If these options change during
** game play, then all players are informed that options have changed.
*/
case SPECIAL:
{
Special = Data.Options.Data;
HouseClass * house = Houses.Raw_Ptr(ID);
sprintf(txt, Text_String(TXT_SPECIAL_WARNING), house->Name);
Messages.Add_Message(txt, MPlayerTColors[house->RemapColor],
TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_FULLSHADOW, 1200, 0, 0);
Map.Flag_To_Redraw(false);
}
break;
/*
** Starts or stops repair on the specified object. This event is triggered by the
** player clicking the repair wrench on a building.
*/
case REPAIR:
CCDebugString ("C&C95 - Repair packet received\n");
techno = As_Techno(Data.Target.Whom);
if (techno && techno->IsActive) {
techno->Repair(-1);
}
break;
/*
** Tells a building/unit to sell. This event is triggered by the player clicking the
** sell animating cursor over the building or unit.
*/
case SELL:
CCDebugString ("C&C95 - Sell packet received\n");
techno = As_Techno(Data.Target.Whom);
if (techno && techno->IsActive && techno->House == Houses.Raw_Ptr(ID)) {
techno->Sell_Back(-1);
} else {
if (Is_Target_Cell(Data.Target.Whom)) {
Houses.Raw_Ptr(ID)->Sell_Wall(As_Cell(Data.Target.Whom));
}
}
break;
/*
** This even is used to trigger an animation that is generated as a direct
** result of player intervention.
*/
case ANIMATION:
anim = new AnimClass(Data.Anim.What, Data.Anim.Where);
if (anim) {
anim->Set_Owner(Data.Anim.Owner);
if (Special.IsVisibleTarget)
{
anim->Set_Visible_Flags(static_cast<unsigned int>(-1));
}
else
{
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;
/*
** This event will place the specified object at the specified location.
** The event is used to place newly constructed buildings down on the map. The
** object type is specified. From this object type, the house can determine the
** exact factory and real object pointer to use.
*/
case PLACE:
CCDebugString ("C&C95 - Place packet received\n");
Houses.Raw_Ptr(ID)->Place_Object(Data.Place.Type, Data.Place.Cell);
break;
/*
** This event starts production of the speicified object type. The house can
** determine from the type and ID value, what object to begin production on and
** what factory to use.
*/
case PRODUCE:
CCDebugString ("C&C95 - Produce packet received\n");
Houses.Raw_Ptr(ID)->Begin_Production(Data.Specific.Type, Data.Specific.ID);
break;
/*
** This event is generated when the player puts production on hold. From the
** object type, the factory can be inferred.
*/
case SUSPEND:
CCDebugString ("C&C95 - Suspend packet received\n");
Houses.Raw_Ptr(ID)->Suspend_Production(Data.Specific.Type);
break;
/*
** This event is generated when the player cancels production of the specified
** object type. From the object type, the exact factory can be inferred.
*/
case ABANDON:
CCDebugString ("C&C95 - Abandon packet received\n");
Houses.Raw_Ptr(ID)->Abandon_Production(Data.Specific.Type);
break;
/*
** Toggles the primary factory state of the specified building.
*/
case PRIMARY:{
CCDebugString ("C&C95 - Primary building packet received\n");
BuildingClass * building = As_Building(Data.Target.Whom);
if (building && building->IsActive) {
building->Toggle_Primary();
}
}
break;
/*
** This is the general purpose mission control event. Most player
** action routes through this event. It sets a unit's mission, TarCom,
** and NavCom to the values specified.
*/
case MEGAMISSION:
techno = As_Techno(Data.MegaMission.Whom);
if (techno && techno->IsActive) {
/*
** Fetch a pointer to the object of the mission.
*/
ObjectClass * object;
if (Target_Legal(Data.MegaMission.Target)) {
object = As_Object(Data.MegaMission.Target);
} else {
object = As_Object(Data.MegaMission.Destination);
}
/*
** Break any existing team contact, since it is now invalid.
*/
if (!techno->IsTethered) {
techno->Transmit_Message(RADIO_OVER_OUT);
}
switch (techno->What_Am_I()) {
case RTTI_INFANTRY:
case RTTI_UNIT:
if (((FootClass *)techno)->Team) {
((FootClass *)techno)->Team->Remove((FootClass *)techno);
}
break;
}
if (object) {
// 2019/09/20 JAS - Added record of who clicked on the object
HouseClass* house = Houses.Raw_Ptr(ID);
bool is_allied = house != nullptr && house->Is_Ally(techno);
if (is_allied || Special.IsVisibleTarget) {
object->Clicked_As_Target((HousesType)ID);
}
}
techno->Assign_Mission(Data.MegaMission.Mission);
/*
** Guard area mode is handled with care. The specified target is actually
** assigned as the location that should be guarded. In addition, the
** movement destination is immediately set to this new location.
*/
if (Data.MegaMission.Mission == MISSION_GUARD_AREA &&
// Target_Legal(Data.MegaMission.Target) &&
(techno->What_Am_I() == RTTI_INFANTRY || techno->What_Am_I() == RTTI_UNIT || techno->What_Am_I() == RTTI_AIRCRAFT)) {
techno->ArchiveTarget = Data.MegaMission.Target;
techno->Assign_Target(TARGET_NONE);
techno->Assign_Destination(Data.MegaMission.Target);
} else if (Data.MegaMission.Mission == MISSION_ENTER &&
object != NULL &&
object->What_Am_I() == RTTI_BUILDING &&
*((BuildingClass*)object) == STRUCT_REFINERY) {
techno->Transmit_Message(RADIO_HELLO, (BuildingClass*)object);
techno->Assign_Destination(TARGET_NONE);
} else {
techno->Assign_Target(Data.MegaMission.Target);
techno->Assign_Destination(Data.MegaMission.Destination);
}
#ifdef NEVER
if ((techno->What_Am_I() == RTTI_UNIT || techno->What_Am_I() == RTTI_INFANTRY) &&
Data.MegaMission.Mission == MISSION_GUARD_AREA) {
techno->ArchiveTarget = Data.MegaMission.Destination;
}
#endif
}
break;
/*
** Request that the unit/infantry/aircraft go into idle mode.
*/
case IDLE:
techno = As_Techno(Data.Target.Whom);
if (techno && techno->IsActive && !techno->IsInLimbo && !techno->IsTethered) {
techno->Assign_Destination(TARGET_NONE);
techno->Assign_Target(TARGET_NONE);
techno->Enter_Idle_Mode();
}
break;
/*
** Request that the unit/infantry/aircraft scatter from its current location.
*/
case SCATTER:
techno = As_Techno(Data.Target.Whom);
if (techno && techno->IsActive && !techno->IsInLimbo && !techno->IsTethered) {
techno->Scatter(0, true);
}
break;
/*
** If we are placing down the ion cannon blast then lets take
** care of it.
*/
case SPECIAL_PLACE:
CCDebugString ("C&C95 - Special blast packet received\n");
Houses.Raw_Ptr(ID)->Place_Special_Blast((SpecialWeaponType)Data.Special.ID, Data.Special.Cell);
break;
/*
** Exit the game.
** Give parting message while palette is fading to black.
*/
case EXIT:
CCDebugString ("C&C95 - Exit game packet received\n");
Theme.Queue_Song(THEME_NONE);
Stop_Speaking();
Speak(VOX_CONTROL_EXIT);
while (Is_Speaking()) {
Call_Back();
}
GameActive = false;
break;
/*
** Process the options menu.
*/
case OPTIONS:
SpecialDialog = SDLG_OPTIONS;
break;
/*
** Process the options Game Speed
*/
case GAMESPEED:
CCDebugString ("C&C95 - Game speed packet received\n");
Options.GameSpeed = Data.General.Value;
break;
/*
** Adjust connection timing for multiplayer games
*/
case RESPONSE_TIME:
char flip[128];
sprintf (flip, "C&C95 - Changing MaxAhead to %d frames\n", Data.FrameInfo.Delay);
CCDebugString (flip);
MPlayerMaxAhead = Data.FrameInfo.Delay;
break;
//
// This event tells all systems to use new timing values. It's like
// RESPONSE_TIME, only it works. It's only used with the
// COMM_MULTI_E_COMP protocol.
//
case TIMING:
CCDebugString ("C&C95 - Timing packet received\n");
//#if(TIMING_FIX)
//
// If MaxAhead is about to increase, we're vulnerable to a Packet-
// Received-Too-Late error, if any system generates an event after
// this TIMING event, but before it executes. So, record the
// period of vulnerability's frame start & end values, so we
// can reschedule these events to execute after it's over.
//
if (Data.Timing.MaxAhead > MPlayerMaxAhead) {
NewMaxAheadFrame1 = Frame;
NewMaxAheadFrame2 = Frame + Data.Timing.MaxAhead;
}
//#endif
DesiredFrameRate = Data.Timing.DesiredFrameRate;
MPlayerMaxAhead = Data.Timing.MaxAhead;
sprintf (flip, "C&C95 - Timing packet: DesiredFrameRate = %d\n", Data.Timing.DesiredFrameRate);
CCDebugString (flip);
sprintf (flip, "C&C95 - Timing packet: MaxAhead = %d\n", Data.Timing.MaxAhead);
CCDebugString (flip);
/*
** If spawned from WChat then we should be getting poked every minute. If not then
** deliberately break the max ahead value
*/
if (Special.IsFromWChat){
MPlayerMaxAhead += DDEServer.Time_Since_Heartbeat()/(70*60);
//if (DDEServer.Time_Since_Heartbeat() >= 70*60) CCDebugString ("C&C95 - Missed a heartbeat\n");
}
break;
//
// This event tells all systems what the other systems' process
// timing requirements are; it's used to compute a desired frame rate
// for the game.
//
case PROCESS_TIME:
for (i = 0; i < MPlayerCount; i++) {
if (MPlayerID == ::MPlayerID[i]) {
TheirProcessTime[i] = Data.ProcessTime.AverageTicks;
char flip[128];
sprintf (flip, "C&C95 - Received PROCESS_TIME packet of %04x ticks\n", Data.ProcessTime.AverageTicks);
CCDebugString (flip);
break;
}
}
break;
/*
** Default: do nothing.
*/
default:
break;
}
}