diff --git a/Core/GameEngine/CMakeLists.txt b/Core/GameEngine/CMakeLists.txt
index 635563d957e..69d218f593b 100644
--- a/Core/GameEngine/CMakeLists.txt
+++ b/Core/GameEngine/CMakeLists.txt
@@ -245,9 +245,9 @@ set(GAMEENGINE_SRC
# Include/GameLogic/AITNGuard.h
# Include/GameLogic/Armor.h
# Include/GameLogic/ArmorSet.h
-# Include/GameLogic/CaveSystem.h
-# Include/GameLogic/CrateSystem.h
-# Include/GameLogic/Damage.h
+ Include/GameLogic/CaveSystem.h
+ Include/GameLogic/CrateSystem.h
+ Include/GameLogic/Damage.h
# Include/GameLogic/ExperienceTracker.h
# Include/GameLogic/FiringTracker.h
# Include/GameLogic/FPUControl.h
@@ -486,7 +486,7 @@ set(GAMEENGINE_SRC
# Include/GameLogic/PartitionManager.h
# Include/GameLogic/PolygonTrigger.h
# Include/GameLogic/Powers.h
-# Include/GameLogic/RankInfo.h
+ Include/GameLogic/RankInfo.h
# Include/GameLogic/ScriptActions.h
# Include/GameLogic/ScriptConditions.h
# Include/GameLogic/ScriptEngine.h
@@ -1090,12 +1090,12 @@ set(GAMEENGINE_SRC
# Source/GameLogic/ScriptEngine/ScriptEngine.cpp
# Source/GameLogic/ScriptEngine/Scripts.cpp
# Source/GameLogic/ScriptEngine/VictoryConditions.cpp
-# Source/GameLogic/System/CaveSystem.cpp
-# Source/GameLogic/System/CrateSystem.cpp
-# Source/GameLogic/System/Damage.cpp
+ Source/GameLogic/System/CaveSystem.cpp
+ Source/GameLogic/System/CrateSystem.cpp
+ Source/GameLogic/System/Damage.cpp
# Source/GameLogic/System/GameLogic.cpp
-# Source/GameLogic/System/GameLogicDispatch.cpp
-# Source/GameLogic/System/RankInfo.cpp
+ Source/GameLogic/System/GameLogicDispatch.cpp
+ Source/GameLogic/System/RankInfo.cpp
Source/GameNetwork/Connection.cpp
Source/GameNetwork/ConnectionManager.cpp
Source/GameNetwork/DisconnectManager.cpp
diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/CaveSystem.h b/Core/GameEngine/Include/GameLogic/CaveSystem.h
similarity index 100%
rename from GeneralsMD/Code/GameEngine/Include/GameLogic/CaveSystem.h
rename to Core/GameEngine/Include/GameLogic/CaveSystem.h
diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/CrateSystem.h b/Core/GameEngine/Include/GameLogic/CrateSystem.h
similarity index 100%
rename from GeneralsMD/Code/GameEngine/Include/GameLogic/CrateSystem.h
rename to Core/GameEngine/Include/GameLogic/CrateSystem.h
diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Damage.h b/Core/GameEngine/Include/GameLogic/Damage.h
similarity index 100%
rename from GeneralsMD/Code/GameEngine/Include/GameLogic/Damage.h
rename to Core/GameEngine/Include/GameLogic/Damage.h
diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/RankInfo.h b/Core/GameEngine/Include/GameLogic/RankInfo.h
similarity index 100%
rename from GeneralsMD/Code/GameEngine/Include/GameLogic/RankInfo.h
rename to Core/GameEngine/Include/GameLogic/RankInfo.h
diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/CaveSystem.cpp b/Core/GameEngine/Source/GameLogic/System/CaveSystem.cpp
similarity index 100%
rename from GeneralsMD/Code/GameEngine/Source/GameLogic/System/CaveSystem.cpp
rename to Core/GameEngine/Source/GameLogic/System/CaveSystem.cpp
diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/CrateSystem.cpp b/Core/GameEngine/Source/GameLogic/System/CrateSystem.cpp
similarity index 100%
rename from GeneralsMD/Code/GameEngine/Source/GameLogic/System/CrateSystem.cpp
rename to Core/GameEngine/Source/GameLogic/System/CrateSystem.cpp
diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/Damage.cpp b/Core/GameEngine/Source/GameLogic/System/Damage.cpp
similarity index 100%
rename from GeneralsMD/Code/GameEngine/Source/GameLogic/System/Damage.cpp
rename to Core/GameEngine/Source/GameLogic/System/Damage.cpp
diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp
similarity index 99%
rename from GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp
rename to Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp
index ccb3fd83c01..c002ff8f239 100644
--- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp
+++ b/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp
@@ -756,25 +756,28 @@ void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData )
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_DO_SPECIAL_POWER_AT_LOCATION:
{
+ const Bool hasAngle = msg->getArgumentCount() >= 6;
+ Int argumentIndex = 0;
+
// first argument is the special power ID
- UnsignedInt specialPowerID = msg->getArgument( 0 )->integer;
+ UnsignedInt specialPowerID = msg->getArgument( argumentIndex++ )->integer;
// Location argument 2 is destination
- Coord3D targetCoord = msg->getArgument(1)->location;
+ Coord3D targetCoord = msg->getArgument( argumentIndex++ )->location;
// Angle argument 3 is the orientation of the special power (if applicable)
- Real angle = msg->getArgument(2)->real;
+ Real angle = hasAngle ? msg->getArgument( argumentIndex++ )->real : INVALID_ANGLE;
// Object in way -- if applicable (some specials care, others don't)
- ObjectID objectID = msg->getArgument( 3 )->objectID;
+ ObjectID objectID = msg->getArgument( argumentIndex++ )->objectID;
Object *objectInWay = findObjectByID( objectID );
// Command button options -- special power may care about variance options
- UnsignedInt options = msg->getArgument( 4 )->integer;
+ UnsignedInt options = msg->getArgument( argumentIndex++ )->integer;
// check for possible specific source, ignoring selection.
- ObjectID sourceID = msg->getArgument(5)->objectID;
- Object* source = findObjectByID(sourceID);
+ ObjectID sourceID = msg->getArgument( argumentIndex++ )->objectID;
+ Object* source = findObjectByID( sourceID );
if (source != nullptr)
{
#if !RETAIL_COMPATIBLE_CRC
@@ -1027,6 +1030,7 @@ void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData )
}
break;
}
+
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_DEBUG_HURT_OBJECT:
{
@@ -1055,9 +1059,6 @@ void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData )
}
#endif
-
-
-
#ifdef ALLOW_SURRENDER
//---------------------------------------------------------------------------------------------
case GameMessage::MSG_DO_SURRENDER:
diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/RankInfo.cpp b/Core/GameEngine/Source/GameLogic/System/RankInfo.cpp
similarity index 100%
rename from GeneralsMD/Code/GameEngine/Source/GameLogic/System/RankInfo.cpp
rename to Core/GameEngine/Source/GameLogic/System/RankInfo.cpp
diff --git a/Generals/Code/GameEngine/CMakeLists.txt b/Generals/Code/GameEngine/CMakeLists.txt
index f28e5c0552b..37a1e2a5d7e 100644
--- a/Generals/Code/GameEngine/CMakeLists.txt
+++ b/Generals/Code/GameEngine/CMakeLists.txt
@@ -228,9 +228,9 @@ set(GAMEENGINE_SRC
Include/GameLogic/AITNGuard.h
Include/GameLogic/Armor.h
Include/GameLogic/ArmorSet.h
- Include/GameLogic/CaveSystem.h
- Include/GameLogic/CrateSystem.h
- Include/GameLogic/Damage.h
+# Include/GameLogic/CaveSystem.h
+# Include/GameLogic/CrateSystem.h
+# Include/GameLogic/Damage.h
Include/GameLogic/ExperienceTracker.h
Include/GameLogic/FiringTracker.h
Include/GameLogic/FPUControl.h
@@ -439,7 +439,7 @@ set(GAMEENGINE_SRC
Include/GameLogic/PartitionManager.h
Include/GameLogic/PolygonTrigger.h
Include/GameLogic/Powers.h
- Include/GameLogic/RankInfo.h
+# Include/GameLogic/RankInfo.h
Include/GameLogic/ScriptActions.h
Include/GameLogic/ScriptConditions.h
Include/GameLogic/ScriptEngine.h
@@ -997,12 +997,12 @@ set(GAMEENGINE_SRC
Source/GameLogic/ScriptEngine/ScriptEngine.cpp
Source/GameLogic/ScriptEngine/Scripts.cpp
Source/GameLogic/ScriptEngine/VictoryConditions.cpp
- Source/GameLogic/System/CaveSystem.cpp
- Source/GameLogic/System/CrateSystem.cpp
- Source/GameLogic/System/Damage.cpp
+# Source/GameLogic/System/CaveSystem.cpp
+# Source/GameLogic/System/CrateSystem.cpp
+# Source/GameLogic/System/Damage.cpp
Source/GameLogic/System/GameLogic.cpp
- Source/GameLogic/System/GameLogicDispatch.cpp
- Source/GameLogic/System/RankInfo.cpp
+# Source/GameLogic/System/GameLogicDispatch.cpp
+# Source/GameLogic/System/RankInfo.cpp
# Source/GameNetwork/Connection.cpp
# Source/GameNetwork/ConnectionManager.cpp
# Source/GameNetwork/DisconnectManager.cpp
diff --git a/Generals/Code/GameEngine/Include/Common/MessageStream.h b/Generals/Code/GameEngine/Include/Common/MessageStream.h
index 2b04f40be2e..a8ddb0f368d 100644
--- a/Generals/Code/GameEngine/Include/Common/MessageStream.h
+++ b/Generals/Code/GameEngine/Include/Common/MessageStream.h
@@ -572,6 +572,7 @@ class GameMessage : public MemoryPoolObject
MSG_CREATE_FORMATION, ///< Creates a formation.
MSG_LOGIC_CRC, ///< CRC from the logic passed around in a network game :)
MSG_SET_MINE_CLEARING_DETAIL, ///< CRC from the logic passed around in a network game :)
+ MSG_ENABLE_RETALIATION_MODE, ///< Turn retaliation mode on or off.
MSG_BEGIN_DEBUG_NETWORK_MESSAGES = 1900, ///< network messages that exist only in debug/internal builds. all grouped separately.
diff --git a/Generals/Code/GameEngine/Include/Common/Player.h b/Generals/Code/GameEngine/Include/Common/Player.h
index 1288b30dc8b..43c00857936 100644
--- a/Generals/Code/GameEngine/Include/Common/Player.h
+++ b/Generals/Code/GameEngine/Include/Common/Player.h
@@ -651,6 +651,11 @@ class Player : public Snapshot
void setCashBounty(Real percentage) { m_cashBountyPercent = percentage; }
void doBountyForKill(const Object* killer, const Object* victim);
+ //Set via logical message. Options menu sets the client value in global data. Player::update()
+ //detects a change, and posts a message. When the message gets processed, this value gets set.
+ Bool isLogicalRetaliationModeEnabled() const { return m_logicalRetaliationModeEnabled; }
+ void setLogicalRetaliationModeEnabled( Bool set ) { m_logicalRetaliationModeEnabled = set; }
+
private:
/** give the science. doesn't check prereqs, nor charge to purchase points...
@@ -805,4 +810,5 @@ class Player : public Snapshot
Squad *m_currentSelection; ///< This player's currently selected group
Bool m_isPlayerDead;
+ Bool m_logicalRetaliationModeEnabled;
};
diff --git a/Generals/Code/GameEngine/Include/GameLogic/CaveSystem.h b/Generals/Code/GameEngine/Include/GameLogic/CaveSystem.h
deleted file mode 100644
index 73ec70eb18d..00000000000
--- a/Generals/Code/GameEngine/Include/GameLogic/CaveSystem.h
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
-** Command & Conquer Generals(tm)
-** Copyright 2025 Electronic Arts Inc.
-**
-** This program is free software: you can redistribute it and/or modify
-** it under the terms of the GNU General Public License as published by
-** the Free Software Foundation, either version 3 of the License, or
-** (at your option) any later version.
-**
-** This program is distributed in the hope that it will be useful,
-** but WITHOUT ANY WARRANTY; without even the implied warranty of
-** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-** GNU General Public License for more details.
-**
-** You should have received a copy of the GNU General Public License
-** along with this program. If not, see .
-*/
-
-////////////////////////////////////////////////////////////////////////////////
-// //
-// (c) 2001-2003 Electronic Arts Inc. //
-// //
-////////////////////////////////////////////////////////////////////////////////
-
-// FILE: CaveSystem.h /////////////////////////////////////////////////////////////////////////////////
-// Author: Graham Smallwood July 2002
-// Desc: System responsible for keeping track of the connectedness of all cave systems on the map
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-#pragma once
-
-class Object;
-class TunnelTracker; // The player owns one such object for his Tunnels, so instead of duplicating
-// so much code, this SubSystem will manage all of the Cave systems.
-
-#include "Common/Snapshot.h"
-#include "Common/SubsystemInterface.h"
-
-/**
- System responsible for Crates as code objects - ini, new/delete etc
-*/
-class CaveSystem : public SubsystemInterface,
- public Snapshot
-{
-public:
- CaveSystem();
- virtual ~CaveSystem() override;
-
- virtual void init() override;
- virtual void reset() override;
- virtual void update() override;
-
- Bool canSwitchIndexToIndex( Int oldIndex, Int newIndex ); // If either Index has guys in it, no, you can't
- void registerNewCave( Int theIndex ); // All Caves are born with a default index, which could be new
- void unregisterCave( Int theIndex ); //
- TunnelTracker *getTunnelTrackerForCaveIndex( Int theIndex );
-
-protected:
-
- // snapshot methods
- virtual void crc( Xfer *xfer ) override { }
- virtual void xfer( Xfer *xfer ) override;
- virtual void loadPostProcess() override { }
-
-private:
- std::vector m_tunnelTrackerVector;// A vector of pointers where the indexes are known by
- // others, so it can have NULLs in it to keep position. I've been advised against a map, so don't be a jerk
- // and use spot 20 first.
-
-};
-
-extern CaveSystem *TheCaveSystem;
diff --git a/Generals/Code/GameEngine/Include/GameLogic/CrateSystem.h b/Generals/Code/GameEngine/Include/GameLogic/CrateSystem.h
deleted file mode 100644
index 90ad674f2c8..00000000000
--- a/Generals/Code/GameEngine/Include/GameLogic/CrateSystem.h
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
-** Command & Conquer Generals(tm)
-** Copyright 2025 Electronic Arts Inc.
-**
-** This program is free software: you can redistribute it and/or modify
-** it under the terms of the GNU General Public License as published by
-** the Free Software Foundation, either version 3 of the License, or
-** (at your option) any later version.
-**
-** This program is distributed in the hope that it will be useful,
-** but WITHOUT ANY WARRANTY; without even the implied warranty of
-** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-** GNU General Public License for more details.
-**
-** You should have received a copy of the GNU General Public License
-** along with this program. If not, see .
-*/
-
-////////////////////////////////////////////////////////////////////////////////
-// //
-// (c) 2001-2003 Electronic Arts Inc. //
-// //
-////////////////////////////////////////////////////////////////////////////////
-
-// FILE: CrateSystem.h /////////////////////////////////////////////////////////////////////////////////
-// Author: Graham Smallwood Feb 2002
-// Desc: System responsible for Crates as code objects - ini, new/delete etc
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-#pragma once
-
-#include "Common/INI.h"
-#include "Common/Overridable.h"
-#include "Common/Override.h"
-
-enum ScienceType CPP_11(: Int);
-
-struct crateCreationEntry
-{
- AsciiString crateName;
- Real crateChance;
-};
-
-typedef std::list< crateCreationEntry > crateCreationEntryList;
-typedef std::list< crateCreationEntry >::iterator crateCreationEntryIterator;
-typedef std::list< crateCreationEntry >::const_iterator crateCreationEntryConstIterator;
-
-/**
- A CrateTemplate is a ini defined set of conditions plus a ThingTemplate that is the Object
- containing the correct CrateCollide module.
-*/
-class CrateTemplate : public Overridable
-{
- MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( CrateTemplate, "CrateTemplate" )
-
-public:
- CrateTemplate();
- // virtual destructor declared by memory pool
-
- void setName( AsciiString name ) { m_name = name; }
- AsciiString getName(){ return m_name; }
-
- const FieldParse *getFieldParse() const { return TheCrateTemplateFieldParseTable; }
- static const FieldParse TheCrateTemplateFieldParseTable[]; ///< the parse table for INI definition
-
- static void parseCrateCreationEntry( INI* ini, void *instance, void *store, const void* /*userData*/ );
-
- AsciiString m_name; ///< name for this CrateTemplate
-
- Real m_creationChance; ///< Condition for random percentage chance of creating
- VeterancyLevel m_veterancyLevel; ///< Condition specifing level of killed unit
- KindOfMaskType m_killedByTypeKindof; ///< Must be killed by something with all these bits set
- ScienceType m_killerScience; ///< Must be killed by something posessing this science
- crateCreationEntryList m_possibleCrates; ///< CreationChance is for this CrateData to succeed, this list controls one-of-n crates created on success
- Bool m_isOwnedByMaker; ///< Design needs crates to be owned sometimes.
-
-private:
-
-};
-
-typedef OVERRIDE CrateTemplateOverride;
-
-
-/**
- System responsible for Crates as code objects - ini, new/delete etc
-*/
-class CrateSystem : public SubsystemInterface
-{
-public:
- CrateSystem();
- virtual ~CrateSystem() override;
-
- virtual void init() override;
- virtual void reset() override;
- virtual void update() override {}
-
- const CrateTemplate *findCrateTemplate(AsciiString name) const;
- CrateTemplate *friend_findCrateTemplate(AsciiString name);
-
- CrateTemplate *newCrateTemplate( AsciiString name );
- CrateTemplate *newCrateTemplateOverride( CrateTemplate *crateToOverride );
-
-
-
- static void parseCrateTemplateDefinition(INI* ini);
-
-private:
- std::vector m_crateTemplateVector;
-
-};
-
-extern CrateSystem *TheCrateSystem;
diff --git a/Generals/Code/GameEngine/Include/GameLogic/Damage.h b/Generals/Code/GameEngine/Include/GameLogic/Damage.h
deleted file mode 100644
index 7f5ed8dbb17..00000000000
--- a/Generals/Code/GameEngine/Include/GameLogic/Damage.h
+++ /dev/null
@@ -1,373 +0,0 @@
-/*
-** Command & Conquer Generals(tm)
-** Copyright 2025 Electronic Arts Inc.
-**
-** This program is free software: you can redistribute it and/or modify
-** it under the terms of the GNU General Public License as published by
-** the Free Software Foundation, either version 3 of the License, or
-** (at your option) any later version.
-**
-** This program is distributed in the hope that it will be useful,
-** but WITHOUT ANY WARRANTY; without even the implied warranty of
-** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-** GNU General Public License for more details.
-**
-** You should have received a copy of the GNU General Public License
-** along with this program. If not, see .
-*/
-
-////////////////////////////////////////////////////////////////////////////////
-// //
-// (c) 2001-2003 Electronic Arts Inc. //
-// //
-////////////////////////////////////////////////////////////////////////////////
-
-// FILE: Damage.h /////////////////////////////////////////////////////////////////////////////////
-// Author: Colin Day, November 2001
-// Desc: Damage description
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-#pragma once
-
-// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
-#include "Common/BitFlags.h"
-#include "Common/GameType.h"
-#include "Common/ObjectStatusTypes.h" // Precompiled header anyway, no detangling possibility
-#include "Common/Snapshot.h"
-
-
-// FORWARD REFERENCES /////////////////////////////////////////////////////////////////////////////
-class Object;
-class INI;
-class ThingTemplate;
-
-//-------------------------------------------------------------------------------------------------
-/** Damage types, keep this in sync with DamageTypeFlags::s_bitNameList[] */
-//-------------------------------------------------------------------------------------------------
-enum DamageType CPP_11(: Int)
-{
- DAMAGE_EXPLOSION = 0,
- DAMAGE_CRUSH = 1,
- DAMAGE_ARMOR_PIERCING = 2,
- DAMAGE_SMALL_ARMS = 3,
- DAMAGE_GATTLING = 4,
- DAMAGE_RADIATION = 5,
- DAMAGE_FLAME = 6,
- DAMAGE_LASER = 7,
- DAMAGE_SNIPER = 8,
- DAMAGE_POISON = 9,
- DAMAGE_HEALING = 10,
- DAMAGE_UNRESISTABLE = 11, // this is for scripting to cause 'armorproof' damage
- DAMAGE_WATER = 12,
- DAMAGE_DEPLOY = 13, // for transports to deploy units and order them to all attack.
- DAMAGE_SURRENDER = 14, // if something "dies" to surrender damage, they surrender.... duh!
- DAMAGE_HACK = 15,
- DAMAGE_KILLPILOT = 16, // special snipe attack that kills the pilot and renders a vehicle unmanned.
- DAMAGE_PENALTY = 17, // from game penalty (you won't receive radar warnings BTW)
- DAMAGE_FALLING = 18,
- DAMAGE_MELEE = 19, // Blades, clubs...
- DAMAGE_DISARM = 20, // "special" damage type used for disarming mines, bombs, etc (NOT for "disarming" an opponent!)
- DAMAGE_HAZARD_CLEANUP = 21, // special damage type for cleaning up hazards like radiation or bio-poison.
- DAMAGE_PARTICLE_BEAM = 22, // Incinerates virtually everything (insanely powerful orbital beam)
- DAMAGE_TOPPLING = 23, // damage from getting toppled.
- DAMAGE_INFANTRY_MISSILE = 24,
- DAMAGE_AURORA_BOMB = 25,
- DAMAGE_LAND_MINE = 26,
- DAMAGE_JET_MISSILES = 27,
- DAMAGE_STEALTHJET_MISSILES = 28,
- DAMAGE_MOLOTOV_COCKTAIL = 29,
- DAMAGE_COMANCHE_VULCAN = 30,
-#if RTS_GENERALS
- DAMAGE_FLESHY_SNIPER = 31, // like DAMAGE_SNIPER, but (generally) does no damage to vehicles.
-#endif
- DAMAGE_SUBDUAL_MISSILE /*= 31*/, ///< Damage that does not kill you, but produces some special effect based on your Body Module. Separate HP from normal damage.
- DAMAGE_SUBDUAL_VEHICLE /*= 32*/,
- DAMAGE_SUBDUAL_BUILDING /*= 33*/,
- DAMAGE_SUBDUAL_UNRESISTABLE /*= 34*/,
- DAMAGE_MICROWAVE /*= 35*/, ///< Radiation that only affects infantry
- DAMAGE_KILL_GARRISONED /*= 36*/, ///< Kills Passengers up to the number specified in Damage
- DAMAGE_STATUS /*= 37*/, ///< Damage that gives a status condition, not that does hitpoint damage
-
- // Please note: There is a string array DamageTypeFlags::s_bitNameList[]
-
- DAMAGE_NUM_TYPES
-};
-
-//-------------------------------------------------------------------------------------------------
-//-------------------------------------------------------------------------------------------------
-
-typedef BitFlags DamageTypeFlags;
-
-inline Bool getDamageTypeFlag(DamageTypeFlags flags, DamageType dt)
-{
- return flags.test(dt);
-}
-
-inline DamageTypeFlags setDamageTypeFlag(DamageTypeFlags flags, DamageType dt)
-{
- flags.set(dt, TRUE);
- return flags;
-}
-
-inline DamageTypeFlags clearDamageTypeFlag(DamageTypeFlags flags, DamageType dt)
-{
- flags.set(dt, FALSE);
- return flags;
-}
-
-// Instead of checking against a single damage type, gather the question here so we can have more than one
-inline Bool IsSubdualDamage( DamageType type )
-{
- switch( type )
- {
- case DAMAGE_SUBDUAL_MISSILE:
- case DAMAGE_SUBDUAL_VEHICLE:
- case DAMAGE_SUBDUAL_BUILDING:
- case DAMAGE_SUBDUAL_UNRESISTABLE:
- return TRUE;
- }
-
- return FALSE;
-}
-
-/// Does this type of damage go to internalChangeHealth?
-inline Bool IsHealthDamagingDamage( DamageType type )
-{
- // The need for this function brought to you by "Have the guy with no game experience write the weapon code" Foundation.
- // Health Damage should be one type of WeaponEffect. Thinking "Weapons can only do damage" is why AoE is boring.
- switch( type )
- {
- case DAMAGE_STATUS:
- case DAMAGE_SUBDUAL_MISSILE:
- case DAMAGE_SUBDUAL_VEHICLE:
- case DAMAGE_SUBDUAL_BUILDING:
- case DAMAGE_SUBDUAL_UNRESISTABLE:
- case DAMAGE_KILLPILOT:
- case DAMAGE_KILL_GARRISONED:
- return FALSE;
- }
-
- return TRUE;
-}
-
-inline void SET_ALL_DAMAGE_TYPE_BITS(DamageTypeFlags& m)
-{
- m.clear();
- m.flip();
-}
-
-extern DamageTypeFlags DAMAGE_TYPE_FLAGS_NONE;
-extern DamageTypeFlags DAMAGE_TYPE_FLAGS_ALL;
-
-
-//-------------------------------------------------------------------------------------------------
-/** Death types, keep this in sync with TheDeathNames[] */
-//-------------------------------------------------------------------------------------------------
-enum DeathType CPP_11(: Int)
-{
- // note that these DELIBERATELY have (slightly) different names from the damage names,
- // since there isn't necessarily a one-to-one correspondence. e.g., DEATH_BURNED
- // can come from DAMAGE_FLAME but also from DAMAGE_PARTICLE_BEAM.
- DEATH_NORMAL = 0,
- DEATH_NONE = 1, ///< this is a "special case" that can't normally cause death
- DEATH_CRUSHED = 2,
- DEATH_BURNED = 3,
- DEATH_EXPLODED = 4,
- DEATH_POISONED = 5,
- DEATH_TOPPLED = 6,
- DEATH_FLOODED = 7,
- DEATH_SUICIDED = 8,
- DEATH_LASERED = 9,
- DEATH_DETONATED = 10, /**< this is the "death" that occurs when a missile/warhead/etc detonates normally,
- as opposed to being shot down, etc */
- DEATH_SPLATTED = 11, /**< the death that results from DAMAGE_FALLING */
- DEATH_POISONED_BETA = 12,
-
- // these are the "extra" types for yet-to-be-defined stuff. Don't bother renaming or adding
- // or removing these; they are reserved for modders :-)
- DEATH_EXTRA_2 = 13,
- DEATH_EXTRA_3 = 14,
- DEATH_EXTRA_4 = 15,
- DEATH_EXTRA_5 = 16,
- DEATH_EXTRA_6 = 17,
- DEATH_EXTRA_7 = 18,
- DEATH_EXTRA_8 = 19,
- DEATH_POISONED_GAMMA = 20,
-
- DEATH_NUM_TYPES
-};
-
-#ifdef DEFINE_DEATH_NAMES
-static const char *const TheDeathNames[] =
-{
- "NORMAL",
- "NONE",
- "CRUSHED",
- "BURNED",
- "EXPLODED",
- "POISONED",
- "TOPPLED",
- "FLOODED",
- "SUICIDED",
- "LASERED",
- "DETONATED",
- "SPLATTED",
- "POISONED_BETA",
- "EXTRA_2",
- "EXTRA_3",
- "EXTRA_4",
- "EXTRA_5",
- "EXTRA_6",
- "EXTRA_7",
- "EXTRA_8",
- "POISONED_GAMMA",
-
- nullptr
-};
-static_assert(ARRAY_SIZE(TheDeathNames) == DEATH_NUM_TYPES + 1, "Incorrect array size");
-#endif // end DEFINE_DEATH_NAMES
-
-
-//-------------------------------------------------------------------------------------------------
-//-------------------------------------------------------------------------------------------------
-
-typedef UnsignedInt DeathTypeFlags;
-
-const DeathTypeFlags DEATH_TYPE_FLAGS_ALL = 0xffffffff;
-const DeathTypeFlags DEATH_TYPE_FLAGS_NONE = 0x00000000;
-
-inline Bool getDeathTypeFlag(DeathTypeFlags flags, DeathType dt)
-{
- return (flags & (1UL << (dt - 1))) != 0;
-}
-
-inline DeathTypeFlags setDeathTypeFlag(DeathTypeFlags flags, DeathType dt)
-{
- return (flags | (1UL << (dt - 1)));
-}
-
-inline DeathTypeFlags clearDeathTypeFlag(DeathTypeFlags flags, DeathType dt)
-{
- return (flags & ~(1UL << (dt - 1)));
-}
-
-//-------------------------------------------------------------------------------------------------
-/** Damage info inputs */
-//-------------------------------------------------------------------------------------------------
-class DamageInfoInput : public Snapshot
-{
-
-public:
-
- DamageInfoInput()
- {
- m_sourceID = INVALID_ID;
- m_sourceTemplate = nullptr;
- m_sourcePlayerMask = 0;
- m_damageType = DAMAGE_EXPLOSION;
- m_damageStatusType = OBJECT_STATUS_NONE;
- m_damageFXOverride = DAMAGE_UNRESISTABLE;
- m_deathType = DEATH_NORMAL;
- m_amount = 0;
- m_kill = FALSE;
-
- m_shockWaveVector.zero();
- m_shockWaveAmount = 0.0f;
- m_shockWaveRadius = 0.0f;
- m_shockWaveTaperOff = 0.0f;
- }
-
- ObjectID m_sourceID; ///< source of the damage
- const ThingTemplate *m_sourceTemplate; ///< source of the damage (the template).
- PlayerMaskType m_sourcePlayerMask; ///< Player mask of m_sourceID.
- DamageType m_damageType; ///< type of damage
- ObjectStatusTypes m_damageStatusType; ///< If status damage, what type
- DamageType m_damageFXOverride; ///< If not marked as the default of Unresistable, the damage type to use in doDamageFX instead of the real damage type
- DeathType m_deathType; ///< if this kills us, death type to be used
- Real m_amount; ///< # value of how much damage to inflict
- Bool m_kill; ///< will always cause object to die regardless of damage.
-
- // These are used for damage causing shockwave, forcing units affected to be pushed around
- Coord3D m_shockWaveVector; ///< This represents the incoming damage vector
- Real m_shockWaveAmount; ///< This represents the amount of shockwave created by the damage. 0 = no shockwave, 1.0 = shockwave equal to damage.
- Real m_shockWaveRadius; ///< This represents the effect radius of the shockwave.
- Real m_shockWaveTaperOff; ///< This represents the taper off effect of the shockwave at the tip of the radius. 0.0 means shockwave is 0% at the radius edge.
-
-
-protected:
-
- // snapshot methods
- virtual void crc( Xfer *xfer ) override { }
- virtual void xfer( Xfer *xfer ) override;
- virtual void loadPostProcess() override { }
-
-};
-
-const Real HUGE_DAMAGE_AMOUNT = 999999.0f;
-
-//-------------------------------------------------------------------------------------------------
-/** Damage into outputs */
-//-------------------------------------------------------------------------------------------------
-class DamageInfoOutput : public Snapshot
-{
-
-public:
-
- DamageInfoOutput()
- {
- m_actualDamageDealt = 0;
- m_actualDamageClipped = 0;
- m_noEffect = false;
- }
-
- /**
- m_actualDamageDealt is the damage we tried to apply to object (after multipliers and such).
- m_actualDamageClipped is the value of m_actualDamageDealt, but clipped to the max health remaining of the obj.
- example:
- a mammoth tank fires a round at a small tank, attempting 100 damage.
- the small tank has a damage multiplier of 50%, meaning that only 50 damage is applied.
- furthermore, the small tank has only 30 health remaining.
- so: m_actualDamageDealt = 50, m_actualDamageClipped = 30.
-
- this distinction is useful, since visual fx really wants to do the fx for "50 damage",
- even though it was more than necessary to kill this object; game logic, on the other hand,
- may want to know the "clipped" damage for ai purposes.
- */
- Real m_actualDamageDealt;
- Real m_actualDamageClipped; ///< (see comment for m_actualDamageDealt)
- Bool m_noEffect; ///< if true, no damage was done at all (generally due to being InactiveBody)
-
-protected:
-
- // snapshot methods
- virtual void crc( Xfer *xfer ) override { }
- virtual void xfer( Xfer *xfer ) override;
- virtual void loadPostProcess() override { }
-
-};
-
-//-------------------------------------------------------------------------------------------------
-/** DamageInfo is a descriptor of damage we're trying to inflict. The structure
- * is divided up into two parts, inputs and outputs.
- *
- * INPUTS: You must provide valid values for these fields in order for damage
- * calculation to correctly take place
- * OUTPUT: Upon returning from damage issuing functions, the output fields
- * will be filled with the results of the damage occurrence
- */
-//-------------------------------------------------------------------------------------------------
-class DamageInfo : public Snapshot
-{
-
-public:
-
- DamageInfoInput in; ///< inputs for the damage info
- DamageInfoOutput out; ///< results for the damage occurrence
-
-protected:
-
- virtual void crc( Xfer *xfer ) override { }
- virtual void xfer( Xfer *xfer ) override;
- virtual void loadPostProcess() override { }
-
-};
diff --git a/Generals/Code/GameEngine/Include/GameLogic/RankInfo.h b/Generals/Code/GameEngine/Include/GameLogic/RankInfo.h
deleted file mode 100644
index f05b28aec5f..00000000000
--- a/Generals/Code/GameEngine/Include/GameLogic/RankInfo.h
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
-** Command & Conquer Generals(tm)
-** Copyright 2025 Electronic Arts Inc.
-**
-** This program is free software: you can redistribute it and/or modify
-** it under the terms of the GNU General Public License as published by
-** the Free Software Foundation, either version 3 of the License, or
-** (at your option) any later version.
-**
-** This program is distributed in the hope that it will be useful,
-** but WITHOUT ANY WARRANTY; without even the implied warranty of
-** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-** GNU General Public License for more details.
-**
-** You should have received a copy of the GNU General Public License
-** along with this program. If not, see .
-*/
-
-////////////////////////////////////////////////////////////////////////////////
-// //
-// (c) 2001-2003 Electronic Arts Inc. //
-// //
-////////////////////////////////////////////////////////////////////////////////
-
-// FILE: RankInfo.h ////////////////////////////////////////////////////////////////////////////////
-// Author: Steven Johnson, Sep 2002
-// Desc: RankInfo descriptions
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-#pragma once
-
-// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
-#include "Common/Science.h"
-#include "Common/UnicodeString.h"
-#include "Common/STLTypedefs.h"
-
-class Player;
-
-//-------------------------------------------------------------------------------------------------
-class RankInfo : public Overridable
-{
- MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( RankInfo, "RankInfo" );
-public:
- UnicodeString m_rankName;
- Int m_skillPointsNeeded;
- Int m_sciencePurchasePointsGranted;
- ScienceVec m_sciencesGranted;
-};
-//EMPTY_DTOR(RankInfo)
-
-//-------------------------------------------------------------------------------------------------
-class RankInfoStore : public SubsystemInterface
-{
-public:
- virtual ~RankInfoStore() override;
-
-public:
- virtual void init() override;
- virtual void reset() override;
- virtual void update() override { }
-
- Int getRankLevelCount() const;
-
- // note that level is 1...n, NOT 0...n-1
- const RankInfo* getRankInfo(Int level) const;
-
- static void friend_parseRankDefinition(INI* ini);
-
-private:
-
- typedef std::vector RankInfoVec;
- RankInfoVec m_rankInfos;
-};
-
-extern RankInfoStore* TheRankInfoStore;
diff --git a/Generals/Code/GameEngine/Source/Common/RTS/Player.cpp b/Generals/Code/GameEngine/Source/Common/RTS/Player.cpp
index b4b5d63db75..7bc48908c7a 100644
--- a/Generals/Code/GameEngine/Source/Common/RTS/Player.cpp
+++ b/Generals/Code/GameEngine/Source/Common/RTS/Player.cpp
@@ -487,6 +487,9 @@ void Player::init(const PlayerTemplate* pt)
deleteInstance(tof);
}
+ //Always off at the beginning of a game! Only GameLogic::update has
+ //the power to turn it on. Don't want to cause desyncs!
+ m_logicalRetaliationModeEnabled = FALSE;
}
//=============================================================================
diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/CaveSystem.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/CaveSystem.cpp
deleted file mode 100644
index 058a98cb938..00000000000
--- a/Generals/Code/GameEngine/Source/GameLogic/System/CaveSystem.cpp
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
-** Command & Conquer Generals(tm)
-** Copyright 2025 Electronic Arts Inc.
-**
-** This program is free software: you can redistribute it and/or modify
-** it under the terms of the GNU General Public License as published by
-** the Free Software Foundation, either version 3 of the License, or
-** (at your option) any later version.
-**
-** This program is distributed in the hope that it will be useful,
-** but WITHOUT ANY WARRANTY; without even the implied warranty of
-** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-** GNU General Public License for more details.
-**
-** You should have received a copy of the GNU General Public License
-** along with this program. If not, see .
-*/
-
-////////////////////////////////////////////////////////////////////////////////
-// //
-// (c) 2001-2003 Electronic Arts Inc. //
-// //
-////////////////////////////////////////////////////////////////////////////////
-
-// FILE: CaveSystem.cpp /////////////////////////////////////////////////////////////////////////////////
-// Author: Graham Smallwood July 2002
-// Desc: System responsible for keeping track of all cave systems on the map
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine
-
-#include "Common/GameState.h"
-#include "Common/TunnelTracker.h"
-#include "Common/Xfer.h"
-#include "GameLogic/CaveSystem.h"
-
-CaveSystem *TheCaveSystem = nullptr;
-
-CaveSystem::CaveSystem()
-{
-}
-
-CaveSystem::~CaveSystem()
-{
-}
-
-void CaveSystem::init()
-{
-}
-
-void CaveSystem::reset()
-{
- for( std::vector::iterator iter = m_tunnelTrackerVector.begin(); iter != m_tunnelTrackerVector.end(); iter++ )
- {
- TunnelTracker *currentTracker = *iter; // could be null, since we don't slide back to fill deleted entries so offsets don't shift
- deleteInstance(currentTracker);
- }
- m_tunnelTrackerVector.clear();
-}
-
-void CaveSystem::update()
-{
-}
-
-Bool CaveSystem::canSwitchIndexToIndex( Int oldIndex, Int newIndex )
-{
- // When I grant permission, you need to do it. ie call Unregister and then re-register with the new number
- TunnelTracker *oldTracker = nullptr;
- TunnelTracker *newTracker = nullptr;
- if( m_tunnelTrackerVector.size() > oldIndex )
- {
- oldTracker = m_tunnelTrackerVector[oldIndex];
- if( oldTracker && oldTracker->getContainCount() > 0 )
- return FALSE;// You can't switch a connection if one of the two is non empty
- }
- if( m_tunnelTrackerVector.size() > newIndex )
- {
- newTracker = m_tunnelTrackerVector[newIndex];
- if( newTracker && newTracker->getContainCount() > 0 )
- return FALSE;// You can't switch a connection if one of the two is non empty
- }
-
- // Both are either empty or non-existent, so go ahead.
- // (Remember non-exist is only a valid case because you are going to do the switch now.)
-
- return TRUE;
-}
-
-void CaveSystem::registerNewCave( Int theIndex )
-{
- Bool needToCreate = FALSE;
- if( theIndex >= m_tunnelTrackerVector.size() )
- {
- // You are new and off the edge, so I will fill NULLs up to you and then make a newTracker at that spot
- while( theIndex >= m_tunnelTrackerVector.size() )
- m_tunnelTrackerVector.push_back( nullptr );
-
- needToCreate = TRUE;
- }
- else
- {
- // else you either exist or have existed, so I will either let things be or re-create that slot
- if( m_tunnelTrackerVector[theIndex] == nullptr )
- needToCreate = TRUE;
- }
-
- if( needToCreate )// if true, we new theIndex is the index of a nullptr to be filled
- m_tunnelTrackerVector[theIndex] = newInstance(TunnelTracker);
-}
-
-void CaveSystem::unregisterCave( Int theIndex )
-{
- // Doesn't need to do a thing. ContainModule logic knows how to say goodbye, and a TunnelTracker
- // knows how to exist while having no entry points.
- theIndex;
-}
-
-TunnelTracker *CaveSystem::getTunnelTrackerForCaveIndex( Int theIndex )
-{
- TunnelTracker *theTracker = nullptr;
- if( theIndex < m_tunnelTrackerVector.size() )
- {
- theTracker = m_tunnelTrackerVector[theIndex];
- }
-
- DEBUG_ASSERTCRASH( theTracker != nullptr, ("No one should be interested in a sub-cave that doesn't exist.") );
-
- return theTracker;
-}
-
-// ------------------------------------------------------------------------------------------------
-/** Xfer Method
- * Version Info
- * 1: Initial version */
-// ------------------------------------------------------------------------------------------------
-void CaveSystem::xfer( Xfer *xfer )
-{
-
- // version
- XferVersion currentVersion = 1;
- XferVersion version = currentVersion;
- xfer->xferVersion( &version, currentVersion );
-
- // tunnel tracker size and data
- UnsignedShort count = m_tunnelTrackerVector.size();
- xfer->xferUnsignedShort( &count );
- TunnelTracker *tracker;
- if( xfer->getXferMode() == XFER_SAVE )
- {
- std::vector< TunnelTracker* >::iterator it;
-
- for( it = m_tunnelTrackerVector.begin(); it != m_tunnelTrackerVector.end(); ++it )
- {
-
- // xfer data
- tracker = *it;
- xfer->xferSnapshot( tracker );
-
- }
-
- }
- else
- {
-
- // the list must be empty now
- if( m_tunnelTrackerVector.empty() == FALSE )
- {
-
- DEBUG_CRASH(( "CaveSystem::xfer - m_tunnelTrackerVector should be empty but is not" ));
- throw SC_INVALID_DATA;
-
- }
-
- // read each item
- for( UnsignedShort i = 0; i < count; ++i )
- {
-
- // allocate new tracker
- tracker = newInstance( TunnelTracker );
-
- // read data
- xfer->xferSnapshot( tracker );
-
- // put in vector
- m_tunnelTrackerVector.push_back( tracker );
-
- }
-
- }
-
-}
-
-
diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/CrateSystem.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/CrateSystem.cpp
deleted file mode 100644
index fe31dcdc64a..00000000000
--- a/Generals/Code/GameEngine/Source/GameLogic/System/CrateSystem.cpp
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
-** Command & Conquer Generals(tm)
-** Copyright 2025 Electronic Arts Inc.
-**
-** This program is free software: you can redistribute it and/or modify
-** it under the terms of the GNU General Public License as published by
-** the Free Software Foundation, either version 3 of the License, or
-** (at your option) any later version.
-**
-** This program is distributed in the hope that it will be useful,
-** but WITHOUT ANY WARRANTY; without even the implied warranty of
-** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-** GNU General Public License for more details.
-**
-** You should have received a copy of the GNU General Public License
-** along with this program. If not, see .
-*/
-
-////////////////////////////////////////////////////////////////////////////////
-// //
-// (c) 2001-2003 Electronic Arts Inc. //
-// //
-////////////////////////////////////////////////////////////////////////////////
-
-// FILE: CrateSystem.cpp /////////////////////////////////////////////////////////////////////////////////
-// Author: Graham Smallwood Feb 2002
-// Desc: System responsible for Crates as code objects - ini, new/delete etc
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine
-
-#define DEFINE_VETERANCY_NAMES // for TheVeterancyNames[]
-
-#include "GameLogic/CrateSystem.h"
-#include "Common/BitFlagsIO.h"
-
-CrateSystem *TheCrateSystem = nullptr;
-
-CrateSystem::CrateSystem()
-{
- m_crateTemplateVector.clear();
-}
-
-CrateSystem::~CrateSystem()
-{
- Int count = m_crateTemplateVector.size();
- for( Int templateIndex = 0; templateIndex < count; templateIndex ++ )
- {
- CrateTemplate *currentTemplate = m_crateTemplateVector[templateIndex];
- deleteInstance(currentTemplate);
- }
- m_crateTemplateVector.clear();
-}
-
-void CrateSystem::init()
-{
- reset();
-}
-
-void CrateSystem::reset()
-{
- // clean up overrides
- std::vector::iterator it;
- for( it = m_crateTemplateVector.begin(); it != m_crateTemplateVector.end(); )
- {
- CrateTemplate *currentTemplate = *it;
- if( currentTemplate )
- {
- Overridable *tempCrateTemplate = currentTemplate->deleteOverrides();
- if (!tempCrateTemplate)
- {
- // base dude was an override - kill it from the vector
- it = m_crateTemplateVector.erase(it);
- }
- else
- {
- ++it;
- }
- }
- else
- {
- it = m_crateTemplateVector.erase(it);
- }
- }
-}
-
-void CrateSystem::parseCrateTemplateDefinition(INI* ini)
-{
- AsciiString name;
-
- // read the crateTemplate name
- const char* c = ini->getNextToken();
- name.set(c);
-
- CrateTemplate *crateTemplate = TheCrateSystem->friend_findCrateTemplate(name);
- if (crateTemplate == nullptr) {
- crateTemplate = TheCrateSystem->newCrateTemplate(name);
-
- if (ini->getLoadType() == INI_LOAD_CREATE_OVERRIDES) {
- crateTemplate->markAsOverride();
- }
- } else if( ini->getLoadType() != INI_LOAD_CREATE_OVERRIDES ) {
- DEBUG_CRASH(( "[LINE: %d in '%s'] Duplicate crate %s found!", ini->getLineNum(), ini->getFilename().str(), name.str() ));
- } else {
- crateTemplate = TheCrateSystem->newCrateTemplateOverride(crateTemplate);
- }
-
- // parse the ini weapon definition
- ini->initFromINI(crateTemplate, crateTemplate->getFieldParse());
-}
-
-CrateTemplate *CrateSystem::newCrateTemplate( AsciiString name )
-{
- // sanity
- if(name.isEmpty())
- return nullptr;
-
- // allocate a new weapon
- CrateTemplate *ct = newInstance(CrateTemplate);
-
- // if the default template is present, get it and copy over any data to the new template
- const CrateTemplate *defaultCT = findCrateTemplate("DefaultCrate");
- if(defaultCT)
- {
- *ct = *defaultCT;
- }
-
- ct->setName( name );
- m_crateTemplateVector.push_back(ct);
-
- return ct;
-}
-
-CrateTemplate *CrateSystem::newCrateTemplateOverride( CrateTemplate *crateToOverride )
-{
- if (!crateToOverride) {
- return nullptr;
- }
-
- CrateTemplate *newOverride = newInstance(CrateTemplate);
- *newOverride = *crateToOverride;
-
- newOverride->markAsOverride();
-
- crateToOverride->setNextOverride(newOverride);
- return newOverride;
-}
-
-const CrateTemplate *CrateSystem::findCrateTemplate(AsciiString name) const
-{
- // search weapon list for name
- for (size_t i = 0; i < m_crateTemplateVector.size(); i++)
- if(m_crateTemplateVector[i]->getName() == name) {
- CrateTemplateOverride overridable(m_crateTemplateVector[i]);
- return overridable;
- }
-
-
- return nullptr;
-}
-
-CrateTemplate *CrateSystem::friend_findCrateTemplate(AsciiString name)
-{
- // search weapon list for name
- for (size_t i = 0; i < m_crateTemplateVector.size(); i++)
- if(m_crateTemplateVector[i]->getName() == name) {
- CrateTemplateOverride overridable(m_crateTemplateVector[i]);
- return const_cast((const CrateTemplate *)overridable);
- }
- return nullptr;
-}
-
-
-
-//--------------------------------------------------------------------------------
-//--------------------------------------------------------------------------------
-//--------------------------------------------------------------------------------
-//--------------------------------------------------------------------------------
-//--------------------------------------------------------------------------------
-const FieldParse CrateTemplate::TheCrateTemplateFieldParseTable[] =
-{
- { "CreationChance", INI::parseReal, nullptr, offsetof( CrateTemplate, m_creationChance ) },
- { "VeterancyLevel", INI::parseIndexList, TheVeterancyNames, offsetof( CrateTemplate, m_veterancyLevel ) },
- { "KilledByType", KindOfMaskType::parseFromINI, nullptr, offsetof( CrateTemplate, m_killedByTypeKindof) },
- { "CrateObject", CrateTemplate::parseCrateCreationEntry, nullptr, 0 },
- { "KillerScience", INI::parseScience, nullptr, offsetof( CrateTemplate, m_killerScience) },
- { "OwnedByMaker", INI::parseBool, nullptr, offsetof( CrateTemplate, m_isOwnedByMaker) },
- { nullptr, nullptr, nullptr, 0 },
-};
-
-CrateTemplate::CrateTemplate()
-{
- m_creationChance = 0;
- CLEAR_KINDOFMASK(m_killedByTypeKindof);
- m_veterancyLevel = LEVEL_INVALID;
- m_killerScience = SCIENCE_INVALID;
- m_possibleCrates.clear();
- m_isOwnedByMaker = FALSE;
-}
-
-CrateTemplate::~CrateTemplate()
-{
- m_possibleCrates.clear();
-}
-
-void CrateTemplate::parseCrateCreationEntry( INI* ini, void *instance, void *, const void* )
-{
- CrateTemplate *self = (CrateTemplate *)instance;
-
- const char *token = ini->getNextToken();
- AsciiString crateName = token;
-
- token = ini->getNextToken();
- Real crateValue;
- if (sscanf( token, "%f", &crateValue ) != 1)
- throw INI_INVALID_DATA;
-
- crateCreationEntry newEntry;
- newEntry.crateName = crateName;
- newEntry.crateChance = crateValue;
-
- self->m_possibleCrates.push_back( newEntry );
-}
-
diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/Damage.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/Damage.cpp
deleted file mode 100644
index c77f9f8bb6b..00000000000
--- a/Generals/Code/GameEngine/Source/GameLogic/System/Damage.cpp
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
-** Command & Conquer Generals(tm)
-** Copyright 2025 Electronic Arts Inc.
-**
-** This program is free software: you can redistribute it and/or modify
-** it under the terms of the GNU General Public License as published by
-** the Free Software Foundation, either version 3 of the License, or
-** (at your option) any later version.
-**
-** This program is distributed in the hope that it will be useful,
-** but WITHOUT ANY WARRANTY; without even the implied warranty of
-** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-** GNU General Public License for more details.
-**
-** You should have received a copy of the GNU General Public License
-** along with this program. If not, see .
-*/
-
-////////////////////////////////////////////////////////////////////////////////
-// //
-// (c) 2001-2003 Electronic Arts Inc. //
-// //
-////////////////////////////////////////////////////////////////////////////////
-
-// FILE: Damage.cpp ///////////////////////////////////////////////////////////////////////////////
-// Author: Colin Day, September 2002
-// Desc: Basic structures for the damage process
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
-#include "PreRTS.h"
-#include "Common/Xfer.h"
-#include "GameLogic/Damage.h"
-#include "Common/BitFlagsIO.h"
-#include "Common/ThingFactory.h"
-#include "Common/ThingTemplate.h"
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-///////////////////////////////////////////////////////////////////////////////////////////////////
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-template<>
-const char* const DamageTypeFlags::s_bitNameList[] =
-{
- "EXPLOSION",
- "CRUSH",
- "ARMOR_PIERCING",
- "SMALL_ARMS",
- "GATTLING",
- "RADIATION",
- "FLAME",
- "LASER",
- "SNIPER",
- "POISON",
- "HEALING",
- "UNRESISTABLE",
- "WATER",
- "DEPLOY",
- "SURRENDER",
- "HACK",
- "KILL_PILOT",
- "PENALTY",
- "FALLING",
- "MELEE",
- "DISARM",
- "HAZARD_CLEANUP",
- "PARTICLE_BEAM",
- "TOPPLING",
- "INFANTRY_MISSILE",
- "AURORA_BOMB",
- "LAND_MINE",
- "JET_MISSILES",
- "STEALTHJET_MISSILES",
- "MOLOTOV_COCKTAIL",
- "COMANCHE_VULCAN",
-#if RTS_GENERALS
- "FLESHY_SNIPER",
-#endif
- "SUBDUAL_MISSILE",
- "SUBDUAL_VEHICLE",
- "SUBDUAL_BUILDING",
- "SUBDUAL_UNRESISTABLE",
- "MICROWAVE",
- "KILL_GARRISONED",
- "STATUS",
-
- nullptr
-};
-static_assert(ARRAY_SIZE(DamageTypeFlags::s_bitNameList) == DamageTypeFlags::NumBits + 1, "Incorrect array size");
-
-DamageTypeFlags DAMAGE_TYPE_FLAGS_NONE; // inits to all zeroes
-DamageTypeFlags DAMAGE_TYPE_FLAGS_ALL(DamageTypeFlags::kInitSetAll);
-
-// ------------------------------------------------------------------------------------------------
-/** Xfer method
- * Version Info:
- * 1: Initial version */
-// ------------------------------------------------------------------------------------------------
-void DamageInfo::xfer( Xfer *xfer )
-{
-
- // version
- XferVersion currentVersion = 1;
- XferVersion version = currentVersion;
- xfer->xferVersion( &version, currentVersion );
-
- // xfer input
- xfer->xferSnapshot( &in );
-
- // xfer output
- xfer->xferSnapshot( &out );
-
-}
-
-// ------------------------------------------------------------------------------------------------
-/** Xfer method
- * Version Info:
- * 1: Initial version
- * 2: Damage FX override (Added for Zero Hour)
- * 3: Shock wave and damage status type (Added for Zero Hour)
-*/
-// ------------------------------------------------------------------------------------------------
-void DamageInfoInput::xfer( Xfer *xfer )
-{
-
- // version
-#if RTS_GENERALS && RETAIL_COMPATIBLE_XFER_SAVE
- XferVersion currentVersion = 1;
-#else
- XferVersion currentVersion = 3;
-#endif
-
- XferVersion version = currentVersion;
- xfer->xferVersion( &version, currentVersion );
-
- // source id
- xfer->xferObjectID( &m_sourceID );
-
- // source player mask
- xfer->xferUser( &m_sourcePlayerMask, sizeof( PlayerMaskType ) );
-
- // damage type
- xfer->xferUser( &m_damageType, sizeof( DamageType ) );
-
- // damage FX Override
- if( version >= 2 )
- xfer->xferUser( &m_damageFXOverride, sizeof( DamageType ) );
-
- // death type
- xfer->xferUser( &m_deathType, sizeof( DeathType ) );
-
- // amount
- xfer->xferReal( &m_amount );
-
- // kill no matter what (old versions default to FALSE).
- if( currentVersion >= 2 )
- {
- xfer->xferBool( &m_kill );
- }
-
- if( version >= 3 )
- {
- xfer->xferUser( &m_damageStatusType, sizeof(ObjectStatusTypes) );//It's an enum
-
- xfer->xferCoord3D(&m_shockWaveVector);
- xfer->xferReal( &m_shockWaveAmount );
- xfer->xferReal( &m_shockWaveRadius );
- xfer->xferReal( &m_shockWaveTaperOff );
-
- AsciiString thingString = m_sourceTemplate ? m_sourceTemplate->getName() : AsciiString::TheEmptyString;
- xfer->xferAsciiString( &thingString );
- if( xfer->getXferMode() == XFER_LOAD )
- {
- m_sourceTemplate = TheThingFactory->findTemplate( thingString );
- }
- }
-
-}
-
-// ------------------------------------------------------------------------------------------------
-/** Xfer method
- * Version Info:
- * 1: Initial version */
-// ------------------------------------------------------------------------------------------------
-void DamageInfoOutput::xfer( Xfer *xfer )
-{
-
- // version
- XferVersion currentVersion = 1;
- XferVersion version = currentVersion;
- xfer->xferVersion( &version, currentVersion );
-
- // actual damage
- xfer->xferReal( &m_actualDamageDealt );
-
- // damage clipped
- xfer->xferReal( &m_actualDamageClipped );
-
- // no effect
- xfer->xferBool( &m_noEffect );
-
-}
-
diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp
deleted file mode 100644
index 78d051ba60b..00000000000
--- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp
+++ /dev/null
@@ -1,2123 +0,0 @@
-/*
-** Command & Conquer Generals(tm)
-** Copyright 2025 Electronic Arts Inc.
-**
-** This program is free software: you can redistribute it and/or modify
-** it under the terms of the GNU General Public License as published by
-** the Free Software Foundation, either version 3 of the License, or
-** (at your option) any later version.
-**
-** This program is distributed in the hope that it will be useful,
-** but WITHOUT ANY WARRANTY; without even the implied warranty of
-** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-** GNU General Public License for more details.
-**
-** You should have received a copy of the GNU General Public License
-** along with this program. If not, see .
-*/
-
-////////////////////////////////////////////////////////////////////////////////
-// //
-// (c) 2001-2003 Electronic Arts Inc. //
-// //
-////////////////////////////////////////////////////////////////////////////////
-
-// FILE: GameLogicDispatch.cpp ////////////////////////////////////////////////////////////////////
-// Author: Mike Booth, Colin Day
-// Description: Message logic to drive the game play
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
-#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine
-
-#include "Common/CRCDebug.h"
-#include "Common/FramePacer.h"
-#include "Common/GameAudio.h"
-#include "Common/GameEngine.h"
-#include "Common/GlobalData.h"
-#include "Common/NameKeyGenerator.h"
-#include "Common/ThingFactory.h"
-#include "Common/Player.h"
-#include "Common/PlayerList.h"
-#include "Common/PlayerTemplate.h"
-#include "Common/MessageStream.h"
-#include "Common/MultiplayerSettings.h"
-#include "Common/Recorder.h"
-#include "Common/BuildAssistant.h"
-#include "Common/SpecialPower.h"
-#include "Common/ThingTemplate.h"
-#include "Common/Upgrade.h"
-#include "Common/StatsCollector.h"
-#include "Common/Radar.h"
-
-#include "GameLogic/AIPathfind.h"
-#include "GameLogic/GameLogic.h"
-#include "GameLogic/Locomotor.h"
-#include "GameLogic/Object.h"
-#include "GameLogic/ObjectCreationList.h"
-#include "GameLogic/ObjectIter.h"
-//#include "GameLogic/PartitionManager.h"
-#include "GameLogic/AI.h"
-#include "GameLogic/Module/AIUpdate.h"
-#include "GameLogic/Module/BodyModule.h"
-#include "GameLogic/Module/OpenContain.h"
-#include "GameLogic/Module/ProductionUpdate.h"
-#include "GameLogic/Module/SpecialPowerModule.h"
-#include "GameLogic/ScriptActions.h"
-#include "GameLogic/ScriptEngine.h"
-#include "GameLogic/VictoryConditions.h"
-#include "GameLogic/Weapon.h"
-
-#include "GameClient/CommandXlat.h"
-#include "GameClient/ControlBar.h"
-#include "GameClient/Drawable.h"
-#include "GameClient/Eva.h"
-#include "GameClient/GameText.h"
-#include "GameClient/GameWindowManager.h"
-#include "GameClient/GUICallbacks.h"
-#include "GameClient/InGameUI.h"
-#include "GameClient/KeyDefs.h"
-#include "GameClient/Mouse.h"
-#include "GameClient/ParticleSys.h"
-#include "GameClient/Shell.h"
-#include "GameClient/Module/BeaconClientUpdate.h"
-#include "GameClient/LookAtXlat.h"
-
-#include "GameNetwork/NetworkInterface.h"
-
-
-
-
-#define MAX_PATH_SUBJECTS 64
-static Bool theBuildPlan = false;
-static Object *thePlanSubject[ MAX_PATH_SUBJECTS ];
-static int thePlanSubjectCount = 0;
-//static WindowLayout *background = nullptr;
-
-// ------------------------------------------------------------------------------------------------
-/** Issue the movement command to the object */
-// ------------------------------------------------------------------------------------------------
-static void doMoveTo( Object *obj, const Coord3D *pos )
-{
- AIUpdateInterface *ai = obj->getAIUpdateInterface();
- DEBUG_ASSERTCRASH(ai, ("Attempted doMoveTo() on an Object with no AI"));
- if (ai)
- {
- if (theBuildPlan)
- {
- int i;
-
- // if this object isn't in the buildPlan set, add it
- for( i=0; iqueueWaypoint( pos );
- }
- else
- {
- ai->clearWaypointQueue();
- obj->leaveGroup();
- obj->releaseWeaponLock(LOCKED_TEMPORARILY); // release any temporary locks.
- ai->aiMoveToPosition( pos, CMD_FROM_PLAYER );
- }
- }
-}
-
-// ------------------------------------------------------------------------------------------------
-// ------------------------------------------------------------------------------------------------
-static void doSetRallyPoint( Object *obj, const Coord3D& pos )
-{
- Bool isLocalPlayer = obj->isLocallyControlled();
-
- //
- // we must be able to find a path from the object to the point they have chosen, cause setting
- // a rally point at a invalid location would suck. To be super nice, we have to make sure
- // that every type of object that can be created from the thing setting the rally point
- // can actually find a path from the thing to the point
- //
-
- // to see the never-finished code to check all locomotor sets, see past revs of GUICommandTranslator.cpp -MDC
-
- //
- // for now, just use the basic human locomotor ... and enable the above code when Steven
- // tells me how to get the locomotor sets based on a thing template (CBD)
- //
- NameKeyType key = NAMEKEY( "BasicHumanLocomotor" );
- LocomotorSet locomotorSet;
- locomotorSet.addLocomotor( TheLocomotorStore->findLocomotorTemplate( key ) );
- if( TheAI->pathfinder()->clientSafeQuickDoesPathExist( locomotorSet, obj->getPosition(), &pos ) == FALSE )
- {
-
- // user feedback
- if( isLocalPlayer )
- {
-
- // display error message to user
- TheInGameUI->message( TheGameText->fetch( "GUI:RallyPointNoPath" ) );
-
- // play the no can do sound
- static AudioEventRTS rallyNotSet("UnableToSetRallyPoint");
- rallyNotSet.setPosition(&pos);
- rallyNotSet.setPlayerIndex(obj->getControllingPlayer()->getPlayerIndex());
- TheAudio->addAudioEvent(&rallyNotSet);
-
- }
-
- return;
-
- }
-
- // feedback to the player
- if( isLocalPlayer )
- {
-
- // print a message to the user
- UnicodeString info;
- info.format( TheGameText->fetch( "GUI:RallyPointSet" ),
- obj->getTemplate()->getDisplayName().str() );
- TheInGameUI->message( info );
-
- // play a sound for setting the rally point
- static AudioEventRTS rallyPointSet("RallyPointSet");
- rallyPointSet.setPosition(&pos);
- rallyPointSet.setPlayerIndex(obj->getControllingPlayer()->getPlayerIndex());
- TheAudio->addAudioEvent(&rallyPointSet);
-
- // mark the UI as dirty so that we re-evaluate the selection and show the rally point
- Drawable *draw = obj->getDrawable();
- if( draw && draw->isSelected() )
- TheControlBar->markUIDirty();
-
- }
-
- // if this object has a ProductionExitUpdate interface, we are setting a rally point
- ExitInterface *exitInterface = obj->getObjectExitInterface();
- if( exitInterface )
- {
- // set the rally point
- exitInterface->setRallyPoint( &pos );
-
- }
-
-}
-
-static Object * getSingleObjectFromSelection(const AIGroup *currentlySelectedGroup)
-{
- if( currentlySelectedGroup && !currentlySelectedGroup->isEmpty() )
- {
- const VecObjectID& selectedObjects = currentlySelectedGroup->getAllIDs();
- DEBUG_ASSERTCRASH(selectedObjects.size() == 1, ("Trying to get single object from multiple selection!"));
- VecObjectID::const_iterator it = selectedObjects.begin();
- return TheGameLogic->findObjectByID(*it);
- }
- return nullptr;
-}
-
-// ------------------------------------------------------------------------------------------------
-// ------------------------------------------------------------------------------------------------
-void GameLogic::closeWindows()
-{
- HideDiplomacy();
- ResetDiplomacy();
- HideInGameChat();
- ResetInGameChat();
- TheControlBar->hidePurchaseScience();
- TheControlBar->hideSpecialPowerShortcut();
- HideQuitMenu();
-
- // hide the options menu
- NameKeyType buttonID = TheNameKeyGenerator->nameToKey( "OptionsMenu.wnd:ButtonBack" );
- GameWindow *button = TheWindowManager->winGetWindowFromId( nullptr, buttonID );
- GameWindow *window = TheWindowManager->winGetWindowFromId( nullptr, TheNameKeyGenerator->nameToKey("OptionsMenu.wnd:OptionsMenuParent") );
- if(window)
- TheWindowManager->winSendSystemMsg( window, GBM_SELECTED,
- (WindowMsgData)button, buttonID );
-}
-
-// ------------------------------------------------------------------------------------------------
-// ------------------------------------------------------------------------------------------------
-void GameLogic::clearGameData( Bool showScoreScreen )
-{
- if( !isInGame() )
- {
- DEBUG_CRASH(("We tried to clear the game data when we weren't in a game"));
- return;
- }
-
- setClearingGameData( TRUE );
-
-// m_background = TheWindowManager->winCreateLayout("Menus/BlankWindow.wnd");
-// DEBUG_ASSERTCRASH(m_background,("We Couldn't Load Menus/BlankWindow.wnd"));
-// m_background->hide(FALSE);
-// m_background->bringForward();
- // reset the game engine to accept data for a new game
- if(TheStatsCollector)
- TheStatsCollector->writeFileEnd();
-
- TheScriptActions->closeWindows(FALSE); // Close victory or defeat windows.
-
- Bool shellGame = FALSE;
- if ((!isInShellGame() || !isInGame()) && showScoreScreen && !TheGlobalData->m_headless)
- {
- shellGame = TRUE;
- TheShell->push("Menus/ScoreScreen.wnd");
- TheShell->showShell(FALSE); // by passing in false, we don't want to run the Init on the shell screen we just pushed on
-
- void FixupScoreScreenMovieWindow();
- FixupScoreScreenMovieWindow();
- }
-
- TheGameEngine->reset();
- setGameMode(GAME_NONE);
-// m_background->bringForward();
-// if(shellGame)
-
-
- if (TheGlobalData->m_initialFile.isEmpty() == FALSE || m_quitToDesktopAfterMatch)
- {
- TheGameEngine->setQuitting(TRUE);
- m_quitToDesktopAfterMatch = FALSE;
- }
-
- HideControlBar();
- closeWindows();
-
- TheMouse->setVisibility(TRUE);
-
- if(m_background)
- {
- m_background->destroyWindows();
- deleteInstance(m_background);
- m_background = nullptr;
- }
-
- setClearingGameData( FALSE );
-
-}
-
-// ------------------------------------------------------------------------------------------------
-/** Prepare for a new game */
-// ------------------------------------------------------------------------------------------------
-void GameLogic::prepareNewGame( GameMode gameMode, GameDifficulty diff, Int rankPoints )
-{
- //Kris: Commented this out, but leaving it around incase it bites us later. I cleaned up the
- // nomenclature. Look for setLoadingMap() and setLoadingSave()
- //setGameLoading(TRUE);
-
- TheScriptEngine->setGlobalDifficulty(diff);
-
- if(!m_background)
- {
- m_background = TheWindowManager->winCreateLayout("Menus/BlankWindow.wnd");
- DEBUG_ASSERTCRASH(m_background,("We Couldn't Load Menus/BlankWindow.wnd"));
- m_background->hide(FALSE);
- m_background->bringForward();
- }
- m_background->getFirstWindow()->winClearStatus(WIN_STATUS_IMAGE);
- setGameMode( gameMode );
- if (!TheGlobalData->m_pendingFile.isEmpty())
- {
- TheWritableGlobalData->m_mapName = TheGlobalData->m_pendingFile;
- TheWritableGlobalData->m_pendingFile.clear();
- }
-
- m_rankPointsToAddAtGameStart = rankPoints;
- DEBUG_LOG(("GameLogic::prepareNewGame() - m_rankPointsToAddAtGameStart = %d", m_rankPointsToAddAtGameStart));
-
- // If we're about to start a game, hide the shell.
- if(!isInShellGame())
- TheShell->hideShell();
-
- m_startNewGame = FALSE;
-
-}
-
-//-------------------------------------------------------------------------------------------------
-/** This message handles dispatches object command messages to the
- * appropriate objects.
- * @todo Rename this to "CommandProcessor", or similar. */
-//-------------------------------------------------------------------------------------------------
-void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData )
-{
-#ifdef RTS_DEBUG
- DEBUG_ASSERTCRASH(msg != nullptr && msg != (GameMessage*)0xdeadbeef, ("bad msg"));
-#endif
-
- Player *msgPlayer = ThePlayerList->getNthPlayer( msg->getPlayerIndex() );
- if (msgPlayer == nullptr)
- {
- DEBUG_CRASH(("logicMessageDispatcher: Processing message from unknown player (player index '%d')", msg->getPlayerIndex()));
- return;
- }
-
- AIGroupPtr currentlySelectedGroup = nullptr;
-
- if (isInGame())
- {
- if (msg->getType() >= GameMessage::MSG_BEGIN_NETWORK_MESSAGES && msg->getType() <= GameMessage::MSG_END_NETWORK_MESSAGES)
- {
- if (msg->getType() != GameMessage::MSG_LOGIC_CRC && msg->getType() != GameMessage::MSG_SET_REPLAY_CAMERA)
- {
- currentlySelectedGroup = TheAI->createGroup(); // can't do this outside a game - it'll cause sync errors galore.
- CRCGEN_LOG(( "Creating AIGroup %d in GameLogic::logicMessageDispatcher()", currentlySelectedGroup?currentlySelectedGroup->getID():0 ));
-#if RETAIL_COMPATIBLE_AIGROUP
- msgPlayer->getCurrentSelectionAsAIGroup(currentlySelectedGroup);
-#else
- msgPlayer->getCurrentSelectionAsAIGroup(currentlySelectedGroup.Peek());
-#endif
-
- // We can't issue commands to groups that contain units that don't belong to the issuing player, so pretend like
- // there's nothing selected. Also, if currentlySelectedGroup is empty, go ahead and delete it, so that we can skip
- // any processing on it.
- if (currentlySelectedGroup->isEmpty())
- {
-#if RETAIL_COMPATIBLE_AIGROUP
- TheAI->destroyGroup(currentlySelectedGroup);
-#endif
- currentlySelectedGroup = nullptr;
- }
-
- // If there are any units that the player doesn't own, then remove them from the "currentlySelectedGroup"
- if (currentlySelectedGroup)
- if (currentlySelectedGroup->removeAnyObjectsNotOwnedByPlayer(msgPlayer))
- currentlySelectedGroup = nullptr;
-
- if(TheStatsCollector)
- TheStatsCollector->collectMsgStats(msg);
- }
- }
- }
-
-#ifdef DEBUG_LOGGING
- AsciiString commandName;
-
- commandName = msg->getCommandAsString();
- if (msg->getType() < GameMessage::MSG_BEGIN_NETWORK_MESSAGES || msg->getType() > GameMessage::MSG_END_NETWORK_MESSAGES)
- {
- commandName.concat(" (NON-LOGIC-MESSAGE!!!)");
- }
- else if (msg->getType() == GameMessage::MSG_BEGIN_NETWORK_MESSAGES)
- {
- commandName = " (CRC message!)";
- }
-#if 0
- if (commandName.isNotEmpty() /*&& msg->getType() != GameMessage::MSG_FRAME_TICK*/)
- {
- DEBUG_LOG(("Frame %d: GameLogic::logicMessageDispatcher() saw a %s from player %d (%ls)", getFrame(), commandName.str(),
- msgPlayer->getPlayerIndex(), msgPlayer->getPlayerDisplayName().str()));
- }
-#endif
-#endif // DEBUG_LOGGING
-
- // process the message
- GameMessage::Type msgType = msg->getType();
- switch( msgType )
- {
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_NEW_GAME:
- {
-#if !RETAIL_COMPATIBLE_CRC
- // TheSuperHackers @fix stephanmeesters 11/03/2026
- // Make sure we're ready to start a new game. This prevents an issue where an infinite disconnect screen
- // can be force-triggered in an online match by using cheats.
- if ( isInGame() || isClearingGameData() || isLoadingMap() )
- {
- DEBUG_CRASH( ("Called MSG_NEW_GAME while game is not ready (inGame=%d, clearingData=%d, loadingMap=%d)",
- isInGame(), isClearingGameData(), isLoadingMap()) );
- break;
- }
-#endif
-
- //DEBUG_ASSERTCRASH(msg->getArgumentCount() == 1 || msg->getArgumentCount() == 2, ("%d arguments to MSG_NEW_GAME", msg->getArgumentCount()));
- GameMode gameMode = (GameMode)msg->getArgument( 0 )->integer;
- Int rankPoints = 0;
- GameDifficulty diff = DIFFICULTY_NORMAL;
- if ( msg->getArgumentCount() >= 2 )
- diff = (GameDifficulty)msg->getArgument( 1 )->integer;
- if ( msg->getArgumentCount() >= 3 )
- rankPoints = msg->getArgument( 2 )->integer;
-
- if ( msg->getArgumentCount() >= 4 )
- {
- Int maxFPS = msg->getArgument( 3 )->integer;
- if (maxFPS < 1 || maxFPS > 1000)
- maxFPS = TheGlobalData->m_framesPerSecondLimit;
- DEBUG_LOG(("Setting max FPS limit to %d FPS", maxFPS));
- TheFramePacer->setFramesPerSecondLimit(maxFPS);
- TheWritableGlobalData->m_useFpsLimit = true;
- }
-
- // prepare for new game
- prepareNewGame( gameMode, diff, rankPoints );
-
- // start new game
- startNewGame( FALSE );
-
- break;
-
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_CLEAR_GAME_DATA:
- {
-
-#if defined(RTS_DEBUG)
- if (TheDisplay && TheGlobalData->m_dumpAssetUsage)
- TheDisplay->dumpAssetUsage(TheGlobalData->m_mapName.str());
-#endif
-
- if (currentlySelectedGroup)
- {
-#if RETAIL_COMPATIBLE_AIGROUP
- TheAI->destroyGroup(currentlySelectedGroup);
-#else
- currentlySelectedGroup->removeAll();
-#endif
- }
- currentlySelectedGroup = nullptr;
- clearGameData();
- break;
-
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_META_BEGIN_PATH_BUILD:
- {
- DEBUG_LOG(("META: begin path build"));
- DEBUG_ASSERTCRASH(!theBuildPlan, ("mismatched theBuildPlan"));
-
- if (theBuildPlan == false)
- {
- theBuildPlan = true;
- thePlanSubjectCount = 0;
- }
-
- break;
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_META_END_PATH_BUILD:
- {
- DEBUG_LOG(("META: end path build"));
- DEBUG_ASSERTCRASH(theBuildPlan, ("mismatched theBuildPlan"));
-
- // tell everyone who participated in the plan to move
- for( int i=0; igetAIUpdateInterface();
- if (ai)
- ai->executeWaypointQueue();
- }
-
- theBuildPlan = false;
- thePlanSubjectCount = 0;
-
- break;
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_SET_RALLY_POINT:
- {
- Object *obj = findObjectByID( msg->getArgument( 0 )->objectID );
- Coord3D dest = msg->getArgument( 1 )->location;
-
- if (obj)
- {
-#if !RETAIL_COMPATIBLE_CRC
- // TheSuperHackers @fix stephanmeesters 11/03/2026 Validate the owner of the source object
- if ( obj->getControllingPlayer() != msgPlayer )
- {
- DEBUG_CRASH( ("MSG_SET_RALLY_POINT: Player '%ls' attempted to set the rally point of object '%s' owned by player '%ls'.",
- msgPlayer->getPlayerDisplayName().str(),
- obj->getTemplate()->getName().str(),
- obj->getControllingPlayer()->getPlayerDisplayName().str()) );
- break;
- }
-#endif
-
- doSetRallyPoint( obj, dest );
- }
-
- break;
-
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_DO_WEAPON:
- {
- WeaponSlotType weaponSlot = (WeaponSlotType)msg->getArgument( 0 )->integer;
- Int maxShotsToFire = msg->getArgument( 1 )->integer;
-
- // lock it just till the weapon is empty or the attack is "done"
- if( currentlySelectedGroup && currentlySelectedGroup->setWeaponLockForGroup( weaponSlot, LOCKED_TEMPORARILY ))
- {
- currentlySelectedGroup->groupAttackPosition( nullptr, maxShotsToFire, CMD_FROM_PLAYER );
- }
-
- break;
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_COMBATDROP_AT_OBJECT:
- {
- Object *targetObject = findObjectByID( msg->getArgument( 0 )->objectID );
-
- // issue command for either single object or for selected group
- if( currentlySelectedGroup && targetObject )
- currentlySelectedGroup->groupCombatDrop( targetObject,
- *targetObject->getPosition(),
- CMD_FROM_PLAYER );
-
-/*
- if( sourceObject && targetObject )
- {
- AIUpdateInterface* sourceAI = sourceObject->getAIUpdateInterface();
- if (sourceAI)
- {
- sourceAI->aiCombatDrop( targetObject, *targetObject->getPosition(), CMD_FROM_PLAYER );
- }
- }
-*/
-
- break;
-
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_COMBATDROP_AT_LOCATION:
- {
- Coord3D targetLoc = msg->getArgument( 0 )->location;
-
- if( currentlySelectedGroup )
- currentlySelectedGroup->groupCombatDrop( nullptr, targetLoc, CMD_FROM_PLAYER );
-
-/*
- if( sourceObject )
- {
- AIUpdateInterface* sourceAI = sourceObject->getAIUpdateInterface();
- if (sourceAI)
- {
- sourceAI->aiCombatDrop( nullptr, targetLoc, CMD_FROM_PLAYER );
- }
- }
-*/
-
- break;
-
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_DO_WEAPON_AT_OBJECT:
- {
- // Lock the weapon choice to the right weapon, then give an attack command
-
- WeaponSlotType weaponSlot = (WeaponSlotType)msg->getArgument( 0 )->integer;
- Object *targetObject = findObjectByID( msg->getArgument( 1 )->objectID );
- Int maxShotsToFire = msg->getArgument( 2 )->integer;
-
- // sanity
- if( targetObject == nullptr )
- break;
-
-
- // issue command for either single object or for selected group
- if( currentlySelectedGroup )
- {
- // lock it just till the weapon is empty or the attack is "done"
- if (currentlySelectedGroup->setWeaponLockForGroup( weaponSlot, LOCKED_TEMPORARILY ))
- currentlySelectedGroup->groupAttackObject( targetObject, maxShotsToFire, CMD_FROM_PLAYER );
- }
- break;
-
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_SWITCH_WEAPONS:
- {
- // use the selected group
- WeaponSlotType weaponSlot = (WeaponSlotType)msg->getArgument( 0 )->integer;
- // lock until un-switched, or switched to something else.
- if( currentlySelectedGroup )
- currentlySelectedGroup->setWeaponLockForGroup( weaponSlot, LOCKED_PERMANENTLY );
- break;
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_SET_MINE_CLEARING_DETAIL:
- {
- if( currentlySelectedGroup )
- {
- currentlySelectedGroup->setMineClearingDetail(true);
- }
- break;
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_DO_WEAPON_AT_LOCATION:
- {
- WeaponSlotType weaponSlot = (WeaponSlotType)msg->getArgument( 0 )->integer;
- Coord3D targetLoc = msg->getArgument( 1 )->location;
- Int maxShotsToFire = msg->getArgument( 2 )->integer;
-
- // issue command for either single object or for selected group
- if( currentlySelectedGroup )
- {
- // lock it just till the weapon is empty or the attack is "done"
- if (currentlySelectedGroup->setWeaponLockForGroup( weaponSlot, LOCKED_TEMPORARILY ))
- currentlySelectedGroup->groupAttackPosition( &targetLoc, maxShotsToFire, CMD_FROM_PLAYER );
-
-
- }
-
- break;
-
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_DO_SPECIAL_POWER:
- {
-
- // first argument is the special power ID
- UnsignedInt specialPowerID = msg->getArgument( 0 )->integer;
-
- // Command button options -- special power may care about variance options
- UnsignedInt options = msg->getArgument( 1 )->integer;
-
- // check for possible specific source, ignoring selection.
- ObjectID sourceID = msg->getArgument(2)->objectID;
- Object* source = findObjectByID(sourceID);
- if (source != nullptr)
- {
-#if !RETAIL_COMPATIBLE_CRC
- // TheSuperHackers @fix stephanmeesters 01/03/2026 Validate the origin of the source object
- if ( source->getControllingPlayer() != msgPlayer )
- {
- DEBUG_CRASH( ("MSG_DO_SPECIAL_POWER: Player '%ls' attempted to control the object '%s' owned by player '%ls'.",
- msgPlayer->getPlayerDisplayName().str(),
- source->getTemplate()->getName().str(),
- source->getControllingPlayer()->getPlayerDisplayName().str()) );
- break;
- }
-#endif
-
- AIGroupPtr theGroup = TheAI->createGroup();
- theGroup->add(source);
- theGroup->groupDoSpecialPower( specialPowerID, options );
-#if RETAIL_COMPATIBLE_AIGROUP
- TheAI->destroyGroup(theGroup);
-#else
- theGroup->removeAll();
-#endif
- }
- else
- {
- //Use the selected group!
- if( currentlySelectedGroup )
- {
- currentlySelectedGroup->groupDoSpecialPower( specialPowerID, options );
- }
- }
- break;
-
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_DO_SPECIAL_POWER_AT_LOCATION:
- {
- // first argument is the special power ID
- UnsignedInt specialPowerID = msg->getArgument( 0 )->integer;
-
- // Location argument 2 is destination
- Coord3D targetCoord = msg->getArgument(1)->location;
-
- // Object in way -- if applicable (some specials care, others don't)
- ObjectID objectID = msg->getArgument( 2 )->objectID;
- Object *objectInWay = findObjectByID( objectID );
-
- // Command button options -- special power may care about variance options
- UnsignedInt options = msg->getArgument( 3 )->integer;
-
- // check for possible specific source, ignoring selection.
- ObjectID sourceID = msg->getArgument(4)->objectID;
- Object* source = findObjectByID(sourceID);
- if (source != nullptr)
- {
-#if !RETAIL_COMPATIBLE_CRC
- // TheSuperHackers @fix stephanmeesters 01/03/2026 Validate the origin of the source object
- if ( source->getControllingPlayer() != msgPlayer )
- {
- DEBUG_CRASH( ("MSG_DO_SPECIAL_POWER_AT_LOCATION: Player '%ls' attempted to control the object '%s' owned by player '%ls'.",
- msgPlayer->getPlayerDisplayName().str(),
- source->getTemplate()->getName().str(),
- source->getControllingPlayer()->getPlayerDisplayName().str()) );
- break;
- }
-#endif
-
- AIGroupPtr theGroup = TheAI->createGroup();
- theGroup->add(source);
- theGroup->groupDoSpecialPowerAtLocation( specialPowerID, &targetCoord, INVALID_ANGLE, objectInWay, options );
-#if RETAIL_COMPATIBLE_AIGROUP
- TheAI->destroyGroup(theGroup);
-#else
- theGroup->removeAll();
-#endif
- }
- else
- {
- //Use the selected group!
- if( currentlySelectedGroup )
- {
- currentlySelectedGroup->groupDoSpecialPowerAtLocation( specialPowerID, &targetCoord, INVALID_ANGLE, objectInWay, options );
- }
- }
- break;
-
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_DO_SPECIAL_POWER_AT_OBJECT:
- {
- // first argument is the special power ID
- UnsignedInt specialPowerID = msg->getArgument( 0 )->integer;
-
- // argument 2 is target object
- ObjectID targetID = msg->getArgument(1)->objectID;
- Object *target = findObjectByID( targetID );
- if( !target )
- {
- break;
- }
-
- // Command button options -- special power may care about variance options
- UnsignedInt options = msg->getArgument( 2 )->integer;
-
- // check for possible specific source, ignoring selection.
- ObjectID sourceID = msg->getArgument(3)->objectID;
- Object* source = findObjectByID(sourceID);
- if (source != nullptr)
- {
-#if !RETAIL_COMPATIBLE_CRC
- // TheSuperHackers @fix stephanmeesters 01/03/2026 Validate the origin of the source object
- if ( source->getControllingPlayer() != msgPlayer )
- {
- DEBUG_CRASH( ("MSG_DO_SPECIAL_POWER_AT_OBJECT: Player '%ls' attempted to control the object '%s' owned by player '%ls'.",
- msgPlayer->getPlayerDisplayName().str(),
- source->getTemplate()->getName().str(),
- source->getControllingPlayer()->getPlayerDisplayName().str()) );
- break;
- }
-#endif
-
- AIGroupPtr theGroup = TheAI->createGroup();
- theGroup->add(source);
- theGroup->groupDoSpecialPowerAtObject( specialPowerID, target, options );
-#if RETAIL_COMPATIBLE_AIGROUP
- TheAI->destroyGroup(theGroup);
-#else
- theGroup->removeAll();
-#endif
- }
- else
- {
- if( currentlySelectedGroup )
- {
- currentlySelectedGroup->groupDoSpecialPowerAtObject( specialPowerID, target, options );
- }
- }
- break;
-
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_DO_ATTACKMOVETO:
- {
- Coord3D dest = msg->getArgument( 0 )->location;
-
- if (currentlySelectedGroup)
- {
- currentlySelectedGroup->releaseWeaponLockForGroup(LOCKED_TEMPORARILY); // release any temporary locks.
- currentlySelectedGroup->groupAttackMoveToPosition( &dest, NO_MAX_SHOTS_LIMIT, CMD_FROM_PLAYER );
- }
-
- break;
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_DO_FORCEMOVETO:
- {
- Coord3D dest = msg->getArgument( 0 )->location;
-
- if (currentlySelectedGroup)
- {
- currentlySelectedGroup->releaseWeaponLockForGroup(LOCKED_TEMPORARILY); // release any temporary locks.
- currentlySelectedGroup->groupMoveToPosition( &dest, false, CMD_FROM_PLAYER );
- }
-
- break;
- }
-
- //---------------------------------------------------------------------------------------------
- // MSG_DO_SALVAGE is intentionally set up to mimic the moveto.
- case GameMessage::MSG_DO_SALVAGE:
- case GameMessage::MSG_DO_MOVETO:
- {
- Coord3D dest = msg->getArgument( 0 )->location;
-
- if( currentlySelectedGroup )
- {
- //DEBUG_LOG(("GameLogicDispatch - got a MSG_DO_MOVETO command"));
- currentlySelectedGroup->releaseWeaponLockForGroup(LOCKED_TEMPORARILY); // release any temporary locks.
- currentlySelectedGroup->groupMoveToPosition( &dest, false, CMD_FROM_PLAYER );
- }
-
- break;
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_ADD_WAYPOINT:
- {
- Coord3D dest = msg->getArgument( 0 )->location;
-
- if( currentlySelectedGroup )
- {
- //DEBUG_LOG(("GameLogicDispatch - got a MSG_DO_MOVETO command"));
- currentlySelectedGroup->releaseWeaponLockForGroup(LOCKED_TEMPORARILY); // release any temporary locks.
- currentlySelectedGroup->groupMoveToPosition( &dest, true, CMD_FROM_PLAYER );
- }
-
- break;
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_DO_GUARD_POSITION:
- {
- Coord3D loc = msg->getArgument( 0 )->location;
- GuardMode gm = (GuardMode)msg->getArgument( 1 )->integer;
- if (currentlySelectedGroup)
- {
- currentlySelectedGroup->groupGuardPosition(&loc, gm, CMD_FROM_PLAYER);
- }
-
- break;
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_DO_GUARD_OBJECT:
- {
- Object* obj = findObjectByID( msg->getArgument( 0 )->objectID );
- if (!obj)
- break;
-
- GuardMode gm = (GuardMode)msg->getArgument( 1 )->integer;
- if (currentlySelectedGroup)
- {
- currentlySelectedGroup->groupGuardObject(obj, gm, CMD_FROM_PLAYER);
- }
-
- break;
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_DO_STOP:
- {
- if (currentlySelectedGroup)
- {
- currentlySelectedGroup->groupIdle(CMD_FROM_PLAYER);
- }
-
- break;
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_DO_SCATTER:
- {
- if (currentlySelectedGroup)
- {
- currentlySelectedGroup->groupScatter(CMD_FROM_PLAYER);
- }
-
- break;
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_CREATE_FORMATION:
- {
- if (currentlySelectedGroup)
- {
- currentlySelectedGroup->groupCreateFormation(CMD_FROM_PLAYER);
- }
-
- break;
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_CLEAR_INGAME_POPUP_MESSAGE:
- {
-
- if( TheInGameUI )
- {
- TheInGameUI->clearPopupMessageData();
- }
- break;
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_DO_CHEER:
- {
- //All selected units play cheer animation.
- if( currentlySelectedGroup )
- {
- currentlySelectedGroup->groupCheer( CMD_FROM_PLAYER );
- }
- break;
- }
-
-#if defined(RTS_DEBUG)
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_DEBUG_KILL_SELECTION:
- {
- //All selected units die
- if( currentlySelectedGroup )
- {
- const VecObjectID& selectedObjects = currentlySelectedGroup->getAllIDs();
- for (VecObjectID::const_iterator it = selectedObjects.begin(); it != selectedObjects.end(); ++it)
- {
- Object *obj = findObjectByID(*it);
- if (obj)
- {
- obj->kill();
- }
- }
- }
- break;
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_DEBUG_HURT_OBJECT:
- {
- Object* objToHurt = TheGameLogic->findObjectByID( msg->getArgument( 0 )->objectID );
- if (objToHurt)
- {
- DamageInfo damageInfo;
- damageInfo.in.m_damageType = DAMAGE_UNRESISTABLE;
- damageInfo.in.m_deathType = DEATH_NORMAL;
- damageInfo.in.m_sourceID = INVALID_ID;
- damageInfo.in.m_amount = objToHurt->getBodyModule()->getMaxHealth() / 10.0f;
- objToHurt->attemptDamage( &damageInfo );
- }
- break;
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_DEBUG_KILL_OBJECT:
- {
- Object* objToHurt = TheGameLogic->findObjectByID( msg->getArgument( 0 )->objectID );
- if (objToHurt)
- {
- objToHurt->kill();
- }
- break;
- }
-#endif
-
-#ifdef ALLOW_SURRENDER
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_DO_SURRENDER:
- {
- //All selected units surrender
- if( currentlySelectedGroup )
- {
- Object* objWeSurrenderedTo = TheGameLogic->findObjectByID( msg->getArgument( 0 )->objectID );
- Bool surrender = msg->getArgument( 1 )->boolean;
- currentlySelectedGroup->groupSurrender( objWeSurrenderedTo, surrender, CMD_FROM_PLAYER );
- }
- break;
- }
-#endif
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_ENTER:
- {
- Object *enter = findObjectByID( msg->getArgument( 1 )->objectID );
-
- // sanity
- if( enter == nullptr )
- break;
-
- if( currentlySelectedGroup )
- {
- currentlySelectedGroup->releaseWeaponLockForGroup(LOCKED_TEMPORARILY); // release any temporary locks.
- currentlySelectedGroup->groupEnter( enter, CMD_FROM_PLAYER );
- }
-
- break;
-
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_EXIT:
- {
- Object *objectWantingToExit = findObjectByID( msg->getArgument( 0 )->objectID );
-#if RETAIL_COMPATIBLE_AIGROUP
- Object *objectContainingExiter = getSingleObjectFromSelection(currentlySelectedGroup);
-#else
- Object *objectContainingExiter = getSingleObjectFromSelection(currentlySelectedGroup.Peek());
-#endif
-
- // sanity
- if( objectWantingToExit == nullptr )
- break;
-
- if( objectContainingExiter == nullptr )
- break;
-
- // sanity, the player must actually control this object
- if( objectWantingToExit->getControllingPlayer() != msgPlayer )
- break;
-
- objectWantingToExit->releaseWeaponLock(LOCKED_TEMPORARILY); // release any temporary locks.
-
- // exit whatever objectWantingToExit is INSIDE of
- AIUpdateInterface *ai = objectWantingToExit->getAIUpdateInterface();
- if( ai )
- ai->aiExit( objectContainingExiter, CMD_FROM_PLAYER );
- // Just like Enter, Exit needs to know the thing to exit. This can no longer be assumed because of the Tunnel system.
- // If you do not specify the thing to Exit, it will Exit the thing it thinks it is in. For a tunnel network,
- // that will be the specific Tunnel it entered. (Scripts can talk directly to the guy to say Get Out Regardless)
-
- break;
-
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_EVACUATE:
- {
- // issue command for either single object or for selected group
-// AIGroup *group = TheAI->findGroup( *selectedGroupID );
- if( currentlySelectedGroup )
- {
- //Coord3D pos;
- //Bool hasArgs = FALSE;
- //hasArgs = (msg->getArgumentCount() > 0);
-
- //if (hasArgs)
- // pos = msg->getArgument(0)->location;
-
- currentlySelectedGroup->releaseWeaponLockForGroup(LOCKED_TEMPORARILY); // release any temporary locks.
-
- // evacuate message is for the selected group
- //if (hasArgs)
- // currentlySelectedGroup->groupMoveToAndEvacuate( &pos, CMD_FROM_PLAYER );
- //else
- currentlySelectedGroup->groupEvacuate( CMD_FROM_PLAYER );
-
-// no, this is bad, don't do here, do when POSTING message
-// pickAndPlayUnitVoiceResponse( TheInGameUI->getAllSelectedDrawables(), GameMessage::MSG_EVACUATE );
-
- }
-
- break;
-
- }
-
- // --------------------------------------------------------------------------------------------
- case GameMessage::MSG_EXECUTE_RAILED_TRANSPORT:
- {
-
- // issue command to currently selected objects
- if( currentlySelectedGroup )
- currentlySelectedGroup->groupExecuteRailedTransport( CMD_FROM_PLAYER );
-
- break;
-
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_INTERNET_HACK:
- {
-// ObjectID sourceID = msg->getArgument( 0 )->objectID;
- if( currentlySelectedGroup )
- {
- currentlySelectedGroup->releaseWeaponLockForGroup(LOCKED_TEMPORARILY); // release any temporary locks.
- currentlySelectedGroup->groupHackInternet( CMD_FROM_PLAYER );
- }
- break;
- }
-
- // --------------------------------------------------------------------------------------------
- case GameMessage::MSG_GET_REPAIRED:
- {
- Object *repairDepot = findObjectByID( msg->getArgument( 0 )->objectID );
-
- // sanity
- if( repairDepot == nullptr )
- break;
-
- // tell the currently selected group to go get repaired
- if( currentlySelectedGroup )
- currentlySelectedGroup->groupGetRepaired( repairDepot, CMD_FROM_PLAYER );
-
- break;
-
- }
-
- // --------------------------------------------------------------------------------------------
- case GameMessage::MSG_DOCK:
- {
- Object *dockBuilding = findObjectByID( msg->getArgument( 0 )->objectID );
-
- // sanity
- if( dockBuilding == nullptr )
- break;
-
- // tell the currently selected group to go get repaired
- if( currentlySelectedGroup )
- currentlySelectedGroup->groupDock( dockBuilding, CMD_FROM_PLAYER );
-
- break;
-
- }
-
- // --------------------------------------------------------------------------------------------
- case GameMessage::MSG_GET_HEALED:
- {
- Object *healDest = findObjectByID( msg->getArgument( 0 )->objectID );
-
- // sanity
- if( healDest == nullptr )
- break;
-
- // tell the currently selected group to enter the building for healing
- if( currentlySelectedGroup )
- currentlySelectedGroup->groupGetHealed( healDest, CMD_FROM_PLAYER );
-
- break;
-
- }
-
- // --------------------------------------------------------------------------------------------
- case GameMessage::MSG_DO_REPAIR:
- {
- Object *repairTarget = findObjectByID( msg->getArgument( 0 )->objectID );
-
- // sanity
- if( repairTarget == nullptr )
- break;
-
- //
- // tell the currently selected group of objects to go repair the target object, note
- // that only one of them will actually go ahead and do the repair
- //
- if( currentlySelectedGroup )
- currentlySelectedGroup->groupRepair( repairTarget, CMD_FROM_PLAYER );
-
- break;
-
- }
-
- // --------------------------------------------------------------------------------------------
- case GameMessage::MSG_RESUME_CONSTRUCTION:
- {
- Object *constructTarget = findObjectByID( msg->getArgument( 0 )->objectID );
-
- // sanity
- if( constructTarget == nullptr )
- break;
-
- //
- // tell the currently selected group of objects to resume construction on
- // the target object, note that only one of them will go off and resume construction
- // on the target
- //
- if( currentlySelectedGroup )
- currentlySelectedGroup->groupResumeConstruction( constructTarget, CMD_FROM_PLAYER );
-
-// no, this is bad, don't do here, do when POSTING message
-// pickAndPlayUnitVoiceResponse( TheInGameUI->getAllSelectedDrawables(), msg->getType() );
-
- break;
-
- }
-
- // --------------------------------------------------------------------------------------------
- case GameMessage::MSG_DO_SPECIAL_POWER_OVERRIDE_DESTINATION:
- {
- const Coord3D *loc = &msg->getArgument( 0 )->location;
- SpecialPowerType spType = (SpecialPowerType)msg->getArgument( 1 )->integer;
-
- ObjectID sourceID = msg->getArgument(2)->objectID;
- Object* source = findObjectByID(sourceID);
- if (source != nullptr)
- {
-#if !RETAIL_COMPATIBLE_CRC
- // TheSuperHackers @fix stephanmeesters 01/03/2026 Validate the origin of the source object
- if ( source->getControllingPlayer() != msgPlayer )
- {
- DEBUG_CRASH( ("MSG_DO_SPECIAL_POWER_OVERRIDE_DESTINATION: Player '%ls' attempted to control the object '%s' owned by player '%ls'.",
- msgPlayer->getPlayerDisplayName().str(),
- source->getTemplate()->getName().str(),
- source->getControllingPlayer()->getPlayerDisplayName().str()) );
- break;
- }
-#endif
-
- AIGroupPtr theGroup = TheAI->createGroup();
- theGroup->add(source);
- theGroup->groupOverrideSpecialPowerDestination( spType, loc, CMD_FROM_PLAYER );
-#if RETAIL_COMPATIBLE_AIGROUP
- TheAI->destroyGroup(theGroup);
-#else
- theGroup->removeAll();
-#endif
- }
- else
- {
- if( currentlySelectedGroup )
- {
- currentlySelectedGroup->groupOverrideSpecialPowerDestination( spType, loc, CMD_FROM_PLAYER );
- }
- }
-
- break;
-
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_DO_ATTACK_OBJECT:
- {
- Object *enemy = findObjectByID( msg->getArgument( 0 )->objectID );
-
- // Check enemy, as it is possible that he died this frame.
- if (enemy)
- {
- if (currentlySelectedGroup)
- {
-
- currentlySelectedGroup->releaseWeaponLockForGroup(LOCKED_TEMPORARILY); // release any temporary locks.
- currentlySelectedGroup->groupAttackObject( enemy, NO_MAX_SHOTS_LIMIT, CMD_FROM_PLAYER );
-
- }
-
- }
-
- break;
-
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_DO_FORCE_ATTACK_OBJECT:
- {
- Object *enemy = findObjectByID( msg->getArgument( 0 )->objectID );
-
- // Check enemy, as it is possible that he died this frame.
- if (enemy)
- {
- if (currentlySelectedGroup)
- {
- currentlySelectedGroup->releaseWeaponLockForGroup(LOCKED_TEMPORARILY); // release any temporary locks.
- currentlySelectedGroup->groupForceAttackObject( enemy, NO_MAX_SHOTS_LIMIT, CMD_FROM_PLAYER );
- }
-
- }
-
- break;
-
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_DO_FORCE_ATTACK_GROUND:
- {
- const Coord3D *pos = &msg->getArgument( 0 )->location;
-
- if (currentlySelectedGroup)
- {
-
- /////////////////////////////////////////////////////////////////////
- //Lorenzen sez: unclear, yet how to solve this for all cases
- //Kris: This code was added to allow the toxin tractor to force attack
- // while contaminating. When this change was made, it was causing
- // rangers and scud launchers to reset to primary weapon mode whenever
- // force attacking while not idle. I fixed this by enforcing the
- // temporary and permanent modes that are already set when attempting
- // the new lock. In this case, the temp lock attempt will fail whenever
- // a permanent lock is in effect, thus fixing the ranger and scud and
- // allowing the tox tractor to work as well.
- Bool forceAttackRequiresPrimaryWeapon = !currentlySelectedGroup->isIdle();
- if ( forceAttackRequiresPrimaryWeapon )
- {
- currentlySelectedGroup->setWeaponLockForGroup( PRIMARY_WEAPON, LOCKED_TEMPORARILY );
- currentlySelectedGroup->groupAttackPosition( pos, NO_MAX_SHOTS_LIMIT, CMD_FROM_PLAYER );
- currentlySelectedGroup->releaseWeaponLockForGroup(LOCKED_TEMPORARILY);
- }
- else
- ///////////////////////////////////////////////////////////////////
- {
- currentlySelectedGroup->releaseWeaponLockForGroup(LOCKED_TEMPORARILY);
- currentlySelectedGroup->groupAttackPosition( pos, NO_MAX_SHOTS_LIMIT, CMD_FROM_PLAYER );
- }
-
-
- }
-
- break;
-
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_QUEUE_UPGRADE:
- {
- const UpgradeTemplate *upgradeT = TheUpgradeCenter->findUpgradeByKey( (NameKeyType)(msg->getArgument( 1 )->integer) );
- if (!upgradeT) // sanity
- break;
-
- if (currentlySelectedGroup)
- currentlySelectedGroup->queueUpgrade( upgradeT );
-
- break;
-
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_CANCEL_UPGRADE:
- {
-#if RETAIL_COMPATIBLE_AIGROUP
- Object *producer = getSingleObjectFromSelection(currentlySelectedGroup);
-#else
- Object *producer = getSingleObjectFromSelection(currentlySelectedGroup.Peek());
-#endif
- const UpgradeTemplate *upgradeT = TheUpgradeCenter->findUpgradeByKey( (NameKeyType)(msg->getArgument( 0 )->integer) );
-
- // sanity
- if( producer == nullptr || upgradeT == nullptr )
- break;
-
- // the player must actually control the producer object
- if( producer->getControllingPlayer() != msgPlayer )
- break;
-
- // producer must have a production update
- ProductionUpdateInterface *pu = producer->getProductionUpdateInterface();
- if( pu == nullptr )
- break;
-
- // cancel the upgrade
- pu->cancelUpgrade( upgradeT );
-
- break;
-
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_QUEUE_UNIT_CREATE:
- {
-#if RETAIL_COMPATIBLE_AIGROUP
- Object *producer = getSingleObjectFromSelection(currentlySelectedGroup);
-#else
- Object *producer = getSingleObjectFromSelection(currentlySelectedGroup.Peek());
-#endif
- const ThingTemplate *whatToCreate;
- ProductionID productionID;
-
- // get data from the message
- whatToCreate = TheThingFactory->findByTemplateID( msg->getArgument( 0 )->integer );
- productionID = (ProductionID)msg->getArgument( 1 )->integer;
-
- // sanity
- if ( producer == nullptr || whatToCreate == nullptr )
- break;
-
- // get the production interface for the producer
- ProductionUpdateInterface *pu = producer->getProductionUpdateInterface();
- if( pu == nullptr )
- {
-
- DEBUG_CRASH( ("MSG_QUEUE_UNIT_CREATE: Producer '%s' doesn't have a unit production interface",
- producer->getTemplate()->getName().str()) );
- break;
-
- }
-
- // queue the build
- pu->queueCreateUnit( whatToCreate, productionID );
-
- break;
-
- }
-
- //-------------------------------------------------------------------------------------------------
- case GameMessage::MSG_CANCEL_UNIT_CREATE:
- {
-#if RETAIL_COMPATIBLE_AIGROUP
- Object *producer = getSingleObjectFromSelection(currentlySelectedGroup);
-#else
- Object *producer = getSingleObjectFromSelection(currentlySelectedGroup.Peek());
-#endif
- ProductionID productionID = (ProductionID)msg->getArgument( 0 )->integer;
-
- // sanity
- if( producer == nullptr )
- break;
-
- // sanity, the player must control the producer
- if( producer->getControllingPlayer() != msgPlayer )
- break;
-
- // get the unit production interface
- ProductionUpdateInterface *pu = producer->getProductionUpdateInterface();
- if( pu == nullptr )
- break;
-
- // cancel the production
- pu->cancelUnitCreate( productionID );
-
- break;
-
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_DOZER_CONSTRUCT:
- case GameMessage::MSG_DOZER_CONSTRUCT_LINE:
- {
- const ThingTemplate *place;
- Coord3D loc;
- Real angle;
-
- // get player, what to place, and location
-#if RETAIL_COMPATIBLE_AIGROUP
- Object *constructorObject = getSingleObjectFromSelection(currentlySelectedGroup);
-#else
- Object *constructorObject = getSingleObjectFromSelection(currentlySelectedGroup.Peek());
-#endif
- place = TheThingFactory->findByTemplateID( msg->getArgument( 0 )->integer );
- loc = msg->getArgument( 1 )->location;
- angle = msg->getArgument( 2 )->real;
-
- if( place == nullptr || constructorObject == nullptr )
- break; //These are not crashes, as the object may have died before this message came in
-
- if( msg->getType() == GameMessage::MSG_DOZER_CONSTRUCT )
- {
-
- TheBuildAssistant->buildObjectNow( constructorObject, place, &loc, angle,
- constructorObject->getControllingPlayer() );
-
- }
- else
- {
- Coord3D locEnd;
-
- // get the end of the line location in the world
- locEnd = msg->getArgument( 3 )->location;
-
- // place the line of structures, the end location being present will make it happen
- TheBuildAssistant->buildObjectLineNow( constructorObject, place, &loc, &locEnd, angle,
- constructorObject->getControllingPlayer() );
-
- }
-
- // place the sound for putting a building down
-
- static AudioEventRTS placeBuilding("PlaceBuilding");
- placeBuilding.setObjectID(constructorObject->getID());
- TheAudio->addAudioEvent( &placeBuilding );
-
-
-// no, this is bad, don't do here, do when POSTING message
-// pickAndPlayUnitVoiceResponse( TheInGameUI->getAllSelectedDrawables(), msg->getType() );
-
- break;
-
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_DOZER_CANCEL_CONSTRUCT:
- {
-
- // get the building to cancel construction on
-#if RETAIL_COMPATIBLE_AIGROUP
- Object *building = getSingleObjectFromSelection(currentlySelectedGroup);
-#else
- Object *building = getSingleObjectFromSelection(currentlySelectedGroup.Peek());
-#endif
- if( building == nullptr )
- break;
-
- // the player sending this message must actually control this building
- if( building->getControllingPlayer() != msgPlayer )
- break;
-
- // Check to make sure it is actually under construction
- if( !building->testStatus(OBJECT_STATUS_UNDER_CONSTRUCTION) )
- break;
-
- // OK, refund the money to the player, unless it is a rebuilding Hole.
- if( !building->testStatus(OBJECT_STATUS_RECONSTRUCTING))
- {
- Money *money = msgPlayer->getMoney();
- UnsignedInt amount = building->getTemplate()->calcCostToBuild( msgPlayer );
- money->deposit( amount, TRUE, FALSE );
- }
-
- //
- // Destroy the building ... killing the
- // building will automatically cause the dozer also cancel its own building
- // behavior and it will go on its merry way doing other assigned tasks
- //
- building->kill();
-
- break;
-
- }
-
- // --------------------------------------------------------------------------------------------
- case GameMessage::MSG_SELL:
- {
-
- // use the selected group
- if( currentlySelectedGroup )
- currentlySelectedGroup->groupSell( CMD_FROM_PLAYER );
-
- break;
-
- }
-
- // --------------------------------------------------------------------------------------------
- case GameMessage::MSG_TOGGLE_OVERCHARGE:
- {
-
- // use the selected group
- if( currentlySelectedGroup )
- currentlySelectedGroup->groupToggleOvercharge( CMD_FROM_PLAYER );
-
- break;
-
- }
-
-#ifdef ALLOW_SURRENDER
- // --------------------------------------------------------------------------------------------
- case GameMessage::MSG_PICK_UP_PRISONER:
- {
- Object *prisoner = TheGameLogic->findObjectByID( msg->getArgument( 0 )->objectID );
-
- if( prisoner )
- {
-
- // use selected group
- if( currentlySelectedGroup )
- currentlySelectedGroup->groupPickUpPrisoner( prisoner, CMD_FROM_PLAYER );
-
- }
-
- break;
-
- }
-#endif
-
-#ifdef ALLOW_SURRENDER
- // --------------------------------------------------------------------------------------------
- case GameMessage::MSG_RETURN_TO_PRISON:
- {
-
- // use selected group
- if( currentlySelectedGroup )
- currentlySelectedGroup->groupReturnToPrison( nullptr, CMD_FROM_PLAYER );
-
- break;
-
- }
-#endif
-
- //---------------------------------------------------------------------------------------------
- // No sound does exactly the same logical processing as the usual message. Just double them up.
- case GameMessage::MSG_CREATE_SELECTED_GROUP_NO_SOUND:
- case GameMessage::MSG_CREATE_SELECTED_GROUP:
- {
- Bool createNewGroup = msg->getArgument( 0 )->boolean;
- Bool firstObject = TRUE;
-
- for (Int i = 1; i < msg->getArgumentCount(); ++i) {
- Object *obj = findObjectByID( msg->getArgument( i )->objectID );
- if (!obj) {
- continue;
- }
-
- selectObject(obj, createNewGroup && firstObject, msgPlayer->getPlayerMask());
- firstObject = FALSE;
- }
-
- break;
-
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_REMOVE_FROM_SELECTED_GROUP:
- {
- for (Int i = 0; i < msg->getArgumentCount(); ++i) {
- ObjectID objID = msg->getArgument(i)->objectID;
- Object *objToRemove = findObjectByID(objID);
- if (!objToRemove) {
- continue;
- }
-
- deselectObject(objToRemove, msgPlayer->getPlayerMask());
- }
-
- break;
-
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_DESTROY_SELECTED_GROUP:
- {
- msgPlayer->setCurrentlySelectedAIGroup(nullptr);
-
- break;
-
- }
-
- // --------------------------------------------------------------------------------------------
- case GameMessage::MSG_SELECTED_GROUP_COMMAND:
- {
-
- break;
-
- }
-
- // --------------------------------------------------------------------------------------------
- case GameMessage::MSG_PLACE_BEACON:
- {
- if (msgPlayer->getPlayerTemplate() == nullptr)
- break;
- Coord3D pos = msg->getArgument( 0 )->location;
- Region3D r;
- TheTerrainLogic->getExtent(&r);
- if (!r.isInRegionNoZ(&pos))
- pos = TheTerrainLogic->findClosestEdgePoint(&pos);
- const ThingTemplate *thing = TheThingFactory->findTemplate( msgPlayer->getPlayerTemplate()->getBeaconTemplate() );
- if (thing && !TheVictoryConditions->hasSinglePlayerBeenDefeated(msgPlayer))
- {
- // how many does this player have active?
- Int count;
- msgPlayer->countObjectsByThingTemplate( 1, &thing, false, &count );
- DEBUG_LOG(("Player already has %d beacons active", count));
- if (count >= TheMultiplayerSettings->getMaxBeaconsPerPlayer())
- {
- if (msgPlayer == ThePlayerList->getLocalPlayer())
- {
- // tell the user
- TheInGameUI->message( TheGameText->fetch("GUI:TooManyBeacons") );
-
- // play a sound
- static AudioEventRTS aSound("BeaconPlacementFailed");
- aSound.setPosition(&pos);
- aSound.setPlayerIndex(msgPlayer->getPlayerIndex());
- TheAudio->addAudioEvent(&aSound);
- }
-
- break;
- }
- Object *object = TheThingFactory->newObject( thing, msgPlayer->getDefaultTeam() );
- object->setPosition( &pos );
- object->setProducer(nullptr);
-
- if (msgPlayer->getRelationship( ThePlayerList->getLocalPlayer()->getDefaultTeam() ) == ALLIES || ThePlayerList->getLocalPlayer()->isPlayerObserver())
- {
- // tell the user
- UnicodeString s;
- s.format(TheGameText->fetch("GUI:BeaconPlaced"), msgPlayer->getPlayerDisplayName().str());
- TheInGameUI->message( s );
-
- // play a sound
- static AudioEventRTS aSound("BeaconPlaced");
- aSound.setPlayerIndex(msgPlayer->getPlayerIndex());
- aSound.setPosition(&pos);
- TheAudio->addAudioEvent(&aSound);
-
- // beacons are a rare event; play a nifty radar event thingy
- TheRadar->createEvent( object->getPosition(), RADAR_EVENT_INFORMATION );
-
- if (ThePlayerList->getLocalPlayer()->getRelationship(msgPlayer->getDefaultTeam()) == ALLIES)
- TheEva->setShouldPlay(EVA_BeaconDetected);
-
- TheControlBar->markUIDirty(); // check if we should grey out the button
- }
- else
- {
-
- Int updateCount = 0;
- static NameKeyType nameKeyClientUpdate = NAMEKEY("BeaconClientUpdate");
- ClientUpdateModule ** clientModules = object->getDrawable()->getClientUpdateModules();
- if (clientModules)
- {
- while (*clientModules)
- {
- if ((*clientModules)->getModuleNameKey() == nameKeyClientUpdate)
- {
- (*(BeaconClientUpdate **)clientModules)->hideBeacon();
- ++updateCount;
- }
-
- ++clientModules;
- }
- }
- DEBUG_ASSERTCRASH(updateCount == 1, ("Saw %d update modules for the beacon!", updateCount));
-
- }
- }
- else
- {
- // tell the user
- TheInGameUI->message( TheGameText->fetch("GUI:BeaconPlacementFailed") );
-
- // play a sound
- static AudioEventRTS aSound("BeaconPlacementFailed");
- aSound.setPosition(&pos);
- aSound.setPlayerIndex(msgPlayer->getPlayerIndex());
- TheAudio->addAudioEvent(&aSound);
- }
- break;
- }
-
- // --------------------------------------------------------------------------------------------
- case GameMessage::MSG_REMOVE_BEACON:
- {
- AIGroupPtr allSelectedObjects = TheAI->createGroup();
-#if RETAIL_COMPATIBLE_AIGROUP
- msgPlayer->getCurrentSelectionAsAIGroup(allSelectedObjects); // need to act on all objects, so we can hide teammates' beacons.
-#else
- msgPlayer->getCurrentSelectionAsAIGroup(allSelectedObjects.Peek()); // need to act on all objects, so we can hide teammates' beacons.
-#endif
- if( allSelectedObjects )
- {
- const VecObjectID& selectedObjects = allSelectedObjects->getAllIDs();
- for (VecObjectID::const_iterator it = selectedObjects.begin(); it != selectedObjects.end(); ++it)
- {
- Object *beacon = findObjectByID(*it);
- if (beacon)
- {
- // TheSuperHackers @bugfix Prevent runtime crashing when a beacon is no longer associated with an initialized player.
- const PlayerTemplate *playerTemplate = beacon->getControllingPlayer()->getPlayerTemplate();
- if (!playerTemplate)
- continue;
-
- const ThingTemplate *thing = TheThingFactory->findTemplate( playerTemplate->getBeaconTemplate() );
- if (thing && thing->isEquivalentTo(beacon->getTemplate()))
- {
- if (beacon->getControllingPlayer() == msgPlayer)
- {
- destroyObject(beacon); // the owner is telling it to go away. such is life.
-
- TheControlBar->markUIDirty(); // check if we should un-grey out the button
- }
- else if (msgPlayer == ThePlayerList->getLocalPlayer())
- {
- Drawable *beaconDrawable = beacon->getDrawable();
- if (beaconDrawable)
- {
-
- static NameKeyType nameKeyClientUpdate = NAMEKEY("BeaconClientUpdate");
- ClientUpdateModule ** clientModules = beaconDrawable->getClientUpdateModules();
- if (clientModules)
- {
- while (*clientModules)
- {
- if ((*clientModules)->getModuleNameKey() == nameKeyClientUpdate)
- (*(BeaconClientUpdate **)clientModules)->hideBeacon();
-
- ++clientModules;
- }
- }
- }
- }
- }
- }
- }
-#if RETAIL_COMPATIBLE_AIGROUP
- if (allSelectedObjects->isEmpty())
- {
- TheAI->destroyGroup(allSelectedObjects);
- allSelectedObjects = nullptr;
- }
-#endif
- }
- break;
- }
-
- // --------------------------------------------------------------------------------------------
- case GameMessage::MSG_SET_BEACON_TEXT:
- {
- if( currentlySelectedGroup )
- {
- const VecObjectID& selectedObjects = currentlySelectedGroup->getAllIDs();
- for (VecObjectID::const_iterator it = selectedObjects.begin(); it != selectedObjects.end(); ++it)
- {
- Object *beacon = findObjectByID(*it);
- if (beacon)
- {
- Drawable *beaconDrawable = beacon->getDrawable();
- if (beaconDrawable)
- {
- UnicodeString s;
- for( int i=0; igetArgumentCount(); i++ )
- {
- s.concat( msg->getArgument(i)->wChar );
- }
-
- if (s.isEmpty())
- beaconDrawable->clearCaptionText();
- else
- beaconDrawable->setCaptionText(s);
- }
- }
- }
- }
- break;
- }
-
- // --------------------------------------------------------------------------------------------
- case GameMessage::MSG_SELF_DESTRUCT:
- {
- if (msg->getArgument(0)->boolean)
- {
- // transfer control to any living ally
- Int i=0;
- for (; igetPlayerCount(); ++i)
- {
- if (i != msgPlayer->getPlayerIndex())
- {
- Player *otherPlayer = ThePlayerList->getNthPlayer(i);
- if (msgPlayer->getRelationship(otherPlayer->getDefaultTeam()) == ALLIES &&
- otherPlayer->getRelationship(msgPlayer->getDefaultTeam()) == ALLIES)
- {
- if (TheVictoryConditions->hasSinglePlayerBeenDefeated(otherPlayer))
- continue;
-
- // a living ally! hooray!
- otherPlayer->transferAssetsFromThat(msgPlayer);
- msgPlayer->killPlayer(); // just to be safe (and to kill beacons etc that don't transfer)
- break;
- }
- }
- }
- if (i == ThePlayerList->getPlayerCount())
- {
- // didn't find any allies. die, loner!
- msgPlayer->killPlayer();
- }
- }
- else
- {
- msgPlayer->killPlayer();
- }
- // There is no reason to do any notification here, it now takes place in the victory conditions.
- // bonehead.
- break;
- }
-
- // --------------------------------------------------------------------------------------------
- case GameMessage::MSG_SET_REPLAY_CAMERA:
- {
- if (TheRecorder->isPlaybackMode() && TheGlobalData->m_useCameraInReplay && TheControlBar->getObserverLookAtPlayer() == msgPlayer)
- {
- if (TheTacticalView->isCameraMovementFinished())
- {
- const Coord3D pos = msg->getArgument( 0 )->location;
- const Real angle = msg->getArgument( 1 )->real;
- const Real pitch = msg->getArgument( 2 )->real;
- const Real zoom = msg->getArgument( 3 )->real;
- const Mouse::MouseCursor mouseCursor = static_cast(msg->getArgument( 4 )->integer);
- const ICoord2D mousePos = msg->getArgument( 5 )->pixel;
-
- // TheSuperHackers @info Definitely call in user mode to ensure the camera operates with auto-zoom
- // over terrain elevations, because the Replay Camera does not store the absolute camera location,
- // but key parameters relative to the terrain height at the camera pivot.
- TheTacticalView->userSetPosition(pos);
- TheTacticalView->userSetAngle(angle);
- TheTacticalView->userSetPitch(pitch);
- TheTacticalView->userSetZoom(zoom);
-
- // TheSuperHackers @fix Make sure there is no scrolling ever.
- const Coord2D scroll = {0, 0};
- TheTacticalView->userScrollBy(&scroll);
-
- if (msg->getArgumentCount() >= 8)
- {
- // TheSuperHackers @feature Override all the settings above with real camera position and view direction.
- // This ensures that the camera looks EXACTLY like it was at the time of recording, no matter how the
- // View is configured or tweaked. Note that the above settings are still required to set regardless, because
- // when the replay camera is exited, then the pivot position and angles will be needed to build the camera
- // where it was left off.
- const Coord3D camPos = msg->getArgument( 6 )->location;
- const Coord3D camDir = msg->getArgument( 7 )->location;
-
- TheTacticalView->setUserControlled(false);
- TheTacticalView->set3DCameraLookAt(camPos, camDir, 0.0f);
- }
-
- // TheSuperHackers @fix Lock the new location to avoid user input from changing the camera in this frame.
- TheTacticalView->lockUserControlUntilFrame( getFrame() + 1 );
-
- if (!TheLookAtTranslator->hasMouseMovedRecently())
- {
- TheMouse->setCursor( mouseCursor );
- TheMouse->setPosition( mousePos.x, mousePos.y );
- TheLookAtTranslator->setCurrentPos( mousePos );
- }
- }
- }
- break;
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_CREATE_TEAM0:
- case GameMessage::MSG_CREATE_TEAM1:
- case GameMessage::MSG_CREATE_TEAM2:
- case GameMessage::MSG_CREATE_TEAM3:
- case GameMessage::MSG_CREATE_TEAM4:
- case GameMessage::MSG_CREATE_TEAM5:
- case GameMessage::MSG_CREATE_TEAM6:
- case GameMessage::MSG_CREATE_TEAM7:
- case GameMessage::MSG_CREATE_TEAM8:
- case GameMessage::MSG_CREATE_TEAM9:
- {
- msgPlayer->processCreateTeamGameMessage(msg->getType() - GameMessage::MSG_CREATE_TEAM0, msg);
- break;
- }
-
- case GameMessage::MSG_SELECT_TEAM0:
- case GameMessage::MSG_SELECT_TEAM1:
- case GameMessage::MSG_SELECT_TEAM2:
- case GameMessage::MSG_SELECT_TEAM3:
- case GameMessage::MSG_SELECT_TEAM4:
- case GameMessage::MSG_SELECT_TEAM5:
- case GameMessage::MSG_SELECT_TEAM6:
- case GameMessage::MSG_SELECT_TEAM7:
- case GameMessage::MSG_SELECT_TEAM8:
- case GameMessage::MSG_SELECT_TEAM9:
- {
- msgPlayer->processSelectTeamGameMessage(msg->getType() - GameMessage::MSG_SELECT_TEAM0);
- break;
- }
-
- case GameMessage::MSG_ADD_TEAM0:
- case GameMessage::MSG_ADD_TEAM1:
- case GameMessage::MSG_ADD_TEAM2:
- case GameMessage::MSG_ADD_TEAM3:
- case GameMessage::MSG_ADD_TEAM4:
- case GameMessage::MSG_ADD_TEAM5:
- case GameMessage::MSG_ADD_TEAM6:
- case GameMessage::MSG_ADD_TEAM7:
- case GameMessage::MSG_ADD_TEAM8:
- case GameMessage::MSG_ADD_TEAM9:
- {
- msgPlayer->processAddTeamGameMessage(msg->getType() - GameMessage::MSG_ADD_TEAM0);
- break;
- }
-
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_LOGIC_CRC:
- {
- if (TheNetwork)
- {
- Int slotIndex = -1;
- for (Int i=0; igetPlayerType() == PLAYER_HUMAN && TheNetwork->getPlayerName(i) == msgPlayer->getPlayerDisplayName())
- {
- slotIndex = i;
- break;
- }
- }
-
- if (slotIndex < 0 || !TheNetwork->isPlayerConnected(slotIndex))
- break;
-
- if (msgPlayer->isLocalPlayer())
- {
-#if defined(RTS_DEBUG)
- // don't even put this in release, cause someone might hack it.
- if (!TheDebugIgnoreSyncErrors)
- {
-#endif
- m_shouldValidateCRCs = TRUE;
-#if defined(RTS_DEBUG)
- }
-#endif
- }
-
- UnsignedInt newCRC = msg->getArgument(0)->integer;
- //DEBUG_LOG(("Received CRC of %8.8X from %ls on frame %d", newCRC,
- //msgPlayer->getPlayerDisplayName().str(), m_frame));
- m_cachedCRCs[msgPlayer->getPlayerIndex()] = newCRC;
- }
- else if (TheRecorder && TheRecorder->isPlaybackMode())
- {
- UnsignedInt newCRC = msg->getArgument(0)->integer;
- //DEBUG_LOG(("Saw CRC of %X from player %d. Our CRC is %X. Arg count is %d",
- //newCRC, msgPlayer->getPlayerIndex(), getCRC(), msg->getArgumentCount()));
-
- TheRecorder->handleCRCMessage(newCRC, msgPlayer->getPlayerIndex(), (msg->getArgument(1)->boolean));
- }
- break;
-
- }
-
- //---------------------------------------------------------------------------------------------
- case GameMessage::MSG_PURCHASE_SCIENCE:
- {
- ScienceType science = (ScienceType)msg->getArgument( 0 )->integer;
-
- // sanity
- if( science == SCIENCE_INVALID )
- break;
-
- msgPlayer->attemptToPurchaseScience(science);
-
- break;
-
- }
-
- }
-
-#if RETAIL_COMPATIBLE_AIGROUP
- // TheSuperHackers @bugfix xezon 28/06/2025 This hack avoids crashing when players are selected during Replay playback.
- // It can read data from an already deleted AIGroup and return this function when its member size is 0, signifying that
- // it is indeed deleted.
- if (currentlySelectedGroup && currentlySelectedGroup->getCount() == 0)
- return;
-#endif
-
- /**/ /// @todo: multiplayer semantics
- if (currentlySelectedGroup && TheRecorder->isPlaybackMode() && TheGlobalData->m_useCameraInReplay && TheControlBar->getObserverLookAtPlayer() == msgPlayer /*&& !TheRecorder->isMultiplayer()*/)
- {
- const VecObjectID& selectedObjects = currentlySelectedGroup->getAllIDs();
- TheInGameUI->deselectAllDrawables();
- for (VecObjectID::const_iterator it = selectedObjects.begin(); it != selectedObjects.end(); ++it)
- {
- const Object *obj = findObjectByID(*it);
- if (obj)
- {
- Drawable *draw = obj->getDrawable();
- if (draw)
- TheInGameUI->selectDrawable(draw);
- }
- }
- }
- /**/
-
- if( currentlySelectedGroup != nullptr )
- {
-#if RETAIL_COMPATIBLE_AIGROUP
- TheAI->destroyGroup(currentlySelectedGroup);
-#else
- currentlySelectedGroup->removeAll();
-#endif
- }
-
-}
diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/RankInfo.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/RankInfo.cpp
deleted file mode 100644
index 18265789d1f..00000000000
--- a/Generals/Code/GameEngine/Source/GameLogic/System/RankInfo.cpp
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
-** Command & Conquer Generals(tm)
-** Copyright 2025 Electronic Arts Inc.
-**
-** This program is free software: you can redistribute it and/or modify
-** it under the terms of the GNU General Public License as published by
-** the Free Software Foundation, either version 3 of the License, or
-** (at your option) any later version.
-**
-** This program is distributed in the hope that it will be useful,
-** but WITHOUT ANY WARRANTY; without even the implied warranty of
-** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-** GNU General Public License for more details.
-**
-** You should have received a copy of the GNU General Public License
-** along with this program. If not, see .
-*/
-
-////////////////////////////////////////////////////////////////////////////////
-// //
-// (c) 2001-2003 Electronic Arts Inc. //
-// //
-////////////////////////////////////////////////////////////////////////////////
-
-// FILE: RankInfo.cpp /////////////////////////////////////////////////////////
-// Created: Steven Johnson, Sep 2002
-// Desc:
-//-----------------------------------------------------------------------------
-
-#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine
-
-#include "Common/INI.h"
-#include "Common/Player.h"
-#include "GameLogic/RankInfo.h"
-
-RankInfoStore* TheRankInfoStore = nullptr;
-
-
-//-----------------------------------------------------------------------------
-RankInfo::~RankInfo()
-{
-}
-
-
-//-----------------------------------------------------------------------------
-RankInfoStore::~RankInfoStore()
-{
- Int level;
- for (level =0; level < getRankLevelCount(); level++)
- {
- RankInfo* ri = m_rankInfos[level];
- deleteInstance(ri);
- }
- m_rankInfos.clear();
-}
-
-
-//-----------------------------------------------------------------------------
-void RankInfoStore::init()
-{
- DEBUG_ASSERTCRASH(m_rankInfos.empty(), ("Hmm"));
- m_rankInfos.clear();
-}
-
-//-----------------------------------------------------------------------------
-void RankInfoStore::reset()
-{
- // nope.
- //m_rankInfos.clear();
-
- for (RankInfoVec::iterator it = m_rankInfos.begin(); it != m_rankInfos.end(); /*++it*/)
- {
- RankInfo* ri = *it;
- if (ri)
- {
- Overridable* temp = ri->deleteOverrides();
- if (!temp)
- {
- DEBUG_CRASH(("hmm, should not be possible for RankInfo"));
- it = m_rankInfos.erase(it);
- }
- else
- {
- ++it;
- }
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-Int RankInfoStore::getRankLevelCount() const
-{
- return m_rankInfos.size();
-}
-
-//-----------------------------------------------------------------------------
-// note that level is 1...n, NOT 0...n-1
-const RankInfo* RankInfoStore::getRankInfo(Int level) const
-{
- if (level >= 1 && level <= getRankLevelCount())
- {
- const RankInfo* ri = m_rankInfos[level-1];
- if (ri)
- {
- return (const RankInfo*)ri->getFinalOverride();
- }
- }
- return nullptr;
-}
-
-//-----------------------------------------------------------------------------
-void RankInfoStore::friend_parseRankDefinition( INI* ini )
-{
- if (TheRankInfoStore)
- {
- Int rank = INI::scanInt(ini->getNextToken());
-
- static const FieldParse myFieldParse[] =
- {
- { "RankName", INI::parseAndTranslateLabel, nullptr, offsetof( RankInfo, m_rankName ) },
- { "SkillPointsNeeded", INI::parseInt, nullptr, offsetof( RankInfo, m_skillPointsNeeded ) },
- { "SciencesGranted", INI::parseScienceVector, nullptr, offsetof( RankInfo, m_sciencesGranted ) },
- { "SciencePurchasePointsGranted", INI::parseUnsignedInt, nullptr, offsetof( RankInfo, m_sciencePurchasePointsGranted ) },
- { nullptr, nullptr, nullptr, 0 }
- };
-
- if (ini->getLoadType() == INI_LOAD_CREATE_OVERRIDES)
- {
- // we aren't allowed to add ranks in overrides, only to override existing ones.
- if (rank < 1 || rank > TheRankInfoStore->m_rankInfos.size())
- {
- DEBUG_CRASH(("Rank not found in map.ini"));
- throw INI_INVALID_DATA;
- }
-
- RankInfo* info = TheRankInfoStore->m_rankInfos[rank-1];
- if (!info)
- {
- DEBUG_CRASH(("Rank not found in map.ini"));
- throw INI_INVALID_DATA;
- }
-
- RankInfo* newInfo = newInstance(RankInfo);
-
- // copy data from final override to 'newInfo' as a set of initial default values
- info = (RankInfo*)(info->friend_getFinalOverride());
-
- *newInfo = *info;
- info->setNextOverride(newInfo);
- newInfo->markAsOverride(); // must do AFTER the copy
-
- ini->initFromINI(newInfo, myFieldParse);
- //TheRankInfoStore->m_rankInfos.push_back(newInfo); // NO, BAD, WRONG -- don't add in this case.
-
- }
- else
- {
- if (rank != TheRankInfoStore->m_rankInfos.size() + 1)
- {
- DEBUG_CRASH(("Ranks must increase monotonically"));
- throw INI_INVALID_DATA;
- }
- RankInfo* info = newInstance(RankInfo);
- ini->initFromINI(info, myFieldParse);
- TheRankInfoStore->m_rankInfos.push_back(info);
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-void INI::parseRankDefinition( INI* ini )
-{
- RankInfoStore::friend_parseRankDefinition(ini);
-}
-
diff --git a/GeneralsMD/Code/GameEngine/CMakeLists.txt b/GeneralsMD/Code/GameEngine/CMakeLists.txt
index 32edb03fdd2..ea7ef771ff7 100644
--- a/GeneralsMD/Code/GameEngine/CMakeLists.txt
+++ b/GeneralsMD/Code/GameEngine/CMakeLists.txt
@@ -238,9 +238,9 @@ set(GAMEENGINE_SRC
Include/GameLogic/AITNGuard.h
Include/GameLogic/Armor.h
Include/GameLogic/ArmorSet.h
- Include/GameLogic/CaveSystem.h
- Include/GameLogic/CrateSystem.h
- Include/GameLogic/Damage.h
+# Include/GameLogic/CaveSystem.h
+# Include/GameLogic/CrateSystem.h
+# Include/GameLogic/Damage.h
Include/GameLogic/ExperienceTracker.h
Include/GameLogic/FiringTracker.h
Include/GameLogic/FPUControl.h
@@ -479,7 +479,7 @@ set(GAMEENGINE_SRC
Include/GameLogic/PartitionManager.h
Include/GameLogic/PolygonTrigger.h
Include/GameLogic/Powers.h
- Include/GameLogic/RankInfo.h
+# Include/GameLogic/RankInfo.h
Include/GameLogic/ScriptActions.h
Include/GameLogic/ScriptConditions.h
Include/GameLogic/ScriptEngine.h
@@ -1075,12 +1075,12 @@ set(GAMEENGINE_SRC
Source/GameLogic/ScriptEngine/ScriptEngine.cpp
Source/GameLogic/ScriptEngine/Scripts.cpp
Source/GameLogic/ScriptEngine/VictoryConditions.cpp
- Source/GameLogic/System/CaveSystem.cpp
- Source/GameLogic/System/CrateSystem.cpp
- Source/GameLogic/System/Damage.cpp
+# Source/GameLogic/System/CaveSystem.cpp
+# Source/GameLogic/System/CrateSystem.cpp
+# Source/GameLogic/System/Damage.cpp
Source/GameLogic/System/GameLogic.cpp
- Source/GameLogic/System/GameLogicDispatch.cpp
- Source/GameLogic/System/RankInfo.cpp
+# Source/GameLogic/System/GameLogicDispatch.cpp
+# Source/GameLogic/System/RankInfo.cpp
# Source/GameNetwork/Connection.cpp
# Source/GameNetwork/ConnectionManager.cpp
# Source/GameNetwork/DisconnectManager.cpp
diff --git a/scripts/cpp/unify_move_files.py b/scripts/cpp/unify_move_files.py
index 87e840d698d..b293dd29157 100644
--- a/scripts/cpp/unify_move_files.py
+++ b/scripts/cpp/unify_move_files.py
@@ -474,7 +474,7 @@ def main():
#unify_file_lib(Game.ZEROHOUR, "Libraries/Source/WWVegas/WW3D2/dx8wrapper.h", Game.CORE, "Libraries/Source/WWVegas/WW3D2/dx8wrapper.h")
#unify_file_lib(Game.ZEROHOUR, "Libraries/Source/WWVegas/WW3D2/dx8caps.cpp", Game.CORE, "Libraries/Source/WWVegas/WW3D2/dx8caps.cpp")
#unify_file_lib(Game.ZEROHOUR, "Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp", Game.CORE, "Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp")
-
+
#unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/Display.cpp", Game.CORE, "GameEngine/Source/GameClient/Display.cpp")
#unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/Display.h", Game.CORE, "GameEngine/Include/GameClient/Display.h")
#unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/System/Anim2D.cpp", Game.CORE, "GameEngine/Source/GameClient/System/Anim2D.cpp")
@@ -527,6 +527,16 @@ def main():
#unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DTextEntry.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DTextEntry.cpp")
#unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DVerticalSlider.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DVerticalSlider.cpp")
+ #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameLogic/CaveSystem.h", Game.CORE, "GameEngine/Include/GameLogic/CaveSystem.h")
+ #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameLogic/CrateSystem.h", Game.CORE, "GameEngine/Include/GameLogic/CrateSystem.h")
+ #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameLogic/Damage.h", Game.CORE, "GameEngine/Include/GameLogic/Damage.h")
+ #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameLogic/RankInfo.h", Game.CORE, "GameEngine/Include/GameLogic/RankInfo.h")
+ #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameLogic/System/CaveSystem.cpp", Game.CORE, "GameEngine/Source/GameLogic/System/CaveSystem.cpp")
+ #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameLogic/System/CrateSystem.cpp", Game.CORE, "GameEngine/Source/GameLogic/System/CrateSystem.cpp")
+ #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameLogic/System/Damage.cpp", Game.CORE, "GameEngine/Source/GameLogic/System/Damage.cpp")
+ #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp", Game.CORE, "GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp")
+ #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameLogic/System/RankInfo.cpp", Game.CORE, "GameEngine/Source/GameLogic/System/RankInfo.cpp")
+
return