From 3e2dd37230d12bcee7bc4b64e7597eab5b9dc7d5 Mon Sep 17 00:00:00 2001 From: Wahid Date: Sat, 11 Apr 2026 16:05:10 +0200 Subject: [PATCH] feat: Add lobby/in-game voice chat (Opus + WASAPI) with persistent per-peer mutes --- GeneralsMD/Code/GameEngine/CMakeLists.txt | 2540 +++++++++-------- .../GeneralsOnline/GeneralsOnline_Settings.h | 79 + .../GeneralsOnline/Voice/NGMPVoiceBridge.h | 65 + .../GeneralsOnline/Voice/VoiceCapture.h | 131 + .../GeneralsOnline/Voice/VoiceManager.h | 198 ++ .../GeneralsOnline/Voice/VoiceOptionsUI.h | 47 + .../GeneralsOnline/Voice/VoiceOpusCodec.h | 100 + .../GeneralsOnline/Voice/VoicePlayback.h | 103 + .../GeneralsOnline/Voice/VoiceSlashCommands.h | 28 + .../GUI/GUICallbacks/Menus/OptionsMenu.cpp | 10 + .../GeneralsOnline_Settings.cpp | 138 +- .../GeneralsOnline/NextGenTransport.cpp | 21 + .../OnlineServices_LobbyInterface.cpp | 85 +- .../GeneralsOnline/Voice/NGMPVoiceBridge.cpp | 449 +++ .../GeneralsOnline/Voice/VoiceCapture.cpp | 579 ++++ .../GeneralsOnline/Voice/VoiceManager.cpp | 346 +++ .../GeneralsOnline/Voice/VoiceOptionsUI.cpp | 305 ++ .../GeneralsOnline/Voice/VoiceOpusCodec.cpp | 151 + .../GeneralsOnline/Voice/VoicePlayback.cpp | 545 ++++ .../Voice/VoiceSlashCommands.cpp | 336 +++ vcpkg.json | 17 +- 21 files changed, 5012 insertions(+), 1261 deletions(-) create mode 100644 GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Voice/NGMPVoiceBridge.h create mode 100644 GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Voice/VoiceCapture.h create mode 100644 GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Voice/VoiceManager.h create mode 100644 GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Voice/VoiceOptionsUI.h create mode 100644 GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Voice/VoiceOpusCodec.h create mode 100644 GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Voice/VoicePlayback.h create mode 100644 GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Voice/VoiceSlashCommands.h create mode 100644 GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/Voice/NGMPVoiceBridge.cpp create mode 100644 GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/Voice/VoiceCapture.cpp create mode 100644 GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/Voice/VoiceManager.cpp create mode 100644 GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/Voice/VoiceOptionsUI.cpp create mode 100644 GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/Voice/VoiceOpusCodec.cpp create mode 100644 GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/Voice/VoicePlayback.cpp create mode 100644 GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/Voice/VoiceSlashCommands.cpp diff --git a/GeneralsMD/Code/GameEngine/CMakeLists.txt b/GeneralsMD/Code/GameEngine/CMakeLists.txt index 5e6d97be8dc..38bd01f6f52 100644 --- a/GeneralsMD/Code/GameEngine/CMakeLists.txt +++ b/GeneralsMD/Code/GameEngine/CMakeLists.txt @@ -1,1251 +1,1289 @@ -set(GAMEENGINE_SRC - Include/Common/AcademyStats.h - Include/Common/ActionManager.h -# Include/Common/ArchiveFile.h -# Include/Common/ArchiveFileSystem.h -# Include/Common/AsciiString.h -# Include/Common/AudioAffect.h -# Include/Common/AudioEventInfo.h -# Include/Common/AudioEventRTS.h -# Include/Common/AudioHandleSpecialValues.h -# Include/Common/AudioRandomValue.h -# Include/Common/AudioRequest.h -# Include/Common/AudioSettings.h - Include/Common/BattleHonors.h - Include/Common/BezFwdIterator.h - Include/Common/BezierSegment.h - Include/Common/BitFlags.h - Include/Common/BitFlagsIO.h - Include/Common/BorderColors.h - Include/Common/BuildAssistant.h - Include/Common/ClientUpdateModule.h - Include/Common/CommandLine.h -# Include/Common/crc.h -# Include/Common/CRCDebug.h - Include/Common/CriticalSection.h - Include/Common/CustomMatchPreferences.h - Include/Common/DamageFX.h - Include/Common/DataChunk.h -# Include/Common/Debug.h - Include/Common/Dict.h - Include/Common/Directory.h - Include/Common/DisabledTypes.h - Include/Common/DiscreteCircle.h - Include/Common/DrawModule.h -# Include/Common/DynamicAudioEventInfo.h - Include/Common/encrypt.h - Include/Common/Energy.h -# Include/Common/Errors.h -# Include/Common/file.h -# Include/Common/FileSystem.h - Include/Common/FunctionLexicon.h -# Include/Common/GameAudio.h -# Include/Common/GameCommon.h -# Include/Common/GameDefines.h - Include/Common/GameEngine.h - Include/Common/GameLOD.h - Include/Common/GameMemory.h -# Include/Common/GameMusic.h -# Include/Common/GameSounds.h - Include/Common/GameSpyMiscPreferences.h - Include/Common/GameState.h - Include/Common/GameStateMap.h -# Include/Common/GameType.h - Include/Common/Geometry.h - Include/Common/GlobalData.h - Include/Common/Handicap.h - Include/Common/IgnorePreferences.h -# Include/Common/INI.h - Include/Common/INIException.h - Include/Common/KindOf.h - Include/Common/LadderPreferences.h - Include/Common/Language.h - Include/Common/LatchRestore.h - Include/Common/List.h -# Include/Common/LocalFile.h -# Include/Common/LocalFileSystem.h -# Include/Common/MapObject.h - Include/Common/MapReaderWriterInfo.h - Include/Common/MessageStream.h - Include/Common/MiniLog.h -# Include/Common/MiscAudio.h - Include/Common/MissionStats.h - Include/Common/ModelState.h - Include/Common/Module.h - Include/Common/ModuleFactory.h - Include/Common/Money.h - Include/Common/MultiplayerSettings.h - Include/Common/NameKeyGenerator.h -# Include/Common/ObjectStatusTypes.h - Include/Common/OSDisplay.h - Include/Common/Overridable.h - Include/Common/Override.h - Include/Common/PartitionSolver.h - Include/Common/PerfMetrics.h - Include/Common/PerfTimer.h - Include/Common/Player.h - Include/Common/PlayerList.h - Include/Common/PlayerTemplate.h - Include/Common/ProductionPrerequisite.h - Include/Common/QuickmatchPreferences.h - Include/Common/QuotedPrintable.h -# Include/Common/Radar.h -# Include/Common/RAMFile.h -# Include/Common/RandomValue.h - Include/Common/Recorder.h -# Include/Common/ReplaySimulation.h - Include/Common/Registry.h - Include/Common/ResourceGatheringManager.h - Include/Common/Science.h - Include/Common/ScopedMutex.h - Include/Common/ScoreKeeper.h -# Include/Common/simpleplayer.h - Include/Common/SkirmishBattleHonors.h - Include/Common/SkirmishPreferences.h -# Include/Common/Snapshot.h - Include/Common/SparseMatchFinder.h - Include/Common/SpecialPower.h - Include/Common/SpecialPowerMaskType.h - Include/Common/SpecialPowerType.h - Include/Common/StackDump.h - Include/Common/StateMachine.h - Include/Common/StatsCollector.h - Include/Common/StatsExporter.h - Include/Common/StatsUploader.h -# Include/Common/STLTypedefs.h -# Include/Common/StreamingArchiveFile.h -# Include/Common/SubsystemInterface.h - Include/Common/SystemInfo.h - Include/Common/Team.h - Include/Common/Terrain.h - Include/Common/TerrainTypes.h - Include/Common/Thing.h - Include/Common/ThingFactory.h - Include/Common/ThingSort.h - Include/Common/ThingTemplate.h - Include/Common/TunnelTracker.h -# Include/Common/UnicodeString.h - Include/Common/UnitTimings.h - Include/Common/Upgrade.h -# Include/Common/urllaunch.h -# Include/Common/UserPreferences.h - Include/Common/version.h - Include/Common/WellKnownKeys.h -# Include/Common/WorkerProcess.h -# Include/Common/Xfer.h -# Include/Common/XferCRC.h -# Include/Common/XferDeepCRC.h -# Include/Common/XferLoad.h -# Include/Common/XferSave.h - Include/GameClient/Anim2D.h - Include/GameClient/AnimateWindowManager.h - Include/GameClient/CampaignManager.h -# Include/GameClient/ChallengeGenerals.h -# Include/GameClient/ClientInstance.h -# Include/GameClient/ClientRandomValue.h -# Include/GameClient/Color.h - Include/GameClient/CommandXlat.h - Include/GameClient/ControlBar.h - Include/GameClient/ControlBarResizer.h - Include/GameClient/ControlBarScheme.h -# Include/GameClient/Credits.h - Include/GameClient/DebugDisplay.h - Include/GameClient/Diplomacy.h - Include/GameClient/DisconnectMenu.h - Include/GameClient/Display.h -# Include/GameClient/DisplayString.h -# Include/GameClient/DisplayStringManager.h - Include/GameClient/Drawable.h - Include/GameClient/DrawableInfo.h -# Include/GameClient/DrawGroupInfo.h - Include/GameClient/EstablishConnectionsMenu.h - Include/GameClient/Eva.h - Include/GameClient/ExtendedMessageBox.h - Include/GameClient/FontDesc.h -# Include/GameClient/FXList.h - Include/GameClient/Gadget.h - Include/GameClient/GadgetCheckBox.h - Include/GameClient/GadgetComboBox.h - Include/GameClient/GadgetListBox.h - Include/GameClient/GadgetProgressBar.h - Include/GameClient/GadgetPushButton.h - Include/GameClient/GadgetRadioButton.h - Include/GameClient/GadgetSlider.h - Include/GameClient/GadgetStaticText.h - Include/GameClient/GadgetTabControl.h - Include/GameClient/GadgetTextEntry.h - Include/GameClient/GameClient.h -# Include/GameClient/GameFont.h - Include/GameClient/GameInfoWindow.h -# Include/GameClient/GameText.h -# Include/GameClient/GameWindow.h -# Include/GameClient/GameWindowGlobal.h - Include/GameClient/GameWindowID.h - Include/GameClient/GameWindowManager.h -# Include/GameClient/GameWindowTransitions.h -# Include/GameClient/GlobalLanguage.h -# Include/GameClient/GraphDraw.h - Include/GameClient/GUICallbacks.h - Include/GameClient/GUICommandTranslator.h -# Include/GameClient/HeaderTemplate.h - Include/GameClient/HintSpy.h - Include/GameClient/HotKey.h - Include/GameClient/Image.h -# Include/GameClient/IMEManager.h - Include/GameClient/InGameUI.h -# Include/GameClient/Keyboard.h - Include/GameClient/KeyDefs.h -# Include/GameClient/LanguageFilter.h -# Include/GameClient/Line2D.h -# Include/GameClient/LoadScreen.h - Include/GameClient/LookAtXlat.h -# Include/GameClient/MapUtil.h - Include/GameClient/MessageBox.h - Include/GameClient/MetaEvent.h - Include/GameClient/Module/AnimatedParticleSysBoneClientUpdate.h - Include/GameClient/Module/BeaconClientUpdate.h - Include/GameClient/Module/SwayClientUpdate.h -# Include/GameClient/Mouse.h -# Include/GameClient/ParabolicEase.h -# Include/GameClient/ParticleSys.h - Include/GameClient/PlaceEventTranslator.h -# Include/GameClient/ProcessAnimateWindow.h -# Include/GameClient/RadiusDecal.h - Include/GameClient/RayEffect.h -# Include/GameClient/SelectionInfo.h - Include/GameClient/SelectionXlat.h - Include/GameClient/Shadow.h - Include/GameClient/Shell.h - Include/GameClient/ShellHooks.h - Include/GameClient/ShellMenuScheme.h -# Include/GameClient/Smudge.h -# Include/GameClient/Snow.h -# Include/GameClient/Statistics.h -# Include/GameClient/TerrainRoads.h -# Include/GameClient/TerrainVisual.h -# Include/GameClient/VideoPlayer.h -# Include/GameClient/View.h -# Include/GameClient/Water.h -# Include/GameClient/WindowLayout.h -# Include/GameClient/WindowVideoManager.h - Include/GameClient/WindowXlat.h -# Include/GameClient/WinInstanceData.h - Include/GameLogic/AI.h - Include/GameLogic/AIDock.h - Include/GameLogic/AIGuard.h - Include/GameLogic/AIGuardRetaliate.h -# Include/GameLogic/AIPathfind.h - Include/GameLogic/AIPlayer.h - Include/GameLogic/AISkirmishPlayer.h - Include/GameLogic/AIStateMachine.h - 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/ExperienceTracker.h - Include/GameLogic/FiringTracker.h - Include/GameLogic/FPUControl.h - Include/GameLogic/GameLogic.h - Include/GameLogic/GhostObject.h - Include/GameLogic/Locomotor.h - Include/GameLogic/LocomotorSet.h -# Include/GameLogic/LogicRandomValue.h - Include/GameLogic/Module/ActiveBody.h - Include/GameLogic/Module/ActiveShroudUpgrade.h - Include/GameLogic/Module/AIUpdate.h - Include/GameLogic/Module/AnimationSteeringUpdate.h - Include/GameLogic/Module/ArmorUpgrade.h - Include/GameLogic/Module/AssaultTransportAIUpdate.h - Include/GameLogic/Module/AssistedTargetingUpdate.h - Include/GameLogic/Module/AutoDepositUpdate.h - Include/GameLogic/Module/AutoFindHealingUpdate.h - Include/GameLogic/Module/AutoHealBehavior.h - Include/GameLogic/Module/BaikonurLaunchPower.h - Include/GameLogic/Module/BaseRegenerateUpdate.h - Include/GameLogic/Module/BattleBusSlowDeathBehavior.h - Include/GameLogic/Module/BattlePlanUpdate.h - Include/GameLogic/Module/BehaviorModule.h - Include/GameLogic/Module/BodyModule.h - Include/GameLogic/Module/BoneFXDamage.h - Include/GameLogic/Module/BoneFXUpdate.h - Include/GameLogic/Module/BridgeBehavior.h - Include/GameLogic/Module/BridgeScaffoldBehavior.h - Include/GameLogic/Module/BridgeTowerBehavior.h - Include/GameLogic/Module/BunkerBusterBehavior.h - Include/GameLogic/Module/CashBountyPower.h - Include/GameLogic/Module/CashHackSpecialPower.h - Include/GameLogic/Module/CaveContain.h - Include/GameLogic/Module/CheckpointUpdate.h - Include/GameLogic/Module/ChinookAIUpdate.h - Include/GameLogic/Module/CleanupAreaPower.h - Include/GameLogic/Module/CleanupHazardUpdate.h - Include/GameLogic/Module/CollideModule.h - Include/GameLogic/Module/CommandButtonHuntUpdate.h - Include/GameLogic/Module/CommandSetUpgrade.h - Include/GameLogic/Module/ContainModule.h - Include/GameLogic/Module/ConvertToCarBombCrateCollide.h - Include/GameLogic/Module/ConvertToHijackedVehicleCrateCollide.h - Include/GameLogic/Module/CostModifierUpgrade.h - Include/GameLogic/Module/CountermeasuresBehavior.h - Include/GameLogic/Module/CrateCollide.h - Include/GameLogic/Module/CreateCrateDie.h - Include/GameLogic/Module/CreateModule.h - Include/GameLogic/Module/CreateObjectDie.h - Include/GameLogic/Module/CrushDie.h - Include/GameLogic/Module/DamageModule.h - Include/GameLogic/Module/DamDie.h - Include/GameLogic/Module/DefaultProductionExitUpdate.h - Include/GameLogic/Module/DefectorSpecialPower.h - Include/GameLogic/Module/DeletionUpdate.h - Include/GameLogic/Module/DeliverPayloadAIUpdate.h - Include/GameLogic/Module/DemoralizeSpecialPower.h - Include/GameLogic/Module/DemoTrapUpdate.h - Include/GameLogic/Module/DeployStyleAIUpdate.h - Include/GameLogic/Module/DestroyDie.h - Include/GameLogic/Module/DestroyModule.h - Include/GameLogic/Module/DieModule.h - Include/GameLogic/Module/DockUpdate.h - Include/GameLogic/Module/DozerAIUpdate.h - Include/GameLogic/Module/DumbProjectileBehavior.h - Include/GameLogic/Module/DynamicGeometryInfoUpdate.h - Include/GameLogic/Module/DynamicShroudClearingRangeUpdate.h - Include/GameLogic/Module/EjectPilotDie.h - Include/GameLogic/Module/EMPUpdate.h - Include/GameLogic/Module/EnemyNearUpdate.h - Include/GameLogic/Module/ExperienceScalarUpgrade.h - Include/GameLogic/Module/FireOCLAfterWeaponCooldownUpdate.h - Include/GameLogic/Module/FireSpreadUpdate.h - Include/GameLogic/Module/FirestormDynamicGeometryInfoUpdate.h - Include/GameLogic/Module/FireWeaponCollide.h - Include/GameLogic/Module/FireWeaponPower.h - Include/GameLogic/Module/FireWeaponUpdate.h - Include/GameLogic/Module/FireWeaponWhenDamagedBehavior.h - Include/GameLogic/Module/FireWeaponWhenDeadBehavior.h - Include/GameLogic/Module/FlammableUpdate.h - Include/GameLogic/Module/FlightDeckBehavior.h - Include/GameLogic/Module/FloatUpdate.h - Include/GameLogic/Module/FXListDie.h - Include/GameLogic/Module/GarrisonContain.h - Include/GameLogic/Module/GenerateMinefieldBehavior.h - Include/GameLogic/Module/GrantScienceUpgrade.h - Include/GameLogic/Module/GrantStealthBehavior.h - Include/GameLogic/Module/GrantUpgradeCreate.h - Include/GameLogic/Module/HackInternetAIUpdate.h - Include/GameLogic/Module/HealContain.h - Include/GameLogic/Module/HealCrateCollide.h - Include/GameLogic/Module/HeightDieUpdate.h - Include/GameLogic/Module/HelicopterSlowDeathUpdate.h - Include/GameLogic/Module/HelixContain.h - Include/GameLogic/Module/HighlanderBody.h - Include/GameLogic/Module/HijackerUpdate.h - Include/GameLogic/Module/HiveStructureBody.h - Include/GameLogic/Module/HordeUpdate.h - Include/GameLogic/Module/ImmortalBody.h - Include/GameLogic/Module/InactiveBody.h - Include/GameLogic/Module/InstantDeathBehavior.h - Include/GameLogic/Module/InternetHackContain.h - Include/GameLogic/Module/JetAIUpdate.h - Include/GameLogic/Module/JetSlowDeathBehavior.h - Include/GameLogic/Module/KeepObjectDie.h - Include/GameLogic/Module/LaserUpdate.h - Include/GameLogic/Module/LifetimeUpdate.h - Include/GameLogic/Module/LockWeaponCreate.h - Include/GameLogic/Module/LocomotorSetUpgrade.h - Include/GameLogic/Module/MaxHealthUpgrade.h - Include/GameLogic/Module/MinefieldBehavior.h - Include/GameLogic/Module/MissileAIUpdate.h - Include/GameLogic/Module/MissileLauncherBuildingUpdate.h - Include/GameLogic/Module/MobMemberSlavedUpdate.h - Include/GameLogic/Module/MobNexusContain.h - Include/GameLogic/Module/ModelConditionUpgrade.h - Include/GameLogic/Module/MoneyCrateCollide.h - Include/GameLogic/Module/NeutronBlastBehavior.h - Include/GameLogic/Module/NeutronMissileSlowDeathUpdate.h - Include/GameLogic/Module/NeutronMissileUpdate.h - Include/GameLogic/Module/ObjectCreationUpgrade.h - Include/GameLogic/Module/ObjectDefectionHelper.h - Include/GameLogic/Module/ObjectHelper.h - Include/GameLogic/Module/ObjectRepulsorHelper.h - Include/GameLogic/Module/ObjectSMCHelper.h - Include/GameLogic/Module/ObjectWeaponStatusHelper.h - Include/GameLogic/Module/OCLSpecialPower.h - Include/GameLogic/Module/OCLUpdate.h - Include/GameLogic/Module/OpenContain.h - Include/GameLogic/Module/OverchargeBehavior.h - Include/GameLogic/Module/OverlordContain.h - Include/GameLogic/Module/ParachuteContain.h - Include/GameLogic/Module/ParkingPlaceBehavior.h - Include/GameLogic/Module/ParticleUplinkCannonUpdate.h - Include/GameLogic/Module/PassengersFireUpgrade.h - Include/GameLogic/Module/PhysicsUpdate.h - Include/GameLogic/Module/PilotFindVehicleUpdate.h - Include/GameLogic/Module/PointDefenseLaserUpdate.h - Include/GameLogic/Module/PoisonedBehavior.h - Include/GameLogic/Module/PowerPlantUpdate.h - Include/GameLogic/Module/PowerPlantUpgrade.h - Include/GameLogic/Module/POWTruckAIUpdate.h - Include/GameLogic/Module/POWTruckBehavior.h - Include/GameLogic/Module/PreorderCreate.h - Include/GameLogic/Module/PrisonBehavior.h - Include/GameLogic/Module/PrisonDockUpdate.h - Include/GameLogic/Module/ProductionUpdate.h - Include/GameLogic/Module/ProjectileStreamUpdate.h - Include/GameLogic/Module/ProneUpdate.h - Include/GameLogic/Module/PropagandaCenterBehavior.h - Include/GameLogic/Module/PropagandaTowerBehavior.h - Include/GameLogic/Module/QueueProductionExitUpdate.h - Include/GameLogic/Module/RadarUpdate.h - Include/GameLogic/Module/RadarUpgrade.h - Include/GameLogic/Module/RadiusDecalUpdate.h - Include/GameLogic/Module/RailedTransportAIUpdate.h - Include/GameLogic/Module/RailedTransportContain.h - Include/GameLogic/Module/RailedTransportDockUpdate.h - Include/GameLogic/Module/RailroadGuideAIUpdate.h - Include/GameLogic/Module/RebuildHoleBehavior.h - Include/GameLogic/Module/RebuildHoleExposeDie.h - Include/GameLogic/Module/RepairDockUpdate.h - Include/GameLogic/Module/ReplaceObjectUpgrade.h - Include/GameLogic/Module/RiderChangeContain.h - Include/GameLogic/Module/SabotageCommandCenterCrateCollide.h - Include/GameLogic/Module/SabotageFakeBuildingCrateCollide.h - Include/GameLogic/Module/SabotageInternetCenterCrateCollide.h - Include/GameLogic/Module/SabotageMilitaryFactoryCrateCollide.h - Include/GameLogic/Module/SabotagePowerPlantCrateCollide.h - Include/GameLogic/Module/SabotageSuperweaponCrateCollide.h - Include/GameLogic/Module/SabotageSupplyCenterCrateCollide.h - Include/GameLogic/Module/SabotageSupplyDropzoneCrateCollide.h - Include/GameLogic/Module/SalvageCrateCollide.h - Include/GameLogic/Module/ShroudCrateCollide.h - Include/GameLogic/Module/SlavedUpdate.h - Include/GameLogic/Module/SlowDeathBehavior.h - Include/GameLogic/Module/SmartBombTargetHomingUpdate.h - Include/GameLogic/Module/SpawnBehavior.h - Include/GameLogic/Module/SpawnPointProductionExitUpdate.h - Include/GameLogic/Module/SpecialAbility.h - Include/GameLogic/Module/SpecialAbilityUpdate.h - Include/GameLogic/Module/SpecialPowerCompletionDie.h - Include/GameLogic/Module/SpecialPowerCreate.h - Include/GameLogic/Module/SpecialPowerModule.h - Include/GameLogic/Module/SpecialPowerUpdateModule.h - Include/GameLogic/Module/SpectreGunshipDeploymentUpdate.h - Include/GameLogic/Module/SpectreGunshipUpdate.h - Include/GameLogic/Module/SpyVisionSpecialPower.h - Include/GameLogic/Module/SpyVisionUpdate.h - Include/GameLogic/Module/SquishCollide.h - Include/GameLogic/Module/StatusBitsUpgrade.h - Include/GameLogic/Module/StatusDamageHelper.h - Include/GameLogic/Module/StealthDetectorUpdate.h - Include/GameLogic/Module/StealthUpdate.h - Include/GameLogic/Module/StealthUpgrade.h - Include/GameLogic/Module/StickyBombUpdate.h - Include/GameLogic/Module/StructureBody.h - Include/GameLogic/Module/StructureCollapseUpdate.h - Include/GameLogic/Module/StructureToppleUpdate.h - Include/GameLogic/Module/SubdualDamageHelper.h - Include/GameLogic/Module/SubObjectsUpgrade.h - Include/GameLogic/Module/SupplyCenterCreate.h - Include/GameLogic/Module/SupplyCenterDockUpdate.h - Include/GameLogic/Module/SupplyCenterProductionExitUpdate.h - Include/GameLogic/Module/SupplyTruckAIUpdate.h - Include/GameLogic/Module/SupplyWarehouseCreate.h - Include/GameLogic/Module/SupplyWarehouseCripplingBehavior.h - Include/GameLogic/Module/SupplyWarehouseDockUpdate.h - Include/GameLogic/Module/TechBuildingBehavior.h - Include/GameLogic/Module/TempWeaponBonusHelper.h - Include/GameLogic/Module/TensileFormationUpdate.h - Include/GameLogic/Module/ToppleUpdate.h - Include/GameLogic/Module/TransitionDamageFX.h - Include/GameLogic/Module/TransportAIUpdate.h - Include/GameLogic/Module/TransportContain.h - Include/GameLogic/Module/TunnelContain.h - Include/GameLogic/Module/UndeadBody.h - Include/GameLogic/Module/UnitCrateCollide.h - Include/GameLogic/Module/UnpauseSpecialPowerUpgrade.h - Include/GameLogic/Module/UpdateModule.h - Include/GameLogic/Module/UpgradeDie.h - Include/GameLogic/Module/UpgradeModule.h - Include/GameLogic/Module/VeterancyCrateCollide.h - Include/GameLogic/Module/VeterancyGainCreate.h - Include/GameLogic/Module/WanderAIUpdate.h - Include/GameLogic/Module/WaveGuideUpdate.h - Include/GameLogic/Module/WeaponBonusUpdate.h - Include/GameLogic/Module/WeaponBonusUpgrade.h - Include/GameLogic/Module/WeaponSetUpgrade.h - Include/GameLogic/Module/WorkerAIUpdate.h - Include/GameLogic/Object.h - Include/GameLogic/ObjectCreationList.h - Include/GameLogic/ObjectIter.h - Include/GameLogic/ObjectScriptStatusBits.h - Include/GameLogic/ObjectTypes.h - Include/GameLogic/PartitionManager.h - Include/GameLogic/PolygonTrigger.h - Include/GameLogic/Powers.h - Include/GameLogic/RankInfo.h - Include/GameLogic/ScriptActions.h - Include/GameLogic/ScriptConditions.h - Include/GameLogic/ScriptEngine.h - Include/GameLogic/Scripts.h - Include/GameLogic/SidesList.h - Include/GameLogic/Squad.h - Include/GameLogic/TerrainLogic.h - Include/GameLogic/TurretAI.h - Include/GameLogic/VictoryConditions.h - Include/GameLogic/Weapon.h - Include/GameLogic/WeaponBonusConditionFlags.h - Include/GameLogic/WeaponSet.h - Include/GameLogic/WeaponSetFlags.h - Include/GameLogic/WeaponSetType.h - Include/GameLogic/WeaponStatus.h -# Include/GameNetwork/Connection.h -# Include/GameNetwork/ConnectionManager.h -# Include/GameNetwork/DisconnectManager.h -# Include/GameNetwork/DownloadManager.h -# Include/GameNetwork/FileTransfer.h -# Include/GameNetwork/FirewallHelper.h -# Include/GameNetwork/FrameData.h -# Include/GameNetwork/FrameDataManager.h -# Include/GameNetwork/FrameMetrics.h -# Include/GameNetwork/GameInfo.h -# Include/GameNetwork/GameMessageParser.h -# Include/GameNetwork/GameSpy/BuddyDefs.h -# Include/GameNetwork/GameSpy/BuddyThread.h -# Include/GameNetwork/GameSpy/GameResultsThread.h -# Include/GameNetwork/GameSpy/GSConfig.h -# Include/GameNetwork/GameSpy/LadderDefs.h -# Include/GameNetwork/GameSpy/LobbyUtils.h -# Include/GameNetwork/GameSpy/MainMenuUtils.h -# Include/GameNetwork/GameSpy/PeerDefs.h -# Include/GameNetwork/GameSpy/PeerDefsImplementation.h -# Include/GameNetwork/GameSpy/PeerThread.h -# Include/GameNetwork/GameSpy/PersistentStorageDefs.h -# Include/GameNetwork/GameSpy/PersistentStorageThread.h -# Include/GameNetwork/GameSpy/PingThread.h -# Include/GameNetwork/GameSpy/StagingRoomGameInfo.h -# Include/GameNetwork/GameSpy/ThreadUtils.h -# Include/GameNetwork/GameSpyChat.h -# Include/GameNetwork/GameSpyGameInfo.h -# Include/GameNetwork/GameSpyGP.h -# Include/GameNetwork/GameSpyOverlay.h -# Include/GameNetwork/GameSpyThread.h - Include/GameNetwork/GUIUtil.h -# Include/GameNetwork/IPEnumeration.h -# Include/GameNetwork/LANAPI.h -# Include/GameNetwork/LANAPICallbacks.h -# Include/GameNetwork/LANGameInfo.h -# Include/GameNetwork/LANPlayer.h -# Include/GameNetwork/NAT.h -# Include/GameNetwork/NetCommandList.h -# Include/GameNetwork/NetCommandMsg.h -# Include/GameNetwork/NetCommandRef.h -# Include/GameNetwork/NetCommandWrapperList.h -# Include/GameNetwork/NetPacket.h -# Include/GameNetwork/NetworkDefs.h -# Include/GameNetwork/NetworkInterface.h -# Include/GameNetwork/networkutil.h -# Include/GameNetwork/RankPointValue.h -# Include/GameNetwork/Transport.h -# Include/GameNetwork/udp.h -# Include/GameNetwork/User.h -# Include/GameNetwork/WOLBrowser/FEBDispatch.h -# Include/GameNetwork/WOLBrowser/WebBrowser.h - Include/GameNetwork/UDPTransport.h - Include/Precompiled/PreRTS.h -# Source/Common/Audio/AudioEventRTS.cpp -# Source/Common/Audio/AudioRequest.cpp -# Source/Common/Audio/DynamicAudioEventInfo.cpp -# Source/Common/Audio/GameAudio.cpp -# Source/Common/Audio/GameMusic.cpp -# Source/Common/Audio/GameSounds.cpp - #Source/Common/Audio/simpleplayer.cpp # unused - #Source/Common/Audio/urllaunch.cpp # unused - Source/Common/Bezier/BezFwdIterator.cpp - Source/Common/Bezier/BezierSegment.cpp - Source/Common/BitFlags.cpp - Source/Common/CommandLine.cpp -# Source/Common/crc.cpp -# Source/Common/CRCDebug.cpp - Source/Common/DamageFX.cpp - Source/Common/Dict.cpp - Source/Common/DiscreteCircle.cpp - Source/Common/GameEngine.cpp - Source/Common/GameLOD.cpp - Source/Common/GameMain.cpp - Source/Common/GlobalData.cpp -# Source/Common/INI/INI.cpp - Source/Common/INI/INIAiData.cpp - Source/Common/INI/INIAnimation.cpp -# Source/Common/INI/INIAudioEventInfo.cpp - Source/Common/INI/INICommandButton.cpp - Source/Common/INI/INICommandSet.cpp - Source/Common/INI/INIControlBarScheme.cpp - Source/Common/INI/INICrate.cpp - Source/Common/INI/INIDamageFX.cpp - Source/Common/INI/INIDrawGroupInfo.cpp - Source/Common/INI/INIGameData.cpp - Source/Common/INI/INIMapCache.cpp - Source/Common/INI/INIMapData.cpp - Source/Common/INI/INIMappedImage.cpp -# Source/Common/INI/INIMiscAudio.cpp - Source/Common/INI/INIModel.cpp - Source/Common/INI/INIMultiplayer.cpp - Source/Common/INI/INIObject.cpp - Source/Common/INI/INIParticleSys.cpp - Source/Common/INI/INISpecialPower.cpp - Source/Common/INI/INITerrain.cpp - Source/Common/INI/INITerrainBridge.cpp - Source/Common/INI/INITerrainRoad.cpp - Source/Common/INI/INIUpgrade.cpp -# Source/Common/INI/INIVideo.cpp - Source/Common/INI/INIWater.cpp - Source/Common/INI/INIWeapon.cpp - Source/Common/INI/INIWebpageURL.cpp - Source/Common/Language.cpp - Source/Common/MessageStream.cpp - Source/Common/MiniLog.cpp - Source/Common/MultiplayerSettings.cpp - Source/Common/NameKeyGenerator.cpp - Source/Common/PartitionSolver.cpp - Source/Common/PerfTimer.cpp -# Source/Common/RandomValue.cpp - Source/Common/Recorder.cpp -# Source/Common/ReplaySimulation.cpp - Source/Common/RTS/AcademyStats.cpp - Source/Common/RTS/ActionManager.cpp - Source/Common/RTS/Energy.cpp - Source/Common/RTS/Handicap.cpp - Source/Common/RTS/MissionStats.cpp - Source/Common/RTS/Money.cpp - Source/Common/RTS/Player.cpp - Source/Common/RTS/PlayerList.cpp - Source/Common/RTS/PlayerTemplate.cpp - Source/Common/RTS/ProductionPrerequisite.cpp - Source/Common/RTS/ResourceGatheringManager.cpp - Source/Common/RTS/Science.cpp - Source/Common/RTS/ScoreKeeper.cpp - Source/Common/RTS/SpecialPower.cpp - Source/Common/RTS/Team.cpp - Source/Common/RTS/TunnelTracker.cpp - Source/Common/SkirmishBattleHonors.cpp - Source/Common/StateMachine.cpp - Source/Common/StatsCollector.cpp - Source/Common/StatsExporter.cpp - Source/Common/StatsUploader.cpp -# Source/Common/System/ArchiveFile.cpp -# Source/Common/System/ArchiveFileSystem.cpp -# Source/Common/System/AsciiString.cpp - Source/Common/System/BuildAssistant.cpp - Source/Common/System/CriticalSection.cpp - Source/Common/System/DataChunk.cpp -# Source/Common/System/Debug.cpp - Source/Common/System/Directory.cpp - Source/Common/System/DisabledTypes.cpp - Source/Common/System/encrypt.cpp -# Source/Common/System/File.cpp -# Source/Common/System/FileSystem.cpp - Source/Common/System/FunctionLexicon.cpp -# Source/Common/System/GameCommon.cpp - #Source/Common/System/GameMemory.cpp -# Source/Common/System/GameType.cpp - Source/Common/System/Geometry.cpp - Source/Common/System/KindOf.cpp - Source/Common/System/List.cpp -# Source/Common/System/LocalFile.cpp -# Source/Common/System/LocalFileSystem.cpp - #Source/Common/System/MemoryInit.cpp -# Source/Common/System/ObjectStatusTypes.cpp - Source/Common/System/QuotedPrintable.cpp -# Source/Common/System/Radar.cpp -# Source/Common/System/RAMFile.cpp - Source/Common/System/registry.cpp - Source/Common/System/SaveGame/GameState.cpp - Source/Common/System/SaveGame/GameStateMap.cpp -# Source/Common/System/Snapshot.cpp - Source/Common/System/StackDump.cpp -# Source/Common/System/StreamingArchiveFile.cpp -# Source/Common/System/SubsystemInterface.cpp - Source/Common/System/Trig.cpp -# Source/Common/System/UnicodeString.cpp - Source/Common/System/Upgrade.cpp -# Source/Common/System/Xfer.cpp -# Source/Common/System/XferCRC.cpp -# Source/Common/System/XferLoad.cpp -# Source/Common/System/XferSave.cpp - Source/Common/TerrainTypes.cpp - Source/Common/Thing/DrawModule.cpp - Source/Common/Thing/Module.cpp - Source/Common/Thing/ModuleFactory.cpp - Source/Common/Thing/Thing.cpp - Source/Common/Thing/ThingFactory.cpp - Source/Common/Thing/ThingTemplate.cpp -# Source/Common/UserPreferences.cpp - Source/Common/version.cpp -# Source/Common/WorkerProcess.cpp -# Source/GameClient/ClientInstance.cpp -# Source/GameClient/Color.cpp -# Source/GameClient/Credits.cpp - Source/GameClient/Display.cpp -# Source/GameClient/DisplayString.cpp -# Source/GameClient/DisplayStringManager.cpp - Source/GameClient/Drawable.cpp - Source/GameClient/Drawable/Update/AnimatedParticleSysBoneClientUpdate.cpp - Source/GameClient/Drawable/Update/BeaconClientUpdate.cpp - Source/GameClient/Drawable/Update/SwayClientUpdate.cpp -# Source/GameClient/DrawGroupInfo.cpp - Source/GameClient/Eva.cpp -# Source/GameClient/FXList.cpp - Source/GameClient/GameClient.cpp - Source/GameClient/GameClientDispatch.cpp -# Source/GameClient/GameText.cpp -# Source/GameClient/GlobalLanguage.cpp -# Source/GameClient/GraphDraw.cpp - Source/GameClient/GUI/AnimateWindowManager.cpp -# Source/GameClient/GUI/ChallengeGenerals.cpp - Source/GameClient/GUI/ControlBar/ControlBar.cpp - Source/GameClient/GUI/ControlBar/ControlBarBeacon.cpp - Source/GameClient/GUI/ControlBar/ControlBarCommand.cpp - Source/GameClient/GUI/ControlBar/ControlBarCommandProcessing.cpp - Source/GameClient/GUI/ControlBar/ControlBarMultiSelect.cpp - Source/GameClient/GUI/ControlBar/ControlBarObserver.cpp - Source/GameClient/GUI/ControlBar/ControlBarOCLTimer.cpp - Source/GameClient/GUI/ControlBar/ControlBarPrintPositions.cpp - Source/GameClient/GUI/ControlBar/ControlBarResizer.cpp - Source/GameClient/GUI/ControlBar/ControlBarScheme.cpp - Source/GameClient/GUI/ControlBar/ControlBarStructureInventory.cpp - Source/GameClient/GUI/ControlBar/ControlBarUnderConstruction.cpp - Source/GameClient/GUI/DisconnectMenu/DisconnectMenu.cpp - Source/GameClient/GUI/EstablishConnectionsMenu/EstablishConnectionsMenu.cpp - Source/GameClient/GUI/Gadget/GadgetCheckBox.cpp - Source/GameClient/GUI/Gadget/GadgetComboBox.cpp - Source/GameClient/GUI/Gadget/GadgetHorizontalSlider.cpp - Source/GameClient/GUI/Gadget/GadgetListBox.cpp - Source/GameClient/GUI/Gadget/GadgetProgressBar.cpp - Source/GameClient/GUI/Gadget/GadgetPushButton.cpp - Source/GameClient/GUI/Gadget/GadgetRadioButton.cpp - Source/GameClient/GUI/Gadget/GadgetStaticText.cpp - Source/GameClient/GUI/Gadget/GadgetTabControl.cpp - Source/GameClient/GUI/Gadget/GadgetTextEntry.cpp - Source/GameClient/GUI/Gadget/GadgetVerticalSlider.cpp -# Source/GameClient/GUI/GameFont.cpp -# Source/GameClient/GUI/GameWindow.cpp -# Source/GameClient/GUI/GameWindowGlobal.cpp - Source/GameClient/GUI/GameWindowManager.cpp - Source/GameClient/GUI/GameWindowManagerScript.cpp -# Source/GameClient/GUI/GameWindowTransitions.cpp - Source/GameClient/GUI/GameWindowTransitionsStyles.cpp - Source/GameClient/GUI/GUICallbacks/ControlBarCallback.cpp - Source/GameClient/GUI/GUICallbacks/ControlBarPopupDescription.cpp - Source/GameClient/GUI/GUICallbacks/Diplomacy.cpp - Source/GameClient/GUI/GUICallbacks/ExtendedMessageBox.cpp - Source/GameClient/GUI/GUICallbacks/GeneralsExpPoints.cpp - Source/GameClient/GUI/GUICallbacks/IMECandidate.cpp - Source/GameClient/GUI/GUICallbacks/InGameChat.cpp - Source/GameClient/GUI/GUICallbacks/InGamePopupMessage.cpp - Source/GameClient/GUI/GUICallbacks/Menus/ChallengeMenu.cpp - Source/GameClient/GUI/GUICallbacks/Menus/CreditsMenu.cpp - Source/GameClient/GUI/GUICallbacks/Menus/DifficultySelect.cpp - Source/GameClient/GUI/GUICallbacks/Menus/DisconnectWindow.cpp - Source/GameClient/GUI/GUICallbacks/Menus/DownloadMenu.cpp - Source/GameClient/GUI/GUICallbacks/Menus/EstablishConnectionsWindow.cpp - Source/GameClient/GUI/GUICallbacks/Menus/GameInfoWindow.cpp - Source/GameClient/GUI/GUICallbacks/Menus/KeyboardOptionsMenu.cpp - Source/GameClient/GUI/GUICallbacks/Menus/LanGameOptionsMenu.cpp - Source/GameClient/GUI/GUICallbacks/Menus/LanLobbyMenu.cpp - Source/GameClient/GUI/GUICallbacks/Menus/LanMapSelectMenu.cpp - Source/GameClient/GUI/GUICallbacks/Menus/MainMenu.cpp - Source/GameClient/GUI/GUICallbacks/Menus/MapSelectMenu.cpp - Source/GameClient/GUI/GUICallbacks/Menus/NetworkDirectConnect.cpp - Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp - Source/GameClient/GUI/GUICallbacks/Menus/PopupCommunicator.cpp - Source/GameClient/GUI/GUICallbacks/Menus/PopupHostGame.cpp - Source/GameClient/GUI/GUICallbacks/Menus/PopupJoinGame.cpp - Source/GameClient/GUI/GUICallbacks/Menus/PopupLadderSelect.cpp - Source/GameClient/GUI/GUICallbacks/Menus/PopupPlayerInfo.cpp - Source/GameClient/GUI/GUICallbacks/Menus/PopupReplay.cpp - Source/GameClient/GUI/GUICallbacks/Menus/PopupSaveLoad.cpp - Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp - Source/GameClient/GUI/GUICallbacks/Menus/ReplayMenu.cpp - Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp - Source/GameClient/GUI/GUICallbacks/Menus/SinglePlayerMenu.cpp - Source/GameClient/GUI/GUICallbacks/Menus/SkirmishGameOptionsMenu.cpp - Source/GameClient/GUI/GUICallbacks/Menus/SkirmishMapSelectMenu.cpp - Source/GameClient/GUI/GUICallbacks/Menus/WOLBuddyOverlay.cpp - Source/GameClient/GUI/GUICallbacks/Menus/WOLCustomScoreScreen.cpp - Source/GameClient/GUI/GUICallbacks/Menus/WOLGameSetupMenu.cpp - Source/GameClient/GUI/GUICallbacks/Menus/WOLLadderScreen.cpp - Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp - Source/GameClient/GUI/GUICallbacks/Menus/WOLLocaleSelectPopup.cpp - Source/GameClient/GUI/GUICallbacks/Menus/WOLLoginMenu.cpp - Source/GameClient/GUI/GUICallbacks/Menus/WOLMapSelectMenu.cpp - Source/GameClient/GUI/GUICallbacks/Menus/WOLMessageWindow.cpp - Source/GameClient/GUI/GUICallbacks/Menus/WOLQMScoreScreen.cpp - Source/GameClient/GUI/GUICallbacks/Menus/WOLQuickMatchMenu.cpp - Source/GameClient/GUI/GUICallbacks/Menus/WOLStatusMenu.cpp - Source/GameClient/GUI/GUICallbacks/Menus/WOLWelcomeMenu.cpp - Source/GameClient/GUI/GUICallbacks/MessageBox.cpp - Source/GameClient/GUI/GUICallbacks/ReplayControls.cpp -# Source/GameClient/GUI/HeaderTemplate.cpp -# Source/GameClient/GUI/IMEManager.cpp -# Source/GameClient/GUI/LoadScreen.cpp -# Source/GameClient/GUI/ProcessAnimateWindow.cpp - Source/GameClient/GUI/Shell/Shell.cpp - Source/GameClient/GUI/Shell/ShellMenuScheme.cpp -# Source/GameClient/GUI/WindowLayout.cpp -# Source/GameClient/GUI/WindowVideoManager.cpp -# Source/GameClient/GUI/WinInstanceData.cpp - Source/GameClient/InGameUI.cpp -# Source/GameClient/Input/Keyboard.cpp -# Source/GameClient/Input/Mouse.cpp -# Source/GameClient/LanguageFilter.cpp -# Source/GameClient/Line2D.cpp -# Source/GameClient/MapUtil.cpp - Source/GameClient/MessageStream/CommandXlat.cpp - Source/GameClient/MessageStream/GUICommandTranslator.cpp - Source/GameClient/MessageStream/HintSpy.cpp - Source/GameClient/MessageStream/HotKey.cpp - Source/GameClient/MessageStream/LookAtXlat.cpp - Source/GameClient/MessageStream/MetaEvent.cpp - Source/GameClient/MessageStream/PlaceEventTranslator.cpp - Source/GameClient/MessageStream/SelectionXlat.cpp - Source/GameClient/MessageStream/WindowXlat.cpp -# Source/GameClient/ParabolicEase.cpp -# Source/GameClient/RadiusDecal.cpp -# Source/GameClient/SelectionInfo.cpp -# Source/GameClient/Snow.cpp -# Source/GameClient/Statistics.cpp - Source/GameClient/System/Anim2D.cpp - Source/GameClient/System/CampaignManager.cpp -# "Source/GameClient/System/Debug Displayers/AudioDebugDisplay.cpp" - Source/GameClient/System/DebugDisplay.cpp - Source/GameClient/System/Image.cpp -# Source/GameClient/System/ParticleSys.cpp - Source/GameClient/System/RayEffect.cpp -# Source/GameClient/System/Smudge.cpp -# Source/GameClient/Terrain/TerrainRoads.cpp -# Source/GameClient/Terrain/TerrainVisual.cpp -# Source/GameClient/VideoPlayer.cpp -# Source/GameClient/VideoStream.cpp -# Source/GameClient/View.cpp -# Source/GameClient/Water.cpp - Source/GameLogic/AI/AI.cpp - Source/GameLogic/AI/AIDock.cpp - Source/GameLogic/AI/AIGroup.cpp - Source/GameLogic/AI/AIGuard.cpp - Source/GameLogic/AI/AIGuardRetaliate.cpp -# Source/GameLogic/AI/AIPathfind.cpp - Source/GameLogic/AI/AIPlayer.cpp - Source/GameLogic/AI/AISkirmishPlayer.cpp - Source/GameLogic/AI/AIStates.cpp - Source/GameLogic/AI/AITNGuard.cpp - Source/GameLogic/AI/Squad.cpp - Source/GameLogic/AI/TurretAI.cpp - Source/GameLogic/Map/PolygonTrigger.cpp - Source/GameLogic/Map/SidesList.cpp - Source/GameLogic/Map/TerrainLogic.cpp - Source/GameLogic/Object/Armor.cpp - Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp - Source/GameLogic/Object/Behavior/BattleBusSlowDeathBehavior.cpp - Source/GameLogic/Object/Behavior/BehaviorModule.cpp - Source/GameLogic/Object/Behavior/BridgeBehavior.cpp - Source/GameLogic/Object/Behavior/BridgeScaffoldBehavior.cpp - Source/GameLogic/Object/Behavior/BridgeTowerBehavior.cpp - Source/GameLogic/Object/Behavior/BunkerBusterBehavior.cpp - Source/GameLogic/Object/Behavior/CountermeasuresBehavior.cpp - Source/GameLogic/Object/Behavior/DumbProjectileBehavior.cpp - Source/GameLogic/Object/Behavior/FireWeaponWhenDamagedBehavior.cpp - Source/GameLogic/Object/Behavior/FireWeaponWhenDeadBehavior.cpp - Source/GameLogic/Object/Behavior/FlightDeckBehavior.cpp - Source/GameLogic/Object/Behavior/GenerateMinefieldBehavior.cpp - Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp - Source/GameLogic/Object/Behavior/InstantDeathBehavior.cpp - Source/GameLogic/Object/Behavior/JetSlowDeathBehavior.cpp - Source/GameLogic/Object/Behavior/MinefieldBehavior.cpp - Source/GameLogic/Object/Behavior/NeutonBlastBehavior.cpp - Source/GameLogic/Object/Behavior/OverchargeBehavior.cpp - Source/GameLogic/Object/Behavior/ParkingPlaceBehavior.cpp - Source/GameLogic/Object/Behavior/PoisonedBehavior.cpp - Source/GameLogic/Object/Behavior/POWTruckBehavior.cpp - Source/GameLogic/Object/Behavior/PrisonBehavior.cpp - Source/GameLogic/Object/Behavior/PropagandaCenterBehavior.cpp - Source/GameLogic/Object/Behavior/PropagandaTowerBehavior.cpp - Source/GameLogic/Object/Behavior/RebuildHoleBehavior.cpp - Source/GameLogic/Object/Behavior/SlowDeathBehavior.cpp - Source/GameLogic/Object/Behavior/SpawnBehavior.cpp - Source/GameLogic/Object/Behavior/SupplyWarehouseCripplingBehavior.cpp - Source/GameLogic/Object/Behavior/TechBuildingBehavior.cpp - Source/GameLogic/Object/Body/ActiveBody.cpp - Source/GameLogic/Object/Body/BodyModule.cpp - Source/GameLogic/Object/Body/HighlanderBody.cpp - Source/GameLogic/Object/Body/HiveStructureBody.cpp - Source/GameLogic/Object/Body/ImmortalBody.cpp - Source/GameLogic/Object/Body/InactiveBody.cpp - Source/GameLogic/Object/Body/StructureBody.cpp - Source/GameLogic/Object/Body/UndeadBody.cpp - Source/GameLogic/Object/Collide/CollideModule.cpp - Source/GameLogic/Object/Collide/CrateCollide/ConvertToCarBombCrateCollide.cpp - Source/GameLogic/Object/Collide/CrateCollide/ConvertToHijackedVehicleCrateCollide.cpp - Source/GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp - Source/GameLogic/Object/Collide/CrateCollide/HealCrateCollide.cpp - Source/GameLogic/Object/Collide/CrateCollide/MoneyCrateCollide.cpp - Source/GameLogic/Object/Collide/CrateCollide/SabotageCommandCenterCrateCollide.cpp - Source/GameLogic/Object/Collide/CrateCollide/SabotageFakeBuilding.cpp - Source/GameLogic/Object/Collide/CrateCollide/SabotageInternetCenterCrateCollide.cpp - Source/GameLogic/Object/Collide/CrateCollide/SabotageMilitaryFactoryCrateCollide.cpp - Source/GameLogic/Object/Collide/CrateCollide/SabotagePowerPlantCrateCollide.cpp - Source/GameLogic/Object/Collide/CrateCollide/SabotageSuperweaponCrateCollide.cpp - Source/GameLogic/Object/Collide/CrateCollide/SabotageSupplyCenterCrateCollide.cpp - Source/GameLogic/Object/Collide/CrateCollide/SabotageSupplyDropzoneCrateCollide.cpp - Source/GameLogic/Object/Collide/CrateCollide/SalvageCrateCollide.cpp - Source/GameLogic/Object/Collide/CrateCollide/ShroudCrateCollide.cpp - Source/GameLogic/Object/Collide/CrateCollide/UnitCrateCollide.cpp - Source/GameLogic/Object/Collide/CrateCollide/VeterancyCrateCollide.cpp - Source/GameLogic/Object/Collide/FireWeaponCollide.cpp - Source/GameLogic/Object/Collide/SquishCollide.cpp - Source/GameLogic/Object/Contain/CaveContain.cpp - Source/GameLogic/Object/Contain/GarrisonContain.cpp - Source/GameLogic/Object/Contain/HealContain.cpp - Source/GameLogic/Object/Contain/HelixContain.cpp - Source/GameLogic/Object/Contain/InternetHackContain.cpp - Source/GameLogic/Object/Contain/MobNexusContain.cpp - Source/GameLogic/Object/Contain/OpenContain.cpp - Source/GameLogic/Object/Contain/OverlordContain.cpp - Source/GameLogic/Object/Contain/ParachuteContain.cpp - Source/GameLogic/Object/Contain/RailedTransportContain.cpp - Source/GameLogic/Object/Contain/RiderChangeContain.cpp - Source/GameLogic/Object/Contain/TransportContain.cpp - Source/GameLogic/Object/Contain/TunnelContain.cpp - Source/GameLogic/Object/Create/CreateModule.cpp - Source/GameLogic/Object/Create/GrantUpgradeCreate.cpp - Source/GameLogic/Object/Create/LockWeaponCreate.cpp - Source/GameLogic/Object/Create/PreorderCreate.cpp - Source/GameLogic/Object/Create/SpecialPowerCreate.cpp - Source/GameLogic/Object/Create/SupplyCenterCreate.cpp - Source/GameLogic/Object/Create/SupplyWarehouseCreate.cpp - Source/GameLogic/Object/Create/VeterancyGainCreate.cpp - Source/GameLogic/Object/Damage/BoneFXDamage.cpp - Source/GameLogic/Object/Damage/DamageModule.cpp - Source/GameLogic/Object/Damage/TransitionDamageFX.cpp - Source/GameLogic/Object/Destroy/DestroyModule.cpp - Source/GameLogic/Object/Die/CreateCrateDie.cpp - Source/GameLogic/Object/Die/CreateObjectDie.cpp - Source/GameLogic/Object/Die/CrushDie.cpp - Source/GameLogic/Object/Die/DamDie.cpp - Source/GameLogic/Object/Die/DestroyDie.cpp - Source/GameLogic/Object/Die/DieModule.cpp - Source/GameLogic/Object/Die/EjectPilotDie.cpp - Source/GameLogic/Object/Die/FXListDie.cpp - Source/GameLogic/Object/Die/KeepObjectDie.cpp - Source/GameLogic/Object/Die/RebuildHoleExposeDie.cpp - Source/GameLogic/Object/Die/SpecialPowerCompletionDie.cpp - Source/GameLogic/Object/Die/UpgradeDie.cpp - Source/GameLogic/Object/ExperienceTracker.cpp - Source/GameLogic/Object/FiringTracker.cpp - Source/GameLogic/Object/GhostObject.cpp - Source/GameLogic/Object/Helper/ObjectDefectionHelper.cpp - Source/GameLogic/Object/Helper/ObjectHelper.cpp - Source/GameLogic/Object/Helper/ObjectRepulsorHelper.cpp - Source/GameLogic/Object/Helper/ObjectSMCHelper.cpp - Source/GameLogic/Object/Helper/ObjectWeaponStatusHelper.cpp - Source/GameLogic/Object/Helper/StatusDamageHelper.cpp - Source/GameLogic/Object/Helper/SubdualDamageHelper.cpp - Source/GameLogic/Object/Helper/TempWeaponBonusHelper.cpp - Source/GameLogic/Object/Locomotor.cpp - Source/GameLogic/Object/Object.cpp - Source/GameLogic/Object/ObjectCreationList.cpp - Source/GameLogic/Object/ObjectTypes.cpp - Source/GameLogic/Object/PartitionManager.cpp - Source/GameLogic/Object/SimpleObjectIterator.cpp - Source/GameLogic/Object/SpecialPower/BaikonurLaunchPower.cpp - Source/GameLogic/Object/SpecialPower/CashBountyPower.cpp - Source/GameLogic/Object/SpecialPower/CashHackSpecialPower.cpp - Source/GameLogic/Object/SpecialPower/CleanupAreaPower.cpp - Source/GameLogic/Object/SpecialPower/DefectorSpecialPower.cpp - Source/GameLogic/Object/SpecialPower/DemoralizeSpecialPower.cpp - Source/GameLogic/Object/SpecialPower/FireWeaponPower.cpp - Source/GameLogic/Object/SpecialPower/OCLSpecialPower.cpp - Source/GameLogic/Object/SpecialPower/SpecialAbility.cpp - Source/GameLogic/Object/SpecialPower/SpecialPowerModule.cpp - Source/GameLogic/Object/SpecialPower/SpyVisionSpecialPower.cpp - Source/GameLogic/Object/Update/AIUpdate.cpp - Source/GameLogic/Object/Update/AIUpdate/AssaultTransportAIUpdate.cpp - Source/GameLogic/Object/Update/AIUpdate/ChinookAIUpdate.cpp - Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp - Source/GameLogic/Object/Update/AIUpdate/DeployStyleAIUpdate.cpp - Source/GameLogic/Object/Update/AIUpdate/DozerAIUpdate.cpp - Source/GameLogic/Object/Update/AIUpdate/HackInternetAIUpdate.cpp - Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp - Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp - Source/GameLogic/Object/Update/AIUpdate/POWTruckAIUpdate.cpp - Source/GameLogic/Object/Update/AIUpdate/RailedTransportAIUpdate.cpp - Source/GameLogic/Object/Update/AIUpdate/RailroadGuideAIUpdate.cpp - Source/GameLogic/Object/Update/AIUpdate/SupplyTruckAIUpdate.cpp - Source/GameLogic/Object/Update/AIUpdate/TransportAIUpdate.cpp - Source/GameLogic/Object/Update/AIUpdate/WanderAIUpdate.cpp - Source/GameLogic/Object/Update/AIUpdate/WorkerAIUpdate.cpp - Source/GameLogic/Object/Update/AnimationSteeringUpdate.cpp - Source/GameLogic/Object/Update/AssistedTargetingUpdate.cpp - Source/GameLogic/Object/Update/AutoDepositUpdate.cpp - Source/GameLogic/Object/Update/AutoFindHealingUpdate.cpp - Source/GameLogic/Object/Update/BaseRenerateUpdate.cpp - Source/GameLogic/Object/Update/BattlePlanUpdate.cpp - Source/GameLogic/Object/Update/BoneFXUpdate.cpp - Source/GameLogic/Object/Update/CheckpointUpdate.cpp - Source/GameLogic/Object/Update/CleanupHazardUpdate.cpp - Source/GameLogic/Object/Update/CommandButtonHuntUpdate.cpp - Source/GameLogic/Object/Update/DeletionUpdate.cpp - Source/GameLogic/Object/Update/DemoTrapUpdate.cpp - Source/GameLogic/Object/Update/DockUpdate/DockUpdate.cpp - Source/GameLogic/Object/Update/DockUpdate/PrisonDockUpdate.cpp - Source/GameLogic/Object/Update/DockUpdate/RailedTransportDockUpdate.cpp - Source/GameLogic/Object/Update/DockUpdate/RepairDockUpdate.cpp - Source/GameLogic/Object/Update/DockUpdate/SupplyCenterDockUpdate.cpp - Source/GameLogic/Object/Update/DockUpdate/SupplyWarehouseDockUpdate.cpp - Source/GameLogic/Object/Update/DynamicGeometryInfoUpdate.cpp - Source/GameLogic/Object/Update/DynamicShroudClearingRangeUpdate.cpp - Source/GameLogic/Object/Update/EMPUpdate.cpp - Source/GameLogic/Object/Update/EnemyNearUpdate.cpp - Source/GameLogic/Object/Update/FireOCLAfterWeaponCooldownUpdate.cpp - Source/GameLogic/Object/Update/FireSpreadUpdate.cpp - Source/GameLogic/Object/Update/FirestormDynamicGeometryInfoUpdate.cpp - Source/GameLogic/Object/Update/FireWeaponUpdate.cpp - Source/GameLogic/Object/Update/FlammableUpdate.cpp - Source/GameLogic/Object/Update/FloatUpdate.cpp - Source/GameLogic/Object/Update/HeightDieUpdate.cpp - Source/GameLogic/Object/Update/HelicopterSlowDeathUpdate.cpp - Source/GameLogic/Object/Update/HijackerUpdate.cpp - Source/GameLogic/Object/Update/HordeUpdate.cpp - Source/GameLogic/Object/Update/LaserUpdate.cpp - Source/GameLogic/Object/Update/LifetimeUpdate.cpp - Source/GameLogic/Object/Update/MissileLauncherBuildingUpdate.cpp - Source/GameLogic/Object/Update/MobMemberSlavedUpdate.cpp - Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp - Source/GameLogic/Object/Update/NeutronMissileUpdate.cpp - Source/GameLogic/Object/Update/OCLUpdate.cpp - Source/GameLogic/Object/Update/ParticleUplinkCannonUpdate.cpp - Source/GameLogic/Object/Update/PhysicsUpdate.cpp - Source/GameLogic/Object/Update/PilotFindVehicleUpdate.cpp - Source/GameLogic/Object/Update/PointDefenseLaserUpdate.cpp - Source/GameLogic/Object/Update/PowerPlantUpdate.cpp - Source/GameLogic/Object/Update/ProductionExitUpdate/DefaultProductionExitUpdate.cpp - Source/GameLogic/Object/Update/ProductionExitUpdate/QueueProductionExitUpdate.cpp - Source/GameLogic/Object/Update/ProductionExitUpdate/SpawnPointProductionExitUpdate.cpp - Source/GameLogic/Object/Update/ProductionExitUpdate/SupplyCenterProductionExitUpdate.cpp - Source/GameLogic/Object/Update/ProductionUpdate.cpp - Source/GameLogic/Object/Update/ProjectileStreamUpdate.cpp - Source/GameLogic/Object/Update/ProneUpdate.cpp - Source/GameLogic/Object/Update/RadarUpdate.cpp - Source/GameLogic/Object/Update/RadiusDecalUpdate.cpp - Source/GameLogic/Object/Update/SlavedUpdate.cpp - Source/GameLogic/Object/Update/SmartBombTargetHomingUpdate.cpp - Source/GameLogic/Object/Update/SpecialAbilityUpdate.cpp - Source/GameLogic/Object/Update/SpecialPowerUpdateModule.cpp - Source/GameLogic/Object/Update/SpectreGunshipDeploymentUpdate.cpp - Source/GameLogic/Object/Update/SpectreGunshipUpdate.cpp - Source/GameLogic/Object/Update/SpyVisionUpdate.cpp - Source/GameLogic/Object/Update/StealthDetectorUpdate.cpp - Source/GameLogic/Object/Update/StealthUpdate.cpp - Source/GameLogic/Object/Update/StickyBombUpdate.cpp - Source/GameLogic/Object/Update/StructureCollapseUpdate.cpp - Source/GameLogic/Object/Update/StructureToppleUpdate.cpp - Source/GameLogic/Object/Update/TensileFormationUpdate.cpp - Source/GameLogic/Object/Update/ToppleUpdate.cpp - Source/GameLogic/Object/Update/UpdateModule.cpp - Source/GameLogic/Object/Update/WaveGuideUpdate.cpp - Source/GameLogic/Object/Update/WeaponBonusUpdate.cpp - Source/GameLogic/Object/Upgrade/ActiveShroudUpgrade.cpp - Source/GameLogic/Object/Upgrade/ArmorUpgrade.cpp - Source/GameLogic/Object/Upgrade/CommandSetUpgrade.cpp - Source/GameLogic/Object/Upgrade/CostModifierUpgrade.cpp - Source/GameLogic/Object/Upgrade/ExperienceScalarUpgrade.cpp - Source/GameLogic/Object/Upgrade/GrantScienceUpgrade.cpp - Source/GameLogic/Object/Upgrade/LocomotorSetUpgrade.cpp - Source/GameLogic/Object/Upgrade/MaxHealthUpgrade.cpp - Source/GameLogic/Object/Upgrade/ModelConditionUpgrade.cpp - Source/GameLogic/Object/Upgrade/ObjectCreationUpgrade.cpp - Source/GameLogic/Object/Upgrade/PassengersFireUpgrade.cpp - Source/GameLogic/Object/Upgrade/PowerPlantUpgrade.cpp - Source/GameLogic/Object/Upgrade/RadarUpgrade.cpp - Source/GameLogic/Object/Upgrade/ReplaceObjectUpgrade.cpp - Source/GameLogic/Object/Upgrade/StatusBitsUpgrade.cpp - Source/GameLogic/Object/Upgrade/StealthUpgrade.cpp - Source/GameLogic/Object/Upgrade/SubObjectsUpgrade.cpp - Source/GameLogic/Object/Upgrade/UnpauseSpecialPowerUpgrade.cpp - Source/GameLogic/Object/Upgrade/UpgradeModule.cpp - Source/GameLogic/Object/Upgrade/WeaponBonusUpgrade.cpp - Source/GameLogic/Object/Upgrade/WeaponSetUpgrade.cpp - Source/GameLogic/Object/Weapon.cpp - Source/GameLogic/Object/WeaponSet.cpp - Source/GameLogic/ScriptEngine/ScriptActions.cpp - Source/GameLogic/ScriptEngine/ScriptConditions.cpp - 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/GameLogic.cpp - Source/GameLogic/System/GameLogicDispatch.cpp - Source/GameLogic/System/RankInfo.cpp -# Source/GameNetwork/Connection.cpp -# Source/GameNetwork/ConnectionManager.cpp -# Source/GameNetwork/DisconnectManager.cpp -# Source/GameNetwork/DownloadManager.cpp -# Source/GameNetwork/FileTransfer.cpp -# Source/GameNetwork/FirewallHelper.cpp -# Source/GameNetwork/FrameData.cpp -# Source/GameNetwork/FrameDataManager.cpp -# Source/GameNetwork/FrameMetrics.cpp -# Source/GameNetwork/GameInfo.cpp -# Source/GameNetwork/GameMessageParser.cpp -# #Source/GameNetwork/GameSpyChat.cpp -# #Source/GameNetwork/GameSpyGameInfo.cpp -# #Source/GameNetwork/GameSpyGP.cpp -# Source/GameNetwork/GameSpy/Chat.cpp -# Source/GameNetwork/GameSpy/GSConfig.cpp -# Source/GameNetwork/GameSpy/LadderDefs.cpp -# Source/GameNetwork/GameSpy/LobbyUtils.cpp -# Source/GameNetwork/GameSpy/MainMenuUtils.cpp -# Source/GameNetwork/GameSpy/PeerDefs.cpp -# Source/GameNetwork/GameSpy/StagingRoomGameInfo.cpp -# Source/GameNetwork/GameSpy/Thread/BuddyThread.cpp -# Source/GameNetwork/GameSpy/Thread/GameResultsThread.cpp -# Source/GameNetwork/GameSpy/Thread/PeerThread.cpp -# Source/GameNetwork/GameSpy/Thread/PersistentStorageThread.cpp -# Source/GameNetwork/GameSpy/Thread/PingThread.cpp -# Source/GameNetwork/GameSpy/Thread/ThreadUtils.cpp -# Source/GameNetwork/GameSpyOverlay.cpp - Source/GameNetwork/GUIUtil.cpp -# Source/GameNetwork/IPEnumeration.cpp -# Source/GameNetwork/LANAPI.cpp -# Source/GameNetwork/LANAPICallbacks.cpp -# Source/GameNetwork/LANAPIhandlers.cpp -# Source/GameNetwork/LANGameInfo.cpp -# Source/GameNetwork/NAT.cpp -# Source/GameNetwork/NetCommandList.cpp -# Source/GameNetwork/NetCommandMsg.cpp -# Source/GameNetwork/NetCommandRef.cpp -# Source/GameNetwork/NetCommandWrapperList.cpp -# Source/GameNetwork/NetMessageStream.cpp -# Source/GameNetwork/NetPacket.cpp -# Source/GameNetwork/Network.cpp -# Source/GameNetwork/NetworkUtil.cpp -# Source/GameNetwork/Transport.cpp -# Source/GameNetwork/udp.cpp -# Source/GameNetwork/User.cpp -# Source/GameNetwork/WOLBrowser/WebBrowser.cpp - Source/GameNetwork/UDPTransport.cpp - Source/Precompiled/PreRTS.cpp - - Include/GameNetwork/GeneralsOnline/json.hpp - Include/GameNetwork/GeneralsOnline/NetworkBitstream.h - Include/GameNetwork/GeneralsOnline/NetworkMesh.h - Include/GameNetwork/GeneralsOnline/NetworkPacket.h - Include/GameNetwork/GeneralsOnline/NextGenMP_defines.h - Include/GameNetwork/GeneralsOnline/NGMPGame.h - Include/GameNetwork/GeneralsOnline/NGMP_include.h - Include/GameNetwork/GeneralsOnline/NGMP_interfaces.h - Include/GameNetwork/GeneralsOnline/NGMP_types.h - Include/GameNetwork/GeneralsOnline/OnlineServices_Auth.h - Include/GameNetwork/GeneralsOnline/OnlineServices_Init.h - Include/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.h - Include/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.h - Include/GameNetwork/GeneralsOnline/OnlineServices_StatsInterface.h - Include/GameNetwork/GeneralsOnline/OnlineServices_SocialInterface.h - Include/GameNetwork/GeneralsOnline/OnlineServices_MatchmakingInterface.h - Include/GameNetwork/GeneralsOnline/GeneralsOnline_Settings.h - Include/GameNetwork/GeneralsOnline/HTTP/HTTPManager.h - Include/GameNetwork/GeneralsOnline/HTTP/HTTPRequest.h - Include/GameNetwork/GeneralsOnline/NextGenTransport.h - Include/GameNetwork/GeneralsOnline/Vendor/ValveNetworkingSockets/steam/isteamnetworkingmessages.h - Include/GameNetwork/GeneralsOnline/Vendor/ValveNetworkingSockets/steam/isteamnetworkingsockets.h - Include/GameNetwork/GeneralsOnline/Vendor/ValveNetworkingSockets/steam/isteamnetworkingutils.h - Include/GameNetwork/GeneralsOnline/Vendor/ValveNetworkingSockets/steam/steam_api_common.h - Include/GameNetwork/GeneralsOnline/Vendor/ValveNetworkingSockets/steam/steamclientpublic.h - Include/GameNetwork/GeneralsOnline/Vendor/ValveNetworkingSockets/steam/steamnetworkingcustomsignaling.h - Include/GameNetwork/GeneralsOnline/Vendor/ValveNetworkingSockets/steam/steamnetworkingsockets.h - Include/GameNetwork/GeneralsOnline/Vendor/ValveNetworkingSockets/steam/steamnetworkingsockets_flat.h - Include/GameNetwork/GeneralsOnline/Vendor/ValveNetworkingSockets/steam/steamnetworkingtypes.h - Include/GameNetwork/GeneralsOnline/Vendor/ValveNetworkingSockets/steam/steamtypes.h - Include/GameNetwork/GeneralsOnline/Vendor/ValveNetworkingSockets/steam/steamuniverse.h - Include/GameNetwork/GeneralsOnline/Vendor/sentry/sentry.h - Include/GameNetwork/GeneralsOnline/Vendor/libcurl/curl.h - Include/GameNetwork/GeneralsOnline/Vendor/libcurl/curlver.h - Include/GameNetwork/GeneralsOnline/Vendor/libcurl/easy.h - Include/GameNetwork/GeneralsOnline/Vendor/libcurl/header.h - Include/GameNetwork/GeneralsOnline/Vendor/libcurl/mprintf.h - Include/GameNetwork/GeneralsOnline/Vendor/libcurl/multi.h - Include/GameNetwork/GeneralsOnline/Vendor/libcurl/options.h - Include/GameNetwork/GeneralsOnline/Vendor/libcurl/stdcheaders.h - Include/GameNetwork/GeneralsOnline/Vendor/libcurl/system.h - Include/GameNetwork/GeneralsOnline/Vendor/libcurl/typecheck-gcc.h - Include/GameNetwork/GeneralsOnline/Vendor/libcurl/urlapi.h - Include/GameNetwork/GeneralsOnline/Vendor/libcurl/websockets.h - Source/GameNetwork/GeneralsOnline/NGMP_Helpers.cpp - Source/GameNetwork/GeneralsOnline/NGMPGame.cpp - Source/GameNetwork/GeneralsOnline/GeneralsOnline_Settings.cpp - Source/GameNetwork/GeneralsOnline/HTTP/HTTPManager.cpp - Source/GameNetwork/GeneralsOnline/HTTP/HTTPRequest.cpp - Source/GameNetwork/GeneralsOnline/NetworkMesh.cpp - Source/GameNetwork/GeneralsOnline/OnlineServices_Init.cpp - Source/GameNetwork/GeneralsOnline/OnlineServices_Auth.cpp - Source/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.cpp - Source/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.cpp - Source/GameNetwork/GeneralsOnline/OnlineServices_StatsInterface.cpp - Source/GameNetwork/GeneralsOnline/OnlineServices_SocialInterface.cpp - Source/GameNetwork/GeneralsOnline/OnlineServices_MatchmakingInterface.cpp - Source/GameNetwork/GeneralsOnline/NextGenTransport.cpp - Source/GameNetwork/GeneralsOnline/NetworkBitstream.cpp -) - -if(RTS_GAMEMEMORY_ENABLE) - # Uses the original Game Memory implementation. - list(APPEND GAMEENGINE_SRC - Source/Common/System/GameMemory.cpp - Source/Common/System/MemoryInit.cpp - ) -else() - # Uses the null implementation when disabled. - list(APPEND GAMEENGINE_SRC - Source/Common/System/GameMemoryNull.cpp - Include/Common/GameMemoryNull.h - ) -endif() - - -add_library(z_gameengine STATIC) - -target_sources(z_gameengine PRIVATE ${GAMEENGINE_SRC}) - -target_include_directories(z_gameengine PUBLIC - Include - - Include/GameNetwork/GeneralsOnline/Vendor/ -) - -target_link_directories(z_gameengine PUBLIC - Source/GameNetwork/GeneralsOnline/Vendor -) - -target_include_directories(z_gameengine PRIVATE - Include/Precompiled -) - -target_link_libraries(z_gameengine PRIVATE - corei_gameengine_private - zi_always -) - -target_link_libraries(z_gameengine PUBLIC - corei_gameengine_public - z_wwvegas -) - -target_precompile_headers(z_gameengine PRIVATE - [["Utility/CppMacros.h"]] # Must be first, to be removed when abandoning VC6 - Include/Precompiled/PreRTS.h -) - - -add_compile_definitions(_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR) -add_compile_definitions(GENERALS_ONLINE) \ No newline at end of file +set(GAMEENGINE_SRC + Include/Common/AcademyStats.h + Include/Common/ActionManager.h +# Include/Common/ArchiveFile.h +# Include/Common/ArchiveFileSystem.h +# Include/Common/AsciiString.h +# Include/Common/AudioAffect.h +# Include/Common/AudioEventInfo.h +# Include/Common/AudioEventRTS.h +# Include/Common/AudioHandleSpecialValues.h +# Include/Common/AudioRandomValue.h +# Include/Common/AudioRequest.h +# Include/Common/AudioSettings.h + Include/Common/BattleHonors.h + Include/Common/BezFwdIterator.h + Include/Common/BezierSegment.h + Include/Common/BitFlags.h + Include/Common/BitFlagsIO.h + Include/Common/BorderColors.h + Include/Common/BuildAssistant.h + Include/Common/ClientUpdateModule.h + Include/Common/CommandLine.h +# Include/Common/crc.h +# Include/Common/CRCDebug.h + Include/Common/CriticalSection.h + Include/Common/CustomMatchPreferences.h + Include/Common/DamageFX.h + Include/Common/DataChunk.h +# Include/Common/Debug.h + Include/Common/Dict.h + Include/Common/Directory.h + Include/Common/DisabledTypes.h + Include/Common/DiscreteCircle.h + Include/Common/DrawModule.h +# Include/Common/DynamicAudioEventInfo.h + Include/Common/encrypt.h + Include/Common/Energy.h +# Include/Common/Errors.h +# Include/Common/file.h +# Include/Common/FileSystem.h + Include/Common/FunctionLexicon.h +# Include/Common/GameAudio.h +# Include/Common/GameCommon.h +# Include/Common/GameDefines.h + Include/Common/GameEngine.h + Include/Common/GameLOD.h + Include/Common/GameMemory.h +# Include/Common/GameMusic.h +# Include/Common/GameSounds.h + Include/Common/GameSpyMiscPreferences.h + Include/Common/GameState.h + Include/Common/GameStateMap.h +# Include/Common/GameType.h + Include/Common/Geometry.h + Include/Common/GlobalData.h + Include/Common/Handicap.h + Include/Common/IgnorePreferences.h +# Include/Common/INI.h + Include/Common/INIException.h + Include/Common/KindOf.h + Include/Common/LadderPreferences.h + Include/Common/Language.h + Include/Common/LatchRestore.h + Include/Common/List.h +# Include/Common/LocalFile.h +# Include/Common/LocalFileSystem.h +# Include/Common/MapObject.h + Include/Common/MapReaderWriterInfo.h + Include/Common/MessageStream.h + Include/Common/MiniLog.h +# Include/Common/MiscAudio.h + Include/Common/MissionStats.h + Include/Common/ModelState.h + Include/Common/Module.h + Include/Common/ModuleFactory.h + Include/Common/Money.h + Include/Common/MultiplayerSettings.h + Include/Common/NameKeyGenerator.h +# Include/Common/ObjectStatusTypes.h + Include/Common/OSDisplay.h + Include/Common/Overridable.h + Include/Common/Override.h + Include/Common/PartitionSolver.h + Include/Common/PerfMetrics.h + Include/Common/PerfTimer.h + Include/Common/Player.h + Include/Common/PlayerList.h + Include/Common/PlayerTemplate.h + Include/Common/ProductionPrerequisite.h + Include/Common/QuickmatchPreferences.h + Include/Common/QuotedPrintable.h +# Include/Common/Radar.h +# Include/Common/RAMFile.h +# Include/Common/RandomValue.h + Include/Common/Recorder.h +# Include/Common/ReplaySimulation.h + Include/Common/Registry.h + Include/Common/ResourceGatheringManager.h + Include/Common/Science.h + Include/Common/ScopedMutex.h + Include/Common/ScoreKeeper.h +# Include/Common/simpleplayer.h + Include/Common/SkirmishBattleHonors.h + Include/Common/SkirmishPreferences.h +# Include/Common/Snapshot.h + Include/Common/SparseMatchFinder.h + Include/Common/SpecialPower.h + Include/Common/SpecialPowerMaskType.h + Include/Common/SpecialPowerType.h + Include/Common/StackDump.h + Include/Common/StateMachine.h + Include/Common/StatsCollector.h + Include/Common/StatsExporter.h + Include/Common/StatsUploader.h +# Include/Common/STLTypedefs.h +# Include/Common/StreamingArchiveFile.h +# Include/Common/SubsystemInterface.h + Include/Common/SystemInfo.h + Include/Common/Team.h + Include/Common/Terrain.h + Include/Common/TerrainTypes.h + Include/Common/Thing.h + Include/Common/ThingFactory.h + Include/Common/ThingSort.h + Include/Common/ThingTemplate.h + Include/Common/TunnelTracker.h +# Include/Common/UnicodeString.h + Include/Common/UnitTimings.h + Include/Common/Upgrade.h +# Include/Common/urllaunch.h +# Include/Common/UserPreferences.h + Include/Common/version.h + Include/Common/WellKnownKeys.h +# Include/Common/WorkerProcess.h +# Include/Common/Xfer.h +# Include/Common/XferCRC.h +# Include/Common/XferDeepCRC.h +# Include/Common/XferLoad.h +# Include/Common/XferSave.h + Include/GameClient/Anim2D.h + Include/GameClient/AnimateWindowManager.h + Include/GameClient/CampaignManager.h +# Include/GameClient/ChallengeGenerals.h +# Include/GameClient/ClientInstance.h +# Include/GameClient/ClientRandomValue.h +# Include/GameClient/Color.h + Include/GameClient/CommandXlat.h + Include/GameClient/ControlBar.h + Include/GameClient/ControlBarResizer.h + Include/GameClient/ControlBarScheme.h +# Include/GameClient/Credits.h + Include/GameClient/DebugDisplay.h + Include/GameClient/Diplomacy.h + Include/GameClient/DisconnectMenu.h + Include/GameClient/Display.h +# Include/GameClient/DisplayString.h +# Include/GameClient/DisplayStringManager.h + Include/GameClient/Drawable.h + Include/GameClient/DrawableInfo.h +# Include/GameClient/DrawGroupInfo.h + Include/GameClient/EstablishConnectionsMenu.h + Include/GameClient/Eva.h + Include/GameClient/ExtendedMessageBox.h + Include/GameClient/FontDesc.h +# Include/GameClient/FXList.h + Include/GameClient/Gadget.h + Include/GameClient/GadgetCheckBox.h + Include/GameClient/GadgetComboBox.h + Include/GameClient/GadgetListBox.h + Include/GameClient/GadgetProgressBar.h + Include/GameClient/GadgetPushButton.h + Include/GameClient/GadgetRadioButton.h + Include/GameClient/GadgetSlider.h + Include/GameClient/GadgetStaticText.h + Include/GameClient/GadgetTabControl.h + Include/GameClient/GadgetTextEntry.h + Include/GameClient/GameClient.h +# Include/GameClient/GameFont.h + Include/GameClient/GameInfoWindow.h +# Include/GameClient/GameText.h +# Include/GameClient/GameWindow.h +# Include/GameClient/GameWindowGlobal.h + Include/GameClient/GameWindowID.h + Include/GameClient/GameWindowManager.h +# Include/GameClient/GameWindowTransitions.h +# Include/GameClient/GlobalLanguage.h +# Include/GameClient/GraphDraw.h + Include/GameClient/GUICallbacks.h + Include/GameClient/GUICommandTranslator.h +# Include/GameClient/HeaderTemplate.h + Include/GameClient/HintSpy.h + Include/GameClient/HotKey.h + Include/GameClient/Image.h +# Include/GameClient/IMEManager.h + Include/GameClient/InGameUI.h +# Include/GameClient/Keyboard.h + Include/GameClient/KeyDefs.h +# Include/GameClient/LanguageFilter.h +# Include/GameClient/Line2D.h +# Include/GameClient/LoadScreen.h + Include/GameClient/LookAtXlat.h +# Include/GameClient/MapUtil.h + Include/GameClient/MessageBox.h + Include/GameClient/MetaEvent.h + Include/GameClient/Module/AnimatedParticleSysBoneClientUpdate.h + Include/GameClient/Module/BeaconClientUpdate.h + Include/GameClient/Module/SwayClientUpdate.h +# Include/GameClient/Mouse.h +# Include/GameClient/ParabolicEase.h +# Include/GameClient/ParticleSys.h + Include/GameClient/PlaceEventTranslator.h +# Include/GameClient/ProcessAnimateWindow.h +# Include/GameClient/RadiusDecal.h + Include/GameClient/RayEffect.h +# Include/GameClient/SelectionInfo.h + Include/GameClient/SelectionXlat.h + Include/GameClient/Shadow.h + Include/GameClient/Shell.h + Include/GameClient/ShellHooks.h + Include/GameClient/ShellMenuScheme.h +# Include/GameClient/Smudge.h +# Include/GameClient/Snow.h +# Include/GameClient/Statistics.h +# Include/GameClient/TerrainRoads.h +# Include/GameClient/TerrainVisual.h +# Include/GameClient/VideoPlayer.h +# Include/GameClient/View.h +# Include/GameClient/Water.h +# Include/GameClient/WindowLayout.h +# Include/GameClient/WindowVideoManager.h + Include/GameClient/WindowXlat.h +# Include/GameClient/WinInstanceData.h + Include/GameLogic/AI.h + Include/GameLogic/AIDock.h + Include/GameLogic/AIGuard.h + Include/GameLogic/AIGuardRetaliate.h +# Include/GameLogic/AIPathfind.h + Include/GameLogic/AIPlayer.h + Include/GameLogic/AISkirmishPlayer.h + Include/GameLogic/AIStateMachine.h + 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/ExperienceTracker.h + Include/GameLogic/FiringTracker.h + Include/GameLogic/FPUControl.h + Include/GameLogic/GameLogic.h + Include/GameLogic/GhostObject.h + Include/GameLogic/Locomotor.h + Include/GameLogic/LocomotorSet.h +# Include/GameLogic/LogicRandomValue.h + Include/GameLogic/Module/ActiveBody.h + Include/GameLogic/Module/ActiveShroudUpgrade.h + Include/GameLogic/Module/AIUpdate.h + Include/GameLogic/Module/AnimationSteeringUpdate.h + Include/GameLogic/Module/ArmorUpgrade.h + Include/GameLogic/Module/AssaultTransportAIUpdate.h + Include/GameLogic/Module/AssistedTargetingUpdate.h + Include/GameLogic/Module/AutoDepositUpdate.h + Include/GameLogic/Module/AutoFindHealingUpdate.h + Include/GameLogic/Module/AutoHealBehavior.h + Include/GameLogic/Module/BaikonurLaunchPower.h + Include/GameLogic/Module/BaseRegenerateUpdate.h + Include/GameLogic/Module/BattleBusSlowDeathBehavior.h + Include/GameLogic/Module/BattlePlanUpdate.h + Include/GameLogic/Module/BehaviorModule.h + Include/GameLogic/Module/BodyModule.h + Include/GameLogic/Module/BoneFXDamage.h + Include/GameLogic/Module/BoneFXUpdate.h + Include/GameLogic/Module/BridgeBehavior.h + Include/GameLogic/Module/BridgeScaffoldBehavior.h + Include/GameLogic/Module/BridgeTowerBehavior.h + Include/GameLogic/Module/BunkerBusterBehavior.h + Include/GameLogic/Module/CashBountyPower.h + Include/GameLogic/Module/CashHackSpecialPower.h + Include/GameLogic/Module/CaveContain.h + Include/GameLogic/Module/CheckpointUpdate.h + Include/GameLogic/Module/ChinookAIUpdate.h + Include/GameLogic/Module/CleanupAreaPower.h + Include/GameLogic/Module/CleanupHazardUpdate.h + Include/GameLogic/Module/CollideModule.h + Include/GameLogic/Module/CommandButtonHuntUpdate.h + Include/GameLogic/Module/CommandSetUpgrade.h + Include/GameLogic/Module/ContainModule.h + Include/GameLogic/Module/ConvertToCarBombCrateCollide.h + Include/GameLogic/Module/ConvertToHijackedVehicleCrateCollide.h + Include/GameLogic/Module/CostModifierUpgrade.h + Include/GameLogic/Module/CountermeasuresBehavior.h + Include/GameLogic/Module/CrateCollide.h + Include/GameLogic/Module/CreateCrateDie.h + Include/GameLogic/Module/CreateModule.h + Include/GameLogic/Module/CreateObjectDie.h + Include/GameLogic/Module/CrushDie.h + Include/GameLogic/Module/DamageModule.h + Include/GameLogic/Module/DamDie.h + Include/GameLogic/Module/DefaultProductionExitUpdate.h + Include/GameLogic/Module/DefectorSpecialPower.h + Include/GameLogic/Module/DeletionUpdate.h + Include/GameLogic/Module/DeliverPayloadAIUpdate.h + Include/GameLogic/Module/DemoralizeSpecialPower.h + Include/GameLogic/Module/DemoTrapUpdate.h + Include/GameLogic/Module/DeployStyleAIUpdate.h + Include/GameLogic/Module/DestroyDie.h + Include/GameLogic/Module/DestroyModule.h + Include/GameLogic/Module/DieModule.h + Include/GameLogic/Module/DockUpdate.h + Include/GameLogic/Module/DozerAIUpdate.h + Include/GameLogic/Module/DumbProjectileBehavior.h + Include/GameLogic/Module/DynamicGeometryInfoUpdate.h + Include/GameLogic/Module/DynamicShroudClearingRangeUpdate.h + Include/GameLogic/Module/EjectPilotDie.h + Include/GameLogic/Module/EMPUpdate.h + Include/GameLogic/Module/EnemyNearUpdate.h + Include/GameLogic/Module/ExperienceScalarUpgrade.h + Include/GameLogic/Module/FireOCLAfterWeaponCooldownUpdate.h + Include/GameLogic/Module/FireSpreadUpdate.h + Include/GameLogic/Module/FirestormDynamicGeometryInfoUpdate.h + Include/GameLogic/Module/FireWeaponCollide.h + Include/GameLogic/Module/FireWeaponPower.h + Include/GameLogic/Module/FireWeaponUpdate.h + Include/GameLogic/Module/FireWeaponWhenDamagedBehavior.h + Include/GameLogic/Module/FireWeaponWhenDeadBehavior.h + Include/GameLogic/Module/FlammableUpdate.h + Include/GameLogic/Module/FlightDeckBehavior.h + Include/GameLogic/Module/FloatUpdate.h + Include/GameLogic/Module/FXListDie.h + Include/GameLogic/Module/GarrisonContain.h + Include/GameLogic/Module/GenerateMinefieldBehavior.h + Include/GameLogic/Module/GrantScienceUpgrade.h + Include/GameLogic/Module/GrantStealthBehavior.h + Include/GameLogic/Module/GrantUpgradeCreate.h + Include/GameLogic/Module/HackInternetAIUpdate.h + Include/GameLogic/Module/HealContain.h + Include/GameLogic/Module/HealCrateCollide.h + Include/GameLogic/Module/HeightDieUpdate.h + Include/GameLogic/Module/HelicopterSlowDeathUpdate.h + Include/GameLogic/Module/HelixContain.h + Include/GameLogic/Module/HighlanderBody.h + Include/GameLogic/Module/HijackerUpdate.h + Include/GameLogic/Module/HiveStructureBody.h + Include/GameLogic/Module/HordeUpdate.h + Include/GameLogic/Module/ImmortalBody.h + Include/GameLogic/Module/InactiveBody.h + Include/GameLogic/Module/InstantDeathBehavior.h + Include/GameLogic/Module/InternetHackContain.h + Include/GameLogic/Module/JetAIUpdate.h + Include/GameLogic/Module/JetSlowDeathBehavior.h + Include/GameLogic/Module/KeepObjectDie.h + Include/GameLogic/Module/LaserUpdate.h + Include/GameLogic/Module/LifetimeUpdate.h + Include/GameLogic/Module/LockWeaponCreate.h + Include/GameLogic/Module/LocomotorSetUpgrade.h + Include/GameLogic/Module/MaxHealthUpgrade.h + Include/GameLogic/Module/MinefieldBehavior.h + Include/GameLogic/Module/MissileAIUpdate.h + Include/GameLogic/Module/MissileLauncherBuildingUpdate.h + Include/GameLogic/Module/MobMemberSlavedUpdate.h + Include/GameLogic/Module/MobNexusContain.h + Include/GameLogic/Module/ModelConditionUpgrade.h + Include/GameLogic/Module/MoneyCrateCollide.h + Include/GameLogic/Module/NeutronBlastBehavior.h + Include/GameLogic/Module/NeutronMissileSlowDeathUpdate.h + Include/GameLogic/Module/NeutronMissileUpdate.h + Include/GameLogic/Module/ObjectCreationUpgrade.h + Include/GameLogic/Module/ObjectDefectionHelper.h + Include/GameLogic/Module/ObjectHelper.h + Include/GameLogic/Module/ObjectRepulsorHelper.h + Include/GameLogic/Module/ObjectSMCHelper.h + Include/GameLogic/Module/ObjectWeaponStatusHelper.h + Include/GameLogic/Module/OCLSpecialPower.h + Include/GameLogic/Module/OCLUpdate.h + Include/GameLogic/Module/OpenContain.h + Include/GameLogic/Module/OverchargeBehavior.h + Include/GameLogic/Module/OverlordContain.h + Include/GameLogic/Module/ParachuteContain.h + Include/GameLogic/Module/ParkingPlaceBehavior.h + Include/GameLogic/Module/ParticleUplinkCannonUpdate.h + Include/GameLogic/Module/PassengersFireUpgrade.h + Include/GameLogic/Module/PhysicsUpdate.h + Include/GameLogic/Module/PilotFindVehicleUpdate.h + Include/GameLogic/Module/PointDefenseLaserUpdate.h + Include/GameLogic/Module/PoisonedBehavior.h + Include/GameLogic/Module/PowerPlantUpdate.h + Include/GameLogic/Module/PowerPlantUpgrade.h + Include/GameLogic/Module/POWTruckAIUpdate.h + Include/GameLogic/Module/POWTruckBehavior.h + Include/GameLogic/Module/PreorderCreate.h + Include/GameLogic/Module/PrisonBehavior.h + Include/GameLogic/Module/PrisonDockUpdate.h + Include/GameLogic/Module/ProductionUpdate.h + Include/GameLogic/Module/ProjectileStreamUpdate.h + Include/GameLogic/Module/ProneUpdate.h + Include/GameLogic/Module/PropagandaCenterBehavior.h + Include/GameLogic/Module/PropagandaTowerBehavior.h + Include/GameLogic/Module/QueueProductionExitUpdate.h + Include/GameLogic/Module/RadarUpdate.h + Include/GameLogic/Module/RadarUpgrade.h + Include/GameLogic/Module/RadiusDecalUpdate.h + Include/GameLogic/Module/RailedTransportAIUpdate.h + Include/GameLogic/Module/RailedTransportContain.h + Include/GameLogic/Module/RailedTransportDockUpdate.h + Include/GameLogic/Module/RailroadGuideAIUpdate.h + Include/GameLogic/Module/RebuildHoleBehavior.h + Include/GameLogic/Module/RebuildHoleExposeDie.h + Include/GameLogic/Module/RepairDockUpdate.h + Include/GameLogic/Module/ReplaceObjectUpgrade.h + Include/GameLogic/Module/RiderChangeContain.h + Include/GameLogic/Module/SabotageCommandCenterCrateCollide.h + Include/GameLogic/Module/SabotageFakeBuildingCrateCollide.h + Include/GameLogic/Module/SabotageInternetCenterCrateCollide.h + Include/GameLogic/Module/SabotageMilitaryFactoryCrateCollide.h + Include/GameLogic/Module/SabotagePowerPlantCrateCollide.h + Include/GameLogic/Module/SabotageSuperweaponCrateCollide.h + Include/GameLogic/Module/SabotageSupplyCenterCrateCollide.h + Include/GameLogic/Module/SabotageSupplyDropzoneCrateCollide.h + Include/GameLogic/Module/SalvageCrateCollide.h + Include/GameLogic/Module/ShroudCrateCollide.h + Include/GameLogic/Module/SlavedUpdate.h + Include/GameLogic/Module/SlowDeathBehavior.h + Include/GameLogic/Module/SmartBombTargetHomingUpdate.h + Include/GameLogic/Module/SpawnBehavior.h + Include/GameLogic/Module/SpawnPointProductionExitUpdate.h + Include/GameLogic/Module/SpecialAbility.h + Include/GameLogic/Module/SpecialAbilityUpdate.h + Include/GameLogic/Module/SpecialPowerCompletionDie.h + Include/GameLogic/Module/SpecialPowerCreate.h + Include/GameLogic/Module/SpecialPowerModule.h + Include/GameLogic/Module/SpecialPowerUpdateModule.h + Include/GameLogic/Module/SpectreGunshipDeploymentUpdate.h + Include/GameLogic/Module/SpectreGunshipUpdate.h + Include/GameLogic/Module/SpyVisionSpecialPower.h + Include/GameLogic/Module/SpyVisionUpdate.h + Include/GameLogic/Module/SquishCollide.h + Include/GameLogic/Module/StatusBitsUpgrade.h + Include/GameLogic/Module/StatusDamageHelper.h + Include/GameLogic/Module/StealthDetectorUpdate.h + Include/GameLogic/Module/StealthUpdate.h + Include/GameLogic/Module/StealthUpgrade.h + Include/GameLogic/Module/StickyBombUpdate.h + Include/GameLogic/Module/StructureBody.h + Include/GameLogic/Module/StructureCollapseUpdate.h + Include/GameLogic/Module/StructureToppleUpdate.h + Include/GameLogic/Module/SubdualDamageHelper.h + Include/GameLogic/Module/SubObjectsUpgrade.h + Include/GameLogic/Module/SupplyCenterCreate.h + Include/GameLogic/Module/SupplyCenterDockUpdate.h + Include/GameLogic/Module/SupplyCenterProductionExitUpdate.h + Include/GameLogic/Module/SupplyTruckAIUpdate.h + Include/GameLogic/Module/SupplyWarehouseCreate.h + Include/GameLogic/Module/SupplyWarehouseCripplingBehavior.h + Include/GameLogic/Module/SupplyWarehouseDockUpdate.h + Include/GameLogic/Module/TechBuildingBehavior.h + Include/GameLogic/Module/TempWeaponBonusHelper.h + Include/GameLogic/Module/TensileFormationUpdate.h + Include/GameLogic/Module/ToppleUpdate.h + Include/GameLogic/Module/TransitionDamageFX.h + Include/GameLogic/Module/TransportAIUpdate.h + Include/GameLogic/Module/TransportContain.h + Include/GameLogic/Module/TunnelContain.h + Include/GameLogic/Module/UndeadBody.h + Include/GameLogic/Module/UnitCrateCollide.h + Include/GameLogic/Module/UnpauseSpecialPowerUpgrade.h + Include/GameLogic/Module/UpdateModule.h + Include/GameLogic/Module/UpgradeDie.h + Include/GameLogic/Module/UpgradeModule.h + Include/GameLogic/Module/VeterancyCrateCollide.h + Include/GameLogic/Module/VeterancyGainCreate.h + Include/GameLogic/Module/WanderAIUpdate.h + Include/GameLogic/Module/WaveGuideUpdate.h + Include/GameLogic/Module/WeaponBonusUpdate.h + Include/GameLogic/Module/WeaponBonusUpgrade.h + Include/GameLogic/Module/WeaponSetUpgrade.h + Include/GameLogic/Module/WorkerAIUpdate.h + Include/GameLogic/Object.h + Include/GameLogic/ObjectCreationList.h + Include/GameLogic/ObjectIter.h + Include/GameLogic/ObjectScriptStatusBits.h + Include/GameLogic/ObjectTypes.h + Include/GameLogic/PartitionManager.h + Include/GameLogic/PolygonTrigger.h + Include/GameLogic/Powers.h + Include/GameLogic/RankInfo.h + Include/GameLogic/ScriptActions.h + Include/GameLogic/ScriptConditions.h + Include/GameLogic/ScriptEngine.h + Include/GameLogic/Scripts.h + Include/GameLogic/SidesList.h + Include/GameLogic/Squad.h + Include/GameLogic/TerrainLogic.h + Include/GameLogic/TurretAI.h + Include/GameLogic/VictoryConditions.h + Include/GameLogic/Weapon.h + Include/GameLogic/WeaponBonusConditionFlags.h + Include/GameLogic/WeaponSet.h + Include/GameLogic/WeaponSetFlags.h + Include/GameLogic/WeaponSetType.h + Include/GameLogic/WeaponStatus.h +# Include/GameNetwork/Connection.h +# Include/GameNetwork/ConnectionManager.h +# Include/GameNetwork/DisconnectManager.h +# Include/GameNetwork/DownloadManager.h +# Include/GameNetwork/FileTransfer.h +# Include/GameNetwork/FirewallHelper.h +# Include/GameNetwork/FrameData.h +# Include/GameNetwork/FrameDataManager.h +# Include/GameNetwork/FrameMetrics.h +# Include/GameNetwork/GameInfo.h +# Include/GameNetwork/GameMessageParser.h +# Include/GameNetwork/GameSpy/BuddyDefs.h +# Include/GameNetwork/GameSpy/BuddyThread.h +# Include/GameNetwork/GameSpy/GameResultsThread.h +# Include/GameNetwork/GameSpy/GSConfig.h +# Include/GameNetwork/GameSpy/LadderDefs.h +# Include/GameNetwork/GameSpy/LobbyUtils.h +# Include/GameNetwork/GameSpy/MainMenuUtils.h +# Include/GameNetwork/GameSpy/PeerDefs.h +# Include/GameNetwork/GameSpy/PeerDefsImplementation.h +# Include/GameNetwork/GameSpy/PeerThread.h +# Include/GameNetwork/GameSpy/PersistentStorageDefs.h +# Include/GameNetwork/GameSpy/PersistentStorageThread.h +# Include/GameNetwork/GameSpy/PingThread.h +# Include/GameNetwork/GameSpy/StagingRoomGameInfo.h +# Include/GameNetwork/GameSpy/ThreadUtils.h +# Include/GameNetwork/GameSpyChat.h +# Include/GameNetwork/GameSpyGameInfo.h +# Include/GameNetwork/GameSpyGP.h +# Include/GameNetwork/GameSpyOverlay.h +# Include/GameNetwork/GameSpyThread.h + Include/GameNetwork/GUIUtil.h +# Include/GameNetwork/IPEnumeration.h +# Include/GameNetwork/LANAPI.h +# Include/GameNetwork/LANAPICallbacks.h +# Include/GameNetwork/LANGameInfo.h +# Include/GameNetwork/LANPlayer.h +# Include/GameNetwork/NAT.h +# Include/GameNetwork/NetCommandList.h +# Include/GameNetwork/NetCommandMsg.h +# Include/GameNetwork/NetCommandRef.h +# Include/GameNetwork/NetCommandWrapperList.h +# Include/GameNetwork/NetPacket.h +# Include/GameNetwork/NetworkDefs.h +# Include/GameNetwork/NetworkInterface.h +# Include/GameNetwork/networkutil.h +# Include/GameNetwork/RankPointValue.h +# Include/GameNetwork/Transport.h +# Include/GameNetwork/udp.h +# Include/GameNetwork/User.h +# Include/GameNetwork/WOLBrowser/FEBDispatch.h +# Include/GameNetwork/WOLBrowser/WebBrowser.h + Include/GameNetwork/UDPTransport.h + Include/Precompiled/PreRTS.h +# Source/Common/Audio/AudioEventRTS.cpp +# Source/Common/Audio/AudioRequest.cpp +# Source/Common/Audio/DynamicAudioEventInfo.cpp +# Source/Common/Audio/GameAudio.cpp +# Source/Common/Audio/GameMusic.cpp +# Source/Common/Audio/GameSounds.cpp + #Source/Common/Audio/simpleplayer.cpp # unused + #Source/Common/Audio/urllaunch.cpp # unused + Source/Common/Bezier/BezFwdIterator.cpp + Source/Common/Bezier/BezierSegment.cpp + Source/Common/BitFlags.cpp + Source/Common/CommandLine.cpp +# Source/Common/crc.cpp +# Source/Common/CRCDebug.cpp + Source/Common/DamageFX.cpp + Source/Common/Dict.cpp + Source/Common/DiscreteCircle.cpp + Source/Common/GameEngine.cpp + Source/Common/GameLOD.cpp + Source/Common/GameMain.cpp + Source/Common/GlobalData.cpp +# Source/Common/INI/INI.cpp + Source/Common/INI/INIAiData.cpp + Source/Common/INI/INIAnimation.cpp +# Source/Common/INI/INIAudioEventInfo.cpp + Source/Common/INI/INICommandButton.cpp + Source/Common/INI/INICommandSet.cpp + Source/Common/INI/INIControlBarScheme.cpp + Source/Common/INI/INICrate.cpp + Source/Common/INI/INIDamageFX.cpp + Source/Common/INI/INIDrawGroupInfo.cpp + Source/Common/INI/INIGameData.cpp + Source/Common/INI/INIMapCache.cpp + Source/Common/INI/INIMapData.cpp + Source/Common/INI/INIMappedImage.cpp +# Source/Common/INI/INIMiscAudio.cpp + Source/Common/INI/INIModel.cpp + Source/Common/INI/INIMultiplayer.cpp + Source/Common/INI/INIObject.cpp + Source/Common/INI/INIParticleSys.cpp + Source/Common/INI/INISpecialPower.cpp + Source/Common/INI/INITerrain.cpp + Source/Common/INI/INITerrainBridge.cpp + Source/Common/INI/INITerrainRoad.cpp + Source/Common/INI/INIUpgrade.cpp +# Source/Common/INI/INIVideo.cpp + Source/Common/INI/INIWater.cpp + Source/Common/INI/INIWeapon.cpp + Source/Common/INI/INIWebpageURL.cpp + Source/Common/Language.cpp + Source/Common/MessageStream.cpp + Source/Common/MiniLog.cpp + Source/Common/MultiplayerSettings.cpp + Source/Common/NameKeyGenerator.cpp + Source/Common/PartitionSolver.cpp + Source/Common/PerfTimer.cpp +# Source/Common/RandomValue.cpp + Source/Common/Recorder.cpp +# Source/Common/ReplaySimulation.cpp + Source/Common/RTS/AcademyStats.cpp + Source/Common/RTS/ActionManager.cpp + Source/Common/RTS/Energy.cpp + Source/Common/RTS/Handicap.cpp + Source/Common/RTS/MissionStats.cpp + Source/Common/RTS/Money.cpp + Source/Common/RTS/Player.cpp + Source/Common/RTS/PlayerList.cpp + Source/Common/RTS/PlayerTemplate.cpp + Source/Common/RTS/ProductionPrerequisite.cpp + Source/Common/RTS/ResourceGatheringManager.cpp + Source/Common/RTS/Science.cpp + Source/Common/RTS/ScoreKeeper.cpp + Source/Common/RTS/SpecialPower.cpp + Source/Common/RTS/Team.cpp + Source/Common/RTS/TunnelTracker.cpp + Source/Common/SkirmishBattleHonors.cpp + Source/Common/StateMachine.cpp + Source/Common/StatsCollector.cpp + Source/Common/StatsExporter.cpp + Source/Common/StatsUploader.cpp +# Source/Common/System/ArchiveFile.cpp +# Source/Common/System/ArchiveFileSystem.cpp +# Source/Common/System/AsciiString.cpp + Source/Common/System/BuildAssistant.cpp + Source/Common/System/CriticalSection.cpp + Source/Common/System/DataChunk.cpp +# Source/Common/System/Debug.cpp + Source/Common/System/Directory.cpp + Source/Common/System/DisabledTypes.cpp + Source/Common/System/encrypt.cpp +# Source/Common/System/File.cpp +# Source/Common/System/FileSystem.cpp + Source/Common/System/FunctionLexicon.cpp +# Source/Common/System/GameCommon.cpp + #Source/Common/System/GameMemory.cpp +# Source/Common/System/GameType.cpp + Source/Common/System/Geometry.cpp + Source/Common/System/KindOf.cpp + Source/Common/System/List.cpp +# Source/Common/System/LocalFile.cpp +# Source/Common/System/LocalFileSystem.cpp + #Source/Common/System/MemoryInit.cpp +# Source/Common/System/ObjectStatusTypes.cpp + Source/Common/System/QuotedPrintable.cpp +# Source/Common/System/Radar.cpp +# Source/Common/System/RAMFile.cpp + Source/Common/System/registry.cpp + Source/Common/System/SaveGame/GameState.cpp + Source/Common/System/SaveGame/GameStateMap.cpp +# Source/Common/System/Snapshot.cpp + Source/Common/System/StackDump.cpp +# Source/Common/System/StreamingArchiveFile.cpp +# Source/Common/System/SubsystemInterface.cpp + Source/Common/System/Trig.cpp +# Source/Common/System/UnicodeString.cpp + Source/Common/System/Upgrade.cpp +# Source/Common/System/Xfer.cpp +# Source/Common/System/XferCRC.cpp +# Source/Common/System/XferLoad.cpp +# Source/Common/System/XferSave.cpp + Source/Common/TerrainTypes.cpp + Source/Common/Thing/DrawModule.cpp + Source/Common/Thing/Module.cpp + Source/Common/Thing/ModuleFactory.cpp + Source/Common/Thing/Thing.cpp + Source/Common/Thing/ThingFactory.cpp + Source/Common/Thing/ThingTemplate.cpp +# Source/Common/UserPreferences.cpp + Source/Common/version.cpp +# Source/Common/WorkerProcess.cpp +# Source/GameClient/ClientInstance.cpp +# Source/GameClient/Color.cpp +# Source/GameClient/Credits.cpp + Source/GameClient/Display.cpp +# Source/GameClient/DisplayString.cpp +# Source/GameClient/DisplayStringManager.cpp + Source/GameClient/Drawable.cpp + Source/GameClient/Drawable/Update/AnimatedParticleSysBoneClientUpdate.cpp + Source/GameClient/Drawable/Update/BeaconClientUpdate.cpp + Source/GameClient/Drawable/Update/SwayClientUpdate.cpp +# Source/GameClient/DrawGroupInfo.cpp + Source/GameClient/Eva.cpp +# Source/GameClient/FXList.cpp + Source/GameClient/GameClient.cpp + Source/GameClient/GameClientDispatch.cpp +# Source/GameClient/GameText.cpp +# Source/GameClient/GlobalLanguage.cpp +# Source/GameClient/GraphDraw.cpp + Source/GameClient/GUI/AnimateWindowManager.cpp +# Source/GameClient/GUI/ChallengeGenerals.cpp + Source/GameClient/GUI/ControlBar/ControlBar.cpp + Source/GameClient/GUI/ControlBar/ControlBarBeacon.cpp + Source/GameClient/GUI/ControlBar/ControlBarCommand.cpp + Source/GameClient/GUI/ControlBar/ControlBarCommandProcessing.cpp + Source/GameClient/GUI/ControlBar/ControlBarMultiSelect.cpp + Source/GameClient/GUI/ControlBar/ControlBarObserver.cpp + Source/GameClient/GUI/ControlBar/ControlBarOCLTimer.cpp + Source/GameClient/GUI/ControlBar/ControlBarPrintPositions.cpp + Source/GameClient/GUI/ControlBar/ControlBarResizer.cpp + Source/GameClient/GUI/ControlBar/ControlBarScheme.cpp + Source/GameClient/GUI/ControlBar/ControlBarStructureInventory.cpp + Source/GameClient/GUI/ControlBar/ControlBarUnderConstruction.cpp + Source/GameClient/GUI/DisconnectMenu/DisconnectMenu.cpp + Source/GameClient/GUI/EstablishConnectionsMenu/EstablishConnectionsMenu.cpp + Source/GameClient/GUI/Gadget/GadgetCheckBox.cpp + Source/GameClient/GUI/Gadget/GadgetComboBox.cpp + Source/GameClient/GUI/Gadget/GadgetHorizontalSlider.cpp + Source/GameClient/GUI/Gadget/GadgetListBox.cpp + Source/GameClient/GUI/Gadget/GadgetProgressBar.cpp + Source/GameClient/GUI/Gadget/GadgetPushButton.cpp + Source/GameClient/GUI/Gadget/GadgetRadioButton.cpp + Source/GameClient/GUI/Gadget/GadgetStaticText.cpp + Source/GameClient/GUI/Gadget/GadgetTabControl.cpp + Source/GameClient/GUI/Gadget/GadgetTextEntry.cpp + Source/GameClient/GUI/Gadget/GadgetVerticalSlider.cpp +# Source/GameClient/GUI/GameFont.cpp +# Source/GameClient/GUI/GameWindow.cpp +# Source/GameClient/GUI/GameWindowGlobal.cpp + Source/GameClient/GUI/GameWindowManager.cpp + Source/GameClient/GUI/GameWindowManagerScript.cpp +# Source/GameClient/GUI/GameWindowTransitions.cpp + Source/GameClient/GUI/GameWindowTransitionsStyles.cpp + Source/GameClient/GUI/GUICallbacks/ControlBarCallback.cpp + Source/GameClient/GUI/GUICallbacks/ControlBarPopupDescription.cpp + Source/GameClient/GUI/GUICallbacks/Diplomacy.cpp + Source/GameClient/GUI/GUICallbacks/ExtendedMessageBox.cpp + Source/GameClient/GUI/GUICallbacks/GeneralsExpPoints.cpp + Source/GameClient/GUI/GUICallbacks/IMECandidate.cpp + Source/GameClient/GUI/GUICallbacks/InGameChat.cpp + Source/GameClient/GUI/GUICallbacks/InGamePopupMessage.cpp + Source/GameClient/GUI/GUICallbacks/Menus/ChallengeMenu.cpp + Source/GameClient/GUI/GUICallbacks/Menus/CreditsMenu.cpp + Source/GameClient/GUI/GUICallbacks/Menus/DifficultySelect.cpp + Source/GameClient/GUI/GUICallbacks/Menus/DisconnectWindow.cpp + Source/GameClient/GUI/GUICallbacks/Menus/DownloadMenu.cpp + Source/GameClient/GUI/GUICallbacks/Menus/EstablishConnectionsWindow.cpp + Source/GameClient/GUI/GUICallbacks/Menus/GameInfoWindow.cpp + Source/GameClient/GUI/GUICallbacks/Menus/KeyboardOptionsMenu.cpp + Source/GameClient/GUI/GUICallbacks/Menus/LanGameOptionsMenu.cpp + Source/GameClient/GUI/GUICallbacks/Menus/LanLobbyMenu.cpp + Source/GameClient/GUI/GUICallbacks/Menus/LanMapSelectMenu.cpp + Source/GameClient/GUI/GUICallbacks/Menus/MainMenu.cpp + Source/GameClient/GUI/GUICallbacks/Menus/MapSelectMenu.cpp + Source/GameClient/GUI/GUICallbacks/Menus/NetworkDirectConnect.cpp + Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp + Source/GameClient/GUI/GUICallbacks/Menus/PopupCommunicator.cpp + Source/GameClient/GUI/GUICallbacks/Menus/PopupHostGame.cpp + Source/GameClient/GUI/GUICallbacks/Menus/PopupJoinGame.cpp + Source/GameClient/GUI/GUICallbacks/Menus/PopupLadderSelect.cpp + Source/GameClient/GUI/GUICallbacks/Menus/PopupPlayerInfo.cpp + Source/GameClient/GUI/GUICallbacks/Menus/PopupReplay.cpp + Source/GameClient/GUI/GUICallbacks/Menus/PopupSaveLoad.cpp + Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp + Source/GameClient/GUI/GUICallbacks/Menus/ReplayMenu.cpp + Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp + Source/GameClient/GUI/GUICallbacks/Menus/SinglePlayerMenu.cpp + Source/GameClient/GUI/GUICallbacks/Menus/SkirmishGameOptionsMenu.cpp + Source/GameClient/GUI/GUICallbacks/Menus/SkirmishMapSelectMenu.cpp + Source/GameClient/GUI/GUICallbacks/Menus/WOLBuddyOverlay.cpp + Source/GameClient/GUI/GUICallbacks/Menus/WOLCustomScoreScreen.cpp + Source/GameClient/GUI/GUICallbacks/Menus/WOLGameSetupMenu.cpp + Source/GameClient/GUI/GUICallbacks/Menus/WOLLadderScreen.cpp + Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp + Source/GameClient/GUI/GUICallbacks/Menus/WOLLocaleSelectPopup.cpp + Source/GameClient/GUI/GUICallbacks/Menus/WOLLoginMenu.cpp + Source/GameClient/GUI/GUICallbacks/Menus/WOLMapSelectMenu.cpp + Source/GameClient/GUI/GUICallbacks/Menus/WOLMessageWindow.cpp + Source/GameClient/GUI/GUICallbacks/Menus/WOLQMScoreScreen.cpp + Source/GameClient/GUI/GUICallbacks/Menus/WOLQuickMatchMenu.cpp + Source/GameClient/GUI/GUICallbacks/Menus/WOLStatusMenu.cpp + Source/GameClient/GUI/GUICallbacks/Menus/WOLWelcomeMenu.cpp + Source/GameClient/GUI/GUICallbacks/MessageBox.cpp + Source/GameClient/GUI/GUICallbacks/ReplayControls.cpp +# Source/GameClient/GUI/HeaderTemplate.cpp +# Source/GameClient/GUI/IMEManager.cpp +# Source/GameClient/GUI/LoadScreen.cpp +# Source/GameClient/GUI/ProcessAnimateWindow.cpp + Source/GameClient/GUI/Shell/Shell.cpp + Source/GameClient/GUI/Shell/ShellMenuScheme.cpp +# Source/GameClient/GUI/WindowLayout.cpp +# Source/GameClient/GUI/WindowVideoManager.cpp +# Source/GameClient/GUI/WinInstanceData.cpp + Source/GameClient/InGameUI.cpp +# Source/GameClient/Input/Keyboard.cpp +# Source/GameClient/Input/Mouse.cpp +# Source/GameClient/LanguageFilter.cpp +# Source/GameClient/Line2D.cpp +# Source/GameClient/MapUtil.cpp + Source/GameClient/MessageStream/CommandXlat.cpp + Source/GameClient/MessageStream/GUICommandTranslator.cpp + Source/GameClient/MessageStream/HintSpy.cpp + Source/GameClient/MessageStream/HotKey.cpp + Source/GameClient/MessageStream/LookAtXlat.cpp + Source/GameClient/MessageStream/MetaEvent.cpp + Source/GameClient/MessageStream/PlaceEventTranslator.cpp + Source/GameClient/MessageStream/SelectionXlat.cpp + Source/GameClient/MessageStream/WindowXlat.cpp +# Source/GameClient/ParabolicEase.cpp +# Source/GameClient/RadiusDecal.cpp +# Source/GameClient/SelectionInfo.cpp +# Source/GameClient/Snow.cpp +# Source/GameClient/Statistics.cpp + Source/GameClient/System/Anim2D.cpp + Source/GameClient/System/CampaignManager.cpp +# "Source/GameClient/System/Debug Displayers/AudioDebugDisplay.cpp" + Source/GameClient/System/DebugDisplay.cpp + Source/GameClient/System/Image.cpp +# Source/GameClient/System/ParticleSys.cpp + Source/GameClient/System/RayEffect.cpp +# Source/GameClient/System/Smudge.cpp +# Source/GameClient/Terrain/TerrainRoads.cpp +# Source/GameClient/Terrain/TerrainVisual.cpp +# Source/GameClient/VideoPlayer.cpp +# Source/GameClient/VideoStream.cpp +# Source/GameClient/View.cpp +# Source/GameClient/Water.cpp + Source/GameLogic/AI/AI.cpp + Source/GameLogic/AI/AIDock.cpp + Source/GameLogic/AI/AIGroup.cpp + Source/GameLogic/AI/AIGuard.cpp + Source/GameLogic/AI/AIGuardRetaliate.cpp +# Source/GameLogic/AI/AIPathfind.cpp + Source/GameLogic/AI/AIPlayer.cpp + Source/GameLogic/AI/AISkirmishPlayer.cpp + Source/GameLogic/AI/AIStates.cpp + Source/GameLogic/AI/AITNGuard.cpp + Source/GameLogic/AI/Squad.cpp + Source/GameLogic/AI/TurretAI.cpp + Source/GameLogic/Map/PolygonTrigger.cpp + Source/GameLogic/Map/SidesList.cpp + Source/GameLogic/Map/TerrainLogic.cpp + Source/GameLogic/Object/Armor.cpp + Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp + Source/GameLogic/Object/Behavior/BattleBusSlowDeathBehavior.cpp + Source/GameLogic/Object/Behavior/BehaviorModule.cpp + Source/GameLogic/Object/Behavior/BridgeBehavior.cpp + Source/GameLogic/Object/Behavior/BridgeScaffoldBehavior.cpp + Source/GameLogic/Object/Behavior/BridgeTowerBehavior.cpp + Source/GameLogic/Object/Behavior/BunkerBusterBehavior.cpp + Source/GameLogic/Object/Behavior/CountermeasuresBehavior.cpp + Source/GameLogic/Object/Behavior/DumbProjectileBehavior.cpp + Source/GameLogic/Object/Behavior/FireWeaponWhenDamagedBehavior.cpp + Source/GameLogic/Object/Behavior/FireWeaponWhenDeadBehavior.cpp + Source/GameLogic/Object/Behavior/FlightDeckBehavior.cpp + Source/GameLogic/Object/Behavior/GenerateMinefieldBehavior.cpp + Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp + Source/GameLogic/Object/Behavior/InstantDeathBehavior.cpp + Source/GameLogic/Object/Behavior/JetSlowDeathBehavior.cpp + Source/GameLogic/Object/Behavior/MinefieldBehavior.cpp + Source/GameLogic/Object/Behavior/NeutonBlastBehavior.cpp + Source/GameLogic/Object/Behavior/OverchargeBehavior.cpp + Source/GameLogic/Object/Behavior/ParkingPlaceBehavior.cpp + Source/GameLogic/Object/Behavior/PoisonedBehavior.cpp + Source/GameLogic/Object/Behavior/POWTruckBehavior.cpp + Source/GameLogic/Object/Behavior/PrisonBehavior.cpp + Source/GameLogic/Object/Behavior/PropagandaCenterBehavior.cpp + Source/GameLogic/Object/Behavior/PropagandaTowerBehavior.cpp + Source/GameLogic/Object/Behavior/RebuildHoleBehavior.cpp + Source/GameLogic/Object/Behavior/SlowDeathBehavior.cpp + Source/GameLogic/Object/Behavior/SpawnBehavior.cpp + Source/GameLogic/Object/Behavior/SupplyWarehouseCripplingBehavior.cpp + Source/GameLogic/Object/Behavior/TechBuildingBehavior.cpp + Source/GameLogic/Object/Body/ActiveBody.cpp + Source/GameLogic/Object/Body/BodyModule.cpp + Source/GameLogic/Object/Body/HighlanderBody.cpp + Source/GameLogic/Object/Body/HiveStructureBody.cpp + Source/GameLogic/Object/Body/ImmortalBody.cpp + Source/GameLogic/Object/Body/InactiveBody.cpp + Source/GameLogic/Object/Body/StructureBody.cpp + Source/GameLogic/Object/Body/UndeadBody.cpp + Source/GameLogic/Object/Collide/CollideModule.cpp + Source/GameLogic/Object/Collide/CrateCollide/ConvertToCarBombCrateCollide.cpp + Source/GameLogic/Object/Collide/CrateCollide/ConvertToHijackedVehicleCrateCollide.cpp + Source/GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp + Source/GameLogic/Object/Collide/CrateCollide/HealCrateCollide.cpp + Source/GameLogic/Object/Collide/CrateCollide/MoneyCrateCollide.cpp + Source/GameLogic/Object/Collide/CrateCollide/SabotageCommandCenterCrateCollide.cpp + Source/GameLogic/Object/Collide/CrateCollide/SabotageFakeBuilding.cpp + Source/GameLogic/Object/Collide/CrateCollide/SabotageInternetCenterCrateCollide.cpp + Source/GameLogic/Object/Collide/CrateCollide/SabotageMilitaryFactoryCrateCollide.cpp + Source/GameLogic/Object/Collide/CrateCollide/SabotagePowerPlantCrateCollide.cpp + Source/GameLogic/Object/Collide/CrateCollide/SabotageSuperweaponCrateCollide.cpp + Source/GameLogic/Object/Collide/CrateCollide/SabotageSupplyCenterCrateCollide.cpp + Source/GameLogic/Object/Collide/CrateCollide/SabotageSupplyDropzoneCrateCollide.cpp + Source/GameLogic/Object/Collide/CrateCollide/SalvageCrateCollide.cpp + Source/GameLogic/Object/Collide/CrateCollide/ShroudCrateCollide.cpp + Source/GameLogic/Object/Collide/CrateCollide/UnitCrateCollide.cpp + Source/GameLogic/Object/Collide/CrateCollide/VeterancyCrateCollide.cpp + Source/GameLogic/Object/Collide/FireWeaponCollide.cpp + Source/GameLogic/Object/Collide/SquishCollide.cpp + Source/GameLogic/Object/Contain/CaveContain.cpp + Source/GameLogic/Object/Contain/GarrisonContain.cpp + Source/GameLogic/Object/Contain/HealContain.cpp + Source/GameLogic/Object/Contain/HelixContain.cpp + Source/GameLogic/Object/Contain/InternetHackContain.cpp + Source/GameLogic/Object/Contain/MobNexusContain.cpp + Source/GameLogic/Object/Contain/OpenContain.cpp + Source/GameLogic/Object/Contain/OverlordContain.cpp + Source/GameLogic/Object/Contain/ParachuteContain.cpp + Source/GameLogic/Object/Contain/RailedTransportContain.cpp + Source/GameLogic/Object/Contain/RiderChangeContain.cpp + Source/GameLogic/Object/Contain/TransportContain.cpp + Source/GameLogic/Object/Contain/TunnelContain.cpp + Source/GameLogic/Object/Create/CreateModule.cpp + Source/GameLogic/Object/Create/GrantUpgradeCreate.cpp + Source/GameLogic/Object/Create/LockWeaponCreate.cpp + Source/GameLogic/Object/Create/PreorderCreate.cpp + Source/GameLogic/Object/Create/SpecialPowerCreate.cpp + Source/GameLogic/Object/Create/SupplyCenterCreate.cpp + Source/GameLogic/Object/Create/SupplyWarehouseCreate.cpp + Source/GameLogic/Object/Create/VeterancyGainCreate.cpp + Source/GameLogic/Object/Damage/BoneFXDamage.cpp + Source/GameLogic/Object/Damage/DamageModule.cpp + Source/GameLogic/Object/Damage/TransitionDamageFX.cpp + Source/GameLogic/Object/Destroy/DestroyModule.cpp + Source/GameLogic/Object/Die/CreateCrateDie.cpp + Source/GameLogic/Object/Die/CreateObjectDie.cpp + Source/GameLogic/Object/Die/CrushDie.cpp + Source/GameLogic/Object/Die/DamDie.cpp + Source/GameLogic/Object/Die/DestroyDie.cpp + Source/GameLogic/Object/Die/DieModule.cpp + Source/GameLogic/Object/Die/EjectPilotDie.cpp + Source/GameLogic/Object/Die/FXListDie.cpp + Source/GameLogic/Object/Die/KeepObjectDie.cpp + Source/GameLogic/Object/Die/RebuildHoleExposeDie.cpp + Source/GameLogic/Object/Die/SpecialPowerCompletionDie.cpp + Source/GameLogic/Object/Die/UpgradeDie.cpp + Source/GameLogic/Object/ExperienceTracker.cpp + Source/GameLogic/Object/FiringTracker.cpp + Source/GameLogic/Object/GhostObject.cpp + Source/GameLogic/Object/Helper/ObjectDefectionHelper.cpp + Source/GameLogic/Object/Helper/ObjectHelper.cpp + Source/GameLogic/Object/Helper/ObjectRepulsorHelper.cpp + Source/GameLogic/Object/Helper/ObjectSMCHelper.cpp + Source/GameLogic/Object/Helper/ObjectWeaponStatusHelper.cpp + Source/GameLogic/Object/Helper/StatusDamageHelper.cpp + Source/GameLogic/Object/Helper/SubdualDamageHelper.cpp + Source/GameLogic/Object/Helper/TempWeaponBonusHelper.cpp + Source/GameLogic/Object/Locomotor.cpp + Source/GameLogic/Object/Object.cpp + Source/GameLogic/Object/ObjectCreationList.cpp + Source/GameLogic/Object/ObjectTypes.cpp + Source/GameLogic/Object/PartitionManager.cpp + Source/GameLogic/Object/SimpleObjectIterator.cpp + Source/GameLogic/Object/SpecialPower/BaikonurLaunchPower.cpp + Source/GameLogic/Object/SpecialPower/CashBountyPower.cpp + Source/GameLogic/Object/SpecialPower/CashHackSpecialPower.cpp + Source/GameLogic/Object/SpecialPower/CleanupAreaPower.cpp + Source/GameLogic/Object/SpecialPower/DefectorSpecialPower.cpp + Source/GameLogic/Object/SpecialPower/DemoralizeSpecialPower.cpp + Source/GameLogic/Object/SpecialPower/FireWeaponPower.cpp + Source/GameLogic/Object/SpecialPower/OCLSpecialPower.cpp + Source/GameLogic/Object/SpecialPower/SpecialAbility.cpp + Source/GameLogic/Object/SpecialPower/SpecialPowerModule.cpp + Source/GameLogic/Object/SpecialPower/SpyVisionSpecialPower.cpp + Source/GameLogic/Object/Update/AIUpdate.cpp + Source/GameLogic/Object/Update/AIUpdate/AssaultTransportAIUpdate.cpp + Source/GameLogic/Object/Update/AIUpdate/ChinookAIUpdate.cpp + Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp + Source/GameLogic/Object/Update/AIUpdate/DeployStyleAIUpdate.cpp + Source/GameLogic/Object/Update/AIUpdate/DozerAIUpdate.cpp + Source/GameLogic/Object/Update/AIUpdate/HackInternetAIUpdate.cpp + Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp + Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp + Source/GameLogic/Object/Update/AIUpdate/POWTruckAIUpdate.cpp + Source/GameLogic/Object/Update/AIUpdate/RailedTransportAIUpdate.cpp + Source/GameLogic/Object/Update/AIUpdate/RailroadGuideAIUpdate.cpp + Source/GameLogic/Object/Update/AIUpdate/SupplyTruckAIUpdate.cpp + Source/GameLogic/Object/Update/AIUpdate/TransportAIUpdate.cpp + Source/GameLogic/Object/Update/AIUpdate/WanderAIUpdate.cpp + Source/GameLogic/Object/Update/AIUpdate/WorkerAIUpdate.cpp + Source/GameLogic/Object/Update/AnimationSteeringUpdate.cpp + Source/GameLogic/Object/Update/AssistedTargetingUpdate.cpp + Source/GameLogic/Object/Update/AutoDepositUpdate.cpp + Source/GameLogic/Object/Update/AutoFindHealingUpdate.cpp + Source/GameLogic/Object/Update/BaseRenerateUpdate.cpp + Source/GameLogic/Object/Update/BattlePlanUpdate.cpp + Source/GameLogic/Object/Update/BoneFXUpdate.cpp + Source/GameLogic/Object/Update/CheckpointUpdate.cpp + Source/GameLogic/Object/Update/CleanupHazardUpdate.cpp + Source/GameLogic/Object/Update/CommandButtonHuntUpdate.cpp + Source/GameLogic/Object/Update/DeletionUpdate.cpp + Source/GameLogic/Object/Update/DemoTrapUpdate.cpp + Source/GameLogic/Object/Update/DockUpdate/DockUpdate.cpp + Source/GameLogic/Object/Update/DockUpdate/PrisonDockUpdate.cpp + Source/GameLogic/Object/Update/DockUpdate/RailedTransportDockUpdate.cpp + Source/GameLogic/Object/Update/DockUpdate/RepairDockUpdate.cpp + Source/GameLogic/Object/Update/DockUpdate/SupplyCenterDockUpdate.cpp + Source/GameLogic/Object/Update/DockUpdate/SupplyWarehouseDockUpdate.cpp + Source/GameLogic/Object/Update/DynamicGeometryInfoUpdate.cpp + Source/GameLogic/Object/Update/DynamicShroudClearingRangeUpdate.cpp + Source/GameLogic/Object/Update/EMPUpdate.cpp + Source/GameLogic/Object/Update/EnemyNearUpdate.cpp + Source/GameLogic/Object/Update/FireOCLAfterWeaponCooldownUpdate.cpp + Source/GameLogic/Object/Update/FireSpreadUpdate.cpp + Source/GameLogic/Object/Update/FirestormDynamicGeometryInfoUpdate.cpp + Source/GameLogic/Object/Update/FireWeaponUpdate.cpp + Source/GameLogic/Object/Update/FlammableUpdate.cpp + Source/GameLogic/Object/Update/FloatUpdate.cpp + Source/GameLogic/Object/Update/HeightDieUpdate.cpp + Source/GameLogic/Object/Update/HelicopterSlowDeathUpdate.cpp + Source/GameLogic/Object/Update/HijackerUpdate.cpp + Source/GameLogic/Object/Update/HordeUpdate.cpp + Source/GameLogic/Object/Update/LaserUpdate.cpp + Source/GameLogic/Object/Update/LifetimeUpdate.cpp + Source/GameLogic/Object/Update/MissileLauncherBuildingUpdate.cpp + Source/GameLogic/Object/Update/MobMemberSlavedUpdate.cpp + Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp + Source/GameLogic/Object/Update/NeutronMissileUpdate.cpp + Source/GameLogic/Object/Update/OCLUpdate.cpp + Source/GameLogic/Object/Update/ParticleUplinkCannonUpdate.cpp + Source/GameLogic/Object/Update/PhysicsUpdate.cpp + Source/GameLogic/Object/Update/PilotFindVehicleUpdate.cpp + Source/GameLogic/Object/Update/PointDefenseLaserUpdate.cpp + Source/GameLogic/Object/Update/PowerPlantUpdate.cpp + Source/GameLogic/Object/Update/ProductionExitUpdate/DefaultProductionExitUpdate.cpp + Source/GameLogic/Object/Update/ProductionExitUpdate/QueueProductionExitUpdate.cpp + Source/GameLogic/Object/Update/ProductionExitUpdate/SpawnPointProductionExitUpdate.cpp + Source/GameLogic/Object/Update/ProductionExitUpdate/SupplyCenterProductionExitUpdate.cpp + Source/GameLogic/Object/Update/ProductionUpdate.cpp + Source/GameLogic/Object/Update/ProjectileStreamUpdate.cpp + Source/GameLogic/Object/Update/ProneUpdate.cpp + Source/GameLogic/Object/Update/RadarUpdate.cpp + Source/GameLogic/Object/Update/RadiusDecalUpdate.cpp + Source/GameLogic/Object/Update/SlavedUpdate.cpp + Source/GameLogic/Object/Update/SmartBombTargetHomingUpdate.cpp + Source/GameLogic/Object/Update/SpecialAbilityUpdate.cpp + Source/GameLogic/Object/Update/SpecialPowerUpdateModule.cpp + Source/GameLogic/Object/Update/SpectreGunshipDeploymentUpdate.cpp + Source/GameLogic/Object/Update/SpectreGunshipUpdate.cpp + Source/GameLogic/Object/Update/SpyVisionUpdate.cpp + Source/GameLogic/Object/Update/StealthDetectorUpdate.cpp + Source/GameLogic/Object/Update/StealthUpdate.cpp + Source/GameLogic/Object/Update/StickyBombUpdate.cpp + Source/GameLogic/Object/Update/StructureCollapseUpdate.cpp + Source/GameLogic/Object/Update/StructureToppleUpdate.cpp + Source/GameLogic/Object/Update/TensileFormationUpdate.cpp + Source/GameLogic/Object/Update/ToppleUpdate.cpp + Source/GameLogic/Object/Update/UpdateModule.cpp + Source/GameLogic/Object/Update/WaveGuideUpdate.cpp + Source/GameLogic/Object/Update/WeaponBonusUpdate.cpp + Source/GameLogic/Object/Upgrade/ActiveShroudUpgrade.cpp + Source/GameLogic/Object/Upgrade/ArmorUpgrade.cpp + Source/GameLogic/Object/Upgrade/CommandSetUpgrade.cpp + Source/GameLogic/Object/Upgrade/CostModifierUpgrade.cpp + Source/GameLogic/Object/Upgrade/ExperienceScalarUpgrade.cpp + Source/GameLogic/Object/Upgrade/GrantScienceUpgrade.cpp + Source/GameLogic/Object/Upgrade/LocomotorSetUpgrade.cpp + Source/GameLogic/Object/Upgrade/MaxHealthUpgrade.cpp + Source/GameLogic/Object/Upgrade/ModelConditionUpgrade.cpp + Source/GameLogic/Object/Upgrade/ObjectCreationUpgrade.cpp + Source/GameLogic/Object/Upgrade/PassengersFireUpgrade.cpp + Source/GameLogic/Object/Upgrade/PowerPlantUpgrade.cpp + Source/GameLogic/Object/Upgrade/RadarUpgrade.cpp + Source/GameLogic/Object/Upgrade/ReplaceObjectUpgrade.cpp + Source/GameLogic/Object/Upgrade/StatusBitsUpgrade.cpp + Source/GameLogic/Object/Upgrade/StealthUpgrade.cpp + Source/GameLogic/Object/Upgrade/SubObjectsUpgrade.cpp + Source/GameLogic/Object/Upgrade/UnpauseSpecialPowerUpgrade.cpp + Source/GameLogic/Object/Upgrade/UpgradeModule.cpp + Source/GameLogic/Object/Upgrade/WeaponBonusUpgrade.cpp + Source/GameLogic/Object/Upgrade/WeaponSetUpgrade.cpp + Source/GameLogic/Object/Weapon.cpp + Source/GameLogic/Object/WeaponSet.cpp + Source/GameLogic/ScriptEngine/ScriptActions.cpp + Source/GameLogic/ScriptEngine/ScriptConditions.cpp + 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/GameLogic.cpp + Source/GameLogic/System/GameLogicDispatch.cpp + Source/GameLogic/System/RankInfo.cpp +# Source/GameNetwork/Connection.cpp +# Source/GameNetwork/ConnectionManager.cpp +# Source/GameNetwork/DisconnectManager.cpp +# Source/GameNetwork/DownloadManager.cpp +# Source/GameNetwork/FileTransfer.cpp +# Source/GameNetwork/FirewallHelper.cpp +# Source/GameNetwork/FrameData.cpp +# Source/GameNetwork/FrameDataManager.cpp +# Source/GameNetwork/FrameMetrics.cpp +# Source/GameNetwork/GameInfo.cpp +# Source/GameNetwork/GameMessageParser.cpp +# #Source/GameNetwork/GameSpyChat.cpp +# #Source/GameNetwork/GameSpyGameInfo.cpp +# #Source/GameNetwork/GameSpyGP.cpp +# Source/GameNetwork/GameSpy/Chat.cpp +# Source/GameNetwork/GameSpy/GSConfig.cpp +# Source/GameNetwork/GameSpy/LadderDefs.cpp +# Source/GameNetwork/GameSpy/LobbyUtils.cpp +# Source/GameNetwork/GameSpy/MainMenuUtils.cpp +# Source/GameNetwork/GameSpy/PeerDefs.cpp +# Source/GameNetwork/GameSpy/StagingRoomGameInfo.cpp +# Source/GameNetwork/GameSpy/Thread/BuddyThread.cpp +# Source/GameNetwork/GameSpy/Thread/GameResultsThread.cpp +# Source/GameNetwork/GameSpy/Thread/PeerThread.cpp +# Source/GameNetwork/GameSpy/Thread/PersistentStorageThread.cpp +# Source/GameNetwork/GameSpy/Thread/PingThread.cpp +# Source/GameNetwork/GameSpy/Thread/ThreadUtils.cpp +# Source/GameNetwork/GameSpyOverlay.cpp + Source/GameNetwork/GUIUtil.cpp +# Source/GameNetwork/IPEnumeration.cpp +# Source/GameNetwork/LANAPI.cpp +# Source/GameNetwork/LANAPICallbacks.cpp +# Source/GameNetwork/LANAPIhandlers.cpp +# Source/GameNetwork/LANGameInfo.cpp +# Source/GameNetwork/NAT.cpp +# Source/GameNetwork/NetCommandList.cpp +# Source/GameNetwork/NetCommandMsg.cpp +# Source/GameNetwork/NetCommandRef.cpp +# Source/GameNetwork/NetCommandWrapperList.cpp +# Source/GameNetwork/NetMessageStream.cpp +# Source/GameNetwork/NetPacket.cpp +# Source/GameNetwork/Network.cpp +# Source/GameNetwork/NetworkUtil.cpp +# Source/GameNetwork/Transport.cpp +# Source/GameNetwork/udp.cpp +# Source/GameNetwork/User.cpp +# Source/GameNetwork/WOLBrowser/WebBrowser.cpp + Source/GameNetwork/UDPTransport.cpp + Source/Precompiled/PreRTS.cpp + + Include/GameNetwork/GeneralsOnline/json.hpp + Include/GameNetwork/GeneralsOnline/NetworkBitstream.h + Include/GameNetwork/GeneralsOnline/NetworkMesh.h + Include/GameNetwork/GeneralsOnline/NetworkPacket.h + Include/GameNetwork/GeneralsOnline/NextGenMP_defines.h + Include/GameNetwork/GeneralsOnline/NGMPGame.h + Include/GameNetwork/GeneralsOnline/NGMP_include.h + Include/GameNetwork/GeneralsOnline/NGMP_interfaces.h + Include/GameNetwork/GeneralsOnline/NGMP_types.h + Include/GameNetwork/GeneralsOnline/OnlineServices_Auth.h + Include/GameNetwork/GeneralsOnline/OnlineServices_Init.h + Include/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.h + Include/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.h + Include/GameNetwork/GeneralsOnline/OnlineServices_StatsInterface.h + Include/GameNetwork/GeneralsOnline/OnlineServices_SocialInterface.h + Include/GameNetwork/GeneralsOnline/OnlineServices_MatchmakingInterface.h + Include/GameNetwork/GeneralsOnline/GeneralsOnline_Settings.h + Include/GameNetwork/GeneralsOnline/HTTP/HTTPManager.h + Include/GameNetwork/GeneralsOnline/HTTP/HTTPRequest.h + Include/GameNetwork/GeneralsOnline/NextGenTransport.h + Include/GameNetwork/GeneralsOnline/Vendor/ValveNetworkingSockets/steam/isteamnetworkingmessages.h + Include/GameNetwork/GeneralsOnline/Vendor/ValveNetworkingSockets/steam/isteamnetworkingsockets.h + Include/GameNetwork/GeneralsOnline/Vendor/ValveNetworkingSockets/steam/isteamnetworkingutils.h + Include/GameNetwork/GeneralsOnline/Vendor/ValveNetworkingSockets/steam/steam_api_common.h + Include/GameNetwork/GeneralsOnline/Vendor/ValveNetworkingSockets/steam/steamclientpublic.h + Include/GameNetwork/GeneralsOnline/Vendor/ValveNetworkingSockets/steam/steamnetworkingcustomsignaling.h + Include/GameNetwork/GeneralsOnline/Vendor/ValveNetworkingSockets/steam/steamnetworkingsockets.h + Include/GameNetwork/GeneralsOnline/Vendor/ValveNetworkingSockets/steam/steamnetworkingsockets_flat.h + Include/GameNetwork/GeneralsOnline/Vendor/ValveNetworkingSockets/steam/steamnetworkingtypes.h + Include/GameNetwork/GeneralsOnline/Vendor/ValveNetworkingSockets/steam/steamtypes.h + Include/GameNetwork/GeneralsOnline/Vendor/ValveNetworkingSockets/steam/steamuniverse.h + Include/GameNetwork/GeneralsOnline/Vendor/sentry/sentry.h + Include/GameNetwork/GeneralsOnline/Vendor/libcurl/curl.h + Include/GameNetwork/GeneralsOnline/Vendor/libcurl/curlver.h + Include/GameNetwork/GeneralsOnline/Vendor/libcurl/easy.h + Include/GameNetwork/GeneralsOnline/Vendor/libcurl/header.h + Include/GameNetwork/GeneralsOnline/Vendor/libcurl/mprintf.h + Include/GameNetwork/GeneralsOnline/Vendor/libcurl/multi.h + Include/GameNetwork/GeneralsOnline/Vendor/libcurl/options.h + Include/GameNetwork/GeneralsOnline/Vendor/libcurl/stdcheaders.h + Include/GameNetwork/GeneralsOnline/Vendor/libcurl/system.h + Include/GameNetwork/GeneralsOnline/Vendor/libcurl/typecheck-gcc.h + Include/GameNetwork/GeneralsOnline/Vendor/libcurl/urlapi.h + Include/GameNetwork/GeneralsOnline/Vendor/libcurl/websockets.h + Source/GameNetwork/GeneralsOnline/NGMP_Helpers.cpp + Source/GameNetwork/GeneralsOnline/NGMPGame.cpp + Source/GameNetwork/GeneralsOnline/GeneralsOnline_Settings.cpp + Source/GameNetwork/GeneralsOnline/HTTP/HTTPManager.cpp + Source/GameNetwork/GeneralsOnline/HTTP/HTTPRequest.cpp + Source/GameNetwork/GeneralsOnline/NetworkMesh.cpp + Source/GameNetwork/GeneralsOnline/OnlineServices_Init.cpp + Source/GameNetwork/GeneralsOnline/OnlineServices_Auth.cpp + Source/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.cpp + Source/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.cpp + Source/GameNetwork/GeneralsOnline/OnlineServices_StatsInterface.cpp + Source/GameNetwork/GeneralsOnline/OnlineServices_SocialInterface.cpp + Source/GameNetwork/GeneralsOnline/OnlineServices_MatchmakingInterface.cpp + Source/GameNetwork/GeneralsOnline/NextGenTransport.cpp + Source/GameNetwork/GeneralsOnline/NetworkBitstream.cpp + # Lobby voice chat + Include/GameNetwork/GeneralsOnline/Voice/VoiceCapture.h + Include/GameNetwork/GeneralsOnline/Voice/VoicePlayback.h + Include/GameNetwork/GeneralsOnline/Voice/VoiceOpusCodec.h + Include/GameNetwork/GeneralsOnline/Voice/VoiceManager.h + Include/GameNetwork/GeneralsOnline/Voice/NGMPVoiceBridge.h + Include/GameNetwork/GeneralsOnline/Voice/VoiceSlashCommands.h + Include/GameNetwork/GeneralsOnline/Voice/VoiceOptionsUI.h + Source/GameNetwork/GeneralsOnline/Voice/VoiceCapture.cpp + Source/GameNetwork/GeneralsOnline/Voice/VoicePlayback.cpp + Source/GameNetwork/GeneralsOnline/Voice/VoiceOpusCodec.cpp + Source/GameNetwork/GeneralsOnline/Voice/VoiceManager.cpp + Source/GameNetwork/GeneralsOnline/Voice/NGMPVoiceBridge.cpp + Source/GameNetwork/GeneralsOnline/Voice/VoiceSlashCommands.cpp + Source/GameNetwork/GeneralsOnline/Voice/VoiceOptionsUI.cpp +) + +if(RTS_GAMEMEMORY_ENABLE) + # Uses the original Game Memory implementation. + list(APPEND GAMEENGINE_SRC + Source/Common/System/GameMemory.cpp + Source/Common/System/MemoryInit.cpp + ) +else() + # Uses the null implementation when disabled. + list(APPEND GAMEENGINE_SRC + Source/Common/System/GameMemoryNull.cpp + Include/Common/GameMemoryNull.h + ) +endif() + + +add_library(z_gameengine STATIC) + +target_sources(z_gameengine PRIVATE ${GAMEENGINE_SRC}) + +target_include_directories(z_gameengine PUBLIC + Include + + Include/GameNetwork/GeneralsOnline/Vendor/ +) + +target_link_directories(z_gameengine PUBLIC + Source/GameNetwork/GeneralsOnline/Vendor +) + +target_include_directories(z_gameengine PRIVATE + Include/Precompiled +) + +target_link_libraries(z_gameengine PRIVATE + corei_gameengine_private + zi_always +) + +target_link_libraries(z_gameengine PUBLIC + corei_gameengine_public + z_wwvegas +) + +# -------------------------------------------------------------------- +# Voice chat support (lobby VoIP) +# +# Uses Opus for encoding + WASAPI for capture/playback. Both are always +# available on Windows: Opus comes via vcpkg, WASAPI is part of the OS. +# The ENABLE_VOICE_CHAT define gates all voice-chat code so the feature +# can be compiled out cleanly. +# -------------------------------------------------------------------- +find_package(Opus CONFIG QUIET) +if(Opus_FOUND) + target_link_libraries(z_gameengine PRIVATE Opus::opus) + target_compile_definitions(z_gameengine PUBLIC ENABLE_VOICE_CHAT) + # WASAPI / MMDevice / CoreAudio + target_link_libraries(z_gameengine PRIVATE + ole32 + winmm + avrt + ) + message(STATUS "Voice chat: ENABLED (Opus ${Opus_VERSION} + WASAPI)") +else() + message(STATUS "Voice chat: DISABLED (Opus not found via vcpkg)") +endif() + +target_precompile_headers(z_gameengine PRIVATE + [["Utility/CppMacros.h"]] # Must be first, to be removed when abandoning VC6 + Include/Precompiled/PreRTS.h +) + + +add_compile_definitions(_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR) +add_compile_definitions(GENERALS_ONLINE) \ No newline at end of file diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/GeneralsOnline_Settings.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/GeneralsOnline_Settings.h index 267cb6684bf..4a4c4b9d153 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/GeneralsOnline_Settings.h +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/GeneralsOnline_Settings.h @@ -1,5 +1,8 @@ #pragma once #include "libcurl/curl.h" +#include +#include +#include enum EHTTPVersion { @@ -61,6 +64,69 @@ class GenOnlineSettings bool Debug_VerboseLogging() const { return m_bVerbose; } + // -------- Lobby voice chat settings -------- + bool Voice_GetEnabled() const { return m_Voice_Enabled; } + const std::wstring& Voice_GetCaptureDeviceID() const { return m_Voice_CaptureDeviceID; } + float Voice_GetMicGain() const { return m_Voice_MicGain; } + float Voice_GetGlobalVolume() const { return m_Voice_GlobalVolume; } + + // -------- Persistent per-client voice ignore list -------- + // Client-local only. Never transmitted, never uploaded. When the local + // user mutes a peer via /voice mute, that peer's NGMP userID gets + // appended here and the list survives game restarts, so a troll that + // constantly leaves and re-joins the same lobby cannot bypass the mute + // just by reconnecting. + // + // Muting affects ONLY the muter's own playback: VoicePlayback drops the + // decoded audio locally. Nobody else's client knows or cares. + const std::vector& Voice_GetMutedPeers() const { return m_Voice_MutedPeers; } + + void Save_Voice_Enabled(bool enabled) + { + m_Voice_Enabled = enabled; + Save(); + } + void Save_Voice_CaptureDeviceID(const std::wstring& deviceID) + { + m_Voice_CaptureDeviceID = deviceID; + Save(); + } + void Save_Voice_MicGain(float gain) + { + if (gain < 0.0f) gain = 0.0f; + if (gain > 4.0f) gain = 4.0f; + m_Voice_MicGain = gain; + Save(); + } + void Save_Voice_GlobalVolume(float volume) + { + if (volume < 0.0f) volume = 0.0f; + if (volume > 2.0f) volume = 2.0f; + m_Voice_GlobalVolume = volume; + Save(); + } + + // Replace the full persistent mute list. Caller is expected to dedupe + // and drop invalid IDs (<=0) beforehand; we still re-validate here so + // a corrupt caller cannot poison the file. + void Save_Voice_MutedPeers(const std::vector& mutedPeers) + { + m_Voice_MutedPeers.clear(); + m_Voice_MutedPeers.reserve(mutedPeers.size()); + for (int64_t id : mutedPeers) + { + if (id <= 0) continue; + // dedupe - small N, linear scan is cheaper than a set + bool dup = false; + for (int64_t existing : m_Voice_MutedPeers) + { + if (existing == id) { dup = true; break; } + } + if (!dup) m_Voice_MutedPeers.push_back(id); + } + Save(); + } + int GetChatLifeSeconds() const { return std::max(m_Chat_LifeSeconds, 10); } void Initialize() @@ -138,4 +204,17 @@ class GenOnlineSettings EHTTPVersion m_Network_HTTPVersion = EHTTPVersion::HTTP_VERSION_AUTO; bool m_Network_UseAlternativeEndpoint = false; + + // -------- Lobby voice chat settings -------- + // Empty string = use the system default communications device. + bool m_Voice_Enabled = true; + std::wstring m_Voice_CaptureDeviceID; + float m_Voice_MicGain = 1.0f; + float m_Voice_GlobalVolume = 1.0f; + + // Persistent per-client ignore list. See comment on Voice_GetMutedPeers. + // Stored in the settings JSON as an array of DECIMAL STRINGS (not raw + // numbers) because NGMP user IDs can exceed the ~2^53 safe integer + // precision of nlohmann::json's default number handling. + std::vector m_Voice_MutedPeers; }; diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Voice/NGMPVoiceBridge.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Voice/NGMPVoiceBridge.h new file mode 100644 index 00000000000..6ca5ba33b27 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Voice/NGMPVoiceBridge.h @@ -0,0 +1,65 @@ +// NGMPVoiceBridge.h +// +// Glue between the VoiceManager (platform-agnostic audio + codec) and +// the NGMP peer mesh. Lives here so Voice/ stays free of NGMP/Steam +// includes and NGMP/ stays free of WASAPI/Opus includes. +// +// The bridge owns: +// - the global TheVoiceManager lifetime (create in Init, destroy in Shutdown) +// - the PacketSink that serialises a voice frame onto the wire with the +// VOICE_MAGIC_NUMBER prefix and broadcasts it to every peer in the mesh +// - the reverse path: given a SteamNetworkingMessage payload whose +// TransportMessageHeader magic == VOICE_MAGIC_NUMBER, strip the header +// and hand the remaining bytes to TheVoiceManager->OnVoicePacket +// - lobby -> in-game state transitions driven by the lobby lifecycle +// - the per-frame PTT poll (Left Alt via GetAsyncKeyState) +// +// Called from: +// - GameEngine::init -> NGMPVoice_Init +// - GameEngine::~GameEngine -> NGMPVoice_Shutdown +// - GameEngine::update -> NGMPVoice_Update +// - OnlineServices_LobbyInterface (Join/Create success) -> NGMPVoice_OnEnteredLobby +// - OnlineServices_LobbyInterface::LeaveCurrentLobby -> NGMPVoice_OnLeftLobby +// - NextGenTransport::doRecv (on magic == VOICE_MAGIC_NUMBER) -> +// NGMPVoice_DispatchIncoming + +#pragma once + +#include + +class NGMPVoiceBridge +{ +public: + // Called once, very early in GameEngine::init. Safe to call without + // ENABLE_VOICE_CHAT (becomes a no-op). + static void Init(); + + // Called once, in GameEngine::~GameEngine. No-op if Init wasn't called. + static void Shutdown(); + + // Called every frame from GameEngine::update. Polls the PTT key and + // performs the lobby -> in-game mode transition. + static void Update(); + + // Called by the NGMP lobby interface after it successfully joins or + // creates a lobby (i.e. after m_pLobbyMesh has been constructed). The + // bridge then asks the VoiceManager to open the mic/speakers and + // installs the broadcast sink. + static void OnEnteredLobby(int64_t myUserID); + + // Called when leaving the current lobby, before the mesh is deleted. + // Closes audio devices and clears the sink pointer. + static void OnLeftLobby(); + + // Dispatch a raw incoming network message that has already been + // identified as a voice packet by its header magic. `payload` must + // point to the bytes that follow the 6-byte TransportMessageHeader, + // and `payloadLen` must be the number of such bytes. + static void DispatchIncoming(int64_t senderUserID, + const unsigned char* payload, + int payloadLen); + + // True if the bridge currently has an active voice session (lobby + // or in-game). Used to gate UI widgets. + static bool IsActive(); +}; diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Voice/VoiceCapture.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Voice/VoiceCapture.h new file mode 100644 index 00000000000..43e78e70829 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Voice/VoiceCapture.h @@ -0,0 +1,131 @@ +// VoiceCapture.h +// +// WASAPI-based microphone capture for lobby voice chat. Runs its own +// worker thread that pulls packets from the OS capture endpoint, +// resamples/converts to 48 kHz mono PCM16 if needed, and hands +// completed 20 ms frames to a caller-supplied callback. The callback +// is invoked on the capture worker thread - callers must be +// thread-safe or marshal work back to the main thread themselves. +// +// Lifecycle: +// 1. Construct (cheap, does nothing). +// 2. Start() - opens the default capture endpoint, starts the +// worker thread, begins delivering frames. Returns false if no +// microphone is available or WASAPI initialisation fails. +// 3. SetTransmitting(true/false) - gates whether the worker actually +// calls the frame callback. When false the mic is still open but +// frames are discarded. This models push-to-talk without +// constantly stopping/restarting the capture stream. +// 4. Stop() - joins the worker and releases the endpoint. +// 5. Destruct. +// +// The capture format is requested as 48 kHz mono PCM16 shared-mode. If +// the endpoint rejects that, we fall back to the endpoint's native +// mix format and convert on the fly using Windows' automatic format +// conversion inside IAudioClient (AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM +// plus AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY). + +#pragma once + +#ifdef ENABLE_VOICE_CHAT + +#include +#include +#include +#include +#include +#include + +namespace Voice +{ + +// Callback signature: receives one 20 ms frame of 48 kHz mono PCM16 +// (960 int16 samples). The pointer is valid only for the duration of +// the call; copy if you need to keep it. +using CaptureFrameCallback = std::function; + +// Description of a capture endpoint as returned by +// VoiceCapture::EnumerateDevices. The id is the opaque WASAPI endpoint +// id string (suitable for IMMDeviceEnumerator::GetDevice); the +// friendlyName is for presenting to the user. +struct CaptureDeviceInfo +{ + std::wstring id; + std::wstring friendlyName; + bool isDefaultCommunications = false; + bool isDefaultConsole = false; +}; + +class VoiceCapture +{ +public: + VoiceCapture(); + ~VoiceCapture(); + + VoiceCapture(const VoiceCapture&) = delete; + VoiceCapture& operator=(const VoiceCapture&) = delete; + + // Opens the default capture endpoint and starts the worker thread. + // Returns false on any failure (no mic, no permission, WASAPI + // unavailable); the object is safe to destruct in that state. + bool Start(CaptureFrameCallback onFrame); + + // Stops the worker thread and releases the WASAPI objects. Safe + // to call multiple times. + void Stop(); + + // When false, captured audio is dropped on the floor instead of + // being passed to the callback. This is how push-to-talk is + // implemented: the mic stream keeps running (avoids click/pop on + // enable) but frames only reach the network path while the PTT + // key is held. + void SetTransmitting(bool transmitting); + + bool IsRunning() const { return m_workerRunning.load(); } + + // Peak level of the most recent transmitted frame, 0.0 - 1.0. + // Useful for a "mic level" indicator in the UI. + float GetCurrentLevel() const { return m_currentLevel.load(); } + + // ---------- Device selection ----------------------------------- + // Enumerate all active capture endpoints. Safe to call before or + // after Start() - uses its own temporary device enumerator so it + // doesn't touch the running capture stream. + static std::vector EnumerateDevices(); + + // Select a capture endpoint by its WASAPI id string. Pass an empty + // string to go back to the Windows default communications endpoint. + // Only takes effect on the next Start(): call Stop() then Start() + // again to apply at runtime. + void SetDeviceID(const std::wstring& id) { m_requestedDeviceID = id; } + const std::wstring& GetDeviceID() const { return m_requestedDeviceID; } + + // ---------- Mic gain ------------------------------------------- + // Linear multiplier applied to captured samples before Opus encode. + // 1.0 = unity (no change), 2.0 = +6 dB, 0.0 = mute. + // Clamped to [0.0, 4.0] internally. Thread-safe. + void SetMicGain(float gain); + float GetMicGain() const { return m_micGain.load(); } + +private: + // Runs on the worker thread. + void WorkerLoop(); + + // Opaque forward declaration so the WASAPI headers don't leak + // through to every TU that includes this file. + struct Impl; + Impl* m_impl; + + CaptureFrameCallback m_onFrame; + std::atomic m_workerRunning; + std::atomic m_workerShouldExit; + std::atomic m_transmitting; + std::atomic m_currentLevel; + std::atomic m_micGain{1.0f}; + std::wstring m_requestedDeviceID; // empty => default comms + std::thread m_workerThread; +}; + +} // namespace Voice + +#endif // ENABLE_VOICE_CHAT diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Voice/VoiceManager.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Voice/VoiceManager.h new file mode 100644 index 00000000000..486d1f8af86 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Voice/VoiceManager.h @@ -0,0 +1,198 @@ +// VoiceManager.h +// +// Orchestrator for the lobby voice chat feature. Owns the single +// capture, the single playback, the encoder, and the routing logic +// between them. +// +// Typical lifecycle from the outside: +// +// // When the player enters a Network Multiplayer lobby: +// TheVoiceManager->EnterLobby(myUserID); +// +// // Per frame or per UI tick: +// TheVoiceManager->Update(); +// +// // Match starts: switch to team-only mode and inform who is on +// // which team. +// TheVoiceManager->EnterInGameTeamMode(teamAssignments); +// +// // Match or lobby ends: +// TheVoiceManager->Leave(); +// +// The manager installs its own PTT key hook via SetPushToTalkPressed +// calls driven from the window input handlers (the key is LEFT-ALT +// held - see VoiceManager::SetPushToTalkPressed). +// +// Incoming voice packets (20 ms Opus frames) arrive via OnVoicePacket +// called from the NGMP receive path. + +#pragma once + +#ifdef ENABLE_VOICE_CHAT + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "GameNetwork/GeneralsOnline/Voice/VoiceCapture.h" // CaptureDeviceInfo + +namespace Voice +{ + +class VoiceCapture; +class VoicePlayback; +class OpusEncoderWrapper; + +enum class VoiceMode +{ + DISABLED, // Not in a lobby or match - voice subsystem idle + LOBBY_ALL, // Pre-game lobby, everyone hears everyone + IN_GAME_TEAM // In a running match, only same team +}; + +class VoiceManager +{ +public: + VoiceManager(); + ~VoiceManager(); + + VoiceManager(const VoiceManager&) = delete; + VoiceManager& operator=(const VoiceManager&) = delete; + + // Idempotent init - allocates capture/playback/encoder. Does NOT + // open audio devices yet, that happens on EnterLobby. + void Init(); + void Shutdown(); + + // Entering a Network Multiplayer pre-game lobby. Opens mic and + // speaker, starts worker threads, sets mode to LOBBY_ALL. The + // user's own ID is needed so outgoing packets can be stamped. + void EnterLobby(int64_t myUserID); + + // Match started - switch to team-only mode. teamAssignments maps + // user ID -> team number. Peers whose team differs from the local + // team are routed past the jitter buffer (muted implicitly). + void EnterInGameTeamMode(int64_t myUserID, + const std::unordered_map& teamAssignments); + + // Leaving lobby/match - close audio, clear peers, back to DISABLED. + void Leave(); + + // Call once per frame from the main UI update. Uses the poll to + // drive the speaking-indicator timers and other lightweight + // bookkeeping. + void Update(); + + // -------- Push-to-talk --------------------------------------- + // Call from the input handler when the PTT key goes down / up. + // The manager handles the state; capture is always live, only + // transmission is gated. + void SetPushToTalkPressed(bool pressed); + bool IsPushToTalkPressed() const { return m_pttPressed.load(); } + + // -------- Outgoing packet hook ------------------------------- + // Set a function that will be called with each encoded voice + // packet ready to go over the wire. The network layer sets this + // to a function that calls NetworkMesh::SendGamePacket() on each + // allowed recipient. + using PacketSink = void(*)(const uint8_t* data, int length); + void SetPacketSink(PacketSink sink) { m_packetSink = sink; } + + // -------- Incoming packet path ------------------------------- + // Called from the NGMP receive thread when a voice packet + // arrives. Parses header, decrypts team filter, pushes into + // VoicePlayback's jitter buffer. + void OnVoicePacket(int64_t senderUserID, + const uint8_t* data, int length); + + // -------- UI queries ----------------------------------------- + bool IsPeerSpeaking(int64_t peerUserID); + bool IsSelfSpeaking(); + void SetPeerMuted(int64_t peerUserID, bool muted); + bool IsPeerMuted(int64_t peerUserID); + float GetMicLevel(); + + // -------- Audio settings (forwarded to capture / playback) --- + // Enumerate capture endpoints for a Settings-screen device picker. + static std::vector EnumerateCaptureDevices(); + + // Select capture device by WASAPI id (empty string = default comms). + // Takes effect on the next EnterLobby / capture restart. + void SetCaptureDevice(const std::wstring& deviceID); + const std::wstring& GetCaptureDevice() const { return m_captureDeviceID; } + + // Linear mic input gain (0.0 .. 4.0, default 1.0). + void SetMicGain(float gain); + float GetMicGain() const { return m_micGain; } + + // Master voice volume (0.0 .. 2.0, default 1.0). Applies to the + // whole mix of everyone else's voices. + void SetGlobalVoiceVolume(float volume); + float GetGlobalVoiceVolume() const { return m_globalVoiceVolume; } + + // Per-peer voice volume (0.0 .. 2.0, default 1.0). Lets a player + // turn a loud team-mate down without muting them. + void SetPeerVolume(int64_t peerUserID, float volume); + float GetPeerVolume(int64_t peerUserID); + + // -------- Packet layout (also used by network layer) -------- + // [0] = packet tag byte (kVoicePacketTag) + // [1..8] = sender user id (little-endian int64) + // [9..10]= sequence number (little-endian uint16) + // [11..12]=encoded payload length (little-endian uint16) + // [13..] = opus payload + static constexpr uint8_t kVoicePacketTag = 0xFA; + static constexpr int kVoiceHeaderBytes = 13; + static constexpr int kMaxWirePacketSize = kVoiceHeaderBytes + 256; + + VoiceMode GetMode() const { return m_mode; } + +private: + // Called by the capture thread when a 20 ms PCM frame is ready. + // Encodes, builds the wire packet, and fans out via m_packetSink. + void OnCaptureFrame(const int16_t* pcm, int sampleCount); + + std::unique_ptr m_capture; + std::unique_ptr m_playback; + std::unique_ptr m_encoder; + std::mutex m_encoderMutex; + + std::atomic m_mode{VoiceMode::DISABLED}; + std::atomic m_myUserID{0}; + std::atomic m_outSeq{0}; + std::atomic m_pttPressed{false}; + std::atomic m_initialised{false}; + + PacketSink m_packetSink = nullptr; + + // Team assignments for in-game filtering. + std::mutex m_teamsMutex; + std::unordered_map m_teams; + int m_myTeam = -1; + + // Mute list - persists across lobby/match boundaries for the + // current session so muting once stays muted through the game. + std::unordered_set m_mutedPeers; + + // Per-peer volume overrides that should persist across + // lobby/match boundaries (so turning a peer down once sticks). + std::unordered_map m_peerVolumes; + + // Settings cached here so they survive Leave()/EnterLobby cycles + // where the capture and playback objects get re-Start()ed. + std::wstring m_captureDeviceID; + float m_micGain = 1.0f; + float m_globalVoiceVolume = 1.0f; +}; + +} // namespace Voice + +// Global instance pointer. Created in NGMP init, destroyed on shutdown. +extern Voice::VoiceManager* TheVoiceManager; + +#endif // ENABLE_VOICE_CHAT diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Voice/VoiceOptionsUI.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Voice/VoiceOptionsUI.h new file mode 100644 index 00000000000..f375f665087 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Voice/VoiceOptionsUI.h @@ -0,0 +1,47 @@ +// ---------------------------------------------------------------------------- +// VoiceOptionsUI.h +// +// GeneralsOnline voice-chat options panel. Builds its own UI controls at +// runtime on top of the existing OptionsMenu layout so no .wnd modifications +// are required — every user that updates their .exe gets the voice panel +// automatically. The module exposes four lightweight entry points that +// OptionsMenu.cpp calls at well-defined lifecycle points. +// +// All functions are safe to call when ENABLE_VOICE_CHAT is undefined — they +// become no-ops via the header guard below. +// ---------------------------------------------------------------------------- + +#pragma once + +#if defined(ENABLE_VOICE_CHAT) + +class GameWindow; + +namespace VoiceOptionsUI +{ + // Called once from OptionsMenuInit() after the vanilla init is done. + // Clones template controls (slider / combobox / checkbox) from the + // already-loaded OptionsMenu layout and populates the voice panel with + // current settings from NGMP_OnlineServicesManager::Settings. + // + // `parent` is the OptionsMenu parent window (the one that winSetModal is + // called on at the end of OptionsMenuInit). + void Build(GameWindow* parent); + + // Called from OptionsMenuShutdown() to release references. Does not + // destroy the created gadgets — they die with the WindowLayout. + void Teardown(); + + // Called from OptionsMenuSystem() before the standard GBM_SELECTED / + // GSM_SLIDER_TRACK dispatch. Returns true if the event was consumed + // by a voice-panel control, in which case OptionsMenu should stop + // processing the message. + bool TryHandleEvent(GameWindow* control, unsigned int msg, void* mData2); + + // Called from the buttonAccept branch of OptionsMenuSystem() right + // before the layout is destroyed. Writes the current voice-panel + // values into GenOnlineSettings and applies them to TheVoiceManager. + void Save(); +} + +#endif // ENABLE_VOICE_CHAT diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Voice/VoiceOpusCodec.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Voice/VoiceOpusCodec.h new file mode 100644 index 00000000000..92dd49666b1 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Voice/VoiceOpusCodec.h @@ -0,0 +1,100 @@ +// VoiceOpusCodec.h +// +// Thin RAII wrappers around libopus for voice chat. Encoder is fixed at +// 48 kHz mono, 20 ms frames (960 samples), targeting ~16 kbps which is +// plenty for speech. Decoder is symmetric and includes Opus' built-in +// packet-loss concealment so dropped frames don't produce audible gaps. +// +// These classes are deliberately minimal and own no threads or buffers; +// callers supply the PCM buffers. That keeps them trivially testable +// and lets the capture/playback side decide lifetime and ownership. + +#pragma once + +#ifdef ENABLE_VOICE_CHAT + +#include +#include + +struct OpusEncoder; +struct OpusDecoder; + +namespace Voice +{ + +// All voice frames in this system are 48 kHz mono PCM16 with 20 ms +// frames. Constants are exposed so the capture/playback modules can +// size their ring buffers without guessing. +constexpr int kSampleRate = 48000; +constexpr int kChannels = 1; +constexpr int kFrameDurationMs = 20; +constexpr int kSamplesPerFrame = (kSampleRate * kFrameDurationMs) / 1000; // 960 +constexpr int kBytesPerPcmFrame = kSamplesPerFrame * kChannels * 2; // 1920 +constexpr int kMaxEncodedBytes = 256; // safe upper bound for ~16-24 kbps Opus + +// ------------------------------------------------------------------ +// OpusEncoderWrapper +// ------------------------------------------------------------------ +class OpusEncoderWrapper +{ +public: + OpusEncoderWrapper(); + ~OpusEncoderWrapper(); + + OpusEncoderWrapper(const OpusEncoderWrapper&) = delete; + OpusEncoderWrapper& operator=(const OpusEncoderWrapper&) = delete; + + // Initialise the encoder. Returns true on success. Safe to call + // again after failure. bitrate is in bits per second; 16000-24000 + // is the sweet spot for speech. + bool Initialise(int bitrateBps = 20000); + + // Release the encoder. Idempotent. + void Shutdown(); + + // Encode exactly one frame of 960 interleaved PCM16 samples. + // Returns the number of bytes written into encodedOut, or 0 on + // failure. encodedOut must be at least kMaxEncodedBytes. + int EncodeFrame(const int16_t* pcmIn, uint8_t* encodedOut, int encodedOutCapacity); + + bool IsReady() const { return m_encoder != nullptr; } + +private: + OpusEncoder* m_encoder; +}; + +// ------------------------------------------------------------------ +// OpusDecoderWrapper +// ------------------------------------------------------------------ +class OpusDecoderWrapper +{ +public: + OpusDecoderWrapper(); + ~OpusDecoderWrapper(); + + OpusDecoderWrapper(const OpusDecoderWrapper&) = delete; + OpusDecoderWrapper& operator=(const OpusDecoderWrapper&) = delete; + + bool Initialise(); + void Shutdown(); + + // Decode one encoded packet into 960 PCM16 samples. Returns the + // number of samples produced (should always be kSamplesPerFrame) + // or 0 on failure. + int DecodeFrame(const uint8_t* encodedIn, int encodedLen, + int16_t* pcmOut, int pcmOutCapacitySamples); + + // Call when a frame is missing to let Opus synthesise a + // replacement via packet-loss concealment. pcmOut must hold + // kSamplesPerFrame samples. + int DecodeLostFrame(int16_t* pcmOut, int pcmOutCapacitySamples); + + bool IsReady() const { return m_decoder != nullptr; } + +private: + OpusDecoder* m_decoder; +}; + +} // namespace Voice + +#endif // ENABLE_VOICE_CHAT diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Voice/VoicePlayback.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Voice/VoicePlayback.h new file mode 100644 index 00000000000..8f8b1cd53d0 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Voice/VoicePlayback.h @@ -0,0 +1,103 @@ +// VoicePlayback.h +// +// WASAPI render-side voice playback. Owns one jitter buffer per sender +// (keyed by int64 user ID) and mixes their decoded audio into a single +// output stream on a dedicated worker thread. +// +// The design trades a little latency (target 60 ms of jitter buffer) +// for robustness against the kind of brief packet-order inversions +// and drops you get on real home internet connections. +// +// Per-peer state: +// - An Opus decoder +// - A ring of decoded 20 ms frames indexed by sequence number +// - A "currently playing" cursor +// - A mute flag +// - A last-activity timestamp for the speaking indicator + +#pragma once + +#ifdef ENABLE_VOICE_CHAT + +#include +#include +#include +#include +#include +#include +#include + +namespace Voice +{ + +struct PeerPlaybackState; // fwd + +class VoicePlayback +{ +public: + VoicePlayback(); + ~VoicePlayback(); + + VoicePlayback(const VoicePlayback&) = delete; + VoicePlayback& operator=(const VoicePlayback&) = delete; + + // Opens the default render endpoint and starts the worker thread. + bool Start(); + void Stop(); + + // Feed an incoming encoded Opus frame from a remote peer. This is + // safe to call from any thread (network receive thread, main + // thread, etc.). If the peer has never been seen before, its + // decoder and jitter buffer are created on the fly. + void SubmitFrame(int64_t senderUserID, uint16_t sequenceNumber, + const uint8_t* encoded, int encodedLen); + + // Drop a peer entirely: decoder released, buffer cleared. Call + // when a player leaves the lobby/match. + void RemovePeer(int64_t senderUserID); + + // Mute / unmute a specific peer. Muted peers still have their + // decoder fed (to keep Opus state coherent if they're unmuted + // later) but contribute silence to the mix. + void SetPeerMuted(int64_t senderUserID, bool muted); + bool IsPeerMuted(int64_t senderUserID); + + // Per-peer linear volume. 1.0 = unity, 0.0 = silent, 2.0 = +6 dB. + // Clamped to [0.0, 2.0]. Applied on top of the global voice volume + // in the mixer. Default is 1.0. + void SetPeerVolume(int64_t senderUserID, float volume); + float GetPeerVolume(int64_t senderUserID); + + // Global voice volume, applied to the whole mix. 1.0 = unity. + // Clamped to [0.0, 2.0]. This is the master slider for "how loud + // are other players" in the Options UI. Default is 1.0. + void SetGlobalVolume(float volume); + float GetGlobalVolume() const { return m_globalVolume.load(); } + + // Query whether a peer has sent voice recently (<400ms ago). + // Used by the "speaking" LEDs in the UI. + bool IsPeerSpeaking(int64_t senderUserID); + + // Drop every peer and clear all state. Used when leaving a + // lobby / match. + void ClearAllPeers(); + +private: + void WorkerLoop(); + void InitialisePeerLocked(int64_t senderUserID); + + struct Impl; + Impl* m_impl; + + std::atomic m_workerRunning; + std::atomic m_workerShouldExit; + std::atomic m_globalVolume{1.0f}; + std::thread m_workerThread; + + std::mutex m_peersMutex; + std::unordered_map m_peers; +}; + +} // namespace Voice + +#endif // ENABLE_VOICE_CHAT diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Voice/VoiceSlashCommands.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Voice/VoiceSlashCommands.h new file mode 100644 index 00000000000..511e17c8003 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Voice/VoiceSlashCommands.h @@ -0,0 +1,28 @@ +// VoiceSlashCommands.h +// +// Shared handler for /voice ... chat commands. Used by both the in-game +// chat and the Generals Online lobby chat so voice chat can be tuned from +// any chat input without needing a bespoke UI. +// +// The caller is expected to have already matched the "voice" token and +// just passes the remainder of the line (may be empty). + +#pragma once + +#ifdef ENABLE_VOICE_CHAT + +#include "Common/AsciiString.h" +#include "Common/UnicodeString.h" +#include + +namespace Voice +{ + // Runs a /voice sub-command. `args` is everything after "/voice " + // (may be empty - defaults to help/status). + // `out` is invoked once per line of textual feedback to display to + // the local user (not sent to other players). + void HandleVoiceSlashCommand(AsciiString args, + std::function out); +} + +#endif // ENABLE_VOICE_CHAT diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp index 2c26f935a9f..8b36c2a2f45 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp @@ -71,6 +71,10 @@ #include "GameNetwork/GameSpy/PeerDefs.h" #include "GameLogic/GameLogic.h" #include "GameLogic/ScriptEngine.h" +#if defined(ENABLE_VOICE_CHAT) +// VoiceOptionsUI removed — voice chat is now configured only via /voice slash commands. +// #include "GameNetwork/GeneralsOnline/Voice/VoiceOptionsUI.h" +#endif #include "WWDownload/Registry.h" #include "GameClient/MessageBox.h" @@ -1384,6 +1388,8 @@ void OptionsMenuInit( WindowLayout *layout, void *userData ) } + // Voice-chat options panel disabled — use /voice slash commands instead. + TheWindowManager->winSetModal(parent); ignoreSelected = FALSE; } @@ -1393,6 +1399,7 @@ void OptionsMenuInit( WindowLayout *layout, void *userData ) //------------------------------------------------------------------------------------------------- void OptionsMenuShutdown( WindowLayout *layout, void *userData ) { + // VoiceOptionsUI::Teardown() removed with /voice-only configuration. /* moved into the back button stuff if (pref) { @@ -1549,6 +1556,8 @@ WindowMsgHandledType OptionsMenuSystem( GameWindow *window, UnsignedInt msg, GameWindow *control = (GameWindow *)mData1; Int controlID = control->winGetWindowId(); + // VoiceOptionsUI event routing removed — /voice-only configuration. + if( controlID == buttonBack ) { // go back one screen @@ -1570,6 +1579,7 @@ WindowMsgHandledType OptionsMenuSystem( GameWindow *window, UnsignedInt msg, } else if (controlID == buttonAccept ) { + // VoiceOptionsUI::Save() removed — /voice slash commands persist directly. saveOptions(); if (pref) diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/GeneralsOnline_Settings.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/GeneralsOnline_Settings.cpp index 68f6756f6ba..b0de59800dd 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/GeneralsOnline_Settings.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/GeneralsOnline_Settings.cpp @@ -3,6 +3,36 @@ #include "../OnlineServices_LobbyInterface.h" #include "../OnlineServices_Init.h" +#include // MultiByteToWideChar / WideCharToMultiByte for voice device id + +namespace +{ + // UTF-8 <-> wstring helpers for JSON-serialising the capture device id. + std::string WStringToUtf8(const std::wstring& ws) + { + if (ws.empty()) return std::string(); + int needed = ::WideCharToMultiByte(CP_UTF8, 0, ws.c_str(), static_cast(ws.size()), + nullptr, 0, nullptr, nullptr); + if (needed <= 0) return std::string(); + std::string out(static_cast(needed), '\0'); + ::WideCharToMultiByte(CP_UTF8, 0, ws.c_str(), static_cast(ws.size()), + out.data(), needed, nullptr, nullptr); + return out; + } + + std::wstring Utf8ToWString(const std::string& s) + { + if (s.empty()) return std::wstring(); + int needed = ::MultiByteToWideChar(CP_UTF8, 0, s.c_str(), static_cast(s.size()), + nullptr, 0); + if (needed <= 0) return std::wstring(); + std::wstring out(static_cast(needed), L'\0'); + ::MultiByteToWideChar(CP_UTF8, 0, s.c_str(), static_cast(s.size()), + out.data(), needed); + return out; + } +} + #define SETTINGS_KEY_CAMERA "camera" #define SETTINGS_KEY_CAMERA_MIN_HEIGHT "min_height" #define SETTINGS_KEY_CAMERA_MOVE_SPEED_RATIO "move_speed_ratio" @@ -33,6 +63,13 @@ #define SETTINGS_KEY_NETWORK_HTTP_VERSION "http_version" #define SETTINGS_KEY_NETWORK_USE_ALTERNATIVE_ENDPOINT "use_alternative_endpoint" +#define SETTINGS_KEY_VOICE "voice" +#define SETTINGS_KEY_VOICE_ENABLED "enabled" +#define SETTINGS_KEY_VOICE_CAPTURE_DEVICE_ID "capture_device_id" +#define SETTINGS_KEY_VOICE_MIC_GAIN "mic_gain" +#define SETTINGS_KEY_VOICE_GLOBAL_VOLUME "global_volume" +#define SETTINGS_KEY_VOICE_MUTED_PEERS "muted_peers" + #define SETTINGS_FILENAME_LEGACY "GeneralsOnline_settings.json" #define SETTINGS_FILENAME "settings.json" @@ -181,6 +218,81 @@ void GenOnlineSettings::Load(void) } } + if (jsonSettings.contains(SETTINGS_KEY_VOICE)) + { + auto voiceSettings = jsonSettings[SETTINGS_KEY_VOICE]; + + if (voiceSettings.contains(SETTINGS_KEY_VOICE_ENABLED)) + { + m_Voice_Enabled = voiceSettings[SETTINGS_KEY_VOICE_ENABLED]; + } + + if (voiceSettings.contains(SETTINGS_KEY_VOICE_CAPTURE_DEVICE_ID)) + { + std::string idUtf8 = voiceSettings[SETTINGS_KEY_VOICE_CAPTURE_DEVICE_ID]; + m_Voice_CaptureDeviceID = Utf8ToWString(idUtf8); + } + + if (voiceSettings.contains(SETTINGS_KEY_VOICE_MIC_GAIN)) + { + m_Voice_MicGain = std::clamp( + static_cast(voiceSettings[SETTINGS_KEY_VOICE_MIC_GAIN]), + 0.0f, 4.0f); + } + + if (voiceSettings.contains(SETTINGS_KEY_VOICE_GLOBAL_VOLUME)) + { + m_Voice_GlobalVolume = std::clamp( + static_cast(voiceSettings[SETTINGS_KEY_VOICE_GLOBAL_VOLUME]), + 0.0f, 2.0f); + } + + // Persistent per-client mute list. Stored as decimal + // strings to preserve int64 precision (see header comment). + if (voiceSettings.contains(SETTINGS_KEY_VOICE_MUTED_PEERS)) + { + m_Voice_MutedPeers.clear(); + const auto& arr = voiceSettings[SETTINGS_KEY_VOICE_MUTED_PEERS]; + if (arr.is_array()) + { + for (const auto& item : arr) + { + int64_t id = 0; + try + { + if (item.is_string()) + { + id = std::stoll(item.get()); + } + else if (item.is_number_integer()) + { + id = item.get(); + } + else + { + continue; + } + } + catch (...) + { + continue; // corrupt entry, skip silently + } + + if (id <= 0) continue; + + // Dedupe on load so a hand-edited file cannot + // blow up memory with repeats. + bool dup = false; + for (int64_t existing : m_Voice_MutedPeers) + { + if (existing == id) { dup = true; break; } + } + if (!dup) m_Voice_MutedPeers.push_back(id); + } + } + } + } + if (jsonSettings.contains(SETTINGS_KEY_DEBUG)) { auto debugSettings = jsonSettings[SETTINGS_KEY_DEBUG]; @@ -321,6 +433,16 @@ void GenOnlineSettings::Save() } }, + { + SETTINGS_KEY_VOICE, + { + {SETTINGS_KEY_VOICE_ENABLED, m_Voice_Enabled}, + {SETTINGS_KEY_VOICE_CAPTURE_DEVICE_ID, WStringToUtf8(m_Voice_CaptureDeviceID)}, + {SETTINGS_KEY_VOICE_MIC_GAIN, m_Voice_MicGain}, + {SETTINGS_KEY_VOICE_GLOBAL_VOLUME, m_Voice_GlobalVolume} + } + }, + { SETTINGS_KEY_SOCIAL, { @@ -335,7 +457,21 @@ void GenOnlineSettings::Save() } }, }; - + + // Persistent voice ignore list. Stored as an array of decimal strings + // to preserve full int64 precision (nlohmann::json uses double for raw + // numbers, which truncates NGMP user IDs above ~2^53). Built after the + // root initializer to avoid init-list template ambiguities. + { + nlohmann::json mutedArr = nlohmann::json::array(); + for (int64_t id : m_Voice_MutedPeers) + { + if (id <= 0) continue; + mutedArr.push_back(std::to_string(id)); + } + root[SETTINGS_KEY_VOICE][SETTINGS_KEY_VOICE_MUTED_PEERS] = mutedArr; + } + std::string strData = root.dump(1); std::string strSettingsFilePath = std::format("{}/GeneralsOnlineData/{}", TheGlobalData->getPath_UserData().str(), SETTINGS_FILENAME); diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/NextGenTransport.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/NextGenTransport.cpp index e8743a4da3d..86c7674248d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/NextGenTransport.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/NextGenTransport.cpp @@ -7,6 +7,9 @@ #include "GameNetwork/GeneralsOnline/ngmp_include.h" #include "GameNetwork/GeneralsOnline/ngmp_interfaces.h" +// GENERALS ONLINE - Voice Chat +#include "GameNetwork/GeneralsOnline/Voice/NGMPVoiceBridge.h" + #ifdef _INTERNAL // for occasional debugging... //#pragma optimize("", off) @@ -183,6 +186,24 @@ Bool NextGenTransport::doRecv(void) } #endif + // GENERALS ONLINE - Voice Chat + // Voice packets reuse the same 6-byte TransportMessageHeader + // prefix but stamp a different magic number so we can + // dispatch them without going through the generals CRC path. + if (incomingMessage.header.magic == VOICE_MAGIC_NUMBER) + { + if (payloadLen > 0) + { + NGMPVoiceBridge::DispatchIncoming( + kvPair.second.m_userID, + incomingMessage.data, + static_cast(payloadLen)); + } + m_incomingPackets[m_statisticsSlot]++; + m_incomingBytes[m_statisticsSlot] += numBytes; + continue; + } + const bool isGenerals = isGeneralsPacket(&incomingMessage); if (!isGenerals) diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.cpp index faa825d863f..e74c6909a7d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.cpp @@ -2,6 +2,10 @@ #include "GameNetwork/GeneralsOnline/json.hpp" #include "GameNetwork/GeneralsOnline/HTTP/HTTPManager.h" #include "GameNetwork/GeneralsOnline/OnlineServices_Init.h" +#include "GameNetwork/GeneralsOnline/Voice/NGMPVoiceBridge.h" // GENERALS ONLINE - Voice Chat +#ifdef ENABLE_VOICE_CHAT +#include "GameNetwork/GeneralsOnline/Voice/VoiceSlashCommands.h" +#endif #include "GameClient/MapUtil.h" #include "GameLogic/GameLogic.h" @@ -470,6 +474,43 @@ void NGMP_OnlineServices_LobbyInterface::UpdateCurrentLobby_ForceReady() void NGMP_OnlineServices_LobbyInterface::SendChatMessageToCurrentLobby(UnicodeString& strChatMsgUnicode, bool bIsAction) { +#ifdef ENABLE_VOICE_CHAT + // ------------------------------------------------------------------ + // Intercept "/voice ..." locally so voice chat can be tuned from the + // lobby chat box without touching any vanilla chat-input files. The + // command never leaves the client - it is consumed here and the + // output is pushed into the same chat listbox via the existing + // m_OnChatCallback pipeline. + // ------------------------------------------------------------------ + if (!bIsAction && strChatMsgUnicode.getLength() >= 6) + { + AsciiString asciiMsg; + asciiMsg.translate(strChatMsgUnicode); + if (asciiMsg.getCharAt(0) == '/') + { + AsciiString rest = asciiMsg.str() + 1; + AsciiString token; + rest.nextToken(&token); + token.toLower(); + if (token == "voice") + { + // Same yellow as existing GenOnline lobby notices (GameMakeColor(255,194,15,255)) + // Color is ARGB packed int: (a<<24)|(r<<16)|(g<<8)|b. + const Color voiceColor = static_cast(0xFFFFC20F); + Voice::HandleVoiceSlashCommand(rest, + [this, voiceColor](const UnicodeString& line) + { + if (m_OnChatCallback != nullptr) + { + m_OnChatCallback(line, voiceColor); + } + }); + return; // fully handled locally, do NOT forward to server + } + } + } +#endif + std::shared_ptr pWS = NGMP_OnlineServicesManager::GetWebSocket();; if (pWS != nullptr) { @@ -551,6 +592,17 @@ void NGMP_OnlineServices_LobbyInterface::SearchForLobbies(std::function lobbyEntryIter["IsTrackingStats"].get_to(lobbyEntry.track_stats); lobbyEntryIter["IsPassworded"].get_to(lobbyEntry.passworded); lobbyEntryIter["AllowObservers"].get_to(lobbyEntry.allow_observers); +#if defined(ENABLE_VOICE_CHAT) + // NGMP: Voice flag is optional for backwards compat; default TRUE. + if (lobbyEntryIter.contains("VoiceEnabled")) + { + lobbyEntryIter["VoiceEnabled"].get_to(lobbyEntry.voice_enabled); + } + else + { + lobbyEntry.voice_enabled = true; + } +#endif lobbyEntryIter["MaximumCameraHeight"].get_to(lobbyEntry.max_cam_height); lobbyEntryIter["ExeCRC"].get_to(lobbyEntry.exe_crc); lobbyEntryIter["IniCRC"].get_to(lobbyEntry.ini_crc); @@ -816,6 +868,17 @@ void NGMP_OnlineServices_LobbyInterface::UpdateRoomDataCache(std::functionGetAndParseServiceConfig([=]() { @@ -1295,6 +1362,11 @@ void NGMP_OnlineServices_LobbyInterface::CreateLobby(UnicodeString strLobbyName, j["passworded"] = bPassworded; j["password"] = strPassword; j["allow_observers"] = bAllowObservers; +#if defined(ENABLE_VOICE_CHAT) + // NGMP: voice chat enabled flag. The server currently ignores + // unknown fields, so this is forward-compatible with older backends. + j["voice_enabled"] = bVoiceEnabled; +#endif j["exe_crc"] = TheGlobalData->m_exeCRC; j["ini_crc"] = TheGlobalData->m_iniCRC; j["max_cam_height"] = NGMP_OnlineServicesManager::Settings.Camera_GetMaxHeight_WhenLobbyHost(); @@ -1413,6 +1485,17 @@ void NGMP_OnlineServices_LobbyInterface::OnJoinedOrCreatedLobby(bool bAlreadyUpd m_pLobbyMesh = new NetworkMesh(); } + // GENERALS ONLINE - Voice Chat: open mic/speaker and hook the sink now + // that we have a live mesh to broadcast over. + { + NGMP_OnlineServices_AuthInterface* pAuthInterface = + NGMP_OnlineServicesManager::GetInterface(); + if (pAuthInterface != nullptr) + { + NGMPVoiceBridge::OnEnteredLobby(pAuthInterface->GetUserID()); + } + } + m_bMarkedGameAsFinished = false; // reset timer diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/Voice/NGMPVoiceBridge.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/Voice/NGMPVoiceBridge.cpp new file mode 100644 index 00000000000..7a7d4362a19 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/Voice/NGMPVoiceBridge.cpp @@ -0,0 +1,449 @@ +// NGMPVoiceBridge.cpp +// +// See NGMPVoiceBridge.h for the role of this file. + +#include "PreRTS.h" + +#include "GameNetwork/GeneralsOnline/Voice/NGMPVoiceBridge.h" + +#ifdef ENABLE_VOICE_CHAT + +#include "GameNetwork/GeneralsOnline/Voice/VoiceManager.h" + +#include "GameNetwork/NetworkDefs.h" // TransportMessageHeader, VOICE_MAGIC_NUMBER +#include "GameNetwork/GeneralsOnline/NetworkMesh.h" // NetworkMesh, PlayerConnection +#include "GameNetwork/GeneralsOnline/ngmp_include.h" +#include "GameNetwork/GeneralsOnline/ngmp_interfaces.h" +#include "GameNetwork/GeneralsOnline/NGMPGame.h" +#include "GameNetwork/GeneralsOnline/OnlineServices_Auth.h" +#include "GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.h" // extern TheNGMPGame +#include "GameNetwork/GeneralsOnline/GeneralsOnline_Settings.h" + +#include "GameLogic/GameLogic.h" // TheGameLogic +#include "GameClient/InGameUI.h" // TheInGameUI->message for in-game notifications + +#include // GetAsyncKeyState, VK_LMENU +#include +#include +#include + +// --------------------------------------------------------------------- +// DEV ONLY: Force-start voice without a real lobby. +// +// When VOICE_DEV_FORCE_START is defined, NGMPVoiceBridge::Init will +// immediately call VoiceManager::EnterLobby with a dummy user id. This +// bypasses the normal "only active inside a Generals Online lobby" +// lifecycle and lets you test voice chat from the main menu or a +// skirmish match. Combine with VOICE_DEV_LOCAL_LOOPBACK in VoiceManager +// to hear your own mic with no network path involved. +// +// Do not define this in shipping builds. +// --------------------------------------------------------------------- +// #define VOICE_DEV_FORCE_START 1 + +#ifdef VOICE_DEV_FORCE_START +static constexpr int64_t kForceStartDummyUserID = 0x10A9BACC10A9BACDll; +#endif + +//--------------------------------------------------------------------- +// Static state +//--------------------------------------------------------------------- +namespace +{ + bool s_bridgeInitialised = false; + + // Last session state observed, used to detect lobby->in-game edge. + enum class SessionState + { + Idle, + Lobby, + InGame + }; + SessionState s_session = SessionState::Idle; + + int64_t s_myUserID = 0; + + // Previous PTT key state so we only call SetPushToTalkPressed on edges. + bool s_pttPrev = false; + + //----------------------------------------------------------------- + // Discord-style speaker notifier state + // + // Tracks per-peer speaking state across frames so we can fire a + // transition event (silent -> speaking) and show + // [MIC] PlayerName + // in the lobby chat or in-game HUD. We also throttle re-notifications + // so a peer that stops+restarts quickly doesn't spam the log; a + // minimum quiet gap is required before we'll announce the same peer + // again. + //----------------------------------------------------------------- + struct SpeakerState + { + bool speaking = false; + int64_t lastSpokeMs = 0; // last time speaking was observed + }; + std::unordered_map s_speakerStates; + + // Re-notify threshold: if a peer has been silent for this long and + // starts talking again, we will re-announce. + constexpr int64_t kRenotifyGapMs = 4000; + + int64_t NowMs() + { + using namespace std::chrono; + return duration_cast( + steady_clock::now().time_since_epoch()).count(); + } + + // Narrow std::string (display_name is ASCII/UTF-8) -> UnicodeString + UnicodeString NarrowToUni(const std::string& narrow) + { + std::wstring w; + w.reserve(narrow.size()); + for (char c : narrow) + w.push_back(static_cast(static_cast(c))); + return UnicodeString(w.c_str()); + } + + // Emit a " started speaking" notification through whichever + // UI sink is appropriate right now: + // - In-match -> TheInGameUI->message (HUD toast) + // - In-lobby -> lobby chat callback (yellow line in chat box) + void EmitSpeakerNotification(const std::string& displayName, int64_t userID) + { + UnicodeString line; + if (!displayName.empty()) + { + line.format(L"[MIC] %ls", NarrowToUni(displayName).str()); + } + else + { + line.format(L"[MIC] player %lld", (long long)userID); + } + + // In-game HUD message (auto-fades, doesn't stack up). + if (TheGameLogic != nullptr && TheGameLogic->isInGame() && TheInGameUI != nullptr) + { + TheInGameUI->message(line); + return; + } + + // Lobby chat box (reuses the already-registered chat callback). + NGMP_OnlineServices_LobbyInterface* pLobby = + NGMP_OnlineServicesManager::GetInterface(); + if (pLobby != nullptr && pLobby->m_OnChatCallback != nullptr) + { + const Color micColor = static_cast(0xFFFFC20F); // yellow + pLobby->m_OnChatCallback(line, micColor); + } + } + + // Called every frame from NGMPVoiceBridge::Update when voice is active. + // Walks current remote peers, compares their speaking state to what we + // saw last frame, and fires a notification on silent->speaking edges. + void PollSpeakerTransitions() + { + if (TheVoiceManager == nullptr) return; + + NetworkMesh* pMesh = NGMP_OnlineServicesManager::GetNetworkMesh(); + if (pMesh == nullptr) + { + // No mesh means we're not in a lobby/match -> reset state so + // stale entries don't carry across sessions. + s_speakerStates.clear(); + return; + } + + NGMP_OnlineServices_LobbyInterface* pLobby = + NGMP_OnlineServicesManager::GetInterface(); + + const int64_t now = NowMs(); + + for (auto& kv : pMesh->GetAllConnections()) + { + const int64_t peerID = kv.first; + if (peerID == s_myUserID) continue; // don't announce ourselves + + const bool nowSpeaking = TheVoiceManager->IsPeerSpeaking(peerID); + SpeakerState& st = s_speakerStates[peerID]; + + if (nowSpeaking) + { + // Fire only on transition, or re-fire if they had a long + // enough silent gap since the last notification. + const bool wasSilent = !st.speaking; + const bool longGap = (now - st.lastSpokeMs) > kRenotifyGapMs; + if (wasSilent && longGap) + { + std::string name; + if (pLobby != nullptr) + { + LobbyMemberEntry e = pLobby->GetRoomMemberFromID(peerID); + if (e.user_id != -1) name = e.display_name; + } + EmitSpeakerNotification(name, peerID); + } + st.speaking = true; + st.lastSpokeMs = now; + } + else + { + st.speaking = false; + // keep lastSpokeMs so the gap logic above still works + } + } + } + + //----------------------------------------------------------------- + // PacketSink - called from the VoiceManager capture thread. + // + // Must have the C-style signature (no capture). We use the globals + // above to find the currently active mesh and broadcast. + //----------------------------------------------------------------- + void VoicePacketSink(const uint8_t* voiceData, int voiceLen) + { + if (voiceData == nullptr || voiceLen <= 0) return; + + NetworkMesh* pMesh = NGMP_OnlineServicesManager::GetNetworkMesh(); + if (pMesh == nullptr) return; + + // Build wire buffer: [TransportMessageHeader][voice payload] + // crc is unused for voice packets (set to 0), magic identifies them. + const int totalLen = + static_cast(sizeof(TransportMessageHeader)) + voiceLen; + + // kMaxWirePacketSize (256 + 13) + 6 header = ~275 bytes, safe on stack. + unsigned char wire[sizeof(TransportMessageHeader) + + Voice::VoiceManager::kMaxWirePacketSize]; + if (totalLen > static_cast(sizeof(wire))) + { + return; // oversized packet, drop + } + + TransportMessageHeader hdr; + hdr.crc = 0; + hdr.magic = VOICE_MAGIC_NUMBER; + std::memcpy(wire, &hdr, sizeof(TransportMessageHeader)); + std::memcpy(wire + sizeof(TransportMessageHeader), + voiceData, + static_cast(voiceLen)); + + // Broadcast to every connected peer in the mesh. + std::map& conns = pMesh->GetAllConnections(); + for (auto& kv : conns) + { + pMesh->SendGamePacket(wire, + static_cast(totalLen), + kv.first); + } + } + + //----------------------------------------------------------------- + // Build team map from TheNGMPGame and push it into the VoiceManager. + //----------------------------------------------------------------- + void EnterInGameTeamModeFromCurrentGame() + { + if (TheVoiceManager == nullptr) return; + if (TheNGMPGame == nullptr) return; + + std::unordered_map teamMap; + + for (Int i = 0; i < MAX_SLOTS; ++i) + { + GameSlot* pSlot = TheNGMPGame->getSlot(i); + if (pSlot == nullptr) continue; + if (!pSlot->isHuman()) continue; + + NGMPGameSlot* pNGMPSlot = static_cast(pSlot); + if (pNGMPSlot->m_userID == -1) continue; + + teamMap[pNGMPSlot->m_userID] = pSlot->getTeamNumber(); + } + + TheVoiceManager->EnterInGameTeamMode(s_myUserID, teamMap); + } +} // anonymous namespace + +//===================================================================== +// Public API +//===================================================================== + +void NGMPVoiceBridge::Init() +{ + if (s_bridgeInitialised) return; + + if (TheVoiceManager == nullptr) + { + TheVoiceManager = new Voice::VoiceManager(); + } + TheVoiceManager->Init(); + TheVoiceManager->SetPacketSink(&VoicePacketSink); + + // Apply persisted voice settings (mic device id, gain, master volume). + // These survive Leave/EnterLobby cycles because VoiceManager caches + // them internally and re-applies them on EnterLobby. + { + GenOnlineSettings& s = NGMP_OnlineServicesManager::Settings; + TheVoiceManager->SetCaptureDevice(s.Voice_GetCaptureDeviceID()); + TheVoiceManager->SetMicGain(s.Voice_GetMicGain()); + TheVoiceManager->SetGlobalVoiceVolume(s.Voice_GetGlobalVolume()); + } + + s_session = SessionState::Idle; + s_myUserID = 0; + s_pttPrev = false; + s_bridgeInitialised = true; + +#ifdef VOICE_DEV_FORCE_START + // Dev-only: jump straight into LOBBY_ALL with a dummy user id so + // PTT works from the main menu. No real lobby is required. + s_myUserID = kForceStartDummyUserID; + s_session = SessionState::Lobby; + TheVoiceManager->EnterLobby(kForceStartDummyUserID); +#endif +} + +void NGMPVoiceBridge::Shutdown() +{ + if (!s_bridgeInitialised) return; + + if (TheVoiceManager != nullptr) + { + TheVoiceManager->SetPacketSink(nullptr); + TheVoiceManager->Shutdown(); + delete TheVoiceManager; + TheVoiceManager = nullptr; + } + + s_session = SessionState::Idle; + s_myUserID = 0; + s_pttPrev = false; + s_bridgeInitialised = false; +} + +void NGMPVoiceBridge::OnEnteredLobby(int64_t myUserID) +{ + if (!s_bridgeInitialised || TheVoiceManager == nullptr) return; + + // NGMP: Honour the host-set voice_enabled flag on the current lobby. + // If the host disabled voice for this lobby we do NOT start the + // VoiceManager session at all: PTT does nothing, no mic is opened, + // and no inbound audio is decoded. This is the hard-enforcement path. + { + NGMP_OnlineServices_LobbyInterface* pLobbyInterface = + NGMP_OnlineServicesManager::GetInterface(); + if (pLobbyInterface != nullptr) + { + const LobbyEntry& lobby = pLobbyInterface->GetCurrentLobby(); + if (!lobby.voice_enabled) + { + s_myUserID = myUserID; + s_session = SessionState::Idle; // voice is OFF for this lobby + return; + } + } + } + + s_myUserID = myUserID; + s_session = SessionState::Lobby; + TheVoiceManager->EnterLobby(myUserID); +} + +void NGMPVoiceBridge::OnLeftLobby() +{ + if (!s_bridgeInitialised || TheVoiceManager == nullptr) return; + + s_session = SessionState::Idle; + s_myUserID = 0; + TheVoiceManager->Leave(); +} + +void NGMPVoiceBridge::Update() +{ + if (!s_bridgeInitialised || TheVoiceManager == nullptr) return; + + // ---- PTT poll (Left Alt held) ------------------------------------ + // GetAsyncKeyState is window-focus independent and does not interact + // with the game's key-consumption pipeline, so PTT won't be eaten + // by a text field etc. + const bool pttNow = + (s_session != SessionState::Idle) && + ((::GetAsyncKeyState(VK_LMENU) & 0x8000) != 0); + + if (pttNow != s_pttPrev) + { + TheVoiceManager->SetPushToTalkPressed(pttNow); + s_pttPrev = pttNow; + } + + // ---- Lobby -> in-game edge detection ----------------------------- + // The same NGMP mesh is reused for in-game, so we just need to flip + // VoiceManager into team-only mode when the match actually starts. + if (s_session == SessionState::Lobby) + { + if (TheNGMPGame != nullptr && + TheGameLogic != nullptr && + TheGameLogic->isInGame()) + { + EnterInGameTeamModeFromCurrentGame(); + s_session = SessionState::InGame; + } + } + else if (s_session == SessionState::InGame) + { + // If the game ended and we're back in a lobby, drop back to + // LOBBY_ALL so everyone in the post-game lobby can talk again. + if (TheGameLogic == nullptr || !TheGameLogic->isInGame()) + { + if (TheNGMPGame != nullptr) + { + TheVoiceManager->EnterLobby(s_myUserID); + s_session = SessionState::Lobby; + } + else + { + TheVoiceManager->Leave(); + s_session = SessionState::Idle; + } + } + } + + TheVoiceManager->Update(); + + // Discord-style "who's talking" indicator: check peers each frame and + // surface a [MIC] name notification whenever a peer starts speaking. + // Pure GenOnline logic - uses TheInGameUI->message and the existing + // lobby chat callback, no vanilla UI file touched. + if (s_session != SessionState::Idle) + { + PollSpeakerTransitions(); + } +} + +void NGMPVoiceBridge::DispatchIncoming(int64_t senderUserID, + const unsigned char* payload, + int payloadLen) +{ + if (!s_bridgeInitialised || TheVoiceManager == nullptr) return; + if (payload == nullptr || payloadLen <= 0) return; + + TheVoiceManager->OnVoicePacket(senderUserID, payload, payloadLen); +} + +bool NGMPVoiceBridge::IsActive() +{ + return s_bridgeInitialised && s_session != SessionState::Idle; +} + +#else // !ENABLE_VOICE_CHAT + +// Stubs so call sites compile without Opus/WASAPI. +void NGMPVoiceBridge::Init() {} +void NGMPVoiceBridge::Shutdown() {} +void NGMPVoiceBridge::Update() {} +void NGMPVoiceBridge::OnEnteredLobby(int64_t) {} +void NGMPVoiceBridge::OnLeftLobby() {} +void NGMPVoiceBridge::DispatchIncoming(int64_t, const unsigned char*, int) {} +bool NGMPVoiceBridge::IsActive() { return false; } + +#endif // ENABLE_VOICE_CHAT diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/Voice/VoiceCapture.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/Voice/VoiceCapture.cpp new file mode 100644 index 00000000000..aa4c1ccd12f --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/Voice/VoiceCapture.cpp @@ -0,0 +1,579 @@ +// VoiceCapture.cpp + +#include "PreRTS.h" + +#ifdef ENABLE_VOICE_CHAT + +#include "GameNetwork/GeneralsOnline/Voice/VoiceCapture.h" +#include "GameNetwork/GeneralsOnline/Voice/VoiceOpusCodec.h" + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace Voice +{ + +// -------------------------------------------------------------------- +// Internal state. Kept in a pimpl so the WASAPI headers stay out of +// the public header file. +// -------------------------------------------------------------------- +struct VoiceCapture::Impl +{ + IMMDeviceEnumerator* deviceEnum = nullptr; + IMMDevice* device = nullptr; + IAudioClient* audioClient = nullptr; + IAudioCaptureClient* captureClient = nullptr; + WAVEFORMATEX* mixFormat = nullptr; + HANDLE bufferReady = nullptr; + bool comInitialised = false; + + // Endpoint may not deliver exactly 48 kHz mono - we always convert + // on the fly in the worker loop. These cache the key numbers. + int endpointSampleRate = 0; + int endpointChannels = 0; + int endpointBitsPerSample = 0; + bool endpointIsFloat = false; + + // Accumulator for building up 20 ms output frames. + std::vector pendingSamples; +}; + +// -------------------------------------------------------------------- +// Tiny helpers for releasing COM interfaces without dragging in ATL. +// -------------------------------------------------------------------- +template +static void SafeRelease(T** pp) +{ + if (*pp != nullptr) + { + (*pp)->Release(); + *pp = nullptr; + } +} + +// -------------------------------------------------------------------- +// Construction / destruction +// -------------------------------------------------------------------- +VoiceCapture::VoiceCapture() + : m_impl(new Impl) + , m_workerRunning(false) + , m_workerShouldExit(false) + , m_transmitting(false) + , m_currentLevel(0.0f) +{ +} + +VoiceCapture::~VoiceCapture() +{ + Stop(); + delete m_impl; + m_impl = nullptr; +} + +// -------------------------------------------------------------------- +// Start: open WASAPI capture and spin up the worker thread. +// -------------------------------------------------------------------- +bool VoiceCapture::Start(CaptureFrameCallback onFrame) +{ + if (m_workerRunning.load()) + { + return true; + } + + m_onFrame = std::move(onFrame); + + // COM init - OK to do this multiple times, Windows reference- + // counts per thread. The main thread is usually STA because of + // DirectInput/DirectShow initialisation. + HRESULT hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + if (hr == RPC_E_CHANGED_MODE) + { + hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); + } + m_impl->comInitialised = SUCCEEDED(hr); + + // Grab the default capture endpoint (default communications role + // to pick up headset mics). + hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, + CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), + reinterpret_cast(&m_impl->deviceEnum)); + if (FAILED(hr)) + { + Stop(); + return false; + } + + // User-picked device takes precedence; otherwise the Windows default + // communications endpoint (headset mic); otherwise console default. + if (!m_requestedDeviceID.empty()) + { + hr = m_impl->deviceEnum->GetDevice(m_requestedDeviceID.c_str(), + &m_impl->device); + if (FAILED(hr) || m_impl->device == nullptr) + { + // Explicit pick is gone (unplugged?) - fall through to default. + m_impl->device = nullptr; + } + } + if (m_impl->device == nullptr) + { + hr = m_impl->deviceEnum->GetDefaultAudioEndpoint(eCapture, + eCommunications, &m_impl->device); + } + if (FAILED(hr) || m_impl->device == nullptr) + { + // Fall back to console endpoint if there's no comms default. + hr = m_impl->deviceEnum->GetDefaultAudioEndpoint(eCapture, + eConsole, &m_impl->device); + if (FAILED(hr) || m_impl->device == nullptr) + { + Stop(); + return false; + } + } + + hr = m_impl->device->Activate(__uuidof(IAudioClient), CLSCTX_ALL, + nullptr, + reinterpret_cast(&m_impl->audioClient)); + if (FAILED(hr)) + { + Stop(); + return false; + } + + // Use the endpoint's native mix format. Trying to force 48 kHz + // mono PCM16 with AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM works on most + // endpoints but some drivers reject it, so we do the conversion + // ourselves in the worker loop. + hr = m_impl->audioClient->GetMixFormat(&m_impl->mixFormat); + if (FAILED(hr) || m_impl->mixFormat == nullptr) + { + Stop(); + return false; + } + + m_impl->endpointSampleRate = m_impl->mixFormat->nSamplesPerSec; + m_impl->endpointChannels = m_impl->mixFormat->nChannels; + m_impl->endpointBitsPerSample = m_impl->mixFormat->wBitsPerSample; + + // Detect float vs PCM. WAVE_FORMAT_EXTENSIBLE is the common case + // on Windows 10/11 and requires peeking at the sub-format GUID. + m_impl->endpointIsFloat = false; + if (m_impl->mixFormat->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) + { + m_impl->endpointIsFloat = true; + } + else if (m_impl->mixFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE) + { + auto* ext = reinterpret_cast(m_impl->mixFormat); + if (ext->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT) + { + m_impl->endpointIsFloat = true; + } + } + + // 60 ms buffer - gives the scheduler enough slack without adding + // noticeable latency. + const REFERENCE_TIME kBufferDuration = 60 * 10000; // 60 ms in hns + hr = m_impl->audioClient->Initialize( + AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + kBufferDuration, 0, + m_impl->mixFormat, nullptr); + if (FAILED(hr)) + { + Stop(); + return false; + } + + m_impl->bufferReady = CreateEventW(nullptr, FALSE, FALSE, nullptr); + if (m_impl->bufferReady == nullptr) + { + Stop(); + return false; + } + hr = m_impl->audioClient->SetEventHandle(m_impl->bufferReady); + if (FAILED(hr)) + { + Stop(); + return false; + } + + hr = m_impl->audioClient->GetService(__uuidof(IAudioCaptureClient), + reinterpret_cast(&m_impl->captureClient)); + if (FAILED(hr)) + { + Stop(); + return false; + } + + hr = m_impl->audioClient->Start(); + if (FAILED(hr)) + { + Stop(); + return false; + } + + m_impl->pendingSamples.clear(); + m_impl->pendingSamples.reserve(kSamplesPerFrame * 2); + + m_workerShouldExit.store(false); + m_workerRunning.store(true); + m_workerThread = std::thread(&VoiceCapture::WorkerLoop, this); + return true; +} + +// -------------------------------------------------------------------- +// Stop: tear down capture and join the worker. +// -------------------------------------------------------------------- +void VoiceCapture::Stop() +{ + if (m_workerRunning.load()) + { + m_workerShouldExit.store(true); + if (m_impl->bufferReady != nullptr) + { + // Nudge the worker so it doesn't wait out the whole + // WaitForSingleObject timeout. + SetEvent(m_impl->bufferReady); + } + if (m_workerThread.joinable()) + { + m_workerThread.join(); + } + m_workerRunning.store(false); + } + + if (m_impl->audioClient != nullptr) + { + m_impl->audioClient->Stop(); + } + SafeRelease(&m_impl->captureClient); + SafeRelease(&m_impl->audioClient); + SafeRelease(&m_impl->device); + SafeRelease(&m_impl->deviceEnum); + + if (m_impl->mixFormat != nullptr) + { + CoTaskMemFree(m_impl->mixFormat); + m_impl->mixFormat = nullptr; + } + if (m_impl->bufferReady != nullptr) + { + CloseHandle(m_impl->bufferReady); + m_impl->bufferReady = nullptr; + } + + if (m_impl->comInitialised) + { + CoUninitialize(); + m_impl->comInitialised = false; + } + m_transmitting.store(false); + m_currentLevel.store(0.0f); +} + +void VoiceCapture::SetTransmitting(bool transmitting) +{ + m_transmitting.store(transmitting); + if (!transmitting) + { + // Reset accumulator so a new PTT press starts on a fresh + // 20 ms boundary. + m_impl->pendingSamples.clear(); + m_currentLevel.store(0.0f); + } +} + +// -------------------------------------------------------------------- +// Worker loop: pulls from WASAPI, converts to 48k mono int16, and +// emits 20 ms frames to the caller's callback. +// -------------------------------------------------------------------- +void VoiceCapture::WorkerLoop() +{ + // MMCSS raises the thread priority for pro audio work. + DWORD taskIndex = 0; + HANDLE mmcssHandle = AvSetMmThreadCharacteristicsW(L"Audio", &taskIndex); + + const int srcRate = m_impl->endpointSampleRate; + const int srcChannels = m_impl->endpointChannels; + const bool srcIsFloat = m_impl->endpointIsFloat; + const int srcBits = m_impl->endpointBitsPerSample; + + // Simple linear resampler state. For voice capture quality this + // is more than good enough; Opus at 48k is where quality matters. + double readPos = 0.0; + const double rateRatio = + static_cast(srcRate) / static_cast(kSampleRate); + + std::vector& pending = m_impl->pendingSamples; + + while (!m_workerShouldExit.load()) + { + DWORD waitResult = WaitForSingleObject(m_impl->bufferReady, 200); + if (m_workerShouldExit.load()) + { + break; + } + if (waitResult != WAIT_OBJECT_0) + { + continue; + } + + UINT32 packetLength = 0; + HRESULT hr = m_impl->captureClient->GetNextPacketSize(&packetLength); + while (SUCCEEDED(hr) && packetLength > 0) + { + BYTE* rawData = nullptr; + UINT32 numFrames = 0; + DWORD flags = 0; + hr = m_impl->captureClient->GetBuffer(&rawData, &numFrames, &flags, + nullptr, nullptr); + if (FAILED(hr)) + { + break; + } + + const bool silent = (flags & AUDCLNT_BUFFERFLAGS_SILENT) != 0; + + // Convert and resample into a per-packet scratch buffer. + const int outFrameCount = + static_cast(numFrames / rateRatio + 1); + std::vector scratch; + scratch.reserve(outFrameCount); + + // Walk the source buffer picking samples nearest readPos. + // Output ends when readPos exceeds numFrames - 1. + while (readPos < static_cast(numFrames)) + { + const int srcIdx = static_cast(readPos); + int16_t mono = 0; + if (!silent && rawData != nullptr) + { + // Mix to mono by averaging channels. + long accum = 0; + for (int ch = 0; ch < srcChannels; ++ch) + { + const int sampleIdx = srcIdx * srcChannels + ch; + if (srcIsFloat && srcBits == 32) + { + const float f = reinterpret_cast(rawData)[sampleIdx]; + int s = static_cast(f * 32767.0f); + if (s > 32767) s = 32767; + if (s < -32768) s = -32768; + accum += s; + } + else if (!srcIsFloat && srcBits == 16) + { + accum += reinterpret_cast(rawData)[sampleIdx]; + } + else if (!srcIsFloat && srcBits == 32) + { + const int32_t s32 = reinterpret_cast(rawData)[sampleIdx]; + accum += (s32 >> 16); + } + // Other formats: treat as silence. + } + mono = static_cast(accum / (srcChannels > 0 ? srcChannels : 1)); + } + scratch.push_back(mono); + readPos += rateRatio; + } + readPos -= static_cast(numFrames); + + hr = m_impl->captureClient->ReleaseBuffer(numFrames); + if (FAILED(hr)) + { + break; + } + + // Append to pending, then emit as many complete frames as + // we have - but only while PTT is held. + if (m_transmitting.load() && m_onFrame) + { + pending.insert(pending.end(), scratch.begin(), scratch.end()); + + while (static_cast(pending.size()) >= kSamplesPerFrame) + { + // Apply mic gain (if not unity) with clipping. + const float gain = m_micGain.load(); + if (gain != 1.0f) + { + for (int i = 0; i < kSamplesPerFrame; ++i) + { + int v = static_cast( + static_cast(pending[i]) * gain); + if (v > 32767) v = 32767; + if (v < -32768) v = -32768; + pending[i] = static_cast(v); + } + } + + // Compute peak for the level meter (post-gain, so the + // UI reflects what is actually being sent). + int peak = 0; + for (int i = 0; i < kSamplesPerFrame; ++i) + { + const int v = std::abs(static_cast(pending[i])); + if (v > peak) peak = v; + } + m_currentLevel.store(static_cast(peak) / 32768.0f); + + m_onFrame(pending.data(), kSamplesPerFrame); + pending.erase(pending.begin(), + pending.begin() + kSamplesPerFrame); + } + } + else + { + // Not transmitting - drop. + pending.clear(); + m_currentLevel.store(0.0f); + } + + hr = m_impl->captureClient->GetNextPacketSize(&packetLength); + } + } + + if (mmcssHandle != nullptr) + { + AvRevertMmThreadCharacteristics(mmcssHandle); + } +} + +// -------------------------------------------------------------------- +// Mic gain +// -------------------------------------------------------------------- +void VoiceCapture::SetMicGain(float gain) +{ + if (gain < 0.0f) gain = 0.0f; + if (gain > 4.0f) gain = 4.0f; + m_micGain.store(gain); +} + +// -------------------------------------------------------------------- +// Device enumeration +// +// We build a temporary device enumerator (independent of the running +// capture stream, if any) and walk every active capture endpoint. +// Default-comms and default-console are queried separately so the UI +// can mark them in a device picker. +// -------------------------------------------------------------------- +std::vector VoiceCapture::EnumerateDevices() +{ + std::vector results; + + const HRESULT hrCo = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + const bool needUninit = + (hrCo == S_OK || hrCo == S_FALSE); // S_FALSE: already init'd on this thread + + IMMDeviceEnumerator* enumerator = nullptr; + HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, + CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), + reinterpret_cast(&enumerator)); + if (FAILED(hr) || enumerator == nullptr) + { + if (needUninit) CoUninitialize(); + return results; + } + + // Resolve default comms and console endpoint ids for flagging. + std::wstring defaultCommsID; + std::wstring defaultConsoleID; + { + IMMDevice* dev = nullptr; + if (SUCCEEDED(enumerator->GetDefaultAudioEndpoint(eCapture, + eCommunications, &dev)) && dev != nullptr) + { + LPWSTR idStr = nullptr; + if (SUCCEEDED(dev->GetId(&idStr)) && idStr != nullptr) + { + defaultCommsID.assign(idStr); + CoTaskMemFree(idStr); + } + dev->Release(); + } + } + { + IMMDevice* dev = nullptr; + if (SUCCEEDED(enumerator->GetDefaultAudioEndpoint(eCapture, + eConsole, &dev)) && dev != nullptr) + { + LPWSTR idStr = nullptr; + if (SUCCEEDED(dev->GetId(&idStr)) && idStr != nullptr) + { + defaultConsoleID.assign(idStr); + CoTaskMemFree(idStr); + } + dev->Release(); + } + } + + IMMDeviceCollection* collection = nullptr; + hr = enumerator->EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE, + &collection); + if (SUCCEEDED(hr) && collection != nullptr) + { + UINT count = 0; + collection->GetCount(&count); + for (UINT i = 0; i < count; ++i) + { + IMMDevice* dev = nullptr; + if (FAILED(collection->Item(i, &dev)) || dev == nullptr) continue; + + CaptureDeviceInfo info; + + LPWSTR idStr = nullptr; + if (SUCCEEDED(dev->GetId(&idStr)) && idStr != nullptr) + { + info.id.assign(idStr); + CoTaskMemFree(idStr); + } + + IPropertyStore* props = nullptr; + if (SUCCEEDED(dev->OpenPropertyStore(STGM_READ, &props)) && + props != nullptr) + { + PROPVARIANT varName; + PropVariantInit(&varName); + if (SUCCEEDED(props->GetValue(PKEY_Device_FriendlyName, + &varName)) && + varName.vt == VT_LPWSTR && varName.pwszVal != nullptr) + { + info.friendlyName.assign(varName.pwszVal); + } + PropVariantClear(&varName); + props->Release(); + } + + info.isDefaultCommunications = + (!info.id.empty() && info.id == defaultCommsID); + info.isDefaultConsole = + (!info.id.empty() && info.id == defaultConsoleID); + + if (!info.id.empty()) + { + results.push_back(std::move(info)); + } + dev->Release(); + } + collection->Release(); + } + + enumerator->Release(); + if (needUninit) CoUninitialize(); + return results; +} + +} // namespace Voice + +#endif // ENABLE_VOICE_CHAT diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/Voice/VoiceManager.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/Voice/VoiceManager.cpp new file mode 100644 index 00000000000..57bf2565583 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/Voice/VoiceManager.cpp @@ -0,0 +1,346 @@ +// VoiceManager.cpp + +#include "PreRTS.h" + +#ifdef ENABLE_VOICE_CHAT + +#include "GameNetwork/GeneralsOnline/Voice/VoiceManager.h" +#include "GameNetwork/GeneralsOnline/Voice/VoiceCapture.h" +#include "GameNetwork/GeneralsOnline/Voice/VoicePlayback.h" +#include "GameNetwork/GeneralsOnline/Voice/VoiceOpusCodec.h" +#include "GameNetwork/GeneralsOnline/ngmp_include.h" +#include "GameNetwork/GeneralsOnline/OnlineServices_Init.h" // NGMP_OnlineServicesManager::Settings +#include "GameNetwork/GeneralsOnline/GeneralsOnline_Settings.h" + +#include +#include + +// --------------------------------------------------------------------- +// DEV ONLY: Local loopback mode. +// +// When VOICE_DEV_LOCAL_LOOPBACK is defined, every frame captured by the +// microphone is additionally fed straight into the playback jitter buffer +// under a fake peer ID. This lets you verify the full capture -> encode +// -> decode -> playback chain without any real peer in the mesh. You +// should hear yourself about 100 ms after speaking. +// +// Intended for local development only. Leave undefined in shipping +// builds: otherwise every player would hear themselves echo back. +// --------------------------------------------------------------------- +// #define VOICE_DEV_LOCAL_LOOPBACK 1 + +#ifdef VOICE_DEV_LOCAL_LOOPBACK +// Fake peer id under which the loopback audio is routed. Kept far from +// any real NGMP user id so it is easy to spot in logs and cannot collide +// with a real peer. +static constexpr int64_t kLoopbackFakePeerID = 0x10A9BACC10A9BACCll; +#endif + +Voice::VoiceManager* TheVoiceManager = nullptr; + +namespace Voice +{ + +// -------------------------------------------------------------------- +VoiceManager::VoiceManager() = default; + +VoiceManager::~VoiceManager() +{ + Shutdown(); +} + +// -------------------------------------------------------------------- +void VoiceManager::Init() +{ + if (m_initialised.load()) return; + + m_capture = std::make_unique(); + m_playback = std::make_unique(); + m_encoder = std::make_unique(); + m_encoder->Initialise(20000); // 20 kbps speech bitrate + + // Pull the persistent per-client ignore list out of GenOnlineSettings + // and seed m_mutedPeers with it. This is a purely local operation: + // nothing is sent over the network, no other clients are informed. + // When EnterLobby runs later it will re-apply every entry in this set + // to the active VoicePlayback, so the mutes take effect before the + // first packet from that peer arrives. + { + const std::vector& persisted = + NGMP_OnlineServicesManager::Settings.Voice_GetMutedPeers(); + for (int64_t id : persisted) + { + if (id > 0) m_mutedPeers.insert(id); + } + } + + m_initialised.store(true); +} + +// -------------------------------------------------------------------- +void VoiceManager::Shutdown() +{ + if (!m_initialised.load()) return; + + Leave(); + + if (m_encoder) { m_encoder->Shutdown(); } + m_capture.reset(); + m_playback.reset(); + m_encoder.reset(); + + m_initialised.store(false); +} + +// -------------------------------------------------------------------- +void VoiceManager::EnterLobby(int64_t myUserID) +{ + if (!m_initialised.load()) Init(); + + m_myUserID.store(myUserID); + m_outSeq.store(0); + m_mode.store(VoiceMode::LOBBY_ALL); + + if (m_playback) + { + m_playback->ClearAllPeers(); + m_playback->Start(); + // Re-apply cached settings so they survive Leave/EnterLobby. + m_playback->SetGlobalVolume(m_globalVoiceVolume); + for (const auto& kv : m_peerVolumes) + { + m_playback->SetPeerVolume(kv.first, kv.second); + } + for (int64_t mutedID : m_mutedPeers) + { + m_playback->SetPeerMuted(mutedID, true); + } + } + if (m_capture) + { + m_capture->SetDeviceID(m_captureDeviceID); + m_capture->SetMicGain(m_micGain); + m_capture->Start([this](const int16_t* pcm, int n) { + this->OnCaptureFrame(pcm, n); + }); + } +} + +// -------------------------------------------------------------------- +void VoiceManager::EnterInGameTeamMode(int64_t myUserID, + const std::unordered_map& teamAssignments) +{ + if (!m_initialised.load()) Init(); + + m_myUserID.store(myUserID); + m_mode.store(VoiceMode::IN_GAME_TEAM); + + { + std::lock_guard lock(m_teamsMutex); + m_teams = teamAssignments; + auto it = m_teams.find(myUserID); + m_myTeam = (it != m_teams.end()) ? it->second : -1; + } + + // Audio devices already running (seamless transition). + if (!m_playback || !m_playback->IsPeerSpeaking(0)) // just use IsPeerSpeaking as "is object alive" + { + // If Leave was called between lobby and game, restart. + if (m_playback) m_playback->Start(); + if (m_capture) + { + m_capture->Start([this](const int16_t* pcm, int n) { + this->OnCaptureFrame(pcm, n); + }); + } + } +} + +// -------------------------------------------------------------------- +void VoiceManager::Leave() +{ + m_mode.store(VoiceMode::DISABLED); + m_pttPressed.store(false); + + if (m_capture) + { + m_capture->SetTransmitting(false); + m_capture->Stop(); + } + if (m_playback) + { + m_playback->Stop(); + m_playback->ClearAllPeers(); + } + { + std::lock_guard lock(m_teamsMutex); + m_teams.clear(); + m_myTeam = -1; + } +} + +// -------------------------------------------------------------------- +void VoiceManager::Update() +{ + // Nothing time-critical here - UI polls IsPeerSpeaking() etc + // directly. Kept as a hook in case we want to do watchdogging + // later (e.g. auto-restart capture if the mic device was lost). +} + +// -------------------------------------------------------------------- +void VoiceManager::SetPushToTalkPressed(bool pressed) +{ + if (m_mode.load() == VoiceMode::DISABLED) return; + + const bool wasPressed = m_pttPressed.exchange(pressed); + if (pressed != wasPressed && m_capture) + { + m_capture->SetTransmitting(pressed); + } +} + +// -------------------------------------------------------------------- +// Capture callback (runs on capture worker thread) +// -------------------------------------------------------------------- +void VoiceManager::OnCaptureFrame(const int16_t* pcm, int sampleCount) +{ + if (sampleCount != kSamplesPerFrame) return; + if (m_mode.load() == VoiceMode::DISABLED) return; + if (!m_pttPressed.load()) return; + +#ifndef VOICE_DEV_LOCAL_LOOPBACK + // In real (non-loopback) builds we need a packet sink to send out. + // In loopback builds we don't, because we never touch the network. + if (m_packetSink == nullptr) return; +#endif + + uint8_t encoded[kMaxEncodedBytes]; + int encodedLen = 0; + { + std::lock_guard lock(m_encoderMutex); + if (!m_encoder || !m_encoder->IsReady()) return; + encodedLen = m_encoder->EncodeFrame(pcm, encoded, sizeof(encoded)); + } + if (encodedLen <= 0) return; + + // Build wire packet. + uint8_t wire[kMaxWirePacketSize]; + wire[0] = kVoicePacketTag; + const int64_t myID = m_myUserID.load(); + std::memcpy(&wire[1], &myID, sizeof(int64_t)); + const uint16_t seq = m_outSeq.fetch_add(1); + std::memcpy(&wire[9], &seq, sizeof(uint16_t)); + const uint16_t lenLE = static_cast(encodedLen); + std::memcpy(&wire[11], &lenLE, sizeof(uint16_t)); + std::memcpy(&wire[kVoiceHeaderBytes], encoded, encodedLen); + const int totalLen = kVoiceHeaderBytes + encodedLen; + + if (m_packetSink != nullptr) + { + m_packetSink(wire, totalLen); + } + +#ifdef VOICE_DEV_LOCAL_LOOPBACK + // Feed the encoded frame straight into playback under a fake peer + // id so the self-drop check in OnVoicePacket doesn't kick in and + // the loopback peer shows up as a distinct speaker. We bypass + // OnVoicePacket entirely (which would also enforce the team filter + // in IN_GAME_TEAM mode and reject our fake id). + if (m_playback) + { + m_playback->SubmitFrame(kLoopbackFakePeerID, seq, + encoded, encodedLen); + } +#endif +} + +// -------------------------------------------------------------------- +// Receive side (runs on network receive thread) +// -------------------------------------------------------------------- +void VoiceManager::OnVoicePacket(int64_t senderUserID, + const uint8_t* data, int length) +{ + if (data == nullptr || length < kVoiceHeaderBytes) return; + if (data[0] != kVoicePacketTag) return; + if (m_mode.load() == VoiceMode::DISABLED) return; + if (senderUserID == m_myUserID.load()) return; // never play self + + uint16_t seq = 0, payloadLen = 0; + std::memcpy(&seq, &data[9], sizeof(uint16_t)); + std::memcpy(&payloadLen, &data[11], sizeof(uint16_t)); + if (payloadLen == 0 || + length < static_cast(kVoiceHeaderBytes + payloadLen)) return; + + // Team filter when in-game. + if (m_mode.load() == VoiceMode::IN_GAME_TEAM) + { + std::lock_guard lock(m_teamsMutex); + auto it = m_teams.find(senderUserID); + if (it == m_teams.end() || it->second != m_myTeam) return; + } + + if (m_playback) + { + m_playback->SubmitFrame(senderUserID, seq, + &data[kVoiceHeaderBytes], payloadLen); + } +} + +// -------------------------------------------------------------------- +// UI queries +// -------------------------------------------------------------------- +bool VoiceManager::IsPeerSpeaking(int64_t peerUserID) +{ + return m_playback ? m_playback->IsPeerSpeaking(peerUserID) : false; +} + +bool VoiceManager::IsSelfSpeaking() +{ + return m_pttPressed.load() && m_capture && m_capture->GetCurrentLevel() > 0.01f; +} + +void VoiceManager::SetPeerMuted(int64_t peerUserID, bool muted) +{ + // Refuse obviously invalid IDs - otherwise we'd write garbage into the + // persistent list and load it back forever. + if (peerUserID <= 0) return; + + const bool wasMuted = (m_mutedPeers.count(peerUserID) > 0); + if (muted == wasMuted) return; // nothing changed, skip disk write + + if (muted) m_mutedPeers.insert(peerUserID); + else m_mutedPeers.erase(peerUserID); + + if (m_playback) m_playback->SetPeerMuted(peerUserID, muted); + + // Persist the full set so the mute survives a game restart. This is + // strictly client-local: Save_Voice_MutedPeers only writes to the + // local settings JSON, it never sends anything over the network. + std::vector snapshot; + snapshot.reserve(m_mutedPeers.size()); + for (int64_t id : m_mutedPeers) snapshot.push_back(id); + NGMP_OnlineServicesManager::Settings.Save_Voice_MutedPeers(snapshot); +} + +bool VoiceManager::IsPeerMuted(int64_t peerUserID) +{ + return m_mutedPeers.count(peerUserID) > 0; +} + +float VoiceManager::GetMicLevel() +{ + return m_capture ? m_capture->GetCurrentLevel() : 0.0f; +} + +// -------------------------------------------------------------------- +// Audio settings (device picker, mic gain, volumes) +// -------------------------------------------------------------------- +std::vector VoiceManager::EnumerateCaptureDevices() +{ + return VoiceCapture::EnumerateDevices(); +} + +void VoiceManager::SetCaptureDevice(const std::wstring& deviceID) +{ + m_captureDeviceID = deviceID; + if \ No newline at end of file diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/Voice/VoiceOptionsUI.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/Voice/VoiceOptionsUI.cpp new file mode 100644 index 00000000000..43cb1e6f751 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/Voice/VoiceOptionsUI.cpp @@ -0,0 +1,305 @@ +// ---------------------------------------------------------------------------- +// VoiceOptionsUI.cpp +// +// Runtime-built voice-chat options panel for the vanilla OptionsMenu. +// See VoiceOptionsUI.h for the architectural intent. +// ---------------------------------------------------------------------------- + +#include "PreRTS.h" + +#if defined(ENABLE_VOICE_CHAT) + +#include "GameNetwork/GeneralsOnline/Voice/VoiceOptionsUI.h" +#include "GameNetwork/GeneralsOnline/Voice/VoiceManager.h" +#include "GameNetwork/GeneralsOnline/Voice/VoiceCapture.h" +#include "GameNetwork/GeneralsOnline/GeneralsOnline_Settings.h" +#include "GameNetwork/GeneralsOnline/ngmp_interfaces.h" + +#include "GameClient/GameWindow.h" +#include "GameClient/GameWindowManager.h" +#include "GameClient/GadgetCheckBox.h" +#include "GameClient/GadgetSlider.h" +#include "GameClient/GadgetComboBox.h" +#include "GameClient/GadgetStaticText.h" +#include "GameClient/GameText.h" +#include "Common/NameKeyGenerator.h" + +#include + +// --------------------------------------------------------------------------- +// Internal state: all pointers live for the lifetime of one OptionsMenu +// instance. They are cleared in Teardown(). Gadgets are owned by the +// WindowLayout, so we never destroy them manually. +// --------------------------------------------------------------------------- +namespace +{ + GameWindow* s_comboMic = nullptr; + GameWindow* s_sliderMicGain = nullptr; + GameWindow* s_sliderVoiceVol = nullptr; + GameWindow* s_checkEnabled = nullptr; + GameWindow* s_labelHeader = nullptr; + GameWindow* s_labelMic = nullptr; + GameWindow* s_labelGain = nullptr; + GameWindow* s_labelVol = nullptr; + + NameKeyType s_comboMicID = NAMEKEY_INVALID; + NameKeyType s_sliderMicGainID = NAMEKEY_INVALID; + NameKeyType s_sliderVoiceVolID = NAMEKEY_INVALID; + NameKeyType s_checkEnabledID = NAMEKEY_INVALID; + + // Bring voice namespace types into local scope for brevity. + using Voice::VoiceManager; + using Voice::CaptureDeviceInfo; + + std::vector s_enumeratedDevices; + + // Slider range: we map the float settings 0.0..4.0 (gain) and + // 0.0..2.0 (volume) onto integer slider ticks. + const Int kGainSliderMin = 0; + const Int kGainSliderMax = 400; // 0.00 .. 4.00 step 0.01 + const Int kVolSliderMin = 0; + const Int kVolSliderMax = 200; // 0.00 .. 2.00 step 0.01 + + // Vertical stack parameters for the runtime-created controls. We + // anchor the panel at the screen's right edge relative to an existing + // control (`sliderVoiceVolume`) that is guaranteed to exist in the + // vanilla OptionsMenu layout. + const Int kPanelPadX = 12; // horizontal gap from anchor + const Int kRowHeight = 22; + const Int kRowSpacing = 6; + const Int kLabelWidth = 90; + const Int kCtrlWidth = 200; + + GameWindow* LookupByName(const char* name) + { + NameKeyType id = TheNameKeyGenerator->nameToKey(name); + return TheWindowManager->winGetWindowFromId(nullptr, id); + } + + // Clone the visual style (WinInstanceData) of a template window onto + // the instance data used to create a new gadget. Returns true on + // success. The caller still has to set m_owner / m_textLabelString. + bool CloneStyleFrom(GameWindow* tpl, WinInstanceData& out) + { + if (tpl == nullptr) return false; + WinInstanceData* src = tpl->winGetInstanceData(); + if (src == nullptr) return false; + out = *src; + return true; + } + + UnicodeString FetchLabel(const char* csfKey, const wchar_t* fallback) + { + UnicodeString s; + if (TheGameText != nullptr) + { + s = TheGameText->fetch(csfKey); + } + if (s.isEmpty()) + { + s = UnicodeString(fallback); + } + return s; + } +} // anonymous namespace + +// --------------------------------------------------------------------------- +// Build +// --------------------------------------------------------------------------- +void VoiceOptionsUI::Build(GameWindow* parent) +{ + Teardown(); // make sure we start clean on re-entry + + if (parent == nullptr || TheWindowManager == nullptr) + { + return; + } + + // The voice checkbox lives as a real entry in the patched + // OptionsMenu.wnd (see Window/Menus/OptionsMenu.wnd override). We + // simply look it up by name and bind its initial checked state from + // GenOnlineSettings. If a user runs on an unpatched .wnd, the lookup + // returns null and we silently skip — slash commands still work. + GenOnlineSettings& settings = NGMP_OnlineServicesManager::Settings; + + s_checkEnabled = LookupByName("OptionsMenu.wnd:VoiceOptions_CheckEnabled"); + if (s_checkEnabled != nullptr) + { + s_checkEnabledID = TheNameKeyGenerator->nameToKey( + "OptionsMenu.wnd:VoiceOptions_CheckEnabled"); + GadgetCheckBoxSetChecked(s_checkEnabled, settings.Voice_GetEnabled()); + } + + // Mic gain slider: map float 0..4 onto slider min..max. + s_sliderMicGain = LookupByName("OptionsMenu.wnd:VoiceOptions_SliderMicGain"); + if (s_sliderMicGain != nullptr) + { + s_sliderMicGainID = TheNameKeyGenerator->nameToKey( + "OptionsMenu.wnd:VoiceOptions_SliderMicGain"); + Int mn = 0, mx = 100; + GadgetSliderGetMinMax(s_sliderMicGain, &mn, &mx); + float gain = settings.Voice_GetMicGain(); + if (gain < 0.0f) gain = 0.0f; + if (gain > 4.0f) gain = 4.0f; + Int pos = mn + (Int)((gain / 4.0f) * (float)(mx - mn) + 0.5f); + GadgetSliderSetPosition(s_sliderMicGain, pos); + } + + // Voice chat volume slider: map float 0..2 onto slider min..max. + s_sliderVoiceVol = LookupByName("OptionsMenu.wnd:VoiceOptions_SliderVoiceVol"); + if (s_sliderVoiceVol != nullptr) + { + s_sliderVoiceVolID = TheNameKeyGenerator->nameToKey( + "OptionsMenu.wnd:VoiceOptions_SliderVoiceVol"); + Int mn = 0, mx = 100; + GadgetSliderGetMinMax(s_sliderVoiceVol, &mn, &mx); + float vol = settings.Voice_GetGlobalVolume(); + if (vol < 0.0f) vol = 0.0f; + if (vol > 2.0f) vol = 2.0f; + Int pos = mn + (Int)((vol / 2.0f) * (float)(mx - mn) + 0.5f); + GadgetSliderSetPosition(s_sliderVoiceVol, pos); + } + return; + + // ---- Legacy runtime-creation path (kept for reference) ----------- + + // Mirror the proven runtime-checkbox pattern from PopupHostGame.cpp: + // clone visual style from an existing checkbox in the same layout + // and place the new gadget directly below it. We use CheckSendDelay + // because it lives in the bottom-left "Network" region of the + // OptionsMenu where there is space for an extra row. + GameWindow* tplCheckBox = LookupByName("OptionsMenu.wnd:CheckSendDelay"); + if (tplCheckBox == nullptr) + { + // Fall back to AlternateMouse, which always exists. + tplCheckBox = LookupByName("OptionsMenu.wnd:CheckAlternateMouse"); + } + if (tplCheckBox == nullptr) + { + return; + } + + // Clone style/visuals from the source checkbox. + WinInstanceData* srcInstData = tplCheckBox->winGetInstanceData(); + WinInstanceData instData; + if (srcInstData != nullptr) + { + instData = *srcInstData; + } + else + { + instData.init(); + } + instData.m_owner = parent; + instData.m_textLabelString = "GUI:VoiceChat"; + + // Match the source checkbox's exact size; place one row below it. + Int chkW = 0, chkH = 0; + tplCheckBox->winGetSize(&chkW, &chkH); + Int chkX = 0, chkY = 0; + tplCheckBox->winGetPosition(&chkX, &chkY); + + const UnsignedInt status = tplCheckBox->winGetStatus(); + + s_checkEnabled = TheWindowManager->gogoGadgetCheckbox( + parent, + status, + chkX, + chkY + chkH + 4, // one checkbox below source, small gap + chkW, + chkH, + &instData, + nullptr, // keep default font from instance data + FALSE); // visuals already cloned + + if (s_checkEnabled != nullptr) + { + s_checkEnabledID = TheNameKeyGenerator->nameToKey( + "OptionsMenu.wnd:VoiceOptions_CheckEnabled"); + s_checkEnabled->winSetWindowId((Int)s_checkEnabledID); + + UnicodeString label = FetchLabel("GUI:VoiceChat", L"Voice Chat"); + GadgetCheckBoxSetText(s_checkEnabled, label); + GadgetCheckBoxSetChecked(s_checkEnabled, + NGMP_OnlineServicesManager::Settings.Voice_GetEnabled()); + } +} + +// --------------------------------------------------------------------------- +// Teardown +// --------------------------------------------------------------------------- +void VoiceOptionsUI::Teardown() +{ + s_comboMic = nullptr; + s_sliderMicGain = nullptr; + s_sliderVoiceVol = nullptr; + s_checkEnabled = nullptr; + s_labelHeader = nullptr; + s_labelMic = nullptr; + s_labelGain = nullptr; + s_labelVol = nullptr; + s_comboMicID = NAMEKEY_INVALID; + s_sliderMicGainID = NAMEKEY_INVALID; + s_sliderVoiceVolID = NAMEKEY_INVALID; + s_checkEnabledID = NAMEKEY_INVALID; + s_enumeratedDevices.clear(); +} + +// --------------------------------------------------------------------------- +// TryHandleEvent +// --------------------------------------------------------------------------- +bool VoiceOptionsUI::TryHandleEvent(GameWindow* control, unsigned int /*msg*/, void* /*mData2*/) +{ + if (control == nullptr) return false; + + // We don't really need to act on live events — the final values are + // read out in Save() from the gadgets themselves. But claim the event + // so OptionsMenu's default handler doesn't misinterpret our controls. + if (control == s_comboMic || + control == s_sliderMicGain || + control == s_sliderVoiceVol || + control == s_checkEnabled) + { + return true; + } + return false; +} + +// --------------------------------------------------------------------------- +// Save +// --------------------------------------------------------------------------- +void VoiceOptionsUI::Save() +{ + GenOnlineSettings& settings = NGMP_OnlineServicesManager::Settings; + + // Only the Enabled checkbox is currently built at runtime; the + // remaining voice settings (mic device, gain, volume) are managed + // via /voice slash commands until they get a real .wnd entry. + if (s_checkEnabled != nullptr) + { + const bool enabled = (GadgetCheckBoxIsChecked(s_checkEnabled) != FALSE); + settings.Save_Voice_Enabled(enabled); + } + + if (s_sliderMicGain != nullptr) + { + Int mn = 0, mx = 100; + GadgetSliderGetMinMax(s_sliderMicGain, &mn, &mx); + const Int pos = GadgetSliderGetPosition(s_sliderMicGain); + const float range = (float)(mx - mn); + const float gain = (range > 0.0f) ? ((float)(pos - mn) / range) * 4.0f : 0.0f; + settings.Save_Voice_MicGain(gain); + } + + if (s_sliderVoiceVol != nullptr) + { + Int mn = 0, mx = 100; + GadgetSliderGetMinMax(s_sliderVoiceVol, &mn, &mx); + const Int pos = GadgetSliderGetPosition(s_sliderVoiceVol); + const float range = (float)(mx - mn); + const float vol = (range > 0.0f) ? ((float)(pos - mn) / range) * 2.0f : 0.0f; + settings.Save_Voice_GlobalVolume(vol); + } +} + +#endif // ENABLE_VOICE_CHAT diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/Voice/VoiceOpusCodec.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/Voice/VoiceOpusCodec.cpp new file mode 100644 index 00000000000..75c00a2755c --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/Voice/VoiceOpusCodec.cpp @@ -0,0 +1,151 @@ +// VoiceOpusCodec.cpp + +#include "PreRTS.h" + +#ifdef ENABLE_VOICE_CHAT + +#include "GameNetwork/GeneralsOnline/Voice/VoiceOpusCodec.h" + +#include + +namespace Voice +{ + +// ------------------------------------------------------------------ +// OpusEncoderWrapper +// ------------------------------------------------------------------ +OpusEncoderWrapper::OpusEncoderWrapper() + : m_encoder(nullptr) +{ +} + +OpusEncoderWrapper::~OpusEncoderWrapper() +{ + Shutdown(); +} + +bool OpusEncoderWrapper::Initialise(int bitrateBps) +{ + Shutdown(); + + int error = OPUS_OK; + m_encoder = opus_encoder_create(kSampleRate, kChannels, + OPUS_APPLICATION_VOIP, &error); + if (error != OPUS_OK || m_encoder == nullptr) + { + m_encoder = nullptr; + return false; + } + + // Speech-oriented configuration. + opus_encoder_ctl(m_encoder, OPUS_SET_BITRATE(bitrateBps)); + opus_encoder_ctl(m_encoder, OPUS_SET_COMPLEXITY(5)); + opus_encoder_ctl(m_encoder, OPUS_SET_SIGNAL(OPUS_SIGNAL_VOICE)); + opus_encoder_ctl(m_encoder, OPUS_SET_VBR(1)); + opus_encoder_ctl(m_encoder, OPUS_SET_INBAND_FEC(1)); + opus_encoder_ctl(m_encoder, OPUS_SET_PACKET_LOSS_PERC(10)); + opus_encoder_ctl(m_encoder, OPUS_SET_DTX(0)); + return true; +} + +void OpusEncoderWrapper::Shutdown() +{ + if (m_encoder != nullptr) + { + opus_encoder_destroy(m_encoder); + m_encoder = nullptr; + } +} + +int OpusEncoderWrapper::EncodeFrame(const int16_t* pcmIn, uint8_t* encodedOut, + int encodedOutCapacity) +{ + if (m_encoder == nullptr || pcmIn == nullptr || + encodedOut == nullptr || encodedOutCapacity <= 0) + { + return 0; + } + + const int result = opus_encode(m_encoder, pcmIn, kSamplesPerFrame, + encodedOut, encodedOutCapacity); + if (result < 0) + { + return 0; + } + return result; +} + +// ------------------------------------------------------------------ +// OpusDecoderWrapper +// ------------------------------------------------------------------ +OpusDecoderWrapper::OpusDecoderWrapper() + : m_decoder(nullptr) +{ +} + +OpusDecoderWrapper::~OpusDecoderWrapper() +{ + Shutdown(); +} + +bool OpusDecoderWrapper::Initialise() +{ + Shutdown(); + + int error = OPUS_OK; + m_decoder = opus_decoder_create(kSampleRate, kChannels, &error); + if (error != OPUS_OK || m_decoder == nullptr) + { + m_decoder = nullptr; + return false; + } + return true; +} + +void OpusDecoderWrapper::Shutdown() +{ + if (m_decoder != nullptr) + { + opus_decoder_destroy(m_decoder); + m_decoder = nullptr; + } +} + +int OpusDecoderWrapper::DecodeFrame(const uint8_t* encodedIn, int encodedLen, + int16_t* pcmOut, int pcmOutCapacitySamples) +{ + if (m_decoder == nullptr || encodedIn == nullptr || encodedLen <= 0 || + pcmOut == nullptr || pcmOutCapacitySamples < kSamplesPerFrame) + { + return 0; + } + + const int result = opus_decode(m_decoder, encodedIn, encodedLen, + pcmOut, kSamplesPerFrame, /*decode_fec=*/0); + if (result < 0) + { + return 0; + } + return result; +} + +int OpusDecoderWrapper::DecodeLostFrame(int16_t* pcmOut, int pcmOutCapacitySamples) +{ + if (m_decoder == nullptr || pcmOut == nullptr || + pcmOutCapacitySamples < kSamplesPerFrame) + { + return 0; + } + + const int result = opus_decode(m_decoder, nullptr, 0, + pcmOut, kSamplesPerFrame, /*decode_fec=*/0); + if (result < 0) + { + return 0; + } + return result; +} + +} // namespace Voice + +#endif // ENABLE_VOICE_CHAT diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/Voice/VoicePlayback.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/Voice/VoicePlayback.cpp new file mode 100644 index 00000000000..c0b59c95eb3 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/Voice/VoicePlayback.cpp @@ -0,0 +1,545 @@ +// VoicePlayback.cpp + +#include "PreRTS.h" + +#ifdef ENABLE_VOICE_CHAT + +#include "GameNetwork/GeneralsOnline/Voice/VoicePlayback.h" +#include "GameNetwork/GeneralsOnline/Voice/VoiceOpusCodec.h" + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace Voice +{ + +// -------------------------------------------------------------------- +// Constants +// -------------------------------------------------------------------- +static constexpr int kTargetJitterFrames = 3; // 3 * 20 ms = 60 ms +static constexpr int kMaxJitterFrames = 8; // hard cap +static constexpr double kSpeakingTimeoutMs = 400.0; + +// -------------------------------------------------------------------- +// PeerPlaybackState: per-sender decoder, jitter buffer, mute flag +// -------------------------------------------------------------------- +struct PeerPlaybackState +{ + OpusDecoderWrapper decoder; + + // Jitter buffer: map sequence -> decoded frame. Using a map so we + // handle out-of-order arrival naturally. + struct Frame + { + uint16_t sequence = 0; + std::vector samples; // kSamplesPerFrame int16 + bool valid = false; + }; + std::deque queue; + uint16_t nextExpectedSeq = 0; + bool primed = false; + + std::atomic muted{false}; + std::atomic volume{1.0f}; + + // Last time a frame was submitted - used for "speaking" state. + std::chrono::steady_clock::time_point lastActivity; +}; + +// -------------------------------------------------------------------- +// VoicePlayback::Impl: WASAPI objects +// -------------------------------------------------------------------- +struct VoicePlayback::Impl +{ + IMMDeviceEnumerator* deviceEnum = nullptr; + IMMDevice* device = nullptr; + IAudioClient* audioClient = nullptr; + IAudioRenderClient* renderClient = nullptr; + WAVEFORMATEX* mixFormat = nullptr; + HANDLE bufferReady = nullptr; + bool comInitialised = false; + + int endpointSampleRate = 0; + int endpointChannels = 0; + int endpointBitsPerSample = 0; + bool endpointIsFloat = false; + + UINT32 bufferFrameCount = 0; +}; + +template +static void SafeRelease(T** pp) +{ + if (*pp != nullptr) + { + (*pp)->Release(); + *pp = nullptr; + } +} + +// -------------------------------------------------------------------- +VoicePlayback::VoicePlayback() + : m_impl(new Impl) + , m_workerRunning(false) + , m_workerShouldExit(false) +{ +} + +VoicePlayback::~VoicePlayback() +{ + Stop(); + ClearAllPeers(); + delete m_impl; + m_impl = nullptr; +} + +// -------------------------------------------------------------------- +bool VoicePlayback::Start() +{ + if (m_workerRunning.load()) + { + return true; + } + + HRESULT hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + if (hr == RPC_E_CHANGED_MODE) + { + hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); + } + m_impl->comInitialised = SUCCEEDED(hr); + + hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, + CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), + reinterpret_cast(&m_impl->deviceEnum)); + if (FAILED(hr)) { Stop(); return false; } + + hr = m_impl->deviceEnum->GetDefaultAudioEndpoint(eRender, + eCommunications, &m_impl->device); + if (FAILED(hr) || m_impl->device == nullptr) + { + hr = m_impl->deviceEnum->GetDefaultAudioEndpoint(eRender, + eConsole, &m_impl->device); + if (FAILED(hr) || m_impl->device == nullptr) + { + Stop(); + return false; + } + } + + hr = m_impl->device->Activate(__uuidof(IAudioClient), CLSCTX_ALL, + nullptr, + reinterpret_cast(&m_impl->audioClient)); + if (FAILED(hr)) { Stop(); return false; } + + hr = m_impl->audioClient->GetMixFormat(&m_impl->mixFormat); + if (FAILED(hr) || m_impl->mixFormat == nullptr) { Stop(); return false; } + + m_impl->endpointSampleRate = m_impl->mixFormat->nSamplesPerSec; + m_impl->endpointChannels = m_impl->mixFormat->nChannels; + m_impl->endpointBitsPerSample = m_impl->mixFormat->wBitsPerSample; + m_impl->endpointIsFloat = false; + if (m_impl->mixFormat->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) + { + m_impl->endpointIsFloat = true; + } + else if (m_impl->mixFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE) + { + auto* ext = reinterpret_cast(m_impl->mixFormat); + if (ext->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT) + { + m_impl->endpointIsFloat = true; + } + } + + const REFERENCE_TIME kBufferDuration = 80 * 10000; // 80 ms + hr = m_impl->audioClient->Initialize( + AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + kBufferDuration, 0, + m_impl->mixFormat, nullptr); + if (FAILED(hr)) { Stop(); return false; } + + m_impl->bufferReady = CreateEventW(nullptr, FALSE, FALSE, nullptr); + if (m_impl->bufferReady == nullptr) { Stop(); return false; } + hr = m_impl->audioClient->SetEventHandle(m_impl->bufferReady); + if (FAILED(hr)) { Stop(); return false; } + + hr = m_impl->audioClient->GetBufferSize(&m_impl->bufferFrameCount); + if (FAILED(hr)) { Stop(); return false; } + + hr = m_impl->audioClient->GetService(__uuidof(IAudioRenderClient), + reinterpret_cast(&m_impl->renderClient)); + if (FAILED(hr)) { Stop(); return false; } + + hr = m_impl->audioClient->Start(); + if (FAILED(hr)) { Stop(); return false; } + + m_workerShouldExit.store(false); + m_workerRunning.store(true); + m_workerThread = std::thread(&VoicePlayback::WorkerLoop, this); + return true; +} + +// -------------------------------------------------------------------- +void VoicePlayback::Stop() +{ + if (m_workerRunning.load()) + { + m_workerShouldExit.store(true); + if (m_impl->bufferReady != nullptr) + { + SetEvent(m_impl->bufferReady); + } + if (m_workerThread.joinable()) + { + m_workerThread.join(); + } + m_workerRunning.store(false); + } + + if (m_impl->audioClient != nullptr) + { + m_impl->audioClient->Stop(); + } + SafeRelease(&m_impl->renderClient); + SafeRelease(&m_impl->audioClient); + SafeRelease(&m_impl->device); + SafeRelease(&m_impl->deviceEnum); + + if (m_impl->mixFormat != nullptr) + { + CoTaskMemFree(m_impl->mixFormat); + m_impl->mixFormat = nullptr; + } + if (m_impl->bufferReady != nullptr) + { + CloseHandle(m_impl->bufferReady); + m_impl->bufferReady = nullptr; + } + if (m_impl->comInitialised) + { + CoUninitialize(); + m_impl->comInitialised = false; + } +} + +// -------------------------------------------------------------------- +void VoicePlayback::InitialisePeerLocked(int64_t senderUserID) +{ + auto it = m_peers.find(senderUserID); + if (it != m_peers.end()) return; + + auto* state = new PeerPlaybackState(); + state->decoder.Initialise(); + state->lastActivity = std::chrono::steady_clock::now(); + m_peers[senderUserID] = state; +} + +// -------------------------------------------------------------------- +void VoicePlayback::SubmitFrame(int64_t senderUserID, uint16_t sequence, + const uint8_t* encoded, int encodedLen) +{ + if (encoded == nullptr || encodedLen <= 0) return; + + std::lock_guard lock(m_peersMutex); + InitialisePeerLocked(senderUserID); + auto* state = m_peers[senderUserID]; + if (state == nullptr || !state->decoder.IsReady()) return; + + // Decode straight away on the submitter's thread. This is fine - + // decode is cheap (~0.1 ms per 20 ms frame) and keeps the audio + // worker loop free of heavy lifting under the mix lock. + PeerPlaybackState::Frame frame; + frame.sequence = sequence; + frame.samples.resize(kSamplesPerFrame); + const int decoded = state->decoder.DecodeFrame(encoded, encodedLen, + frame.samples.data(), + kSamplesPerFrame); + frame.valid = (decoded == kSamplesPerFrame); + + // Insert into queue ordered by sequence. + auto insertPos = std::upper_bound( + state->queue.begin(), state->queue.end(), frame, + [](const PeerPlaybackState::Frame& a, const PeerPlaybackState::Frame& b) { + return static_cast(a.sequence - b.sequence) < 0; + }); + state->queue.insert(insertPos, std::move(frame)); + + // Drop stale frames if queue overflows. + while (static_cast(state->queue.size()) > kMaxJitterFrames) + { + state->queue.pop_front(); + } + + // Mark primed once we have the target jitter depth. + if (!state->primed && + static_cast(state->queue.size()) >= kTargetJitterFrames) + { + state->primed = true; + state->nextExpectedSeq = state->queue.front().sequence; + } + + state->lastActivity = std::chrono::steady_clock::now(); +} + +// -------------------------------------------------------------------- +void VoicePlayback::RemovePeer(int64_t senderUserID) +{ + std::lock_guard lock(m_peersMutex); + auto it = m_peers.find(senderUserID); + if (it != m_peers.end()) + { + delete it->second; + m_peers.erase(it); + } +} + +void VoicePlayback::SetPeerMuted(int64_t senderUserID, bool muted) +{ + std::lock_guard lock(m_peersMutex); + InitialisePeerLocked(senderUserID); + m_peers[senderUserID]->muted.store(muted); +} + +bool VoicePlayback::IsPeerMuted(int64_t senderUserID) +{ + std::lock_guard lock(m_peersMutex); + auto it = m_peers.find(senderUserID); + return (it != m_peers.end()) ? it->second->muted.load() : false; +} + +void VoicePlayback::SetPeerVolume(int64_t senderUserID, float volume) +{ + if (volume < 0.0f) volume = 0.0f; + if (volume > 2.0f) volume = 2.0f; + std::lock_guard lock(m_peersMutex); + InitialisePeerLocked(senderUserID); + m_peers[senderUserID]->volume.store(volume); +} + +float VoicePlayback::GetPeerVolume(int64_t senderUserID) +{ + std::lock_guard lock(m_peersMutex); + auto it = m_peers.find(senderUserID); + return (it != m_peers.end()) ? it->second->volume.load() : 1.0f; +} + +void VoicePlayback::SetGlobalVolume(float volume) +{ + if (volume < 0.0f) volume = 0.0f; + if (volume > 2.0f) volume = 2.0f; + m_globalVolume.store(volume); +} + +bool VoicePlayback::IsPeerSpeaking(int64_t senderUserID) +{ + std::lock_guard lock(m_peersMutex); + auto it = m_peers.find(senderUserID); + if (it == m_peers.end()) return false; + + const auto now = std::chrono::steady_clock::now(); + const auto ms = std::chrono::duration_cast( + now - it->second->lastActivity).count(); + return static_cast(ms) < kSpeakingTimeoutMs; +} + +void VoicePlayback::ClearAllPeers() +{ + std::lock_guard lock(m_peersMutex); + for (auto& kv : m_peers) delete kv.second; + m_peers.clear(); +} + +// -------------------------------------------------------------------- +// Worker loop: pulls one chunk of mixed audio per WASAPI event. +// -------------------------------------------------------------------- +void VoicePlayback::WorkerLoop() +{ + DWORD taskIndex = 0; + HANDLE mmcssHandle = AvSetMmThreadCharacteristicsW(L"Audio", &taskIndex); + + const int dstRate = m_impl->endpointSampleRate; + const int dstChannels = m_impl->endpointChannels; + const bool dstIsFloat = m_impl->endpointIsFloat; + const int dstBits = m_impl->endpointBitsPerSample; + + // Upsample ratio from 48 kHz source to endpoint. + const double rateRatio = + static_cast(kSampleRate) / static_cast(dstRate); + + // Per-frame mono mix buffer (48 kHz int16). + std::vector monoMix; + monoMix.reserve(kSamplesPerFrame * 4); + + // Carry for fractional resample position. + double readPos = 0.0; + + while (!m_workerShouldExit.load()) + { + DWORD waitResult = WaitForSingleObject(m_impl->bufferReady, 200); + if (m_workerShouldExit.load()) break; + if (waitResult != WAIT_OBJECT_0) continue; + + UINT32 padding = 0; + if (FAILED(m_impl->audioClient->GetCurrentPadding(&padding))) continue; + UINT32 framesAvailable = m_impl->bufferFrameCount - padding; + if (framesAvailable == 0) continue; + + // How many source (48k mono) samples do we need for this chunk? + const int needSrcSamples = + static_cast(framesAvailable * rateRatio) + 2; + + // Ensure the mono mix has enough samples by pulling frames + // from each peer's jitter buffer. + while (static_cast(monoMix.size()) < needSrcSamples) + { + // Mix one 20 ms (kSamplesPerFrame) chunk across all peers. + int32_t accum[kSamplesPerFrame] = {0}; + const float globalVolume = m_globalVolume.load(); + + { + std::lock_guard lock(m_peersMutex); + for (auto& kv : m_peers) + { + auto* peer = kv.second; + if (peer == nullptr || !peer->primed) continue; + if (peer->muted.load()) continue; + if (peer->queue.empty()) continue; + + // Pop the next expected frame. If the head doesn't + // match the expected seq, treat as lost and let the + // decoder produce PLC output. + PeerPlaybackState::Frame frame; + const auto& head = peer->queue.front(); + if (head.sequence == peer->nextExpectedSeq) + { + frame = std::move(peer->queue.front()); + peer->queue.pop_front(); + } + else + { + // Missing frame - synthesise replacement. + frame.samples.resize(kSamplesPerFrame); + peer->decoder.DecodeLostFrame(frame.samples.data(), + kSamplesPerFrame); + frame.valid = true; + } + peer->nextExpectedSeq++; + + if (frame.valid && + static_cast(frame.samples.size()) == kSamplesPerFrame) + { + // Per-peer volume fold-in. Skip the multiply + // entirely when the combined gain is unity so + // we don't pay for the floating point in the + // common case. + const float peerVolume = peer->volume.load(); + const float combined = peerVolume * globalVolume; + if (combined == 1.0f) + { + for (int i = 0; i < kSamplesPerFrame; ++i) + { + accum[i] += frame.samples[i]; + } + } + else if (combined != 0.0f) + { + for (int i = 0; i < kSamplesPerFrame; ++i) + { + accum[i] += static_cast( + static_cast(frame.samples[i]) * combined); + } + } + } + + // Un-prime if buffer drains completely so we + // re-buffer on the next talk-spurt. + if (peer->queue.empty()) peer->primed = false; + } + } + + for (int i = 0; i < kSamplesPerFrame; ++i) + { + int v = accum[i]; + if (v > 32767) v = 32767; + if (v < -32768) v = -32768; + monoMix.push_back(static_cast(v)); + } + } + + // Now resample + format-convert + fill the WASAPI buffer. + BYTE* renderBuffer = nullptr; + HRESULT hr = m_impl->renderClient->GetBuffer(framesAvailable, &renderBuffer); + if (FAILED(hr) || renderBuffer == nullptr) continue; + + for (UINT32 outFrame = 0; outFrame < framesAvailable; ++outFrame) + { + const int srcIdx = static_cast(readPos); + int16_t monoSample = 0; + if (srcIdx >= 0 && srcIdx < static_cast(monoMix.size())) + { + monoSample = monoMix[srcIdx]; + } + readPos += rateRatio; + + // Write into all destination channels. + for (int ch = 0; ch < dstChannels; ++ch) + { + if (dstIsFloat && dstBits == 32) + { + float* dst = reinterpret_cast(renderBuffer); + dst[outFrame * dstChannels + ch] = + static_cast(monoSample) / 32768.0f; + } + else if (!dstIsFloat && dstBits == 16) + { + int16_t* dst = reinterpret_cast(renderBuffer); + dst[outFrame * dstChannels + ch] = monoSample; + } + else if (!dstIsFloat && dstBits == 32) + { + int32_t* dst = reinterpret_cast(renderBuffer); + dst[outFrame * dstChannels + ch] = + static_cast(monoSample) << 16; + } + // Other formats not supported - silent. + } + } + + // Shift consumed samples out of monoMix. + const int consumed = + static_cast(readPos); + if (consumed > 0) + { + if (consumed >= static_cast(monoMix.size())) + { + monoMix.clear(); + readPos = 0.0; + } + else + { + monoMix.erase(monoMix.begin(), monoMix.begin() + consumed); + readPos -= static_cast(consumed); + } + } + + m_impl->renderClient->ReleaseBuffer(framesAvailable, 0); + } + + if (mmcssHandle != nullptr) + { + AvRevertMmThreadCharacteristics(mmcssHandle); + } +} + +} // namespace Voice + +#endif // ENABLE_VOICE_CHAT diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/Voice/VoiceSlashCommands.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/Voice/VoiceSlashCommands.cpp new file mode 100644 index 00000000000..3cbd3f2b5e7 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/Voice/VoiceSlashCommands.cpp @@ -0,0 +1,336 @@ +// VoiceSlashCommands.cpp +// +// See VoiceSlashCommands.h for the role of this file. +// +// This is the single source of truth for /voice chat commands. Both the +// in-game chat (InGameChat.cpp) and the Generals Online lobby chat +// (WOLGameSetupMenu.cpp) delegate here so the command set stays in sync. + +#include "PreRTS.h" + +#include "GameNetwork/GeneralsOnline/Voice/VoiceSlashCommands.h" + +#ifdef ENABLE_VOICE_CHAT + +#include "GameNetwork/GeneralsOnline/Voice/VoiceManager.h" +#include "GameNetwork/GeneralsOnline/OnlineServices_Init.h" +#include "GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.h" +#include "GameNetwork/GeneralsOnline/GeneralsOnline_Settings.h" +#include "GameNetwork/GeneralsOnline/NetworkMesh.h" + +#include +#include +#include +#include +#include +#include + +namespace +{ + // ------------------------------------------------------------ + // Small helpers + // ------------------------------------------------------------ + void Emit(const std::function& out, + const wchar_t* msg) + { + out(UnicodeString(msg)); + } + + void EmitFmt(const std::function& out, + const wchar_t* fmt, ...) + { + UnicodeString s; + va_list ap; + va_start(ap, fmt); + s.format_va(fmt, ap); + va_end(ap); + out(s); + } + + // Convert a narrow (UTF-8 / ASCII) std::string into UnicodeString for + // chat output. + UnicodeString WideFromNarrow(const std::string& narrow) + { + std::wstring w; + w.reserve(narrow.size()); + for (char c : narrow) w.push_back(static_cast(static_cast(c))); + return UnicodeString(w.c_str()); + } + + bool ParseInt64(const char* s, int64_t& out) + { + if (s == nullptr || *s == 0) return false; + char* end = nullptr; + long long v = std::strtoll(s, &end, 10); + if (end == s) return false; + out = static_cast(v); + return true; + } + + bool ParseFloat(const char* s, float& out) + { + if (s == nullptr || *s == 0) return false; + char* end = nullptr; + double v = std::strtod(s, &end); + if (end == s) return false; + out = static_cast(v); + return true; + } + + bool ParseInt(const char* s, int& out) + { + if (s == nullptr || *s == 0) return false; + char* end = nullptr; + long v = std::strtol(s, &end, 10); + if (end == s) return false; + out = static_cast(v); + return true; + } + + // Look up a display name for a peer user id, if the lobby knows it. + // Falls back to an empty string. + std::string LookupPeerName(int64_t userID) + { + NGMP_OnlineServices_LobbyInterface* pLobby = + NGMP_OnlineServicesManager::GetInterface(); + if (pLobby == nullptr) return {}; + LobbyMemberEntry e = pLobby->GetRoomMemberFromID(userID); + if (e.user_id == -1) return {}; + return e.display_name; + } + + // Case-insensitive ASCII comparison. Display names in NGMP are UTF-8 + // but for the /voice command we only need rough equivalence; real + // collisions are resolved through the ambiguous-match error path + // below, which forces the user to disambiguate via userID. + bool IEqualsAscii(const std::string& a, const std::string& b) + { + if (a.size() != b.size()) return false; + for (size_t i = 0; i < a.size(); ++i) + { + unsigned char ca = static_cast(a[i]); + unsigned char cb = static_cast(b[i]); + if (ca >= 'A' && ca <= 'Z') ca = static_cast(ca + ('a' - 'A')); + if (cb >= 'A' && cb <= 'Z') cb = static_cast(cb + ('a' - 'A')); + if (ca != cb) return false; + } + return true; + } + + // Resolve an arg string to a single peer user id. + // + // - If the arg parses as a positive integer, we treat it as a + // userID directly. The caller gets that id back regardless of + // whether that user is currently in our lobby: this lets the + // user unmute a saved id for someone who already left. + // + // - Otherwise we scan the CURRENT lobby's member list for a + // display-name match (case-insensitive ASCII). Exactly one match + // -> success. Zero matches -> NotFound. More than one match -> + // Ambiguous. The caller is expected to report the error. + // + // IMPORTANT: Name-based lookup only ever resolves within the lobby + // we can see RIGHT NOW. Two different NGMP accounts that happen to + // share a display name therefore cannot be muted by accident, as + // long as they are not both in the current lobby - the second + // account will simply never match until the user adds it by id. + enum class PeerResolveResult { Ok, NotFound, Ambiguous, LobbyUnavailable }; + + PeerResolveResult ResolvePeerArg(const char* arg, int64_t& outID, + std::string& outName, + int& outMatchCount) + { + outID = 0; + outName.clear(); + outMatchCount = 0; + if (arg == nullptr || *arg == 0) return PeerResolveResult::NotFound; + + // Numeric path: accept any positive 64-bit int as a raw userID. + int64_t parsed = 0; + if (ParseInt64(arg, parsed) && parsed > 0) + { + outID = parsed; + outName = LookupPeerName(parsed); // may be empty + outMatchCount = 1; + return PeerResolveResult::Ok; + } + + // Name path: must consult the current lobby. + NGMP_OnlineServices_LobbyInterface* pLobby = + NGMP_OnlineServicesManager::GetInterface(); + if (pLobby == nullptr) return PeerResolveResult::LobbyUnavailable; + + const std::string target(arg); + std::vector& members = pLobby->GetMembersListForCurrentRoom(); + int64_t firstID = 0; + std::string firstName; + for (const LobbyMemberEntry& m : members) + { + if (m.user_id <= 0) continue; + if (!IEqualsAscii(m.display_name, target)) continue; + ++outMatchCount; + if (outMatchCount == 1) + { + firstID = m.user_id; + firstName = m.display_name; + } + } + + if (outMatchCount == 0) return PeerResolveResult::NotFound; + if (outMatchCount > 1) return PeerResolveResult::Ambiguous; + + outID = firstID; + outName = firstName; + return PeerResolveResult::Ok; + } + + // ------------------------------------------------------------ + // Sub-command implementations + // ------------------------------------------------------------ + void CmdHelp(const std::function& out) + { + Emit(out, L"Voice chat commands:"); + Emit(out, L" /voice status - show current settings"); + Emit(out, L" /voice mics - list microphone devices"); + Emit(out, L" /voice mic - select microphone by number"); + Emit(out, L" /voice gain <0.0-4.0> - set microphone input gain"); + Emit(out, L" /voice volume <0.0-2.0> - set master voice volume"); + Emit(out, L" /voice peers - list remote voice peers"); + Emit(out, L" /voice mute - mute a peer (persistent)"); + Emit(out, L" /voice unmute - unmute a peer (persistent)"); + Emit(out, L" /voice mutes - list saved ignores"); + Emit(out, L" /voice peervol <0-2> - set per-peer volume"); + Emit(out, L"Push-to-talk: hold LEFT ALT while speaking."); + } + + void CmdStatus(const std::function& out) + { + if (TheVoiceManager == nullptr) + { + Emit(out, L"Voice subsystem is not initialised."); + return; + } + + Voice::VoiceMode mode = TheVoiceManager->GetMode(); + const wchar_t* modeStr = L"disabled"; + switch (mode) + { + case Voice::VoiceMode::LOBBY_ALL: modeStr = L"lobby (all)"; break; + case Voice::VoiceMode::IN_GAME_TEAM: modeStr = L"in-game (team)"; break; + case Voice::VoiceMode::DISABLED: modeStr = L"disabled"; break; + } + + const std::wstring& dev = TheVoiceManager->GetCaptureDevice(); + EmitFmt(out, L"Voice: mode=%ls mic='%ls' gain=%.2f volume=%.2f", + modeStr, + dev.empty() ? L"" : dev.c_str(), + TheVoiceManager->GetMicGain(), + TheVoiceManager->GetGlobalVoiceVolume()); + } + + void CmdListMics(const std::function& out) + { + std::vector devices = + Voice::VoiceManager::EnumerateCaptureDevices(); + if (devices.empty()) + { + Emit(out, L"No capture devices found."); + return; + } + EmitFmt(out, L"%d capture device(s):", (int)devices.size()); + + const std::wstring& currentID = + TheVoiceManager ? TheVoiceManager->GetCaptureDevice() : std::wstring(); + + for (size_t i = 0; i < devices.size(); ++i) + { + const Voice::CaptureDeviceInfo& d = devices[i]; + const bool selected = + (!currentID.empty() && currentID == d.id) || + (currentID.empty() && d.isDefaultCommunications); + + EmitFmt(out, L" %d%ls %ls%ls%ls", + (int)(i + 1), + selected ? L" *" : L" ", + d.friendlyName.c_str(), + d.isDefaultCommunications ? L" [default comms]" : L"", + d.isDefaultConsole ? L" [default console]" : L""); + } + Emit(out, L"Use /voice mic to select one."); + } + + void CmdSelectMic(int index, const std::function& out) + { + std::vector devices = + Voice::VoiceManager::EnumerateCaptureDevices(); + if (index < 1 || index > static_cast(devices.size())) + { + EmitFmt(out, L"Invalid device number. Use /voice mics to see the list (1..%d).", + (int)devices.size()); + return; + } + const Voice::CaptureDeviceInfo& d = devices[index - 1]; + + if (TheVoiceManager != nullptr) + { + TheVoiceManager->SetCaptureDevice(d.id); + } + + // Persist to JSON settings. + NGMP_OnlineServicesManager::Settings.Save_Voice_CaptureDeviceID(d.id); + + EmitFmt(out, L"Microphone set to: %ls", d.friendlyName.c_str()); + } + + void CmdGain(float gain, const std::function& out) + { + if (gain < 0.0f) gain = 0.0f; + if (gain > 4.0f) gain = 4.0f; + if (TheVoiceManager != nullptr) + { + TheVoiceManager->SetMicGain(gain); + } + NGMP_OnlineServicesManager::Settings.Save_Voice_MicGain(gain); + EmitFmt(out, L"Microphone gain set to %.2f", gain); + } + + void CmdVolume(float volume, const std::function& out) + { + if (volume < 0.0f) volume = 0.0f; + if (volume > 2.0f) volume = 2.0f; + if (TheVoiceManager != nullptr) + { + TheVoiceManager->SetGlobalVoiceVolume(volume); + } + NGMP_OnlineServicesManager::Settings.Save_Voice_GlobalVolume(volume); + EmitFmt(out, L"Master voice volume set to %.2f", volume); + } + + void CmdPeers(const std::function& out) + { + NetworkMesh* pMesh = NGMP_OnlineServicesManager::GetNetworkMesh(); + if (pMesh == nullptr) + { + Emit(out, L"No active network mesh (not in a lobby/match)."); + return; + } + std::map& conns = pMesh->GetAllConnections(); + if (conns.empty()) + { + Emit(out, L"No remote peers connected."); + return; + } + EmitFmt(out, L"%d peer(s):", (int)conns.size()); + for (auto& kv : conns) + { + const int64_t id = kv.first; + std::string name = LookupPeerName(id); + const bool muted = TheVoiceManager ? TheVoiceManager->IsPeerMuted(id) : false; + const float pvol = TheVoiceManager ? TheVoiceManager->GetPeerVolume(id) : 1.0f; + const bool speaking = TheVoiceManager ? TheVoiceManager->IsPeerSpeaking(id) : false; + + EmitFmt(out, L" %lld %ls vol=%.2f%ls%ls", + (long long)id, + name.empty() ? L"" : WideFromNarrow(name).str(), + pvol, + muted ? L" [m \ No newline at end of file diff --git a/vcpkg.json b/vcpkg.json index 011b913c8aa..0137d05dc52 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,8 +1,9 @@ -{ - "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", - "builtin-baseline": "b02e341c927f16d991edbd915d8ea43eac52096c", - "dependencies": [ - "zlib", - "ffmpeg" - ] - } \ No newline at end of file +{ + "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", + "builtin-baseline": "b02e341c927f16d991edbd915d8ea43eac52096c", + "dependencies": [ + "zlib", + "ffmpeg", + "opus" + ] + } \ No newline at end of file