From e33e19d0587146859d48a134ec9fd94e7b7ba5cd Mon Sep 17 00:00:00 2001 From: "FWoltermann@gmail.com" Date: Thu, 8 Dec 2011 14:53:40 +0000 Subject: Initial upload --- Stars45/Asteroid.cpp | 116 + Stars45/Asteroid.h | 35 + Stars45/AudDlg.cpp | 201 ++ Stars45/AudDlg.h | 79 + Stars45/AudioConfig.cpp | 382 +++ Stars45/AudioConfig.h | 71 + Stars45/Authorization.cpp | 202 ++ Stars45/Authorization.h | 30 + Stars45/AwardDlg.cpp | 152 + Stars45/AwardDlg.h | 59 + Stars45/AwardShowDlg.cpp | 159 ++ Stars45/AwardShowDlg.h | 63 + Stars45/BaseScreen.h | 83 + Stars45/Callsign.cpp | 102 + Stars45/Callsign.h | 29 + Stars45/CameraDirector.cpp | 1196 ++++++++ Stars45/CameraDirector.h | 156 + Stars45/Campaign.cpp | 2347 ++++++++++++++++ Stars45/Campaign.h | 282 ++ Stars45/CampaignMissionFighter.cpp | 2152 ++++++++++++++ Stars45/CampaignMissionFighter.h | 121 + Stars45/CampaignMissionRequest.cpp | 39 + Stars45/CampaignMissionRequest.h | 84 + Stars45/CampaignMissionStarship.cpp | 1405 ++++++++++ Stars45/CampaignMissionStarship.h | 107 + Stars45/CampaignPlan.h | 58 + Stars45/CampaignPlanAssignment.cpp | 158 ++ Stars45/CampaignPlanAssignment.h | 50 + Stars45/CampaignPlanEvent.cpp | 1315 +++++++++ Stars45/CampaignPlanEvent.h | 72 + Stars45/CampaignPlanMission.cpp | 374 +++ Stars45/CampaignPlanMission.h | 58 + Stars45/CampaignPlanMovement.cpp | 168 ++ Stars45/CampaignPlanMovement.h | 44 + Stars45/CampaignPlanStrategic.cpp | 483 ++++ Stars45/CampaignPlanStrategic.h | 59 + Stars45/CampaignSaveGame.cpp | 729 +++++ Stars45/CampaignSaveGame.h | 69 + Stars45/CampaignSituationReport.cpp | 416 +++ Stars45/CampaignSituationReport.h | 59 + Stars45/CarrierAI.cpp | 400 +++ Stars45/CarrierAI.h | 64 + Stars45/CmdDlg.cpp | 186 ++ Stars45/CmdDlg.h | 83 + Stars45/CmdForceDlg.cpp | 718 +++++ Stars45/CmdForceDlg.h | 69 + Stars45/CmdIntelDlg.cpp | 393 +++ Stars45/CmdIntelDlg.h | 76 + Stars45/CmdMissionsDlg.cpp | 325 +++ Stars45/CmdMissionsDlg.h | 65 + Stars45/CmdMsgDlg.cpp | 92 + Stars45/CmdMsgDlg.h | 54 + Stars45/CmdOrdersDlg.cpp | 139 + Stars45/CmdOrdersDlg.h | 56 + Stars45/CmdTheaterDlg.cpp | 216 ++ Stars45/CmdTheaterDlg.h | 62 + Stars45/CmdTitleDlg.cpp | 78 + Stars45/CmdTitleDlg.h | 56 + Stars45/CmpCompleteDlg.cpp | 108 + Stars45/CmpCompleteDlg.h | 50 + Stars45/CmpFileDlg.cpp | 191 ++ Stars45/CmpFileDlg.h | 64 + Stars45/CmpLoadDlg.cpp | 118 + Stars45/CmpLoadDlg.h | 46 + Stars45/CmpSceneDlg.cpp | 188 ++ Stars45/CmpSceneDlg.h | 68 + Stars45/CmpSelectDlg.cpp | 623 ++++ Stars45/CmpSelectDlg.h | 91 + Stars45/CmpnScreen.cpp | 647 +++++ Stars45/CmpnScreen.h | 137 + Stars45/CombatAction.cpp | 352 +++ Stars45/CombatAction.h | 205 ++ Stars45/CombatAssignment.cpp | 68 + Stars45/CombatAssignment.h | 57 + Stars45/CombatEvent.cpp | 153 + Stars45/CombatEvent.h | 122 + Stars45/CombatGroup.cpp | 1589 +++++++++++ Stars45/CombatGroup.h | 231 ++ Stars45/CombatRoster.cpp | 93 + Stars45/CombatRoster.h | 48 + Stars45/CombatUnit.cpp | 402 +++ Stars45/CombatUnit.h | 134 + Stars45/CombatZone.cpp | 282 ++ Stars45/CombatZone.h | 103 + Stars45/Combatant.cpp | 172 ++ Stars45/Combatant.h | 73 + Stars45/Component.cpp | 177 ++ Stars45/Component.h | 100 + Stars45/Computer.cpp | 93 + Stars45/Computer.h | 41 + Stars45/ConfirmDlg.cpp | 150 + Stars45/ConfirmDlg.h | 64 + Stars45/Contact.cpp | 365 +++ Stars45/Contact.h | 91 + Stars45/CtlDlg.cpp | 461 +++ Stars45/CtlDlg.h | 119 + Stars45/DebriefDlg.cpp | 375 +++ Stars45/DebriefDlg.h | 81 + Stars45/Debris.cpp | 220 ++ Stars45/Debris.h | 43 + Stars45/DetailSet.cpp | 228 ++ Stars45/DetailSet.h | 73 + Stars45/DisplayView.cpp | 239 ++ Stars45/DisplayView.h | 71 + Stars45/Drive.cpp | 497 ++++ Stars45/Drive.h | 93 + Stars45/DriveSprite.cpp | 143 + Stars45/DriveSprite.h | 47 + Stars45/Drone.cpp | 196 ++ Stars45/Drone.h | 69 + Stars45/DropShipAI.cpp | 122 + Stars45/DropShipAI.h | 47 + Stars45/Element.cpp | 667 +++++ Stars45/Element.h | 172 ++ Stars45/EngDlg.cpp | 1043 +++++++ Stars45/EngDlg.h | 106 + Stars45/ExceptionHandler.cpp | 429 +++ Stars45/ExitDlg.cpp | 152 + Stars45/ExitDlg.h | 60 + Stars45/Explosion.cpp | 611 ++++ Stars45/Explosion.h | 84 + Stars45/Farcaster.cpp | 288 ++ Stars45/Farcaster.h | 92 + Stars45/FighterAI.cpp | 1831 ++++++++++++ Stars45/FighterAI.h | 85 + Stars45/FighterTacticalAI.cpp | 562 ++++ Stars45/FighterTacticalAI.h | 56 + Stars45/FirstTimeDlg.cpp | 147 + Stars45/FirstTimeDlg.h | 56 + Stars45/FlightComp.cpp | 305 ++ Stars45/FlightComp.h | 63 + Stars45/FlightDeck.cpp | 1186 ++++++++ Stars45/FlightDeck.h | 195 ++ Stars45/FlightPlanner.cpp | 372 +++ Stars45/FlightPlanner.h | 51 + Stars45/FltDlg.cpp | 1084 +++++++ Stars45/FltDlg.h | 81 + Stars45/Galaxy.cpp | 283 ++ Stars45/Galaxy.h | 74 + Stars45/GameScreen.cpp | 1254 +++++++++ Stars45/GameScreen.h | 192 ++ Stars45/Grid.cpp | 92 + Stars45/Grid.h | 50 + Stars45/GroundAI.cpp | 191 ++ Stars45/GroundAI.h | 61 + Stars45/HUDSounds.cpp | 118 + Stars45/HUDSounds.h | 46 + Stars45/HUDView.cpp | 4364 ++++++++++++++++++++++++++++ Stars45/HUDView.h | 218 ++ Stars45/Hangar.cpp | 933 ++++++ Stars45/Hangar.h | 127 + Stars45/HardPoint.cpp | 104 + Stars45/HardPoint.h | 82 + Stars45/Hoop.cpp | 156 + Stars45/Hoop.h | 45 + Stars45/Instruction.cpp | 569 ++++ Stars45/Instruction.h | 166 ++ Stars45/Intel.cpp | 46 + Stars45/Intel.h | 37 + Stars45/JoyDlg.cpp | 230 ++ Stars45/JoyDlg.h | 56 + Stars45/KeyDlg.cpp | 199 ++ Stars45/KeyDlg.h | 65 + Stars45/KeyMap.cpp | 824 ++++++ Stars45/KeyMap.h | 181 ++ Stars45/LandingGear.cpp | 278 ++ Stars45/LandingGear.h | 66 + Stars45/LoadDlg.cpp | 77 + Stars45/LoadDlg.h | 41 + Stars45/LoadScreen.cpp | 163 ++ Stars45/LoadScreen.h | 64 + Stars45/Main.cpp | 186 ++ Stars45/MapView.cpp | 3478 +++++++++++++++++++++++ Stars45/MapView.h | 223 ++ Stars45/MenuDlg.cpp | 276 ++ Stars45/MenuDlg.h | 86 + Stars45/MenuScreen.cpp | 1076 +++++++ Stars45/MenuScreen.h | 199 ++ Stars45/MenuView.cpp | 333 +++ Stars45/MenuView.h | 77 + Stars45/Mfd.cpp | 1403 +++++++++ Stars45/Mfd.h | 104 + Stars45/Mission.cpp | 2160 ++++++++++++++ Stars45/Mission.h | 426 +++ Stars45/MissionEvent.cpp | 874 ++++++ Stars45/MissionEvent.h | 167 ++ Stars45/MissionTemplate.cpp | 846 ++++++ Stars45/MissionTemplate.h | 117 + Stars45/ModConfig.cpp | 381 +++ Stars45/ModConfig.h | 75 + Stars45/ModDlg.cpp | 350 +++ Stars45/ModDlg.h | 94 + Stars45/ModInfo.cpp | 292 ++ Stars45/ModInfo.h | 122 + Stars45/ModInfoDlg.cpp | 115 + Stars45/ModInfoDlg.h | 65 + Stars45/MsnDlg.cpp | 303 ++ Stars45/MsnDlg.h | 73 + Stars45/MsnEditDlg.cpp | 896 ++++++ Stars45/MsnEditDlg.h | 116 + Stars45/MsnEditNavDlg.cpp | 300 ++ Stars45/MsnEditNavDlg.h | 78 + Stars45/MsnElemDlg.cpp | 805 ++++++ Stars45/MsnElemDlg.h | 99 + Stars45/MsnEventDlg.cpp | 416 +++ Stars45/MsnEventDlg.h | 86 + Stars45/MsnNavDlg.cpp | 104 + Stars45/MsnNavDlg.h | 54 + Stars45/MsnObjDlg.cpp | 316 +++ Stars45/MsnObjDlg.h | 68 + Stars45/MsnPkgDlg.cpp | 336 +++ Stars45/MsnPkgDlg.h | 66 + Stars45/MsnSelectDlg.cpp | 499 ++++ Stars45/MsnSelectDlg.h | 81 + Stars45/MsnWepDlg.cpp | 517 ++++ Stars45/MsnWepDlg.h | 86 + Stars45/MusicDirector.cpp | 520 ++++ Stars45/MusicDirector.h | 114 + Stars45/MusicTrack.cpp | 273 ++ Stars45/MusicTrack.h | 77 + Stars45/NPClient.h | 190 ++ Stars45/NPClientWraps.cpp | 257 ++ Stars45/NPClientWraps.h | 33 + Stars45/NavAI.cpp | 622 ++++ Stars45/NavAI.h | 74 + Stars45/NavDlg.cpp | 1126 ++++++++ Stars45/NavDlg.h | 119 + Stars45/NavLight.cpp | 188 ++ Stars45/NavLight.h | 67 + Stars45/NavSystem.cpp | 113 + Stars45/NavSystem.h | 55 + Stars45/NetAddrDlg.cpp | 160 ++ Stars45/NetAddrDlg.h | 60 + Stars45/NetAdminChat.cpp | 122 + Stars45/NetAdminChat.h | 33 + Stars45/NetAdminServer.cpp | 887 ++++++ Stars45/NetAdminServer.h | 91 + Stars45/NetAuth.cpp | 216 ++ Stars45/NetAuth.h | 51 + Stars45/NetBrokerClient.cpp | 239 ++ Stars45/NetBrokerClient.h | 43 + Stars45/NetChat.cpp | 53 + Stars45/NetChat.h | 52 + Stars45/NetClientConfig.cpp | 332 +++ Stars45/NetClientConfig.h | 73 + Stars45/NetClientDlg.cpp | 431 +++ Stars45/NetClientDlg.h | 80 + Stars45/NetData.cpp | 1453 ++++++++++ Stars45/NetData.h | 966 +++++++ Stars45/NetFileServlet.cpp | 118 + Stars45/NetFileServlet.h | 50 + Stars45/NetGame.cpp | 330 +++ Stars45/NetGame.h | 127 + Stars45/NetGameClient.cpp | 1069 +++++++ Stars45/NetGameClient.h | 87 + Stars45/NetGameServer.cpp | 1199 ++++++++ Stars45/NetGameServer.h | 90 + Stars45/NetLobby.cpp | 630 +++++ Stars45/NetLobby.h | 289 ++ Stars45/NetLobbyClient.cpp | 809 ++++++ Stars45/NetLobbyClient.h | 104 + Stars45/NetLobbyDlg.cpp | 475 ++++ Stars45/NetLobbyDlg.h | 89 + Stars45/NetLobbyServer.cpp | 1359 +++++++++ Stars45/NetLobbyServer.h | 119 + Stars45/NetPacket.cpp | 356 +++ Stars45/NetPacket.h | 74 + Stars45/NetPassDlg.cpp | 143 + Stars45/NetPassDlg.h | 58 + Stars45/NetPlayer.cpp | 464 +++ Stars45/NetPlayer.h | 98 + Stars45/NetServerConfig.cpp | 463 +++ Stars45/NetServerConfig.h | 99 + Stars45/NetServerDlg.cpp | 179 ++ Stars45/NetServerDlg.h | 64 + Stars45/NetUnitDlg.cpp | 641 +++++ Stars45/NetUnitDlg.h | 93 + Stars45/NetUser.cpp | 117 + Stars45/NetUser.h | 112 + Stars45/NetUtil.cpp | 424 +++ Stars45/NetUtil.h | 56 + Stars45/OptDlg.cpp | 322 +++ Stars45/OptDlg.h | 87 + Stars45/PlanScreen.cpp | 395 +++ Stars45/PlanScreen.h | 108 + Stars45/Player.cpp | 1552 ++++++++++ Stars45/Player.h | 187 ++ Stars45/PlayerDlg.cpp | 389 +++ Stars45/PlayerDlg.h | 86 + Stars45/Power.cpp | 300 ++ Stars45/Power.h | 62 + Stars45/QuantumDrive.cpp | 246 ++ Stars45/QuantumDrive.h | 73 + Stars45/QuantumFlash.cpp | 175 ++ Stars45/QuantumFlash.h | 60 + Stars45/QuantumView.cpp | 318 +++ Stars45/QuantumView.h | 73 + Stars45/QuitView.cpp | 302 ++ Stars45/QuitView.h | 64 + Stars45/RLoc.cpp | 88 + Stars45/RLoc.h | 71 + Stars45/RadioHandler.cpp | 561 ++++ Stars45/RadioHandler.h | 53 + Stars45/RadioMessage.cpp | 165 ++ Stars45/RadioMessage.h | 155 + Stars45/RadioTraffic.cpp | 391 +++ Stars45/RadioTraffic.h | 65 + Stars45/RadioView.cpp | 640 +++++ Stars45/RadioView.h | 87 + Stars45/RadioVox.cpp | 249 ++ Stars45/RadioVox.h | 59 + Stars45/SeekerAI.cpp | 257 ++ Stars45/SeekerAI.h | 73 + Stars45/Sensor.cpp | 850 ++++++ Stars45/Sensor.h | 86 + Stars45/Shield.cpp | 259 ++ Stars45/Shield.h | 77 + Stars45/ShieldRep.cpp | 250 ++ Stars45/ShieldRep.h | 48 + Stars45/Ship.cpp | 5305 +++++++++++++++++++++++++++++++++++ Stars45/Ship.h | 583 ++++ Stars45/ShipAI.cpp | 1358 +++++++++ Stars45/ShipAI.h | 153 + Stars45/ShipCtrl.cpp | 341 +++ Stars45/ShipCtrl.h | 59 + Stars45/ShipDesign.cpp | 3700 ++++++++++++++++++++++++ Stars45/ShipDesign.h | 292 ++ Stars45/ShipKiller.cpp | 263 ++ Stars45/ShipKiller.h | 55 + Stars45/ShipSolid.cpp | 96 + Stars45/ShipSolid.h | 52 + Stars45/Shot.cpp | 626 +++++ Stars45/Shot.h | 127 + Stars45/Sim.cpp | 3810 +++++++++++++++++++++++++ Stars45/Sim.h | 330 +++ Stars45/SimEvent.cpp | 262 ++ Stars45/SimEvent.h | 163 ++ Stars45/SimObject.cpp | 150 + Stars45/SimObject.h | 99 + Stars45/Sky.cpp | 726 +++++ Stars45/Sky.h | 105 + Stars45/StarServer.cpp | 545 ++++ Stars45/StarServer.h | 76 + Stars45/StarSystem.cpp | 1974 +++++++++++++ Stars45/StarSystem.h | 322 +++ Stars45/Stars.dsp | 1585 +++++++++++ Stars45/Stars.dsw | 29 + Stars45/Stars.ico | Bin 0 -> 2998 bytes Stars45/Stars.rc | 99 + Stars45/Stars.vcxproj | 522 ++++ Stars45/Stars.vcxproj.filters | 1123 ++++++++ Stars45/Starshatter.cpp | 2903 +++++++++++++++++++ Stars45/Starshatter.h | 225 ++ Stars45/StarshipAI.cpp | 822 ++++++ Stars45/StarshipAI.h | 62 + Stars45/StarshipTacticalAI.cpp | 276 ++ Stars45/StarshipTacticalAI.h | 47 + Stars45/SteerAI.cpp | 453 +++ Stars45/SteerAI.h | 125 + Stars45/System.cpp | 399 +++ Stars45/System.h | 174 ++ Stars45/SystemDesign.cpp | 168 ++ Stars45/SystemDesign.h | 54 + Stars45/TacRefDlg.cpp | 689 +++++ Stars45/TacRefDlg.h | 101 + Stars45/TacticalAI.cpp | 958 +++++++ Stars45/TacticalAI.h | 91 + Stars45/TacticalView.cpp | 1495 ++++++++++ Stars45/TacticalView.h | 120 + Stars45/Terrain.cpp | 561 ++++ Stars45/Terrain.h | 119 + Stars45/TerrainApron.cpp | 336 +++ Stars45/TerrainApron.h | 71 + Stars45/TerrainClouds.cpp | 269 ++ Stars45/TerrainClouds.h | 64 + Stars45/TerrainHaze.cpp | 90 + Stars45/TerrainHaze.h | 46 + Stars45/TerrainLayer.h | 63 + Stars45/TerrainPatch.cpp | 1113 ++++++++ Stars45/TerrainPatch.h | 94 + Stars45/TerrainRegion.cpp | 266 ++ Stars45/TerrainRegion.h | 126 + Stars45/Thruster.cpp | 595 ++++ Stars45/Thruster.h | 97 + Stars45/TrackIR.cpp | 247 ++ Stars45/TrackIR.h | 53 + Stars45/Trail.cpp | 200 ++ Stars45/Trail.h | 60 + Stars45/VidDlg.cpp | 516 ++++ Stars45/VidDlg.h | 106 + Stars45/Weapon.cpp | 1184 ++++++++ Stars45/Weapon.h | 204 ++ Stars45/WeaponDesign.cpp | 726 +++++ Stars45/WeaponDesign.h | 187 ++ Stars45/WeaponGroup.cpp | 348 +++ Stars45/WeaponGroup.h | 106 + Stars45/Weather.cpp | 178 ++ Stars45/Weather.h | 67 + Stars45/WepView.cpp | 531 ++++ Stars45/WepView.h | 88 + Stars45/resource.h | 18 + 401 files changed, 139229 insertions(+) create mode 100644 Stars45/Asteroid.cpp create mode 100644 Stars45/Asteroid.h create mode 100644 Stars45/AudDlg.cpp create mode 100644 Stars45/AudDlg.h create mode 100644 Stars45/AudioConfig.cpp create mode 100644 Stars45/AudioConfig.h create mode 100644 Stars45/Authorization.cpp create mode 100644 Stars45/Authorization.h create mode 100644 Stars45/AwardDlg.cpp create mode 100644 Stars45/AwardDlg.h create mode 100644 Stars45/AwardShowDlg.cpp create mode 100644 Stars45/AwardShowDlg.h create mode 100644 Stars45/BaseScreen.h create mode 100644 Stars45/Callsign.cpp create mode 100644 Stars45/Callsign.h create mode 100644 Stars45/CameraDirector.cpp create mode 100644 Stars45/CameraDirector.h create mode 100644 Stars45/Campaign.cpp create mode 100644 Stars45/Campaign.h create mode 100644 Stars45/CampaignMissionFighter.cpp create mode 100644 Stars45/CampaignMissionFighter.h create mode 100644 Stars45/CampaignMissionRequest.cpp create mode 100644 Stars45/CampaignMissionRequest.h create mode 100644 Stars45/CampaignMissionStarship.cpp create mode 100644 Stars45/CampaignMissionStarship.h create mode 100644 Stars45/CampaignPlan.h create mode 100644 Stars45/CampaignPlanAssignment.cpp create mode 100644 Stars45/CampaignPlanAssignment.h create mode 100644 Stars45/CampaignPlanEvent.cpp create mode 100644 Stars45/CampaignPlanEvent.h create mode 100644 Stars45/CampaignPlanMission.cpp create mode 100644 Stars45/CampaignPlanMission.h create mode 100644 Stars45/CampaignPlanMovement.cpp create mode 100644 Stars45/CampaignPlanMovement.h create mode 100644 Stars45/CampaignPlanStrategic.cpp create mode 100644 Stars45/CampaignPlanStrategic.h create mode 100644 Stars45/CampaignSaveGame.cpp create mode 100644 Stars45/CampaignSaveGame.h create mode 100644 Stars45/CampaignSituationReport.cpp create mode 100644 Stars45/CampaignSituationReport.h create mode 100644 Stars45/CarrierAI.cpp create mode 100644 Stars45/CarrierAI.h create mode 100644 Stars45/CmdDlg.cpp create mode 100644 Stars45/CmdDlg.h create mode 100644 Stars45/CmdForceDlg.cpp create mode 100644 Stars45/CmdForceDlg.h create mode 100644 Stars45/CmdIntelDlg.cpp create mode 100644 Stars45/CmdIntelDlg.h create mode 100644 Stars45/CmdMissionsDlg.cpp create mode 100644 Stars45/CmdMissionsDlg.h create mode 100644 Stars45/CmdMsgDlg.cpp create mode 100644 Stars45/CmdMsgDlg.h create mode 100644 Stars45/CmdOrdersDlg.cpp create mode 100644 Stars45/CmdOrdersDlg.h create mode 100644 Stars45/CmdTheaterDlg.cpp create mode 100644 Stars45/CmdTheaterDlg.h create mode 100644 Stars45/CmdTitleDlg.cpp create mode 100644 Stars45/CmdTitleDlg.h create mode 100644 Stars45/CmpCompleteDlg.cpp create mode 100644 Stars45/CmpCompleteDlg.h create mode 100644 Stars45/CmpFileDlg.cpp create mode 100644 Stars45/CmpFileDlg.h create mode 100644 Stars45/CmpLoadDlg.cpp create mode 100644 Stars45/CmpLoadDlg.h create mode 100644 Stars45/CmpSceneDlg.cpp create mode 100644 Stars45/CmpSceneDlg.h create mode 100644 Stars45/CmpSelectDlg.cpp create mode 100644 Stars45/CmpSelectDlg.h create mode 100644 Stars45/CmpnScreen.cpp create mode 100644 Stars45/CmpnScreen.h create mode 100644 Stars45/CombatAction.cpp create mode 100644 Stars45/CombatAction.h create mode 100644 Stars45/CombatAssignment.cpp create mode 100644 Stars45/CombatAssignment.h create mode 100644 Stars45/CombatEvent.cpp create mode 100644 Stars45/CombatEvent.h create mode 100644 Stars45/CombatGroup.cpp create mode 100644 Stars45/CombatGroup.h create mode 100644 Stars45/CombatRoster.cpp create mode 100644 Stars45/CombatRoster.h create mode 100644 Stars45/CombatUnit.cpp create mode 100644 Stars45/CombatUnit.h create mode 100644 Stars45/CombatZone.cpp create mode 100644 Stars45/CombatZone.h create mode 100644 Stars45/Combatant.cpp create mode 100644 Stars45/Combatant.h create mode 100644 Stars45/Component.cpp create mode 100644 Stars45/Component.h create mode 100644 Stars45/Computer.cpp create mode 100644 Stars45/Computer.h create mode 100644 Stars45/ConfirmDlg.cpp create mode 100644 Stars45/ConfirmDlg.h create mode 100644 Stars45/Contact.cpp create mode 100644 Stars45/Contact.h create mode 100644 Stars45/CtlDlg.cpp create mode 100644 Stars45/CtlDlg.h create mode 100644 Stars45/DebriefDlg.cpp create mode 100644 Stars45/DebriefDlg.h create mode 100644 Stars45/Debris.cpp create mode 100644 Stars45/Debris.h create mode 100644 Stars45/DetailSet.cpp create mode 100644 Stars45/DetailSet.h create mode 100644 Stars45/DisplayView.cpp create mode 100644 Stars45/DisplayView.h create mode 100644 Stars45/Drive.cpp create mode 100644 Stars45/Drive.h create mode 100644 Stars45/DriveSprite.cpp create mode 100644 Stars45/DriveSprite.h create mode 100644 Stars45/Drone.cpp create mode 100644 Stars45/Drone.h create mode 100644 Stars45/DropShipAI.cpp create mode 100644 Stars45/DropShipAI.h create mode 100644 Stars45/Element.cpp create mode 100644 Stars45/Element.h create mode 100644 Stars45/EngDlg.cpp create mode 100644 Stars45/EngDlg.h create mode 100644 Stars45/ExceptionHandler.cpp create mode 100644 Stars45/ExitDlg.cpp create mode 100644 Stars45/ExitDlg.h create mode 100644 Stars45/Explosion.cpp create mode 100644 Stars45/Explosion.h create mode 100644 Stars45/Farcaster.cpp create mode 100644 Stars45/Farcaster.h create mode 100644 Stars45/FighterAI.cpp create mode 100644 Stars45/FighterAI.h create mode 100644 Stars45/FighterTacticalAI.cpp create mode 100644 Stars45/FighterTacticalAI.h create mode 100644 Stars45/FirstTimeDlg.cpp create mode 100644 Stars45/FirstTimeDlg.h create mode 100644 Stars45/FlightComp.cpp create mode 100644 Stars45/FlightComp.h create mode 100644 Stars45/FlightDeck.cpp create mode 100644 Stars45/FlightDeck.h create mode 100644 Stars45/FlightPlanner.cpp create mode 100644 Stars45/FlightPlanner.h create mode 100644 Stars45/FltDlg.cpp create mode 100644 Stars45/FltDlg.h create mode 100644 Stars45/Galaxy.cpp create mode 100644 Stars45/Galaxy.h create mode 100644 Stars45/GameScreen.cpp create mode 100644 Stars45/GameScreen.h create mode 100644 Stars45/Grid.cpp create mode 100644 Stars45/Grid.h create mode 100644 Stars45/GroundAI.cpp create mode 100644 Stars45/GroundAI.h create mode 100644 Stars45/HUDSounds.cpp create mode 100644 Stars45/HUDSounds.h create mode 100644 Stars45/HUDView.cpp create mode 100644 Stars45/HUDView.h create mode 100644 Stars45/Hangar.cpp create mode 100644 Stars45/Hangar.h create mode 100644 Stars45/HardPoint.cpp create mode 100644 Stars45/HardPoint.h create mode 100644 Stars45/Hoop.cpp create mode 100644 Stars45/Hoop.h create mode 100644 Stars45/Instruction.cpp create mode 100644 Stars45/Instruction.h create mode 100644 Stars45/Intel.cpp create mode 100644 Stars45/Intel.h create mode 100644 Stars45/JoyDlg.cpp create mode 100644 Stars45/JoyDlg.h create mode 100644 Stars45/KeyDlg.cpp create mode 100644 Stars45/KeyDlg.h create mode 100644 Stars45/KeyMap.cpp create mode 100644 Stars45/KeyMap.h create mode 100644 Stars45/LandingGear.cpp create mode 100644 Stars45/LandingGear.h create mode 100644 Stars45/LoadDlg.cpp create mode 100644 Stars45/LoadDlg.h create mode 100644 Stars45/LoadScreen.cpp create mode 100644 Stars45/LoadScreen.h create mode 100644 Stars45/Main.cpp create mode 100644 Stars45/MapView.cpp create mode 100644 Stars45/MapView.h create mode 100644 Stars45/MenuDlg.cpp create mode 100644 Stars45/MenuDlg.h create mode 100644 Stars45/MenuScreen.cpp create mode 100644 Stars45/MenuScreen.h create mode 100644 Stars45/MenuView.cpp create mode 100644 Stars45/MenuView.h create mode 100644 Stars45/Mfd.cpp create mode 100644 Stars45/Mfd.h create mode 100644 Stars45/Mission.cpp create mode 100644 Stars45/Mission.h create mode 100644 Stars45/MissionEvent.cpp create mode 100644 Stars45/MissionEvent.h create mode 100644 Stars45/MissionTemplate.cpp create mode 100644 Stars45/MissionTemplate.h create mode 100644 Stars45/ModConfig.cpp create mode 100644 Stars45/ModConfig.h create mode 100644 Stars45/ModDlg.cpp create mode 100644 Stars45/ModDlg.h create mode 100644 Stars45/ModInfo.cpp create mode 100644 Stars45/ModInfo.h create mode 100644 Stars45/ModInfoDlg.cpp create mode 100644 Stars45/ModInfoDlg.h create mode 100644 Stars45/MsnDlg.cpp create mode 100644 Stars45/MsnDlg.h create mode 100644 Stars45/MsnEditDlg.cpp create mode 100644 Stars45/MsnEditDlg.h create mode 100644 Stars45/MsnEditNavDlg.cpp create mode 100644 Stars45/MsnEditNavDlg.h create mode 100644 Stars45/MsnElemDlg.cpp create mode 100644 Stars45/MsnElemDlg.h create mode 100644 Stars45/MsnEventDlg.cpp create mode 100644 Stars45/MsnEventDlg.h create mode 100644 Stars45/MsnNavDlg.cpp create mode 100644 Stars45/MsnNavDlg.h create mode 100644 Stars45/MsnObjDlg.cpp create mode 100644 Stars45/MsnObjDlg.h create mode 100644 Stars45/MsnPkgDlg.cpp create mode 100644 Stars45/MsnPkgDlg.h create mode 100644 Stars45/MsnSelectDlg.cpp create mode 100644 Stars45/MsnSelectDlg.h create mode 100644 Stars45/MsnWepDlg.cpp create mode 100644 Stars45/MsnWepDlg.h create mode 100644 Stars45/MusicDirector.cpp create mode 100644 Stars45/MusicDirector.h create mode 100644 Stars45/MusicTrack.cpp create mode 100644 Stars45/MusicTrack.h create mode 100644 Stars45/NPClient.h create mode 100644 Stars45/NPClientWraps.cpp create mode 100644 Stars45/NPClientWraps.h create mode 100644 Stars45/NavAI.cpp create mode 100644 Stars45/NavAI.h create mode 100644 Stars45/NavDlg.cpp create mode 100644 Stars45/NavDlg.h create mode 100644 Stars45/NavLight.cpp create mode 100644 Stars45/NavLight.h create mode 100644 Stars45/NavSystem.cpp create mode 100644 Stars45/NavSystem.h create mode 100644 Stars45/NetAddrDlg.cpp create mode 100644 Stars45/NetAddrDlg.h create mode 100644 Stars45/NetAdminChat.cpp create mode 100644 Stars45/NetAdminChat.h create mode 100644 Stars45/NetAdminServer.cpp create mode 100644 Stars45/NetAdminServer.h create mode 100644 Stars45/NetAuth.cpp create mode 100644 Stars45/NetAuth.h create mode 100644 Stars45/NetBrokerClient.cpp create mode 100644 Stars45/NetBrokerClient.h create mode 100644 Stars45/NetChat.cpp create mode 100644 Stars45/NetChat.h create mode 100644 Stars45/NetClientConfig.cpp create mode 100644 Stars45/NetClientConfig.h create mode 100644 Stars45/NetClientDlg.cpp create mode 100644 Stars45/NetClientDlg.h create mode 100644 Stars45/NetData.cpp create mode 100644 Stars45/NetData.h create mode 100644 Stars45/NetFileServlet.cpp create mode 100644 Stars45/NetFileServlet.h create mode 100644 Stars45/NetGame.cpp create mode 100644 Stars45/NetGame.h create mode 100644 Stars45/NetGameClient.cpp create mode 100644 Stars45/NetGameClient.h create mode 100644 Stars45/NetGameServer.cpp create mode 100644 Stars45/NetGameServer.h create mode 100644 Stars45/NetLobby.cpp create mode 100644 Stars45/NetLobby.h create mode 100644 Stars45/NetLobbyClient.cpp create mode 100644 Stars45/NetLobbyClient.h create mode 100644 Stars45/NetLobbyDlg.cpp create mode 100644 Stars45/NetLobbyDlg.h create mode 100644 Stars45/NetLobbyServer.cpp create mode 100644 Stars45/NetLobbyServer.h create mode 100644 Stars45/NetPacket.cpp create mode 100644 Stars45/NetPacket.h create mode 100644 Stars45/NetPassDlg.cpp create mode 100644 Stars45/NetPassDlg.h create mode 100644 Stars45/NetPlayer.cpp create mode 100644 Stars45/NetPlayer.h create mode 100644 Stars45/NetServerConfig.cpp create mode 100644 Stars45/NetServerConfig.h create mode 100644 Stars45/NetServerDlg.cpp create mode 100644 Stars45/NetServerDlg.h create mode 100644 Stars45/NetUnitDlg.cpp create mode 100644 Stars45/NetUnitDlg.h create mode 100644 Stars45/NetUser.cpp create mode 100644 Stars45/NetUser.h create mode 100644 Stars45/NetUtil.cpp create mode 100644 Stars45/NetUtil.h create mode 100644 Stars45/OptDlg.cpp create mode 100644 Stars45/OptDlg.h create mode 100644 Stars45/PlanScreen.cpp create mode 100644 Stars45/PlanScreen.h create mode 100644 Stars45/Player.cpp create mode 100644 Stars45/Player.h create mode 100644 Stars45/PlayerDlg.cpp create mode 100644 Stars45/PlayerDlg.h create mode 100644 Stars45/Power.cpp create mode 100644 Stars45/Power.h create mode 100644 Stars45/QuantumDrive.cpp create mode 100644 Stars45/QuantumDrive.h create mode 100644 Stars45/QuantumFlash.cpp create mode 100644 Stars45/QuantumFlash.h create mode 100644 Stars45/QuantumView.cpp create mode 100644 Stars45/QuantumView.h create mode 100644 Stars45/QuitView.cpp create mode 100644 Stars45/QuitView.h create mode 100644 Stars45/RLoc.cpp create mode 100644 Stars45/RLoc.h create mode 100644 Stars45/RadioHandler.cpp create mode 100644 Stars45/RadioHandler.h create mode 100644 Stars45/RadioMessage.cpp create mode 100644 Stars45/RadioMessage.h create mode 100644 Stars45/RadioTraffic.cpp create mode 100644 Stars45/RadioTraffic.h create mode 100644 Stars45/RadioView.cpp create mode 100644 Stars45/RadioView.h create mode 100644 Stars45/RadioVox.cpp create mode 100644 Stars45/RadioVox.h create mode 100644 Stars45/SeekerAI.cpp create mode 100644 Stars45/SeekerAI.h create mode 100644 Stars45/Sensor.cpp create mode 100644 Stars45/Sensor.h create mode 100644 Stars45/Shield.cpp create mode 100644 Stars45/Shield.h create mode 100644 Stars45/ShieldRep.cpp create mode 100644 Stars45/ShieldRep.h create mode 100644 Stars45/Ship.cpp create mode 100644 Stars45/Ship.h create mode 100644 Stars45/ShipAI.cpp create mode 100644 Stars45/ShipAI.h create mode 100644 Stars45/ShipCtrl.cpp create mode 100644 Stars45/ShipCtrl.h create mode 100644 Stars45/ShipDesign.cpp create mode 100644 Stars45/ShipDesign.h create mode 100644 Stars45/ShipKiller.cpp create mode 100644 Stars45/ShipKiller.h create mode 100644 Stars45/ShipSolid.cpp create mode 100644 Stars45/ShipSolid.h create mode 100644 Stars45/Shot.cpp create mode 100644 Stars45/Shot.h create mode 100644 Stars45/Sim.cpp create mode 100644 Stars45/Sim.h create mode 100644 Stars45/SimEvent.cpp create mode 100644 Stars45/SimEvent.h create mode 100644 Stars45/SimObject.cpp create mode 100644 Stars45/SimObject.h create mode 100644 Stars45/Sky.cpp create mode 100644 Stars45/Sky.h create mode 100644 Stars45/StarServer.cpp create mode 100644 Stars45/StarServer.h create mode 100644 Stars45/StarSystem.cpp create mode 100644 Stars45/StarSystem.h create mode 100644 Stars45/Stars.dsp create mode 100644 Stars45/Stars.dsw create mode 100644 Stars45/Stars.ico create mode 100644 Stars45/Stars.rc create mode 100644 Stars45/Stars.vcxproj create mode 100644 Stars45/Stars.vcxproj.filters create mode 100644 Stars45/Starshatter.cpp create mode 100644 Stars45/Starshatter.h create mode 100644 Stars45/StarshipAI.cpp create mode 100644 Stars45/StarshipAI.h create mode 100644 Stars45/StarshipTacticalAI.cpp create mode 100644 Stars45/StarshipTacticalAI.h create mode 100644 Stars45/SteerAI.cpp create mode 100644 Stars45/SteerAI.h create mode 100644 Stars45/System.cpp create mode 100644 Stars45/System.h create mode 100644 Stars45/SystemDesign.cpp create mode 100644 Stars45/SystemDesign.h create mode 100644 Stars45/TacRefDlg.cpp create mode 100644 Stars45/TacRefDlg.h create mode 100644 Stars45/TacticalAI.cpp create mode 100644 Stars45/TacticalAI.h create mode 100644 Stars45/TacticalView.cpp create mode 100644 Stars45/TacticalView.h create mode 100644 Stars45/Terrain.cpp create mode 100644 Stars45/Terrain.h create mode 100644 Stars45/TerrainApron.cpp create mode 100644 Stars45/TerrainApron.h create mode 100644 Stars45/TerrainClouds.cpp create mode 100644 Stars45/TerrainClouds.h create mode 100644 Stars45/TerrainHaze.cpp create mode 100644 Stars45/TerrainHaze.h create mode 100644 Stars45/TerrainLayer.h create mode 100644 Stars45/TerrainPatch.cpp create mode 100644 Stars45/TerrainPatch.h create mode 100644 Stars45/TerrainRegion.cpp create mode 100644 Stars45/TerrainRegion.h create mode 100644 Stars45/Thruster.cpp create mode 100644 Stars45/Thruster.h create mode 100644 Stars45/TrackIR.cpp create mode 100644 Stars45/TrackIR.h create mode 100644 Stars45/Trail.cpp create mode 100644 Stars45/Trail.h create mode 100644 Stars45/VidDlg.cpp create mode 100644 Stars45/VidDlg.h create mode 100644 Stars45/Weapon.cpp create mode 100644 Stars45/Weapon.h create mode 100644 Stars45/WeaponDesign.cpp create mode 100644 Stars45/WeaponDesign.h create mode 100644 Stars45/WeaponGroup.cpp create mode 100644 Stars45/WeaponGroup.h create mode 100644 Stars45/Weather.cpp create mode 100644 Stars45/Weather.h create mode 100644 Stars45/WepView.cpp create mode 100644 Stars45/WepView.h create mode 100644 Stars45/resource.h (limited to 'Stars45') diff --git a/Stars45/Asteroid.cpp b/Stars45/Asteroid.cpp new file mode 100644 index 0000000..a20a0eb --- /dev/null +++ b/Stars45/Asteroid.cpp @@ -0,0 +1,116 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Asteroid.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Asteroid Sprite animation class +*/ + +#include "MemDebug.h" +#include "Asteroid.h" +#include "Shot.h" +#include "Explosion.h" +#include "Sim.h" + +#include "Solid.h" +#include "Bitmap.h" +#include "DataLoader.h" +#include "Game.h" + +// +--------------------------------------------------------------------+ + +static Point asteroid_velocity = Point(0,0,0); +static Model* asteroid_model[32]; + +// +--------------------------------------------------------------------+ + +Asteroid::Asteroid(int t, const Vec3& pos, double m) + : Debris(asteroid_model[t%6], pos, asteroid_velocity, m) +{ + life = -1; +} + +// +--------------------------------------------------------------------+ + +void +Asteroid::Initialize() +{ + ZeroMemory(asteroid_model, sizeof(asteroid_model)); + + DataLoader* loader = DataLoader::GetLoader(); + Text old_path = loader->GetDataPath(); + loader->SetDataPath("Galaxy/Asteroids/"); + + int n = 0; + + Model* a = new(__FILE__,__LINE__) Model; + if (a) { + a->Load("a1.mag", 100); + asteroid_model[n++] = a; + } + + a = new(__FILE__,__LINE__) Model; + if (a) { + a->Load("a2.mag", 50); + asteroid_model[n++] = a; + } + + a = new(__FILE__,__LINE__) Model; + if (a) { + a->Load("a1.mag", 8); + asteroid_model[n++] = a; + } + + a = new(__FILE__,__LINE__) Model; + if (a) { + a->Load("a2.mag", 10); + asteroid_model[n++] = a; + } + + a = new(__FILE__,__LINE__) Model; + if (a) { + a->Load("a3.mag", 30); + asteroid_model[n++] = a; + } + + a = new(__FILE__,__LINE__) Model; + if (a) { + a->Load("a4.mag", 20); + asteroid_model[n++] = a; + } + + List mod_asteroids; + loader->SetDataPath("Mods/Galaxy/Asteroids/"); + loader->ListFiles("*.mag", mod_asteroids); + + ListIter iter = mod_asteroids; + while (++iter && n < 32) { + a = new(__FILE__,__LINE__) Model; + if (a) { + a->Load(*iter.value(), 50); + asteroid_model[n++] = a; + } + } + + loader->SetDataPath(old_path); +} + +void +Asteroid::Close() +{ + for (int i = 0; i < 32; i++) + delete asteroid_model[i]; + + ZeroMemory(asteroid_model, sizeof(asteroid_model)); +} + +// +--------------------------------------------------------------------+ + + + diff --git a/Stars45/Asteroid.h b/Stars45/Asteroid.h new file mode 100644 index 0000000..bbba83f --- /dev/null +++ b/Stars45/Asteroid.h @@ -0,0 +1,35 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Asteroid.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Asteroid Sprite class +*/ + +#ifndef Asteroid_h +#define Asteroid_h + +#include "Types.h" +#include "Geometry.h" +#include "SimObject.h" +#include "Debris.h" + +// +--------------------------------------------------------------------+ + +class Asteroid : public Debris +{ +public: + Asteroid(int type, const Vec3& pos, double mass); + + static void Initialize(); + static void Close(); +}; + +#endif Asteroid_h + diff --git a/Stars45/AudDlg.cpp b/Stars45/AudDlg.cpp new file mode 100644 index 0000000..30d3c67 --- /dev/null +++ b/Stars45/AudDlg.cpp @@ -0,0 +1,201 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: AudDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Main Menu Dialog Active Window class +*/ + +#include "MemDebug.h" +#include "AudDlg.h" +#include "MenuScreen.h" +#include "Starshatter.h" +#include "AudioConfig.h" + +#include "DataLoader.h" +#include "Button.h" +#include "ListBox.h" +#include "Slider.h" +#include "Video.h" +#include "Keyboard.h" +#include "MachineInfo.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +DEF_MAP_CLIENT(AudDlg, OnApply); +DEF_MAP_CLIENT(AudDlg, OnCancel); +DEF_MAP_CLIENT(AudDlg, OnAudio); +DEF_MAP_CLIENT(AudDlg, OnVideo); +DEF_MAP_CLIENT(AudDlg, OnOptions); +DEF_MAP_CLIENT(AudDlg, OnControls); +DEF_MAP_CLIENT(AudDlg, OnMod); + +// +--------------------------------------------------------------------+ + +AudDlg::AudDlg(Screen* s, FormDef& def, BaseScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), manager(mgr), + apply(0), cancel(0), vid_btn(0), aud_btn(0), ctl_btn(0), opt_btn(0), mod_btn(0), + closed(true) +{ + Init(def); +} + +AudDlg::~AudDlg() +{ +} + +// +--------------------------------------------------------------------+ + +void +AudDlg::RegisterControls() +{ + if (apply) + return; + + efx_volume_slider = (Slider*) FindControl(201); + gui_volume_slider = (Slider*) FindControl(202); + wrn_volume_slider = (Slider*) FindControl(203); + vox_volume_slider = (Slider*) FindControl(204); + + menu_music_slider = (Slider*) FindControl(205); + game_music_slider = (Slider*) FindControl(206); + + apply = (Button*) FindControl(1); + REGISTER_CLIENT(EID_CLICK, apply, AudDlg, OnApply); + + cancel = (Button*) FindControl(2); + REGISTER_CLIENT(EID_CLICK, cancel, AudDlg, OnCancel); + + vid_btn = (Button*) FindControl(901); + REGISTER_CLIENT(EID_CLICK, vid_btn, AudDlg, OnVideo); + + aud_btn = (Button*) FindControl(902); + REGISTER_CLIENT(EID_CLICK, aud_btn, AudDlg, OnAudio); + + ctl_btn = (Button*) FindControl(903); + REGISTER_CLIENT(EID_CLICK, ctl_btn, AudDlg, OnControls); + + opt_btn = (Button*) FindControl(904); + REGISTER_CLIENT(EID_CLICK, opt_btn, AudDlg, OnOptions); + + mod_btn = (Button*) FindControl(905); + if (mod_btn) { + REGISTER_CLIENT(EID_CLICK, mod_btn, AudDlg, OnMod); + } +} + +// +--------------------------------------------------------------------+ + +void +AudDlg::Show() +{ + FormWindow::Show(); + + if (closed && AudioConfig::GetInstance()) { + AudioConfig* audio = AudioConfig::GetInstance(); + + if (efx_volume_slider) + efx_volume_slider->SetValue(audio->GetEfxVolume()); + + if (gui_volume_slider) + gui_volume_slider->SetValue(audio->GetGuiVolume()); + + if (wrn_volume_slider) + wrn_volume_slider->SetValue(audio->GetWrnVolume()); + + if (vox_volume_slider) + vox_volume_slider->SetValue(audio->GetVoxVolume()); + + if (menu_music_slider) + menu_music_slider->SetValue(audio->GetMenuMusic()); + + if (game_music_slider) + game_music_slider->SetValue(audio->GetGameMusic()); + } + + if (vid_btn) vid_btn->SetButtonState(0); + if (aud_btn) aud_btn->SetButtonState(1); + if (ctl_btn) ctl_btn->SetButtonState(0); + if (opt_btn) opt_btn->SetButtonState(0); + if (mod_btn) mod_btn->SetButtonState(0); + + closed = false; +} + +// +--------------------------------------------------------------------+ + +void +AudDlg::ExecFrame() +{ + if (Keyboard::KeyDown(VK_RETURN)) { + OnApply(0); + } +} + +// +--------------------------------------------------------------------+ + +void AudDlg::OnAudio(AWEvent* event) { manager->ShowAudDlg(); } +void AudDlg::OnVideo(AWEvent* event) { manager->ShowVidDlg(); } +void AudDlg::OnOptions(AWEvent* event) { manager->ShowOptDlg(); } +void AudDlg::OnControls(AWEvent* event) { manager->ShowCtlDlg(); } +void AudDlg::OnMod(AWEvent* event) { manager->ShowModDlg(); } + +// +--------------------------------------------------------------------+ + +void +AudDlg::OnApply(AWEvent* event) +{ + if (manager) + manager->ApplyOptions(); +} + +void +AudDlg::OnCancel(AWEvent* event) +{ + manager->CancelOptions(); +} + +// +--------------------------------------------------------------------+ + +void +AudDlg::Apply() +{ + if (!closed && AudioConfig::GetInstance()) { + AudioConfig* audio = AudioConfig::GetInstance(); + + if (efx_volume_slider) + audio->SetEfxVolume(efx_volume_slider->GetValue()); + + if (gui_volume_slider) + audio->SetGuiVolume(gui_volume_slider->GetValue()); + + if (wrn_volume_slider) + audio->SetWrnVolume(wrn_volume_slider->GetValue()); + + if (vox_volume_slider) + audio->SetVoxVolume(vox_volume_slider->GetValue()); + + if (menu_music_slider) + audio->SetMenuMusic(menu_music_slider->GetValue()); + + if (game_music_slider) + audio->SetGameMusic(game_music_slider->GetValue()); + + audio->Save(); + } + + closed = true; +} + +void +AudDlg::Cancel() +{ + closed = true; +} diff --git a/Stars45/AudDlg.h b/Stars45/AudDlg.h new file mode 100644 index 0000000..829caf7 --- /dev/null +++ b/Stars45/AudDlg.h @@ -0,0 +1,79 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: AudDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Main Menu Dialog Active Window class +*/ + +#ifndef AudDlg_h +#define AudDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "Bitmap.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Font.h" + +// +--------------------------------------------------------------------+ + +class BaseScreen; + +// +--------------------------------------------------------------------+ + +class AudDlg : public FormWindow +{ +public: + AudDlg(Screen* s, FormDef& def, BaseScreen* mgr); + virtual ~AudDlg(); + + virtual void RegisterControls(); + virtual void Show(); + virtual void ExecFrame(); + + // Operations: + virtual void Apply(); + virtual void Cancel(); + + virtual void OnApply(AWEvent* event); + virtual void OnCancel(AWEvent* event); + + virtual void OnAudio(AWEvent* event); + virtual void OnVideo(AWEvent* event); + virtual void OnOptions(AWEvent* event); + virtual void OnControls(AWEvent* event); + virtual void OnMod(AWEvent* event); + +protected: + BaseScreen* manager; + + Slider* efx_volume_slider; + Slider* gui_volume_slider; + Slider* wrn_volume_slider; + Slider* vox_volume_slider; + + Slider* menu_music_slider; + Slider* game_music_slider; + + Button* aud_btn; + Button* vid_btn; + Button* opt_btn; + Button* ctl_btn; + Button* mod_btn; + + Button* apply; + Button* cancel; + + bool closed; +}; + +#endif AudDlg_h + diff --git a/Stars45/AudioConfig.cpp b/Stars45/AudioConfig.cpp new file mode 100644 index 0000000..1d6a20a --- /dev/null +++ b/Stars45/AudioConfig.cpp @@ -0,0 +1,382 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: AudioConfig.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Audio Configuration class +*/ + +#include "MemDebug.h" +#include "AudioConfig.h" + +#include "DataLoader.h" +#include "ParseUtil.h" +#include "Button.h" +#include "Game.h" + +// +--------------------------------------------------------------------+ + +static AudioConfig* audio_config = 0; + +// +--------------------------------------------------------------------+ + +AudioConfig::AudioConfig() + : menu_music(90), + game_music(90), + efx_volume(90), + gui_volume(90), + wrn_volume(90), + vox_volume(90), + training(false) +{ + if (!audio_config) + audio_config = this; +} + +AudioConfig::~AudioConfig() +{ + if (audio_config == this) + audio_config = 0; +} + +// +--------------------------------------------------------------------+ + +void +AudioConfig::Initialize() +{ + audio_config = new(__FILE__,__LINE__) AudioConfig; + if (audio_config) + audio_config->Load(); +} + +void +AudioConfig::Close() +{ + delete audio_config; + audio_config = 0; +} + +AudioConfig* +AudioConfig::GetInstance() +{ + return audio_config; +} + +// +--------------------------------------------------------------------+ + +int +AudioConfig::MenuMusic() +{ + if (audio_config) + return -50 * (100 - audio_config->menu_music); + + return 0; +} + +int +AudioConfig::GameMusic() +{ + int vol = 0; + + if (audio_config) { + vol = -50 * (100 - audio_config->game_music); + + if (audio_config->training) + vol -= 2000; + } + + return vol; +} + +int +AudioConfig::EfxVolume() +{ + int vol = 0; + + if (audio_config) { + vol = -50 * (100 - audio_config->efx_volume); + + if (audio_config->training) + vol -= 2000; + } + + return vol; +} + +int +AudioConfig::GuiVolume() +{ + if (audio_config) + return -50 * (100 - audio_config->gui_volume); + + return 0; +} + +int +AudioConfig::WrnVolume() +{ + int vol = 0; + + if (audio_config) { + vol = -50 * (100 - audio_config->wrn_volume); + + if (audio_config->training) + vol -= 2000; + } + + return vol; +} + +int +AudioConfig::VoxVolume() +{ + int vol = 0; + + if (audio_config) { + vol = -50 * (100 - audio_config->vox_volume); + + if (audio_config->training && vol < -750) + vol = -750; + } + + return vol; +} + +int +AudioConfig::Silence() +{ + return -5000; +} + +void +AudioConfig::SetTraining(bool t) +{ + if (audio_config) + audio_config->training = t; +} + +// +--------------------------------------------------------------------+ + +void +AudioConfig::SetMenuMusic(int v) +{ + if (v < 0) v = 0; + else if (v > 100) v = 100; + + menu_music = v; +} + +void +AudioConfig::SetGameMusic(int v) +{ + if (v < 0) v = 0; + else if (v > 100) v = 100; + + game_music = v; +} + +void +AudioConfig::SetEfxVolume(int v) +{ + if (v < 0) v = 0; + else if (v > 100) v = 100; + + efx_volume = v; +} + +void +AudioConfig::SetGuiVolume(int v) +{ + if (v < 0) v = 0; + else if (v > 100) v = 100; + + gui_volume = v; + Button::SetVolume(-50 * (100 - gui_volume)); +} + +void +AudioConfig::SetWrnVolume(int v) +{ + if (v < 0) v = 0; + else if (v > 100) v = 100; + + wrn_volume = v; + Button::SetVolume(-50 * (100 - wrn_volume)); +} + +void +AudioConfig::SetVoxVolume(int v) +{ + if (v < 0) v = 0; + else if (v > 100) v = 100; + + vox_volume = v; +} + +// +--------------------------------------------------------------------+ + +void +AudioConfig::Load() +{ + DataLoader* loader = DataLoader::GetLoader(); + Text old_path = loader->GetDataPath(); + loader->SetDataPath(0); + + // read the config file: + BYTE* block = 0; + int blocklen = 0; + const char* filename = "audio.cfg"; + + FILE* f = ::fopen(filename, "rb"); + + if (f) { + ::fseek(f, 0, SEEK_END); + blocklen = ftell(f); + ::fseek(f, 0, SEEK_SET); + + block = new(__FILE__,__LINE__) BYTE[blocklen+1]; + block[blocklen] = 0; + + ::fread(block, blocklen, 1, f); + ::fclose(f); + } + + if (blocklen == 0) + return; + + Parser parser(new(__FILE__,__LINE__) BlockReader((const char*) block, blocklen)); + Term* term = parser.ParseTerm(); + + if (!term) { + Print("ERROR: could not parse '%s'.\n", filename); + exit(-3); + } + else { + TermText* file_type = term->isText(); + if (!file_type || file_type->value() != "AUDIO") { + Print("WARNING: invalid %s file. Using defaults\n", filename); + return; + } + } + + do { + delete term; + + term = parser.ParseTerm(); + + if (term) { + int v = 0; + TermDef* def = term->isDef(); + + if (def) { + if (def->name()->value() == "menu_music") { + GetDefNumber(v, def, filename); + + if (v < 0 || v > 100) { + Print("WARNING: Invalid menu_music (%d) in '%s'\n", v, filename); + } + else { + menu_music = v; + } + } + + else if (def->name()->value() == "game_music") { + GetDefNumber(v, def, filename); + + if (v < 0 || v > 100) { + Print("WARNING: Invalid game_music (%d) in '%s'\n", v, filename); + } + else { + game_music = v; + } + } + + else if (def->name()->value() == "efx_volume") { + GetDefNumber(v, def, filename); + + if (v < 0 || v > 100) { + Print("WARNING: Invalid efx_volume (%d) in '%s'\n", v, filename); + } + else { + efx_volume = v; + } + } + + else if (def->name()->value() == "gui_volume") { + GetDefNumber(v, def, filename); + + if (v < 0 || v > 100) { + Print("WARNING: Invalid gui_volume (%d) in '%s'\n", v, filename); + } + else { + gui_volume = v; + + Button::SetVolume(-50 * (100 - gui_volume)); + } + } + + else if (def->name()->value() == "wrn_volume") { + GetDefNumber(v, def, filename); + + if (v < 0 || v > 100) { + Print("WARNING: Invalid wrn_volume (%d) in '%s'\n", v, filename); + } + else { + wrn_volume = v; + } + } + + else if (def->name()->value() == "vox_volume") { + GetDefNumber(v, def, filename); + + if (v < 0 || v > 100) { + Print("WARNING: Invalid vox_volume (%d) in '%s'\n", v, filename); + } + else { + vox_volume = v; + } + } + + else + Print("WARNING: unknown label '%s' in '%s'\n", + def->name()->value().data(), filename); + } + else { + Print("WARNING: term ignored in '%s'\n", filename); + term->print(); + Print("\n"); + } + } + } + while (term); + + loader->ReleaseBuffer(block); + loader->SetDataPath(old_path); +} + +void +AudioConfig::Save() +{ + FILE* f = fopen("audio.cfg", "w"); + if (f) { + fprintf(f, "AUDIO\n\n"); + fprintf(f, "menu_music: %3d\n", menu_music); + fprintf(f, "game_music: %3d\n\n", game_music); + fprintf(f, "efx_volume: %3d\n", efx_volume); + fprintf(f, "gui_volume: %3d\n", gui_volume); + fprintf(f, "wrn_volume: %3d\n", wrn_volume); + fprintf(f, "vox_volume: %3d\n", vox_volume); + fclose(f); + } +} + +// +--------------------------------------------------------------------+ + + + diff --git a/Stars45/AudioConfig.h b/Stars45/AudioConfig.h new file mode 100644 index 0000000..5264f41 --- /dev/null +++ b/Stars45/AudioConfig.h @@ -0,0 +1,71 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: AudioConfig.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Audio Configuration class +*/ + +#ifndef AudioConfig_h +#define AudioConfig_h + +#include "Types.h" + +// +--------------------------------------------------------------------+ + +class AudioConfig +{ +public: + AudioConfig(); + ~AudioConfig(); + + static void Initialize(); + static void Close(); + static AudioConfig* GetInstance(); + + void Load(); + void Save(); + + static int MenuMusic(); + static int GameMusic(); + static int EfxVolume(); + static int GuiVolume(); + static int WrnVolume(); + static int VoxVolume(); + static int Silence(); + static void SetTraining(bool t); + + int GetMenuMusic() const { return menu_music; } + int GetGameMusic() const { return game_music; } + int GetEfxVolume() const { return efx_volume; } + int GetGuiVolume() const { return gui_volume; } + int GetWrnVolume() const { return wrn_volume; } + int GetVoxVolume() const { return vox_volume; } + + void SetMenuMusic(int v); + void SetGameMusic(int v); + void SetEfxVolume(int v); + void SetGuiVolume(int v); + void SetWrnVolume(int v); + void SetVoxVolume(int v); + +protected: + int menu_music; + int game_music; + + int efx_volume; + int gui_volume; + int wrn_volume; + int vox_volume; + + bool training; +}; + +#endif AudioConfig_h + diff --git a/Stars45/Authorization.cpp b/Stars45/Authorization.cpp new file mode 100644 index 0000000..a0dd411 --- /dev/null +++ b/Stars45/Authorization.cpp @@ -0,0 +1,202 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Authorization.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Authorization Sprite animation class +*/ + +#include "MemDebug.h" +#include "Authorization.h" + +#include "Game.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +static Text GetCDKeyFromRegistry() +{ + Text cdkey; + BYTE cdbuf[64]; + DWORD cdlen = 0; + HKEY hkey = 0; + + ZeroMemory(cdbuf, sizeof(cdbuf)); + + RegOpenKeyEx(HKEY_LOCAL_MACHINE, + "SOFTWARE\\Matrix Games\\Starshatter", + 0, + KEY_QUERY_VALUE, + &hkey); + + if (hkey) { + cdlen = 64; + + LONG result = + RegQueryValueEx(hkey, + "authorized", + NULL, + NULL, + cdbuf, + &cdlen); + + if (result == ERROR_SUCCESS && cdlen > 0) + cdkey = (const char*) cdbuf; + + RegCloseKey(hkey); + } + + return cdkey; +} + +static Text GetCDKeyFromIniFile() +{ + Text cdkey; + char cdbuf[256]; + + ZeroMemory(cdbuf, sizeof(cdbuf)); + + FILE* f = fopen("maga.mg", "r"); + if (f) { + bool found_section = false; + + while (fgets(cdbuf, sizeof(cdbuf)-1, f)) { + Text line = Text(cdbuf).trim(); + line.setSensitive(false); + + if (line == "[SerialNumber]") + found_section = true; + + if (found_section) { + if (line.indexOf("serial") == 0) { + // found the proper line in the proper section, + // now we need to parse the 'name = value' sentence: + + const char* p = line.data(); + + // find the equal sign: + while (p && *p && *p != '=') + p++; + + // skip the equal sign: + p++; + + // find the string after the equal sign: + while (p && *p && isspace(*p)) + p++; + + if (p && *p) { + // deal with quoted strings: + int cutoff = sizeof(cdbuf)-1; + + if (*p == '"') { + char* s = cdbuf; + p++; + + while (*p && *p != '"' && cutoff-- > 0) { + *s++ = *p++; + } + + *s = 0; + } + + // and unquoted strings: + else { + char* s = cdbuf; + + while (*p && cutoff-- > 0) { + *s++ = *p++; + } + + *s = 0; + } + + cdkey = cdbuf; + } + } + } + } + + fclose(f); + } + + return cdkey; +} + +// +--------------------------------------------------------------------+ + +static char serial_number[64]; +int execRegistrationProgram(); + +bool +Authorization::IsUserAuthorized() +{ + // XXX DEBUG ONLY! + // return true; + + int authcode = execRegistrationProgram(); + if (authcode != 1) { + ::Print("Authorization failed, code = %d\n", authcode); + } + + return (authcode == 1); +} + +// +--------------------------------------------------------------------+ + +const char* +Authorization::GetSerialNumber() +{ + return serial_number; +} + +// +--------------------------------------------------------------------+ + +int execRegistrationProgram() +{ + int result = 999; + char cmdline[256]; + strcpy(cmdline, "SS2rez"); + + STARTUPINFO s; + ZeroMemory(&s, sizeof(s)); + s.cb = sizeof(s); + + PROCESS_INFORMATION pi; + ZeroMemory(&pi, sizeof(pi)); + + if (CreateProcess("SS2rez.exe", cmdline, 0, 0, 0, 0, 0, 0, &s, &pi)) { + DWORD exitcode = STILL_ACTIVE; + + WaitForSingleObject(pi.hProcess, 20000); + GetExitCodeProcess(pi.hProcess, &exitcode); + + if (exitcode != STILL_ACTIVE) + result = exitcode; + + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } + else { + TCHAR message[256]; + DWORD errcode = GetLastError(); + + ::Print(" WARN: Failed to create authorization process: %08X.\n", errcode); + + if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, errcode, 0, message, 256, 0)) { + ::Print(" "); + ::Print(message); + ::Print("\n"); + } + } + + return result; +} + + diff --git a/Stars45/Authorization.h b/Stars45/Authorization.h new file mode 100644 index 0000000..107bb5f --- /dev/null +++ b/Stars45/Authorization.h @@ -0,0 +1,30 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Authorization.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Authorization Sprite class +*/ + +#ifndef Authorization_h +#define Authorization_h + +#include "Types.h" + +// +--------------------------------------------------------------------+ + +class Authorization +{ +public: + static bool IsUserAuthorized(); + static const char* GetSerialNumber(); +}; + +#endif Authorization_h + diff --git a/Stars45/AwardDlg.cpp b/Stars45/AwardDlg.cpp new file mode 100644 index 0000000..138bdb1 --- /dev/null +++ b/Stars45/AwardDlg.cpp @@ -0,0 +1,152 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: AwardDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Main Menu Dialog Active Window class +*/ + +#include "MemDebug.h" +#include "AwardDlg.h" +#include "PlanScreen.h" +#include "Starshatter.h" +#include "Ship.h" +#include "Player.h" +#include "Campaign.h" + +#include "Game.h" +#include "DataLoader.h" +#include "Button.h" +#include "ListBox.h" +#include "EditBox.h" +#include "ImageBox.h" +#include "FormatUtil.h" +#include "Video.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "Sound.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +DEF_MAP_CLIENT(AwardDlg, OnClose); + +// +--------------------------------------------------------------------+ + +AwardDlg::AwardDlg(Screen* s, FormDef& def, PlanScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), manager(mgr), + lbl_name(0), lbl_info(0), img_rank(0), btn_close(0), exit_latch(true) +{ + Init(def); +} + +AwardDlg::~AwardDlg() +{ +} + +// +--------------------------------------------------------------------+ + +void +AwardDlg::RegisterControls() +{ + lbl_name = FindControl(203); + lbl_info = FindControl(201); + img_rank = (ImageBox*) FindControl(202); + + btn_close = (Button*) FindControl(1); + REGISTER_CLIENT(EID_CLICK, btn_close, AwardDlg, OnClose); +} + +// +--------------------------------------------------------------------+ + +void +AwardDlg::Show() +{ + FormWindow::Show(); + ShowPlayer(); + + exit_latch = true; +} + +// +--------------------------------------------------------------------+ + +void +AwardDlg::ExecFrame() +{ + if (Keyboard::KeyDown(VK_RETURN)) { + if (!exit_latch) + OnClose(0); + } + + else if (Keyboard::KeyDown(VK_ESCAPE)) { + if (!exit_latch) + OnClose(0); + } + + else { + exit_latch = false; + } +} + +// +--------------------------------------------------------------------+ + +void +AwardDlg::ShowPlayer() +{ + Player* p = Player::GetCurrentPlayer(); + + if (p) { + if (lbl_name) { + lbl_name->SetText(p->AwardName()); + } + + if (lbl_info) { + lbl_info->SetText(p->AwardDesc()); + } + + if (img_rank) { + img_rank->SetPicture(*p->AwardImage()); + img_rank->Show(); + } + + Sound* congrats = p->AwardSound(); + if (congrats) { + congrats->Play(); + } + } + else { + if (lbl_info) lbl_info->SetText(""); + if (img_rank) img_rank->Hide(); + } +} + +// +--------------------------------------------------------------------+ + +void +AwardDlg::OnClose(AWEvent* event) +{ + Player* player = Player::GetCurrentPlayer(); + if (player) + player->ClearShowAward(); + + Starshatter* stars = Starshatter::GetInstance(); + + if (stars) { + Mouse::Show(false); + + Campaign* campaign = Campaign::GetCampaign(); + if (campaign && campaign->GetCampaignId() < Campaign::SINGLE_MISSIONS) + stars->SetGameMode(Starshatter::CMPN_MODE); + else + stars->SetGameMode(Starshatter::MENU_MODE); + } + + else + Game::Panic("AwardDlg::OnClose() - Game instance not found"); +} diff --git a/Stars45/AwardDlg.h b/Stars45/AwardDlg.h new file mode 100644 index 0000000..1b84b94 --- /dev/null +++ b/Stars45/AwardDlg.h @@ -0,0 +1,59 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: AwardDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Main Menu Dialog Active Window class +*/ + +#ifndef AwardDlg_h +#define AwardDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "Bitmap.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Font.h" + +// +--------------------------------------------------------------------+ + +class PlanScreen; +class Player; + +// +--------------------------------------------------------------------+ + +class AwardDlg : public FormWindow +{ +public: + AwardDlg(Screen* s, FormDef& def, PlanScreen* mgr); + virtual ~AwardDlg(); + + virtual void RegisterControls(); + virtual void Show(); + virtual void ExecFrame(); + + // Operations: + virtual void OnClose(AWEvent* event); + virtual void ShowPlayer(); + +protected: + PlanScreen* manager; + + ActiveWindow* lbl_name; + ActiveWindow* lbl_info; + ImageBox* img_rank; + Button* btn_close; + + bool exit_latch; +}; + +#endif AwardDlg_h + diff --git a/Stars45/AwardShowDlg.cpp b/Stars45/AwardShowDlg.cpp new file mode 100644 index 0000000..8ba188c --- /dev/null +++ b/Stars45/AwardShowDlg.cpp @@ -0,0 +1,159 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: AwardShowDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Main Menu Dialog Active Window class +*/ + +#include "MemDebug.h" +#include "AwardShowDlg.h" +#include "MenuScreen.h" +#include "Starshatter.h" +#include "Ship.h" +#include "Player.h" +#include "Campaign.h" + +#include "Game.h" +#include "DataLoader.h" +#include "Button.h" +#include "ListBox.h" +#include "EditBox.h" +#include "ImageBox.h" +#include "FormatUtil.h" +#include "Video.h" +#include "Keyboard.h" +#include "Mouse.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +DEF_MAP_CLIENT(AwardShowDlg, OnClose); + +// +--------------------------------------------------------------------+ + +AwardShowDlg::AwardShowDlg(Screen* s, FormDef& def, MenuScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), manager(mgr), + lbl_name(0), lbl_info(0), img_rank(0), btn_close(0), exit_latch(true), + rank(-1), medal(-1) +{ + Init(def); +} + +AwardShowDlg::~AwardShowDlg() +{ +} + +// +--------------------------------------------------------------------+ + +void +AwardShowDlg::RegisterControls() +{ + lbl_name = FindControl(203); + lbl_info = FindControl(201); + img_rank = (ImageBox*) FindControl(202); + + btn_close = (Button*) FindControl(1); + REGISTER_CLIENT(EID_CLICK, btn_close, AwardShowDlg, OnClose); +} + +// +--------------------------------------------------------------------+ + +void +AwardShowDlg::Show() +{ + FormWindow::Show(); + ShowAward(); +} + +// +--------------------------------------------------------------------+ + +void +AwardShowDlg::ExecFrame() +{ + if (Keyboard::KeyDown(VK_RETURN)) { + if (!exit_latch) + OnClose(0); + } + + else if (Keyboard::KeyDown(VK_ESCAPE)) { + if (!exit_latch) + OnClose(0); + } + + else { + exit_latch = false; + } +} + +// +--------------------------------------------------------------------+ + +void +AwardShowDlg::SetRank(int r) +{ + rank = r; + medal = -1; +} + +void +AwardShowDlg::SetMedal(int m) +{ + rank = -1; + medal = m; +} + +// +--------------------------------------------------------------------+ + +void +AwardShowDlg::ShowAward() +{ + if (rank >= 0) { + if (lbl_name) { + lbl_name->SetText(Text("Rank of ") + Player::RankName(rank)); + } + + if (lbl_info) { + lbl_info->SetText(Player::RankDescription(rank)); + } + + if (img_rank) { + img_rank->SetPicture(*Player::RankInsignia(rank, 1)); + img_rank->Show(); + } + } + + else if (medal >= 0) { + if (lbl_name) { + lbl_name->SetText(Player::MedalName(medal)); + } + + if (lbl_info) { + lbl_info->SetText(Player::MedalDescription(medal)); + } + + if (img_rank) { + img_rank->SetPicture(*Player::MedalInsignia(medal, 1)); + img_rank->Show(); + } + } + + else { + if (lbl_name) lbl_name->SetText(""); + if (lbl_info) lbl_info->SetText(""); + if (img_rank) img_rank->Hide(); + } +} + +// +--------------------------------------------------------------------+ + +void +AwardShowDlg::OnClose(AWEvent* event) +{ + manager->ShowPlayerDlg(); +} diff --git a/Stars45/AwardShowDlg.h b/Stars45/AwardShowDlg.h new file mode 100644 index 0000000..29c3208 --- /dev/null +++ b/Stars45/AwardShowDlg.h @@ -0,0 +1,63 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: AwardShowDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Main Menu Dialog Active Window class +*/ + +#ifndef AwardShowDlg_h +#define AwardShowDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "Bitmap.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Font.h" + +// +--------------------------------------------------------------------+ + +class MenuScreen; + +// +--------------------------------------------------------------------+ + +class AwardShowDlg : public FormWindow +{ +public: + AwardShowDlg(Screen* s, FormDef& def, MenuScreen* mgr); + virtual ~AwardShowDlg(); + + virtual void RegisterControls(); + virtual void Show(); + virtual void ExecFrame(); + + // Operations: + virtual void OnClose(AWEvent* event); + virtual void ShowAward(); + virtual void SetRank(int r); + virtual void SetMedal(int r); + +protected: + MenuScreen* manager; + + ActiveWindow* lbl_name; + ActiveWindow* lbl_info; + ImageBox* img_rank; + Button* btn_close; + + bool exit_latch; + + int rank; + int medal; +}; + +#endif AwardShowDlg_h + diff --git a/Stars45/BaseScreen.h b/Stars45/BaseScreen.h new file mode 100644 index 0000000..ccc3ede --- /dev/null +++ b/Stars45/BaseScreen.h @@ -0,0 +1,83 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: BaseScreen.h + AUTHOR: John DiCamillo + +*/ + +#ifndef BaseScreen_h +#define BaseScreen_h + +#include "Types.h" +#include "Bitmap.h" +#include "Screen.h" + +// +--------------------------------------------------------------------+ + +class Screen; +class Sim; +class Window; +class Font; +class NavDlg; +class MsnElemDlg; +class AudDlg; +class VidDlg; +class ModDlg; +class ModInfoDlg; +class OptDlg; +class CtlDlg; +class KeyDlg; +class JoyDlg; +class MsgDlg; + +// +--------------------------------------------------------------------+ + +class BaseScreen +{ +public: + BaseScreen() { } + virtual ~BaseScreen() { } + + virtual void ShowNavDlg() { } + virtual void HideNavDlg() { } + virtual bool IsNavShown() { return false; } + virtual NavDlg* GetNavDlg() { return 0; } + + virtual void ShowMsnElemDlg() { } + virtual void HideMsnElemDlg() { } + virtual MsnElemDlg* GetMsnElemDlg() { return 0; } + + virtual AudDlg* GetAudDlg() const { return 0; } + virtual VidDlg* GetVidDlg() const { return 0; } + virtual ModDlg* GetModDlg() const { return 0; } + virtual ModInfoDlg* GetModInfoDlg() const { return 0; } + virtual OptDlg* GetOptDlg() const { return 0; } + virtual CtlDlg* GetCtlDlg() const { return 0; } + virtual JoyDlg* GetJoyDlg() const { return 0; } + virtual KeyDlg* GetKeyDlg() const { return 0; } + + virtual void ShowAudDlg() { } + virtual void ShowVidDlg() { } + virtual void ShowModDlg() { } + virtual void ShowModInfoDlg() { } + virtual void ShowOptDlg() { } + virtual void ShowCtlDlg() { } + virtual void ShowJoyDlg() { } + virtual void ShowKeyDlg() { } + + virtual void ShowMsgDlg() { } + virtual void HideMsgDlg() { } + virtual bool IsMsgShown() { return false; } + virtual MsgDlg* GetMsgDlg() { return 0; } + + virtual void ApplyOptions() { } + virtual void CancelOptions() { } +}; + +// +--------------------------------------------------------------------+ + +#endif BaseScreen_h + diff --git a/Stars45/Callsign.cpp b/Stars45/Callsign.cpp new file mode 100644 index 0000000..db4c1d3 --- /dev/null +++ b/Stars45/Callsign.cpp @@ -0,0 +1,102 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Callsign.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Package Callsign catalog class +*/ + +#include "MemDebug.h" +#include "Callsign.h" + +// +----------------------------------------------------------------------+ + +static int callsign_index = -1; + +static char civilian_catalog[32][16] = { + "Aleph", "Vehan", "Galvin", "Caleb", + "Mercury", "Lancet", "Hera", "Zeus", + "Odin", "Thor", "Nereus", "Klono", + "Athena", "Helios", "Leto", "Nivas", + + "Selene", "Proteus", "Triton", "Thetis", + "Aurora", "Ceres", "Rana", "Doradus", + "Perseus", "Corina", "Cygnus", "Lago", + "Andil", "Galen", "Temas", "Dalan" +}; + +static char alliance_catalog[32][16] = { + "Alpha", "Bravo", "Delta", "Echo", + "Ranger", "Magic", "Falcon", "Omega", + "Vulcan", "Hammer", "Nomad", "Dragon", + "Sierra", "Tango", "Victor", "Zulu", + + "Sentry", "Wolf", "Zeta", "Jackal", + "Merlin", "Eagle", "Blade", "Tiger", + "Raptor", "Ares", "Condor", "Rogue", + "Hornet", "Gold", "Mustang", "Voodoo" +}; + +static char hegemony_catalog[32][16] = { + "Nagal", "Nalak", "Olkar", "Kalar", + "Narom", "Tranor", "Orrin", "Orlak", + "Nardik", "Sorrin", "Amnar", "Kizek", + "Orten", "Ronar", "Molkar", "Ternal", + + "Martak", "Komar", "Malik", "Morgul", + "Kliek", "Torlan", "Arvin", "Artak", + "Ralka", "Sernal", "Roten", "Reza", + "Tinet", "Aliek", "Salar", "Sona" +}; + +static char pirate_catalog[32][16] = { + "Raider", "Skull", "Black", "Blood", + "Roger", "Hook", "Galleon", "Privateer", + "Cutlass", "Sabre", "Pike", "Blackbeard", + "Pistol", "Cortez", "Pirate", "Buccaneer", + + "Raider", "Skull", "Black", "Blood", + "Morgan", "Redbeard", "Cutlass", "Sabre", + "Iron", "Stocks", "Quarter", "Gray", + "Ruby", "Cross", "Pirate", "Raider" +}; + +static char zolon_catalog[32][16] = { + "Cancer", "Krill", "Bluefin", "Scylla", + "Charybdis","Finback", "Mantis", "Skate", + "Sealion", "Lamprey", "Tarpon", "Hammerhead", + "Orca", "Skipjack", "Sculpin", "Thresher", + + "Devilray", "Chinook", "Moray", "Seastar", + "Heron", "Puffin", "Rockeye", "Tiburon", + "Coho", "Stingray", "Mako", "Conger", + "Scad", "Pompano", "Tusk", "Nautilus" +}; + + +// +----------------------------------------------------------------------+ + +const char* +Callsign::GetCallsign(int IFF) +{ + if (callsign_index < 0) + callsign_index = rand()/1000; + + if (callsign_index > 31) + callsign_index = 0; + + switch (IFF) { + case 0: return civilian_catalog[callsign_index++]; + default: + case 1: return alliance_catalog[callsign_index++]; + case 2: return hegemony_catalog[callsign_index++]; + case 3: return pirate_catalog[callsign_index++]; + case 4: return zolon_catalog[callsign_index++]; + } +} diff --git a/Stars45/Callsign.h b/Stars45/Callsign.h new file mode 100644 index 0000000..6e1b3d7 --- /dev/null +++ b/Stars45/Callsign.h @@ -0,0 +1,29 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Callsign.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Package Callsign catalog class +*/ + +#ifndef Callsign_h +#define Callsign_h + +#include "Types.h" + +// +--------------------------------------------------------------------+ + +class Callsign +{ +public: + static const char* GetCallsign(int IFF=1); +}; + +#endif Callsign_h + diff --git a/Stars45/CameraDirector.cpp b/Stars45/CameraDirector.cpp new file mode 100644 index 0000000..dc8ba10 --- /dev/null +++ b/Stars45/CameraDirector.cpp @@ -0,0 +1,1196 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CameraDirector.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Camera Director singleton manages the main view camera based on the current ship +*/ + +#include "MemDebug.h" +#include "CameraDirector.h" +#include "Ship.h" +#include "FlightDeck.h" +#include "Contact.h" +#include "Sim.h" +#include "StarSystem.h" +#include "Terrain.h" +#include "HUDView.h" +#include "DetailSet.h" +#include "Starshatter.h" + +#include "Game.h" + +// +----------------------------------------------------------------------+ + +CameraDirector* CameraDirector::instance = 0; + +static double range_max_limit = 300e3; + +// +----------------------------------------------------------------------+ + +CameraDirector* +CameraDirector::GetInstance() +{ + if (!instance) + instance = new(__FILE__,__LINE__) CameraDirector; + + return instance; +} + +// +----------------------------------------------------------------------+ + +CameraDirector::CameraDirector() + : mode(MODE_COCKPIT), requested_mode(MODE_NONE), old_mode(MODE_NONE), + sim(0), ship(0), region(0), external_ship(0), external_body(0), + virt_az(0), virt_el(0), virt_x(0), virt_y(0), virt_z(0), + azimuth(PI/4), elevation(PI/4), az_rate(0), el_rate(0), range_rate(0), + range(0), range_min(100), range_max(range_max_limit), + base_range(0), transition(0), hud(0) +{ + instance = this; +} + +// +--------------------------------------------------------------------+ + +CameraDirector::~CameraDirector() +{ + if (instance == this) + instance = 0; +} + +// +--------------------------------------------------------------------+ + +void +CameraDirector::Reset() +{ + mode = MODE_COCKPIT; + requested_mode = MODE_NONE; + old_mode = MODE_NONE; + + sim = 0; + ship = 0; + region = 0; + external_ship = 0; + external_body = 0; + transition = 0; + hud = 0; +} + +// +--------------------------------------------------------------------+ + +void +CameraDirector::SetShip(Ship* s) +{ + sim = Sim::GetSim(); + hud = HUDView::GetInstance(); + + // can't take control of a dead ship: + if (s && (s->Life() == 0 || s->IsDying() || s->IsDead())) + return; + + // leaving the old ship, so make sure it is visible: + if (ship && ship != s) { + ship->ShowRep(); + ship->HideCockpit(); + } + + // taking control of the new ship: + ship = s; + + if (ship) { + Observe(ship); + region = ship->GetRegion(); + + if (sim && ship->GetRegion() != sim->GetActiveRegion()) + sim->ActivateRegion(ship->GetRegion()); + + range = ship->Radius() * 4; + + if (mode == MODE_COCKPIT) + mode = MODE_CHASE; + + SetMode(MODE_COCKPIT); + ExecFrame(0); + } +} + +// +--------------------------------------------------------------------+ + +void +CameraDirector::SetMode(int m, double t) +{ + if (requested_mode == m) + return; + + external_point = Point(); + + // save current mode for after transition: + if (m == MODE_DROP && mode != MODE_DROP) + old_mode = mode; + + // if manually leaving drop mode, forget about + // restoring the previous mode when the drop + // expires... + else if (m != MODE_DROP && mode == MODE_DROP) + old_mode = MODE_NONE; + + if (m == MODE_VIRTUAL && ship && !ship->Cockpit()) + return; + + if (mode == m) { + if (mode == MODE_TARGET || mode == MODE_ORBIT) + CycleViewObject(); + + return; + } + + if (m > MODE_NONE && m < MODE_LAST) { + if (m <= MODE_VIRTUAL) { + requested_mode = m; + transition = t; + external_ship = 0; + ClearGroup(); + + // no easy way to do a smooth transition between + // certain modes, so just go immediately: + if ((mode == MODE_TARGET && m == MODE_CHASE) || + (mode == MODE_COCKPIT && m == MODE_VIRTUAL) || + (mode == MODE_VIRTUAL && m == MODE_COCKPIT)) + { + mode = m; + requested_mode = 0; + transition = 0; + } + } + + else if (m == MODE_TRANSLATE || m == MODE_ZOOM) { + requested_mode = m; + transition = t; + base_range = range; + } + + else if (m >= MODE_DOCKING) { + mode = m; + transition = 0; + external_ship = 0; + ClearGroup(); + } + + virt_az = 0; + virt_el = 0; + virt_x = 0; + virt_y = 0; + virt_z = 0; + } +} + +// +--------------------------------------------------------------------+ + +static const char* get_camera_mode_name(int m) +{ + switch (m) { + default: + case CameraDirector::MODE_NONE: return ""; break; + case CameraDirector::MODE_COCKPIT: return ""; break; + case CameraDirector::MODE_CHASE: return "Chase Cam"; break; + case CameraDirector::MODE_TARGET: return "Padlock"; break; + case CameraDirector::MODE_THREAT: return "Threatlock"; break; + case CameraDirector::MODE_VIRTUAL: return "Virtual"; break; + case CameraDirector::MODE_ORBIT: + case CameraDirector::MODE_TRANSLATE: + case CameraDirector::MODE_ZOOM: return "Orbit Cam"; break; + case CameraDirector::MODE_DOCKING: return "Dock Cam"; break; + case CameraDirector::MODE_DROP: return ""; break; + } +} + +const char* +CameraDirector::GetModeName() +{ + int m = GetCameraMode(); + + if (m != CameraDirector::MODE_VIRTUAL) { + return get_camera_mode_name(m); + } + else if (instance) { + if (instance->virt_az > 30*DEGREES && instance->virt_az < 100*DEGREES) + return "RIGHT"; + + else if (instance->virt_az >= 100*DEGREES) + return "RIGHT-AFT"; + + else if (instance->virt_az < -30*DEGREES && instance->virt_az > -100*DEGREES) + return "LEFT"; + + else if (instance->virt_az <= -100*DEGREES) + return "LEFT-AFT"; + + else if (instance->virt_el > 15*DEGREES) + return "UP"; + + else if (instance->virt_el < -15*DEGREES) + return "DOWN"; + + return get_camera_mode_name(m); + } + + return ""; +} + +int +CameraDirector::GetCameraMode() +{ + if (instance) { + int op_mode = instance->mode; + if (instance->requested_mode > instance->mode) + op_mode = instance->requested_mode; + return op_mode; + } + + return 0; +} + +void +CameraDirector::SetCameraMode(int m, double t) +{ + if (instance) + instance->SetMode(m, t); +} + +// +--------------------------------------------------------------------+ + +double +CameraDirector::GetRangeLimit() +{ + return range_max_limit; +} + +void +CameraDirector::SetRangeLimit(double r) +{ + if (r >= 1e3) + range_max_limit = r; +} + +void +CameraDirector::SetRangeLimits(double min, double max) +{ + if (instance) { + instance->range_min = min; + instance->range_max = max; + } +} + +// +--------------------------------------------------------------------+ + +void +CameraDirector::ClearGroup() +{ + external_group.clear(); +} + +// +--------------------------------------------------------------------+ + +void +CameraDirector::CycleViewObject() +{ + if (!ship) return; + + Ship* current = external_ship; + external_ship = 0; + + ListIter iter = ship->ContactList(); + while (++iter && !external_ship) { + Contact* c = iter.value(); + Ship* c_ship = c->GetShip(); + + if (c_ship && !current) { + external_ship = c_ship; + } + + else if (current && c_ship == current) { + while (++iter && !external_ship) { + c = iter.value(); + if (c->ActLock()) + external_ship = c->GetShip(); + } + } + } + + if (external_ship != current) { + if (external_ship) { + if (external_ship->Life() == 0 || external_ship->IsDying() || external_ship->IsDead()) { + external_point = external_ship->Location(); + external_ship = 0; + } + else { + Observe(external_ship); + } + } + + if (mode == MODE_ORBIT) { + SetMode(MODE_TRANSLATE); + ExternalRange(1); + } + } +} + +// +--------------------------------------------------------------------+ + +void +CameraDirector::SetViewOrbital(Orbital* orb) +{ + external_body = orb; + + if (external_body) { + range_min = external_body->Radius() * 2.5; + ClearGroup(); + external_ship = 0; + + if (sim) { + region = sim->FindNearestSpaceRegion(orb); + if (region) + sim->ActivateRegion(region); + } + + if (ship && !region) + region = ship->GetRegion(); + } +} + +// +--------------------------------------------------------------------+ + +void +CameraDirector::SetViewObject(Ship* obj, bool quick) +{ + if (!ship) return; + if (!obj) { + obj = ship; + region = ship->GetRegion(); + } + + external_body = 0; + external_point = Point(); + + Starshatter* stars = Starshatter::GetInstance(); + + if (obj->GetIFF() != ship->GetIFF() && !stars->InCutscene()) { + // only view solid contacts: + Contact* c = ship->FindContact(obj); + if (!c || !c->ActLock()) + return; + } + + if (mode == MODE_TARGET) { + ClearGroup(); + if (external_ship) { + external_ship = 0; + } + } + else if (mode >= MODE_ORBIT) { + if (quick) { + mode = MODE_ORBIT; + transition = 0; + } + else { + SetMode(MODE_TRANSLATE); + } + + if (external_group.size()) { + ClearGroup(); + + if (external_ship) { + external_ship = 0; + } + } + + else { + if ((obj == external_ship) || (obj==ship && external_ship==0)) { + if (!quick) + SetMode(MODE_ZOOM); + } + + else if (external_ship) { + external_ship = 0; + } + } + } + + if (external_ship != obj) { + external_ship = obj; + + if (external_ship) { + region = external_ship->GetRegion(); + + if (external_ship->Life() == 0 || external_ship->IsDying() || external_ship->IsDead()) { + external_ship = 0; + range_min = 100; + } + else { + Observe(external_ship); + + if (sim) + sim->ActivateRegion(external_ship->GetRegion()); + + range_min = external_ship->Radius() * 1.5; + } + } + + Observe(external_ship); + ExternalRange(1); + } +} + +// +--------------------------------------------------------------------+ + +void +CameraDirector::SetViewObjectGroup(ListIter group, bool quick) +{ + if (!ship) return; + + Starshatter* stars = Starshatter::GetInstance(); + + if (!stars->InCutscene()) { + // only view solid contacts: + while (++group) { + Ship* s = group.value(); + + if (s->GetIFF() != ship->GetIFF()) { + Contact* c = ship->FindContact(s); + if (!c || !c->ActLock()) + return; + } + + if (s->Life() == 0 || s->IsDying() || s->IsDead()) + return; + } + } + + group.reset(); + + if (external_group.size() > 1 && + external_group.size() == group.size()) { + + bool same = true; + + for (int i = 0; same && i < external_group.size(); i++) { + if (external_group[i] != group.container()[i]) + same = false; + } + + if (same) { + SetMode(MODE_ZOOM); + return; + } + } + + ClearGroup(); + + if (quick) { + mode = MODE_ORBIT; + transition = 0; + } + else { + SetMode(MODE_TRANSLATE); + } + + external_group.append(group.container()); + + ListIter iter = external_group; + while (++iter) { + Ship* s = iter.value(); + region = s->GetRegion(); + Observe(s); + } +} + +// +--------------------------------------------------------------------+ + +void +CameraDirector::VirtualHead(double az, double el) +{ + if (mode == MODE_VIRTUAL || mode == MODE_TARGET || mode == MODE_COCKPIT) { + const double alimit = 3*PI/4; + const double e_lo = PI/8; + const double e_hi = PI/3; + const double escale = e_hi; + + virt_az = az * alimit; + virt_el = el * escale; + + if (virt_az > alimit) + virt_az = alimit; + + else if (virt_az < -alimit) + virt_az = -alimit; + + if (virt_el > e_hi) + virt_el = e_hi; + + else if (virt_el < -e_lo) + virt_el = -e_lo; + } +} + +void +CameraDirector::VirtualHeadOffset(double x, double y, double z) +{ + if (mode == MODE_VIRTUAL || mode == MODE_TARGET || mode == MODE_COCKPIT) { + virt_x = x; + virt_y = y; + virt_z = z; + } +} + +void +CameraDirector::VirtualAzimuth(double delta) +{ + if (mode == MODE_VIRTUAL || mode == MODE_TARGET || mode == MODE_COCKPIT) { + virt_az += delta; + + const double alimit = 3*PI/4; + + if (virt_az > alimit) + virt_az = alimit; + + else if (virt_az < -alimit) + virt_az = -alimit; + } +} + +void +CameraDirector::VirtualElevation(double delta) +{ + if (mode == MODE_VIRTUAL || mode == MODE_TARGET || mode == MODE_COCKPIT) { + virt_el += delta; + + const double e_lo = PI/8; + const double e_hi = PI/3; + + if (virt_el > e_hi) + virt_el = e_hi; + + else if (virt_el < -e_lo) + virt_el = -e_lo; + } +} + +// +--------------------------------------------------------------------+ + +void +CameraDirector::ExternalAzimuth(double delta) +{ + azimuth += delta; + + if (azimuth > PI) + azimuth = -2*PI + azimuth; + + else if (azimuth < -PI) + azimuth = 2*PI + azimuth; +} + +void +CameraDirector::ExternalElevation(double delta) +{ + elevation += delta; + + const double limit = (0.43 * PI); + + if (elevation > limit) + elevation = limit; + else if (elevation < -limit) + elevation = -limit; +} + +void +CameraDirector::ExternalRange(double delta) +{ + range *= delta; + + if (ship && ship->IsAirborne()) + range_max = 30e3; + else + range_max = range_max_limit; + + if (range < range_min) + range = range_min; + + else if (range > range_max) + range = range_max; +} + +// +--------------------------------------------------------------------+ + +void +CameraDirector::SetOrbitPoint(double a, double e, double r) +{ + azimuth = a; + elevation = e; + range = r; + + if (external_body) { + if (range < external_body->Radius() * 2) + range = external_body->Radius() * 2; + + else if (range > external_body->Radius() * 6) + range = external_body->Radius() * 6; + } + + else { + if (range < range_min) + range = range_min; + + else if (range > range_max) + range = range_max; + } +} + +void +CameraDirector::SetOrbitRates(double ar, double er, double rr) +{ + az_rate = ar; + el_rate = er; + + range_rate = rr; +} + +// +--------------------------------------------------------------------+ + +bool +CameraDirector::Update(SimObject* obj) +{ + if (obj->Type() == SimObject::SIM_SHIP) { + Ship* s = (Ship*) obj; + if (ship == s) + ship = 0; + + if (external_ship == s) { + external_point = s->Location(); + external_ship = 0; + } + + if (external_group.contains(s)) + external_group.remove(s); + } + + return SimObserver::Update(obj); +} + +const char* +CameraDirector::GetObserverName() const +{ + return "CameraDirector"; +} + +// +--------------------------------------------------------------------+ + +void +CameraDirector::ExecFrame(double seconds) +{ + if (!ship) + return; + + hud = HUDView::GetInstance(); + + int flight_phase = ship->GetFlightPhase(); + + if (flight_phase < Ship::LOCKED) + SetMode(MODE_DOCKING); + + if (ship->IsAirborne()) { + if (flight_phase >= Ship::DOCKING) + SetMode(MODE_DOCKING); + } + else { + if (flight_phase >= Ship::RECOVERY) + SetMode(MODE_DOCKING); + } + + if (flight_phase >= Ship::LOCKED && flight_phase < Ship::ACTIVE) { + int m = GetMode(); + if (m != MODE_COCKPIT && m != MODE_VIRTUAL) + SetMode(MODE_COCKPIT); + } + + if (ship->InTransition()) { + SetMode(MODE_DROP); + } + // automatically restore mode after transition: + else if (old_mode != MODE_NONE) { + mode = old_mode; + requested_mode = old_mode; + old_mode = MODE_NONE; + } + + int op_mode = mode; + if (requested_mode > mode) + op_mode = requested_mode; + + Starshatter* stars = Starshatter::GetInstance(); + + // if we are in padlock, and have not locked a ship + // try to padlock the current target: + if (op_mode == MODE_TARGET && !external_ship) { + SimObject* tgt = ship->GetTarget(); + if (tgt && tgt->Type() == SimObject::SIM_SHIP) + SetViewObject((Ship*) tgt); + } + + // if in an external mode, check the external ship: + else if (op_mode >= MODE_TARGET && op_mode <= MODE_ZOOM) { + if (external_ship && external_ship != ship && !stars->InCutscene()) { + Contact* c = ship->FindContact(external_ship); + if (!c || !c->ActLock()) { + SetViewObject(ship); + } + } + } + + if (ship->Rep()) { + if (op_mode == MODE_COCKPIT) { + ship->HideRep(); + ship->HideCockpit(); + } + else if (op_mode == MODE_VIRTUAL || op_mode == MODE_TARGET) { + if (ship->Cockpit()) { + ship->HideRep(); + ship->ShowCockpit(); + } + else { + ship->ShowRep(); + } + } + else { + ship->Rep()->SetForeground(op_mode == MODE_DOCKING); + ship->ShowRep(); + ship->HideCockpit(); + } + } + + if (hud && hud->Ambient() != Color::Black) + sim->GetScene()->SetAmbient( hud->Ambient() ); + else + sim->GetScene()->SetAmbient( sim->GetStarSystem()->Ambient() ); + + switch (op_mode) { + default: + case MODE_COCKPIT: Cockpit(seconds); break; + case MODE_CHASE: Chase(seconds); break; + case MODE_TARGET: Target(seconds); break; + case MODE_THREAT: Threat(seconds); break; + case MODE_VIRTUAL: Virtual(seconds); break; + case MODE_ORBIT: + case MODE_TRANSLATE: + case MODE_ZOOM: Orbit(seconds); break; + case MODE_DOCKING: Docking(seconds); break; + case MODE_DROP: Drop(seconds); break; + } + + if (ship->Shake() > 0 && + (op_mode < MODE_ORBIT || + (op_mode == MODE_VIRTUAL && ship->Cockpit()))) { + + Point vib = ship->Vibration() * 0.2; + camera.MoveBy(vib); + camera.Aim(0, vib.y, vib.z); + } + + Transition(seconds); + DetailSet::SetReference(region, camera.Pos()); +} + +// +--------------------------------------------------------------------+ + +void +CameraDirector::Transition(double seconds) +{ + if (transition > 0) + transition -= seconds * 1.5; + + if (transition <= 0) { + transition = 0; + + if (requested_mode && requested_mode != mode) + mode = requested_mode; + + requested_mode = 0; + + if (mode == MODE_TRANSLATE || mode == MODE_ZOOM) { + if (mode == MODE_ZOOM) + range = range_min; + + mode = MODE_ORBIT; + } + } +} + +// +--------------------------------------------------------------------+ + +bool +CameraDirector::IsViewCentered() +{ + if (instance) { + double fvaz = fabs(instance->virt_az); + double fvel = fabs(instance->virt_el); + + return fvaz < 15*DEGREES && fvel < 15*DEGREES; + } + + return true; +} + +// +--------------------------------------------------------------------+ + +void +CameraDirector::Cockpit(double seconds) +{ + camera.Clone(ship->Cam()); + + Point bridge = ship->BridgeLocation(); + Point cpos = camera.Pos() + + camera.vrt() * bridge.x + + camera.vpn() * bridge.y + + camera.vup() * bridge.z; + + camera.MoveTo(cpos); +} + +// +--------------------------------------------------------------------+ + +void +CameraDirector::Virtual(double seconds) +{ + camera.Clone(ship->Cam()); + + Point bridge = ship->BridgeLocation(); + Point cpos = camera.Pos() + + camera.vrt() * (bridge.x + virt_x) + + camera.vpn() * (bridge.y + virt_z) + + camera.vup() * (bridge.z + virt_y); + + camera.MoveTo(cpos); + + camera.Yaw(virt_az); + camera.Pitch(-virt_el); + + double fvaz = fabs(virt_az); + double fvel = fabs(virt_el); + + if (fvaz > 0.01*DEGREES && fvaz < 15*DEGREES) { + double bleed = fvaz * 2; + + if (virt_az > 0) + virt_az -= bleed * seconds; + else + virt_az += bleed * seconds; + } + + if (fvel > 0.01*DEGREES && fvel < 15*DEGREES) { + double bleed = fvel; + + if (virt_el > 0) + virt_el -= bleed * seconds; + else + virt_el += bleed * seconds; + } +} + +// +--------------------------------------------------------------------+ + +void +CameraDirector::Chase(double seconds) +{ + double step = 1; + + if (requested_mode == MODE_COCKPIT) + step = transition; + + else if (requested_mode == MODE_CHASE) + step = 1 - transition; + + camera.Clone(ship->Cam()); + Point velocity = camera.vpn(); + + if (ship->Velocity().length() > 10) { + velocity = ship->Velocity(); + velocity.Normalize(); + velocity *= 0.25; + velocity += camera.vpn() * 2; + velocity.Normalize(); + } + + Point chase = ship->ChaseLocation(); + Point bridge = ship->BridgeLocation(); + Point cpos = camera.Pos() + + camera.vrt() * bridge.x * (1-step) + + camera.vpn() * bridge.y * (1-step) + + camera.vup() * bridge.z * (1-step) + + velocity * chase.y * step + + camera.vup() * chase.z * step; + + camera.MoveTo(cpos); +} + +// +--------------------------------------------------------------------+ + +void +CameraDirector::Target(double seconds) +{ + Point target_loc = external_point; + + if (external_ship) + target_loc = external_ship->Location(); + + if (!external_ship || external_ship == ship) { + if (!external_point) { + if (ship->Cockpit()) + Virtual(seconds); + else + Orbit(seconds); + + return; + } + } + + double step = 1; + + if (requested_mode == MODE_COCKPIT) + step = transition; + + else if (requested_mode == MODE_TARGET) + step = 1 - transition; + + if (ship->Cockpit()) { + // internal padlock: + Cockpit(seconds); + camera.Padlock(target_loc, 3*PI/4, PI/8, PI/3); + } + else { + // external padlock: + Point delta = target_loc - ship->Location(); + delta.Normalize(); + delta *= -5 * ship->Radius() * step; + delta.y += ship->Radius() * step; + + camera.MoveTo(ship->Location() + delta); + camera.LookAt(target_loc); + } +} + +// +--------------------------------------------------------------------+ + +void +CameraDirector::Threat(double seconds) +{ + Chase(seconds); +} + +// +--------------------------------------------------------------------+ + +void +CameraDirector::Orbit(double seconds) +{ + Point cpos = ship->Location(); + int op_mode = GetCameraMode(); + + if (seconds < 0) seconds = 0; + else if (seconds > 0.2) seconds = 0.2; + + // auto rotate + azimuth += az_rate * seconds; + elevation += el_rate * seconds; + range *= 1 + range_rate * seconds; + + if (external_body && external_body->Rep()) { + range_min = external_body->Radius() * 2.5; + cpos = external_body->Rep()->Location(); + } + + else if (external_group.size()) { + Point neg(1e9, 1e9, 1e9); + Point pos(-1e9, -1e9, -1e9); + + ListIter iter(external_group); + while (++iter) { + Point loc = iter->Location(); + + if (loc.x < neg.x) neg.x = loc.x; + if (loc.x > pos.x) pos.x = loc.x; + if (loc.y < neg.y) neg.y = loc.y; + if (loc.y > pos.y) pos.y = loc.y; + if (loc.z < neg.z) neg.z = loc.z; + if (loc.z > pos.z) pos.z = loc.z; + } + + double dx = pos.x - neg.x; + double dy = pos.y - neg.y; + double dz = pos.z - neg.z; + + if (dx > dy) { + if (dx > dz) range_min = dx * 1.2; + else range_min = dz * 1.2; + } + else { + if (dy > dz) range_min = dy * 1.2; + else range_min = dz * 1.2; + } + + // focus on median location: + cpos = neg + Point(dx/2, dy/2, dz/2); + } + else if (external_ship) { + range_min = external_ship->Radius() * 1.5; + cpos = external_ship->Location(); + } + else { + range_min = ship->Radius() * 1.5; + cpos = ship->Location(); + } + + if (range < range_min) + range = range_min; + + double er = range; + double az = azimuth; + double el = elevation; + + if (requested_mode == MODE_TRANSLATE) { + cpos = cpos*(1-transition) + base_loc*(transition); + } + + else if (requested_mode == MODE_ZOOM) { + er = base_range * transition; + + if (er < range_min) { + er = range_min; + range = range_min; + transition = 0; + requested_mode = 0; + } + } + + // transitions: + else if (mode < MODE_ORBIT || requested_mode > 0 && requested_mode < MODE_ORBIT) { + double az0 = ship->CompassHeading(); + if (fabs(az-az0) > PI) az0 -= 2*PI; + + double r0 = 0; + double z0 = 0; + + if (mode == MODE_CHASE || requested_mode == MODE_CHASE || + mode == MODE_TARGET || requested_mode == MODE_TARGET) { + r0 = ship->ChaseLocation().length(); + z0 = 20 * DEGREES; + } + + // pull out: + if (mode < MODE_ORBIT) { + er *= (1-transition); + az *= (1-transition); + el *= (1-transition); + + er += r0 * transition; + az += az0 * transition; + el += z0 * transition; + } + + // push in: + else { + er *= transition; + az *= transition; + el *= transition; + + er += r0 * (1-transition); + az += az0 * (1-transition); + el += z0 * (1-transition); + } + } + + else { + // save base location for next time we re-focus + base_loc = cpos; + } + + double dx = er * sin(az) * cos(el); + double dy = er * cos(az) * cos(el); + double dz = er * sin(el); + + Point cloc = cpos + Point(dx,dz,dy); + + Terrain* terrain = ship->GetRegion()->GetTerrain(); + + if (terrain) { + double cam_agl = cloc.y - terrain->Height(cloc.x, cloc.z); + + if (cam_agl < 100) + cloc.y = terrain->Height(cloc.x, cloc.z) + 100; + } + + if (external_ship == 0 && er < 0.5 * ship->Radius()) + ship->Rep()->Hide(); + + camera.MoveTo(cloc.x, cloc.y, cloc.z); + camera.LookAt(cpos); +} + +// +--------------------------------------------------------------------+ + +void +CameraDirector::Docking(double seconds) +{ + FlightDeck* dock = ship->GetDock(); + + if (!dock) { + Cockpit(seconds); + return; + } + + if (!ship->IsAirborne()) + sim->GetScene()->SetAmbient(Color(120,130,140)); + int flight_phase = ship->GetFlightPhase(); + + Point bridge = ship->BridgeLocation(); + Point cloc = ship->Location() + + ship->Cam().vrt() * bridge.x + + ship->Cam().vpn() * bridge.y + + ship->Cam().vup() * bridge.z; + + Point cpos = dock->CamLoc(); + + // preflight: + if (flight_phase < Ship::LOCKED) { + base_loc = cpos; + } + + else if (flight_phase == Ship::LOCKED) { + if (hud) + hud->SetHUDMode(HUDView::HUD_MODE_TAC); + cpos = base_loc * transition + + cloc * (1-transition); + } + + // recovery: + else if (flight_phase > Ship::APPROACH) { + if (hud) + hud->SetTacticalMode(1); + } + + camera.MoveTo(cpos); + camera.LookAt(cloc); +} + +// +--------------------------------------------------------------------+ + +void +CameraDirector::Drop(double seconds) +{ + // orbital transitions use "drop cam" at transition_loc + camera.MoveTo(ship->TransitionLocation()); + camera.LookAt(ship->Location()); +} diff --git a/Stars45/CameraDirector.h b/Stars45/CameraDirector.h new file mode 100644 index 0000000..7cafb53 --- /dev/null +++ b/Stars45/CameraDirector.h @@ -0,0 +1,156 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CameraDirector.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Camera Director singleton manages the main view camera based on the current ship +*/ + +#ifndef CameraDirector_h +#define CameraDirector_h + +#include "Types.h" +#include "Camera.h" +#include "SimObject.h" +#include "List.h" + +// +--------------------------------------------------------------------+ + +class HUDView; +class Orbital; +class Ship; +class Sim; + +// +--------------------------------------------------------------------+ + +class CameraDirector : public SimObserver +{ +public: + static const char* TYPENAME() { return "CameraDirector"; } + + enum CAM_MODE { + MODE_NONE, + + MODE_COCKPIT, + MODE_CHASE, + MODE_TARGET, + MODE_THREAT, + MODE_ORBIT, + MODE_VIRTUAL, + MODE_TRANSLATE, + MODE_ZOOM, + MODE_DOCKING, + MODE_DROP, + + MODE_LAST + }; + + // CONSTRUCTORS: + CameraDirector(); + virtual ~CameraDirector(); + + int operator == (const CameraDirector& that) const { return this == &that; } + + static CameraDirector* GetInstance(); + + // CAMERA: + static int GetCameraMode(); + static void SetCameraMode(int m, double trans_time=1); + static const char* GetModeName(); + + static double GetRangeLimit(); + static void SetRangeLimit(double r); + static void SetRangeLimits(double min, double max); + + virtual void Reset(); + virtual void SetMode(int m, double trans_time=1); + virtual int GetMode() const { return requested_mode > MODE_NONE ? requested_mode : mode; } + virtual Camera* GetCamera() { return &camera; } + virtual Ship* GetShip() { return ship; } + virtual void SetShip(Ship* s); + + virtual void VirtualHead(double az, double el); + virtual void VirtualHeadOffset(double x, double y, double z); + virtual void VirtualAzimuth(double delta); + virtual void VirtualElevation(double delta); + virtual void ExternalAzimuth(double delta); + virtual void ExternalElevation(double delta); + virtual void ExternalRange(double delta); + virtual void SetOrbitPoint(double az, double el, double range); + virtual void SetOrbitRates(double az_rate, double el_rate, double r_rate); + + static bool IsViewCentered(); + + virtual void CycleViewObject(); + + virtual Orbital* GetViewOrbital() const { return external_body; } + virtual Ship* GetViewObject() const { return external_ship; } + + virtual void SetViewOrbital(Orbital* orb); + virtual void SetViewObject(Ship* obj, bool quick=false); + virtual void SetViewObjectGroup(ListIter group, bool quick=false); + virtual void ClearGroup(); + + // BEHAVIORS: + virtual void ExecFrame(double s); + virtual void Transition(double s); + + // SimObserver: + virtual bool Update(SimObject* obj); + virtual const char* GetObserverName() const; + +protected: + virtual void Cockpit(double s); + virtual void Chase(double s); + virtual void Target(double s); + virtual void Threat(double s); + virtual void Virtual(double s); + virtual void Orbit(double s); + virtual void Docking(double s); + virtual void Drop(double s); + + int mode; + int requested_mode; + int old_mode; + Camera camera; + + Ship* ship; + + SimRegion* region; + Orbital* external_body; + Ship* external_ship; + List external_group; + Point external_point; + + Point base_loc; + + double virt_az; + double virt_el; + double virt_x; + double virt_y; + double virt_z; + double azimuth; + double elevation; + double az_rate; + double el_rate; + double range_rate; + double range; + double range_min; + double range_max; + double base_range; + double transition; + + Sim* sim; + HUDView* hud; + + static CameraDirector* instance; +}; + +#endif CameraDirector_h + diff --git a/Stars45/Campaign.cpp b/Stars45/Campaign.cpp new file mode 100644 index 0000000..bfa368d --- /dev/null +++ b/Stars45/Campaign.cpp @@ -0,0 +1,2347 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2006. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Campaign.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Campaign defines a strategic military scenario. +*/ + +#include "MemDebug.h" +#include "Campaign.h" +#include "CampaignPlanStrategic.h" +#include "CampaignPlanAssignment.h" +#include "CampaignPlanEvent.h" +#include "CampaignPlanMission.h" +#include "CampaignPlanMovement.h" +#include "CampaignSituationReport.h" +#include "CampaignSaveGame.h" +#include "Combatant.h" +#include "CombatAction.h" +#include "CombatEvent.h" +#include "CombatGroup.h" +#include "CombatRoster.h" +#include "CombatUnit.h" +#include "CombatZone.h" +#include "Galaxy.h" +#include "Mission.h" +#include "StarSystem.h" +#include "Starshatter.h" +#include "Player.h" + +#include "Game.h" +#include "Bitmap.h" +#include "DataLoader.h" +#include "ParseUtil.h" +#include "Random.h" +#include "FormatUtil.h" + + +const int TIME_NEVER = (int) 1e9; +const int ONE_DAY = (int) 24 * 3600; + +// +====================================================================+ + +MissionInfo::MissionInfo() + : mission(0), start(0), type(0), id(0), min_rank(0), max_rank(0), + action_id(0), action_status(0), exec_once(0), + start_before(TIME_NEVER), start_after(0) +{ } + +MissionInfo::~MissionInfo() +{ + delete mission; +} + +bool +MissionInfo::IsAvailable() +{ + Campaign* campaign = Campaign::GetCampaign(); + Player* player = Player::GetCurrentPlayer(); + CombatGroup* player_group = campaign->GetPlayerGroup(); + + if (campaign->GetTime() < start_after) + return false; + + if (campaign->GetTime() > start_before) + return false; + + if (region.length() && player_group->GetRegion() != region) + return false; + + if (min_rank && player->Rank() < min_rank) + return false; + + if (max_rank && player->Rank() > max_rank) + return false; + + if (exec_once < 0) + return false; + + if (exec_once > 0) + exec_once = -1; + + return true; +} + +// +====================================================================+ + +TemplateList::TemplateList() + : mission_type(0), group_type(0), index(0) +{ } + +TemplateList::~TemplateList() +{ + missions.destroy(); +} + +// +====================================================================+ + +static List campaigns; +static Campaign* current_campaign = 0; + +// +--------------------------------------------------------------------+ + +Campaign::Campaign(int id, const char* n) + : campaign_id(id), name(n), mission_id(-1), mission(0), net_mission(0), + scripted(false), sequential(false), time(0), startTime(0), loadTime(0), + player_group(0), player_unit(0), status(CAMPAIGN_INIT), lockout(0), + loaded_from_savegame(false) +{ + ZeroMemory(path, sizeof(path)); + Load(); +} + +Campaign::Campaign(int id, const char* n, const char* p) + : campaign_id(id), name(n), mission_id(-1), mission(0), net_mission(0), + scripted(false), sequential(false), time(0), startTime(0), loadTime(0), + player_group(0), player_unit(0), status(CAMPAIGN_INIT), lockout(0), + loaded_from_savegame(false) +{ + ZeroMemory(path, sizeof(path)); + strncpy(path, p, sizeof(path)); + Load(); +} + +// +--------------------------------------------------------------------+ + +Campaign::~Campaign() +{ + for (int i = 0; i < NUM_IMAGES; i++) + image[i].ClearImage(); + + delete net_mission; + + actions.destroy(); + events.destroy(); + missions.destroy(); + templates.destroy(); + planners.destroy(); + zones.destroy(); + combatants.destroy(); +} + +// +--------------------------------------------------------------------+ + +void +Campaign::Initialize() +{ + Campaign* c = 0; + DataLoader* loader = DataLoader::GetLoader(); + + for (int i = 1; i < 100; i++) { + char path[256]; + sprintf(path, "Campaigns/%02d/", i); + + loader->UseFileSystem(true); + loader->SetDataPath(path); + + if (loader->FindFile("campaign.def")) { + char txt[256]; + sprintf(txt, "Dynamic Campaign %02d", i); + c = new(__FILE__,__LINE__) Campaign(i, txt); + + if (c) { + campaigns.insertSort(c); + } + } + } + + c = new(__FILE__,__LINE__) Campaign(SINGLE_MISSIONS, "Single Missions"); + if (c) { + campaigns.insertSort(c); + current_campaign = c; + } + + c = new(__FILE__,__LINE__) Campaign(MULTIPLAYER_MISSIONS, "Multiplayer Missions"); + if (c) { + campaigns.insertSort(c); + } + + c = new(__FILE__,__LINE__) Campaign(CUSTOM_MISSIONS, "Custom Missions"); + if (c) { + campaigns.insertSort(c); + } +} + +void +Campaign::Close() +{ + Print("Campaign::Close() - destroying all campaigns\n"); + current_campaign = 0; + campaigns.destroy(); +} + +Campaign* +Campaign::GetCampaign() +{ + return current_campaign; +} + +Campaign* +Campaign::SelectCampaign(const char* name) +{ + Campaign* c = 0; + ListIter iter = campaigns; + + while (++iter && !c) { + if (!stricmp(iter->Name(), name)) + c = iter.value(); + } + + if (c) { + Print("Campaign: Selected '%s'\n", c->Name()); + current_campaign = c; + } + else { + Print("Campaign: could not find '%s'\n", name); + } + + return c; +} + +Campaign* +Campaign::CreateCustomCampaign(const char* name, const char* path) +{ + int id = 0; + + if (name && *name && path && *path) { + ListIter iter = campaigns; + + while (++iter) { + Campaign* c = iter.value(); + if (c->GetCampaignId() >= id) + id = c->GetCampaignId() + 1; + + if (!strcmp(c->Name(), name)) { + Print("Campaign: custom campaign '%s' already exists.\n", name); + return 0; + } + } + } + + if (id == 0) + id = CUSTOM_MISSIONS + 1; + + Campaign* c = new(__FILE__,__LINE__) Campaign(id, name, path); + Print("Campaign: created custom campaign %d '%s'\n", id, name); + campaigns.append(c); + + return c; +} + +List& +Campaign::GetAllCampaigns() +{ + return campaigns; +} + +int +Campaign::GetLastCampaignId() +{ + int result = 0; + + for (int i = 0; i < campaigns.size(); i++) { + Campaign* c = campaigns.at(i); + + if (c->IsDynamic() && c->GetCampaignId() > result) { + result = c->GetCampaignId(); + } + } + + return result; +} + +// +--------------------------------------------------------------------+ + +CombatEvent* +Campaign::GetLastEvent() +{ + CombatEvent* result = 0; + + if (!events.isEmpty()) + result = events.last(); + + return result; +} + +// +--------------------------------------------------------------------+ + +int +Campaign::CountNewEvents() const +{ + int result = 0; + + for (int i = 0; i < events.size(); i++) + if (/*events[i]->Source() != CombatEvent::TACNET &&*/ !events[i]->Visited()) + result++; + + return result; +} + +// +--------------------------------------------------------------------+ + +void +Campaign::Clear() +{ + missions.destroy(); + planners.destroy(); + combatants.destroy(); + events.destroy(); + actions.destroy(); + + player_group = 0; + player_unit = 0; + + updateTime = time; +} + +// +--------------------------------------------------------------------+ + +void +Campaign::Load() +{ + // first, unload any existing data: + Unload(); + + if (!path[0]) { + // then load the campaign from files: + switch (campaign_id) { + case SINGLE_MISSIONS: strcpy(path, "Missions/"); break; + case CUSTOM_MISSIONS: strcpy(path, "Mods/Missions/"); break; + case MULTIPLAYER_MISSIONS: strcpy(path, "Multiplayer/"); break; + default: sprintf(path, "Campaigns/%02d/", + campaign_id); break; + } + } + + DataLoader* loader = DataLoader::GetLoader(); + loader->UseFileSystem(true); + loader->SetDataPath(path); + systems.clear(); + + if (loader->FindFile("zones.def")) + zones.append(CombatZone::Load("zones.def")); + + for (int i = 0; i < zones.size(); i++) { + Text s = zones[i]->System(); + bool found = false; + + for (int n = 0; !found && n < systems.size(); n++) { + if (s == systems[n]->Name()) + found = true; + } + + if (!found) + systems.append(Galaxy::GetInstance()->GetSystem(s)); + } + + loader->UseFileSystem(Starshatter::UseFileSystem()); + + if (loader->FindFile("campaign.def")) + LoadCampaign(loader); + + if (campaign_id == CUSTOM_MISSIONS) { + loader->SetDataPath(path); + LoadCustomMissions(loader); + } + else { + bool found = false; + + if (loader->FindFile("missions.def")) { + loader->SetDataPath(path); + LoadMissionList(loader); + found = true; + } + + if (loader->FindFile("templates.def")) { + loader->SetDataPath(path); + LoadTemplateList(loader); + found = true; + } + + if (!found) { + loader->SetDataPath(path); + LoadCustomMissions(loader); + } + } + + loader->UseFileSystem(true); + loader->SetDataPath(path); + + if (loader->FindFile("image.pcx")) { + loader->LoadBitmap("image.pcx", image[ 0]); + loader->LoadBitmap("selected.pcx", image[ 1]); + loader->LoadBitmap("unavail.pcx", image[ 2]); + loader->LoadBitmap("banner.pcx", image[ 3]); + } + + loader->SetDataPath(0); + loader->UseFileSystem(Starshatter::UseFileSystem()); +} + +void +Campaign::Unload() +{ + SetStatus(CAMPAIGN_INIT); + + Game::ResetGameTime(); + StarSystem::SetBaseTime(0); + + startTime = Stardate(); + loadTime = startTime; + lockout = 0; + + for (int i = 0; i < NUM_IMAGES; i++) + image[i].ClearImage(); + + Clear(); + + zones.destroy(); +} + +void +Campaign::LoadCampaign(DataLoader* loader, bool full) +{ + BYTE* block = 0; + const char* filename = "campaign.def"; + + loader->UseFileSystem(true); + loader->LoadBuffer(filename, block, true); + loader->UseFileSystem(Starshatter::UseFileSystem()); + Parser parser(new(__FILE__,__LINE__) BlockReader((const char*) block)); + + Term* term = parser.ParseTerm(); + + if (!term) { + return; + } + else { + TermText* file_type = term->isText(); + if (!file_type || file_type->value() != "CAMPAIGN") { + return; + } + } + + do { + delete term; term = 0; + term = parser.ParseTerm(); + + if (term) { + TermDef* def = term->isDef(); + if (def) { + if (def->name()->value() == "name") { + if (!def->term() || !def->term()->isText()) { + ::Print("WARNING: name missing in '%s/%s'\n", loader->GetDataPath(), filename); + } + else { + name = def->term()->isText()->value(); + name = Game::GetText(name); + } + } + else if (def->name()->value() == "desc") { + if (!def->term() || !def->term()->isText()) { + ::Print("WARNING: description missing in '%s/%s'\n", loader->GetDataPath(), filename); + } + else { + description = def->term()->isText()->value(); + description = Game::GetText(description); + } + } + else if (def->name()->value() == "situation") { + if (!def->term() || !def->term()->isText()) { + ::Print("WARNING: situation missing in '%s/%s'\n", loader->GetDataPath(), filename); + } + else { + situation = def->term()->isText()->value(); + situation = Game::GetText(situation); + } + } + else if (def->name()->value() == "orders") { + if (!def->term() || !def->term()->isText()) { + ::Print("WARNING: orders missing in '%s/%s'\n", loader->GetDataPath(), filename); + } + else { + orders = def->term()->isText()->value(); + orders = Game::GetText(orders); + } + } + else if (def->name()->value() == "scripted") { + if (def->term() && def->term()->isBool()) { + scripted = def->term()->isBool()->value(); + } + } + else if (def->name()->value() == "sequential") { + if (def->term() && def->term()->isBool()) { + sequential = def->term()->isBool()->value(); + } + } + else if (full && def->name()->value() == "combatant") { + if (!def->term() || !def->term()->isStruct()) { + ::Print("WARNING: combatant struct missing in '%s/%s'\n", loader->GetDataPath(), filename); + } + else { + TermStruct* val = def->term()->isStruct(); + + char name[64]; + int iff = 0; + CombatGroup* force = 0; + CombatGroup* clone = 0; + + ZeroMemory(name, sizeof(name)); + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + if (pdef->name()->value() == "name") { + GetDefText(name, pdef, filename); + + force = CombatRoster::GetInstance()->GetForce(name); + + if (force) + clone = force->Clone(false); // shallow copy + } + + else if (pdef->name()->value() == "group") { + ParseGroup(pdef->term()->isStruct(), force, clone, filename); + } + } + } + + loader->SetDataPath(path); + Combatant* c = new(__FILE__,__LINE__) Combatant(name, clone); + if (c) { + combatants.append(c); + } + else { + Unload(); + return; + } + } + } + else if (full && def->name()->value() == "action") { + if (!def->term() || !def->term()->isStruct()) { + ::Print("WARNING: action struct missing in '%s/%s'\n", loader->GetDataPath(), filename); + } + else { + TermStruct* val = def->term()->isStruct(); + ParseAction(val, filename); + } + } + } + } + } + while (term); + + loader->ReleaseBuffer(block); +} + +// +--------------------------------------------------------------------+ + +void +Campaign::ParseGroup(TermStruct* val, + CombatGroup* force, + CombatGroup* clone, + const char* filename) +{ + if (!val) { + ::Print("invalid combat group in campaign %s\n", name.data()); + return; + } + + int type = 0; + int id = 0; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + if (pdef->name()->value() == "type") { + char type_name[64]; + GetDefText(type_name, pdef, filename); + type = CombatGroup::TypeFromName(type_name); + } + + else if (pdef->name()->value() == "id") { + GetDefNumber(id, pdef, filename); + } + } + } + + if (type && id) { + CombatGroup* g = force->FindGroup(type, id); + + // found original group, now clone it over + if (g && g->GetParent()) { + CombatGroup* parent = CloneOver(force, clone, g->GetParent()); + parent->AddComponent(g->Clone()); + } + } +} + +// +--------------------------------------------------------------------+ + +void +Campaign::ParseAction(TermStruct* val, const char* filename) +{ + if (!val) { + ::Print("invalid action in campaign %s\n", name.data()); + return; + } + + int id = 0; + int type = 0; + int subtype = 0; + int opp_type = -1; + int team = 0; + int source = 0; + Vec3 loc(0.0f, 0.0f, 0.0f); + Text system; + Text region; + Text file; + Text image; + Text scene; + Text text; + int count = 1; + int start_before = TIME_NEVER; + int start_after = 0; + int min_rank = 0; + int max_rank = 100; + int delay = 0; + int probability = 100; + + int asset_type = 0; + int asset_id = 0; + int target_type = 0; + int target_id = 0; + int target_iff = 0; + + CombatAction* action = 0; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + if (pdef->name()->value() == "id") { + GetDefNumber(id, pdef, filename); + } + else if (pdef->name()->value() == "type") { + char txt[64]; + GetDefText(txt, pdef, filename); + type = CombatAction::TypeFromName(txt); + } + else if (pdef->name()->value() == "subtype") { + if (pdef->term()->isNumber()) { + GetDefNumber(subtype, pdef, filename); + } + + else if (pdef->term()->isText()) { + char txt[64]; + GetDefText(txt, pdef, filename); + + if (type == CombatAction::MISSION_TEMPLATE) { + subtype = Mission::TypeFromName(txt); + } + else if (type == CombatAction::COMBAT_EVENT) { + subtype = CombatEvent::TypeFromName(txt); + } + else if (type == CombatAction::INTEL_EVENT) { + subtype = Intel::IntelFromName(txt); + } + } + } + else if (pdef->name()->value() == "opp_type") { + if (pdef->term()->isNumber()) { + GetDefNumber(opp_type, pdef, filename); + } + + else if (pdef->term()->isText()) { + char txt[64]; + GetDefText(txt, pdef, filename); + + if (type == CombatAction::MISSION_TEMPLATE) { + opp_type = Mission::TypeFromName(txt); + } + } + } + else if (pdef->name()->value() == "source") { + char txt[64]; + GetDefText(txt, pdef, filename); + source = CombatEvent::SourceFromName(txt); + } + else if (pdef->name()->value() == "team") { + GetDefNumber(team, pdef, filename); + } + else if (pdef->name()->value() == "iff") { + GetDefNumber(team, pdef, filename); + } + else if (pdef->name()->value() == "count") { + GetDefNumber(count, pdef, filename); + } + else if (pdef->name()->value().contains("before")) { + if (pdef->term()->isNumber()) { + GetDefNumber(start_before, pdef, filename); + } + else { + GetDefTime(start_before, pdef, filename); + start_before -= ONE_DAY; + } + } + else if (pdef->name()->value().contains("after")) { + if (pdef->term()->isNumber()) { + GetDefNumber(start_after, pdef, filename); + } + else { + GetDefTime(start_after, pdef, filename); + start_after -= ONE_DAY; + } + } + else if (pdef->name()->value() == "min_rank") { + if (pdef->term()->isNumber()) { + GetDefNumber(min_rank, pdef, filename); + } + else { + char rank_name[64]; + GetDefText(rank_name, pdef, filename); + min_rank = Player::RankFromName(rank_name); + } + } + else if (pdef->name()->value() == "max_rank") { + if (pdef->term()->isNumber()) { + GetDefNumber(max_rank, pdef, filename); + } + else { + char rank_name[64]; + GetDefText(rank_name, pdef, filename); + max_rank = Player::RankFromName(rank_name); + } + } + else if (pdef->name()->value() == "delay") { + GetDefNumber(delay, pdef, filename); + } + else if (pdef->name()->value() == "probability") { + GetDefNumber(probability, pdef, filename); + } + else if (pdef->name()->value() == "asset_type") { + char type_name[64]; + GetDefText(type_name, pdef, filename); + asset_type = CombatGroup::TypeFromName(type_name); + } + else if (pdef->name()->value() == "target_type") { + char type_name[64]; + GetDefText(type_name, pdef, filename); + target_type = CombatGroup::TypeFromName(type_name); + } + else if (pdef->name()->value() == "location" || + pdef->name()->value() == "loc") { + GetDefVec(loc, pdef, filename); + } + else if (pdef->name()->value() == "system" || + pdef->name()->value() == "sys") { + GetDefText(system, pdef, filename); + } + else if (pdef->name()->value() == "region" || + pdef->name()->value() == "rgn" || + pdef->name()->value() == "zone") { + GetDefText(region, pdef, filename); + } + else if (pdef->name()->value() == "file") { + GetDefText(file, pdef, filename); + } + else if (pdef->name()->value() == "image") { + GetDefText(image, pdef, filename); + } + else if (pdef->name()->value() == "scene") { + GetDefText(scene, pdef, filename); + } + else if (pdef->name()->value() == "text") { + GetDefText(text, pdef, filename); + text = Game::GetText(text); + } + else if (pdef->name()->value() == "asset_id") { + GetDefNumber(asset_id, pdef, filename); + } + else if (pdef->name()->value() == "target_id") { + GetDefNumber(target_id, pdef, filename); + } + else if (pdef->name()->value() == "target_iff") { + GetDefNumber(target_iff, pdef, filename); + } + + else if (pdef->name()->value() == "asset_kill") { + if (!action) + action = new(__FILE__,__LINE__) CombatAction(id, type, subtype, team); + + if (action) { + char txt[64]; + GetDefText(txt, pdef, filename); + action->AssetKills().append(new (__FILE__,__LINE__) Text(txt)); + } + } + + else if (pdef->name()->value() == "target_kill") { + if (!action) + action = new(__FILE__,__LINE__) CombatAction(id, type, subtype, team); + + if (action) { + char txt[64]; + GetDefText(txt, pdef, filename); + action->TargetKills().append(new (__FILE__,__LINE__) Text(txt)); + } + } + + else if (pdef->name()->value() == "req") { + if (!action) + action = new(__FILE__,__LINE__) CombatAction(id, type, subtype, team); + + if (!pdef->term() || !pdef->term()->isStruct()) { + ::Print("WARNING: action req struct missing in '%s'\n", filename); + } + else if (action) { + TermStruct* val2 = pdef->term()->isStruct(); + + int act = 0; + int stat = CombatAction::COMPLETE; + bool not = false; + + Combatant* c1 = 0; + Combatant* c2 = 0; + int comp = 0; + int score = 0; + int intel = 0; + int gtype = 0; + int gid = 0; + + for (int i = 0; i < val2->elements()->size(); i++) { + TermDef* pdef2 = val2->elements()->at(i)->isDef(); + if (pdef2) { + if (pdef2->name()->value() == "action") { + GetDefNumber(act, pdef2, filename); + } + else if (pdef2->name()->value() == "status") { + char txt[64]; + GetDefText(txt, pdef2, filename); + stat = CombatAction::StatusFromName(txt); + } + else if (pdef2->name()->value() == "not") { + GetDefBool(not, pdef2, filename); + } + + else if (pdef2->name()->value() == "c1") { + char txt[64]; + GetDefText(txt, pdef2, filename); + c1 = GetCombatant(txt); + } + else if (pdef2->name()->value() == "c2") { + char txt[64]; + GetDefText(txt, pdef2, filename); + c2 = GetCombatant(txt); + } + else if (pdef2->name()->value() == "comp") { + char txt[64]; + GetDefText(txt, pdef2, filename); + comp = CombatActionReq::CompFromName(txt); + } + else if (pdef2->name()->value() == "score") { + GetDefNumber(score, pdef2, filename); + } + else if (pdef2->name()->value() == "intel") { + if (pdef2->term()->isNumber()) { + GetDefNumber(intel, pdef2, filename); + } + else if (pdef2->term()->isText()) { + char txt[64]; + GetDefText(txt, pdef2, filename); + intel = Intel::IntelFromName(txt); + } + } + else if (pdef2->name()->value() == "group_type") { + char type_name[64]; + GetDefText(type_name, pdef2, filename); + gtype = CombatGroup::TypeFromName(type_name); + } + else if (pdef2->name()->value() == "group_id") { + GetDefNumber(gid, pdef2, filename); + } + } + } + + if (act) + action->AddRequirement(act, stat, not); + + else if (gtype) + action->AddRequirement(c1, gtype, gid, comp, score, intel); + + else + action->AddRequirement(c1, c2, comp, score); + } + } + } + } + + if (!action) + action = new(__FILE__,__LINE__) CombatAction(id, type, subtype, team); + + if (action) { + action->SetSource(source); + action->SetOpposingType(opp_type); + action->SetLocation(loc); + action->SetSystem(system); + action->SetRegion(region); + action->SetFilename(file); + action->SetImageFile(image); + action->SetSceneFile(scene); + action->SetCount(count); + action->SetStartAfter(start_after); + action->SetStartBefore(start_before); + action->SetMinRank(min_rank); + action->SetMaxRank(max_rank); + action->SetDelay(delay); + action->SetProbability(probability); + + action->SetAssetId(asset_id); + action->SetAssetType(asset_type); + action->SetTargetId(target_id); + action->SetTargetIFF(target_iff); + action->SetTargetType(target_type); + action->SetText(text); + + actions.append(action); + } +} + +// +--------------------------------------------------------------------+ + +CombatGroup* +Campaign::CloneOver(CombatGroup* force, CombatGroup* clone, CombatGroup* group) +{ + CombatGroup* orig_parent = group->GetParent(); + + if (orig_parent) { + CombatGroup* clone_parent = clone->FindGroup(orig_parent->Type(), orig_parent->GetID()); + + if (!clone_parent) + clone_parent = CloneOver(force, clone, orig_parent); + + CombatGroup* new_clone = clone->FindGroup(group->Type(), group->GetID()); + + if (!new_clone) { + new_clone = group->Clone(false); + clone_parent->AddComponent(new_clone); + } + + return new_clone; + } + else { + return clone; + } +} + +// +--------------------------------------------------------------------+ + +void +Campaign::LoadMissionList(DataLoader* loader) +{ + bool ok = true; + BYTE* block = 0; + const char* filename = "Missions.def"; + + loader->UseFileSystem(true); + loader->LoadBuffer(filename, block, true); + loader->UseFileSystem(Starshatter::UseFileSystem()); + Parser parser(new(__FILE__,__LINE__) BlockReader((const char*) block)); + + Term* term = parser.ParseTerm(); + + if (!term) { + return; + } + else { + TermText* file_type = term->isText(); + if (!file_type || file_type->value() != "MISSIONLIST") { + ::Print("WARNING: invalid mission list file '%s'\n", filename); + term->print(10); + return; + } + } + + do { + delete term; term = 0; + term = parser.ParseTerm(); + + if (term) { + TermDef* def = term->isDef(); + if (def) { + if (def->name()->value() == "mission") { + if (!def->term() || !def->term()->isStruct()) { + ::Print("WARNING: mission struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + + int id = 0; + Text name; + Text desc; + char script[256]; + char system[256]; + char region[256]; + int start = 0; + int type = 0; + + ZeroMemory(script, sizeof(script)); + + strcpy(system, "Unknown"); + strcpy(region, "Unknown"); + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + if (pdef->name()->value() == "id") + GetDefNumber(id, pdef, filename); + + else if (pdef->name()->value() == "name") { + GetDefText(name, pdef, filename); + name = Game::GetText(name); + } + + else if (pdef->name()->value() == "desc") { + GetDefText(desc, pdef, filename); + if (desc.length() > 0 && desc.length() < 32) + desc = Game::GetText(desc); + } + + else if (pdef->name()->value() == "start") + GetDefTime(start, pdef, filename); + + else if (pdef->name()->value() == "system") + GetDefText(system, pdef, filename); + + else if (pdef->name()->value() == "region") + GetDefText(region, pdef, filename); + + else if (pdef->name()->value() == "script") + GetDefText(script, pdef, filename); + + else if (pdef->name()->value() == "type") { + char typestr[64]; + GetDefText(typestr, pdef, filename); + type = Mission::TypeFromName(typestr); + } + } + } + + MissionInfo* info = new(__FILE__,__LINE__) MissionInfo; + if (info) { + info->id = id; + info->name = name; + info->description = desc; + info->system = system; + info->region = region; + info->script = script; + info->start = start; + info->type = type; + info->mission = 0; + + info->script.setSensitive(false); + + missions.append(info); + } + else { + ok = false; + } + } + } + } + } + } + while (term); + + loader->ReleaseBuffer(block); + + if (!ok) + Unload(); +} + +void +Campaign::LoadCustomMissions(DataLoader* loader) +{ + bool ok = true; + List files; + loader->UseFileSystem(true); + loader->ListFiles("*.*", files); + + for (int i = 0; i < files.size(); i++) { + Text file = *files[i]; + file.setSensitive(false); + + if (file.contains(".def")) { + BYTE* block = 0; + const char* filename = file.data(); + + loader->UseFileSystem(true); + loader->LoadBuffer(filename, block, true); + loader->UseFileSystem(Starshatter::UseFileSystem()); + + if (strstr((const char*) block, "MISSION") == (const char*) block) { + Text name; + Text desc; + Text system = "Unknown"; + Text region = "Unknown"; + int start = 0; + int type = 0; + int msn_id = 0; + + Parser parser(new(__FILE__,__LINE__) BlockReader((const char*) block)); + Term* term = parser.ParseTerm(); + + if (!term) { + Print("ERROR: could not parse '%s'\n", filename); + continue; + } + else { + TermText* file_type = term->isText(); + if (!file_type || file_type->value() != "MISSION") { + Print("ERROR: invalid mission file '%s'\n", filename); + term->print(10); + continue; + } + } + + do { + delete term; term = 0; + term = parser.ParseTerm(); + + if (term) { + TermDef* def = term->isDef(); + if (def) { + if (def->name()->value() == "name") { + GetDefText(name, def, filename); + name = Game::GetText(name); + } + + else if (def->name()->value() == "type") { + char typestr[64]; + GetDefText(typestr, def, filename); + type = Mission::TypeFromName(typestr); + } + + else if (def->name()->value() == "id") + GetDefNumber(msn_id, def, filename); + + else if (def->name()->value() == "desc") { + GetDefText(desc, def, filename); + if (desc.length() > 0 && desc.length() < 32) + desc = Game::GetText(desc); + } + + else if (def->name()->value() == "system") + GetDefText(system, def, filename); + + else if (def->name()->value() == "region") + GetDefText(region, def, filename); + + else if (def->name()->value() == "start") + GetDefTime(start, def, filename); + } + } + } + while (term); + + loader->ReleaseBuffer(block); + + if (strstr(filename, "custom") == filename) { + sscanf(filename+6, "%d", &msn_id); + + if (msn_id <= i) + msn_id = i+1; + } + else if (msn_id < 1) { + msn_id = i+1; + } + + MissionInfo* info = new(__FILE__,__LINE__) MissionInfo; + if (info) { + info->id = msn_id; + info->name = name; + info->type = type; + info->description = desc; + info->system = system; + info->region = region; + info->script = filename; + info->start = start; + info->mission = 0; + + info->script.setSensitive(false); + + missions.append(info); + } + else { + ok = false; + } + } + else { + Print("Invalid Custom Mission File: '%s'\n", filename); + } + + loader->ReleaseBuffer(block); + } + } + + files.destroy(); + + if (!ok) + Unload(); + else + missions.sort(); +} + +void +Campaign::LoadTemplateList(DataLoader* loader) +{ + BYTE* block = 0; + const char* filename = "Templates.def"; + + loader->UseFileSystem(true); + loader->LoadBuffer(filename, block, true); + loader->UseFileSystem(Starshatter::UseFileSystem()); + Parser parser(new(__FILE__,__LINE__) BlockReader((const char*) block)); + + Term* term = parser.ParseTerm(); + + if (!term) { + return; + } + else { + TermText* file_type = term->isText(); + if (!file_type || file_type->value() != "TEMPLATELIST") { + ::Print("WARNING: invalid template list file '%s'\n", filename); + term->print(10); + return; + } + } + + do { + delete term; term = 0; + term = parser.ParseTerm(); + + if (term) { + TermDef* def = term->isDef(); + if (def) { + if (def->name()->value() == "mission") { + if (!def->term() || !def->term()->isStruct()) { + ::Print("WARNING: mission struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + + char name[256]; + char script[256]; + char region[256]; + int id = 0; + int msn_type = 0; + int grp_type = 0; + + int min_rank = 0; + int max_rank = 0; + int action_id = 0; + int action_status = 0; + int exec_once = 0; + int start_before = TIME_NEVER; + int start_after = 0; + + name[0] = 0; + script[0] = 0; + region[0] = 0; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + if (pdef->name()->value() == "id") + GetDefNumber(id, pdef, filename); + + else if (pdef->name()->value() == "name") + GetDefText(name, pdef, filename); + + else if (pdef->name()->value() == "script") + GetDefText(script, pdef, filename); + + else if (pdef->name()->value() == "rgn" || pdef->name()->value() == "region") + GetDefText(region, pdef, filename); + + else if (pdef->name()->value() == "type") { + char typestr[64]; + GetDefText(typestr, pdef, filename); + msn_type = Mission::TypeFromName(typestr); + } + + else if (pdef->name()->value() == "group") { + char typestr[64]; + GetDefText(typestr, pdef, filename); + grp_type = CombatGroup::TypeFromName(typestr); + } + + else if (pdef->name()->value() == "min_rank") + GetDefNumber(min_rank, pdef, filename); + + else if (pdef->name()->value() == "max_rank") + GetDefNumber(max_rank, pdef, filename); + + else if (pdef->name()->value() == "action_id") + GetDefNumber(action_id, pdef, filename); + + else if (pdef->name()->value() == "action_status") + GetDefNumber(action_status, pdef, filename); + + else if (pdef->name()->value() == "exec_once") + GetDefNumber(exec_once, pdef, filename); + + else if (pdef->name()->value().contains("before")) { + if (pdef->term()->isNumber()) { + GetDefNumber(start_before, pdef, filename); + } + else { + GetDefTime(start_before, pdef, filename); + start_before -= ONE_DAY; + } + } + else if (pdef->name()->value().contains("after")) { + if (pdef->term()->isNumber()) { + GetDefNumber(start_after, pdef, filename); + } + else { + GetDefTime(start_after, pdef, filename); + start_after -= ONE_DAY; + } + } + } + } + + MissionInfo* info = new(__FILE__,__LINE__) MissionInfo; + if (info) { + info->id = id; + info->name = name; + info->script = script; + info->region = region; + info->type = msn_type; + info->min_rank = min_rank; + info->max_rank = max_rank; + info->action_id = action_id; + info->action_status = action_status; + info->exec_once = exec_once; + info->start_before = start_before; + info->start_after = start_after; + + info->script.setSensitive(false); + + TemplateList* templist = GetTemplateList(msn_type, grp_type); + + if (!templist) { + templist = new(__FILE__,__LINE__) TemplateList; + templist->mission_type = msn_type; + templist->group_type = grp_type; + templates.append(templist); + } + + templist->missions.append(info); + } + } + } + } + } + } + while (term); + + loader->ReleaseBuffer(block); +} + +// +--------------------------------------------------------------------+ + +void +Campaign::CreatePlanners() +{ + if (planners.size() > 0) + planners.destroy(); + + CampaignPlan* p; + + // PLAN EVENT MUST BE FIRST PLANNER: + p = new(__FILE__,__LINE__) CampaignPlanEvent(this); + if (p) + planners.append(p); + + p = new(__FILE__,__LINE__) CampaignPlanStrategic(this); + if (p) + planners.append(p); + + p = new(__FILE__,__LINE__) CampaignPlanAssignment(this); + if (p) + planners.append(p); + + p = new(__FILE__,__LINE__) CampaignPlanMovement(this); + if (p) + planners.append(p); + + p = new(__FILE__,__LINE__) CampaignPlanMission(this); + if (p) + planners.append(p); + + if (lockout > 0 && planners.size()) { + ListIter plan = planners; + while (++plan) + plan->SetLockout(lockout); + } +} + +// +--------------------------------------------------------------------+ + +int +Campaign::GetPlayerIFF() +{ + int iff = 1; + + if (player_group) + iff = player_group->GetIFF(); + + return iff; +} + +void +Campaign::SetPlayerGroup(CombatGroup* pg) +{ + if (player_group != pg) { + ::Print("Campaign::SetPlayerGroup(%s)\n", pg ? pg->Name().data() : "0"); + + // should verify that the player group is + // actually part of this campaign, first! + + player_group = pg; + player_unit = 0; + + // need to regenerate missions when changing + // player combat group: + if (IsDynamic()) { + ::Print(" destroying mission list...\n"); + missions.destroy(); + } + } +} + +void +Campaign::SetPlayerUnit(CombatUnit* unit) +{ + if (player_unit != unit) { + ::Print("Campaign::SetPlayerUnit(%s)\n", unit ? unit->Name().data() : "0"); + + // should verify that the player unit is + // actually part of this campaign, first! + + player_unit = unit; + + if (unit) + player_group = unit->GetCombatGroup(); + + // need to regenerate missions when changing + // player combat unit: + if (IsDynamic()) { + ::Print(" destroying mission list...\n"); + missions.destroy(); + } + } +} + +// +--------------------------------------------------------------------+ + +CombatZone* +Campaign::GetZone(const char* rgn) +{ + ListIter z = zones; + while (++z) { + if (z->HasRegion(rgn)) + return z.value(); + } + + return 0; +} + +StarSystem* +Campaign::GetSystem(const char* sys) +{ + return Galaxy::GetInstance()->GetSystem(sys); +} + +Combatant* +Campaign::GetCombatant(const char* cname) +{ + ListIter iter = combatants; + while (++iter) { + Combatant* c = iter.value(); + if (!strcmp(c->Name(), cname)) + return c; + } + + return 0; +} + +// +--------------------------------------------------------------------+ + +Mission* +Campaign::GetMission() +{ + return GetMission(mission_id); +} + +Mission* +Campaign::GetMission(int id) +{ + if (id < 0) { + ::Print("ERROR - Campaign::GetMission(%d) invalid mission id\n", id); + return 0; + } + + if (mission && mission->Identity() == id) { + return mission; + } + + MissionInfo* info = 0; + for (int i = 0; !info && i < missions.size(); i++) + if (missions[i]->id == id) + info = missions[i]; + + if (info) { + if (!info->mission) { + ::Print("Campaign::GetMission(%d) loading mission...\n", id); + info->mission = new(__FILE__,__LINE__) Mission(id, info->script, path); + if (info->mission) + info->mission->Load(); + } + + if (IsDynamic()) { + if (info->mission) { + if (!stricmp(info->mission->Situation(), "Unknown")) { + ::Print("Campaign::GetMission(%d) generating sitrep...\n", id); + CampaignSituationReport sitrep(this, info->mission); + sitrep.GenerateSituationReport(); + } + } + else { + ::Print("Campaign::GetMission(%d) could not find/load mission.\n", id); + } + } + + return info->mission; + } + + return 0; +} + +Mission* +Campaign::GetMissionByFile(const char* filename) +{ + if (!filename || !*filename) { + ::Print("ERROR - Campaign::GetMissionByFile() invalid filename\n"); + return 0; + } + + int id = 0; + int maxid = 0; + MissionInfo* info = 0; + + for (int i = 0; !info && i < missions.size(); i++) { + MissionInfo* m = missions[i]; + + if (m->id > maxid) + maxid = m->id; + + if (m->script == filename) + info = m; + } + + if (info) { + id = info->id; + + if (!info->mission) { + ::Print("Campaign::GetMission(%d) loading mission...\n", id); + info->mission = new(__FILE__,__LINE__) Mission(id, info->script, path); + if (info->mission) + info->mission->Load(); + } + + if (IsDynamic()) { + if (info->mission) { + if (!stricmp(info->mission->Situation(), "Unknown")) { + ::Print("Campaign::GetMission(%d) generating sitrep...\n", id); + CampaignSituationReport sitrep(this, info->mission); + sitrep.GenerateSituationReport(); + } + } + else { + ::Print("Campaign::GetMission(%d) could not find/load mission.\n", id); + } + } + } + + else { + info = new(__FILE__,__LINE__) MissionInfo; + if (info) { + info->id = maxid+1; + info->name = "New Custom Mission"; + info->script = filename; + info->mission = new(__FILE__,__LINE__) Mission(info->id, info->script, "Mods/Missions/"); + info->mission->SetName(info->name); + + info->script.setSensitive(false); + + missions.append(info); + } + } + + return info->mission; +} + +MissionInfo* +Campaign::CreateNewMission() +{ + int id = 0; + int maxid = 0; + MissionInfo* info = 0; + + if (campaign_id == MULTIPLAYER_MISSIONS) + maxid = 10; + + for (int i = 0; !info && i < missions.size(); i++) { + MissionInfo* m = missions[i]; + + if (m->id > maxid) + maxid = m->id; + } + + char filename[64]; + sprintf(filename, "custom%03d.def", maxid+1); + + info = new(__FILE__,__LINE__) MissionInfo; + if (info) { + info->id = maxid+1; + info->name = "New Custom Mission"; + info->script = filename; + info->mission = new(__FILE__,__LINE__) Mission(info->id, filename, path); + info->mission->SetName(info->name); + + info->script.setSensitive(false); + + missions.append(info); + } + + return info; +} + +void +Campaign::DeleteMission(int id) +{ + if (id < 0) { + ::Print("ERROR - Campaign::DeleteMission(%d) invalid mission id\n", id); + return; + } + + MissionInfo* m = 0; + int index = -1; + + for (int i = 0; !m && i < missions.size(); i++) { + if (missions[i]->id == id) { + m = missions[i]; + index = i; + } + } + + if (m) { + char full_path[256]; + + if (path[strlen(path)-1] == '/') + sprintf(full_path, "%s%s", path, m->script); + else + sprintf(full_path, "%s/%s", path, m->script); + + DeleteFile(full_path); + Load(); + } + + else { + ::Print("ERROR - Campaign::DeleteMission(%d) could not find mission\n", id); + } +} + +MissionInfo* +Campaign::GetMissionInfo(int id) +{ + if (id < 0) { + ::Print("ERROR - Campaign::GetMissionInfo(%d) invalid mission id\n", id); + return 0; + } + + MissionInfo* m = 0; + for (int i = 0; !m && i < missions.size(); i++) + if (missions[i]->id == id) + m = missions[i]; + + if (m) { + if (!m->mission) { + m->mission = new(__FILE__,__LINE__) Mission(id, m->script); + if (m->mission) + m->mission->Load(); + } + + return m; + } + + else { + ::Print("ERROR - Campaign::GetMissionInfo(%d) could not find mission\n", id); + } + + return 0; +} + +void +Campaign::ReloadMission(int id) +{ + if (mission && mission == net_mission) { + delete net_mission; + net_mission = 0; + } + + mission = 0; + + if (id >= 0 && id < missions.size()) { + MissionInfo* m = missions[id]; + delete m->mission; + m->mission = 0; + } +} + +void +Campaign::LoadNetMission(int id, const char* net_mission_script) +{ + if (mission && mission == net_mission) { + delete net_mission; + net_mission = 0; + } + + mission_id = id; + mission = new(__FILE__,__LINE__) Mission(id); + + if (mission && mission->ParseMission(net_mission_script)) + mission->Validate(); + + net_mission = mission; +} + +// +--------------------------------------------------------------------+ + +CombatAction* +Campaign::FindAction(int action_id) +{ + ListIter iter = actions; + while (++iter) { + CombatAction* a = iter.value(); + + if (a->Identity() == action_id) + return a; + } + + return 0; +} + +// +--------------------------------------------------------------------+ + +MissionInfo* +Campaign::FindMissionTemplate(int mission_type, CombatGroup* player_group) +{ + MissionInfo* info = 0; + + if (!player_group) + return info; + + TemplateList* templates = GetTemplateList(mission_type, player_group->Type()); + + if (!templates || !templates->missions.size()) + return info; + + int tries = 0; + int msize = templates->missions.size(); + + while (!info && tries < msize) { + // get next template: + int index = templates->index; + + if (index >= msize) + index = 0; + + info = templates->missions[index]; + templates->index = index + 1; + tries++; + + // validate the template: + if (info) { + if (info->action_id) { + CombatAction* a = FindAction(info->action_id); + + if (a && a->Status() != info->action_status) + info = 0; + } + + if (info && !info->IsAvailable()) { + info = 0; + } + } + } + + return info; +} + +// +--------------------------------------------------------------------+ + +TemplateList* +Campaign::GetTemplateList(int msn_type, int grp_type) +{ + for (int i = 0; i < templates.size(); i++) { + if (templates[i]->mission_type == msn_type && + templates[i]->group_type == grp_type) + return templates[i]; + } + + return 0; +} + +// +--------------------------------------------------------------------+ + +void +Campaign::SetMissionId(int id) +{ + ::Print("Campaign::SetMissionId(%d)\n", id); + + if (id > 0) + mission_id = id; + else + ::Print(" retaining mission id = %d\n", mission_id); +} + +// +--------------------------------------------------------------------+ + +double +Campaign::Stardate() +{ + return StarSystem::Stardate(); +} + +// +--------------------------------------------------------------------+ + +void +Campaign::SelectDefaultPlayerGroup(CombatGroup* g, int type) +{ + if (player_group || !g) return; + + if (g->Type() == type && !g->IsReserve() && g->Value() > 0) { + player_group = g; + player_unit = 0; + return; + } + + for (int i = 0; i < g->GetComponents().size(); i++) + SelectDefaultPlayerGroup(g->GetComponents()[i], type); +} + +// +--------------------------------------------------------------------+ + +void +Campaign::Prep() +{ + // if this is a new campaign, + // create combatants from roster and template: + if (IsDynamic() && combatants.isEmpty()) { + DataLoader* loader = DataLoader::GetLoader(); + loader->SetDataPath(path); + LoadCampaign(loader, true); + } + + StarSystem::SetBaseTime(loadTime); + + // load scripted missions: + if (IsScripted() && actions.isEmpty()) { + DataLoader* loader = DataLoader::GetLoader(); + loader->SetDataPath(path); + LoadCampaign(loader, true); + + ListIter m = missions; + while (++m) { + GetMission(m->id); + } + } + + CheckPlayerGroup(); +} + +void +Campaign::Start() +{ + ::Print("Campaign::Start()\n"); + + Prep(); + + // create planners: + CreatePlanners(); + SetStatus(CAMPAIGN_ACTIVE); +} + +void +Campaign::ExecFrame() +{ + if (InCutscene()) + return; + + time = Stardate() - startTime; + + if (status < CAMPAIGN_ACTIVE) + return; + + if (IsDynamic()) { + bool completed = false; + ListIter m = missions; + while (++m) { + if (m->mission && m->mission->IsComplete()) { + ::Print("Campaign::ExecFrame() completed mission %d '%s'\n", m->id, m->name.data()); + completed = true; + } + } + + if (completed) { + ::Print("Campaign::ExecFrame() destroying mission list after completion...\n"); + missions.destroy(); + + if (!player_group || player_group->IsFighterGroup()) + time += 10 * 3600; + else + time += 20 * 3600; + + StarSystem::SetBaseTime(startTime + time - Game::GameTime()/1000.0); + } + else { + m.reset(); + + while (++m) { + if (m->start < time && !m->mission->IsActive()) { + MissionInfo* info = m.removeItem(); + + if (info) + ::Print("Campaign::ExecFrame() deleting expired mission %d start: %d current: %d\n", + info->id, + info->start, + (int) time); + + delete info; + } + } + } + + // PLAN EVENT MUST BE FIRST PLANNER: + if (loaded_from_savegame && planners.size() > 0) { + CampaignPlanEvent* plan_event = (CampaignPlanEvent*) planners.first(); + plan_event->ExecScriptedEvents(); + + loaded_from_savegame = false; + } + + ListIter plan = planners; + while (++plan) { + CheckPlayerGroup(); + plan->ExecFrame(); + } + + CheckPlayerGroup(); + + // Auto save game AFTER planners have run! + // This is done to ensure that campaign status + // is properly recorded after winning or losing + // the campaign. + + if (completed) { + CampaignSaveGame save(this); + save.SaveAuto(); + } + } + else { + // PLAN EVENT MUST BE FIRST PLANNER: + if (planners.size() > 0) { + CampaignPlanEvent* plan_event = (CampaignPlanEvent*) planners.first(); + plan_event->ExecScriptedEvents(); + } + } +} + +// +--------------------------------------------------------------------+ + +void +Campaign::LockoutEvents(int seconds) +{ + lockout = seconds; +} + +void +Campaign::CheckPlayerGroup() +{ + if (!player_group || player_group->IsReserve() || player_group->CalcValue() < 1) { + int player_iff = GetPlayerIFF(); + player_group = 0; + + CombatGroup* force = 0; + for (int i = 0; i < combatants.size() && !force; i++) { + if (combatants[i]->GetIFF() == player_iff) { + force = combatants[i]->GetForce(); + } + } + + if (force) { + force->CalcValue(); + SelectDefaultPlayerGroup(force, CombatGroup::WING); + + if (!player_group) + SelectDefaultPlayerGroup(force, CombatGroup::DESTROYER_SQUADRON); + } + } + + if (player_unit && player_unit->GetValue() < 1) + SetPlayerUnit(0); +} + +// +--------------------------------------------------------------------+ + +void FPU2Extended(); +void FPURestore(); + +void +Campaign::StartMission() +{ + Mission* m = GetMission(); + + if (m) { + ::Print("\n\nCampaign Start Mission - %d. '%s'\n", m->Identity(), m->Name()); + + if (!scripted) { + FPU2Extended(); + + double gtime = (double) Game::GameTime() / 1000.0; + double base = startTime + m->Start() - 15 - gtime; + + StarSystem::SetBaseTime(base); + + double current_time = Stardate() - startTime; + + char buffer[32]; + FormatDayTime(buffer, current_time); + ::Print(" current time: %s\n", buffer); + + FormatDayTime(buffer, m->Start()); + ::Print(" mission start: %s\n", buffer); + ::Print("\n"); + } + } +} + +void +Campaign::RollbackMission() +{ + ::Print("Campaign::RollbackMission()\n"); + + Mission* m = GetMission(); + + if (m) { + if (!scripted) { + FPU2Extended(); + + double gtime = (double) Game::GameTime() / 1000.0; + double base = startTime + m->Start() - 60 - gtime; + + StarSystem::SetBaseTime(base); + + double current_time = Stardate() - startTime; + ::Print(" mission start: %d\n", m->Start()); + ::Print(" current time: %d\n", (int) current_time); + } + + m->SetActive(false); + m->SetComplete(false); + } +} + +// +--------------------------------------------------------------------+ + +bool +Campaign::InCutscene() const +{ + Starshatter* stars = Starshatter::GetInstance(); + return stars ? stars->InCutscene() : false; +} + +bool +Campaign::IsDynamic() const +{ + return campaign_id >= DYNAMIC_CAMPAIGN && + campaign_id < SINGLE_MISSIONS; +} + +bool +Campaign::IsTraining() const +{ + return campaign_id == TRAINING_CAMPAIGN; +} + +bool +Campaign::IsScripted() const +{ + return scripted; +} + +bool +Campaign::IsSequential() const +{ + return sequential; +} + +// +--------------------------------------------------------------------+ + +static CombatGroup* FindGroup(CombatGroup* g, int type, int id) +{ + if (g->Type() == type && g->GetID() == id) + return g; + + CombatGroup* result = 0; + + ListIter subgroup = g->GetComponents(); + while (++subgroup && !result) + result = FindGroup(subgroup.value(), type, id); + + return result; +} + +CombatGroup* +Campaign::FindGroup(int iff, int type, int id) +{ + CombatGroup* result = 0; + + ListIter combatant = combatants; + while (++combatant && !result) { + if (combatant->GetIFF() == iff) { + result = ::FindGroup(combatant->GetForce(), type, id); + } + } + + return result; +} + +// +--------------------------------------------------------------------+ + +static void FindGroups(CombatGroup* g, int type, CombatGroup* near_group, + List& groups) +{ + if (g->Type() == type && g->IntelLevel() > Intel::RESERVE) { + if (!near_group || g->GetAssignedZone() == near_group->GetAssignedZone()) + groups.append(g); + } + + ListIter subgroup = g->GetComponents(); + while (++subgroup) + FindGroups(subgroup.value(), type, near_group, groups); +} + +CombatGroup* +Campaign::FindGroup(int iff, int type, CombatGroup* near_group) +{ + CombatGroup* result = 0; + List groups; + + ListIter combatant = combatants; + while (++combatant) { + if (combatant->GetIFF() == iff) { + FindGroups(combatant->GetForce(), type, near_group, groups); + } + } + + if (groups.size() > 0) { + int index = (int) Random(0, groups.size()); + if (index >= groups.size()) index = groups.size() - 1; + result = groups[index]; + } + + return result; +} + +// +--------------------------------------------------------------------+ + +static void FindStrikeTargets(CombatGroup* g, CombatGroup* strike_group, + List& groups) +{ + if (!strike_group || !strike_group->GetAssignedZone()) return; + + if (g->IsStrikeTarget() && g->IntelLevel() > Intel::RESERVE) { + if (strike_group->GetAssignedZone() == g->GetAssignedZone() || + strike_group->GetAssignedZone()->HasRegion(g->GetRegion())) + groups.append(g); + } + + ListIter subgroup = g->GetComponents(); + while (++subgroup) + FindStrikeTargets(subgroup.value(), strike_group, groups); +} + +CombatGroup* +Campaign::FindStrikeTarget(int iff, CombatGroup* strike_group) +{ + CombatGroup* result = 0; + + List groups; + + ListIter combatant = GetCombatants(); + while (++combatant) { + if (combatant->GetIFF() != 0 && combatant->GetIFF() != iff) { + FindStrikeTargets(combatant->GetForce(), strike_group, groups); + } + } + + if (groups.size() > 0) { + int index = rand() % groups.size(); + result = groups[index]; + } + + return result; +} + +// +--------------------------------------------------------------------+ + +void +Campaign::CommitExpiredActions() +{ + ListIter iter = actions; + while (++iter) { + CombatAction* a = iter.value(); + + if (a->IsAvailable()) + a->SetStatus(CombatAction::COMPLETE); + } + + updateTime = time; +} + +// +--------------------------------------------------------------------+ + +int +Campaign::GetPlayerTeamScore() +{ + int score_us = 0; + int score_them = 0; + + if (player_group) { + int iff = player_group->GetIFF(); + + ListIter iter = combatants; + while (++iter) { + Combatant* c = iter.value(); + + if (iff <= 1) { + if (c->GetIFF() <= 1) + score_us += c->Score(); + else + score_them += c->Score(); + } + + else { + if (c->GetIFF() <= 1) + score_them += c->Score(); + else + score_us += c->Score(); + } + } + } + + return score_us - score_them; +} + +// +--------------------------------------------------------------------+ + +void +Campaign::SetStatus(int s) +{ + status = s; + + // record the win in player profile: + if (status == CAMPAIGN_SUCCESS) { + Player* player = Player::GetCurrentPlayer(); + + if (player) + player->SetCampaignComplete(campaign_id); + } + + if (status > CAMPAIGN_ACTIVE) { + ::Print("Campaign::SetStatus() destroying mission list at campaign end\n"); + missions.destroy(); + } +} + +// +--------------------------------------------------------------------+ + +static void GetCombatUnits(CombatGroup* g, List& units) +{ + if (g) { + ListIter unit = g->GetUnits(); + while (++unit) { + CombatUnit* u = unit.value(); + + if (u->Count() - u->DeadCount() > 0) + units.append(u); + } + + ListIter comp = g->GetComponents(); + while (++comp) { + CombatGroup* g2 = comp.value(); + + if (!g2->IsReserve()) + GetCombatUnits(g2, units); + } + } +} + +int +Campaign::GetAllCombatUnits(int iff, List& units) +{ + units.clear(); + + ListIter iter = combatants; + while (++iter) { + Combatant* c = iter.value(); + + if (iff < 0 || c->GetIFF() == iff) { + GetCombatUnits(c->GetForce(), units); + } + } + + return units.size(); +} diff --git a/Stars45/Campaign.h b/Stars45/Campaign.h new file mode 100644 index 0000000..c844604 --- /dev/null +++ b/Stars45/Campaign.h @@ -0,0 +1,282 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2006. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Campaign.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Campaign defines a strategic military scenario. This class + owns (or generates) the Mission list that defines the action + in the campaign. +*/ + +#ifndef Campaign_h +#define Campaign_h + +#include "Types.h" +#include "Bitmap.h" +#include "Geometry.h" +#include "Text.h" +#include "Term.h" +#include "List.h" + +// +--------------------------------------------------------------------+ + +class Campaign; +class CampaignPlan; +class Combatant; +class CombatAction; +class CombatEvent; +class CombatGroup; +class CombatUnit; +class CombatZone; +class DataLoader; +class Mission; +class MissionTemplate; +class StarSystem; + +// +--------------------------------------------------------------------+ + +class MissionInfo +{ +public: + static const char* TYPENAME() { return "MissionInfo"; } + + MissionInfo(); + ~MissionInfo(); + + int operator == (const MissionInfo& m) const { return id == m.id; } + int operator < (const MissionInfo& m) const { return id < m.id; } + int operator <= (const MissionInfo& m) const { return id <= m.id; } + + bool IsAvailable(); + + int id; + Text name; + Text player_info; + Text description; + Text system; + Text region; + Text script; + int start; + int type; + + int min_rank; + int max_rank; + int action_id; + int action_status; + int exec_once; + int start_before; + int start_after; + + Mission* mission; +}; + +class TemplateList +{ +public: + static const char* TYPENAME() { return "TemplateList"; } + + TemplateList(); + ~TemplateList(); + + int mission_type; + int group_type; + int index; + List missions; +}; + +// +--------------------------------------------------------------------+ + +class Campaign +{ +public: + static const char* TYPENAME() { return "Campaign"; } + + enum CONSTANTS { + TRAINING_CAMPAIGN = 1, + DYNAMIC_CAMPAIGN, + MOD_CAMPAIGN = 100, + SINGLE_MISSIONS = 1000, + MULTIPLAYER_MISSIONS, + CUSTOM_MISSIONS, + + NUM_IMAGES = 6 + }; + + enum STATUS { + CAMPAIGN_INIT, + CAMPAIGN_ACTIVE, + CAMPAIGN_SUCCESS, + CAMPAIGN_FAILED + }; + + Campaign(int id, const char* name=0); + Campaign(int id, const char* name, const char* path); + virtual ~Campaign(); + + int operator == (const Campaign& s) const { return name == s.name; } + int operator < (const Campaign& s) const { return campaign_id < s.campaign_id; } + + // operations: + virtual void Load(); + virtual void Prep(); + virtual void Start(); + virtual void ExecFrame(); + virtual void Unload(); + + virtual void Clear(); + virtual void CommitExpiredActions(); + virtual void LockoutEvents(int seconds); + virtual void CheckPlayerGroup(); + void CreatePlanners(); + + // accessors: + const char* Name() const { return name; } + const char* Description() const { return description; } + const char* Path() const { return path; } + + const char* Situation() const { return situation; } + const char* Orders() const { return orders; } + + void SetSituation(const char* s) { situation = s; } + void SetOrders(const char* o) { orders = o; } + + int GetPlayerTeamScore(); + List& GetMissionList() { return missions; } + List& GetCombatants() { return combatants; } + List& GetZones() { return zones; } + List& GetSystemList() { return systems; } + List& GetActions() { return actions; } + List& GetEvents() { return events; } + CombatEvent* GetLastEvent(); + + CombatAction* FindAction(int id); + + int CountNewEvents() const; + + int GetPlayerIFF(); + CombatGroup* GetPlayerGroup() { return player_group; } + void SetPlayerGroup(CombatGroup* pg); + CombatUnit* GetPlayerUnit() { return player_unit; } + void SetPlayerUnit(CombatUnit* pu); + + Combatant* GetCombatant(const char* name); + CombatGroup* FindGroup(int iff, int type, int id); + CombatGroup* FindGroup(int iff, int type, CombatGroup* near_group=0); + CombatGroup* FindStrikeTarget(int iff, CombatGroup* strike_group); + + StarSystem* GetSystem(const char* sys); + CombatZone* GetZone(const char* rgn); + MissionInfo* CreateNewMission(); + void DeleteMission(int id); + Mission* GetMission(); + Mission* GetMission(int id); + Mission* GetMissionByFile(const char* filename); + MissionInfo* GetMissionInfo(int id); + MissionInfo* FindMissionTemplate(int msn_type, CombatGroup* player_group); + void ReloadMission(int id); + void LoadNetMission(int id, const char* net_mission); + void StartMission(); + void RollbackMission(); + + void SetCampaignId(int id); + int GetCampaignId() const { return campaign_id; } + void SetMissionId(int id); + int GetMissionId() const { return mission_id; } + Bitmap* GetImage(int n) { return &image[n]; } + double GetTime() const { return time; } + double GetStartTime() const { return startTime; } + void SetStartTime(double t) { startTime = t; } + double GetLoadTime() const { return loadTime; } + void SetLoadTime(double t) { loadTime = t; } + double GetUpdateTime() const { return updateTime; } + void SetUpdateTime(double t) { updateTime = t; } + + bool InCutscene() const; + bool IsDynamic() const; + bool IsTraining() const; + bool IsScripted() const; + bool IsSequential() const; + bool IsSaveGame() const { return loaded_from_savegame; } + void SetSaveGame(bool s) { loaded_from_savegame = s; } + + bool IsActive() const { return status == CAMPAIGN_ACTIVE; } + bool IsComplete() const { return status == CAMPAIGN_SUCCESS; } + bool IsFailed() const { return status == CAMPAIGN_FAILED; } + void SetStatus(int s); + int GetStatus() const { return status; } + + int GetAllCombatUnits(int iff, List& units); + + static void Initialize(); + static void Close(); + static Campaign* GetCampaign(); + static List& + GetAllCampaigns(); + static int GetLastCampaignId(); + static Campaign* SelectCampaign(const char* name); + static Campaign* CreateCustomCampaign(const char* name, const char* path); + + static double Stardate(); + +protected: + void LoadCampaign(DataLoader* loader, bool full=false); + void LoadTemplateList(DataLoader* loader); + void LoadMissionList(DataLoader* loader); + void LoadCustomMissions(DataLoader* loader); + void ParseGroup(TermStruct* val, + CombatGroup* force, + CombatGroup* clone, + const char* filename); + void ParseAction(TermStruct* val, + const char* filename); + CombatGroup* CloneOver(CombatGroup* force, + CombatGroup* clone, + CombatGroup* group); + void SelectDefaultPlayerGroup(CombatGroup* g, int type); + TemplateList* GetTemplateList(int msn_type, int grp_type); + + // attributes: + int campaign_id; + int status; + char filename[64]; + char path[64]; + Text name; + Text description; + Text situation; + Text orders; + Bitmap image[NUM_IMAGES]; + + bool scripted; + bool sequential; + bool loaded_from_savegame; + + List combatants; + List systems; + List zones; + List planners; + List missions; + List templates; + List actions; + List events; + CombatGroup* player_group; + CombatUnit* player_unit; + + int mission_id; + Mission* mission; + Mission* net_mission; + + double time; + double loadTime; + double startTime; + double updateTime; + int lockout; +}; + +#endif Campaign_h + diff --git a/Stars45/CampaignMissionFighter.cpp b/Stars45/CampaignMissionFighter.cpp new file mode 100644 index 0000000..f859ad8 --- /dev/null +++ b/Stars45/CampaignMissionFighter.cpp @@ -0,0 +1,2152 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CampaignMissionFighter.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + CampaignMissionFighter generates missions and mission + info for the player's FIGHTER SQUADRON as part of a + dynamic campaign. +*/ + +#include "MemDebug.h" +#include "CampaignMissionFighter.h" +#include "CampaignMissionRequest.h" +#include "Campaign.h" +#include "Combatant.h" +#include "CombatAssignment.h" +#include "CombatGroup.h" +#include "CombatUnit.h" +#include "CombatZone.h" +#include "Callsign.h" +#include "Galaxy.h" +#include "Mission.h" +#include "MissionTemplate.h" +#include "Instruction.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "Starshatter.h" +#include "StarSystem.h" +#include "Player.h" + +#include "Random.h" + +static int pkg_id = 1000; +extern int dump_missions; + +// +--------------------------------------------------------------------+ + +CampaignMissionFighter::CampaignMissionFighter(Campaign* c) + : campaign(c), squadron(0), mission(0), player_elem(0), + strike_group(0), strike_target(0), prime_target(0), + carrier_elem(0), ward(0), escort(0), airborne(false), airbase(false), + ownside(0), enemy(-1), mission_type(0), mission_info(0) +{ + if (!campaign || !campaign->GetPlayerGroup()) { + ::Print("ERROR - CMF campaign=0x%08x player_group=0x%08x\n", + campaign, (DWORD) campaign?campaign->GetPlayerGroup():0); + return; + } + + CombatGroup* player_group = campaign->GetPlayerGroup(); + + switch (player_group->Type()) { + case CombatGroup::WING: { + CombatGroup* wing = player_group; + ListIter iter = wing->GetComponents(); + + while (++iter) { + if (iter->Type() == CombatGroup::FIGHTER_SQUADRON) { + squadron = iter.value(); + } + } + } + break; + + case CombatGroup::FIGHTER_SQUADRON: + case CombatGroup::INTERCEPT_SQUADRON: + case CombatGroup::ATTACK_SQUADRON: + squadron = player_group; + break; + + default: + ::Print("ERROR - CMF invalid player group: %s IFF %d\n", + player_group->GetDescription(), + player_group->GetIFF()); + break; + } + + if (squadron) { + CombatGroup* carrier = squadron->FindCarrier(); + + if (carrier && carrier->Type() == CombatGroup::STARBASE) { + airbase = true; + } + } +} + +CampaignMissionFighter::~CampaignMissionFighter() {} + +// +--------------------------------------------------------------------+ + +void +CampaignMissionFighter::CreateMission(CampaignMissionRequest* req) +{ + if (!campaign || !squadron || !req) + return; + + ::Print("\n-----------------------------------------------\n"); + if (req->Script().length()) + ::Print("CMF CreateMission() request: %s '%s'\n", + Mission::RoleName(req->Type()), + (const char*) req->Script()); + + else + ::Print("CMF CreateMission() request: %s %s\n", + Mission::RoleName(req->Type()), + req->GetObjective() ? req->GetObjective()->Name() : "(no target)"); + + request = req; + mission_info = 0; + + if (request->GetPrimaryGroup()) { + switch (request->GetPrimaryGroup()->Type()) { + case CombatGroup::FIGHTER_SQUADRON: + case CombatGroup::INTERCEPT_SQUADRON: + case CombatGroup::ATTACK_SQUADRON: + squadron = request->GetPrimaryGroup(); + break; + } + } + + ownside = squadron->GetIFF(); + + for (int i = 0; i < campaign->GetCombatants().size(); i++) { + int iff = campaign->GetCombatants().at(i)->GetIFF(); + if (iff > 0 && iff != ownside) { + enemy = iff; + break; + } + } + + static int id_key = 1; + GenerateMission(id_key++); + DefineMissionObjectives(); + + MissionInfo* info = DescribeMission(); + + if (info) { + campaign->GetMissionList().append(info); + + ::Print("CMF Created %03d '%s' %s\n\n", + info->id, + (const char*) info->name, + Mission::RoleName(mission->Type())); + + if (dump_missions) { + Text script = mission->Serialize(); + char fname[32]; + + sprintf(fname, "msn%03d.def", info->id); + FILE* f = fopen(fname, "w"); + if (f) { + fprintf(f, "%s\n", script.data()); + fclose(f); + } + } + } + else { + ::Print("CMF failed to create mission.\n"); + } +} + +// +--------------------------------------------------------------------+ + +Mission* +CampaignMissionFighter::GenerateMission(int id) +{ + bool found = false; + + SelectType(); + + if (request && request->Script().length()) { + MissionTemplate* mt = new(__FILE__,__LINE__) MissionTemplate(id, request->Script(), campaign->Path()); + if (mt) + mt->SetPlayerSquadron(squadron); + mission = mt; + found = true; + } + + else { + mission_info = campaign->FindMissionTemplate(mission_type, squadron); + found = mission_info != 0; + + if (found) { + MissionTemplate* mt = new(__FILE__,__LINE__) MissionTemplate(id, mission_info->script, campaign->Path()); + if (mt) + mt->SetPlayerSquadron(squadron); + mission = mt; + } + else { + mission = new(__FILE__,__LINE__) Mission(id); + if (mission) + mission->SetType(mission_type); + } + } + + if (!mission) { + Exit(); + return 0; + } + + char name[64]; + sprintf(name, "Fighter Mission %d", id); + + mission->SetName(name); + mission->SetTeam(squadron->GetIFF()); + mission->SetStart(request->StartTime()); + + SelectRegion(); + GenerateStandardElements(); + CreatePatrols(); + + if (!found) { + GenerateMissionElements(); + mission->SetOK(true); + mission->Validate(); + } + + else { + mission->Load(); + + if (mission->IsOK()) { + player_elem = mission->GetPlayer(); + prime_target = mission->GetTarget(); + ward = mission->GetWard(); + + Player* p = Player::GetCurrentPlayer(); + + if (player_elem && p) + player_elem->SetAlert(!p->FlyingStart()); + } + + // if there was a problem, scrap the mission + // and start over: + else { + delete mission; + + mission = new(__FILE__,__LINE__) Mission(id); + mission->SetType(mission_type); + mission->SetName(name); + mission->SetTeam(squadron->GetIFF()); + mission->SetStart(request->StartTime()); + + SelectRegion(); + GenerateStandardElements(); + GenerateMissionElements(); + + mission->SetOK(true); + mission->Validate(); + } + } + + return mission; +} + +// +--------------------------------------------------------------------+ + +bool +CampaignMissionFighter::IsGroundObjective(CombatGroup* obj) +{ + bool ground = false; + + if (obj) { + CombatGroup* pgroup = campaign->GetPlayerGroup(); + + if (pgroup) { + CombatZone* zone = pgroup->GetAssignedZone(); + + if (zone) { + StarSystem* system = campaign->GetSystem(zone->System()); + + if (system) { + OrbitalRegion* region = system->FindRegion(obj->GetRegion()); + + if (region && region->Type() == Orbital::TERRAIN) { + ground = true; + } + } + } + } + } + + return ground; +} + +// +--------------------------------------------------------------------+ + +void +CampaignMissionFighter::SelectType() +{ + int type = Mission::PATROL; + + if (request) { + type = request->Type(); + if (type == Mission::STRIKE) { + strike_group = request->GetPrimaryGroup(); + + // verify that objective is a ground target: + if (!IsGroundObjective(request->GetObjective())) { + type = Mission::ASSAULT; + } + } + + else if (type == Mission::ESCORT_STRIKE) { + strike_group = request->GetSecondaryGroup(); + if (!strike_group || strike_group->CalcValue() < 1) { + type = Mission::SWEEP; + strike_group = 0; + } + } + } + + mission_type = type; +} + +void +CampaignMissionFighter::SelectRegion() +{ + CombatZone* zone = squadron->GetAssignedZone(); + + if (!zone) + zone = squadron->GetCurrentZone(); + + if (zone) { + mission->SetStarSystem(campaign->GetSystem(zone->System())); + mission->SetRegion(*zone->GetRegions().at(0)); + + orb_region = mission->GetRegion(); + + if (zone->GetRegions().size() > 1) { + air_region = *zone->GetRegions().at(1); + + StarSystem* system = mission->GetStarSystem(); + OrbitalRegion* rgn = 0; + + if (system) + rgn = system->FindRegion(air_region); + + if (!rgn || rgn->Type() != Orbital::TERRAIN) + air_region = ""; + } + + if (air_region.length() > 0) { + if (request && IsGroundObjective(request->GetObjective())) { + airborne = true; + } + + else if (mission->Type() >= Mission::AIR_PATROL && + mission->Type() <= Mission::AIR_INTERCEPT) { + airborne = true; + } + + else if (mission->Type() == Mission::STRIKE || + mission->Type() == Mission::ESCORT_STRIKE) { + if (strike_group) { + strike_target = campaign->FindStrikeTarget(ownside, strike_group); + + if (strike_target && strike_target->GetRegion() == air_region) + airborne = true; + } + } + + if (airbase) { + mission->SetRegion(air_region); + } + } + } + + else { + ::Print("WARNING: CMF - No zone for '%s'\n", squadron->Name().data()); + + StarSystem* s = campaign->GetSystemList()[0]; + + mission->SetStarSystem(s); + mission->SetRegion(s->Regions()[0]->Name()); + } + + if (!airborne) { + switch (mission->Type()) { + case Mission::AIR_PATROL: mission->SetType(Mission::PATROL); break; + case Mission::AIR_SWEEP: mission->SetType(Mission::SWEEP); break; + case Mission::AIR_INTERCEPT: mission->SetType(Mission::INTERCEPT); break; + default: break; + } + } +} + +// +--------------------------------------------------------------------+ + +void +CampaignMissionFighter::GenerateStandardElements() +{ + ListIter z_iter = campaign->GetZones(); + while (++z_iter) { + CombatZone* z = z_iter.value(); + + ListIter iter = z->GetForces(); + while (++iter) { + ZoneForce* force = iter.value(); + ListIter group = force->GetGroups(); + + while (++group) { + CombatGroup* g = group.value(); + + switch (g->Type()) { + case CombatGroup::INTERCEPT_SQUADRON: + case CombatGroup::FIGHTER_SQUADRON: + case CombatGroup::ATTACK_SQUADRON: + case CombatGroup::LCA_SQUADRON: + CreateSquadron(g); + break; + + case CombatGroup::DESTROYER_SQUADRON: + case CombatGroup::BATTLE_GROUP: + case CombatGroup::CARRIER_GROUP: + CreateElements(g); + break; + + case CombatGroup::MINEFIELD: + case CombatGroup::BATTERY: + case CombatGroup::MISSILE: + case CombatGroup::STATION: + case CombatGroup::STARBASE: + case CombatGroup::SUPPORT: + case CombatGroup::COURIER: + case CombatGroup::MEDICAL: + case CombatGroup::SUPPLY: + case CombatGroup::REPAIR: + CreateElements(g); + break; + + case CombatGroup::CIVILIAN: + case CombatGroup::WAR_PRODUCTION: + case CombatGroup::FACTORY: + case CombatGroup::REFINERY: + case CombatGroup::RESOURCE: + case CombatGroup::INFRASTRUCTURE: + case CombatGroup::TRANSPORT: + case CombatGroup::NETWORK: + case CombatGroup::HABITAT: + case CombatGroup::STORAGE: + case CombatGroup::NON_COM: + CreateElements(g); + break; + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +CampaignMissionFighter::GenerateMissionElements() +{ + CreateWards(); + CreatePlayer(squadron); + CreateTargets(); + CreateEscorts(); + + if (player_elem) { + Instruction* obj = new(__FILE__,__LINE__) Instruction(mission->GetRegion(), + Point(0,0,0), + Instruction::RTB); + + if (obj) + player_elem->AddObjective(obj); + } +} + +// +--------------------------------------------------------------------+ + +void +CampaignMissionFighter::CreateElements(CombatGroup* g) +{ + MissionElement* elem = 0; + List& units = g->GetUnits(); + + CombatUnit* cmdr = 0; + + for (int i = 0; i < units.size(); i++) { + elem = CreateSingleElement(g, units[i]); + + if (elem) { + if (!cmdr) { + cmdr = units[i]; + } + else { + elem->SetCommander(cmdr->Name()); + + if (g->Type() == CombatGroup::CARRIER_GROUP && + elem->MissionRole() == Mission::ESCORT) { + Instruction* obj = new(__FILE__,__LINE__) Instruction(Instruction::ESCORT, cmdr->Name()); + if (obj) + elem->AddObjective(obj); + } + } + + mission->AddElement(elem); + } + } +} + +MissionElement* +CampaignMissionFighter::CreateSingleElement(CombatGroup* g, CombatUnit* u) +{ + if (!g || g->IsReserve()) return 0; + if (!u || u->LiveCount() < 1) return 0; + + // make sure this unit is actually in the right star system: + Galaxy* galaxy = Galaxy::GetInstance(); + if (galaxy) { + if (galaxy->FindSystemByRegion(u->GetRegion()) != + galaxy->FindSystemByRegion(squadron->GetRegion())) { + return 0; + } + } + + // make sure this unit isn't already in the mission: + ListIter e_iter = mission->GetElements(); + while (++e_iter) { + MissionElement* elem = e_iter.value(); + + if (elem->GetCombatUnit() == u) + return 0; + } + + MissionElement* elem = new(__FILE__,__LINE__) MissionElement; + if (!elem) { + Exit(); + return 0; + } + + if (u->Name().length()) + elem->SetName(u->Name()); + else + elem->SetName(u->DesignName()); + + elem->SetElementID(pkg_id++); + + elem->SetDesign(u->GetDesign()); + elem->SetCount(u->LiveCount()); + elem->SetIFF(u->GetIFF()); + elem->SetIntelLevel(g->IntelLevel()); + elem->SetRegion(u->GetRegion()); + elem->SetHeading(u->GetHeading()); + + int unit_index = g->GetUnits().index(u); + Point base_loc = u->Location(); + bool exact = u->IsStatic(); // exact unit-level placement + + if (base_loc.length() < 1) { + base_loc = g->Location(); + exact = false; + } + + if (unit_index < 0 || unit_index > 0 && !exact) { + Point loc = RandomDirection(); + + if (!u->IsStatic()) { + while (fabs(loc.y) > fabs(loc.x)) + loc = RandomDirection(); + + loc *= 10e3 + 9e3 * unit_index; + } + else { + loc *= 2e3 + 2e3 * unit_index; + } + + elem->SetLocation(base_loc + loc); + } + else { + elem->SetLocation(base_loc); + } + + if (g->Type() == CombatGroup::CARRIER_GROUP) { + if (u->Type() == Ship::CARRIER) { + elem->SetMissionRole(Mission::FLIGHT_OPS); + + if (squadron && elem->GetCombatGroup() == squadron->FindCarrier()) + carrier_elem = elem; + + else if (!carrier_elem && u->GetIFF() == squadron->GetIFF()) + carrier_elem = elem; + } + else { + elem->SetMissionRole(Mission::ESCORT); + } + } + else if (u->Type() == Ship::STATION || + u->Type() == Ship::STARBASE) { + elem->SetMissionRole(Mission::FLIGHT_OPS); + + if (squadron && elem->GetCombatGroup() == squadron->FindCarrier()) { + carrier_elem = elem; + + if (u->Type() == Ship::STARBASE) + airbase = true; + } + } + else if (u->Type() == Ship::FARCASTER) { + elem->SetMissionRole(Mission::OTHER); + + // link farcaster to other terminus: + Text name = u->Name(); + int dash = -1; + + for (int i = 0; i < (int) name.length(); i++) + if (name[i] == '-') + dash = i; + + Text src = name.substring(0, dash); + Text dst = name.substring(dash+1, name.length() - (dash+1)); + + Instruction* obj = new(__FILE__,__LINE__) Instruction(Instruction::VECTOR, dst + "-" + src); + elem->AddObjective(obj); + } + else if (u->Type() & Ship::STARSHIPS != 0) { + elem->SetMissionRole(Mission::FLEET); + } + + elem->SetCombatGroup(g); + elem->SetCombatUnit(u); + + return elem; +} + +CombatUnit* +CampaignMissionFighter::FindCarrier(CombatGroup* g) +{ + CombatGroup* carrier = g->FindCarrier(); + + if (carrier && carrier->GetUnits().size()) { + MissionElement* carrier_elem = mission->FindElement(carrier->Name()); + + if (carrier_elem) + return carrier->GetUnits().at(0); + } + + return 0; +} + +void +CampaignMissionFighter::CreateSquadron(CombatGroup* g) +{ + if (!g || g->IsReserve()) return; + + CombatUnit* fighter = g->GetUnits().at(0); + CombatUnit* carrier = FindCarrier(g); + + if (!fighter || !carrier) return; + + int live_count = fighter->LiveCount(); + int maint_count = (live_count > 4) ? live_count / 2 : 0; + + MissionElement* elem = new(__FILE__,__LINE__) MissionElement; + + if (!elem) { + Exit(); + return; + } + + elem->SetName(g->Name()); + elem->SetElementID(pkg_id++); + + elem->SetDesign(fighter->GetDesign()); + elem->SetCount(fighter->Count()); + elem->SetDeadCount(fighter->DeadCount()); + elem->SetMaintCount(maint_count); + elem->SetIFF(fighter->GetIFF()); + elem->SetIntelLevel(g->IntelLevel()); + elem->SetRegion(fighter->GetRegion()); + + elem->SetCarrier(carrier->Name()); + elem->SetCommander(carrier->Name()); + elem->SetLocation(carrier->Location() + RandomPoint()); + + elem->SetCombatGroup(g); + elem->SetCombatUnit(fighter); + + mission->AddElement(elem); +} + +void +CampaignMissionFighter::CreatePlayer(CombatGroup* g) +{ + int pkg_size = 2; + + if (mission->Type() == Mission::STRIKE || mission->Type() == Mission::ASSAULT) { + if (request && request->GetObjective()) { + int tgt_type = request->GetObjective()->Type(); + + if (tgt_type >= CombatGroup::FLEET && tgt_type <= CombatGroup::CARRIER_GROUP) + pkg_size = 4; + + if (tgt_type == CombatGroup::STATION || tgt_type == CombatGroup::STARBASE) + pkg_size = 4; + } + } + + MissionElement* elem = CreateFighterPackage(g, pkg_size, mission->Type()); + + if (elem) { + Player* p = Player::GetCurrentPlayer(); + elem->SetAlert(p ? !p->FlyingStart() : true); + elem->SetPlayer(1); + + if (ward) { + Point approach = elem->Location() - ward->Location(); + approach.Normalize(); + + Point pickup = ward->Location() + approach * 50e3; + double delta = (pickup - elem->Location()).length(); + + if (delta > 30e3) { + Instruction* n = new(__FILE__,__LINE__) Instruction(elem->Region(), pickup, Instruction::ESCORT); + n->SetTarget(ward->Name()); + n->SetSpeed(750); + elem->AddNavPoint(n); + } + + Instruction* obj = new(__FILE__,__LINE__) Instruction(Instruction::ESCORT, ward->Name()); + + switch (mission->Type()) { + case Mission::ESCORT_FREIGHT: + obj->SetTargetDesc(Text("the star freighter ") + ward->Name()); + break; + + case Mission::ESCORT_SHUTTLE: + obj->SetTargetDesc(Text("the shuttle ") + ward->Name()); + break; + + case Mission::ESCORT_STRIKE: + obj->SetTargetDesc(Text("the ") + ward->Name() + Text(" strike package")); + break; + + case Mission::DEFEND: + obj->SetTargetDesc(Text("the ") + ward->Name()); + break; + + default: + if (ward->GetCombatGroup()) { + obj->SetTargetDesc(Text("the ") + ward->GetCombatGroup()->GetDescription()); + } + else { + obj->SetTargetDesc(Text("the ") + ward->Name()); + } + break; + } + + elem->AddObjective(obj); + } + + mission->AddElement(elem); + + player_elem = elem; + } +} + +// +--------------------------------------------------------------------+ + +void +CampaignMissionFighter::CreatePatrols() +{ + List patrols; + + ListIter iter = mission->GetElements(); + while (++iter) { + MissionElement* squad_elem = iter.value(); + CombatGroup* squadron = squad_elem->GetCombatGroup(); + CombatUnit* unit = squad_elem->GetCombatUnit(); + + if (!squad_elem->IsSquadron() || !squadron || !unit || unit->LiveCount() < 4) + continue; + + if (squadron->Type() == CombatGroup::INTERCEPT_SQUADRON || + squadron->Type() == CombatGroup::FIGHTER_SQUADRON) { + + StarSystem* system = mission->GetStarSystem(); + CombatGroup* base = squadron->FindCarrier(); + OrbitalRegion* region = system->FindRegion(base->GetRegion()); + int patrol_type = Mission::PATROL; + Point base_loc; + + if (!base || !region) + continue; + + if (region->Type() == Orbital::TERRAIN) { + patrol_type = Mission::AIR_PATROL; + + if (RandomChance(2,3)) + continue; + } + + base_loc = base->Location() + RandomPoint() * 1.5; + + if (region->Type() == Orbital::TERRAIN) + base_loc += Point(0, 0, 14.0e3); + + MissionElement* elem = CreateFighterPackage(squadron, 2, patrol_type); + if (elem) { + elem->SetIntelLevel(Intel::KNOWN); + elem->SetRegion(base->GetRegion()); + elem->SetLocation(base_loc); + patrols.append(elem); + } + } + } + + iter.attach(patrols); + while (++iter) + mission->AddElement(iter.value()); +} + +// +--------------------------------------------------------------------+ + +void +CampaignMissionFighter::CreateWards() +{ + switch (mission->Type()) { + case Mission::ESCORT_FREIGHT: CreateWardFreight(); break; + case Mission::ESCORT_SHUTTLE: CreateWardShuttle(); break; + case Mission::ESCORT_STRIKE: CreateWardStrike(); break; + default: break; + } +} + +void +CampaignMissionFighter::CreateWardFreight() +{ + if (!mission || !mission->GetStarSystem()) return; + + CombatUnit* carrier = FindCarrier(squadron); + CombatGroup* freight = 0; + + if (request) + freight = request->GetObjective(); + + if (!freight) + freight = campaign->FindGroup(ownside, CombatGroup::FREIGHT); + + if (!freight || freight->CalcValue() < 1) return; + + CombatUnit* unit = freight->GetNextUnit(); + if (!unit) return; + + MissionElement* elem = CreateSingleElement(freight, unit); + if (!elem) return; + + elem->SetMissionRole(Mission::CARGO); + elem->SetIntelLevel(Intel::KNOWN); + elem->SetRegion(squadron->GetRegion()); + + if (carrier) + elem->SetLocation(carrier->Location() + RandomPoint() * 2); + + ward = elem; + mission->AddElement(elem); + + + StarSystem* system = mission->GetStarSystem(); + OrbitalRegion* rgn1 = system->FindRegion(elem->Region()); + Point delta = rgn1->Location() - rgn1->Primary()->Location(); + Point npt_loc = elem->Location(); + Instruction* n = 0; + + delta.Normalize(); + delta *= 200.0e3; + + npt_loc += delta; + + n = new(__FILE__,__LINE__) Instruction(elem->Region(), + npt_loc, + Instruction::VECTOR); + + if (n) { + n->SetSpeed(500); + elem->AddNavPoint(n); + } + + Text rgn2 = elem->Region(); + List& zones = campaign->GetZones(); + if (zones[zones.size()-1]->HasRegion(rgn2)) + rgn2 = *zones[0]->GetRegions()[0]; + else + rgn2 = *zones[zones.size()-1]->GetRegions()[0]; + + n = new(__FILE__,__LINE__) Instruction(rgn2, + Point(0, 0, 0), + Instruction::VECTOR); + + if (n) { + n->SetSpeed(750); + elem->AddNavPoint(n); + } +} + +void +CampaignMissionFighter::CreateWardShuttle() +{ + if (!mission || !mission->GetStarSystem()) return; + + CombatUnit* carrier = FindCarrier(squadron); + CombatGroup* shuttle = campaign->FindGroup(ownside, CombatGroup::LCA_SQUADRON); + + if (!shuttle || shuttle->CalcValue() < 1) return; + + List& units = shuttle->GetUnits(); + + MissionElement* elem = CreateFighterPackage(shuttle, 1, Mission::CARGO); + if (!elem) return; + + elem->SetIntelLevel(Intel::KNOWN); + elem->SetRegion(orb_region); + elem->Loadouts().destroy(); + + if (carrier) + elem->SetLocation(carrier->Location() + RandomPoint() * 2); + + ward = elem; + mission->AddElement(elem); + + // if there is terrain nearby, then have the shuttle fly down to it: + if (air_region.length() > 0) { + StarSystem* system = mission->GetStarSystem(); + OrbitalRegion* rgn1 = system->FindRegion(elem->Region()); + Point delta = rgn1->Location() - rgn1->Primary()->Location(); + Point npt_loc = elem->Location(); + Instruction* n = 0; + + delta.Normalize(); + delta *= -200.0e3; + + npt_loc += delta; + + n = new(__FILE__,__LINE__) Instruction(elem->Region(), + npt_loc, + Instruction::VECTOR); + + if (n) { + n->SetSpeed(500); + elem->AddNavPoint(n); + } + + n = new(__FILE__,__LINE__) Instruction(air_region, + Point(0, 0, 10.0e3), + Instruction::VECTOR); + + if (n) { + n->SetSpeed(500); + elem->AddNavPoint(n); + } + } + + // otherwise, escort the shuttle in for a landing on the carrier: + else if (carrier) { + Point src = carrier->Location() + RandomDirection() * 150e3; + Point dst = carrier->Location() + RandomDirection() * 25e3; + Instruction* n = 0; + + elem->SetLocation(src); + + n = new(__FILE__,__LINE__) Instruction(elem->Region(), dst, Instruction::DOCK); + if (n) { + n->SetTarget(carrier->Name()); + n->SetSpeed(500); + elem->AddNavPoint(n); + } + } +} + +void +CampaignMissionFighter::CreateWardStrike() +{ + if (!mission || !mission->GetStarSystem()) return; + + CombatUnit* carrier = FindCarrier(squadron); + CombatGroup* strike = strike_group; + + if (!strike || strike->CalcValue() < 1) return; + + List& units = strike->GetUnits(); + + int type = Mission::ASSAULT; + + if (airborne) + type = Mission::STRIKE; + + MissionElement* elem = CreateFighterPackage(strike, 2, type); + if (!elem) return; + + if (strike->GetParent() == squadron->GetParent()) { + Player* p = Player::GetCurrentPlayer(); + elem->SetAlert(p ? !p->FlyingStart() : true); + } + + elem->SetIntelLevel(Intel::KNOWN); + elem->SetRegion(squadron->GetRegion()); + + if (strike_target) { + Instruction* obj = new(__FILE__,__LINE__) Instruction(Instruction::ASSAULT, strike_target->Name()); + + if (obj) { + if (airborne) + obj->SetAction(Instruction::STRIKE); + + elem->AddObjective(obj); + } + } + + ward = elem; + mission->AddElement(elem); + + + StarSystem* system = mission->GetStarSystem(); + OrbitalRegion* rgn1 = system->FindRegion(elem->Region()); + Point delta = rgn1->Location() - rgn1->Primary()->Location(); + Point npt_loc = elem->Location(); + Instruction* n = 0; + + if (airborne) { + delta.Normalize(); + delta *= -30.0e3; + npt_loc += delta; + + n = new(__FILE__,__LINE__) Instruction(elem->Region(), + npt_loc, + Instruction::VECTOR); + if (n) { + n->SetSpeed(500); + elem->AddNavPoint(n); + } + + npt_loc = Point(0, 0, 10.0e3); + + n = new(__FILE__,__LINE__) Instruction(air_region, + npt_loc, + Instruction::VECTOR); + if (n) { + n->SetSpeed(500); + elem->AddNavPoint(n); + } + } + + // IP: + if (strike_target) { + delta = strike_target->Location() - npt_loc; + delta.Normalize(); + delta *= 15.0e3; + + npt_loc = strike_target->Location() + delta + Point(0, 0, 8.0e3); + + n = new(__FILE__,__LINE__) Instruction(strike_target->GetRegion(), + npt_loc, + Instruction::STRIKE); + if (n) { + n->SetSpeed(500); + elem->AddNavPoint(n); + } + } + + if (airborne) { + n = new(__FILE__,__LINE__) Instruction(air_region, + Point(0, 0, 30.0e3), + Instruction::VECTOR); + if (n) { + n->SetSpeed(500); + elem->AddNavPoint(n); + } + } + + if (carrier) { + n = new(__FILE__,__LINE__) Instruction(elem->Region(), + carrier->Location() - Point(0, -20.0e3, 0), + Instruction::VECTOR); + if (n) { + n->SetSpeed(500); + elem->AddNavPoint(n); + } + } + + // find the strike target element: + if (strike_target) { + prime_target = mission->FindElement(strike_target->Name()); + } +} + +// +--------------------------------------------------------------------+ + +void +CampaignMissionFighter::CreateEscorts() +{ + bool escort_needed = false; + + if (mission->Type() == Mission::STRIKE || mission->Type() == Mission::ASSAULT) { + if (request && request->GetObjective()) { + int tgt_type = request->GetObjective()->Type(); + + if (tgt_type == CombatGroup::CARRIER_GROUP || + tgt_type == CombatGroup::STATION || + tgt_type == CombatGroup::STARBASE) + + escort_needed = true; + } + } + + if (player_elem && escort_needed) { + CombatGroup* s = FindSquadron(ownside, CombatGroup::INTERCEPT_SQUADRON); + + if (s && s->IsAssignable()) { + MissionElement* elem = CreateFighterPackage(s, 2, Mission::ESCORT_STRIKE); + + if (elem) { + Point offset = Point(2.0e3, 2.0e3, 1.0e3); + + ListIter npt_iter = player_elem->NavList(); + while (++npt_iter) { + Instruction* npt = npt_iter.value(); + Instruction* n = new(__FILE__,__LINE__) Instruction(npt->RegionName(), + npt->Location() + offset, + Instruction::ESCORT); + if (n) { + n->SetSpeed(npt->Speed()); + elem->AddNavPoint(n); + } + } + + mission->AddElement(elem); + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +CampaignMissionFighter::CreateTargets() +{ + switch (mission->Type()) { + default: + case Mission::DEFEND: + case Mission::PATROL: CreateTargetsPatrol(); break; + case Mission::SWEEP: CreateTargetsSweep(); break; + case Mission::INTERCEPT: CreateTargetsIntercept(); break; + case Mission::ESCORT_FREIGHT: CreateTargetsFreightEscort(); break; + + case Mission::ESCORT: + case Mission::ESCORT_SHUTTLE: CreateTargetsShuttleEscort(); break; + case Mission::ESCORT_STRIKE: CreateTargetsStrikeEscort(); break; + case Mission::STRIKE: CreateTargetsStrike(); break; + case Mission::ASSAULT: CreateTargetsAssault(); break; + } +} + +void +CampaignMissionFighter::CreateTargetsPatrol() +{ + if (!squadron || !player_elem) return; + + Text region = squadron->GetRegion(); + Point base_loc = player_elem->Location(); + Point patrol_loc; + + if (airborne) + base_loc = RandomPoint() * 2 + Point(0, 0, 12.0e3); + + else if (carrier_elem) + base_loc = carrier_elem->Location(); + + if (airborne) { + if (!airbase) + PlanetaryInsertion(player_elem); + region = air_region; + patrol_loc = base_loc + + RandomDirection() * Random( 60e3, 100e3); + } + else { + patrol_loc = base_loc + + RandomDirection() * Random(110e3, 160e3); + } + + Instruction* n = new(__FILE__,__LINE__) Instruction(region, + patrol_loc, + Instruction::PATROL); + if (n) + player_elem->AddNavPoint(n); + + int ntargets = (int) Random(2.0,5.1); + + while (ntargets > 0) { + int t = CreateRandomTarget(region, patrol_loc); + ntargets -= t; + if (t < 1) break; + } + + if (airborne && !airbase) { + OrbitalInsertion(player_elem); + } + + Instruction* obj = new(__FILE__,__LINE__) Instruction(*n); + obj->SetTargetDesc("inbound enemy units"); + player_elem->AddObjective(obj); + + if (carrier_elem && !airborne) { + obj = new(__FILE__,__LINE__) Instruction(Instruction::DEFEND, carrier_elem->Name()); + if (obj) { + obj->SetTargetDesc(Text("the ") + carrier_elem->Name() + " battle group"); + player_elem->AddObjective(obj); + } + } +} + +void +CampaignMissionFighter::CreateTargetsSweep() +{ + if (!squadron || !player_elem) return; + + double traverse = PI; + double a = Random(-PI/2, PI/2); + Point base_loc = player_elem->Location(); + Point sweep_loc = base_loc; + Text region = player_elem->Region(); + Instruction* n = 0; + + if (carrier_elem) + base_loc = carrier_elem->Location(); + + if (airborne) { + PlanetaryInsertion(player_elem); + region = air_region; + sweep_loc = RandomPoint() + Point(0, 0, 10.0e3); // keep it airborne! + } + + sweep_loc += Point(sin(a), -cos(a), 0) * 100.0e3; + + n = new(__FILE__,__LINE__) Instruction(region, + sweep_loc, + Instruction::VECTOR); + if (n) { + n->SetSpeed(750); + player_elem->AddNavPoint(n); + } + + int index = 0; + int ntargets = 6; + + while (traverse > 0) { + double a1 = Random(PI/4, PI/2); + traverse -= a1; + a += a1; + + sweep_loc += Point(sin(a), -cos(a), 0) * 80.0e3; + + n = new(__FILE__,__LINE__) Instruction(region, + sweep_loc, + Instruction::SWEEP); + if (n) { + n->SetSpeed(750); + n->SetFormation(Instruction::SPREAD); + player_elem->AddNavPoint(n); + } + + if (ntargets && RandomChance()) { + ntargets -= CreateRandomTarget(region, sweep_loc); + } + + index++; + } + + if (ntargets > 0) + CreateRandomTarget(region, sweep_loc); + + if (airborne && !airbase) { + OrbitalInsertion(player_elem); + region = player_elem->Region(); + } + + sweep_loc = base_loc; + sweep_loc.y += 30.0e3; + + n = new(__FILE__,__LINE__) Instruction(region, + sweep_loc, + Instruction::VECTOR); + if (n) { + n->SetSpeed(750); + player_elem->AddNavPoint(n); + } + + Instruction* obj = new(__FILE__,__LINE__) Instruction(region, + sweep_loc, + Instruction::SWEEP); + if (obj) { + obj->SetTargetDesc("enemy patrols"); + player_elem->AddObjective(obj); + } + + if (carrier_elem && !airborne) { + obj = new(__FILE__,__LINE__) Instruction(Instruction::DEFEND, carrier_elem->Name()); + if (obj) { + obj->SetTargetDesc(Text("the ") + carrier_elem->Name() + " battle group"); + player_elem->AddObjective(obj); + } + } +} + +void +CampaignMissionFighter::CreateTargetsIntercept() +{ + if (!squadron || !player_elem) return; + + CombatUnit* carrier = FindCarrier(squadron); + CombatGroup* s = FindSquadron(enemy, CombatGroup::ATTACK_SQUADRON); + CombatGroup* s2 = FindSquadron(enemy, CombatGroup::FIGHTER_SQUADRON); + + if (!s || !s2) return; + + int ninbound = 2 + (int) (RandomIndex() < 5); + bool second = ninbound > 2; + Text attacker; + + while (ninbound--) { + MissionElement* elem = CreateFighterPackage(s, 4, Mission::ASSAULT); + if (elem) { + elem->SetIntelLevel(Intel::KNOWN); + elem->Loadouts().destroy(); + elem->Loadouts().append(new(__FILE__,__LINE__) MissionLoad(-1, "Hvy Ship Strike")); + + if (carrier) { + Instruction* obj = new(__FILE__,__LINE__) Instruction(Instruction::ASSAULT, carrier->Name()); + if (obj) { + elem->AddObjective(obj); + elem->SetLocation(carrier->Location() + RandomPoint() * 6); + } + } + else { + elem->SetLocation(squadron->Location() + RandomPoint() * 5); + } + + mission->AddElement(elem); + + attacker = elem->Name(); + + if (!prime_target) { + prime_target = elem; + Instruction* obj = new(__FILE__,__LINE__) Instruction(Instruction::INTERCEPT, attacker); + if (obj) { + obj->SetTargetDesc(Text("inbound strike package '") + elem->Name() + "'"); + player_elem->AddObjective(obj); + } + } + + MissionElement* e2 = CreateFighterPackage(s2, 2, Mission::ESCORT); + if (e2) { + e2->SetIntelLevel(Intel::KNOWN); + e2->SetLocation(elem->Location() + RandomPoint() * 0.25); + + Instruction* obj = new(__FILE__,__LINE__) Instruction(Instruction::ESCORT, elem->Name()); + if (obj) + e2->AddObjective(obj); + mission->AddElement(e2); + } + } + } + + if (second) { + // second friendly fighter package + CombatGroup* s = FindSquadron(ownside, CombatGroup::FIGHTER_SQUADRON); + + if (s) { + MissionElement* elem = CreateFighterPackage(s, 2, Mission::INTERCEPT); + if (elem) { + Player* p = Player::GetCurrentPlayer(); + elem->SetAlert(p ? !p->FlyingStart() : true); + + Instruction* obj = new(__FILE__,__LINE__) Instruction(Instruction::INTERCEPT, attacker); + if (obj) + elem->AddObjective(obj); + mission->AddElement(elem); + } + } + } + + if (carrier && !airborne) { + Instruction* obj = new(__FILE__,__LINE__) Instruction(Instruction::DEFEND, carrier->Name()); + if (obj) { + obj->SetTargetDesc(Text("the ") + carrier->Name() + " battle group"); + player_elem->AddObjective(obj); + } + } +} + +void +CampaignMissionFighter::CreateTargetsFreightEscort() +{ + if (!squadron || !player_elem) return; + + if (!ward) { + CreateTargetsPatrol(); + return; + } + + CombatUnit* carrier = FindCarrier(squadron); + CombatGroup* s = FindSquadron(enemy, CombatGroup::ATTACK_SQUADRON); + CombatGroup* s2 = FindSquadron(enemy, CombatGroup::FIGHTER_SQUADRON); + + if (!s) s = s2; + + if (!s || !s2) return; + + MissionElement* elem = CreateFighterPackage(s, 2, Mission::ASSAULT); + if (elem) { + elem->SetIntelLevel(Intel::KNOWN); + + elem->SetLocation(ward->Location() + RandomPoint() * 5); + + Instruction* obj = new(__FILE__,__LINE__) Instruction(Instruction::ASSAULT, ward->Name()); + if (obj) + elem->AddObjective(obj); + mission->AddElement(elem); + + MissionElement* e2 = CreateFighterPackage(s2, 2, Mission::ESCORT); + if (e2) { + e2->SetIntelLevel(Intel::KNOWN); + e2->SetLocation(elem->Location() + RandomPoint() * 0.25); + + Instruction* obj2 = new(__FILE__,__LINE__) Instruction(Instruction::ESCORT, elem->Name()); + if (obj2) + e2->AddObjective(obj2); + mission->AddElement(e2); + } + } + + Instruction* obj3 = new(__FILE__,__LINE__) Instruction(mission->GetRegion(), + Point(0,0,0), + Instruction::PATROL); + + if (obj3) { + obj3->SetTargetDesc("enemy patrols"); + player_elem->AddObjective(obj3); + } +} + +void +CampaignMissionFighter::CreateTargetsShuttleEscort() +{ + CreateTargetsFreightEscort(); +} + +// +--------------------------------------------------------------------+ + +void +CampaignMissionFighter::CreateTargetsStrikeEscort() +{ + if (!squadron || !player_elem) return; + + if (ward) { + Point offset = Point(2.0e3, 2.0e3, 1.0e3); + + ListIter npt_iter = ward->NavList(); + while (++npt_iter) { + Instruction* npt = npt_iter.value(); + Instruction* n = new(__FILE__,__LINE__) Instruction(npt->RegionName(), + npt->Location() + offset, + Instruction::ESCORT); + if (n) { + n->SetSpeed(npt->Speed()); + player_elem->AddNavPoint(n); + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +CampaignMissionFighter::CreateTargetsStrike() +{ + if (!squadron || !player_elem) return; + + if (request && request->GetObjective()) + strike_target = request->GetObjective(); + + if (strike_target && strike_group) { + CreateElements(strike_target); + + ListIter e_iter = mission->GetElements(); + while (++e_iter) { + MissionElement* elem = e_iter.value(); + + if (elem->GetCombatGroup() == strike_target) { + prime_target = elem; + Instruction* obj = new(__FILE__,__LINE__) Instruction(Instruction::STRIKE, elem->Name()); + if (obj) { + obj->SetTargetDesc(Text("preplanned target '") + elem->Name() + "'"); + player_elem->AddObjective(obj); + } + + // create flight plan: + RLoc rloc; + Point loc = Point(0, 0, 15e3); + Instruction* n = 0; + + PlanetaryInsertion(player_elem); + + // target approach and strike: + Point delta = prime_target->Location() - loc; + + if (delta.length() >= 100e3) { + Point mid = loc + delta * 0.5; + mid.z = 10.0e3; + + rloc.SetReferenceLoc(0); + rloc.SetBaseLocation(mid); + rloc.SetDistance(20e3); + rloc.SetDistanceVar(5e3); + rloc.SetAzimuth(90*DEGREES); + rloc.SetAzimuthVar(25*DEGREES); + + n = new(__FILE__,__LINE__) Instruction(prime_target->Region(), + Point(), + Instruction::VECTOR); + if (n) { + n->SetSpeed(750); + n->GetRLoc() = rloc; + player_elem->AddNavPoint(n); + } + + loc = mid; + } + + delta = loc - prime_target->Location(); + delta.Normalize(); + delta *= 25.0e3; + + loc = prime_target->Location() + delta; + loc.z = 8.0e3; + + n = new(__FILE__,__LINE__) Instruction(prime_target->Region(), + loc, + Instruction::STRIKE); + if (n) { + n->SetSpeed(500); + player_elem->AddNavPoint(n); + } + + // exeunt: + rloc.SetReferenceLoc(0); + rloc.SetBaseLocation(Point(0, 0, 30.0e3)); + rloc.SetDistance(50e3); + rloc.SetDistanceVar(5e3); + rloc.SetAzimuth(-90*DEGREES); + rloc.SetAzimuthVar(25*DEGREES); + + n = new(__FILE__,__LINE__) Instruction(prime_target->Region(), + Point(), + Instruction::VECTOR); + if (n) { + n->SetSpeed(750); + n->GetRLoc() = rloc; + player_elem->AddNavPoint(n); + } + + if (carrier_elem) { + rloc.SetReferenceLoc(0); + rloc.SetBaseLocation(carrier_elem->Location()); + rloc.SetDistance(60e3); + rloc.SetDistanceVar(10e3); + rloc.SetAzimuth(180*DEGREES); + rloc.SetAzimuthVar(30*DEGREES); + + n = new(__FILE__,__LINE__) Instruction(carrier_elem->Region(), + Point(), + Instruction::RTB); + if (n) { + n->SetSpeed(750); + n->GetRLoc() = rloc; + player_elem->AddNavPoint(n); + } + } + + break; + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +CampaignMissionFighter::CreateTargetsAssault() +{ + if (!squadron || !player_elem) return; + + CombatGroup* assigned = 0; + + if (request) + assigned = request->GetObjective(); + + if (assigned) { + if (assigned->Type() > CombatGroup::WING && assigned->Type() < CombatGroup::FLEET) { + mission->AddElement(CreateFighterPackage(assigned, 2, Mission::CARGO)); + } + else { + CreateElements(assigned); + } + + // select the prime target element - choose the lowest ranking + // unit of a DESRON, CBG, or CVBG: + + ListIter e_iter = mission->GetElements(); + while (++e_iter) { + MissionElement* elem = e_iter.value(); + + if (elem->GetCombatGroup() == assigned) { + if (!prime_target || assigned->Type() <= CombatGroup::CARRIER_GROUP) { + prime_target = elem; + } + } + } + + if (prime_target) { + MissionElement* elem = prime_target; + + Instruction* obj = new(__FILE__,__LINE__) Instruction(Instruction::ASSAULT, elem->Name()); + if (obj) { + obj->SetTargetDesc(Text("preplanned target '") + elem->Name() + "'"); + player_elem->AddObjective(obj); + } + + // create flight plan: + RLoc rloc; + Vec3 dummy(0,0,0); + Instruction* instr = 0; + Point loc = player_elem->Location(); + Point tgt = elem->Location(); + Point mid; + + CombatGroup* tgt_group = elem->GetCombatGroup(); + if (tgt_group && tgt_group->GetFirstUnit() && tgt_group->IsMovable()) { + tgt = tgt_group->GetFirstUnit()->Location(); + } + + if (carrier_elem) + loc = carrier_elem->Location(); + + mid = loc + (elem->Location() - loc) * 0.5; + + rloc.SetReferenceLoc(0); + rloc.SetBaseLocation(mid); + rloc.SetDistance(40e3); + rloc.SetDistanceVar(5e3); + rloc.SetAzimuth(90*DEGREES); + rloc.SetAzimuthVar(45*DEGREES); + + instr = new(__FILE__,__LINE__) Instruction(elem->Region(), dummy, Instruction::VECTOR); + if (instr) { + instr->SetSpeed(750); + instr->GetRLoc() = rloc; + + player_elem->AddNavPoint(instr); + + if (RandomChance()) { + CreateRandomTarget(elem->Region(), rloc.Location()); + } + } + + rloc.SetReferenceLoc(0); + rloc.SetBaseLocation(tgt); + rloc.SetDistance(60e3); + rloc.SetDistanceVar(5e3); + rloc.SetAzimuth(120*DEGREES); + rloc.SetAzimuthVar(15*DEGREES); + + instr = new(__FILE__,__LINE__) Instruction(elem->Region(), dummy, Instruction::ASSAULT); + if (instr) { + instr->SetSpeed(750); + instr->GetRLoc() = rloc; + instr->SetTarget(elem->Name()); + + player_elem->AddNavPoint(instr); + } + + if (carrier_elem) { + rloc.SetReferenceLoc(0); + rloc.SetBaseLocation(loc); + rloc.SetDistance(30e3); + rloc.SetDistanceVar(0); + rloc.SetAzimuth(180*DEGREES); + rloc.SetAzimuthVar(60*DEGREES); + + instr = new(__FILE__,__LINE__) Instruction(carrier_elem->Region(), dummy, Instruction::RTB); + if (instr) { + instr->SetSpeed(500); + instr->GetRLoc() = rloc; + + player_elem->AddNavPoint(instr); + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +int +CampaignMissionFighter::CreateRandomTarget(const char* rgn, Point base_loc) +{ + if (!mission) return 0; + + int ntargets = 0; + int ttype = RandomIndex(); + bool oca = (mission->Type() == Mission::SWEEP); + + if (ttype < 8) { + CombatGroup* s = 0; + + if (ttype < 4) + s = FindSquadron(enemy, CombatGroup::INTERCEPT_SQUADRON); + else + s = FindSquadron(enemy, CombatGroup::FIGHTER_SQUADRON); + + if (s) { + MissionElement* elem = CreateFighterPackage(s, 2, Mission::SWEEP); + if (elem) { + elem->SetIntelLevel(Intel::KNOWN); + elem->SetRegion(rgn); + elem->SetLocation(base_loc + RandomPoint() * 1.5); + mission->AddElement(elem); + ntargets++; + } + } + } + else if (ttype < 12) { + if (oca) { + CombatGroup* s = FindSquadron(enemy, CombatGroup::LCA_SQUADRON); + + if (s) { + MissionElement* elem = CreateFighterPackage(s, 1, Mission::CARGO); + if (elem) { + elem->SetIntelLevel(Intel::KNOWN); + elem->SetRegion(rgn); + elem->SetLocation(base_loc + RandomPoint() * 2); + mission->AddElement(elem); + ntargets++; + + CombatGroup* s2 = FindSquadron(enemy, CombatGroup::FIGHTER_SQUADRON); + + if (s2) { + MissionElement* e2 = CreateFighterPackage(s2, 2, Mission::ESCORT); + if (e2) { + e2->SetIntelLevel(Intel::KNOWN); + e2->SetRegion(rgn); + e2->SetLocation(elem->Location() + RandomPoint() * 0.5); + + Instruction* obj = new(__FILE__,__LINE__) Instruction(Instruction::ESCORT, elem->Name()); + if (obj) + e2->AddObjective(obj); + mission->AddElement(e2); + ntargets++; + } + } + } + } + } + else { + CombatGroup* s = FindSquadron(enemy, CombatGroup::ATTACK_SQUADRON); + + if (s) { + MissionElement* elem = CreateFighterPackage(s, 2, Mission::ASSAULT); + if (elem) { + elem->SetIntelLevel(Intel::KNOWN); + elem->SetRegion(rgn); + elem->SetLocation(base_loc + RandomPoint() * 1.3); + mission->AddElement(elem); + ntargets++; + } + } + } + } + else if (ttype < 15) { + if (oca) { + CombatGroup* s = 0; + + if (airborne) + s = FindSquadron(enemy, CombatGroup::LCA_SQUADRON); + else + s = FindSquadron(enemy, CombatGroup::FREIGHT); + + if (s) { + MissionElement* elem = CreateFighterPackage(s, 1, Mission::CARGO); + if (elem) { + elem->SetIntelLevel(Intel::KNOWN); + elem->SetRegion(rgn); + elem->SetLocation(base_loc + RandomPoint() * 2); + mission->AddElement(elem); + ntargets++; + + CombatGroup* s2 = FindSquadron(enemy, CombatGroup::INTERCEPT_SQUADRON); + + if (s2) { + MissionElement* e2 = CreateFighterPackage(s2, 2, Mission::ESCORT); + if (e2) { + e2->SetIntelLevel(Intel::KNOWN); + e2->SetRegion(rgn); + e2->SetLocation(elem->Location() + RandomPoint() * 0.5); + + Instruction* obj = new(__FILE__,__LINE__) Instruction(Instruction::ESCORT, elem->Name()); + if (obj) + e2->AddObjective(obj); + mission->AddElement(e2); + ntargets++; + } + } + } + } + } + else { + CombatGroup* s = FindSquadron(enemy, CombatGroup::ATTACK_SQUADRON); + + if (s) { + MissionElement* elem = CreateFighterPackage(s, 2, Mission::ASSAULT); + if (elem) { + elem->SetIntelLevel(Intel::KNOWN); + elem->SetRegion(rgn); + elem->SetLocation(base_loc + RandomPoint() * 1.1); + mission->AddElement(elem); + ntargets++; + + CombatGroup* s2 = FindSquadron(enemy, CombatGroup::FIGHTER_SQUADRON); + + if (s2) { + MissionElement* e2 = CreateFighterPackage(s2, 2, Mission::ESCORT); + if (e2) { + e2->SetIntelLevel(Intel::KNOWN); + e2->SetRegion(rgn); + e2->SetLocation(elem->Location() + RandomPoint() * 0.5); + + Instruction* obj = new(__FILE__,__LINE__) Instruction(Instruction::ESCORT, elem->Name()); + if (obj) + e2->AddObjective(obj); + mission->AddElement(e2); + ntargets++; + } + } + } + } + } + } + else { + CombatGroup* s = FindSquadron(enemy, CombatGroup::LCA_SQUADRON); + + if (s) { + MissionElement* elem = CreateFighterPackage(s, 2, Mission::CARGO); + if (elem) { + elem->SetIntelLevel(Intel::KNOWN); + elem->SetRegion(rgn); + elem->SetLocation(base_loc + RandomPoint() * 2); + mission->AddElement(elem); + ntargets++; + } + } + } + + return ntargets; +} + +// +--------------------------------------------------------------------+ + +void +CampaignMissionFighter::PlanetaryInsertion(MissionElement* elem) +{ + if (!mission || !elem) return; + if (!mission->GetStarSystem()) return; + + MissionElement* carrier = mission->FindElement(elem->Commander()); + StarSystem* system = mission->GetStarSystem(); + OrbitalRegion* rgn1 = system->FindRegion(elem->Region()); + OrbitalRegion* rgn2 = system->FindRegion(air_region); + Point npt_loc = elem->Location(); + Instruction* n = 0; + Player* p = Player::GetCurrentPlayer(); + + int flying_start = p ? p->FlyingStart() : 0; + + if (carrier && !flying_start) { + npt_loc = carrier->Location() + Point(1e3, -5e3, 0); + } + + if (rgn1 && rgn2) { + double delta_t = mission->Start() - campaign->GetTime(); + Point r1 = rgn1->PredictLocation(delta_t); + Point r2 = rgn2->PredictLocation(delta_t); + + Point delta = r2 - r1; + + delta.y *= -1; + delta.Normalize(); + delta *= 10e3; + + npt_loc += delta; + + n = new(__FILE__,__LINE__) Instruction(elem->Region(), + npt_loc, + Instruction::VECTOR); + if (n) { + n->SetSpeed(750); + elem->AddNavPoint(n); + } + } + + n = new(__FILE__,__LINE__) Instruction(air_region, + Point(0, 0, 15e3), + Instruction::VECTOR); + if (n) { + n->SetSpeed(750); + elem->AddNavPoint(n); + } +} + +void +CampaignMissionFighter::OrbitalInsertion(MissionElement* elem) +{ + Instruction* n = new(__FILE__,__LINE__) Instruction(air_region, + Point(0, 0, 30.0e3), + Instruction::VECTOR); + if (n) { + n->SetSpeed(750); + elem->AddNavPoint(n); + } +} + +// +--------------------------------------------------------------------+ + +MissionElement* +CampaignMissionFighter::CreateFighterPackage(CombatGroup* squadron, int count, int role) +{ + if (!squadron) return 0; + + CombatUnit* fighter = squadron->GetUnits().at(0); + CombatUnit* carrier = FindCarrier(squadron); + + if (!fighter) + return 0; + + int avail = fighter->LiveCount(); + int actual = count; + + if (avail < actual) + actual = avail; + + if (avail < 1) { + ::Print("CMF - Insufficient fighters in squadron '%s' - %d required, %d available\n", + squadron->Name().data(), count, avail); + return 0; + } + + MissionElement* elem = new(__FILE__,__LINE__) MissionElement; + if (!elem) { + Exit(); + return 0; + } + + elem->SetName(Callsign::GetCallsign(fighter->GetIFF())); + elem->SetElementID(pkg_id++); + + if (carrier) { + elem->SetCommander(carrier->Name()); + elem->SetHeading(carrier->GetHeading()); + } + else { + elem->SetHeading(fighter->GetHeading()); + } + + elem->SetDesign(fighter->GetDesign()); + elem->SetCount(actual); + elem->SetIFF(fighter->GetIFF()); + elem->SetIntelLevel(squadron->IntelLevel()); + elem->SetRegion(fighter->GetRegion()); + elem->SetSquadron(squadron->Name()); + elem->SetMissionRole(role); + + switch (role) { + case Mission::ASSAULT: + if (request->GetObjective() && + request->GetObjective()->Type() == CombatGroup::MINEFIELD) + elem->Loadouts().append(new(__FILE__,__LINE__) MissionLoad(-1, "Rockets")); + else + elem->Loadouts().append(new(__FILE__,__LINE__) MissionLoad(-1, "Ship Strike")); + break; + + case Mission::STRIKE: + elem->Loadouts().append(new(__FILE__,__LINE__) MissionLoad(-1, "Ground Strike")); + break; + + default: + elem->Loadouts().append(new(__FILE__,__LINE__) MissionLoad(-1, "ACM Medium Range")); + break; + } + + if (carrier) { + Point offset = RandomPoint() * 0.3; + offset.y = fabs(offset.y); + offset.z += 2e3; + elem->SetLocation(carrier->Location() + offset); + } + else { + elem->SetLocation(fighter->Location() + RandomPoint()); + } + + elem->SetCombatGroup(squadron); + elem->SetCombatUnit(fighter); + + return elem; +} + +// +--------------------------------------------------------------------+ + +static CombatGroup* FindCombatGroup(CombatGroup* g, int type) +{ + if (g->IntelLevel() <= Intel::RESERVE) + return 0; + + if (g->GetUnits().size() > 0) { + for (int i = 0; i < g->GetUnits().size(); i++) { + CombatUnit* u = g->GetUnits().at(i); + if (g->Type() == type && u->LiveCount() > 0) + return g; + } + } + + CombatGroup* result = 0; + + ListIter subgroup = g->GetComponents(); + while (++subgroup && !result) + result = FindCombatGroup(subgroup.value(), type); + + return result; +} + +// +--------------------------------------------------------------------+ + +CombatGroup* +CampaignMissionFighter::FindSquadron(int iff, int type) +{ + if (!squadron) return 0; + + CombatGroup* result = 0; + Campaign* campaign = Campaign::GetCampaign(); + + if (campaign) { + ListIter combatant = campaign->GetCombatants(); + while (++combatant && !result) { + if (combatant->GetIFF() == iff) { + result = ::FindCombatGroup(combatant->GetForce(), type); + + if (result && result->CountUnits() < 1) { + result = 0; + } + } + } + } + + return result; +} + +// +--------------------------------------------------------------------+ + +void +CampaignMissionFighter::DefineMissionObjectives() +{ + if (!mission || !player_elem) return; + + if (prime_target) mission->SetTarget(prime_target); + if (ward) mission->SetWard(ward); + + Text objectives; + + for (int i = 0; i < player_elem->Objectives().size(); i++) { + Instruction* obj = player_elem->Objectives().at(i); + objectives += "* "; + objectives += obj->GetDescription(); + objectives += ".\n"; + } + + mission->SetObjective(objectives); +} + +// +--------------------------------------------------------------------+ + +MissionInfo* +CampaignMissionFighter::DescribeMission() +{ + if (!mission || !player_elem) return 0; + + char name[256]; + char player_info[256]; + + if (mission_info && mission_info->name.length()) + sprintf(name, "MSN-%03d %s", mission->Identity(), mission_info->name.data()); + + else if (ward) + sprintf(name, "MSN-%03d %s %s", mission->Identity(), Game::GetText(mission->TypeName()).data(), ward->Name().data()); + + else if (prime_target) + sprintf(name, "MSN-%03d %s %s %s", mission->Identity(), Game::GetText(mission->TypeName()).data(), + Ship::ClassName(prime_target->GetDesign()->type), + prime_target->Name().data()); + + else + sprintf(name, "MSN-%03d %s", mission->Identity(), Game::GetText(mission->TypeName()).data()); + + if (player_elem) { + sprintf(player_info, "%d x %s %s '%s'", + player_elem->Count(), + (const char*) player_elem->GetDesign()->abrv, + (const char*) player_elem->GetDesign()->name, + (const char*) player_elem->Name()); + } + + MissionInfo* info = new(__FILE__,__LINE__) MissionInfo; + + info->id = mission->Identity(); + info->mission = mission; + info->name = name; + info->type = mission->Type(); + info->player_info = player_info; + info->description = mission->Objective(); + info->start = mission->Start(); + + if (mission->GetStarSystem()) + info->system = mission->GetStarSystem()->Name(); + info->region = mission->GetRegion(); + + mission->SetName(name); + + return info; +} + +// +--------------------------------------------------------------------+ + +void +CampaignMissionFighter::Exit() +{ + Starshatter* stars = Starshatter::GetInstance(); + if (stars) + stars->SetGameMode(Starshatter::MENU_MODE); +} diff --git a/Stars45/CampaignMissionFighter.h b/Stars45/CampaignMissionFighter.h new file mode 100644 index 0000000..3c2bb6f --- /dev/null +++ b/Stars45/CampaignMissionFighter.h @@ -0,0 +1,121 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CampaignMissionFighter.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + CampaignMissionFighter generates missions and mission + info for the player's FIGHTER SQUADRON as part of a + dynamic campaign. +*/ + +#ifndef CampaignMissionFighter_h +#define CampaignMissionFighter_h + +#include "Types.h" +#include "Geometry.h" +#include "List.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class Campaign; +class CampaignMissionRequest; +class CombatGroup; +class CombatUnit; +class CombatZone; +class Mission; +class MissionElement; +class MissionInfo; +class MissionTemplate; + +// +--------------------------------------------------------------------+ + +class CampaignMissionFighter +{ +public: + static const char* TYPENAME() { return "CampaignMissionFighter"; } + + CampaignMissionFighter(Campaign* c); + virtual ~CampaignMissionFighter(); + + virtual void CreateMission(CampaignMissionRequest* request); + +protected: + virtual Mission* GenerateMission(int id); + virtual void SelectType(); + virtual void SelectRegion(); + virtual void GenerateStandardElements(); + virtual void GenerateMissionElements(); + virtual void CreateElements(CombatGroup* g); + virtual void CreateSquadron(CombatGroup* g); + virtual void CreatePlayer(CombatGroup* g); + + virtual void CreatePatrols(); + virtual void CreateWards(); + virtual void CreateWardFreight(); + virtual void CreateWardShuttle(); + virtual void CreateWardStrike(); + + virtual void CreateEscorts(); + + virtual void CreateTargets(); + virtual void CreateTargetsPatrol(); + virtual void CreateTargetsSweep(); + virtual void CreateTargetsIntercept(); + virtual void CreateTargetsFreightEscort(); + virtual void CreateTargetsShuttleEscort(); + virtual void CreateTargetsStrikeEscort(); + virtual void CreateTargetsStrike(); + virtual void CreateTargetsAssault(); + virtual int CreateRandomTarget(const char* rgn, Point base_loc); + + virtual bool IsGroundObjective(CombatGroup* obj); + + virtual void PlanetaryInsertion(MissionElement* elem); + virtual void OrbitalInsertion(MissionElement* elem); + + virtual MissionElement* + CreateSingleElement(CombatGroup* g, + CombatUnit* u); + virtual MissionElement* + CreateFighterPackage(CombatGroup* squadron, + int count, + int role); + + virtual CombatGroup* FindSquadron(int iff, int type); + virtual CombatUnit* FindCarrier(CombatGroup* g); + + virtual void DefineMissionObjectives(); + virtual MissionInfo* DescribeMission(); + virtual void Exit(); + + Campaign* campaign; + CampaignMissionRequest* request; + MissionInfo* mission_info; + + CombatGroup* squadron; + CombatGroup* strike_group; + CombatGroup* strike_target; + Mission* mission; + MissionElement* player_elem; + MissionElement* carrier_elem; + MissionElement* ward; + MissionElement* prime_target; + MissionElement* escort; + Text air_region; + Text orb_region; + bool airborne; + bool airbase; + int ownside; + int enemy; + int mission_type; +}; + +#endif CampaignMissionFighter_h + diff --git a/Stars45/CampaignMissionRequest.cpp b/Stars45/CampaignMissionRequest.cpp new file mode 100644 index 0000000..a3df9a0 --- /dev/null +++ b/Stars45/CampaignMissionRequest.cpp @@ -0,0 +1,39 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CampaignMissionRequest.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + CampaignMissionRequest +*/ + +#include "MemDebug.h" +#include "CampaignMissionRequest.h" +#include "Campaign.h" +#include "Combatant.h" +#include "CombatAssignment.h" +#include "CombatGroup.h" +#include "CombatUnit.h" +#include "CombatZone.h" +#include "Mission.h" +#include "Instruction.h" +#include "ShipDesign.h" +#include "StarSystem.h" +#include "Random.h" + +// +--------------------------------------------------------------------+ + +CampaignMissionRequest::CampaignMissionRequest(Campaign* c, + int t, + int s, + CombatGroup* p, + CombatGroup* tgt) + : campaign(c), type(t), opp_type(-1), start(s), + primary_group(p), secondary_group(0), + objective(tgt), use_loc(false) +{ } diff --git a/Stars45/CampaignMissionRequest.h b/Stars45/CampaignMissionRequest.h new file mode 100644 index 0000000..5b70f70 --- /dev/null +++ b/Stars45/CampaignMissionRequest.h @@ -0,0 +1,84 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CampaignMissionRequest.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + CampaignMissionRequest +*/ + +#ifndef CampaignMissionRequest_h +#define CampaignMissionRequest_h + +#include "Types.h" +#include "Geometry.h" +#include "List.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class Campaign; +class CombatGroup; +class CombatUnit; +class CombatZone; +class Mission; +class MissionElement; +class MissionInfo; + +// +--------------------------------------------------------------------+ + +class CampaignMissionRequest +{ +public: + static const char* TYPENAME() { return "CampaignMissionRequest"; } + + CampaignMissionRequest(Campaign* c, int type, int start, + CombatGroup* primary, CombatGroup* tgt=0); + + Campaign* GetCampaign() { return campaign; } + int Type() { return type; } + int OpposingType() { return opp_type; } + int StartTime() { return start; } + CombatGroup* GetPrimaryGroup() { return primary_group; } + CombatGroup* GetSecondaryGroup() { return secondary_group; } + CombatGroup* GetObjective() { return objective; } + + bool IsLocSpecified() { return use_loc; } + const Text& RegionName() { return region; } + Point Location() { return location; } + const Text& Script() { return script; } + + void SetType(int t) { type = t; } + void SetOpposingType(int t) { opp_type = t; } + void SetStartTime(int s) { start = s; } + void SetPrimaryGroup(CombatGroup* g) { primary_group = g; } + void SetSecondaryGroup(CombatGroup* g) { secondary_group = g; } + void SetObjective(CombatGroup* g) { objective = g; } + + void SetRegionName(const char* rgn) { region = rgn; use_loc = true; } + void SetLocation(const Point& loc) { location = loc; use_loc = true; } + void SetScript(const char* s) { script = s; } + +private: + Campaign* campaign; + + int type; // type of mission + int opp_type; // opposing mission type + int start; // start time + CombatGroup* primary_group; // player's group + CombatGroup* secondary_group; // optional support group + CombatGroup* objective; // target or ward + + bool use_loc; // use the specified location + Text region; + Point location; + Text script; +}; + +#endif CampaignMissionRequest_h + diff --git a/Stars45/CampaignMissionStarship.cpp b/Stars45/CampaignMissionStarship.cpp new file mode 100644 index 0000000..f0c935f --- /dev/null +++ b/Stars45/CampaignMissionStarship.cpp @@ -0,0 +1,1405 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CampaignMissionStarship.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + CampaignMissionStarship generates missions and mission + info for the player's STARSHIP GROUP as part of a + dynamic campaign. +*/ + +#include "MemDebug.h" +#include "CampaignMissionStarship.h" +#include "CampaignMissionRequest.h" +#include "Campaign.h" +#include "Combatant.h" +#include "CombatAssignment.h" +#include "CombatGroup.h" +#include "CombatUnit.h" +#include "CombatZone.h" +#include "Callsign.h" +#include "Mission.h" +#include "MissionTemplate.h" +#include "Instruction.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "Starshatter.h" +#include "StarSystem.h" +#include "Player.h" +#include "Random.h" + +static int pkg_id = 1000; +extern int dump_missions; + +// +--------------------------------------------------------------------+ + +CampaignMissionStarship::CampaignMissionStarship(Campaign* c) + : campaign(c), player_group(0), player_unit(0), mission(0), player(0), + strike_group(0), strike_target(0), prime_target(0), + ward(0), escort(0), ownside(0), enemy(-1), mission_type(0) +{ + if (!campaign || !campaign->GetPlayerGroup()) { + ::Print("ERROR - CMS campaign=0x%08x player_group=0x%08x\n", + campaign, (DWORD) campaign?campaign->GetPlayerGroup():0); + return; + } + + player_group = campaign->GetPlayerGroup(); + player_unit = campaign->GetPlayerUnit(); +} + +CampaignMissionStarship::~CampaignMissionStarship() {} + +// +--------------------------------------------------------------------+ + +void +CampaignMissionStarship::CreateMission(CampaignMissionRequest* req) +{ + if (!campaign || !req) + return; + + ::Print("\n-----------------------------------------------\n"); + if (req->Script().length()) + ::Print("CMS CreateMission() request: %s '%s'\n", + Mission::RoleName(req->Type()), + (const char*) req->Script()); + + else + ::Print("CMS CreateMission() request: %s %s\n", + Mission::RoleName(req->Type()), + req->GetObjective() ? req->GetObjective()->Name() : "(no target)"); + + request = req; + + if (!player_group) + return; + + if (request->GetPrimaryGroup() != player_group) { + player_group = request->GetPrimaryGroup(); + player_unit = 0; + } + + ownside = player_group->GetIFF(); + mission_info = 0; + + for (int i = 0; i < campaign->GetCombatants().size(); i++) { + int iff = campaign->GetCombatants().at(i)->GetIFF(); + if (iff > 0 && iff != ownside) { + enemy = iff; + break; + } + } + + static int id_key = 1; + GenerateMission(id_key++); + DefineMissionObjectives(); + + MissionInfo* info = DescribeMission(); + + if (info) { + campaign->GetMissionList().append(info); + + ::Print("CMS Created %03d '%s' %s\n\n", + info->id, + (const char*) info->name, + Mission::RoleName(mission->Type())); + + if (dump_missions) { + Text script = mission->Serialize(); + char fname[32]; + + sprintf(fname, "msn%03d.def", info->id); + FILE* f = fopen(fname, "w"); + if (f) { + fprintf(f, "%s\n", script.data()); + fclose(f); + } + } + } + + else { + ::Print("CMS failed to create mission.\n"); + } +} + +// +--------------------------------------------------------------------+ + +Mission* +CampaignMissionStarship::GenerateMission(int id) +{ + bool found = false; + + SelectType(); + + if (request && request->Script().length()) { + MissionTemplate* mt = new(__FILE__,__LINE__) MissionTemplate(id, request->Script(), campaign->Path()); + if (mt) + mt->SetPlayerSquadron(player_group); + mission = mt; + found = true; + } + + else { + mission_info = campaign->FindMissionTemplate(mission_type, player_group); + + found = mission_info != 0; + + if (found) { + MissionTemplate* mt = new(__FILE__,__LINE__) MissionTemplate(id, mission_info->script, campaign->Path()); + if (mt) + mt->SetPlayerSquadron(player_group); + mission = mt; + } + else { + mission = new(__FILE__,__LINE__) Mission(id); + if (mission) + mission->SetType(mission_type); + } + } + + if (!mission || !player_group) { + Exit(); + return 0; + } + + char name[64]; + sprintf(name, "Starship Mission %d", id); + + mission->SetName(name); + mission->SetTeam(player_group->GetIFF()); + mission->SetStart(request->StartTime()); + + SelectRegion(); + GenerateStandardElements(); + + if (!found) { + GenerateMissionElements(); + mission->SetOK(true); + mission->Validate(); + } + + else { + CreatePlayer(); + mission->Load(); + + if (mission->IsOK()) { + player = mission->GetPlayer(); + prime_target = mission->GetTarget(); + ward = mission->GetWard(); + } + + // if there was a problem, scrap the mission + // and start over: + else { + delete mission; + + mission = new(__FILE__,__LINE__) Mission(id); + + if (!mission) { + Exit(); + return 0; + } + + mission->SetType(mission_type); + mission->SetName(name); + mission->SetTeam(player_group->GetIFF()); + mission->SetStart(request->StartTime()); + + SelectRegion(); + GenerateStandardElements(); + GenerateMissionElements(); + + mission->SetOK(true); + mission->Validate(); + } + } + + return mission; +} + +void +CampaignMissionStarship::SelectType() +{ + if (request) + mission_type = request->Type(); + + else + mission_type = Mission::PATROL; + + if (player_unit && player_unit->GetShipClass() == Ship::CARRIER) + mission_type = Mission::FLIGHT_OPS; +} + +void +CampaignMissionStarship::SelectRegion() +{ + if (!player_group) { + ::Print("WARNING: CMS - no player group in SelectRegion\n"); + return; + } + + CombatZone* zone = player_group->GetAssignedZone(); + + if (!zone) + zone = player_group->GetCurrentZone(); + + if (zone) { + mission->SetStarSystem(campaign->GetSystem(zone->System())); + + if (zone->HasRegion(player_group->GetRegion())) + mission->SetRegion(player_group->GetRegion()); + + else + mission->SetRegion(*zone->GetRegions().at(0)); + } + + else { + ::Print("WARNING: CMS - No zone for '%s'\n", player_group->Name().data()); + + StarSystem* s = campaign->GetSystemList()[0]; + + mission->SetStarSystem(s); + mission->SetRegion(s->Regions()[0]->Name()); + } +} + +// +--------------------------------------------------------------------+ + +void +CampaignMissionStarship::GenerateStandardElements() +{ + ListIter z = campaign->GetZones(); + while (++z) { + ListIter iter = z->GetForces(); + while (++iter) { + ZoneForce* force = iter.value(); + ListIter group = force->GetGroups(); + + while (++group) { + CombatGroup* g = group.value(); + + switch (g->Type()) { + case CombatGroup::INTERCEPT_SQUADRON: + case CombatGroup::FIGHTER_SQUADRON: + case CombatGroup::ATTACK_SQUADRON: + case CombatGroup::LCA_SQUADRON: + CreateSquadron(g); + break; + + case CombatGroup::DESTROYER_SQUADRON: + case CombatGroup::BATTLE_GROUP: + case CombatGroup::CARRIER_GROUP: + CreateElements(g); + break; + + case CombatGroup::MINEFIELD: + case CombatGroup::BATTERY: + case CombatGroup::MISSILE: + case CombatGroup::STATION: + case CombatGroup::STARBASE: + case CombatGroup::SUPPORT: + case CombatGroup::COURIER: + case CombatGroup::MEDICAL: + case CombatGroup::SUPPLY: + case CombatGroup::REPAIR: + CreateElements(g); + break; + + case CombatGroup::CIVILIAN: + case CombatGroup::WAR_PRODUCTION: + case CombatGroup::FACTORY: + case CombatGroup::REFINERY: + case CombatGroup::RESOURCE: + case CombatGroup::INFRASTRUCTURE: + case CombatGroup::TRANSPORT: + case CombatGroup::NETWORK: + case CombatGroup::HABITAT: + case CombatGroup::STORAGE: + case CombatGroup::NON_COM: + CreateElements(g); + break; + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +CampaignMissionStarship::GenerateMissionElements() +{ + CreatePlayer(); + CreateWards(); + CreateTargets(); + + if (ward && player) { + Instruction* obj = new(__FILE__,__LINE__) Instruction(Instruction::ESCORT, ward->Name()); + + if (obj) { + switch (mission->Type()) { + case Mission::ESCORT_FREIGHT: + obj->SetTargetDesc(Text("the star freighter ") + ward->Name()); + break; + + case Mission::ESCORT_SHUTTLE: + obj->SetTargetDesc(Text("the shuttle ") + ward->Name()); + break; + + case Mission::ESCORT_STRIKE: + obj->SetTargetDesc(Text("the ") + ward->Name() + Text(" strike package")); + break; + + default: + if (ward->GetCombatGroup()) { + obj->SetTargetDesc(Text("the ") + ward->GetCombatGroup()->GetDescription()); + } + else { + obj->SetTargetDesc(Text("the ") + ward->Name()); + } + break; + } + + player->AddObjective(obj); + } + } +} + +// +--------------------------------------------------------------------+ + +void +CampaignMissionStarship::CreatePlayer() +{ + // prepare elements for the player's group + MissionElement* elem = 0; + + if (player_group) { + ListIter iter = mission->GetElements(); + while (++iter) { + MissionElement* e = iter.value(); + if (e->GetCombatGroup() == player_group) { + player_group_elements.append(e); + + // match the player to the requested unit, if possible: + if ((!player_unit && !elem) || (player_unit == e->GetCombatUnit())) { + elem = e; + } + } + } + } + + if (elem) { + elem->SetPlayer(1); + elem->SetCommandAI(0); + player = elem; + } + else if (player_group) { + ::Print("CMS GenerateMissionElements() could not find player element '%s'\n", + player_group->Name().data()); + } + else { + ::Print("CMS GenerateMissionElements() could not find player element (no player group)\n"); + } +} + +// +--------------------------------------------------------------------+ + +void +CampaignMissionStarship::CreateElements(CombatGroup* g) +{ + MissionElement* elem = 0; + List& units = g->GetUnits(); + + CombatUnit* cmdr = 0; + + for (int i = 0; i < units.size(); i++) { + elem = CreateSingleElement(g, units[i]); + + if (elem) { + if (!cmdr) { + cmdr = units[i]; + + if (player_group && player_group->GetIFF() == g->GetIFF()) { + // the grand admiral is all powerful! + Player* player = Player::GetCurrentPlayer(); + if (player && player->Rank() >= 10) { + elem->SetCommander(player_group->Name()); + } + } + } + else { + elem->SetCommander(cmdr->Name()); + + if (g->Type() == CombatGroup::CARRIER_GROUP && + elem->MissionRole() == Mission::ESCORT) { + Instruction* obj = new(__FILE__,__LINE__) Instruction(Instruction::ESCORT, cmdr->Name()); + if (obj) { + obj->SetTargetDesc(Text("the ") + g->GetDescription()); + elem->AddObjective(obj); + } + } + } + + mission->AddElement(elem); + } + } +} + +MissionElement* +CampaignMissionStarship::CreateSingleElement(CombatGroup* g, CombatUnit* u) +{ + if (!g || g->IsReserve()) return 0; + if (!u || u->LiveCount() < 1) return 0; + if (!mission->GetStarSystem()) return 0; + + // no ground units in starship missions: + StarSystem* system = mission->GetStarSystem(); + OrbitalRegion* rgn = system->FindRegion(u->GetRegion()); + + if (!rgn || rgn->Type() == Orbital::TERRAIN) + return 0; + + // make sure this unit isn't already in the mission: + ListIter e_iter = mission->GetElements(); + while (++e_iter) { + MissionElement* elem = e_iter.value(); + + if (elem->GetCombatUnit() == u) + return 0; + } + + MissionElement* elem = new(__FILE__,__LINE__) MissionElement; + if (!elem) { + Exit(); + return 0; + } + + if (u->Name().length()) + elem->SetName(u->Name()); + else + elem->SetName(u->DesignName()); + + elem->SetElementID(pkg_id++); + + elem->SetDesign(u->GetDesign()); + elem->SetCount(u->LiveCount()); + elem->SetIFF(u->GetIFF()); + elem->SetIntelLevel(g->IntelLevel()); + elem->SetRegion(u->GetRegion()); + elem->SetHeading(u->GetHeading()); + + int unit_index = g->GetUnits().index(u); + Point base_loc = u->Location(); + bool exact = u->IsStatic(); // exact unit-level placement + + if (base_loc.length() < 1) { + base_loc = g->Location(); + exact = false; + } + + if (unit_index < 0 || unit_index > 0 && !exact) { + Point loc = RandomDirection(); + + if (!u->IsStatic()) { + while (fabs(loc.y) > fabs(loc.x)) + loc = RandomDirection(); + + loc *= 10e3 + 9e3 * unit_index; + } + else { + loc *= 2e3 + 2e3 * unit_index; + } + + elem->SetLocation(base_loc + loc); + } + else { + elem->SetLocation(base_loc); + } + + if (g->Type() == CombatGroup::CARRIER_GROUP) { + if (u->Type() == Ship::CARRIER) { + elem->SetMissionRole(Mission::FLIGHT_OPS); + } + else { + elem->SetMissionRole(Mission::ESCORT); + } + } + else if (u->Type() == Ship::STATION || u->Type() == Ship::FARCASTER) { + elem->SetMissionRole(Mission::OTHER); + + // link farcaster to other terminus: + if (u->Type() == Ship::FARCASTER) { + Text name = u->Name(); + int dash = -1; + + for (int i = 0; i < (int) name.length(); i++) + if (name[i] == '-') + dash = i; + + Text src = name.substring(0, dash); + Text dst = name.substring(dash+1, name.length() - (dash+1)); + + Instruction* obj = new(__FILE__,__LINE__) Instruction(Instruction::VECTOR, dst + "-" + src); + if (obj) + elem->AddObjective(obj); + } + } + else if (u->Type() & Ship::STARSHIPS != 0) { + elem->SetMissionRole(Mission::FLEET); + } + + elem->SetCombatGroup(g); + elem->SetCombatUnit(u); + + return elem; +} + +CombatUnit* +CampaignMissionStarship::FindCarrier(CombatGroup* g) +{ + CombatGroup* carrier = g->FindCarrier(); + + if (carrier && carrier->GetUnits().size()) { + MissionElement* carrier_elem = mission->FindElement(carrier->Name()); + + if (carrier_elem) + return carrier->GetUnits().at(0); + } + + return 0; +} + +void +CampaignMissionStarship::CreateSquadron(CombatGroup* g) +{ + if (!g || g->IsReserve()) return; + + CombatUnit* fighter = g->GetUnits().at(0); + CombatUnit* carrier = FindCarrier(g); + + if (!fighter || !carrier) return; + + int live_count = fighter->LiveCount(); + int maint_count = (live_count > 4) ? live_count / 2 : 0; + + MissionElement* elem = new(__FILE__,__LINE__) MissionElement; + + if (!elem) { + Exit(); + return; + } + + elem->SetName(g->Name()); + elem->SetElementID(pkg_id++); + + elem->SetDesign(fighter->GetDesign()); + elem->SetCount(fighter->Count()); + elem->SetDeadCount(fighter->DeadCount()); + elem->SetMaintCount(maint_count); + elem->SetIFF(fighter->GetIFF()); + elem->SetIntelLevel(g->IntelLevel()); + elem->SetRegion(fighter->GetRegion()); + + elem->SetCarrier(carrier->Name()); + elem->SetCommander(carrier->Name()); + elem->SetLocation(carrier->Location() + RandomPoint()); + + elem->SetCombatGroup(g); + elem->SetCombatUnit(fighter); + + mission->AddElement(elem); +} + +// +--------------------------------------------------------------------+ + +void +CampaignMissionStarship::CreateWards() +{ + switch (mission->Type()) { + case Mission::ESCORT_FREIGHT: CreateWardFreight(); break; + default: break; + } +} + +void +CampaignMissionStarship::CreateWardFreight() +{ + if (!mission || !mission->GetStarSystem() || !player_group) return; + + CombatGroup* freight = 0; + + if (request) + freight = request->GetObjective(); + + if (!freight) + freight = campaign->FindGroup(ownside, CombatGroup::FREIGHT); + + if (!freight || freight->CalcValue() < 1) return; + + CombatUnit* unit = freight->GetNextUnit(); + if (!unit) return; + + MissionElement* elem = CreateSingleElement(freight, unit); + if (!elem) return; + + elem->SetMissionRole(Mission::CARGO); + elem->SetIntelLevel(Intel::KNOWN); + elem->SetRegion(player_group->GetRegion()); + + ward = elem; + mission->AddElement(elem); + + + StarSystem* system = mission->GetStarSystem(); + OrbitalRegion* rgn1 = system->FindRegion(elem->Region()); + Point delta = rgn1->Location() - rgn1->Primary()->Location(); + Point navpt_loc = elem->Location(); + Instruction* n = 0; + + delta.Normalize(); + delta *= 200.0e3; + + navpt_loc += delta; + + n = new(__FILE__,__LINE__) Instruction(elem->Region(), + navpt_loc, + Instruction::VECTOR); + if (n) { + n->SetSpeed(500); + elem->AddNavPoint(n); + } + + Text rgn2 = elem->Region(); + List& zones = campaign->GetZones(); + if (zones[zones.size()-1]->HasRegion(rgn2)) + rgn2 = *zones[0]->GetRegions()[0]; + else + rgn2 = *zones[zones.size()-1]->GetRegions()[0]; + + n = new(__FILE__,__LINE__) Instruction(rgn2, + Point(0, 0, 0), + Instruction::VECTOR); + if (n) { + n->SetSpeed(750); + elem->AddNavPoint(n); + } +} + +// +--------------------------------------------------------------------+ + +void +CampaignMissionStarship::CreateEscorts() +{ +} + +// +--------------------------------------------------------------------+ + +void +CampaignMissionStarship::CreateTargets() +{ + if (player_group && player_group->Type() == CombatGroup::CARRIER_GROUP) { + CreateTargetsCarrier(); + } + + else { + switch (mission->Type()) { + default: + case Mission::PATROL: CreateTargetsPatrol(); break; + case Mission::ASSAULT: + case Mission::STRIKE: CreateTargetsAssault(); break; + case Mission::ESCORT_FREIGHT: CreateTargetsFreightEscort(); break; + } + } +} + +// +--------------------------------------------------------------------+ + +void +CampaignMissionStarship::CreateTargetsAssault() +{ + if (!player) return; + + CombatGroup* assigned = 0; + + if (request) + assigned = request->GetObjective(); + + if (assigned) { + CreateElements(assigned); + + ListIter e_iter = mission->GetElements(); + while (++e_iter) { + MissionElement* elem = e_iter.value(); + + if (elem->GetCombatGroup() == assigned) { + if (!prime_target) { + prime_target = elem; + + MissionElement* player_lead = player_group_elements[0]; + + if (!player_lead) return; + + Instruction* obj = new(__FILE__,__LINE__) Instruction(Instruction::ASSAULT, prime_target->Name()); + if (obj) { + obj->SetTargetDesc(Text("preplanned target '") + prime_target->Name() + "'"); + player_lead->AddObjective(obj); + } + + // create flight plan: + RLoc rloc; + RLoc* ref = 0; + Vec3 dummy(0,0,0); + Instruction* instr = 0; + Point loc = player_lead->Location(); + Point tgt = prime_target->Location(); + Point mid; + + mid = loc + (prime_target->Location() - loc) * 0.35; + + rloc.SetReferenceLoc(0); + rloc.SetBaseLocation(mid); + rloc.SetDistance(50e3); + rloc.SetDistanceVar(5e3); + rloc.SetAzimuth(90*DEGREES); + rloc.SetAzimuthVar(45*DEGREES); + + instr = new(__FILE__,__LINE__) Instruction(prime_target->Region(), dummy, Instruction::VECTOR); + + if (!instr) + return; + + instr->SetSpeed(750); + instr->GetRLoc() = rloc; + + ref = &instr->GetRLoc(); + + player_lead->AddNavPoint(instr); + + for (int i = 1; i < player_group_elements.size(); i++) { + MissionElement* pge = player_group_elements[i]; + RLoc rloc2; + + rloc2.SetReferenceLoc(ref); + rloc2.SetDistance(50e3); + rloc2.SetDistanceVar(5e3); + + instr = new(__FILE__,__LINE__) Instruction(prime_target->Region(), dummy, Instruction::VECTOR); + + if (!instr) + return; + + instr->SetSpeed(750); + instr->GetRLoc() = rloc2; + + pge->AddNavPoint(instr); + } + + double extra = 10e3; + + if (prime_target && prime_target->GetDesign()) { + switch (prime_target->GetDesign()->type) { + default: extra = 20e3; break; + case Ship::FRIGATE: extra = 25e3; break; + case Ship::DESTROYER: extra = 30e3; break; + case Ship::CRUISER: extra = 50e3; break; + case Ship::BATTLESHIP: extra = 70e3; break; + case Ship::DREADNAUGHT: extra = 80e3; break; + case Ship::CARRIER: extra = 90e3; break; + } + } + + rloc.SetReferenceLoc(0); + rloc.SetBaseLocation(tgt); + rloc.SetDistance(100e3 + extra); + rloc.SetDistanceVar(15e3); + rloc.SetAzimuth(90*DEGREES); + rloc.SetAzimuthVar(45*DEGREES); + + instr = new(__FILE__,__LINE__) Instruction(prime_target->Region(), dummy, Instruction::ASSAULT); + + if (!instr) + return; + + instr->SetSpeed(500); + instr->GetRLoc() = rloc; + instr->SetTarget(prime_target->Name()); + + ref = &instr->GetRLoc(); + + player_lead->AddNavPoint(instr); + + for (i = 1; i < player_group_elements.size(); i++) { + MissionElement* pge = player_group_elements[i]; + RLoc rloc2; + + rloc2.SetReferenceLoc(ref); + rloc2.SetDistance(50e3); + rloc2.SetDistanceVar(5e3); + + instr = new(__FILE__,__LINE__) Instruction(prime_target->Region(), dummy, Instruction::ASSAULT); + + if (!instr) + return; + + instr->SetSpeed(500); + instr->GetRLoc() = rloc2; + instr->SetTarget(prime_target->Name()); + + pge->AddNavPoint(instr); + } + } + } + } + } +} + + +// +--------------------------------------------------------------------+ + +void +CampaignMissionStarship::CreateTargetsCarrier() +{ + if (!player_group || !player) return; + + Text region = player_group->GetRegion(); + Point base_loc = player->Location(); + Point patrol_loc = base_loc + + RandomDirection() * Random( 75e3, 150e3); + Point loc2 = patrol_loc + + RandomDirection() * Random( 50e3, 100e3); + + + int ntargets = 2 + RandomChance() ? 1 : 0; + int ntries = 8; + + while (ntargets > 0 && ntries > 0) { + Point target_loc = RandomChance() ? patrol_loc : loc2; + int t = CreateRandomTarget(region, target_loc); + ntargets -= t; + if (t < 1) ntries--; + } +} + +// +--------------------------------------------------------------------+ + +void +CampaignMissionStarship::CreateTargetsPatrol() +{ + if (!player_group || !player) return; + + Text region = player_group->GetRegion(); + Point base_loc = player->Location(); + Point patrol_loc = base_loc + + RandomDirection() * Random(170e3, 250e3); + + Instruction* n = new(__FILE__,__LINE__) Instruction(region, + patrol_loc, + Instruction::PATROL); + player->AddNavPoint(n); + + for (int i = 1; i < player_group_elements.size(); i++) { + MissionElement* elem = player_group_elements[i]; + + n = new(__FILE__,__LINE__) Instruction(region, + patrol_loc + RandomDirection() * Random(20e3, 40e3), + Instruction::PATROL); + if (n) + elem->AddNavPoint(n); + } + + Point loc2 = patrol_loc + RandomDirection() * Random(150e3, 200e3); + + n = new(__FILE__,__LINE__) Instruction(region, + loc2, + Instruction::PATROL); + if (n) + player->AddNavPoint(n); + + for (i = 1; i < player_group_elements.size(); i++) { + MissionElement* elem = player_group_elements[i]; + + n = new(__FILE__,__LINE__) Instruction(region, + loc2 + RandomDirection() * Random(20e3, 40e3), + Instruction::PATROL); + + if (n) + elem->AddNavPoint(n); + } + + int ntargets = 2 + RandomChance() ? 1 : 0; + int ntries = 8; + + while (ntargets > 0 && ntries > 0) { + Point target_loc = RandomChance() ? patrol_loc : loc2; + int t = CreateRandomTarget(region, target_loc); + ntargets -= t; + if (t < 1) ntries--; + } + + Instruction* obj = new(__FILE__,__LINE__) Instruction(*n); + if (obj) { + obj->SetTargetDesc("inbound enemy units"); + player->AddObjective(obj); + } +} + +// +--------------------------------------------------------------------+ + +void +CampaignMissionStarship::CreateTargetsFreightEscort() +{ + if (!ward) { + CreateTargetsPatrol(); + return; + } + + CombatGroup* s = FindSquadron(enemy, CombatGroup::ATTACK_SQUADRON); + CombatGroup* s2 = FindSquadron(enemy, CombatGroup::FIGHTER_SQUADRON); + + if (!s || !s2) return; + + MissionElement* elem = CreateFighterPackage(s, 2, Mission::ASSAULT); + if (elem) { + elem->SetIntelLevel(Intel::KNOWN); + + elem->SetLocation(ward->Location() + RandomPoint() * 5); + + Instruction* obj = new(__FILE__,__LINE__) Instruction(Instruction::ASSAULT, ward->Name()); + if (obj) + elem->AddObjective(obj); + mission->AddElement(elem); + + MissionElement* e2 = CreateFighterPackage(s2, 2, Mission::ESCORT); + if (e2) { + e2->SetIntelLevel(Intel::KNOWN); + e2->SetLocation(elem->Location() + RandomPoint() * 0.25); + + Instruction* obj2 = new(__FILE__,__LINE__) Instruction(Instruction::ESCORT, elem->Name()); + if (obj2) + e2->AddObjective(obj2); + mission->AddElement(e2); + } + } + + Instruction* obj3 = new(__FILE__,__LINE__) Instruction(mission->GetRegion(), + Point(0,0,0), + Instruction::PATROL); + if (player && obj3) { + obj3->SetTargetDesc("enemy patrols"); + player->AddObjective(obj3); + } +} + +// +--------------------------------------------------------------------+ + +int +CampaignMissionStarship::CreateRandomTarget(const char* rgn, Point base_loc) +{ + int ntargets = 0; + int ttype = RandomIndex(); + + if (player_group && player_group->Type() == CombatGroup::CARRIER_GROUP) { + switch (ttype) { + case 0: + case 1: + case 2: + case 3: ttype = 0; break; + case 4: + case 5: ttype = 1; break; + case 6: + case 7: ttype = 2; break; + case 8: + case 9: ttype = 3; break; + case 10: + case 11: ttype = 4; break; + case 12: + case 13: + case 14: + case 15: ttype = 5; break; + } + } + else { + switch (ttype) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: ttype = 0; break; + case 6: + case 7: + case 8: ttype = 1; break; + case 9: + case 10: ttype = 4; break; + case 11: + case 12: + case 13: + case 14: + case 15: ttype = 5; break; + } + } + + switch (ttype) { + case 0: { + CombatGroup* s = 0; + + s = FindSquadron(enemy, CombatGroup::DESTROYER_SQUADRON); + + if (s) { + for (int i = 0; i < 2; i++) { + CombatUnit* u = s->GetRandomUnit(); + MissionElement* elem = CreateSingleElement(s, u); + if (elem) { + elem->SetIntelLevel(Intel::KNOWN); + elem->SetRegion(rgn); + elem->SetLocation(base_loc + RandomPoint() * 1.5); + elem->SetMissionRole(Mission::FLEET); + mission->AddElement(elem); + ntargets++; + } + } + } + } + break; + + case 1: { + CombatGroup* s = FindSquadron(enemy, CombatGroup::LCA_SQUADRON); + + if (s) { + MissionElement* elem = CreateFighterPackage(s, 2, Mission::CARGO); + if (elem) { + elem->SetIntelLevel(Intel::KNOWN); + elem->SetRegion(rgn); + elem->SetLocation(base_loc + RandomPoint() * 2); + mission->AddElement(elem); + ntargets++; + + CombatGroup* s2 = FindSquadron(enemy, CombatGroup::FIGHTER_SQUADRON); + + if (s2) { + MissionElement* e2 = CreateFighterPackage(s2, 2, Mission::ESCORT); + if (e2) { + e2->SetIntelLevel(Intel::KNOWN); + e2->SetRegion(rgn); + e2->SetLocation(elem->Location() + RandomPoint() * 0.5); + + Instruction* obj = new(__FILE__,__LINE__) Instruction(Instruction::ESCORT, elem->Name()); + if (obj) + e2->AddObjective(obj); + + mission->AddElement(e2); + } + } + } + } + } + break; + + case 2: { + CombatGroup* s = FindSquadron(enemy, CombatGroup::INTERCEPT_SQUADRON); + + if (s) { + MissionElement* elem = CreateFighterPackage(s, 4, Mission::PATROL); + if (elem) { + elem->SetIntelLevel(Intel::SECRET); + elem->SetRegion(rgn); + elem->SetLocation(base_loc); + mission->AddElement(elem); + ntargets++; + } + } + } + break; + + case 3: { + CombatGroup* s = FindSquadron(enemy, CombatGroup::FIGHTER_SQUADRON); + + if (s) { + MissionElement* elem = CreateFighterPackage(s, 3, Mission::ASSAULT); + if (elem) { + elem->SetIntelLevel(Intel::KNOWN); + elem->Loadouts().destroy(); + elem->Loadouts().append(new(__FILE__,__LINE__) MissionLoad(-1, "Ship Strike")); + elem->SetRegion(rgn); + elem->SetLocation(base_loc + RandomPoint()); + mission->AddElement(elem); + + if (player) { + Instruction* n = new(__FILE__,__LINE__) Instruction(player->Region(), + player->Location() + RandomPoint(), + Instruction::ASSAULT); + n->SetTarget(player->Name()); + elem->AddNavPoint(n); + } + + ntargets++; + } + } + } + break; + + case 4: { + CombatGroup* s = FindSquadron(enemy, CombatGroup::ATTACK_SQUADRON); + + if (s) { + MissionElement* elem = CreateFighterPackage(s, 2, Mission::ASSAULT); + if (elem) { + elem->SetIntelLevel(Intel::KNOWN); + elem->Loadouts().destroy(); + elem->Loadouts().append(new(__FILE__,__LINE__) MissionLoad(-1, "Hvy Ship Strike")); + elem->SetRegion(rgn); + elem->SetLocation(base_loc + RandomPoint() * 1.3); + mission->AddElement(elem); + + if (player) { + Instruction* n = new(__FILE__,__LINE__) Instruction(player->Region(), + player->Location() + RandomPoint(), + Instruction::ASSAULT); + n->SetTarget(player->Name()); + elem->AddNavPoint(n); + } + + ntargets++; + } + } + } + break; + + default: { + CombatGroup* s = 0; + + s = FindSquadron(enemy, CombatGroup::FREIGHT); + + if (s) { + CombatUnit* u = s->GetRandomUnit(); + MissionElement* elem = CreateSingleElement(s, u); + if (elem) { + elem->SetIntelLevel(Intel::KNOWN); + elem->SetRegion(rgn); + elem->SetLocation(base_loc + RandomPoint() * 2); + elem->SetMissionRole(Mission::CARGO); + mission->AddElement(elem); + ntargets++; + + CombatGroup* s2 = FindSquadron(enemy, CombatGroup::INTERCEPT_SQUADRON); + + if (s2) { + MissionElement* e2 = CreateFighterPackage(s2, 2, Mission::ESCORT); + if (e2) { + e2->SetIntelLevel(Intel::KNOWN); + e2->SetRegion(rgn); + e2->SetLocation(elem->Location() + RandomPoint() * 0.5); + + Instruction* obj = new(__FILE__,__LINE__) Instruction(Instruction::ESCORT, elem->Name()); + if (obj) + e2->AddObjective(obj); + mission->AddElement(e2); + ntargets++; + } + } + } + } + } + break; + } + + return ntargets; +} + +// +--------------------------------------------------------------------+ + +MissionElement* +CampaignMissionStarship::CreateFighterPackage(CombatGroup* squadron, int count, int role) +{ + if (!squadron || squadron->IsReserve()) + return 0; + + CombatUnit* fighter = squadron->GetUnits().at(0); + CombatUnit* carrier = FindCarrier(squadron); + + if (!fighter) + return 0; + + int avail = fighter->LiveCount(); + int actual = count; + + if (avail < actual) + actual = avail; + + if (avail < 1) { + ::Print("CMS - Insufficient fighters in squadron '%s' - %d required, %d available\n", + squadron->Name().data(), count, avail); + return 0; + } + + MissionElement* elem = new(__FILE__,__LINE__) MissionElement; + + if (!elem) { + Exit(); + return 0; + } + + elem->SetName(Callsign::GetCallsign(fighter->GetIFF())); + elem->SetElementID(pkg_id++); + + if (carrier) { + elem->SetCommander(carrier->Name()); + elem->SetHeading(carrier->GetHeading()); + } + else { + elem->SetHeading(fighter->GetHeading()); + } + + elem->SetDesign(fighter->GetDesign()); + elem->SetCount(actual); + elem->SetIFF(fighter->GetIFF()); + elem->SetIntelLevel(squadron->IntelLevel()); + elem->SetRegion(fighter->GetRegion()); + elem->SetSquadron(fighter->Name()); + elem->SetMissionRole(role); + elem->Loadouts().append(new(__FILE__,__LINE__) MissionLoad(-1, "ACM Medium Range")); + + if (carrier) + elem->SetLocation(carrier->Location() + RandomPoint() * 0.3); + else + elem->SetLocation(fighter->Location() + RandomPoint()); + + elem->SetCombatGroup(squadron); + elem->SetCombatUnit(fighter); + + return elem; +} + +// +--------------------------------------------------------------------+ + +CombatGroup* +CampaignMissionStarship::FindSquadron(int iff, int type) +{ + if (!player_group) return 0; + + CombatGroup* result = 0; + CombatZone* zone = player_group->GetAssignedZone(); + if (!zone) zone = player_group->GetCurrentZone(); + + if (!zone) { + ::Print("CMS Warning: no zone for %s\n", player_group->Name().data()); + return result; + } + + ZoneForce* force = zone->FindForce(iff); + + if (force) { + List groups; + ListIter group = force->GetGroups(); + while (++group) { + CombatGroup* g = group.value(); + + if (g->Type() == type && g->CountUnits() > 0) { + result = g; + groups.append(g); + } + } + + if (groups.size() > 1) { + int index = (int) Random(0, groups.size()); + if (index >= groups.size()) index = groups.size() - 1; + result = groups[index]; + } + } + + return result; +} + +// +--------------------------------------------------------------------+ + +void +CampaignMissionStarship::DefineMissionObjectives() +{ + if (!mission || !player) return; + + if (prime_target) mission->SetTarget(prime_target); + if (ward) mission->SetWard(ward); + + Text objectives; + + if (player->Objectives().size() > 0) { + for (int i = 0; i < player->Objectives().size(); i++) { + Instruction* obj = player->Objectives().at(i); + objectives += "* "; + objectives += obj->GetDescription(); + objectives += ".\n"; + } + } + else { + objectives += "* Perform standard fleet operations in the "; + objectives += mission->GetRegion(); + objectives += " sector.\n"; + } + + mission->SetObjective(objectives); +} + +// +--------------------------------------------------------------------+ + +MissionInfo* +CampaignMissionStarship::DescribeMission() +{ + if (!mission || !player) return 0; + + char name[256]; + char player_info[256]; + + if (mission_info && mission_info->name.length()) + sprintf(name, "MSN-%03d %s", mission->Identity(), mission_info->name.data()); + + else if (ward) + sprintf(name, "MSN-%03d %s %s", mission->Identity(), Game::GetText(mission->TypeName()).data(), ward->Name().data()); + + else if (prime_target) + sprintf(name, "MSN-%03d %s %s %s", mission->Identity(), Game::GetText(mission->TypeName()).data(), + Ship::ClassName(prime_target->GetDesign()->type), + prime_target->Name().data()); + + else + sprintf(name, "MSN-%03d %s", mission->Identity(), Game::GetText(mission->TypeName()).data()); + + if (player) { + strcpy(player_info, player->GetCombatGroup()->GetDescription()); + } + + MissionInfo* info = new(__FILE__,__LINE__) MissionInfo; + + if (info) { + info->id = mission->Identity(); + info->mission = mission; + info->name = name; + info->type = mission->Type(); + info->player_info = player_info; + info->description = mission->Objective(); + info->start = mission->Start(); + + if (mission->GetStarSystem()) + info->system = mission->GetStarSystem()->Name(); + info->region = mission->GetRegion(); + } + + mission->SetName(name); + + return info; +} + +// +--------------------------------------------------------------------+ + +void +CampaignMissionStarship::Exit() +{ + Starshatter* stars = Starshatter::GetInstance(); + if (stars) + stars->SetGameMode(Starshatter::MENU_MODE); +} diff --git a/Stars45/CampaignMissionStarship.h b/Stars45/CampaignMissionStarship.h new file mode 100644 index 0000000..708e7a0 --- /dev/null +++ b/Stars45/CampaignMissionStarship.h @@ -0,0 +1,107 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CampaignMissionStarship.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + CampaignMissionStarship generates missions and mission + info for the player's STARSHIP GROUP as part of a + dynamic campaign. +*/ + +#ifndef CampaignMissionStarship_h +#define CampaignMissionStarship_h + +#include "Types.h" +#include "Geometry.h" +#include "List.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class Campaign; +class CampaignMissionRequest; +class CombatGroup; +class CombatUnit; +class CombatZone; +class Mission; +class MissionElement; +class MissionInfo; +class MissionTemplate; + +// +--------------------------------------------------------------------+ + +class CampaignMissionStarship +{ +public: + static const char* TYPENAME() { return "CampaignMissionStarship"; } + + CampaignMissionStarship(Campaign* c); + virtual ~CampaignMissionStarship(); + + virtual void CreateMission(CampaignMissionRequest* request); + +protected: + virtual Mission* GenerateMission(int id); + virtual void SelectType(); + virtual void SelectRegion(); + virtual void GenerateStandardElements(); + virtual void GenerateMissionElements(); + virtual void CreateElements(CombatGroup* g); + virtual void CreateSquadron(CombatGroup* g); + virtual void CreatePlayer(); + + virtual void CreateWards(); + virtual void CreateWardFreight(); + + virtual void CreateEscorts(); + + virtual void CreateTargets(); + virtual void CreateTargetsAssault(); + virtual void CreateTargetsPatrol(); + virtual void CreateTargetsCarrier(); + virtual void CreateTargetsFreightEscort(); + virtual int CreateRandomTarget(const char* rgn, Point base_loc); + + virtual MissionElement* + CreateSingleElement(CombatGroup* g, + CombatUnit* u); + virtual MissionElement* + CreateFighterPackage(CombatGroup* squadron, + int count, + int role); + + virtual CombatGroup* FindSquadron(int iff, int type); + virtual CombatUnit* FindCarrier(CombatGroup* g); + + virtual void DefineMissionObjectives(); + virtual MissionInfo* DescribeMission(); + virtual void Exit(); + + Campaign* campaign; + CampaignMissionRequest* request; + MissionInfo* mission_info; + + CombatUnit* player_unit; + CombatGroup* player_group; + CombatGroup* strike_group; + CombatGroup* strike_target; + Mission* mission; + List player_group_elements; + MissionElement* player; + MissionElement* ward; + MissionElement* prime_target; + MissionElement* escort; + + int ownside; + int enemy; + int mission_type; +}; + +#endif CampaignMissionStarship_h + diff --git a/Stars45/CampaignPlan.h b/Stars45/CampaignPlan.h new file mode 100644 index 0000000..f823a7f --- /dev/null +++ b/Stars45/CampaignPlan.h @@ -0,0 +1,58 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CampaignPlan.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + CampaignPlan defines the interface for all campaign + planning algorithms. Known subclasses: + CampaignPlanStrategic - strategic planning + CampaignPlanAssignment - logistics planning + CampaignPlanMission - mission planning + CampaignPlanMovement - starship movement + CampaignPlanEvent - scripted events +*/ + +#ifndef CampaignPlan_h +#define CampaignPlan_h + +#include "Types.h" +#include "Text.h" +#include "Term.h" +#include "List.h" + +// +--------------------------------------------------------------------+ + +class Campaign; +class Combatant; +class CombatGroup; +class CombatUnit; + +// +--------------------------------------------------------------------+ + +class CampaignPlan +{ +public: + static const char* TYPENAME() { return "CampaignPlan"; } + + CampaignPlan(Campaign* c) : campaign(c), exec_time(-1e6) { } + virtual ~CampaignPlan() { } + + int operator == (const CampaignPlan& p) const { return this == &p; } + + // operations: + virtual void ExecFrame() { } + virtual void SetLockout(int seconds) { } + +protected: + Campaign* campaign; + double exec_time; +}; + +#endif CampaignPlan_h + diff --git a/Stars45/CampaignPlanAssignment.cpp b/Stars45/CampaignPlanAssignment.cpp new file mode 100644 index 0000000..3e2728b --- /dev/null +++ b/Stars45/CampaignPlanAssignment.cpp @@ -0,0 +1,158 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CampaignPlanAssignment.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + CampaignPlanAssignment creates combat assignments for + assets within each combat zone as the third step in + force tasking. +*/ + +#include "MemDebug.h" +#include "CampaignPlanAssignment.h" +#include "Campaign.h" +#include "Combatant.h" +#include "CombatAssignment.h" +#include "CombatGroup.h" +#include "CombatUnit.h" +#include "CombatZone.h" +#include "Mission.h" + +// +--------------------------------------------------------------------+ + +void +CampaignPlanAssignment::ExecFrame() +{ + if (campaign && campaign->IsActive()) { + // once every few minutes is plenty: + if (Campaign::Stardate() - exec_time < 300) + return; + + ListIter iter = campaign->GetCombatants(); + while (++iter) { + ProcessCombatant(iter.value()); + } + + exec_time = Campaign::Stardate(); + } +} + +// +--------------------------------------------------------------------+ + +void +CampaignPlanAssignment::ProcessCombatant(Combatant* c) +{ + CombatGroup* force = c->GetForce(); + if (force) { + force->CalcValue(); + force->ClearAssignments(); + } + + ListIter zone = campaign->GetZones(); + while (++zone) { + ProcessZone(c, zone.value()); + } +} + +// +--------------------------------------------------------------------+ + +void +CampaignPlanAssignment::BuildZoneList(CombatGroup* g, CombatZone* zone, List& groups) +{ + if (!g) + return; + + if (g->GetAssignedZone() == zone) + groups.append(g); + + ListIter iter = g->GetComponents(); + while (++iter) + BuildZoneList(iter.value(), zone, groups); +} + +// +--------------------------------------------------------------------+ + +void +CampaignPlanAssignment::BuildAssetList(const int* pref, + List& groups, + List& assets) +{ + if (!pref) + return; + + while (*pref) { + ListIter g = groups; + while (++g) { + if (g->Type() == *pref && g->CountUnits() > 0) + assets.append(g.value()); + } + + pref++; + } +} + +// +--------------------------------------------------------------------+ + +void +CampaignPlanAssignment::ProcessZone(Combatant* c, CombatZone* zone) +{ + List groups; + BuildZoneList(c->GetForce(), zone, groups); + + ZoneForce* force = zone->FindForce(c->GetIFF()); + + // defensive assignments: + ListIter def = force->GetDefendList(); + while (++def) { + List assets; + BuildAssetList(CombatGroup::PreferredDefender(def->Type()), groups, assets); + + ListIter g = assets; + while (++g) { + CombatAssignment* a = new(__FILE__,__LINE__) + CombatAssignment(Mission::DEFEND, + def.value(), + g.value()); + + if (a) + g->GetAssignments().append(a); + } + } + + // offensive assignments: + ListIter tgt = force->GetTargetList(); + while (++tgt) { + CombatGroup* target = tgt.value(); + + List assets; + BuildAssetList(CombatGroup::PreferredAttacker(tgt->Type()), groups, assets); + + ListIter g = assets; + while (++g) { + CombatGroup* asset = g.value(); + int mtype = Mission::ASSAULT; + + if (target->IsStrikeTarget()) + mtype = Mission::STRIKE; + + else if (target->IsFighterGroup()) + mtype = Mission::SWEEP; + + else if (target->Type() == CombatGroup::LCA_SQUADRON) + mtype = Mission::INTERCEPT; + + CombatAssignment* a = new(__FILE__,__LINE__) + CombatAssignment(mtype, target, asset); + + if (a) + g->GetAssignments().append(a); + } + } +} + diff --git a/Stars45/CampaignPlanAssignment.h b/Stars45/CampaignPlanAssignment.h new file mode 100644 index 0000000..f73ca41 --- /dev/null +++ b/Stars45/CampaignPlanAssignment.h @@ -0,0 +1,50 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CampaignPlanAssignment.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + CampaignPlanAssignment creates combat assignments for + assets within each combat zone as the third step in + force tasking. +*/ + +#ifndef CampaignPlanAssignment_h +#define CampaignPlanAssignment_h + +#include "Types.h" +#include "CampaignPlan.h" + +// +--------------------------------------------------------------------+ + +class CombatGroup; +class CombatUnit; +class CombatZone; + +// +--------------------------------------------------------------------+ + +class CampaignPlanAssignment : public CampaignPlan +{ +public: + static const char* TYPENAME() { return "CampaignPlanAssignment"; } + + CampaignPlanAssignment(Campaign* c) : CampaignPlan(c) { } + virtual ~CampaignPlanAssignment() { } + + // operations: + virtual void ExecFrame(); + +protected: + virtual void ProcessCombatant(Combatant* c); + virtual void ProcessZone(Combatant* c, CombatZone* zone); + virtual void BuildZoneList(CombatGroup* g, CombatZone* zone, List& list); + virtual void BuildAssetList(const int* pref, List& avail, List& assets); +}; + +#endif CampaignPlanAssignment_h + diff --git a/Stars45/CampaignPlanEvent.cpp b/Stars45/CampaignPlanEvent.cpp new file mode 100644 index 0000000..bb1480d --- /dev/null +++ b/Stars45/CampaignPlanEvent.cpp @@ -0,0 +1,1315 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CampaignPlanEvent.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + CampaignPlanEvent generates simulated combat + events based on a statistical analysis of the + combatants within the context of a dynamic + campaign. +*/ + +#include "MemDebug.h" +#include "CampaignPlanEvent.h" +#include "Campaign.h" +#include "Combatant.h" +#include "CombatAction.h" +#include "CombatAssignment.h" +#include "CombatEvent.h" +#include "CombatGroup.h" +#include "CombatUnit.h" +#include "CombatZone.h" +#include "Mission.h" +#include "Random.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "FormatUtil.h" + +// +--------------------------------------------------------------------+ + +CampaignPlanEvent::CampaignPlanEvent(Campaign* c) + : CampaignPlan(c), event_time(0) +{ + if (campaign) { + event_time = (int) campaign->GetTime(); + } +} + +CampaignPlanEvent::~CampaignPlanEvent() +{ } + +// +--------------------------------------------------------------------+ + +void +CampaignPlanEvent::ExecFrame() +{ + if (campaign && campaign->IsActive()) { + if (!campaign->GetPlayerGroup()) + return; + + // once every twenty minutes is plenty: + if (Campaign::Stardate() - exec_time < 1200) + return; + + if (!ExecScriptedEvents()) + ExecStatisticalEvents(); + + exec_time = Campaign::Stardate(); + event_time = (int) campaign->GetTime(); + } +} + +void +CampaignPlanEvent::SetLockout(int seconds) +{ + exec_time = Campaign::Stardate() + seconds; +} + +// +--------------------------------------------------------------------+ + +bool +CampaignPlanEvent::ExecScriptedEvents() +{ + bool scripted_event = false; + + if (campaign) { + ListIter iter = campaign->GetActions(); + while (++iter) { + CombatAction* action = iter.value(); + + if (action->IsAvailable()) { + + switch (action->Type()) { + case CombatAction::COMBAT_EVENT: + { + CombatEvent* event = new(__FILE__,__LINE__) + CombatEvent(campaign, + action->Subtype(), + (int) campaign->GetTime(), + action->GetIFF(), + action->Source(), + action->Region()); + + if (!event) + return false; + + event->SetTitle(action->GetText()); + + if (*action->Filename() != 0) + event->SetFilename(action->Filename()); + + if (*action->ImageFile() != 0) + event->SetImageFile(action->ImageFile()); + + if (*action->SceneFile() != 0) + event->SetSceneFile(action->SceneFile()); + + event->Load(); + + ProsecuteKills(action); + campaign->GetEvents().append(event); + + action->FireAction(); + scripted_event = true; + + if (action->Subtype() == CombatEvent::CAMPAIGN_END) { + ::Print(">>>>> CAMPAIGN %d END (Action %03d) <<<<<\n", campaign->GetCampaignId(), action->Identity()); + campaign->SetStatus(Campaign::CAMPAIGN_SUCCESS); + } + + else if (action->Subtype() == CombatEvent::CAMPAIGN_FAIL) { + ::Print(">>>>> CAMPAIGN %d FAIL (Action %03d) <<<<<\n", campaign->GetCampaignId(), action->Identity()); + campaign->SetStatus(Campaign::CAMPAIGN_FAILED); + } + } + break; + + case CombatAction::STRATEGIC_DIRECTIVE: + { + CombatGroup* g = campaign->FindGroup(action->GetIFF(), + action->AssetType(), + action->AssetId()); + + if (g) { + g->SetStrategicDirection(action->GetText()); + action->FireAction(); + } + else { + action->FailAction(); + } + + scripted_event = true; + } + break; + + case CombatAction::CAMPAIGN_SITUATION: + { + campaign->SetSituation(action->GetText()); + action->FireAction(); + scripted_event = true; + } + break; + + case CombatAction::CAMPAIGN_ORDERS: + { + campaign->SetOrders(action->GetText()); + action->FireAction(); + scripted_event = true; + } + break; + + case CombatAction::INTEL_EVENT: + { + CombatGroup* g = campaign->FindGroup(action->GetIFF(), + action->AssetType(), + action->AssetId()); + + if (g) { + g->SetIntelLevel(action->Subtype()); + action->FireAction(); + } + else { + ::Print("WARNING: Action %d (intel level) Could not find group (IFF:%d, type:%d, id:%d)\n", + action->Identity(), + action->GetIFF(), + action->AssetType(), + action->AssetId()); + + action->FailAction(); + } + + scripted_event = true; + } + break; + + case CombatAction::ZONE_ASSIGNMENT: + { + CombatGroup* g = campaign->FindGroup(action->GetIFF(), + action->AssetType(), + action->AssetId()); + + if (g) { + bool found = false; + + if (*action->Region()) { + CombatZone* zone = campaign->GetZone(action->Region()); + + if (zone) { + g->SetAssignedZone(zone); + g->SetZoneLock(true); + found = true; + + // don't announce the move unless it's for the player's team: + if (action->GetIFF() == campaign->GetPlayerIFF() && + stricmp(action->GetText(), "do-not-display")) { + CombatEvent* event = new(__FILE__,__LINE__) + CombatEvent(campaign, + CombatEvent::MOVE_TO, + (int) campaign->GetTime(), + action->GetIFF(), + CombatEvent::FORCOM, + action->Region()); + + if (!event) + return false; + + Text title = Text(g->Name()) + " Orders: Proceed to " + action->Region() + " Sector"; + event->SetTitle(title); + + double eta = campaign->GetTime() + 3600; + eta -= fmod(eta, 1800); + + char text[64]; + FormatDayTime(text, eta); + + Text info = "ORDERS:\n\nEffective immediately, "; + info += g->GetDescription(); + info += " and all associated units shall proceed to "; + info += action->Region(); + info += " sector and commence spaceborne operations in that area. ETA rendevous point "; + info += text; + info += ".\n\nFleet Admiral A. Evars FORCOM\nCommanding"; + + event->SetInformation(info); + + if (*action->ImageFile() != 0) + event->SetImageFile(action->ImageFile()); + + if (*action->SceneFile() != 0) + event->SetSceneFile(action->SceneFile()); + + event->Load(); + campaign->GetEvents().append(event); + } + } + } + + if (!found) { + ::Print("WARNING: Action %d Could not find assigned zone '%s' for '%s'\n", + action->Identity(), + action->Region() ? action->Region() : "NULL", + g->Name().data()); + + g->SetAssignedZone(0); + } + + action->FireAction(); + } + else { + ::Print("WARNING: Action %d (zone assignment) Could not find group (IFF:%d, type:%d, id:%d)\n", + action->Identity(), + action->GetIFF(), + action->AssetType(), + action->AssetId()); + + action->FailAction(); + } + + scripted_event = true; + } + break; + + case CombatAction::SYSTEM_ASSIGNMENT: + { + CombatGroup* g = campaign->FindGroup(action->GetIFF(), + action->AssetType(), + action->AssetId()); + + if (g) { + bool found = false; + + if (*action->System()) { + Text system = action->System(); + + if (campaign->GetSystem(system)) { + g->SetAssignedSystem(system); + found = true; + + // don't announce the move unless it's for the player's team: + if (action->GetIFF() == campaign->GetPlayerIFF() && + stricmp(action->GetText(), "do-not-display")) { + CombatEvent* event = new(__FILE__,__LINE__) + CombatEvent(campaign, + CombatEvent::MOVE_TO, + (int) campaign->GetTime(), + action->GetIFF(), + CombatEvent::FORCOM, + action->Region()); + + if (!event) + return false; + + Text title = Text(g->Name()) + " Orders: Proceed to " + action->System() + " System"; + event->SetTitle(title); + + double eta = campaign->GetTime() + 3600; + eta -= fmod(eta, 1800); + + char text[64]; + FormatDayTime(text, eta); + + Text info = "ORDERS:\n\nEffective immediately, "; + info += g->GetDescription(); + info += " and all associated units shall proceed to the "; + info += action->System(); + info += " star system and commence spaceborne operations in that area. ETA rendevous point "; + info += text; + info += ".\n\nFleet Admiral A. Evars FORCOM\nCommanding"; + + event->SetInformation(info); + + if (*action->ImageFile() != 0) + event->SetImageFile(action->ImageFile()); + + if (*action->SceneFile() != 0) + event->SetSceneFile(action->SceneFile()); + + event->Load(); + campaign->GetEvents().append(event); + } + } + } + + if (!found) { + ::Print("WARNING: Action %d Could not find assigned system '%s' for '%s'\n", + action->Identity(), + action->System() ? action->System() : "NULL", + g->Name().data()); + + g->SetAssignedSystem(""); + } + + action->FireAction(); + } + else { + ::Print("WARNING: Action %d (system assignment) Could not find group (IFF:%d, type:%d, id:%d)\n", + action->Identity(), + action->GetIFF(), + action->AssetType(), + action->AssetId()); + + action->FailAction(); + } + + scripted_event = true; + } + break; + + case CombatAction::NO_ACTION: + action->FireAction(); + scripted_event = true; + break; + + default: + break; + } + } + } + } + + return scripted_event; +} + +// +--------------------------------------------------------------------+ + +void +CampaignPlanEvent::ProsecuteKills(CombatAction* action) +{ + if (action->AssetKills().size() > 0) { + CombatGroup* g = campaign->FindGroup(action->GetIFF(), + action->AssetType(), + action->AssetId()); + + if (g) { + ListIter iter = action->AssetKills(); + while (++iter) { + Text* name = iter.value(); + CombatUnit* asset = g->FindUnit(*name); + + if (asset) { + int value_killed = asset->Kill(1); + + ListIter iter = campaign->GetCombatants(); + while (++iter) { + Combatant* c = iter.value(); + if (c->GetIFF() > 0 && c->GetIFF() != asset->GetIFF()) { + // damage to neutral assets must be scored to bad guys: + if (asset->GetIFF() > 0 || c->GetIFF() > 1) { + c->AddScore(value_killed); + break; + } + } + } + } + } + } + } + + if (action->TargetKills().size() > 0) { + CombatGroup* g = campaign->FindGroup(action->TargetIFF(), + action->TargetType(), + action->TargetId()); + + if (g) { + ListIter iter = action->TargetKills(); + while (++iter) { + Text* name = iter.value(); + CombatUnit* target = g->FindUnit(*name); + + if (target) { + int value_killed = target->Kill(1); + + ListIter iter = campaign->GetCombatants(); + while (++iter) { + Combatant* c = iter.value(); + if (c->GetIFF() > 0 && c->GetIFF() != target->GetIFF()) { + // damage to neutral assets must be scored to bad guys: + if (target->GetIFF() > 0 || c->GetIFF() > 1) { + c->AddScore(value_killed); + break; + } + } + } + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +bool +CampaignPlanEvent::ExecStatisticalEvents() +{ + bool result = false; + + if (campaign) { + ListIter iter = campaign->GetCombatants(); + while (++iter && !result) { + Combatant* c = iter.value(); + CombatAssignment* a = ChooseAssignment(c->GetForce()); + + // prefer assignments not in player's zone: + if (a) { + CombatGroup* objective = a->GetObjective(); + CombatGroup* player = campaign->GetPlayerGroup(); + + if (objective && player && + objective->GetCurrentZone() == player->GetCurrentZone()) + a = ChooseAssignment(c->GetForce()); + } + + if (a) { + result = CreateEvent(a); + } + } + } + + return result; +} + +// +--------------------------------------------------------------------+ + +bool +CampaignPlanEvent::CreateEvent(CombatAssignment* a) +{ + CombatEvent* event = 0; + + if (campaign && a && a->GetResource() && RandomChance(1,2)) { + event_time = (int) Random(event_time, campaign->GetTime()); + + CombatGroup* group = a->GetResource(); + + if (group == campaign->GetPlayerGroup()) { + + if (group->Type() == CombatGroup::DESTROYER_SQUADRON || + group->Type() == CombatGroup::BATTLE_GROUP || + group->Type() == CombatGroup::CARRIER_GROUP) { + + return false; + } + } + + CombatGroup* target = a->GetObjective(); + + if (target && target == campaign->GetPlayerGroup()) { + + if (target->Type() == CombatGroup::DESTROYER_SQUADRON || + target->Type() == CombatGroup::BATTLE_GROUP || + target->Type() == CombatGroup::CARRIER_GROUP) { + + return false; + } + } + + switch (a->Type()) { + case Mission::DEFEND: + event = CreateEventDefend(a); + break; + + case Mission::ASSAULT: + if (group->IsStarshipGroup()) + event = CreateEventStarship(a); + else + event = CreateEventFighterAssault(a); + break; + + case Mission::STRIKE: + if (group->IsStarshipGroup()) + event = CreateEventStarship(a); + else + event = CreateEventFighterStrike(a); + break; + + case Mission::SWEEP: + event = CreateEventFighterSweep(a); + break; + } + + if (event) { + campaign->GetEvents().append(event); + return true; + } + } + + return false; +} + +// +--------------------------------------------------------------------+ + +static void FindAssignments(CombatGroup* g, List& alist) +{ + if (!g) return; + + alist.append(g->GetAssignments()); + + ListIter iter = g->GetComponents(); + while (++iter) + FindAssignments(iter.value(), alist); +} + +CombatAssignment* +CampaignPlanEvent::ChooseAssignment(CombatGroup* g) +{ + List alist; + FindAssignments(g, alist); + + int tries = 5; + + if (alist.size() > 0) { + while (tries-- > 0) { + int index = (int) Random(0, alist.size()); + + if (index >= alist.size()) + index = 0; + + CombatAssignment* a = alist[index]; + + if (!a) continue; + + CombatGroup* resource = a->GetResource(); + CombatGroup* objective = a->GetObjective(); + + if (!resource || !objective) + continue; + + if (resource->IsReserve() || objective->IsReserve()) + continue; + + if (resource->CalcValue() < 50 || objective->CalcValue() < 50) + continue; + + if (resource == campaign->GetPlayerGroup() || objective == campaign->GetPlayerGroup()) + continue; + + return a; + } + } + + return 0; +} + +// +--------------------------------------------------------------------+ + +CombatEvent* +CampaignPlanEvent::CreateEventDefend(CombatAssignment* a) +{ + bool friendly = IsFriendlyAssignment(a); + + if (!friendly) + return 0; + + CombatEvent* event = 0; + CombatGroup* group = a->GetResource(); + CombatGroup* obj = a->GetObjective(); + CombatUnit* unit = group->GetRandomUnit(); + CombatUnit* tgt = obj->GetRandomUnit(); + + if (!unit || !tgt) + return 0; + + bool success = Success(a); + Text rgn = group->GetRegion(); + Text title = Text(group->Name()) + " in Defensive Engagement"; + Text info; + + event = new(__FILE__,__LINE__) CombatEvent(campaign, + CombatEvent::DEFEND, + event_time, + group->GetIFF(), + CombatEvent::TACNET, + rgn); + + if (!event) + return 0; + + int tgt_count = 0; + int unit_count = 0; + + if (!success) { + if (tgt) { + if (tgt->Kill(1) > 0) + tgt_count++; + Combatant* c = group->GetCombatant(); + if (c) c->AddScore(tgt->GetSingleValue()); + } + + if (unit && RandomChance(1,5)) { + if (unit->Kill(1) > 0) + unit_count++; + Combatant* c = obj->GetCombatant(); + if (c) c->AddScore(unit->GetSingleValue()); + } + } + + CombatGroup* us = group; + CombatGroup* them = obj; + int us_count = unit_count; + int them_count = tgt_count; + + if (obj->IsStrikeTarget()) { + info = Text("EVENT: ") + rgn + " Sector\n\n"; + } + + else { + info = Text("MISSION: Escort ") + obj->Name() + ", " + rgn + " Sector\n\n"; + } + + info += GetTeamName(group); + info += Text(" ") + group->GetDescription(); + + if (success) + info += " successfully defended "; + else + info += " was unable to defend "; + + info += GetTeamName(obj); + info += Text(" ") + obj->GetDescription() + ".\n\n"; + + // need to find an enemy group to do the attacking... + + event->SetTitle(title); + event->SetInformation(info); + return event; +} + +// +--------------------------------------------------------------------+ + +CombatEvent* +CampaignPlanEvent::CreateEventFighterAssault(CombatAssignment* a) +{ + CombatEvent* event = 0; + CombatGroup* group = a->GetResource(); + CombatGroup* obj = a->GetObjective(); + CombatUnit* unit = group->GetRandomUnit(); + CombatUnit* tgt = obj->GetRandomUnit(); + + if (!unit || !tgt) + return 0; + + bool success = Success(a); + Text rgn = group->GetRegion(); + Text title = Text(group->Name()); + Text info; + + event = new(__FILE__,__LINE__) CombatEvent(campaign, + CombatEvent::ATTACK, + event_time, + group->GetIFF(), + CombatEvent::TACNET, + rgn); + + if (!event) + return 0; + + title += Text(" Assault ") + obj->Name(); + + int tgt_count = 0; + int unit_count = 0; + + if (success) { + if (tgt) { + int killed = tgt->Kill(1 + tgt->Count()/2); + if (killed > 0) + tgt_count += killed / tgt->GetSingleValue(); + Combatant* c = group->GetCombatant(); + if (c) c->AddScore(tgt->GetSingleValue()); + } + + if (unit && RandomChance(1,5)) { + if (unit->Kill(1) > 0) + unit_count++; + Combatant* c = obj->GetCombatant(); + if (c) c->AddScore(unit->GetSingleValue()); + } + } + else { + for (int i = 0; i < 2; i++) { + if (unit && RandomChance(1,4)) { + if (unit->Kill(1) > 0) + unit_count++; + Combatant* c = obj->GetCombatant(); + if (c) c->AddScore(unit->GetSingleValue()); + } + } + } + + CombatGroup* us = group; + CombatGroup* them = obj; + int us_count = unit_count; + int them_count = tgt_count; + + bool friendly = IsFriendlyAssignment(a); + + if (friendly) { + info = Text("MISSION: Strike, ") + rgn + " Sector\n\n"; + } + + else { + info = Text("EVENT: ") + rgn + " Sector\n\n"; + + us = obj; + them = group; + us_count = tgt_count; + them_count = unit_count; + } + + info += GetTeamName(group); + info += Text(" ") + group->GetDescription(); + + if (success) + info += " successfully assault "; + else if (!friendly) + info += " assault averted against "; + else + info += " attempted assault on "; + + info += GetTeamName(obj); + info += Text(" ") + obj->GetDescription() + ".\n\n"; + + char text[256]; + + if (them_count) { + if (friendly) { + if (them_count > 1) + sprintf(text, "ENEMY KILLED:\t %d %s destroyed\n", + them_count, tgt->Name().data()); + else + sprintf(text, "ENEMY KILLED:\t %s destroyed\n", + tgt->Name().data()); + } + else { + sprintf(text, "ENEMY KILLED:\t %d %s destroyed\n", + them_count, them->Name().data()); + } + + info += text; + } + else { + info += "ENEMY KILLED:\t 0\n"; + } + + if (us_count) { + if (!friendly) + sprintf(text, "ALLIED LOSSES:\t %s destroyed\n", + tgt->Name().data()); + else + sprintf(text, "ALLIED LOSSES:\t %d %s destroyed", + us_count, + us->Name().data()); + + info += text; + } + else { + info += "ALLIED LOSSES:\t 0"; + } + + + event->SetTitle(title); + event->SetInformation(info); + return event; +} + +// +--------------------------------------------------------------------+ + +CombatEvent* +CampaignPlanEvent::CreateEventFighterStrike(CombatAssignment* a) +{ + CombatEvent* event = 0; + CombatGroup* group = a->GetResource(); + CombatGroup* obj = a->GetObjective(); + CombatUnit* unit = group->GetRandomUnit(); + CombatUnit* tgt = obj->GetRandomUnit(); + + if (!unit || !tgt) + return 0; + + bool success = Success(a); + Text rgn = group->GetRegion(); + Text title = Text(group->Name()); + Text info; + + event = new(__FILE__,__LINE__) CombatEvent(campaign, + CombatEvent::ATTACK, + event_time, + group->GetIFF(), + CombatEvent::TACNET, + rgn); + + if (!event) + return 0; + + if (unit) + title += Text(" ") + unit->GetDesign()->abrv + "s"; + + if (success) { + title += " Successfully Strike " + obj->Name(); + } + else { + title += " Attempt Strike on " + obj->Name(); + } + + int tgt_count = 0; + int unit_count = 0; + + if (success) { + if (tgt) { + int killed = tgt->Kill(1 + tgt->Count()/2); + if (killed > 0) + tgt_count += killed / tgt->GetSingleValue(); + Combatant* c = group->GetCombatant(); + if (c) c->AddScore(tgt->GetSingleValue()); + } + + if (unit && RandomChance(1,5)) { + if (unit->Kill(1) > 0) + unit_count++; + Combatant* c = obj->GetCombatant(); + if (c) c->AddScore(unit->GetSingleValue()); + } + } + else { + for (int i = 0; i < 2; i++) { + if (unit && RandomChance(1,4)) { + if (unit->Kill(1) > 0) + unit_count++; + Combatant* c = obj->GetCombatant(); + if (c) c->AddScore(unit->GetSingleValue()); + } + } + } + + CombatGroup* us = group; + CombatGroup* them = obj; + int us_count = unit_count; + int them_count = tgt_count; + + bool friendly = IsFriendlyAssignment(a); + + if (friendly) { + info = Text("MISSION: Strike, ") + rgn + " Sector\n\n"; + } + + else { + info = Text("EVENT: ") + rgn + " Sector\n\n"; + + us = obj; + them = group; + us_count = tgt_count; + them_count = unit_count; + } + + info += GetTeamName(group); + info += Text(" ") + group->GetDescription(); + + if (success) + info += " successfully strike "; + else if (!friendly) + info += " strike against "; + else + info += " attempted strike on "; + + info += GetTeamName(obj); + info += Text(" ") + obj->GetDescription(); + + if (!success && !friendly) + info += " averted.\n\n"; + else + info += ".\n\n"; + + char text[256]; + + if (them_count) { + if (friendly) { + if (them_count > 1) + sprintf(text, "ENEMY KILLED:\t %d %s destroyed\n", + them_count, tgt->Name().data()); + else + sprintf(text, "ENEMY KILLED:\t %s destroyed\n", + tgt->Name().data()); + } + else { + sprintf(text, "ENEMY KILLED:\t %d %s destroyed\n", + them_count, + them->Name().data()); + } + + info += text; + } + else { + info += "ENEMY KILLED:\t 0\n"; + } + + if (us_count) { + if (!friendly) + sprintf(text, "ALLIED LOSSES:\t %s destroyed\n", + tgt->Name().data()); + else + sprintf(text, "ALLIED LOSSES:\t %d %s destroyed", + us_count, + us->Name().data()); + + info += text; + } + else { + info += "ALLIED LOSSES:\t 0"; + } + + event->SetTitle(title); + event->SetInformation(info); + return event; +} + +// +--------------------------------------------------------------------+ + +CombatEvent* +CampaignPlanEvent::CreateEventFighterSweep(CombatAssignment* a) +{ + CombatEvent* event = 0; + CombatGroup* group = a->GetResource(); + CombatGroup* obj = a->GetObjective(); + CombatUnit* unit = group->GetRandomUnit(); + CombatUnit* tgt = obj->GetRandomUnit(); + + if (!unit || !tgt) + return 0; + + bool success = Success(a); + Text rgn = group->GetRegion(); + Text title = Text(group->Name()); + Text info; + + event = new(__FILE__,__LINE__) CombatEvent(campaign, + CombatEvent::ATTACK, + event_time, + group->GetIFF(), + CombatEvent::TACNET, + rgn); + + if (!event) + return 0; + + if (unit) + title += Text(" ") + unit->GetDesign()->abrv + "s"; + else + title += " Fighters"; + + if (RandomChance(1, 4)) title += " Clash with "; + else if (RandomChance(1, 4)) title += " Engage "; + else if (RandomChance(1, 4)) title += " Intercept "; + else title += " Encounter "; + + title += obj->Name(); + + int tgt_count = 0; + int unit_count = 0; + + if (success) { + for (int i = 0; i < 2; i++) { + if (tgt && RandomChance(3,4)) { + if (tgt->Kill(1) > 0) + tgt_count++; + Combatant* c = group->GetCombatant(); + if (c) c->AddScore(tgt->GetSingleValue()); + } + } + + if (tgt_count > 1) { + if (tgt && RandomChance(1,4)) { + if (tgt->Kill(1) > 0) + tgt_count++; + Combatant* c = group->GetCombatant(); + if (c) c->AddScore(tgt->GetSingleValue()); + } + } + + else { + if (unit && RandomChance(1,5)) { + if (unit->Kill(1) > 0) + unit_count++; + Combatant* c = obj->GetCombatant(); + if (c) c->AddScore(unit->GetSingleValue()); + } + } + } + else { + for (int i = 0; i < 2; i++) { + if (unit && RandomChance(3,4)) { + if (unit->Kill(1) > 0) + unit_count++; + Combatant* c = obj->GetCombatant(); + if (c) c->AddScore(unit->GetSingleValue()); + } + } + + if (tgt && RandomChance(1,4)) { + if (tgt->Kill(1) > 0) + tgt_count++; + Combatant* c = group->GetCombatant(); + if (c) c->AddScore(tgt->GetSingleValue()); + } + } + + CombatGroup* us = group; + CombatGroup* them = obj; + int us_count = unit_count; + int them_count = tgt_count; + + bool friendly = IsFriendlyAssignment(a); + + if (!friendly) { + us = obj; + them = group; + us_count = tgt_count; + them_count = unit_count; + } + + if (friendly) { + if (RandomChance()) + info = Text("MISSION: OCA Sweep, ") + rgn + " Sector\n\n"; + else + info = Text("MISSION: FORCAP, ") + rgn + " Sector\n\n"; + + info += GetTeamName(group); + info += Text(" ") + group->GetDescription(); + info += Text(" engaged ") + GetTeamName(obj); + info += Text(" ") + obj->GetDescription() + ".\n\n"; + } + else { + info = Text("MISSION: Patrol, ") + rgn + " Sector\n\n"; + + info += GetTeamName(obj); + info += Text(" ") + obj->GetDescription(); + info += Text(" engaged ") + GetTeamName(group); + info += Text(" ") + group->GetDescription() + ".\n\n"; + } + + char text[256]; + + if (them_count) { + sprintf(text, "ENEMY KILLED:\t %d %s destroyed\n", + them_count, + them->Name().data()); + + info += text; + } + else { + info += "ENEMY KILLED:\t 0\n"; + } + + if (us_count) { + sprintf(text, "ALLIED LOSSES:\t %d %s destroyed", + us_count, + us->Name().data()); + info += text; + } + else { + info += "ALLIED LOSSES:\t 0"; + } + + event->SetTitle(title); + event->SetInformation(info); + return event; +} + +// +--------------------------------------------------------------------+ + +CombatEvent* +CampaignPlanEvent::CreateEventStarship(CombatAssignment* a) +{ + CombatEvent* event = 0; + CombatGroup* group = a->GetResource(); + CombatGroup* obj = a->GetObjective(); + CombatUnit* unit = group->GetRandomUnit(); + CombatUnit* tgt = obj->GetRandomUnit(); + + if (!unit || !tgt) + return 0; + + bool success = Success(a); + Text rgn = group->GetRegion(); + Text title = Text(group->Name()); + Text info; + + event = new(__FILE__,__LINE__) CombatEvent(campaign, + CombatEvent::ATTACK, + event_time, + group->GetIFF(), + CombatEvent::TACNET, + group->GetRegion()); + + if (!event) + return 0; + + title += Text(" Assaults ") + a->GetObjective()->Name(); + + int tgt_count = 0; + int unit_count = 0; + + if (success) { + if (tgt) { + if (tgt->Kill(1) > 0) + tgt_count++; + Combatant* c = group->GetCombatant(); + if (c) c->AddScore(tgt->GetSingleValue()); + } + + if (unit && RandomChance(1,5)) { + if (unit->Kill(1) > 0) + unit_count++; + Combatant* c = obj->GetCombatant(); + if (c) c->AddScore(unit->GetSingleValue()); + } + } + else { + for (int i = 0; i < 2; i++) { + if (unit && RandomChance(1,4)) { + if (unit->Kill(1) > 0) + unit_count++; + Combatant* c = obj->GetCombatant(); + if (c) c->AddScore(unit->GetSingleValue()); + } + } + } + + CombatGroup* us = group; + CombatGroup* them = obj; + int us_count = unit_count; + int them_count = tgt_count; + + bool friendly = IsFriendlyAssignment(a); + + if (friendly) { + info = Text("MISSION: Fleet Action, ") + rgn + " Sector\n\n"; + } + + else { + info = Text("EVENT: ") + rgn + " Sector\n\n"; + + us = obj; + them = group; + us_count = tgt_count; + them_count = unit_count; + } + + info += GetTeamName(group); + info += Text(" ") + group->GetDescription(); + + if (success) + info += " successfully assaulted "; + else if (!friendly) + info += " assault against "; + else + info += " attempted assault on "; + + info += GetTeamName(obj); + info += Text(" ") + obj->GetDescription(); + + if (!success && !friendly) + info += " failed.\n\n"; + else + info += ".\n\n"; + + char text[256]; + + if (them_count) { + if (friendly) { + if (tgt->Count() > 1) { + sprintf(text, "ENEMY KILLED:\t %d %s destroyed\n", + them_count, + tgt->Name().data()); + } + else { + sprintf(text, "ENEMY KILLED:\t %s destroyed\n", + tgt->Name().data()); + } + } + else { + if (unit->Count() > 1) { + sprintf(text, "ENEMY KILLED:\t %d %s destroyed\n", + them_count, + unit->Name().data()); + } + else { + sprintf(text, "ENEMY KILLED:\t %s destroyed\n", + unit->Name().data()); + } + } + + info += text; + } + else { + info += "ENEMY KILLED:\t 0\n"; + } + + if (us_count) { + if (!friendly) + sprintf(text, "ALLIED LOSSES:\t %s destroyed\n", + tgt->Name().data()); + else + sprintf(text, "ALLIED LOSSES:\t %s destroyed", + unit->Name().data()); + + info += text; + } + else { + info += "ALLIED LOSSES:\t 0"; + } + + event->SetTitle(title); + event->SetInformation(info); + return event; +} + +// +--------------------------------------------------------------------+ + +bool +CampaignPlanEvent::IsFriendlyAssignment(CombatAssignment* a) +{ + if (!campaign || !a || !a->GetResource()) + return false; + + int a_team = a->GetResource()->GetIFF(); + CombatGroup* player = campaign->GetPlayerGroup(); + + if (player && (player->GetIFF() == a_team)) + return true; + + return false; +} + +bool +CampaignPlanEvent::Success(CombatAssignment* a) +{ + if (!campaign || !a || !a->GetResource()) + return false; + + int odds = 6 - campaign->GetCampaignId(); + + if (odds < 1) + odds = 1; + + bool success = RandomChance(odds, 5); + + if (!IsFriendlyAssignment(a)) + success = !success; + + return success; +} + +// +--------------------------------------------------------------------+ + +Text +CampaignPlanEvent::GetTeamName(CombatGroup* g) +{ + while (g->GetParent()) + g = g->GetParent(); + + return g->Name(); +} diff --git a/Stars45/CampaignPlanEvent.h b/Stars45/CampaignPlanEvent.h new file mode 100644 index 0000000..74bfea9 --- /dev/null +++ b/Stars45/CampaignPlanEvent.h @@ -0,0 +1,72 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CampaignPlanEvent.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + CampaignPlanEvent generates simulated combat + events based on a statistical analysis of the + combatants within the context of a dynamic + campaign. +*/ + +#ifndef CampaignPlanEvent_h +#define CampaignPlanEvent_h + +#include "Types.h" +#include "CampaignPlan.h" + +// +--------------------------------------------------------------------+ + +class CombatAction; +class CombatAssignment; +class CombatEvent; +class CombatGroup; +class CombatUnit; +class CombatZone; + +// +--------------------------------------------------------------------+ + +class CampaignPlanEvent : public CampaignPlan +{ +public: + static const char* TYPENAME() { return "CampaignPlanEvent"; } + + CampaignPlanEvent(Campaign* c); + virtual ~CampaignPlanEvent(); + + // operations: + virtual void ExecFrame(); + virtual void SetLockout(int seconds); + + virtual bool ExecScriptedEvents(); + virtual bool ExecStatisticalEvents(); + +protected: + virtual void ProsecuteKills(CombatAction* action); + + virtual CombatAssignment* + ChooseAssignment(CombatGroup* c); + virtual bool CreateEvent(CombatAssignment* a); + + virtual CombatEvent* CreateEventDefend(CombatAssignment* a); + virtual CombatEvent* CreateEventFighterAssault(CombatAssignment* a); + virtual CombatEvent* CreateEventFighterStrike(CombatAssignment* a); + virtual CombatEvent* CreateEventFighterSweep(CombatAssignment* a); + virtual CombatEvent* CreateEventStarship(CombatAssignment* a); + + virtual bool IsFriendlyAssignment(CombatAssignment* a); + virtual bool Success(CombatAssignment* a); + virtual Text GetTeamName(CombatGroup* g); + + // attributes: + int event_time; +}; + +#endif CampaignPlanEvent_h + diff --git a/Stars45/CampaignPlanMission.cpp b/Stars45/CampaignPlanMission.cpp new file mode 100644 index 0000000..6cc228a --- /dev/null +++ b/Stars45/CampaignPlanMission.cpp @@ -0,0 +1,374 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2006. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CampaignPlanMission.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + CampaignPlanMission generates missions and mission + info for the player's combat group as part of a + dynamic campaign. +*/ + +#include "MemDebug.h" +#include "CampaignPlanMission.h" +#include "CampaignMissionRequest.h" +#include "CampaignMissionFighter.h" +#include "CampaignMissionStarship.h" +#include "Campaign.h" +#include "Combatant.h" +#include "CombatAction.h" +#include "CombatAssignment.h" +#include "CombatGroup.h" +#include "CombatUnit.h" +#include "CombatZone.h" +#include "Mission.h" +#include "StarSystem.h" +#include "Random.h" + + +// +--------------------------------------------------------------------+ + +void +CampaignPlanMission::ExecFrame() +{ + if (campaign && campaign->IsActive()) { + player_group = campaign->GetPlayerGroup(); + if (!player_group) return; + + int missionCount = campaign->GetMissionList().size(); + + if (missionCount > 0) { + // starships only get one mission to pick from: + if (player_group->IsStarshipGroup()) + return; + + // fighters get a maximum of five missions: + if (missionCount >= 5) + return; + + // otherwise, once every few seconds is plenty: + if (Campaign::Stardate() - exec_time < 1) + return; + } + + SelectStartTime(); + + if (player_group->IsFighterGroup()) { + slot++; + if (slot > 2) slot = 0; + + CampaignMissionRequest* request = PlanFighterMission(); + CampaignMissionFighter generator(campaign); + generator.CreateMission(request); + delete request; + } + + else if (player_group->IsStarshipGroup()) { + // starships should always check for campaign and strategic missions + slot = 0; + + CampaignMissionRequest* request = PlanStarshipMission(); + CampaignMissionStarship generator(campaign); + generator.CreateMission(request); + delete request; + } + + exec_time = Campaign::Stardate(); + } +} + +// +--------------------------------------------------------------------+ + +void +CampaignPlanMission::SelectStartTime() +{ + const int HOUR = 3600; // 60 minutes + const int MISSION_DELAY = 1800; // 30 minutes + double base_time = 0; + + List& info_list = campaign->GetMissionList(); + + if (info_list.size() > 0) { + MissionInfo* info = info_list[info_list.size()-1]; + base_time = info->start; + } + + if (base_time == 0) + base_time = campaign->GetTime() + MISSION_DELAY; + + start = (int) base_time + MISSION_DELAY; + start -= start % MISSION_DELAY; +} + +// +--------------------------------------------------------------------+ + +CampaignMissionRequest* +CampaignPlanMission::PlanStarshipMission() +{ + CampaignMissionRequest* request = 0; + + if (!request) request = PlanCampaignMission(); + if (!request) request = PlanStrategicMission(); + if (!request) request = PlanRandomStarshipMission(); + + return request; +} + +// +--------------------------------------------------------------------+ + +CampaignMissionRequest* +CampaignPlanMission::PlanFighterMission() +{ + CampaignMissionRequest* request = 0; + + if (!request) request = PlanCampaignMission(); + if (!request) request = PlanStrategicMission(); + if (!request) request = PlanRandomFighterMission(); + + return request; +} + +// +--------------------------------------------------------------------+ + +CampaignMissionRequest* +CampaignPlanMission::PlanCampaignMission() +{ + CampaignMissionRequest* request = 0; + + ListIter iter = campaign->GetActions(); + while (++iter && !request) { + CombatAction* action = iter.value(); + + if (action->Type() != CombatAction::MISSION_TEMPLATE) + continue; + + if (action->IsAvailable()) { + + // only fire each action once every two hours: + if (action->ExecTime() > 0 && campaign->GetTime() - action->ExecTime() < 7200) + continue; + + CombatGroup* g = campaign->FindGroup(action->GetIFF(), + action->AssetType(), + action->AssetId()); + + if (g && (g == player_group || + (player_group->Type() == CombatGroup::WING && + player_group->FindGroup(g->Type(), g->GetID())))) { + + request = new(__FILE__,__LINE__) + CampaignMissionRequest(campaign, + action->Subtype(), + start, + g); + + if (request) { + request->SetOpposingType(action->OpposingType()); + request->SetScript(action->GetText()); + } + + action->FireAction(); + } + } + } + + return request; +} + +// +--------------------------------------------------------------------+ + +CampaignMissionRequest* +CampaignPlanMission::PlanStrategicMission() +{ + CampaignMissionRequest* request = 0; + + if (slot > 1) + return request; + + // build list of assignments: + List assignments; + assignments.append(player_group->GetAssignments()); + + if (player_group->Type() == CombatGroup::WING) { + ListIter iter = player_group->GetComponents(); + while (++iter) { + CombatGroup* g = iter.value(); + assignments.append(g->GetAssignments()); + } + } + + // pick next assignment as basis for mission: + static int assignment_index = 0; + + if (assignments.size()) { + if (assignment_index >= assignments.size()) + assignment_index = 0; + + CombatAssignment* a = assignments[assignment_index++]; + + request = new(__FILE__,__LINE__) + CampaignMissionRequest(campaign, + a->Type(), + start, + a->GetResource()); + + if (request) + request->SetObjective(a->GetObjective()); + } + + return request; +} + +// +--------------------------------------------------------------------+ + +static int mission_type_index = -1; +static int mission_types[16] = { + Mission::PATROL, + Mission::PATROL, + Mission::ESCORT_FREIGHT, + Mission::PATROL, + Mission::ESCORT_FREIGHT, + Mission::PATROL, + Mission::ESCORT_FREIGHT, + Mission::ESCORT_FREIGHT, + Mission::PATROL, + Mission::ESCORT_FREIGHT, + Mission::PATROL, + Mission::ESCORT_FREIGHT, + Mission::PATROL, + Mission::PATROL, + Mission::ESCORT_FREIGHT, + Mission::PATROL +}; + +// +--------------------------------------------------------------------+ + +CampaignMissionRequest* +CampaignPlanMission::PlanRandomStarshipMission() +{ + int type = Mission::PATROL; + int r = RandomIndex(); + int ownside = player_group->GetIFF(); + + if (mission_type_index < 0) + mission_type_index = r; + + else if (mission_type_index >= 16) + mission_type_index = 0; + + type = mission_types[mission_type_index++]; + + if (type == Mission::ESCORT_FREIGHT) { + CombatGroup* freight = campaign->FindGroup(ownside, CombatGroup::FREIGHT); + if (!freight || freight->CountUnits() < 1) + type = Mission::PATROL; + } + + CampaignMissionRequest* request = 0; + request = new(__FILE__,__LINE__) + CampaignMissionRequest(campaign, type, start, player_group); + + return request; +} + +// +--------------------------------------------------------------------+ + +static int fighter_mission_index = 0; +static int fighter_mission_types[16] = { + Mission::PATROL, + Mission::SWEEP, + Mission::ESCORT_SHUTTLE, + Mission::AIR_PATROL, + Mission::SWEEP, + Mission::ESCORT_SHUTTLE, + Mission::PATROL, + Mission::PATROL, + Mission::AIR_SWEEP, + Mission::PATROL, + Mission::AIR_PATROL, + Mission::ESCORT_SHUTTLE, + Mission::PATROL, + Mission::SWEEP, + Mission::PATROL, + Mission::AIR_SWEEP +}; + +CampaignMissionRequest* +CampaignPlanMission::PlanRandomFighterMission() +{ + CampaignMissionRequest* request = 0; + int type = fighter_mission_types[fighter_mission_index++]; + int ownside = player_group->GetIFF(); + CombatGroup* primary = player_group; + CombatGroup* obj = 0; + + if (fighter_mission_index > 15) + fighter_mission_index = 0; + + if (type == Mission::ESCORT_FREIGHT) { + CombatGroup* freight = campaign->FindGroup(ownside, CombatGroup::FREIGHT); + if (!freight || freight->CalcValue() < 1) + type = Mission::PATROL; + else + obj = freight; + } + + else if (type == Mission::ESCORT_SHUTTLE) { + CombatGroup* shuttle = campaign->FindGroup(ownside, CombatGroup::LCA_SQUADRON); + if (!shuttle || shuttle->CalcValue() < 1) + type = Mission::PATROL; + else + obj = shuttle; + } + + else if (primary->Type() == CombatGroup::WING) { + if (RandomChance()) + primary = primary->FindGroup(CombatGroup::INTERCEPT_SQUADRON); + else + primary = primary->FindGroup(CombatGroup::FIGHTER_SQUADRON); + } + + if (type >= Mission::AIR_PATROL && type <= Mission::AIR_INTERCEPT) { + CombatZone* zone = 0; + bool airborne = false; + + if (primary) + zone = primary->GetAssignedZone(); + + if (zone && zone->GetRegions().size() > 1) { + Text air_region = *zone->GetRegions().at(1); + StarSystem* system = campaign->GetSystem(zone->System()); + + if (system) { + OrbitalRegion* rgn = system->FindRegion(air_region); + + if (rgn && rgn->Type() == Orbital::TERRAIN) + airborne = true; + } + } + + if (!airborne) { + if (type == Mission::AIR_INTERCEPT) + type = Mission::INTERCEPT; + + else if (type == Mission::AIR_SWEEP) + type = Mission::SWEEP; + + else + type = Mission::PATROL; + } + } + + request = new(__FILE__,__LINE__) + CampaignMissionRequest(campaign, type, start, primary); + + if (request) + request->SetObjective(obj); + + return request; +} diff --git a/Stars45/CampaignPlanMission.h b/Stars45/CampaignPlanMission.h new file mode 100644 index 0000000..4496ce6 --- /dev/null +++ b/Stars45/CampaignPlanMission.h @@ -0,0 +1,58 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CampaignPlanMission.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + CampaignPlanMission generates missions and mission + info for the player's combat group as part of a + dynamic campaign. +*/ + +#ifndef CampaignPlanMission_h +#define CampaignPlanMission_h + +#include "Types.h" +#include "CampaignPlan.h" + +// +--------------------------------------------------------------------+ + +class CampaignMissionRequest; +class CombatGroup; +class CombatUnit; +class CombatZone; + +// +--------------------------------------------------------------------+ + +class CampaignPlanMission : public CampaignPlan +{ +public: + static const char* TYPENAME() { return "CampaignPlanMission"; } + + CampaignPlanMission(Campaign* c) : CampaignPlan(c), start(0), slot(0) { } + virtual ~CampaignPlanMission() { } + + // operations: + virtual void ExecFrame(); + +protected: + virtual void SelectStartTime(); + virtual CampaignMissionRequest* PlanCampaignMission(); + virtual CampaignMissionRequest* PlanStrategicMission(); + virtual CampaignMissionRequest* PlanRandomStarshipMission(); + virtual CampaignMissionRequest* PlanRandomFighterMission(); + virtual CampaignMissionRequest* PlanStarshipMission(); + virtual CampaignMissionRequest* PlanFighterMission(); + + CombatGroup* player_group; + int start; + int slot; +}; + +#endif CampaignPlanMission_h + diff --git a/Stars45/CampaignPlanMovement.cpp b/Stars45/CampaignPlanMovement.cpp new file mode 100644 index 0000000..dc08798 --- /dev/null +++ b/Stars45/CampaignPlanMovement.cpp @@ -0,0 +1,168 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CampaignPlanMovement.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + CampaignPlanMovement simulates random patrol movements + of starship groups between missions. +*/ + +#include "MemDebug.h" +#include "CampaignPlanMovement.h" +#include "Campaign.h" +#include "Combatant.h" +#include "CombatGroup.h" +#include "CombatUnit.h" +#include "CombatZone.h" +#include "Random.h" +#include "ShipDesign.h" + +// +--------------------------------------------------------------------+ + +void +CampaignPlanMovement::ExecFrame() +{ + if (campaign && campaign->IsActive()) { + if (Campaign::Stardate() - exec_time < 7200) + return; + + campaign->GetAllCombatUnits(-1, all_units); + + ListIter iter = all_units; + while (++iter) { + CombatUnit* u = iter.value(); + + if (u->IsStarship() && !u->IsStatic()) + MoveUnit(u); + } + + all_units.clear(); + + exec_time = Campaign::Stardate(); + } +} + +// +--------------------------------------------------------------------+ + +void +CampaignPlanMovement::MoveUnit(CombatUnit* u) +{ + if (u) { + // starship repair: + double damage = u->GetSustainedDamage(); + + if (damage > 0 && u->GetDesign()) { + int percent = (int) (100 * damage / u->GetDesign()->integrity); + + if (percent > 50) { + u->SetSustainedDamage(0.90 * damage); + } + } + + Point loc = u->Location(); + Point dir = loc; + double dist = dir.Normalize(); + + const double MAX_RAD = 320e3; + const double MIN_DIST = 150e3; + + if (dist < MAX_RAD) { + double scale = 1 - dist/MAX_RAD; + + loc += dir * (Random(30e3, 90e3) * scale) + RandomDirection() * 10e3; + + if (fabs(loc.z) > 20e3) + loc.z *= 0.1; + + u->MoveTo(loc); + + CombatGroup* g = u->GetCombatGroup(); + if (g && g->Type() > CombatGroup::FLEET && g->GetFirstUnit() == u) { + g->MoveTo(loc); + + if (g->IntelLevel() > Intel::KNOWN) + g->SetIntelLevel(Intel::KNOWN); + } + } + + else if (dist > 1.25 * MAX_RAD) { + double scale = 1 - dist/MAX_RAD; + + loc += dir * (Random(80e3, 120e3) * scale) + RandomDirection() * 3e3; + + if (fabs(loc.z) > 20e3) + loc.z *= 0.1; + + u->MoveTo(loc); + + CombatGroup* g = u->GetCombatGroup(); + if (g && g->Type() > CombatGroup::FLEET && g->GetFirstUnit() == u) { + g->MoveTo(loc); + + if (g->IntelLevel() > Intel::KNOWN) + g->SetIntelLevel(Intel::KNOWN); + } + } + + else { + loc += RandomDirection() * 30e3; + + if (fabs(loc.z) > 20e3) + loc.z *= 0.1; + + u->MoveTo(loc); + + CombatGroup* g = u->GetCombatGroup(); + if (g && g->Type() > CombatGroup::FLEET && g->GetFirstUnit() == u) { + g->MoveTo(loc); + + if (g->IntelLevel() > Intel::KNOWN) + g->SetIntelLevel(Intel::KNOWN); + } + } + + CombatUnit* closest_unit = 0; + double closest_dist = 1e6; + + ListIter iter = all_units; + while (++iter) { + CombatUnit* unit = iter.value(); + + if (unit->GetCombatGroup() != u->GetCombatGroup() && unit->GetRegion() == u->GetRegion() && !unit->IsDropship()) { + Point delta = loc - unit->Location(); + double dist = delta.Normalize(); + + if (dist < closest_dist) { + closest_unit = unit; + closest_dist = dist; + } + } + } + + if (closest_unit && closest_dist < MIN_DIST) { + Point delta = loc - closest_unit->Location(); + double dist = delta.Normalize(); + + loc += delta * 1.1 * (MIN_DIST - closest_dist); + + if (fabs(loc.z) > 20e3) + loc.z *= 0.1; + + u->MoveTo(loc); + + CombatGroup* g = u->GetCombatGroup(); + if (g && g->Type() > CombatGroup::FLEET && g->GetFirstUnit() == u) { + g->MoveTo(loc); + + if (g->IntelLevel() > Intel::KNOWN) + g->SetIntelLevel(Intel::KNOWN); + } + } + } +} diff --git a/Stars45/CampaignPlanMovement.h b/Stars45/CampaignPlanMovement.h new file mode 100644 index 0000000..e2d94ad --- /dev/null +++ b/Stars45/CampaignPlanMovement.h @@ -0,0 +1,44 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CampaignPlanMovement.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + CampaignPlanMovement simulates random patrol movements + of starship groups between missions. This agitation + keeps the ships from bunching up in the middle of a + sector. +*/ + +#ifndef CampaignPlanMovement_h +#define CampaignPlanMovement_h + +#include "Types.h" +#include "CampaignPlan.h" + +// +--------------------------------------------------------------------+ + +class CampaignPlanMovement : public CampaignPlan +{ +public: + static const char* TYPENAME() { return "CampaignPlanMovement"; } + + CampaignPlanMovement(Campaign* c) : CampaignPlan(c) { } + virtual ~CampaignPlanMovement() { } + + // operations: + virtual void ExecFrame(); + +protected: + void MoveUnit(CombatUnit* u); + + List all_units; +}; + +#endif CampaignPlanMovement_h + diff --git a/Stars45/CampaignPlanStrategic.cpp b/Stars45/CampaignPlanStrategic.cpp new file mode 100644 index 0000000..34c1922 --- /dev/null +++ b/Stars45/CampaignPlanStrategic.cpp @@ -0,0 +1,483 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CampaignPlanStrategic.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + CampaignPlanStrategic prioritizes targets and defensible + allied forces as the first step in force tasking. +*/ + +#include "MemDebug.h" +#include "CampaignPlanStrategic.h" +#include "Campaign.h" +#include "Combatant.h" +#include "CombatGroup.h" +#include "CombatUnit.h" +#include "CombatZone.h" +#include "Random.h" + +// +--------------------------------------------------------------------+ + +void +CampaignPlanStrategic::ExecFrame() +{ + if (campaign && campaign->IsActive()) { + if (Campaign::Stardate() - exec_time < 300) + return; + + ListIter zone = campaign->GetZones(); + while (++zone) + zone->Clear(); + + ListIter iter = campaign->GetCombatants(); + while (++iter) { + Combatant* c = iter.value(); + CombatGroup* force = c->GetForce(); + + force->CalcValue(); + + PlaceGroup(force); + ScoreCombatant(c); + ScoreNeeds(c); + + force->ClearUnlockedZones(); + AssignZones(c); + ResolveZoneMovement(force); + } + + exec_time = Campaign::Stardate(); + } +} + +// +--------------------------------------------------------------------+ + +void +CampaignPlanStrategic::PlaceGroup(CombatGroup* g) +{ + if (!g) + return; + + Text rgn = g->GetRegion(); + CombatZone* zone = campaign->GetZone(rgn); + + // if we couldn't find anything suitable, + // just pick a zone at random: + if (!zone && g->IsMovable()) { + int nzones = campaign->GetZones().size(); + int n = RandomIndex() % nzones; + zone = campaign->GetZones().at(n); + + Text assigned_rgn; + if (!campaign->GetZone(rgn)) { + assigned_rgn = *zone->GetRegions().at(0); + g->AssignRegion(assigned_rgn); + } + } + + if (zone && !zone->HasGroup(g)) + zone->AddGroup(g); + + ListIter iter = g->GetComponents(); + while (++iter) + PlaceGroup(iter.value()); +} + +// +--------------------------------------------------------------------+ + +void +CampaignPlanStrategic::ScoreCombatant(Combatant* c) +{ + // prep lists: + c->GetDefendList().clear(); + c->GetTargetList().clear(); + + ScoreDefensible(c); + + ListIter iter = campaign->GetCombatants(); + while (++iter) { + if (iter->GetIFF() > 0 && iter->GetIFF() != c->GetIFF()) + ScoreTargets(c, iter.value()); + } + + // sort lists: + c->GetDefendList().sort(); + c->GetTargetList().sort(); +} + +// +--------------------------------------------------------------------+ + +void +CampaignPlanStrategic::ScoreDefensible(Combatant* c) +{ + if (c->GetForce()) + ScoreDefend(c, c->GetForce()); +} + +void +CampaignPlanStrategic::ScoreDefend(Combatant* c, CombatGroup* g) +{ + if (!g || g->IsReserve()) + return; + + if (g->IsDefensible()) { + g->SetPlanValue(g->Value()); + c->GetDefendList().append(g); + + CombatZone* zone = campaign->GetZone(g->GetRegion()); + ZoneForce* force = 0; + + if (zone) + force = zone->FindForce(c->GetIFF()); + + if (force) + force->GetDefendList().append(g); + } + + ListIter iter = g->GetComponents(); + while (++iter) { + ScoreDefend(c, iter.value()); + } +} + +// +--------------------------------------------------------------------+ + +void +CampaignPlanStrategic::ScoreTargets(Combatant* c, Combatant* t) +{ + if (t->GetForce()) + ScoreTarget(c, t->GetForce()); +} + +void +CampaignPlanStrategic::ScoreTarget(Combatant* c, CombatGroup* g) +{ + if (!g || g->IntelLevel() <= Intel::SECRET) + return; + + if (g->IsTargetable()) { + g->SetPlanValue(g->Value() * c->GetTargetStratFactor(g->Type())); + c->GetTargetList().append(g); + + CombatZone* zone = campaign->GetZone(g->GetRegion()); + ZoneForce* force = 0; + + if (zone) + force = zone->FindForce(c->GetIFF()); + + if (force) + force->GetTargetList().append(g); + } + + ListIter iter = g->GetComponents(); + while (++iter) { + ScoreTarget(c, iter.value()); + } +} + +// +--------------------------------------------------------------------+ + +void +CampaignPlanStrategic::ScoreNeeds(Combatant* c) +{ + ListIter zone = campaign->GetZones(); + while (++zone) { + ZoneForce* force = zone->FindForce(c->GetIFF()); + + // clear needs: + force->SetNeed(CombatGroup::CARRIER_GROUP, 0); + force->SetNeed(CombatGroup::BATTLE_GROUP, 0); + force->SetNeed(CombatGroup::DESTROYER_SQUADRON, 0); + force->SetNeed(CombatGroup::ATTACK_SQUADRON, 0); + force->SetNeed(CombatGroup::FIGHTER_SQUADRON, 0); + force->SetNeed(CombatGroup::INTERCEPT_SQUADRON, 0); + + // what defensive assets are needed in this zone? + ListIter def = force->GetDefendList(); + while (++def) { + int defender_type = *CombatGroup::PreferredDefender(def->Type()); + force->AddNeed(defender_type, def->Value()); + } + + // what offensive assets are needed in this zone? + ListIter tgt = force->GetTargetList(); + while (++tgt) { + int attacker_type = *CombatGroup::PreferredAttacker(tgt->Type()); + force->AddNeed(attacker_type, tgt->Value()); + } + } +} + +// +--------------------------------------------------------------------+ + +void +CampaignPlanStrategic::BuildGroupList(CombatGroup* g, List& groups) +{ + if (!g || g->IsReserve()) + return; + + if (g->IsAssignable()) + groups.append(g); + + ListIter iter = g->GetComponents(); + while (++iter) + BuildGroupList(iter.value(), groups); +} + +// +--------------------------------------------------------------------+ + +void +CampaignPlanStrategic::AssignZones(Combatant* c) +{ + // find the list of assignable groups, in priority order: + List groups; + BuildGroupList(c->GetForce(), groups); + groups.sort(); + + // for each group, assign a zone: + ListIter g_iter = groups; + + // first pass: fighter and attack squadrons assigned to star bases + while (++g_iter) { + CombatGroup* g = g_iter.value(); + int gtype = g->Type(); + + if (gtype == CombatGroup::ATTACK_SQUADRON || + gtype == CombatGroup::FIGHTER_SQUADRON || + gtype == CombatGroup::INTERCEPT_SQUADRON) { + CombatGroup* parent = g->GetParent(); + + if (parent && parent->Type() == CombatGroup::WING) + parent = parent->GetParent(); + + if (!parent || parent->Type() == CombatGroup::CARRIER_GROUP) + continue; + + // these groups are attached to fixed resources, + // so they must be assigned to the parent's zone: + CombatZone* parent_zone = campaign->GetZone(parent->GetRegion()); + + if (parent_zone) { + ZoneForce* parent_force = parent_zone->FindForce(g->GetIFF()); + + if (parent_force) { + g->SetAssignedZone(parent_zone); + parent_force->AddNeed(g->Type(), -(g->Value())); + } + } + } + } + + // second pass: carrier groups + g_iter.reset(); + while (++g_iter) { + CombatGroup* g = g_iter.value(); + int gtype = g->Type(); + + if (gtype == CombatGroup::CARRIER_GROUP) { + int current_zone_need = 0; + int highest_zone_need = 0; + CombatZone* highest_zone = 0; + ZoneForce* highest_force = 0; + CombatZone* current_zone = 0; + ZoneForce* current_force = 0; + + List possible_zones; + + if (g->IsZoneLocked()) { + current_zone = g->GetAssignedZone(); + current_force = current_zone->FindForce(g->GetIFF()); + } + + else { + ListIter z_iter = campaign->GetZones(); + while (++z_iter) { + CombatZone* zone = z_iter.value(); + ZoneForce* force = zone->FindForce(g->GetIFF()); + int need = force->GetNeed(CombatGroup::CARRIER_GROUP) + + force->GetNeed(CombatGroup::ATTACK_SQUADRON) + + force->GetNeed(CombatGroup::FIGHTER_SQUADRON) + + force->GetNeed(CombatGroup::INTERCEPT_SQUADRON); + + if (g->IsSystemLocked() && zone->System() != g->GetAssignedSystem()) + continue; + + possible_zones.append(zone); + + if (zone->HasRegion(g->GetRegion())) { + current_zone_need = need; + current_zone = zone; + current_force = force; + } + + if (need > highest_zone_need) { + highest_zone_need = need; + highest_zone = zone; + highest_force = force; + } + } + } + + CombatZone* assigned_zone = current_zone; + ZoneForce* assigned_force = current_force; + + if (highest_zone_need > current_zone_need) { + assigned_zone = highest_zone; + assigned_force = highest_force; + } + + // if we couldn't find anything suitable, + // just pick a zone at random: + if (!assigned_zone) { + if (possible_zones.isEmpty()) + possible_zones.append(campaign->GetZones()); + + int nzones = possible_zones.size(); + int n = RandomIndex() % nzones; + + assigned_zone = possible_zones.at(n); + assigned_force = assigned_zone->FindForce(g->GetIFF()); + } + + if (assigned_force && assigned_zone) { + Text assigned_rgn; + if (!campaign->GetZone(g->GetRegion())) { + assigned_rgn = *assigned_zone->GetRegions().at(0); + g->AssignRegion(assigned_rgn); + } + + g->SetAssignedZone(assigned_zone); + assigned_force->AddNeed(g->Type(), -(g->Value())); + + // also assign the carrier's wing and squadrons to the same zone: + ListIter squadron = g->GetComponents(); + while (++squadron) { + squadron->SetAssignedZone(assigned_zone); + assigned_force->AddNeed(squadron->Type(), -(squadron->Value())); + + if (squadron->Type() == CombatGroup::WING) { + ListIter s = squadron->GetComponents(); + while (++s) { + s->SetAssignedZone(assigned_zone); + assigned_force->AddNeed(s->Type(), -(s->Value())); + } + } + } + } + } + } + + // third pass: everything else + g_iter.reset(); + while (++g_iter) { + CombatGroup* g = g_iter.value(); + int gtype = g->Type(); + + if (gtype == CombatGroup::BATTLE_GROUP || gtype == CombatGroup::DESTROYER_SQUADRON) { + int current_zone_need = 0; + int highest_zone_need = 0; + CombatZone* highest_zone = 0; + ZoneForce* highest_force = 0; + CombatZone* current_zone = 0; + ZoneForce* current_force = 0; + + List possible_zones; + + if (g->IsZoneLocked()) { + current_zone = g->GetAssignedZone(); + current_force = current_zone->FindForce(g->GetIFF()); + } + + else { + ListIter z_iter = campaign->GetZones(); + while (++z_iter) { + CombatZone* zone = z_iter.value(); + ZoneForce* force = zone->FindForce(g->GetIFF()); + int need = force->GetNeed(g->Type()); + + if (g->IsSystemLocked() && zone->System() != g->GetAssignedSystem()) + continue; + + possible_zones.append(zone); + + // battle groups can do double-duty: + if (gtype == CombatGroup::BATTLE_GROUP) + need += force->GetNeed(CombatGroup::DESTROYER_SQUADRON); + + if (zone->HasRegion(g->GetRegion())) { + current_zone_need = need; + current_zone = zone; + current_force = force; + } + + if (need > highest_zone_need) { + highest_zone_need = need; + highest_zone = zone; + highest_force = force; + } + } + } + + if (highest_zone_need > current_zone_need) { + g->SetAssignedZone(highest_zone); + + if (highest_force) + highest_force->AddNeed(g->Type(), -(g->Value())); + } + else { + if (!current_zone) { + if (possible_zones.isEmpty()) + possible_zones.append(campaign->GetZones()); + + int nzones = possible_zones.size(); + int n = RandomIndex() % nzones; + + current_zone = possible_zones.at(n); + current_force = current_zone->FindForce(g->GetIFF()); + } + + g->SetAssignedZone(current_zone); + + if (current_force) + current_force->AddNeed(g->Type(), -(g->Value())); + + Text assigned_rgn; + if (!campaign->GetZone(g->GetRegion())) { + assigned_rgn = *current_zone->GetRegions().at(0); + g->AssignRegion(assigned_rgn); + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +CampaignPlanStrategic::ResolveZoneMovement(CombatGroup* g) +{ + CombatZone* zone = g->GetAssignedZone(); + bool move = false; + + if (zone && !zone->HasRegion(g->GetRegion())) { + move = true; + CombatZone* old_zone = g->GetCurrentZone(); + if (old_zone) + old_zone->RemoveGroup(g); + zone->AddGroup(g); + } + + ListIter comp = g->GetComponents(); + while (++comp) + ResolveZoneMovement(comp.value()); + + // assign region last, to allow components to + // resolve their zones: + if (zone && move) + g->AssignRegion(*zone->GetRegions().at(0)); +} diff --git a/Stars45/CampaignPlanStrategic.h b/Stars45/CampaignPlanStrategic.h new file mode 100644 index 0000000..b5376f8 --- /dev/null +++ b/Stars45/CampaignPlanStrategic.h @@ -0,0 +1,59 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CampaignPlanStrategic.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + CampaignPlanStrategic prioritizes targets and defensible + allied forces as the first step in force tasking. This + algorithm computes which enemy resources are most important + to attack, based on the AI value of each combat group, and + strategic weighting factors that help shape the strategy + to the objectives for the current campaign. +*/ + +#ifndef CampaignPlanStrategic_h +#define CampaignPlanStrategic_h + +#include "Types.h" +#include "CampaignPlan.h" + +// +--------------------------------------------------------------------+ + +class CampaignPlanStrategic : public CampaignPlan +{ +public: + static const char* TYPENAME() { return "CampaignPlanStrategic"; } + + CampaignPlanStrategic(Campaign* c) : CampaignPlan(c) { } + virtual ~CampaignPlanStrategic() { } + + // operations: + virtual void ExecFrame(); + +protected: + void PlaceGroup(CombatGroup* g); + + void ScoreCombatant(Combatant* c); + + void ScoreDefensible(Combatant* c); + void ScoreDefend(Combatant* c, CombatGroup* g); + + void ScoreTargets(Combatant* c, Combatant* t); + void ScoreTarget(Combatant* c, CombatGroup* g); + + void ScoreNeeds(Combatant* c); + + // zone alocation: + void BuildGroupList(CombatGroup* g, List& groups); + void AssignZones(Combatant* c); + void ResolveZoneMovement(CombatGroup* g); +}; + +#endif CampaignPlanStrategic_h + diff --git a/Stars45/CampaignSaveGame.cpp b/Stars45/CampaignSaveGame.cpp new file mode 100644 index 0000000..5584f8f --- /dev/null +++ b/Stars45/CampaignSaveGame.cpp @@ -0,0 +1,729 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CampaignSaveGame.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + CampaignSaveGame contains the logic needed to save and load + campaign games in progress. +*/ + +#include "MemDebug.h" +#include "CampaignSaveGame.h" +#include "Campaign.h" +#include "Combatant.h" +#include "CombatAction.h" +#include "CombatEvent.h" +#include "CombatGroup.h" +#include "CombatUnit.h" +#include "CombatZone.h" +#include "Galaxy.h" +#include "Mission.h" +#include "StarSystem.h" +#include "Player.h" + +#include "Game.h" +#include "DataLoader.h" +#include "ParseUtil.h" +#include "FormatUtil.h" + +static const char* SAVE_DIR = "SaveGame"; + +// +--------------------------------------------------------------------+ + +CampaignSaveGame::CampaignSaveGame(Campaign* c) + : campaign(c) +{ } + +// +--------------------------------------------------------------------+ + +CampaignSaveGame::~CampaignSaveGame() +{ } + +// +--------------------------------------------------------------------+ + +Text +CampaignSaveGame::GetSaveDirectory() +{ + return GetSaveDirectory(Player::GetCurrentPlayer()); +} + +Text +CampaignSaveGame::GetSaveDirectory(Player* player) +{ + if (player) { + char save_dir[32]; + sprintf(save_dir, "%s/%02d", SAVE_DIR, player->Identity()); + + return save_dir; + } + + return SAVE_DIR; +} + +void +CampaignSaveGame::CreateSaveDirectory() +{ + HANDLE hDir = CreateFile(SAVE_DIR, 0, 0, 0, OPEN_EXISTING, 0, 0); + + if (hDir == INVALID_HANDLE_VALUE) + CreateDirectory(SAVE_DIR, NULL); + else + CloseHandle(hDir); + + hDir = CreateFile(GetSaveDirectory(), 0, 0, 0, OPEN_EXISTING, 0, 0); + + if (hDir == INVALID_HANDLE_VALUE) + CreateDirectory(GetSaveDirectory(), NULL); + else + CloseHandle(hDir); +} + +// +--------------------------------------------------------------------+ + +static char multiline[4096]; +static char* FormatMultiLine(const char* s) +{ + int i = 4095; + char* p = multiline; + + while (*s && i > 0) { + if (*s == '\n') { + *p++ = '\\'; + *p++ = 'n'; + s++; + i -= 2; + } + else if (*s == '"') { + *p++ = '\\'; + *p++ = '"'; + s++; + i -= 2; + } + else { + *p++ = *s++; + i--; + } + } + + *p = 0; + return multiline; +} + +static char* ParseMultiLine(const char* s) +{ + int i = 4095; + char* p = multiline; + + while (*s && i > 0) { + if (*s == '\\') { + s++; + if (*s == 'n') { + *p++ = '\n'; + *s++; + i--; + } + else if (*s == '"') { + *p++ = '"'; + *s++; + i--; + } + else { + *p++ = *s++; + i--; + } + } + else { + *p++ = *s++; + i--; + } + } + + *p = 0; + return multiline; +} + +void +CampaignSaveGame::Load(const char* filename) +{ + Print("-------------------------\nLOADING SAVEGAME (%s).\n", filename); + campaign = 0; + + if (!filename || !filename[0]) return; + + DataLoader* loader = DataLoader::GetLoader(); + bool use_file_sys = loader->IsFileSystemEnabled(); + loader->UseFileSystem(true); + loader->SetDataPath(GetSaveDirectory() + "/"); + + BYTE* block; + loader->LoadBuffer(filename, block, true); + loader->UseFileSystem(use_file_sys); + + Sleep(10); + + Parser parser(new(__FILE__,__LINE__) BlockReader((const char*) block)); + Term* term = parser.ParseTerm(); + + if (!term) { + Print("ERROR: could not parse save game '%s'\n", filename); + loader->SetDataPath(0); + return; + } + else { + TermText* file_type = term->isText(); + if (!file_type || file_type->value() != "SAVEGAME") { + Print("ERROR: invalid save game file '%s'\n", filename); + term->print(10); + loader->SetDataPath(0); + delete term; + return; + } + } + + int grp_iff = 0; + int grp_type = 0; + int grp_id = 0; + int status = 0; + double baseTime = 0; + double time = 0; + Text unit; + Text sitrep; + Text orders; + + do { + Sleep(5); + delete term; term = 0; + term = parser.ParseTerm(); + + if (term) { + TermDef* def = term->isDef(); + if (def) { + if (def->name()->value() == "campaign") { + Text cname; + int cid=0; + + if (def->term()) { + if (def->term()->isText()) + cname = def->term()->isText()->value(); + else if (def->term()->isNumber()) + cid = (int) def->term()->isNumber()->value(); + } + + if (!campaign) { + List& list = Campaign::GetAllCampaigns(); + + for (int i = 0; i < list.size() && !campaign; i++) { + Campaign* c = list.at(i); + + if (cname == c->Name() || cid == c->GetCampaignId()) { + campaign = c; + campaign->Load(); + campaign->Prep(); // restore campaign to pristine state + + loader->SetDataPath(GetSaveDirectory() + "/"); + } + } + } + } + + else if (def->name()->value() == "grp_iff") { + GetDefNumber(grp_iff, def, filename); + } + + else if (def->name()->value() == "grp_type") { + GetDefNumber(grp_type, def, filename); + } + + else if (def->name()->value() == "grp_id") { + GetDefNumber(grp_id, def, filename); + } + + else if (def->name()->value() == "unit") { + GetDefText(unit, def, filename); + } + + else if (def->name()->value() == "status") { + GetDefNumber(status, def, filename); + } + + else if (def->name()->value() == "basetime") { + GetDefNumber(baseTime, def, filename); + } + + else if (def->name()->value() == "time") { + GetDefNumber(time, def, filename); + } + + else if (def->name()->value() == "sitrep") { + GetDefText(sitrep, def, filename); + } + + else if (def->name()->value() == "orders") { + GetDefText(orders, def, filename); + } + + else if (def->name()->value() == "combatant") { + if (!def->term() || !def->term()->isStruct()) { + ::Print("WARNING: combatant struct missing in '%s/%s'\n", loader->GetDataPath(), filename); + } + else { + TermStruct* val = def->term()->isStruct(); + + char name[64]; + int iff = 0; + int score = 0; + + ZeroMemory(name, sizeof(name)); + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + if (pdef->name()->value() == "name") + GetDefText(name, pdef, filename); + + else if (pdef->name()->value() == "iff") { + GetDefNumber(iff, pdef, filename); + } + + else if (pdef->name()->value() == "score") { + GetDefNumber(score, pdef, filename); + } + } + } + + if (campaign && name[0]) { + Combatant* combatant = campaign->GetCombatant(name); + + if (combatant) { + CombatGroup::MergeOrderOfBattle(block, filename, iff, combatant, campaign); + combatant->SetScore(score); + } + else { + ::Print("WARNING: could not find combatant '%s' in campaign.\n", name); + } + } + } + } + else if (def->name()->value() == "event") { + if (!def->term() || !def->term()->isStruct()) { + ::Print("WARNING: event struct missing in '%s/%s'\n", loader->GetDataPath(), filename); + } + else { + TermStruct* val = def->term()->isStruct(); + + char type[64]; + char source[64]; + char region[64]; + char title[256]; + char file[256]; + char image[256]; + char scene[256]; + char info[4096]; + int time = 0; + int team = 0; + int points = 0; + + type[0] = 0; + info[0] = 0; + file[0] = 0; + image[0] = 0; + scene[0] = 0; + title[0] = 0; + region[0] = 0; + source[0] = 0; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + if (pdef->name()->value() == "type") + GetDefText(type, pdef, filename); + + else if (pdef->name()->value() == "source") + GetDefText(source, pdef, filename); + + else if (pdef->name()->value() == "region") + GetDefText(region, pdef, filename); + + else if (pdef->name()->value() == "title") + GetDefText(title, pdef, filename); + + else if (pdef->name()->value() == "file") + GetDefText(file, pdef, filename); + + else if (pdef->name()->value() == "image") + GetDefText(image, pdef, filename); + + else if (pdef->name()->value() == "scene") + GetDefText(scene, pdef, filename); + + else if (pdef->name()->value() == "info") + GetDefText(info, pdef, filename); + + else if (pdef->name()->value() == "time") + GetDefNumber(time, pdef, filename); + + else if (pdef->name()->value() == "team") + GetDefNumber(team, pdef, filename); + + else if (pdef->name()->value() == "points") + GetDefNumber(points, pdef, filename); + } + } + + if (campaign && type[0]) { + loader->SetDataPath(campaign->Path()); + + CombatEvent* event = new(__FILE__,__LINE__) + CombatEvent(campaign, + CombatEvent::TypeFromName(type), + time, + team, + CombatEvent::SourceFromName(source), + region); + + if (event) { + event->SetTitle(title); + event->SetFilename(file); + event->SetImageFile(image); + event->SetSceneFile(scene); + event->Load(); + + if (info[0]) + event->SetInformation(ParseMultiLine(info)); + + event->SetVisited(true); + campaign->GetEvents().append(event); + } + } + } + } + else if (def->name()->value() == "action") { + if (!def->term() || !def->term()->isStruct()) { + ::Print("WARNING: action struct missing in '%s/%s'\n", loader->GetDataPath(), filename); + } + else { + TermStruct* val = def->term()->isStruct(); + + int id = -1; + int stat = 0; + int count = 0; + int after = 0; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + if (pdef->name()->value() == "id") + GetDefNumber(id, pdef, filename); + + else if (pdef->name()->value() == "stat") + GetDefNumber(stat, pdef, filename); + + else if (pdef->name()->value() == "count") + GetDefNumber(count, pdef, filename); + + else if (pdef->name()->value().contains("after")) + GetDefNumber(after, pdef, filename); + } + } + + if (campaign && id >= 0) { + ListIter a_iter = campaign->GetActions(); + while (++a_iter) { + CombatAction* a = a_iter.value(); + if (a->Identity() == id) { + a->SetStatus(stat); + + if (count) + a->SetCount(count); + + if (after) + a->SetStartAfter(after); + + break; + } + } + } + } + } + } + } + } + while (term); + + if (term) { + delete term; + term = 0; + } + + if (campaign) { + campaign->SetSaveGame(true); + + List& list = Campaign::GetAllCampaigns(); + + if (status < Campaign::CAMPAIGN_SUCCESS) { + campaign->SetStatus(status); + if (sitrep.length()) campaign->SetSituation(sitrep); + if (orders.length()) campaign->SetOrders(orders); + campaign->SetStartTime(baseTime); + campaign->SetLoadTime(baseTime + time); + campaign->LockoutEvents(3600); + campaign->Start(); + + if (grp_type >= CombatGroup::FLEET && grp_type <= CombatGroup::PRIVATE) { + CombatGroup* player_group = campaign->FindGroup(grp_iff, grp_type, grp_id); + if (player_group) { + CombatUnit* player_unit = 0; + + if (unit.length()) + player_unit = player_group->FindUnit(unit); + + if (player_unit) + campaign->SetPlayerUnit(player_unit); + else + campaign->SetPlayerGroup(player_group); + } + } + } + + // failed - restart current campaign: + else if (status == Campaign::CAMPAIGN_FAILED) { + Print("CampaignSaveGame: Loading FAILED campaign, restarting '%s'\n", + campaign->Name()); + + campaign->Load(); + campaign->Prep(); // restore campaign to pristine state + campaign->SetSaveGame(false); + + loader->SetDataPath(GetSaveDirectory() + "/"); + } + + // start next campaign: + else if (status == Campaign::CAMPAIGN_SUCCESS) { + Print("CampaignSaveGame: Loading COMPLETED campaign '%s', searching for next campaign...\n", + campaign->Name()); + + bool found = false; + + for (int i = 0; i < list.size() && !found; i++) { + Campaign* c = list.at(i); + + if (c->GetCampaignId() == campaign->GetCampaignId()+1) { + campaign = c; + campaign->Load(); + campaign->Prep(); // restore campaign to pristine state + + Print("Advanced to campaign %d '%s'\n", + campaign->GetCampaignId(), + campaign->Name()); + + loader->SetDataPath(GetSaveDirectory() + "/"); + found = true; + } + } + + // if no next campaign found, start over from the beginning: + for (i = 0; i < list.size() && !found; i++) { + Campaign* c = list.at(i); + + if (c->IsDynamic()) { + campaign = c; + campaign->Load(); + campaign->Prep(); // restore campaign to pristine state + + Print("Completed full series, restarting at %d '%s'\n", + campaign->GetCampaignId(), + campaign->Name()); + + loader->SetDataPath(GetSaveDirectory() + "/"); + found = true; + } + } + } + } + + loader->ReleaseBuffer(block); + loader->SetDataPath(0); + Print("SAVEGAME LOADED (%s).\n\n", filename); +} + +// +--------------------------------------------------------------------+ + +void +CampaignSaveGame::Save(const char* name) +{ + if (!campaign) return; + + CreateSaveDirectory(); + + Text s = GetSaveDirectory() + Text("/") + Text(name); + + FILE* f = fopen(s, "w"); + if (f) { + char timestr[32]; + FormatDayTime(timestr, campaign->GetTime()); + + CombatGroup* player_group = campaign->GetPlayerGroup(); + CombatUnit* player_unit = campaign->GetPlayerUnit(); + + fprintf(f, "SAVEGAME\n\n"); + fprintf(f, "campaign: \"%s\"\n\n", campaign->Name()); + fprintf(f, "grp_iff: %d\n", (int) player_group->GetIFF()); + fprintf(f, "grp_type: %d\n", (int) player_group->Type()); + fprintf(f, "grp_id: %d\n", (int) player_group->GetID()); + if (player_unit) + fprintf(f, "unit: \"%s\"\n", player_unit->Name().data()); + + fprintf(f, "status: %d\n", (int) campaign->GetStatus()); + fprintf(f, "basetime: %f\n", campaign->GetStartTime()); + fprintf(f, "time: %f // %s\n\n", + campaign->GetTime(), + timestr); + + fprintf(f, "sitrep: \"%s\"\n", campaign->Situation()); + fprintf(f, "orders: \"%s\"\n\n", campaign->Orders()); + + ListIter c_iter = campaign->GetCombatants(); + while (++c_iter) { + Combatant* c = c_iter.value(); + + fprintf(f, "combatant: {"); + fprintf(f, " name:\"%s\",", c->Name()); + fprintf(f, " iff:%d,", c->GetIFF()); + fprintf(f, " score:%d,", c->Score()); + fprintf(f, " }\n"); + } + + fprintf(f, "\n"); + + ListIter a_iter = campaign->GetActions(); + while (++a_iter) { + CombatAction* a = a_iter.value(); + fprintf(f, "action: { id:%4d, stat:%d", a->Identity(), a->Status()); + + if (a->Status() == CombatAction::PENDING) { + if (a->Count()) + fprintf(f, ", count:%d", a->Count()); + + if (a->StartAfter()) + fprintf(f, ", after:%d", a->StartAfter()); + } + + fprintf(f, " }\n"); + } + + fprintf(f, "\n"); + + ListIter e_iter = campaign->GetEvents(); + while (++e_iter) { + CombatEvent* e = e_iter.value(); + + fprintf(f, "event: {"); + fprintf(f, " type:%-18s,", e->TypeName()); + fprintf(f, " time:0x%08x,", e->Time()); + fprintf(f, " team:%d,", e->GetIFF()); + fprintf(f, " points:%d,", e->Points()); + fprintf(f, " source:\"%s\",", e->SourceName()); + fprintf(f, " region:\"%s\",", e->Region()); + fprintf(f, " title:\"%s\",", e->Title()); + if (e->Filename()) + fprintf(f, " file:\"%s\",", e->Filename()); + if (e->ImageFile()) + fprintf(f, " image:\"%s\",", e->ImageFile()); + if (e->SceneFile()) + fprintf(f, " scene:\"%s\",", e->SceneFile()); + if (!e->Filename() || *e->Filename() == 0) + fprintf(f, " info:\"%s\"", FormatMultiLine(e->Information())); + fprintf(f, " }\n"); + } + + fprintf(f, "\n// ORDER OF BATTLE:\n\n"); + fclose(f); + + c_iter.reset(); + while (++c_iter) { + Combatant* c = c_iter.value(); + CombatGroup* g = c->GetForce(); + CombatGroup::SaveOrderOfBattle(s, g); + } + } +} + +void +CampaignSaveGame::Delete(const char* name) +{ + DeleteFile(GetSaveDirectory() + "/" + name); +} + +void +CampaignSaveGame::RemovePlayer(Player* p) +{ + List save_list; + Text save_dir = GetSaveDirectory(p) + "/"; + + DataLoader* loader = DataLoader::GetLoader(); + bool use_file_sys = loader->IsFileSystemEnabled(); + loader->UseFileSystem(true); + loader->SetDataPath(save_dir); + loader->ListFiles("*.*", save_list); + loader->SetDataPath(0); + loader->UseFileSystem(use_file_sys); + + for (int i = 0; i < save_list.size(); i++) { + Text* filename = save_list[i]; + DeleteFile(save_dir + filename->data()); + } + + save_list.destroy(); + + RemoveDirectory(GetSaveDirectory(p)); +} + +// +--------------------------------------------------------------------+ + +void +CampaignSaveGame::SaveAuto() +{ + Save("AutoSave"); +} + +void +CampaignSaveGame::LoadAuto() +{ + Load("AutoSave"); +} + +// +--------------------------------------------------------------------+ + +Text +CampaignSaveGame::GetResumeFile() +{ + // check for auto save game: + FILE* f = ::fopen(GetSaveDirectory() + "/AutoSave", "r"); + if (f) { + ::fclose(f); + + return "AutoSave"; + } + + return Text(); +} + +int +CampaignSaveGame::GetSaveGameList(List& save_list) +{ + DataLoader* loader = DataLoader::GetLoader(); + bool use_file_sys = loader->IsFileSystemEnabled(); + loader->UseFileSystem(true); + loader->SetDataPath(GetSaveDirectory() + "/"); + loader->ListFiles("*.*", save_list); + loader->SetDataPath(0); + loader->UseFileSystem(use_file_sys); + + return save_list.size(); +} \ No newline at end of file diff --git a/Stars45/CampaignSaveGame.h b/Stars45/CampaignSaveGame.h new file mode 100644 index 0000000..896fbb3 --- /dev/null +++ b/Stars45/CampaignSaveGame.h @@ -0,0 +1,69 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CampaignSaveGame.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + CampaignSaveGame contains the logic needed to save and load + campaign games in progress. +*/ + +#ifndef CampaignSaveGame_h +#define CampaignSaveGame_h + +#include "Types.h" +#include "Geometry.h" +#include "text.h" +#include "term.h" +#include "List.h" + +// +--------------------------------------------------------------------+ + +class Campaign; +class CampaignPlan; +class Combatant; +class CombatGroup; +class CombatZone; +class DataLoader; +class Mission; +class Player; +class StarSystem; + +// +--------------------------------------------------------------------+ + +class CampaignSaveGame +{ +public: + static const char* TYPENAME() { return "CampaignSaveGame"; } + + CampaignSaveGame(Campaign* c=0); + virtual ~CampaignSaveGame(); + + virtual Campaign* GetCampaign() { return campaign; } + + virtual void Load(const char* name); + virtual void Save(const char* name); + static void Delete(const char* name); + static void RemovePlayer(Player* p); + + virtual void LoadAuto(); + virtual void SaveAuto(); + + static Text GetResumeFile(); + static int GetSaveGameList(List& save_list); + +private: + static Text GetSaveDirectory(); + static Text GetSaveDirectory(Player* p); + static void CreateSaveDirectory(); + + Campaign* campaign; +}; + +#endif CampaignSaveGame_h + diff --git a/Stars45/CampaignSituationReport.cpp b/Stars45/CampaignSituationReport.cpp new file mode 100644 index 0000000..8f53cc0 --- /dev/null +++ b/Stars45/CampaignSituationReport.cpp @@ -0,0 +1,416 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CampaignSituationReport.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + CampaignSituationReport generates the situation report + portion of the briefing for a dynamically generated + mission in a dynamic campaign. +*/ + +#include "MemDebug.h" +#include "CampaignSituationReport.h" +#include "Campaign.h" +#include "Combatant.h" +#include "CombatAssignment.h" +#include "CombatGroup.h" +#include "CombatUnit.h" +#include "CombatZone.h" +#include "Callsign.h" +#include "Mission.h" +#include "Instruction.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "StarSystem.h" +#include "Random.h" +#include "Player.h" + +// +--------------------------------------------------------------------+ + +CampaignSituationReport::CampaignSituationReport(Campaign* c, Mission* m) + : campaign(c), mission(m) +{} + +CampaignSituationReport::~CampaignSituationReport() {} + +// +--------------------------------------------------------------------+ + +void +CampaignSituationReport::GenerateSituationReport() +{ + if (!campaign || !mission) + return; + + sitrep = Text(); + + GlobalSituation(); + MissionSituation(); + + mission->SetSituation(sitrep); +} + +// +--------------------------------------------------------------------+ + +static const char* outlooks[4] = { "good", "fluid", "poor", "bleak" }; + +void +CampaignSituationReport::GlobalSituation() +{ + if (campaign->GetTime() < 40 * 3600) + sitrep = Text(campaign->Name()) + Text(" is still in its early stages and the situation is "); + else + sitrep = Text("The overall outlook for ") + Text(campaign->Name()) + Text(" is "); + + int score = campaign->GetPlayerTeamScore(); + + if (score > 1000) + sitrep += outlooks[0]; + else if (score > -1000) + sitrep += outlooks[1]; + else if (score > -2000) + sitrep += outlooks[2]; + else + sitrep += outlooks[3]; + + sitrep += ". "; + + Text strat_dir; + + CombatGroup* pg = campaign->GetPlayerGroup(); + + if (pg) + strat_dir = pg->GetStrategicDirection(); + + if (strat_dir.length()) + sitrep += strat_dir; + else + sitrep += Text("Establishing and maintaining military control of the ") + + mission->GetStarSystem()->Name() + " System remains a key priority."; +} + +// +--------------------------------------------------------------------+ + +void +CampaignSituationReport::MissionSituation() +{ + if (mission) { + MissionElement* player = mission->GetPlayer(); + MissionElement* target = mission->GetTarget(); + MissionElement* ward = mission->GetWard(); + MissionElement* escort = FindEscort(player); + Text threat = GetThreatInfo(); + Text sector = mission->GetRegion(); + + sector += " sector."; + + switch (mission->Type()) { + case Mission::PATROL: + case Mission::AIR_PATROL: + sitrep += "\n\nThis mission is a routine patrol of the "; + sitrep += sector; + break; + + case Mission::SWEEP: + case Mission::AIR_SWEEP: + sitrep += "\n\nFor this mission, you will be performing a fighter sweep of the "; + sitrep += sector; + break; + + case Mission::INTERCEPT: + case Mission::AIR_INTERCEPT: + sitrep += "\n\nWe have detected hostile elements inbound. "; + sitrep += "Your mission is to intercept them before they are able to engage their targets."; + break; + + + case Mission::STRIKE: + sitrep += "\n\nThe goal of this mission is to perform a strike on preplanned targets in the "; + sitrep += sector; + + if (target) { + sitrep += " Your package has been assigned to strike the "; + + if (target->GetCombatGroup()) + sitrep += target->GetCombatGroup()->GetDescription(); + else + sitrep += target->Name(); + + sitrep += "."; + } + break; + + case Mission::ASSAULT: + sitrep += "\n\nThis mission is to assault preplanned targets in the "; + sitrep += sector; + + if (target) { + sitrep += " Your package has been assigned to strike the "; + + if (target->GetCombatGroup()) + sitrep += target->GetCombatGroup()->GetDescription(); + else + sitrep += target->Name(); + + sitrep += "."; + } + break; + + case Mission::DEFEND: + if (ward) { + sitrep += "\n\nFor this mission, you will need to defend "; + sitrep += ward->Name(); + sitrep += " in the "; + sitrep += sector; + } + else { + sitrep += "\n\nThis is a defensive patrol mission in the "; + sitrep += sector; + } + break; + + case Mission::ESCORT: + if (ward) { + sitrep += "\n\nFor this mission, you will need to escort the "; + sitrep += ward->Name(); + sitrep += " in the "; + sitrep += sector; + } + else { + sitrep += "\n\nThis is an escort mission in the "; + sitrep += sector; + } + break; + + case Mission::ESCORT_FREIGHT: + if (ward) { + sitrep += "\n\nFor this mission, you will need to escort the freighter "; + sitrep += ward->Name(); + sitrep += "."; + } + else { + sitrep += "\n\nThis is a freight escort mission in the "; + sitrep += sector; + } + break; + + case Mission::ESCORT_SHUTTLE: + if (ward) { + sitrep += "\n\nFor this mission, you will need to escort the shuttle "; + sitrep += ward->Name(); + sitrep += "."; + } + else { + sitrep += "\n\nThis is a shuttle escort mission in the "; + sitrep += sector; + } + break; + + case Mission::ESCORT_STRIKE: + if (ward) { + sitrep += "\n\nFor this mission, you will need to protect the "; + sitrep += ward->Name(); + sitrep += " strike package from hostile interceptors."; + } + else { + sitrep += "\n\nFor this mission, you will be responsible for strike escort duty."; + } + break; + + case Mission::INTEL: + case Mission::SCOUT: + case Mission::RECON: + sitrep += "\n\nThis is an intelligence gathering mission in the "; + sitrep += sector; + break; + + case Mission::BLOCKADE: + sitrep += "\n\nThis mission is part of the blockade operation in the "; + sitrep += sector; + break; + + case Mission::FLEET: + sitrep += "\n\nThis mission is a routine fleet patrol of the "; + sitrep += sector; + break; + + case Mission::BOMBARDMENT: + sitrep += "\n\nOur goal for this mission is to engage and destroy preplanned targets in the "; + sitrep += sector; + break; + + case Mission::FLIGHT_OPS: + sitrep += "\n\nFor this mission, the "; + sitrep += player->Name(); + sitrep += " will be conducting combat flight operations in the "; + sitrep += sector; + break; + + case Mission::TRAINING: + sitrep += "\n\nThis will be a training mission."; + break; + + case Mission::TRANSPORT: + case Mission::CARGO: + case Mission::OTHER: + default: + break; + } + + if (threat.length()) { + sitrep += " "; + sitrep += threat; + sitrep += "\n\n"; + } + } + else { + sitrep += "\n\n"; + } + + Text rank; + Text name; + + Player* p = Player::GetCurrentPlayer(); + + if (p) { + if (p->Rank() > 6) + rank = ", Admiral"; + else + rank = Text(", ") + Player::RankName(p->Rank()); + + name = Text(", ") + p->Name(); + } + + sitrep += "You have a mission to perform. "; + + switch (RandomIndex()) { + case 0: sitrep += "You'd better go get to it!"; break; + case 1: sitrep += "And let's be careful out there!"; break; + case 2: sitrep += "Good luck, sir!"; break; + case 3: sitrep += "Let's keep up the good work out there."; break; + case 4: sitrep += "Don't lose your focus."; break; + case 5: sitrep += "Good luck out there."; break; + case 6: sitrep += "What are you waiting for, cocktail hour?"; break; + case 7: sitrep += Text("Godspeed") + rank + "!"; break; + case 8: sitrep += Text("Good luck") + rank + "!"; break; + case 9: sitrep += Text("Good luck") + name + "!"; break; + case 10: sitrep += "If everything is clear, get your team ready and get underway."; + break; + case 11: sitrep += Text("Go get to it") + rank + "!"; break; + case 12: sitrep += "The clock is ticking, so let's move it!"; break; + case 13: sitrep += "Stay sharp out there!"; break; + case 14: sitrep += Text("Go get 'em") + rank + "!"; break; + case 15: sitrep += "Now get out of here and get to work!"; break; + } +} + +// +--------------------------------------------------------------------+ + +MissionElement* +CampaignSituationReport::FindEscort(MissionElement* player) +{ + MissionElement* escort = 0; + + if (!mission || !player) return escort; + + ListIter iter = mission->GetElements(); + while (++iter) { + MissionElement* elem = iter.value(); + + } + + return escort; +} + +// +--------------------------------------------------------------------+ + +Text +CampaignSituationReport::GetThreatInfo() +{ + Text threat_info; + + int enemy_fighters = 0; + int enemy_starships = 0; + int enemy_sites = 0; + + if (mission && mission->GetPlayer()) { + MissionElement* player = mission->GetPlayer(); + Text rgn0 = player->Region(); + Text rgn1; + int iff = player->GetIFF(); + + ListIter nav = player->NavList(); + while (++nav) { + if (rgn0 != nav->RegionName()) + rgn1 = nav->RegionName(); + } + + ListIter elem = mission->GetElements(); + while (++elem) { + if (elem->GetIFF() > 0 && elem->GetIFF() != iff && elem->IntelLevel() > Intel::SECRET) { + if (elem->IsGroundUnit()) { + if (!elem->GetDesign() || + elem->GetDesign()->type != Ship::SAM) + continue; + + if (elem->Region() != rgn0 && + elem->Region() != rgn1) + continue; + } + + int mission_role = elem->MissionRole(); + + if (mission_role == Mission::STRIKE || + mission_role == Mission::INTEL || + mission_role == Mission::CARGO || + mission_role == Mission::TRANSPORT) + continue; + + if (elem->GetDesign()->type >= Ship::MINE && elem->GetDesign()->type <= Ship::SWACS) + enemy_sites += elem->Count(); + + else if (elem->IsDropship()) + enemy_fighters += elem->Count(); + + else if (elem->IsStarship()) + enemy_starships += elem->Count(); + + else if (elem->IsGroundUnit()) + enemy_sites += elem->Count(); + } + } + } + + if (enemy_starships > 0) { + threat_info = "We have reports of several enemy starships in the vicinity."; + + if (enemy_fighters > 0) { + threat_info += " Also be advised that enemy fighters may be operating nearby."; + } + + else if (enemy_sites > 0) { + threat_info += " And be on the lookout for mines and defense satellites."; + } + } + + else if (enemy_fighters > 0) { + threat_info = "We have reports of several enemy fighters in your operating area."; + } + + else if (enemy_sites > 0) { + if (mission->Type() >= Mission::AIR_PATROL && mission->Type() <= Mission::STRIKE) + threat_info = "Remember to check air-to-ground sensors for SAM and AAA sites."; + else + threat_info = "Be on the lookout for mines and defense satellites."; + } + else { + threat_info = "We have no reliable information on threats in your operating area."; + } + + return threat_info; +} diff --git a/Stars45/CampaignSituationReport.h b/Stars45/CampaignSituationReport.h new file mode 100644 index 0000000..118e7eb --- /dev/null +++ b/Stars45/CampaignSituationReport.h @@ -0,0 +1,59 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CampaignSituationReport.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + CampaignSituationReport generates the situation report + portion of the briefing for a dynamically generated + mission in a dynamic campaign. +*/ + +#ifndef CampaignSituationReport_h +#define CampaignSituationReport_h + +#include "Types.h" +#include "Geometry.h" +#include "List.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class Campaign; +class CombatGroup; +class CombatUnit; +class CombatZone; +class Mission; +class MissionElement; + +// +--------------------------------------------------------------------+ + +class CampaignSituationReport +{ +public: + static const char* TYPENAME() { return "CampaignSituationReport"; } + + CampaignSituationReport(Campaign* c, Mission* m); + virtual ~CampaignSituationReport(); + + virtual void GenerateSituationReport(); + +protected: + virtual void GlobalSituation(); + virtual void MissionSituation(); + virtual MissionElement* + FindEscort(MissionElement* elem); + virtual Text GetThreatInfo(); + + Campaign* campaign; + Mission* mission; + Text sitrep; +}; + +#endif CampaignSituationReport_h + diff --git a/Stars45/CarrierAI.cpp b/Stars45/CarrierAI.cpp new file mode 100644 index 0000000..8f584b9 --- /dev/null +++ b/Stars45/CarrierAI.cpp @@ -0,0 +1,400 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CarrierAI.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + "Air Boss" AI class for managing carrier fighter squadrons +*/ + +#include "MemDebug.h" +#include "CarrierAI.h" +#include "ShipAI.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "Element.h" +#include "FlightPlanner.h" +#include "Instruction.h" +#include "RadioMessage.h" +#include "RadioTraffic.h" +#include "Hangar.h" +#include "FlightDeck.h" +#include "Mission.h" +#include "Contact.h" +#include "Sim.h" +#include "StarSystem.h" +#include "Callsign.h" +#include "NetUtil.h" + +#include "Game.h" +#include "Random.h" + +// +----------------------------------------------------------------------+ + +CarrierAI::CarrierAI(Ship* s, int level) + : sim(0), ship(s), hangar(0), exec_time(0), flight_planner(0), + hold_time(0), ai_level(level) +{ + if (ship) { + sim = Sim::GetSim(); + hangar = ship->GetHangar(); + + for (int i = 0; i < 4; i++) + patrol_elem[i] = 0; + + if (ship) + flight_planner = new(__FILE__,__LINE__) FlightPlanner(ship); + + hold_time = (int) Game::GameTime(); + } +} + +CarrierAI::~CarrierAI() +{ + delete flight_planner; +} + +// +--------------------------------------------------------------------+ + +void +CarrierAI::ExecFrame(double secs) +{ + const int INIT_HOLD = 15000; + const int EXEC_PERIOD = 3000; + + if (!sim || !ship || !hangar) + return; + + if (((int) Game::GameTime() - hold_time >= INIT_HOLD) && + ((int) Game::GameTime() - exec_time > EXEC_PERIOD)) { + + CheckHostileElements(); + CheckPatrolCoverage(); + + exec_time = (int) Game::GameTime(); + } +} + +// +--------------------------------------------------------------------+ + +bool +CarrierAI::CheckPatrolCoverage() +{ + const DWORD PATROL_PERIOD = 900 * 1000; + + // pick up existing patrol elements: + + ListIter iter = sim->GetElements(); + while (++iter) { + Element* elem = iter.value(); + + if (elem->GetCarrier() == ship && + (elem->Type() == Mission::PATROL || + elem->Type() == Mission::SWEEP || + elem->Type() == Mission::AIR_PATROL || + elem->Type() == Mission::AIR_SWEEP) && + !elem->IsSquadron() && + !elem->IsFinished()) { + + bool found = false; + int open = -1; + + for (int i = 0; i < 4; i++) { + if (patrol_elem[i] == elem) + found = true; + + else if (patrol_elem[i] == 0 && open < 0) + open = i; + } + + if (!found && open >= 0) { + patrol_elem[open] = elem; + } + } + } + + // manage the four screening patrols: + + for (int i = 0; i < 4; i++) { + Element* elem = patrol_elem[i]; + + if (elem) { + if (elem->IsFinished()) { + patrol_elem[i] = 0; + } + + else { + LaunchElement(elem); + } + } + + else if (Game::GameTime() - hangar->GetLastPatrolLaunch() > PATROL_PERIOD || + hangar->GetLastPatrolLaunch() == 0) { + Element* patrol = CreatePackage(0, 2, Mission::PATROL, 0, "ACM Medium Range"); + if (patrol) { + patrol_elem[i] = patrol; + + if (flight_planner) + flight_planner->CreatePatrolRoute(patrol, i); + + hangar->SetLastPatrolLaunch(Game::GameTime()); + return true; + } + } + } + + return false; +} + +// +--------------------------------------------------------------------+ + +bool +CarrierAI::CheckHostileElements() +{ + List assigned; + ListIter iter = sim->GetElements(); + while (++iter) { + Element* elem = iter.value(); + + // if this element is hostile to us + // or if the element is a target objective + // of the carrier, or is hostile to any + // of our squadrons... + + bool hostile = false; + + if (elem->IsHostileTo(ship) || elem->IsObjectiveTargetOf(ship)) { + hostile = true; + } + else { + for (int i = 0; i < hangar->NumSquadrons() && !hostile; i++) { + int squadron_iff = hangar->SquadronIFF(i); + + if (elem->IsHostileTo(squadron_iff)) + hostile = true; + } + } + + if (hostile) { + sim->GetAssignedElements(elem, assigned); + + // is one of our fighter elements already assigned to this target? + bool found = false; + ListIter a_iter = assigned; + while (++a_iter && !found) { + Element* a = a_iter.value(); + + if (a->GetCarrier() == ship) + found = true; + } + + // nobody is assigned yet, create an attack package + if (!found && CreateStrike(elem)) { + hold_time = (int) Game::GameTime() + 30000; + return true; + } + } + } + + return false; +} + +bool +CarrierAI::CreateStrike(Element* elem) +{ + Element* strike = 0; + Ship* target = elem->GetShip(1); + + if (target && !target->IsGroundUnit()) { + Contact* contact = ship->FindContact(target); + if (contact && contact->GetIFF(ship) > 0) { + + // fighter intercept + if (target->IsDropship()) { + int squadron = 0; + if (hangar->NumShipsReady(1) >= hangar->NumShipsReady(0)) + squadron = 1; + + int count = 2; + + if (count < elem->NumShips()) + count = elem->NumShips(); + + strike = CreatePackage(squadron, count, Mission::INTERCEPT, elem->Name(), "ACM Medium Range"); + + if (strike) { + strike->SetAssignment(elem); + + if (flight_planner) + flight_planner->CreateStrikeRoute(strike, elem); + } + } + + // starship or station assault + else { + int squadron = 0; + if (hangar->NumSquadrons() > 1) + squadron = 1; + if (hangar->NumSquadrons() > 2) + squadron = 2; + + int count = 2; + + if (target->Class() > Ship::FRIGATE) { + count = 4; + strike = CreatePackage(squadron, count, Mission::ASSAULT, elem->Name(), "Hvy Ship Strike"); + } + else { + count = 2; + strike = CreatePackage(squadron, count, Mission::ASSAULT, elem->Name(), "Ship Strike"); + } + + if (strike) { + strike->SetAssignment(elem); + + if (flight_planner) + flight_planner->CreateStrikeRoute(strike, elem); + + // strike escort if target has fighter protection: + if (target->GetHangar()) { + if (squadron > 1) squadron--; + Element* escort = CreatePackage(squadron, 2, Mission::ESCORT_STRIKE, strike->Name(), "ACM Short Range"); + + if (escort && flight_planner) + flight_planner->CreateEscortRoute(escort, strike); + } + } + } + } + } + + return strike != 0; +} + +// +--------------------------------------------------------------------+ + +Element* +CarrierAI::CreatePackage(int squadron, int size, int code, const char* target, const char* loadname) +{ + if (squadron < 0 || size < 1 || code < Mission::PATROL || hangar->NumShipsReady(squadron) < size) + return 0; + + Sim* sim = Sim::GetSim(); + const char* call = sim->FindAvailCallsign(ship->GetIFF()); + Element* elem = sim->CreateElement(call, ship->GetIFF(), code); + FlightDeck* deck = 0; + int queue = 1000; + int* load = 0; + const ShipDesign* + design = hangar->SquadronDesign(squadron); + + elem->SetSquadron(hangar->SquadronName(squadron)); + elem->SetCarrier(ship); + + if (target) { + int i_code = 0; + + switch (code) { + case Mission::ASSAULT: i_code = Instruction::ASSAULT; break; + case Mission::STRIKE: i_code = Instruction::STRIKE; break; + + case Mission::AIR_INTERCEPT: + case Mission::INTERCEPT: i_code = Instruction::INTERCEPT; break; + + case Mission::ESCORT: + case Mission::ESCORT_STRIKE: + case Mission::ESCORT_FREIGHT: + i_code = Instruction::ESCORT; break; + + case Mission::DEFEND: i_code = Instruction::DEFEND; break; + } + + Instruction* objective = new(__FILE__,__LINE__) Instruction(i_code, target); + if (objective) + elem->AddObjective(objective); + } + + if (design && loadname) { + Text name = loadname; + name.setSensitive(false); + + ListIter sl = (List&) design->loadouts; + while (++sl) { + if (name == sl->name) { + load = sl->load; + elem->SetLoadout(load); + } + } + } + + for (int i = 0; i < ship->NumFlightDecks(); i++) { + FlightDeck* d = ship->GetFlightDeck(i); + + if (d && d->IsLaunchDeck()) { + int dq = hangar->PreflightQueue(d); + + if (dq < queue) { + queue = dq; + deck = d; + } + } + } + + int npackage = 0; + int slots[4]; + + for (i = 0; i < 4; i++) + slots[i] = -1; + + for (int slot = 0; slot < hangar->SquadronSize(squadron); slot++) { + const HangarSlot* s = hangar->GetSlot(squadron, slot); + + if (hangar->GetState(s) == Hangar::STORAGE) { + if (npackage < 4) + slots[npackage] = slot; + + hangar->GotoAlert(squadron, slot, deck, elem, load, code > Mission::SWEEP); + npackage++; + + if (npackage >= size) + break; + } + } + + NetUtil::SendElemCreate(elem, squadron, slots, code <= Mission::SWEEP); + + return elem; +} + +// +--------------------------------------------------------------------+ + +bool +CarrierAI::LaunchElement(Element* elem) +{ + bool result = false; + + if (!elem) + return result; + + for (int squadron = 0; squadron < hangar->NumSquadrons(); squadron++) { + for (int slot = 0; slot < hangar->SquadronSize(squadron); slot++) { + const HangarSlot* s = hangar->GetSlot(squadron, slot); + + if (hangar->GetState(s) == Hangar::ALERT && + hangar->GetPackageElement(s) == elem) { + + hangar->Launch(squadron, slot); + NetUtil::SendShipLaunch(ship, squadron, slot); + + result = true; + } + } + } + + return result; +} diff --git a/Stars45/CarrierAI.h b/Stars45/CarrierAI.h new file mode 100644 index 0000000..651c8f5 --- /dev/null +++ b/Stars45/CarrierAI.h @@ -0,0 +1,64 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CarrierAI.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + "Air Boss" AI class for managing carrier fighter squadrons +*/ + +#ifndef CarrierAI_h +#define CarrierAI_h + +#include "Types.h" +#include "Director.h" + +// +--------------------------------------------------------------------+ + +class Sim; +class Ship; +class ShipAI; +class Instruction; +class Hangar; +class Element; +class FlightPlanner; + +// +--------------------------------------------------------------------+ + +class CarrierAI : public Director +{ +public: + CarrierAI(Ship* s, int level); + virtual ~CarrierAI(); + + virtual void ExecFrame(double seconds); + +protected: + virtual bool CheckPatrolCoverage(); + virtual bool CheckHostileElements(); + + virtual bool CreateStrike(Element* elem); + + virtual Element* CreatePackage(int squad, int size, int code, const char* target=0, const char* loadname=0); + virtual bool LaunchElement(Element* elem); + + Sim* sim; + Ship* ship; + Hangar* hangar; + FlightPlanner* flight_planner; + int exec_time; + int hold_time; + int ai_level; + + Element* patrol_elem[4]; +}; + +// +--------------------------------------------------------------------+ + +#endif CarrierAI_h + diff --git a/Stars45/CmdDlg.cpp b/Stars45/CmdDlg.cpp new file mode 100644 index 0000000..c6f5433 --- /dev/null +++ b/Stars45/CmdDlg.cpp @@ -0,0 +1,186 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CmdDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Operational Command Dialog Active Window class +*/ + +#include "MemDebug.h" +#include "CmdDlg.h" +#include "CmpFileDlg.h" +#include "CmpnScreen.h" +#include "Starshatter.h" +#include "Campaign.h" +#include "Combatant.h" +#include "CombatGroup.h" +#include "CombatUnit.h" +#include "ShipDesign.h" + +#include "Game.h" +#include "DataLoader.h" +#include "Button.h" +#include "Video.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "ParseUtil.h" +#include "FormatUtil.h" + +// +--------------------------------------------------------------------+ + +CmdDlg::CmdDlg(CmpnScreen* mgr) + : cmpn_screen(mgr), + txt_group(0), txt_score(0), txt_name(0), txt_time(0), + btn_save(0), btn_exit(0), stars(0), campaign(0), mode(0) +{ + stars = Starshatter::GetInstance(); + campaign = Campaign::GetCampaign(); + + for (int i = 0; i < 5; i++) + btn_mode[i] = 0; +} + +CmdDlg::~CmdDlg() +{ +} + +// +--------------------------------------------------------------------+ + +void +CmdDlg::RegisterCmdControls(FormWindow* win) +{ + btn_save = (Button*) win->FindControl( 1); + btn_exit = (Button*) win->FindControl( 2); + + for (int i = 0; i < 5; i++) { + btn_mode[i] = (Button*) win->FindControl(100 + i); + } + + txt_group = win->FindControl(200); + txt_score = win->FindControl(201); + txt_name = win->FindControl(300); + txt_time = win->FindControl(301); +} + +// +--------------------------------------------------------------------+ + +void +CmdDlg::ShowCmdDlg() +{ + campaign = Campaign::GetCampaign(); + + if (txt_name) { + if (campaign) + txt_name->SetText(campaign->Name()); + else + txt_name->SetText("No Campaign Selected"); + } + + ShowMode(); + + if (btn_save) + btn_save->SetEnabled(!campaign->IsTraining()); + + if (btn_mode[MODE_FORCES]) btn_mode[MODE_FORCES]->SetEnabled(!campaign->IsTraining()); + if (btn_mode[MODE_INTEL]) btn_mode[MODE_INTEL]->SetEnabled(!campaign->IsTraining()); +} + +// +--------------------------------------------------------------------+ + +void +CmdDlg::ExecFrame() +{ + CombatGroup* g = campaign->GetPlayerGroup(); + if (g && txt_group) + txt_group->SetText(g->GetDescription()); + + if (txt_score) { + char score[32]; + sprintf(score, "Team Score: %d", campaign->GetPlayerTeamScore()); + txt_score->SetText(score); + txt_score->SetTextAlign(DT_RIGHT); + } + + if (txt_time) { + double t = campaign->GetTime(); + char daytime[32]; + FormatDayTime(daytime, t); + txt_time->SetText(daytime); + } + + int unread = campaign->CountNewEvents(); + + if (btn_mode[MODE_INTEL]) { + if (unread > 0) { + char text[64]; + sprintf(text, "INTEL (%d)", unread); + btn_mode[MODE_INTEL]->SetText(text); + } + else { + btn_mode[MODE_INTEL]->SetText("INTEL"); + } + } +} + +// +--------------------------------------------------------------------+ + +void +CmdDlg::ShowMode() +{ + for (int i = 0; i < 5; i++) { + if (btn_mode[i]) btn_mode[i]->SetButtonState(0); + } + + if (mode < 0 || mode > 4) + mode = 0; + + if (btn_mode[mode]) btn_mode[mode]->SetButtonState(1); +} + +void +CmdDlg::OnMode(AWEvent* event) +{ + for (int i = MODE_ORDERS; i < NUM_MODES; i++) { + Button* btn = btn_mode[i]; + + if (event->window == btn) { + mode = i; + } + } + + switch (mode) { + case MODE_ORDERS: cmpn_screen->ShowCmdOrdersDlg(); break; + case MODE_THEATER: cmpn_screen->ShowCmdTheaterDlg(); break; + case MODE_FORCES: cmpn_screen->ShowCmdForceDlg(); break; + case MODE_INTEL: cmpn_screen->ShowCmdIntelDlg(); break; + case MODE_MISSIONS: cmpn_screen->ShowCmdMissionsDlg(); break; + default: cmpn_screen->ShowCmdOrdersDlg(); break; + } +} + +void +CmdDlg::OnSave(AWEvent* event) +{ + if (campaign && cmpn_screen) { + CmpFileDlg* fdlg = cmpn_screen->GetCmpFileDlg(); + + cmpn_screen->ShowCmpFileDlg(); + } +} + +void +CmdDlg::OnExit(AWEvent* event) +{ + if (stars) { + Mouse::Show(false); + stars->SetGameMode(Starshatter::MENU_MODE); + } +} + +// +--------------------------------------------------------------------+ diff --git a/Stars45/CmdDlg.h b/Stars45/CmdDlg.h new file mode 100644 index 0000000..5d98f8a --- /dev/null +++ b/Stars45/CmdDlg.h @@ -0,0 +1,83 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CmdDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Operational Command Dialog Active Window class +*/ + +#ifndef CmdDlg_h +#define CmdDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "Bitmap.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Font.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class CmpnScreen; +class Campaign; +class Combatant; +class CombatGroup; +class CombatUnit; +class Starshatter; + +// +--------------------------------------------------------------------+ + +class CmdDlg +{ +public: + enum MODE { + MODE_ORDERS, + MODE_THEATER, + MODE_FORCES, + MODE_INTEL, + MODE_MISSIONS, + NUM_MODES + }; + + CmdDlg(CmpnScreen* mgr); + virtual ~CmdDlg(); + + virtual void RegisterCmdControls(FormWindow* win); + virtual void ShowCmdDlg(); + + virtual void ExecFrame(); + + // Operations: + virtual void OnMode(AWEvent* event); + virtual void OnSave(AWEvent* event); + virtual void OnExit(AWEvent* event); + +protected: + virtual void ShowMode(); + + CmpnScreen* cmpn_screen; + + ActiveWindow* txt_group; + ActiveWindow* txt_score; + ActiveWindow* txt_name; + ActiveWindow* txt_time; + Button* btn_mode[NUM_MODES]; + Button* btn_save; + Button* btn_exit; + + Starshatter* stars; + Campaign* campaign; + + int mode; +}; + +#endif CmdDlg_h + diff --git a/Stars45/CmdForceDlg.cpp b/Stars45/CmdForceDlg.cpp new file mode 100644 index 0000000..205817d --- /dev/null +++ b/Stars45/CmdForceDlg.cpp @@ -0,0 +1,718 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CmdForceDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Operational Command Dialog (Order of Battle Tab) +*/ + +#include "MemDebug.h" +#include "CmdForceDlg.h" +#include "CmdMsgDlg.h" +#include "CmpnScreen.h" +#include "Starshatter.h" +#include "Campaign.h" +#include "Combatant.h" +#include "CombatAssignment.h" +#include "CombatGroup.h" +#include "CombatUnit.h" +#include "CombatZone.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "Player.h" +#include "Weapon.h" + +#include "Game.h" +#include "DataLoader.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Slider.h" +#include "Video.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "ParseUtil.h" +#include "FormatUtil.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +DEF_MAP_CLIENT(CmdForceDlg, OnMode); +DEF_MAP_CLIENT(CmdForceDlg, OnSave); +DEF_MAP_CLIENT(CmdForceDlg, OnExit); +DEF_MAP_CLIENT(CmdForceDlg, OnForces); +DEF_MAP_CLIENT(CmdForceDlg, OnCombat); +DEF_MAP_CLIENT(CmdForceDlg, OnTransfer); + +// +--------------------------------------------------------------------+ + +CmdForceDlg::CmdForceDlg(Screen* s, FormDef& def, CmpnScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), manager(mgr), + CmdDlg(mgr), + cmb_forces(0), lst_combat(0), lst_desc(0), + stars(0), campaign(0), current_group(0), current_unit(0), + btn_transfer(0) +{ + stars = Starshatter::GetInstance(); + campaign = Campaign::GetCampaign(); + + Init(def); +} + +CmdForceDlg::~CmdForceDlg() +{ +} + +// +--------------------------------------------------------------------+ + +void +CmdForceDlg::RegisterControls() +{ + cmb_forces = (ComboBox*) FindControl(400); + lst_combat = (ListBox*) FindControl(401); + lst_desc = (ListBox*) FindControl(402); + btn_transfer = (Button*) FindControl(403); + + RegisterCmdControls(this); + + if (btn_save) + REGISTER_CLIENT(EID_CLICK, btn_save, CmdForceDlg, OnSave); + + if (btn_exit) + REGISTER_CLIENT(EID_CLICK, btn_exit, CmdForceDlg, OnExit); + + for (int i = 0; i < 5; i++) { + if (btn_mode[i]) + REGISTER_CLIENT(EID_CLICK, btn_mode[i], CmdForceDlg, OnMode); + } + + if (cmb_forces) + REGISTER_CLIENT(EID_SELECT, cmb_forces, CmdForceDlg, OnForces); + + if (lst_combat) { + lst_combat->AddColumn("datatype", 0); + REGISTER_CLIENT(EID_SELECT, lst_combat, CmdForceDlg, OnCombat); + } + + if (btn_transfer) { + btn_transfer->SetEnabled(false); + REGISTER_CLIENT(EID_CLICK, btn_transfer, CmdForceDlg, OnTransfer); + } +} + +// +--------------------------------------------------------------------+ + +void +CmdForceDlg::ExecFrame() +{ + CmdDlg::ExecFrame(); +} + +// +--------------------------------------------------------------------+ + +void +CmdForceDlg::Show() +{ + mode = MODE_FORCES; + + FormWindow::Show(); + ShowCmdDlg(); + + cmb_forces->ClearItems(); + + campaign = Campaign::GetCampaign(); + + if (campaign) { + List& combatants = campaign->GetCombatants(); + + if (combatants.size() > 0) { + for (int i = 0; i < combatants.size(); i++) { + if (IsVisible(combatants[i])) { + cmb_forces->AddItem(combatants[i]->Name()); + } + } + + cmb_forces->SetSelection(0); + Combatant* c = combatants[0]; + ShowCombatant(c); + } + } +} + +// +--------------------------------------------------------------------+ + +bool +CmdForceDlg::IsVisible(Combatant* c) +{ + int nvis = 0; + + if (c) { + CombatGroup* force = c->GetForce(); + + if (force) { + List& groups = force->GetComponents(); + for (int i = 0; i < groups.size(); i++) { + CombatGroup* g = groups[i]; + + if (g->Type() < CombatGroup::CIVILIAN && + g->CountUnits() > 0 && + g->IntelLevel() >= Intel::KNOWN) + + nvis++; + } + } + } + + return nvis > 0; +} + +// +--------------------------------------------------------------------+ + +static char pipe_stack[32]; +static bool blank_line = false; + +void +CmdForceDlg::ShowCombatant(Combatant* c) +{ + if (!lst_combat || !c) return; + + lst_combat->ClearItems(); + ZeroMemory(pipe_stack, 32); + + CombatGroup* force = c->GetForce(); + + if (force) { + List& groups = force->GetComponents(); + for (int i = 0; i < groups.size(); i++) { + CombatGroup* g = groups[i]; + if (g->Type() < CombatGroup::CIVILIAN && g->CountUnits() > 0) + AddCombatGroup(g); + } + } + + current_group = 0; + current_unit = 0; + + if (lst_desc) lst_desc->ClearItems(); + if (btn_transfer) btn_transfer->SetEnabled(false); +} + +void +CmdForceDlg::AddCombatGroup(CombatGroup* grp, bool last_child) +{ + if (!lst_combat || !grp || grp->IntelLevel() < Intel::KNOWN) return; + + char prefix[4]; + + if (!grp->GetParent() || grp->GetParent()->Type() == CombatGroup::FORCE) { + if (!grp->IsExpanded()) { + prefix[0] = Font::PIPE_PLUS; + prefix[1] = 0; + } + else { + prefix[0] = Font::PIPE_MINUS; + prefix[1] = 0; + } + } + else { + prefix[0] = last_child ? Font::PIPE_LL : Font::PIPE_LT; + prefix[1] = Font::PIPE_HORZ; + prefix[2] = 0; + prefix[3] = 0; + + if (grp->GetLiveComponents().size() > 0 || grp->GetUnits().size() > 0) { + if (grp->IsExpanded()) + prefix[1] = Font::PIPE_MINUS; + else + prefix[1] = Font::PIPE_PLUS; + } + } + + int index = lst_combat->AddItemWithData( + Text(pipe_stack) + + Text(prefix) + + grp->GetDescription(), + (DWORD) grp); + lst_combat->SetItemData(index-1, 1, 0); + blank_line = false; + + int stacklen = strlen(pipe_stack); + + if (!grp->GetParent() || grp->GetParent()->Type() == CombatGroup::FORCE) + ; // no stack after first entry + else if (prefix[0] == Font::PIPE_LT) + pipe_stack[stacklen++] = Font::PIPE_VERT; + else + pipe_stack[stacklen++] = Font::PIPE_NBSP; + pipe_stack[stacklen] = 0; + + if (grp->IsExpanded() && grp->GetUnits().size() > 0) { + prefix[0] = (grp->GetLiveComponents().size() > 0) ? Font::PIPE_VERT : Font::PIPE_NBSP; + prefix[1] = Font::PIPE_NBSP; + prefix[2] = 0; + prefix[3] = 0; + + ListIter unit_iter = grp->GetUnits(); + while (++unit_iter) { + CombatUnit* unit = unit_iter.value(); + char info[512]; + int damage = (int) (100 * unit->GetSustainedDamage() / unit->GetDesign()->integrity); + + if (damage < 1 || unit->DeadCount() >= unit->Count()) { + sprintf(info, "%s%s%s", pipe_stack, prefix, unit->GetDescription()); + } + else { + sprintf(info, "%s%s%s %d%% damage", pipe_stack, prefix, unit->GetDescription(), + damage); + } + + int index = lst_combat->AddItemWithData(info, (DWORD) unit); + + lst_combat->SetItemData(index-1, 1, 1); + lst_combat->SetItemColor(index-1, lst_combat->GetForeColor() * 0.65); + } + + // blank line after last unit in group: + lst_combat->AddItem(Text(pipe_stack) + Text(prefix)); + blank_line = true; + } + + if (grp->IsExpanded() && grp->GetLiveComponents().size() > 0) { + List& groups = grp->GetLiveComponents(); + for (int i = 0; i < groups.size(); i++) { + AddCombatGroup(groups[i], i == groups.size()-1); + } + + // blank line after last group in group: + if (!blank_line) { + lst_combat->AddItem(pipe_stack); + blank_line = true; + } + } + + pipe_stack[stacklen-1] = 0; +} + +// +--------------------------------------------------------------------+ + +void +CmdForceDlg::OnForces(AWEvent* event) +{ + Text name = cmb_forces->GetSelectedItem(); + + campaign = Campaign::GetCampaign(); + + if (campaign) { + ListIter iter = campaign->GetCombatants(); + + while (++iter) { + Combatant* c = iter.value(); + + if (name == c->Name()) { + ShowCombatant(c); + break; + } + } + } +} + +// +--------------------------------------------------------------------+ + +struct WepGroup { + Text name; + int count; + + WepGroup() : count(0) { } +}; + +WepGroup* FindWepGroup(WepGroup* weapons, const char* name) +{ + WepGroup* group = 0; + WepGroup* iter = weapons; + int w = 0; + int first = -1; + + while (!group && w < 8) { + if (first < 0 && iter->name.length() == 0) + first = w; + + if (!stricmp(iter->name, name)) + group = iter; + + iter++; + w++; + } + + if (!group && first >= 0) { + group = weapons + first; + group->name = name; + } + + return group; +} + +void +CmdForceDlg::OnCombat(AWEvent* event) +{ + static int old_index = -1; + + int top_index = 0; + bool expand = false; + DWORD data = 0; + DWORD type = 0; + + current_group = 0; + current_unit = 0; + + if (lst_combat) { + top_index = lst_combat->GetTopIndex(); + + int index = lst_combat->GetListIndex(); + data = lst_combat->GetItemData(index); + type = lst_combat->GetItemData(index, 1); + Text item = lst_combat->GetItemText(index); + int nplus = item.indexOf(Font::PIPE_PLUS); + int xplus = -1; + int dx = 10000; + + if (nplus < 0) + nplus = item.indexOf(Font::PIPE_MINUS); + + if (nplus >= 0 && nplus < 64) { + char pipe[64]; + strncpy(pipe, item.data(), nplus+1); + pipe[nplus+1] = 0; + + Rect rect(0, 0, 1000, 20); + lst_combat->DrawText(pipe, 0, rect, DT_LEFT|DT_SINGLELINE|DT_VCENTER|DT_CALCRECT); + + xplus = rect.w; + } + + if (xplus > 0) { + dx = Mouse::X() - (lst_combat->GetRect().x + xplus - 16); + } + + // look for click on plus/minus in pipe + if (dx >= 0 && dx < 16) { + if (data && type == 0) { + CombatGroup* grp = (CombatGroup*) data; + grp->SetExpanded(!grp->IsExpanded()); + expand = true; + + current_group = grp; + current_unit = 0; + } + } + + old_index = index; + } + + if (campaign && data) { + if (!expand) { + lst_desc->ClearItems(); + + // combat group + if (type == 0) { + CombatGroup* grp = (CombatGroup*) data; + + current_group = grp; + current_unit = 0; + + // if has units, show location and assignment + if (grp->GetUnits().size() > 0) { + char txt[64]; + int n; + + n = lst_desc->AddItem("Group:") - 1; + lst_desc->SetItemText(n, 1, grp->GetDescription()); + n = lst_desc->AddItem("Sector:") - 1; + lst_desc->SetItemText(n, 1, grp->GetRegion()); + + lst_desc->AddItem(" "); + n = lst_desc->AddItem("Sorties:") - 1; + + if (grp->Sorties() >= 0) + sprintf(txt, "%d", grp->Sorties()); + else + strcpy(txt, "Unavail"); + + lst_desc->SetItemText(n, 1, txt); + + n = lst_desc->AddItem("Kills:") - 1; + + if (grp->Kills() >= 0) + sprintf(txt, "%d", grp->Kills()); + else + strcpy(txt, "Unavail"); + + lst_desc->SetItemText(n, 1, txt); + + n = lst_desc->AddItem("Eff Rating:") - 1; + + if (grp->Points() >= 0) { + if (grp->Sorties() > 0) + sprintf(txt, "%.1f", (double) grp->Points() / grp->Sorties()); + else + sprintf(txt, "%.1f", (double) grp->Points()); + } + else { + strcpy(txt, "Unavail"); + } + + lst_desc->SetItemText(n, 1, txt); + } + + // else (if high-level) show components + else { + int n; + + n = lst_desc->AddItem("Group:") - 1; + lst_desc->SetItemText(n, 1, grp->GetDescription()); + + ListIter c = grp->GetLiveComponents(); + while (++c) { + n = lst_desc->AddItem("-") - 1; + lst_desc->SetItemText(n, 1, c->GetDescription()); + } + } + } + // combat unit + else { + CombatUnit* unit = (CombatUnit*) data; + current_group = unit->GetCombatGroup(); + current_unit = unit; + + int n; + char txt[64]; + + n = lst_desc->AddItem("Unit:") - 1; + lst_desc->SetItemText(n, 1, unit->GetDescription()); + n = lst_desc->AddItem("Sector:") - 1; + lst_desc->SetItemText(n, 1, unit->GetRegion()); + + const ShipDesign* design = unit->GetDesign(); + if (design) { + lst_desc->AddItem(" "); + n = lst_desc->AddItem("Type:") - 1; + lst_desc->SetItemText(n, 1, Ship::ClassName(design->type)); + n = lst_desc->AddItem("Class:") - 1; + lst_desc->SetItemText(n, 1, design->DisplayName()); + + if (design->type < Ship::STATION) + FormatNumber(txt, design->radius/2); + else + FormatNumber(txt, design->radius*2); + + strcat(txt, " m"); + + n = lst_desc->AddItem("Length:") - 1; + lst_desc->SetItemText(n, 1, txt); + + FormatNumber(txt, design->mass); + strcat(txt, " T"); + + n = lst_desc->AddItem("Mass:") - 1; + lst_desc->SetItemText(n, 1, txt); + + FormatNumber(txt, design->integrity); + n = lst_desc->AddItem("Hull:") - 1; + lst_desc->SetItemText(n, 1, txt); + + if (design->weapons.size()) { + lst_desc->AddItem(" "); + n = lst_desc->AddItem("Weapons:") - 1; + + WepGroup groups[8]; + for (int w = 0; w < design->weapons.size(); w++) { + Weapon* gun = design->weapons[w]; + WepGroup* group = FindWepGroup(groups, gun->Group()); + + if (group) + group->count++; + } + + for (int g = 0; g < 8; g++) { + WepGroup* group = &groups[g]; + if (group && group->count) { + sprintf(txt, "%s (%d)", group->name.data(), group->count); + if (g > 0) n = lst_desc->AddItem(" ") - 1; + lst_desc->SetItemText(n, 1, txt); + } + } + } + } + } + } + + else { + List& combatants = campaign->GetCombatants(); + Combatant* c = combatants[0]; + + if (cmb_forces) { + Text name = cmb_forces->GetSelectedItem(); + + for (int i = 0; i < combatants.size(); i++) { + c = combatants[i]; + + if (name == c->Name()) { + break; + } + } + } + + if (c) { + ShowCombatant(c); + + lst_combat->ScrollTo(top_index); + lst_combat->SetSelected(old_index); + } + } + } + + if (btn_transfer && campaign && current_group) + btn_transfer->SetEnabled( campaign->IsActive() && + CanTransfer(current_group) ); +} + +// +--------------------------------------------------------------------+ + +bool +CmdForceDlg::CanTransfer(CombatGroup* grp) +{ + if (!grp || !campaign) + return false; + + if (grp->Type() < CombatGroup::WING) + return false; + + if (grp->Type() > CombatGroup::CARRIER_GROUP) + return false; + + if (grp->Type() == CombatGroup::FLEET || + grp->Type() == CombatGroup::LCA_SQUADRON) + return false; + + CombatGroup* player_group = campaign->GetPlayerGroup(); + if (player_group->GetIFF() != grp->GetIFF()) + return false; + + return true; +} + + +// +--------------------------------------------------------------------+ + +void +CmdForceDlg::OnSave(AWEvent* event) +{ + CmdDlg::OnSave(event); +} + +void +CmdForceDlg::OnExit(AWEvent* event) +{ + CmdDlg::OnExit(event); +} + +void +CmdForceDlg::OnMode(AWEvent* event) +{ + CmdDlg::OnMode(event); +} + + +// +--------------------------------------------------------------------+ + +void +CmdForceDlg::OnTransfer(AWEvent* event) +{ + if (campaign && current_group) { + + // check player rank/responsibility: + Player* player = Player::GetCurrentPlayer(); + int cmd_class = Ship::FIGHTER; + + switch (current_group->Type()) { + case CombatGroup::WING: + case CombatGroup::INTERCEPT_SQUADRON: + case CombatGroup::FIGHTER_SQUADRON: + cmd_class = Ship::FIGHTER; + break; + + case CombatGroup::ATTACK_SQUADRON: + cmd_class = Ship::ATTACK; + break; + + case CombatGroup::LCA_SQUADRON: + cmd_class = Ship::LCA; + break; + + case CombatGroup::DESTROYER_SQUADRON: + cmd_class = Ship::DESTROYER; + break; + + case CombatGroup::BATTLE_GROUP: + cmd_class = Ship::CRUISER; + break; + + case CombatGroup::CARRIER_GROUP: + case CombatGroup::FLEET: + cmd_class = Ship::CARRIER; + break; + } + + char transfer_info[512]; + + if (player->CanCommand(cmd_class)) { + if (current_unit) { + campaign->SetPlayerUnit(current_unit); + + sprintf(transfer_info, "Your transfer request has been approved, %s %s. You are now assigned to the %s. Good luck.\n\nFleet Admiral A. Evars FORCOM\nCommanding", + Player::RankName(player->Rank()), + player->Name().data(), + current_unit->GetDescription()); + } + else { + campaign->SetPlayerGroup(current_group); + + sprintf(transfer_info, "Your transfer request has been approved, %s %s. You are now assigned to the %s. Good luck.\n\nFleet Admiral A. Evars FORCOM\nCommanding", + Player::RankName(player->Rank()), + player->Name().data(), + current_group->GetDescription()); + } + + Button::PlaySound(Button::SND_ACCEPT); + + CmdMsgDlg* msgdlg = manager->GetCmdMsgDlg(); + msgdlg->Title()->SetText("Transfer Approved"); + msgdlg->Message()->SetText(transfer_info); + msgdlg->Message()->SetTextAlign(DT_LEFT); + + manager->ShowCmdMsgDlg(); + } + + else { + Button::PlaySound(Button::SND_REJECT); + + sprintf(transfer_info, "Your transfer request has been denied, %s %s. The %s requires a command rank of %s. Please return to your unit and your duties.\n\nFleet Admiral A. Evars FORCOM\nCommanding", + Player::RankName(player->Rank()), + player->Name().data(), + current_group->GetDescription(), + Player::RankName(Player::CommandRankRequired(cmd_class))); + + CmdMsgDlg* msgdlg = manager->GetCmdMsgDlg(); + msgdlg->Title()->SetText("Transfer Denied"); + msgdlg->Message()->SetText(transfer_info); + msgdlg->Message()->SetTextAlign(DT_LEFT); + + manager->ShowCmdMsgDlg(); + } + } +} diff --git a/Stars45/CmdForceDlg.h b/Stars45/CmdForceDlg.h new file mode 100644 index 0000000..86fb348 --- /dev/null +++ b/Stars45/CmdForceDlg.h @@ -0,0 +1,69 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CmdForceDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Operational Command Dialog (Order of Battle Tab) +*/ + +#ifndef CmdForceDlg_h +#define CmdForceDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "CmdDlg.h" +#include "Bitmap.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Font.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class CmdForceDlg : public FormWindow, + public CmdDlg +{ +public: + CmdForceDlg(Screen* s, FormDef& def, CmpnScreen* mgr); + virtual ~CmdForceDlg(); + + virtual void RegisterControls(); + virtual void Show(); + virtual void ExecFrame(); + + // Operations: + virtual void OnMode(AWEvent* event); + virtual void OnSave(AWEvent* event); + virtual void OnExit(AWEvent* event); + virtual void OnForces(AWEvent* event); + virtual void OnCombat(AWEvent* event); + virtual void OnTransfer(AWEvent* event); + +protected: + void ShowCombatant(Combatant* c); + void AddCombatGroup(CombatGroup* grp, bool last_child=false); + bool CanTransfer(CombatGroup* grp); + bool IsVisible(Combatant* c); + + CmpnScreen* manager; + + ComboBox* cmb_forces; + ListBox* lst_combat; + ListBox* lst_desc; + Button* btn_transfer; + + Starshatter* stars; + Campaign* campaign; + CombatGroup* current_group; + CombatUnit* current_unit; +}; + +#endif CmdForceDlg_h + diff --git a/Stars45/CmdIntelDlg.cpp b/Stars45/CmdIntelDlg.cpp new file mode 100644 index 0000000..a08e816 --- /dev/null +++ b/Stars45/CmdIntelDlg.cpp @@ -0,0 +1,393 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CmdIntelDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Operational Command Dialog (Intel/Newsfeed Tab) +*/ + +#include "MemDebug.h" +#include "CmdIntelDlg.h" +#include "CmpnScreen.h" +#include "Starshatter.h" +#include "Campaign.h" +#include "Combatant.h" +#include "CombatEvent.h" +#include "CombatGroup.h" +#include "CombatUnit.h" +#include "ShipDesign.h" +#include "Starshatter.h" +#include "Sim.h" +#include "CameraDirector.h" + +#include "Game.h" +#include "DataLoader.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Slider.h" +#include "Video.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "ParseUtil.h" +#include "FormatUtil.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +DEF_MAP_CLIENT(CmdIntelDlg, OnMode); +DEF_MAP_CLIENT(CmdIntelDlg, OnSave); +DEF_MAP_CLIENT(CmdIntelDlg, OnExit); +DEF_MAP_CLIENT(CmdIntelDlg, OnNews); +DEF_MAP_CLIENT(CmdIntelDlg, OnPlay); + +// +--------------------------------------------------------------------+ + +CmdIntelDlg::CmdIntelDlg(Screen* s, FormDef& def, CmpnScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), CmdDlg(mgr), manager(mgr), + stars(0), campaign(0), update_time(0), start_scene(0), + cam_view(0), dsp_view(0) +{ + stars = Starshatter::GetInstance(); + campaign = Campaign::GetCampaign(); + + if (campaign) + update_time = campaign->GetUpdateTime(); + + Init(def); +} + +CmdIntelDlg::~CmdIntelDlg() +{ +} + +// +--------------------------------------------------------------------+ + +void +CmdIntelDlg::RegisterControls() +{ + lst_news = (ListBox*) FindControl(401); + txt_news = (RichTextBox*) FindControl(402); + img_news = (ImageBox*) FindControl(403); + mov_news = FindControl(404); + btn_play = (Button*) FindControl(405); + + RegisterCmdControls(this); + + if (btn_save) + REGISTER_CLIENT(EID_CLICK, btn_save, CmdIntelDlg, OnSave); + + if (btn_exit) + REGISTER_CLIENT(EID_CLICK, btn_exit, CmdIntelDlg, OnExit); + + if (btn_play) + REGISTER_CLIENT(EID_CLICK, btn_play, CmdIntelDlg, OnPlay); + + for (int i = 0; i < 5; i++) { + if (btn_mode[i]) + REGISTER_CLIENT(EID_CLICK, btn_mode[i], CmdIntelDlg, OnMode); + } + + if (lst_news) { + REGISTER_CLIENT(EID_SELECT, lst_news, CmdIntelDlg, OnNews); + } + + if (img_news) { + img_news->GetPicture(bmp_default); + } + + if (mov_news) { + CameraDirector* cam_dir = CameraDirector::GetInstance(); + cam_view = new(__FILE__,__LINE__) CameraView(mov_news, cam_dir->GetCamera(), 0); + if (cam_view) + mov_news->AddView(cam_view); + + dsp_view = DisplayView::GetInstance(); + if (dsp_view) { + dsp_view->SetWindow(mov_news); + mov_news->AddView(dsp_view); + } + + mov_news->Hide(); + } +} + +// +--------------------------------------------------------------------+ + +void +CmdIntelDlg::Show() +{ + mode = MODE_INTEL; + + FormWindow::Show(); + ShowCmdDlg(); + + if (btn_play) + btn_play->Hide(); + + if (mov_news) + mov_news->Hide(); +} + +// +--------------------------------------------------------------------+ + +void +CmdIntelDlg::ExecFrame() +{ + CmdDlg::ExecFrame(); + + if (campaign != Campaign::GetCampaign() || campaign->GetUpdateTime() != update_time) { + campaign = Campaign::GetCampaign(); + update_time = campaign->GetUpdateTime(); + + lst_news->ClearItems(); + txt_news->SetText(""); + + if (img_news) + img_news->SetPicture(bmp_default); + } + + if (campaign) { + List& events = campaign->GetEvents(); + bool auto_scroll = false; + + if (events.size() > lst_news->NumItems()) { + while (events.size() > lst_news->NumItems()) { + CombatEvent* info = events[lst_news->NumItems()]; + + const char* unread = info->Visited() ? " " : "*"; + int i = lst_news->AddItemWithData(unread, (DWORD) info) - 1; + + char dateline[32]; + FormatDayTime(dateline, info->Time()); + lst_news->SetItemText(i, 1, dateline); + lst_news->SetItemText(i, 2, info->Title()); + lst_news->SetItemText(i, 3, info->Region()); + lst_news->SetItemText(i, 4, Game::GetText(info->SourceName())); + + if (!info->Visited()) + auto_scroll = true; + } + + if (lst_news->GetSortColumn() > 0) + lst_news->SortItems(); + } + + else if (events.size() < lst_news->NumItems()) { + lst_news->ClearItems(); + + for (int i = 0; i < events.size(); i++) { + CombatEvent* info = events[i]; + + const char* unread = info->Visited() ? " " : "*"; + int i = lst_news->AddItemWithData(unread, (DWORD) info) - 1; + + char dateline[32]; + FormatDayTime(dateline, info->Time()); + lst_news->SetItemText(i, 1, dateline); + lst_news->SetItemText(i, 2, info->Title()); + lst_news->SetItemText(i, 3, info->Region()); + lst_news->SetItemText(i, 4, Game::GetText(info->SourceName())); + + if (!info->Visited()) + auto_scroll = true; + } + + if (lst_news->GetSortColumn() > 0) + lst_news->SortItems(); + + txt_news->SetText(""); + + if (img_news) + img_news->SetPicture(bmp_default); + } + + if (auto_scroll) { + int first_unread = -1; + + for (int i = 0; i < lst_news->NumItems(); i++) { + if (lst_news->GetItemText(i, 0) == "*") { + first_unread = i; + break; + } + } + + if (first_unread >= 0) + lst_news->ScrollTo(first_unread); + } + } + + Starshatter* stars = Starshatter::GetInstance(); + + if (start_scene > 0) { + ShowMovie(); + + start_scene--; + + if (start_scene == 0) { + if (stars && campaign) { + stars->ExecCutscene(event_scene, campaign->Path()); + + if (stars->InCutscene()) { + Sim* sim = Sim::GetSim(); + + if (sim) { + cam_view->UseCamera(CameraDirector::GetInstance()->GetCamera()); + cam_view->UseScene(sim->GetScene()); + } + } + } + + event_scene = ""; + } + } + else { + if (dsp_view) + dsp_view->ExecFrame(); + + if (stars->InCutscene()) + ShowMovie(); + else + HideMovie(); + } +} + +// +--------------------------------------------------------------------+ + +void +CmdIntelDlg::OnSave(AWEvent* event) +{ + CmdDlg::OnSave(event); +} + +void +CmdIntelDlg::OnExit(AWEvent* event) +{ + CmdDlg::OnExit(event); +} + +void +CmdIntelDlg::OnMode(AWEvent* event) +{ + CmdDlg::OnMode(event); +} + + +// +--------------------------------------------------------------------+ + +void +CmdIntelDlg::OnNews(AWEvent* e) +{ + CombatEvent* event = 0; + int index = lst_news->GetSelection(); + + if (index >= 0) { + event = (CombatEvent*) lst_news->GetItemData(index, 0); + } + + if (event) { + Text info(""); + info += event->Title(); + info += "\n\n"; + info += event->Information(); + + txt_news->SetText(info); + txt_news->EnsureVisible(0); + + lst_news->SetItemText(index, 0, " "); + + if (img_news) { + if (event->Image().Width() >= 64) + img_news->SetPicture(event->Image()); + else + img_news->SetPicture(bmp_default); + } + + if (btn_play) { + if (event->SceneFile() && *event->SceneFile()) + btn_play->Show(); + else + btn_play->Hide(); + } + + if (!event->Visited() && btn_play->IsEnabled()) + OnPlay(0); + + event->SetVisited(true); + } + else { + txt_news->SetText(""); + + if (img_news) + img_news->SetPicture(bmp_default); + + if (btn_play) + btn_play->Hide(); + } +} + +// +--------------------------------------------------------------------+ + +void +CmdIntelDlg::OnPlay(AWEvent* e) +{ + CombatEvent* event = 0; + int index = lst_news->GetSelection(); + + if (index >= 0) { + event = (CombatEvent*) lst_news->GetItemData(index, 0); + } + + if (mov_news && cam_view && event && event->SceneFile() && *event->SceneFile()) { + event_scene = event->SceneFile(); + start_scene = 2; + ShowMovie(); + } +} + +void +CmdIntelDlg::ShowMovie() +{ + if (mov_news) { + mov_news->Show(); + dsp_view->SetWindow(mov_news); + + if (img_news) img_news->Hide(); + if (txt_news) txt_news->Hide(); + if (btn_play) btn_play->Hide(); + } +} + +void +CmdIntelDlg::HideMovie() +{ + CombatEvent* event = 0; + int index = lst_news->GetSelection(); + bool play = false; + + if (index >= 0) { + event = (CombatEvent*) lst_news->GetItemData(index, 0); + + if (event && event->SceneFile() && *event->SceneFile()) + play = true; + } + + if (mov_news) { + mov_news->Hide(); + + if (img_news) img_news->Show(); + if (txt_news) txt_news->Show(); + if (btn_play) { + if (play) + btn_play->Show(); + else + btn_play->Hide(); + } + } +} diff --git a/Stars45/CmdIntelDlg.h b/Stars45/CmdIntelDlg.h new file mode 100644 index 0000000..ee86d60 --- /dev/null +++ b/Stars45/CmdIntelDlg.h @@ -0,0 +1,76 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CmdIntelDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Operational Command Dialog (Intel/Newsfeed Tab) +*/ + +#ifndef CmdIntelDlg_h +#define CmdIntelDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "CmdDlg.h" +#include "CameraView.h" +#include "DisplayView.h" + +#include "Bitmap.h" +#include "Button.h" +#include "ImageBox.h" +#include "ListBox.h" +#include "RichTextBox.h" +#include "Font.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class CmdIntelDlg : public FormWindow, + public CmdDlg +{ +public: + CmdIntelDlg(Screen* s, FormDef& def, CmpnScreen* mgr); + virtual ~CmdIntelDlg(); + + virtual void RegisterControls(); + virtual void Show(); + virtual void ExecFrame(); + + // Operations: + virtual void OnMode(AWEvent* event); + virtual void OnSave(AWEvent* event); + virtual void OnExit(AWEvent* event); + virtual void OnNews(AWEvent* event); + virtual void OnPlay(AWEvent* event); + +protected: + virtual void ShowMovie(); + virtual void HideMovie(); + + CmpnScreen* manager; + + ListBox* lst_news; + RichTextBox* txt_news; + ImageBox* img_news; + Button* btn_play; + ActiveWindow* mov_news; + CameraView* cam_view; + DisplayView* dsp_view; + + Bitmap bmp_default; + + Starshatter* stars; + Campaign* campaign; + double update_time; + int start_scene; + Text event_scene; +}; + +#endif CmdIntelDlg_h + diff --git a/Stars45/CmdMissionsDlg.cpp b/Stars45/CmdMissionsDlg.cpp new file mode 100644 index 0000000..2df5ebd --- /dev/null +++ b/Stars45/CmdMissionsDlg.cpp @@ -0,0 +1,325 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CmdMissionsDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Operational Command Dialog (Mission List Tab) +*/ + +#include "MemDebug.h" +#include "CmdMissionsDlg.h" +#include "CmpnScreen.h" +#include "Starshatter.h" +#include "Campaign.h" +#include "Combatant.h" +#include "CombatAssignment.h" +#include "CombatGroup.h" +#include "CombatUnit.h" +#include "CombatZone.h" +#include "Mission.h" +#include "ShipDesign.h" +#include "Player.h" + +#include "Game.h" +#include "DataLoader.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Slider.h" +#include "Video.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "ParseUtil.h" +#include "FormatUtil.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +DEF_MAP_CLIENT(CmdMissionsDlg, OnMode); +DEF_MAP_CLIENT(CmdMissionsDlg, OnSave); +DEF_MAP_CLIENT(CmdMissionsDlg, OnExit); +DEF_MAP_CLIENT(CmdMissionsDlg, OnMission); +DEF_MAP_CLIENT(CmdMissionsDlg, OnAccept); + +// +--------------------------------------------------------------------+ + +CmdMissionsDlg::CmdMissionsDlg(Screen* s, FormDef& def, CmpnScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), CmdDlg(mgr), manager(mgr), + lst_missions(0), txt_desc(0), btn_accept(0), + stars(0), campaign(0), mission(0) +{ + stars = Starshatter::GetInstance(); + campaign = Campaign::GetCampaign(); + + Init(def); +} + +CmdMissionsDlg::~CmdMissionsDlg() +{ +} + +// +--------------------------------------------------------------------+ + +void +CmdMissionsDlg::RegisterControls() +{ + lst_missions = (ListBox*) FindControl(401); + txt_desc = FindControl(402); + btn_accept = (Button*) FindControl(403); + + RegisterCmdControls(this); + + if (btn_save) + REGISTER_CLIENT(EID_CLICK, btn_save, CmdMissionsDlg, OnSave); + + if (btn_exit) + REGISTER_CLIENT(EID_CLICK, btn_exit, CmdMissionsDlg, OnExit); + + for (int i = 0; i < 5; i++) { + if (btn_mode[i]) + REGISTER_CLIENT(EID_CLICK, btn_mode[i], CmdMissionsDlg, OnMode); + } + + if (lst_missions) { + REGISTER_CLIENT(EID_SELECT, lst_missions, CmdMissionsDlg, OnMission); + } + + if (btn_accept) { + btn_accept->SetEnabled(false); + REGISTER_CLIENT(EID_CLICK, btn_accept, CmdMissionsDlg, OnAccept); + } +} + +// +--------------------------------------------------------------------+ + +void +CmdMissionsDlg::Show() +{ + mode = MODE_MISSIONS; + + FormWindow::Show(); + ShowCmdDlg(); + + campaign = Campaign::GetCampaign(); + + if (campaign) { + if (lst_missions) { + lst_missions->ClearItems(); + + Player* player = Player::GetCurrentPlayer(); + List& missions = campaign->GetMissionList(); + for (int i = 0; i < missions.size(); i++) { + MissionInfo* info = missions[i]; + lst_missions->AddItemWithData(info->name, info->id); + + Mission* m = info->mission; + if (m) { + if (m->Type() == Mission::TRAINING && player->HasTrained(m->Identity())) { + lst_missions->SetItemText(i, 1, Game::GetText("CmdMissionsDlg.training")); + } + else { + lst_missions->SetItemText(i, 1, m->TypeName()); + } + } + + char start_time[64]; + FormatDayTime(start_time, info->start); + lst_missions->SetItemText(i, 2, start_time); + } + } + } +} + +void +CmdMissionsDlg::ExecFrame() +{ + CmdDlg::ExecFrame(); + + if (campaign) { + List& missions = campaign->GetMissionList(); + Player* player = Player::GetCurrentPlayer(); + + if (missions.size() > lst_missions->NumItems()) { + while (missions.size() > lst_missions->NumItems()) { + MissionInfo* info = missions[lst_missions->NumItems()]; + int i = lst_missions->AddItemWithData(info->name, info->id) - 1; + + Mission* m = info->mission; + if (m) { + if (m->Type() == Mission::TRAINING && player->HasTrained(m->Identity())) { + lst_missions->SetItemText(i, 1, Game::GetText("CmdMissionsDlg.training")); + } + else { + lst_missions->SetItemText(i, 1, m->TypeName()); + } + } + + char start_time[64]; + FormatDayTime(start_time, info->start); + lst_missions->SetItemText(i, 2, start_time); + } + } + + else if (missions.size() < lst_missions->NumItems()) { + lst_missions->ClearItems(); + + for (int i = 0; i < missions.size(); i++) { + MissionInfo* info = missions[i]; + lst_missions->AddItemWithData(info->name, info->id); + + Mission* m = info->mission; + if (m) { + if (m->Type() == Mission::TRAINING && player->HasTrained(m->Identity())) { + lst_missions->SetItemText(i, 1, Game::GetText("CmdMissionsDlg.training")); + } + else { + lst_missions->SetItemText(i, 1, m->TypeName()); + } + } + + char start_time[64]; + FormatDayTime(start_time, info->start); + lst_missions->SetItemText(i, 2, start_time); + } + } + + else if (missions.size() > 0 && lst_missions->NumItems() > 0) { + int id = lst_missions->GetItemData(0); + MissionInfo* info = campaign->GetMissionInfo(id); + + if (!info) { + int seln = -1; + int seln_id = 0; + + for (int i = 0; i < lst_missions->NumItems(); i++) + if (lst_missions->IsSelected(i)) + seln = i; + + if (seln >= 0) + seln_id = lst_missions->GetItemData(seln); + + lst_missions->ClearItems(); + seln = -1; + + for (i = 0; i < missions.size(); i++) { + MissionInfo* info = missions[i]; + lst_missions->AddItemWithData(info->name, info->id); + + Mission* m = info->mission; + if (m) { + if (m->Type() == Mission::TRAINING && player->HasTrained(m->Identity())) { + lst_missions->SetItemText(i, 1, Game::GetText("CmdMissionsDlg.training")); + } + else { + lst_missions->SetItemText(i, 1, m->TypeName()); + } + } + + char start_time[64]; + FormatDayTime(start_time, info->start); + lst_missions->SetItemText(i, 2, start_time); + + if (info->id == seln_id) + seln = i; + } + + if (seln >= 0) + lst_missions->SetSelected(seln); + } + } + + bool found = false; + + for (int i = 0; i < missions.size() && !found; i++) { + MissionInfo* info = missions[i]; + if (info->mission == mission) + found = true; + } + + if (!found) { + mission = 0; + txt_desc->SetText(""); + btn_accept->SetEnabled(false); + } + } +} + +// +--------------------------------------------------------------------+ + +void +CmdMissionsDlg::OnSave(AWEvent* event) +{ + CmdDlg::OnSave(event); +} + +void +CmdMissionsDlg::OnExit(AWEvent* event) +{ + CmdDlg::OnExit(event); +} + +void +CmdMissionsDlg::OnMode(AWEvent* event) +{ + CmdDlg::OnMode(event); +} + + +// +--------------------------------------------------------------------+ + +void +CmdMissionsDlg::OnMission(AWEvent* event) +{ + if (campaign && lst_missions) { + MissionInfo* info = 0; + mission = 0; + + for (int i = 0; i < lst_missions->NumItems(); i++) { + if (lst_missions->IsSelected(i)) { + int id = lst_missions->GetItemData(i); + info = campaign->GetMissionInfo(id); + } + } + + btn_accept->SetEnabled((info != 0) ? true : false); + + if (info) { + Text desc(""); + desc += info->name; + desc += "\n\n"; + desc += info->player_info; + desc += "\n\n"; + desc += info->description; + + txt_desc->SetText(desc); + mission = info->mission; + } + else { + txt_desc->SetText(" "); + } + } +} + +// +--------------------------------------------------------------------+ + +void +CmdMissionsDlg::OnAccept(AWEvent* event) +{ + if (!campaign || !mission) { + ::Print(" ERROR CMD::Accept campaign=0x%08x mission=0x%08x\n", campaign, mission); + return; + } + + ::Print(" CMD::Accept Mission %d\n", mission->Identity()); + + Mouse::Show(false); + campaign->SetMissionId(mission->Identity()); + campaign->StartMission(); + stars->SetGameMode(Starshatter::PREP_MODE); +} diff --git a/Stars45/CmdMissionsDlg.h b/Stars45/CmdMissionsDlg.h new file mode 100644 index 0000000..b2a060a --- /dev/null +++ b/Stars45/CmdMissionsDlg.h @@ -0,0 +1,65 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CmdMissionsDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Operational Command Dialog (Mission List Tab) +*/ + +#ifndef CmdMissionsDlg_h +#define CmdMissionsDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "CmdDlg.h" +#include "Bitmap.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Font.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class Mission; + +// +--------------------------------------------------------------------+ + +class CmdMissionsDlg : public FormWindow, + public CmdDlg +{ +public: + CmdMissionsDlg(Screen* s, FormDef& def, CmpnScreen* mgr); + virtual ~CmdMissionsDlg(); + + virtual void RegisterControls(); + virtual void Show(); + virtual void ExecFrame(); + + // Operations: + virtual void OnMode(AWEvent* event); + virtual void OnSave(AWEvent* event); + virtual void OnExit(AWEvent* event); + virtual void OnMission(AWEvent* event); + virtual void OnAccept(AWEvent* event); + +protected: + CmpnScreen* manager; + + ListBox* lst_missions; + ActiveWindow* txt_desc; + Button* btn_accept; + + Starshatter* stars; + Campaign* campaign; + Mission* mission; +}; + +#endif CmdMissionsDlg_h + diff --git a/Stars45/CmdMsgDlg.cpp b/Stars45/CmdMsgDlg.cpp new file mode 100644 index 0000000..d151954 --- /dev/null +++ b/Stars45/CmdMsgDlg.cpp @@ -0,0 +1,92 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CmdMsgDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== +*/ + +#include "MemDebug.h" +#include "CmdMsgDlg.h" +#include "CmpnScreen.h" +#include "Starshatter.h" + +#include "Game.h" +#include "ListBox.h" +#include "ComboBox.h" +#include "Button.h" +#include "Keyboard.h" +#include "FormatUtil.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +DEF_MAP_CLIENT(CmdMsgDlg, OnApply); + +// +--------------------------------------------------------------------+ + +CmdMsgDlg::CmdMsgDlg(Screen* s, FormDef& def, CmpnScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), manager(mgr), + exit_latch(false) +{ + Init(def); +} + +CmdMsgDlg::~CmdMsgDlg() +{ +} + +void +CmdMsgDlg::RegisterControls() +{ + title = FindControl(100); + message = FindControl(101); + + apply = (Button*) FindControl(1); + REGISTER_CLIENT(EID_CLICK, apply, CmdMsgDlg, OnApply); +} + +// +--------------------------------------------------------------------+ + +void +CmdMsgDlg::ExecFrame() +{ + if (Keyboard::KeyDown(VK_RETURN)) { + OnApply(0); + } + + if (Keyboard::KeyDown(VK_ESCAPE)) { + if (!exit_latch) + OnApply(0); + + exit_latch = true; + } + else { + exit_latch = false; + } +} + +// +--------------------------------------------------------------------+ + +void +CmdMsgDlg::Show() +{ + FormWindow::Show(); + SetFocus(); +} + +// +--------------------------------------------------------------------+ + +void +CmdMsgDlg::OnApply(AWEvent* event) +{ + if (manager) + manager->CloseTopmost(); +} + +// +--------------------------------------------------------------------+ diff --git a/Stars45/CmdMsgDlg.h b/Stars45/CmdMsgDlg.h new file mode 100644 index 0000000..e089097 --- /dev/null +++ b/Stars45/CmdMsgDlg.h @@ -0,0 +1,54 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CmdMsgDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Navigation Active Window class +*/ + +#ifndef CmdMsgDlg_h +#define CmdMsgDlg_h + +#include "Types.h" +#include "FormWindow.h" + +// +--------------------------------------------------------------------+ + +class CmpnScreen; + +// +--------------------------------------------------------------------+ + +class CmdMsgDlg : public FormWindow +{ +public: + CmdMsgDlg(Screen* s, FormDef& def, CmpnScreen* mgr); + virtual ~CmdMsgDlg(); + + virtual void RegisterControls(); + virtual void Show(); + + // Operations: + virtual void ExecFrame(); + virtual void OnApply(AWEvent* event); + + ActiveWindow* Title() { return title; } + ActiveWindow* Message() { return message; } + +protected: + CmpnScreen* manager; + + ActiveWindow* title; + ActiveWindow* message; + + Button* apply; + bool exit_latch; +}; + +#endif CmdMsgDlg_h + diff --git a/Stars45/CmdOrdersDlg.cpp b/Stars45/CmdOrdersDlg.cpp new file mode 100644 index 0000000..c874d22 --- /dev/null +++ b/Stars45/CmdOrdersDlg.cpp @@ -0,0 +1,139 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CmdOrdersDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Operational Command Dialog (Campaign Orders Tab) +*/ + +#include "MemDebug.h" +#include "CmdOrdersDlg.h" +#include "CmdDlg.h" +#include "CmpnScreen.h" +#include "Starshatter.h" +#include "Campaign.h" +#include "Combatant.h" +#include "CombatGroup.h" +#include "CombatUnit.h" +#include "ShipDesign.h" + +#include "Game.h" +#include "DataLoader.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Slider.h" +#include "Video.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "ParseUtil.h" +#include "FormatUtil.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +DEF_MAP_CLIENT(CmdOrdersDlg, OnMode); +DEF_MAP_CLIENT(CmdOrdersDlg, OnSave); +DEF_MAP_CLIENT(CmdOrdersDlg, OnExit); + +// +--------------------------------------------------------------------+ + +CmdOrdersDlg::CmdOrdersDlg(Screen* s, FormDef& def, CmpnScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), CmdDlg(mgr), manager(mgr), + lbl_orders(0), stars(0), campaign(0) +{ + stars = Starshatter::GetInstance(); + campaign = Campaign::GetCampaign(); + + Init(def); +} + +CmdOrdersDlg::~CmdOrdersDlg() +{ +} + +// +--------------------------------------------------------------------+ + +void +CmdOrdersDlg::RegisterControls() +{ + lbl_orders = FindControl(400); + + RegisterCmdControls(this); + + if (btn_save) + REGISTER_CLIENT(EID_CLICK, btn_save, CmdOrdersDlg, OnSave); + + if (btn_exit) + REGISTER_CLIENT(EID_CLICK, btn_exit, CmdOrdersDlg, OnExit); + + for (int i = 0; i < 5; i++) { + if (btn_mode[i]) + REGISTER_CLIENT(EID_CLICK, btn_mode[i], CmdOrdersDlg, OnMode); + } +} + +// +--------------------------------------------------------------------+ + +void +CmdOrdersDlg::Show() +{ + mode = MODE_ORDERS; + + FormWindow::Show(); + ShowCmdDlg(); + + campaign = Campaign::GetCampaign(); + + if (campaign && lbl_orders) { + Text orders(""); + orders += Game::GetText("CmdOrdersDlg.situation"); + orders += "\n"; + if (*campaign->Situation()) + orders += campaign->Situation(); + else + orders += campaign->Description(); + + orders += "\n\n"; + orders += Game::GetText("CmdOrdersDlg.orders"); + orders += "\n"; + orders += campaign->Orders(); + + lbl_orders->SetText(orders); + } +} + +// +--------------------------------------------------------------------+ + +void +CmdOrdersDlg::ExecFrame() +{ + CmdDlg::ExecFrame(); +} + +// +--------------------------------------------------------------------+ + +void +CmdOrdersDlg::OnSave(AWEvent* event) +{ + CmdDlg::OnSave(event); +} + +void +CmdOrdersDlg::OnExit(AWEvent* event) +{ + CmdDlg::OnExit(event); +} + +void +CmdOrdersDlg::OnMode(AWEvent* event) +{ + CmdDlg::OnMode(event); +} + diff --git a/Stars45/CmdOrdersDlg.h b/Stars45/CmdOrdersDlg.h new file mode 100644 index 0000000..41ff736 --- /dev/null +++ b/Stars45/CmdOrdersDlg.h @@ -0,0 +1,56 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CmdOrdersDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Operational Command Dialog (Campaign Orders Tab) +*/ + +#ifndef CmdOrdersDlg_h +#define CmdOrdersDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "CmdDlg.h" +#include "Bitmap.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Font.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class CmdOrdersDlg : public FormWindow, + public CmdDlg +{ +public: + CmdOrdersDlg(Screen* s, FormDef& def, CmpnScreen* mgr); + virtual ~CmdOrdersDlg(); + + virtual void RegisterControls(); + virtual void ExecFrame(); + virtual void Show(); + + // Operations: + virtual void OnMode(AWEvent* event); + virtual void OnSave(AWEvent* event); + virtual void OnExit(AWEvent* event); + +protected: + CmpnScreen* manager; + + ActiveWindow* lbl_orders; + + Starshatter* stars; + Campaign* campaign; +}; + +#endif CmdOrdersDlg_h + diff --git a/Stars45/CmdTheaterDlg.cpp b/Stars45/CmdTheaterDlg.cpp new file mode 100644 index 0000000..74d351d --- /dev/null +++ b/Stars45/CmdTheaterDlg.cpp @@ -0,0 +1,216 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CmdTheaterDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Operational Command Dialog (Theater Map Tab) +*/ + +#include "MemDebug.h" +#include "CmdTheaterDlg.h" +#include "CmdDlg.h" +#include "CmpnScreen.h" +#include "Galaxy.h" +#include "Starshatter.h" +#include "StarSystem.h" +#include "Campaign.h" +#include "Combatant.h" +#include "CombatGroup.h" +#include "CombatUnit.h" +#include "ShipDesign.h" + +#include "Game.h" +#include "DataLoader.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Slider.h" +#include "Video.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "ParseUtil.h" +#include "FormatUtil.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +DEF_MAP_CLIENT(CmdTheaterDlg, OnMode); +DEF_MAP_CLIENT(CmdTheaterDlg, OnSave); +DEF_MAP_CLIENT(CmdTheaterDlg, OnExit); +DEF_MAP_CLIENT(CmdTheaterDlg, OnView); + +// Supported Selection Modes: + +const int SELECT_NONE = -1; +const int SELECT_SYSTEM = 0; +const int SELECT_PLANET = 1; +const int SELECT_REGION = 2; +const int SELECT_STATION = 3; +const int SELECT_STARSHIP = 4; +const int SELECT_FIGHTER = 5; + +const int VIEW_GALAXY = 0; +const int VIEW_SYSTEM = 1; +const int VIEW_REGION = 2; + +// +--------------------------------------------------------------------+ + +CmdTheaterDlg::CmdTheaterDlg(Screen* s, FormDef& def, CmpnScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), CmdDlg(mgr), manager(mgr), + map_theater(0), map_view(0), stars(0), campaign(0) +{ + stars = Starshatter::GetInstance(); + campaign = Campaign::GetCampaign(); + + Init(def); +} + +CmdTheaterDlg::~CmdTheaterDlg() +{ +} + +// +--------------------------------------------------------------------+ + +void +CmdTheaterDlg::RegisterControls() +{ + map_theater = FindControl(400); + + RegisterCmdControls(this); + + if (btn_save) + REGISTER_CLIENT(EID_CLICK, btn_save, CmdTheaterDlg, OnSave); + + if (btn_exit) + REGISTER_CLIENT(EID_CLICK, btn_exit, CmdTheaterDlg, OnExit); + + for (int i = 0; i < 5; i++) { + if (btn_mode[i]) + REGISTER_CLIENT(EID_CLICK, btn_mode[i], CmdTheaterDlg, OnMode); + } + + if (map_theater) + map_view = new(__FILE__,__LINE__) MapView(map_theater); + + for (i = 0; i < 3; i++) { + view_btn[i] = (Button*) FindControl(401 + i); + REGISTER_CLIENT(EID_CLICK, view_btn[i], CmdTheaterDlg, OnView); + } + + zoom_in_btn = (Button*) FindControl(410); + zoom_out_btn = (Button*) FindControl(411); +} + +// +--------------------------------------------------------------------+ + +void +CmdTheaterDlg::Show() +{ + mode = MODE_THEATER; + + FormWindow::Show(); + ShowCmdDlg(); + + campaign = Campaign::GetCampaign(); + + if (campaign && map_theater) { + map_view->SetCampaign(campaign); + } +} + +// +--------------------------------------------------------------------+ + +void +CmdTheaterDlg::ExecFrame() +{ + CmdDlg::ExecFrame(); + + if (!map_view) + return; + + if (Keyboard::KeyDown(VK_ADD) || + (zoom_in_btn && zoom_in_btn->GetButtonState() > 0)) { + map_view->ZoomIn(); + } + else if (Keyboard::KeyDown(VK_SUBTRACT) || + (zoom_out_btn && zoom_out_btn->GetButtonState() > 0)) { + map_view->ZoomOut(); + } + + else if (Mouse::Wheel() > 0) { + map_view->ZoomIn(); + map_view->ZoomIn(); + map_view->ZoomIn(); + } + + else if (Mouse::Wheel() < 0) { + map_view->ZoomOut(); + map_view->ZoomOut(); + map_view->ZoomOut(); + } +} + +// +--------------------------------------------------------------------+ + +void +CmdTheaterDlg::OnSave(AWEvent* event) +{ + CmdDlg::OnSave(event); +} + +void +CmdTheaterDlg::OnExit(AWEvent* event) +{ + CmdDlg::OnExit(event); +} + +void +CmdTheaterDlg::OnMode(AWEvent* event) +{ + CmdDlg::OnMode(event); +} + +// +--------------------------------------------------------------------+ + +void +CmdTheaterDlg::OnView(AWEvent* event) +{ + int use_filter_mode = -1; + + view_btn[VIEW_GALAXY]->SetButtonState(0); + view_btn[VIEW_SYSTEM]->SetButtonState(0); + view_btn[VIEW_REGION]->SetButtonState(0); + + if (view_btn[0] == event->window) { + if (map_view) map_view->SetViewMode(VIEW_GALAXY); + view_btn[VIEW_GALAXY]->SetButtonState(1); + use_filter_mode = SELECT_SYSTEM; + } + + else if (view_btn[VIEW_SYSTEM] == event->window) { + if (map_view) map_view->SetViewMode(VIEW_SYSTEM); + view_btn[VIEW_SYSTEM]->SetButtonState(1); + use_filter_mode = SELECT_REGION; + } + + else if (view_btn[VIEW_REGION] == event->window) { + if (map_view) map_view->SetViewMode(VIEW_REGION); + view_btn[VIEW_REGION]->SetButtonState(1); + use_filter_mode = SELECT_STARSHIP; + } + + if (use_filter_mode >= 0) { + if (map_view) map_view->SetSelectionMode(use_filter_mode); + } +} + +// +--------------------------------------------------------------------+ + + + diff --git a/Stars45/CmdTheaterDlg.h b/Stars45/CmdTheaterDlg.h new file mode 100644 index 0000000..d549ca4 --- /dev/null +++ b/Stars45/CmdTheaterDlg.h @@ -0,0 +1,62 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CmdTheaterDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Operational Command Dialog (Order of Battle Tab) +*/ + +#ifndef CmdTheaterDlg_h +#define CmdTheaterDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "CmdDlg.h" +#include "Bitmap.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "MapView.h" +#include "Font.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class CmdTheaterDlg : public FormWindow, + public CmdDlg +{ +public: + CmdTheaterDlg(Screen* s, FormDef& def, CmpnScreen* mgr); + virtual ~CmdTheaterDlg(); + + virtual void RegisterControls(); + virtual void Show(); + virtual void ExecFrame(); + + // Operations: + virtual void OnMode(AWEvent* event); + virtual void OnSave(AWEvent* event); + virtual void OnExit(AWEvent* event); + virtual void OnView(AWEvent* event); + +protected: + CmpnScreen* manager; + + ActiveWindow* map_theater; + MapView* map_view; + Button* view_btn[3]; + Button* zoom_in_btn; + Button* zoom_out_btn; + + Starshatter* stars; + Campaign* campaign; +}; + +#endif CmdTheaterDlg_h + diff --git a/Stars45/CmdTitleDlg.cpp b/Stars45/CmdTitleDlg.cpp new file mode 100644 index 0000000..58ae5fc --- /dev/null +++ b/Stars45/CmdTitleDlg.cpp @@ -0,0 +1,78 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CmdTitleDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Operational Command Dialog (Intel/Newsfeed Tab) +*/ + +#include "MemDebug.h" +#include "CmdTitleDlg.h" +#include "CmpnScreen.h" +#include "Starshatter.h" +#include "Campaign.h" +#include "Combatant.h" +#include "CombatEvent.h" +#include "CombatGroup.h" +#include "CombatUnit.h" +#include "ShipDesign.h" + +#include "Game.h" +#include "DataLoader.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Slider.h" +#include "Video.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "ParseUtil.h" +#include "FormatUtil.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +// +--------------------------------------------------------------------+ + +CmdTitleDlg::CmdTitleDlg(Screen* s, FormDef& def, CmpnScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), manager(mgr), + stars(0), campaign(0), showTime(0) +{ + stars = Starshatter::GetInstance(); + campaign = Campaign::GetCampaign(); + + Init(def); +} + +CmdTitleDlg::~CmdTitleDlg() +{ +} + +// +--------------------------------------------------------------------+ + +void +CmdTitleDlg::RegisterControls() +{ + img_title = (ImageBox*) FindControl(200); +} + +// +--------------------------------------------------------------------+ + +void +CmdTitleDlg::Show() +{ + FormWindow::Show(); +} + +// +--------------------------------------------------------------------+ + +void +CmdTitleDlg::ExecFrame() +{ +} diff --git a/Stars45/CmdTitleDlg.h b/Stars45/CmdTitleDlg.h new file mode 100644 index 0000000..70a151d --- /dev/null +++ b/Stars45/CmdTitleDlg.h @@ -0,0 +1,56 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CmdTitleDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Campaign Title Card +*/ + +#ifndef CmdTitleDlg_h +#define CmdTitleDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "Bitmap.h" +#include "Button.h" +#include "ImageBox.h" +#include "ListBox.h" +#include "Font.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class CmpnScreen; +class Campaign; +class Starshatter; + +// +--------------------------------------------------------------------+ + +class CmdTitleDlg : public FormWindow +{ +public: + CmdTitleDlg(Screen* s, FormDef& def, CmpnScreen* mgr); + virtual ~CmdTitleDlg(); + + virtual void RegisterControls(); + virtual void Show(); + virtual void ExecFrame(); + +protected: + CmpnScreen* manager; + + ImageBox* img_title; + + Starshatter* stars; + Campaign* campaign; + double showTime; +}; + +#endif CmdTitleDlg_h + diff --git a/Stars45/CmpCompleteDlg.cpp b/Stars45/CmpCompleteDlg.cpp new file mode 100644 index 0000000..0e47a89 --- /dev/null +++ b/Stars45/CmpCompleteDlg.cpp @@ -0,0 +1,108 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CmpCompleteDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== +*/ + +#include "MemDebug.h" +#include "CmpCompleteDlg.h" +#include "CmpnScreen.h" +#include "Campaign.h" +#include "CombatEvent.h" +#include "Starshatter.h" + +#include "Game.h" +#include "DataLoader.h" +#include "Video.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "ImageBox.h" +#include "Button.h" +#include "FormatUtil.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +DEF_MAP_CLIENT(CmpCompleteDlg, OnClose); + +// +--------------------------------------------------------------------+ + +CmpCompleteDlg::CmpCompleteDlg(Screen* s, FormDef& def, CmpnScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), manager(mgr), + lbl_info(0), img_title(0), btn_close(0) +{ + Init(def); +} + +CmpCompleteDlg::~CmpCompleteDlg() +{ +} + +void +CmpCompleteDlg::RegisterControls() +{ + img_title = (ImageBox*) FindControl(100); + lbl_info = FindControl(101); + btn_close = (Button*) FindControl(1); + + REGISTER_CLIENT(EID_CLICK, btn_close, CmpCompleteDlg, OnClose); +} + +void +CmpCompleteDlg::Show() +{ + FormWindow::Show(); + + Campaign* c = Campaign::GetCampaign(); + + if (img_title && c) { + DataLoader* loader = DataLoader::GetLoader(); + Starshatter* stars = Starshatter::GetInstance(); + CombatEvent* event = c->GetLastEvent(); + char img_name[256]; + + if (event) { + strcpy(img_name, event->ImageFile()); + + if (!strstr(img_name, ".pcx")) { + strcat(img_name, ".pcx"); + } + + if (loader) { + loader->SetDataPath(c->Path()); + loader->LoadBitmap(img_name, banner); + loader->SetDataPath(0); + + Rect tgt_rect; + tgt_rect.w = img_title->Width(); + tgt_rect.h = img_title->Height(); + + img_title->SetTargetRect(tgt_rect); + img_title->SetPicture(banner); + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +CmpCompleteDlg::ExecFrame() +{ +} + +// +--------------------------------------------------------------------+ + +void +CmpCompleteDlg::OnClose(AWEvent* event) +{ + if (manager) + manager->ShowCmdDlg(); +} diff --git a/Stars45/CmpCompleteDlg.h b/Stars45/CmpCompleteDlg.h new file mode 100644 index 0000000..a32a712 --- /dev/null +++ b/Stars45/CmpCompleteDlg.h @@ -0,0 +1,50 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CmpCompleteDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Campaign title card and load progress dialog +*/ + +#ifndef CmpCompleteDlg_h +#define CmpCompleteDlg_h + +#include "Types.h" +#include "FormWindow.h" + +// +--------------------------------------------------------------------+ + +class CmpnScreen; + +// +--------------------------------------------------------------------+ + +class CmpCompleteDlg : public FormWindow +{ +public: + CmpCompleteDlg(Screen* s, FormDef& def, CmpnScreen* mgr); + virtual ~CmpCompleteDlg(); + + virtual void RegisterControls(); + virtual void Show(); + + // Operations: + virtual void ExecFrame(); + virtual void OnClose(AWEvent* event); + +protected: + ImageBox* img_title; + ActiveWindow* lbl_info; + Button* btn_close; + Bitmap banner; + + CmpnScreen* manager; +}; + +#endif CmpCompleteDlg_h + diff --git a/Stars45/CmpFileDlg.cpp b/Stars45/CmpFileDlg.cpp new file mode 100644 index 0000000..22a093d --- /dev/null +++ b/Stars45/CmpFileDlg.cpp @@ -0,0 +1,191 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CmpFileDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Mission Select Dialog Active Window class +*/ + +#include "MemDebug.h" +#include "CmpFileDlg.h" +#include "CmpnScreen.h" +#include "Starshatter.h" +#include "Campaign.h" +#include "CampaignSaveGame.h" +#include "CombatGroup.h" + +#include "Game.h" +#include "DataLoader.h" +#include "Button.h" +#include "EditBox.h" +#include "ListBox.h" +#include "Slider.h" +#include "Video.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "FormatUtil.h" +#include "ParseUtil.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +DEF_MAP_CLIENT(CmpFileDlg, OnSave); +DEF_MAP_CLIENT(CmpFileDlg, OnCancel); +DEF_MAP_CLIENT(CmpFileDlg, OnCampaign); + +// +--------------------------------------------------------------------+ + +CmpFileDlg::CmpFileDlg(Screen* s, FormDef& def, CmpnScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), manager(mgr), + exit_latch(false), btn_save(0), btn_cancel(0), edt_name(0), lst_campaigns(0) +{ + Init(def); +} + +CmpFileDlg::~CmpFileDlg() +{ +} + +// +--------------------------------------------------------------------+ + +void +CmpFileDlg::RegisterControls() +{ + btn_save = (Button*) FindControl(1); + btn_cancel = (Button*) FindControl(2); + + if (btn_save) + REGISTER_CLIENT(EID_CLICK, btn_save, CmpFileDlg, OnSave); + + if (btn_cancel) + REGISTER_CLIENT(EID_CLICK, btn_cancel, CmpFileDlg, OnCancel); + + edt_name = (EditBox*) FindControl(200); + + if (edt_name) + edt_name->SetText(""); + + lst_campaigns = (ListBox*) FindControl(201); + + if (lst_campaigns) + REGISTER_CLIENT(EID_SELECT, lst_campaigns, CmpFileDlg, OnCampaign); +} + +// +--------------------------------------------------------------------+ + +void +CmpFileDlg::Show() +{ + FormWindow::Show(); + + if (lst_campaigns) { + lst_campaigns->ClearItems(); + lst_campaigns->SetLineHeight(12); + + List save_list; + + CampaignSaveGame::GetSaveGameList(save_list); + save_list.sort(); + + for (int i = 0; i < save_list.size(); i++) + lst_campaigns->AddItem(*save_list[i]); + + save_list.destroy(); + } + + if (edt_name) { + char save_name[256]; + save_name[0] = 0; + + campaign = Campaign::GetCampaign(); + if (campaign && campaign->GetPlayerGroup()) { + const char* op_name = campaign->Name(); + char day[32]; + CombatGroup* group = campaign->GetPlayerGroup(); + + if (strstr(op_name, "Operation ")) + op_name += 10; + + FormatDay(day, campaign->GetTime()); + + sprintf(save_name, "%s %s (%s)", + op_name, + day, + group->GetRegion().data()); + } + + edt_name->SetText(save_name); + edt_name->SetFocus(); + } +} + +// +--------------------------------------------------------------------+ + +void +CmpFileDlg::ExecFrame() +{ + if (Keyboard::KeyDown(VK_RETURN)) { + OnSave(0); + } + + if (Keyboard::KeyDown(VK_ESCAPE)) { + if (!exit_latch) + OnCancel(0); + + exit_latch = true; + } + else { + exit_latch = false; + } +} + +// +--------------------------------------------------------------------+ + +void +CmpFileDlg::OnSave(AWEvent* event) +{ + if (edt_name && edt_name->GetText().length() > 0) { + campaign = Campaign::GetCampaign(); + CampaignSaveGame save(campaign); + + char filename[256]; + strcpy(filename, edt_name->GetText()); + char* newline = strchr(filename, '\n'); + if (newline) + *newline = 0; + + save.Save(filename); + save.SaveAuto(); + + if (manager) + manager->HideCmpFileDlg(); + } +} + +void +CmpFileDlg::OnCancel(AWEvent* event) +{ + if (manager) + manager->HideCmpFileDlg(); +} + +// +--------------------------------------------------------------------+ + +void +CmpFileDlg::OnCampaign(AWEvent* event) +{ + int n = lst_campaigns->GetSelection(); + + if (n >= 0) { + Text cmpn = lst_campaigns->GetItemText(n); + + if (edt_name) + edt_name->SetText(cmpn); + } +} \ No newline at end of file diff --git a/Stars45/CmpFileDlg.h b/Stars45/CmpFileDlg.h new file mode 100644 index 0000000..f3b19cd --- /dev/null +++ b/Stars45/CmpFileDlg.h @@ -0,0 +1,64 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CmpFileDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Mission Select Dialog Active Window class +*/ + +#ifndef CmpFileDlg_h +#define CmpFileDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "Bitmap.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Font.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class CmpnScreen; +class Campaign; +class Starshatter; + +// +--------------------------------------------------------------------+ + +class CmpFileDlg : public FormWindow +{ +public: + CmpFileDlg(Screen* s, FormDef& def, CmpnScreen* mgr); + virtual ~CmpFileDlg(); + + virtual void RegisterControls(); + virtual void Show(); + + // Operations: + virtual void ExecFrame(); + + virtual void OnSave(AWEvent* event); + virtual void OnCancel(AWEvent* event); + virtual void OnCampaign(AWEvent* event); + +protected: + CmpnScreen* manager; + Campaign* campaign; + + Button* btn_save; + Button* btn_cancel; + EditBox* edt_name; + ListBox* lst_campaigns; + + bool exit_latch; +}; + +#endif CmpFileDlg_h + diff --git a/Stars45/CmpLoadDlg.cpp b/Stars45/CmpLoadDlg.cpp new file mode 100644 index 0000000..8bcf422 --- /dev/null +++ b/Stars45/CmpLoadDlg.cpp @@ -0,0 +1,118 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CmpLoadDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== +*/ + +#include "MemDebug.h" +#include "CmpLoadDlg.h" +#include "Campaign.h" +#include "Starshatter.h" +#include "FormatUtil.h" + +#include "Game.h" +#include "DataLoader.h" +#include "Video.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "ImageBox.h" +#include "Slider.h" + +// +--------------------------------------------------------------------+ + +CmpLoadDlg::CmpLoadDlg(Screen* s, FormDef& def) + : FormWindow(s, 0, 0, s->Width(), s->Height()), + lbl_progress(0), lbl_activity(0), lbl_title(0), img_title(0), show_time(0) +{ + Init(def); +} + +CmpLoadDlg::~CmpLoadDlg() +{ +} + +void +CmpLoadDlg::RegisterControls() +{ + img_title = (ImageBox*) FindControl(100); + lbl_title = FindControl(200); + lbl_activity = FindControl(101); + lbl_progress = (Slider*) FindControl(102); +} + +void +CmpLoadDlg::Show() +{ + FormWindow::Show(); + + Campaign* campaign = Campaign::GetCampaign(); + + if (campaign) { + Bitmap* bmp = campaign->GetImage(3); + if (img_title && bmp) { + Rect tgt_rect; + tgt_rect.w = img_title->Width(); + tgt_rect.h = img_title->Height(); + + img_title->SetTargetRect(tgt_rect); + img_title->SetPicture(*bmp); + } + + if (lbl_title) + lbl_title->SetText(campaign->Name()); + } + + show_time = Game::RealTime(); +} + +// +--------------------------------------------------------------------+ + +void +CmpLoadDlg::ExecFrame() +{ + Starshatter* stars = Starshatter::GetInstance(); + + if (stars) { + if (lbl_activity) lbl_activity->SetText(stars->GetLoadActivity()); + if (lbl_progress) lbl_progress->SetValue(stars->GetLoadProgress()); + } +} + +// +--------------------------------------------------------------------+ + +void +CmpLoadDlg::MoveTo(const Rect& r) +{ + FormWindow::MoveTo(r); + + Campaign* campaign = Campaign::GetCampaign(); + + if (campaign && img_title && campaign->GetImage(3)) { + Bitmap* bmp = campaign->GetImage(3); + + Rect tgt_rect; + tgt_rect.w = img_title->Width(); + tgt_rect.h = img_title->Height(); + + img_title->SetTargetRect(tgt_rect); + img_title->SetPicture(*bmp); + } +} + +// +--------------------------------------------------------------------+ + +bool +CmpLoadDlg::IsDone() +{ + if (Game::RealTime() - show_time < 5000) + return false; + + return true; +} diff --git a/Stars45/CmpLoadDlg.h b/Stars45/CmpLoadDlg.h new file mode 100644 index 0000000..64dd538 --- /dev/null +++ b/Stars45/CmpLoadDlg.h @@ -0,0 +1,46 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CmpLoadDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Campaign title card and load progress dialog +*/ + +#ifndef CmpLoadDlg_h +#define CmpLoadDlg_h + +#include "Types.h" +#include "FormWindow.h" + +// +--------------------------------------------------------------------+ + +class CmpLoadDlg : public FormWindow +{ +public: + CmpLoadDlg(Screen* s, FormDef& def); + virtual ~CmpLoadDlg(); + + // Operations: + virtual void ExecFrame(); + virtual void MoveTo(const Rect& r); + virtual void RegisterControls(); + virtual void Show(); + + virtual bool IsDone(); + +protected: + ActiveWindow* lbl_activity; + Slider* lbl_progress; + ActiveWindow* lbl_title; + ImageBox* img_title; + DWORD show_time; +}; + +#endif CmpLoadDlg_h + diff --git a/Stars45/CmpSceneDlg.cpp b/Stars45/CmpSceneDlg.cpp new file mode 100644 index 0000000..ccc8a0d --- /dev/null +++ b/Stars45/CmpSceneDlg.cpp @@ -0,0 +1,188 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2006. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CmpSceneDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== +*/ + +#include "MemDebug.h" +#include "CmpSceneDlg.h" +#include "CmpnScreen.h" +#include "GameScreen.h" +#include "Campaign.h" +#include "CombatEvent.h" +#include "Starshatter.h" +#include "Sim.h" +#include "CameraDirector.h" +#include "Mission.h" +#include "MissionEvent.h" + +#include "Game.h" +#include "DataLoader.h" +#include "Video.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "ImageBox.h" +#include "RichTextBox.h" +#include "Button.h" +#include "FormatUtil.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +// +--------------------------------------------------------------------+ + +CmpSceneDlg::CmpSceneDlg(Screen* s, FormDef& def, CmpnScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), manager(mgr), + mov_scene(0), subtitles_box(0), cam_view(0), disp_view(0), old_disp_win(0), + flare1(0), flare2(0), flare3(0), flare4(0), subtitles_delay(0), subtitles_time(0) +{ + Init(def); + + DataLoader* loader = DataLoader::GetLoader(); + const char* oldpath = loader->GetDataPath(); + + loader->SetDataPath(0); + loader->LoadTexture("flare0+.pcx", flare1, Bitmap::BMP_TRANSLUCENT); + loader->LoadTexture("flare2.pcx", flare2, Bitmap::BMP_TRANSLUCENT); + loader->LoadTexture("flare3.pcx", flare3, Bitmap::BMP_TRANSLUCENT); + loader->LoadTexture("flare4.pcx", flare4, Bitmap::BMP_TRANSLUCENT); + loader->SetDataPath(oldpath); +} + +CmpSceneDlg::~CmpSceneDlg() +{ +} + +void +CmpSceneDlg::RegisterControls() +{ + mov_scene = FindControl(101); + subtitles_box = (RichTextBox*) FindControl(102); + + if (mov_scene) { + CameraDirector* cam_dir = CameraDirector::GetInstance(); + cam_view = new(__FILE__,__LINE__) CameraView(mov_scene, cam_dir->GetCamera(), 0); + if (cam_view) + mov_scene->AddView(cam_view); + + disp_view = DisplayView::GetInstance(); + } +} + +void +CmpSceneDlg::Show() +{ + FormWindow::Show(); + + Starshatter* stars = Starshatter::GetInstance(); + + if (stars->InCutscene()) { + Sim* sim = Sim::GetSim(); + + if (sim) { + cam_view->UseCamera(CameraDirector::GetInstance()->GetCamera()); + cam_view->UseScene(sim->GetScene()); + } + + // initialize lens flare bitmaps: + if (stars->LensFlare()) { + cam_view->LensFlareElements(flare1, flare4, flare2, flare3); + cam_view->LensFlare(true); + } + + // if lens flare disabled, just create the corona: + else if (stars->Corona()) { + cam_view->LensFlareElements(flare1, 0, 0, 0); + cam_view->LensFlare(true); + } + + if (disp_view) { + old_disp_win = disp_view->GetWindow(); + + disp_view->SetWindow(mov_scene); + mov_scene->AddView(disp_view); + } + + if (subtitles_box) { + subtitles_box->SetText(stars->GetSubtitles()); + subtitles_delay = 0; + subtitles_time = 0; + } + } +} + +void +CmpSceneDlg::Hide() +{ + FormWindow::Hide(); + + if (disp_view && mov_scene && old_disp_win) { + mov_scene->DelView(disp_view); + disp_view->SetWindow(old_disp_win); + } +} + +// +--------------------------------------------------------------------+ + +CameraView* +CmpSceneDlg::GetCameraView() +{ + return cam_view; +} + +DisplayView* +CmpSceneDlg::GetDisplayView() +{ + return disp_view; +} + +// +--------------------------------------------------------------------+ + +void +CmpSceneDlg::ExecFrame() +{ + Starshatter* stars = Starshatter::GetInstance(); + Mission* cutscene_mission = stars->GetCutsceneMission(); + + if (cutscene_mission && disp_view) { + disp_view->ExecFrame(); + + if (subtitles_box && subtitles_box->GetLineCount() > 0) { + if (subtitles_delay == 0) { + int nlines = subtitles_box->GetLineCount(); + + MissionEvent* begin_scene = cutscene_mission->FindEvent(MissionEvent::BEGIN_SCENE); + MissionEvent* end_scene = cutscene_mission->FindEvent(MissionEvent::END_SCENE); + + if (begin_scene && end_scene) { + double total_time = end_scene->Time() - begin_scene->Time(); + subtitles_delay = total_time / nlines; + subtitles_time = Game::RealTime() / 1000.0 + subtitles_delay; + } + else { + subtitles_delay = -1; + } + } + + if (subtitles_delay > 0) { + double seconds = Game::RealTime() / 1000.0; + + if (subtitles_time <= seconds) { + subtitles_time = seconds + subtitles_delay; + subtitles_box->Scroll(ScrollWindow::SCROLL_DOWN); + } + } + } + } + else { + manager->ShowCmdDlg(); + } +} + diff --git a/Stars45/CmpSceneDlg.h b/Stars45/CmpSceneDlg.h new file mode 100644 index 0000000..86ff8d6 --- /dev/null +++ b/Stars45/CmpSceneDlg.h @@ -0,0 +1,68 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CmpSceneDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Campaign title card and load progress dialog +*/ + +#ifndef CmpSceneDlg_h +#define CmpSceneDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "CameraView.h" +#include "DisplayView.h" + +#include "ImageBox.h" +#include "Font.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class CmpnScreen; + +// +--------------------------------------------------------------------+ + +class CmpSceneDlg : public FormWindow +{ +public: + CmpSceneDlg(Screen* s, FormDef& def, CmpnScreen* mgr); + virtual ~CmpSceneDlg(); + + virtual void RegisterControls(); + virtual void Show(); + virtual void Hide(); + + // Operations: + virtual void ExecFrame(); + + CameraView* GetCameraView(); + DisplayView* GetDisplayView(); + +protected: + ActiveWindow* mov_scene; + RichTextBox* subtitles_box; + CameraView* cam_view; + DisplayView* disp_view; + Window* old_disp_win; + + Bitmap* flare1; + Bitmap* flare2; + Bitmap* flare3; + Bitmap* flare4; + + CmpnScreen* manager; + + double subtitles_delay; + double subtitles_time; +}; + +#endif CmpSceneDlg_h + diff --git a/Stars45/CmpSelectDlg.cpp b/Stars45/CmpSelectDlg.cpp new file mode 100644 index 0000000..39b5923 --- /dev/null +++ b/Stars45/CmpSelectDlg.cpp @@ -0,0 +1,623 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CmpSelectDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Mission Select Dialog Active Window class +*/ + +#include "MemDebug.h" +#include "CmpSelectDlg.h" +#include "ConfirmDlg.h" +#include "MenuScreen.h" +#include "Starshatter.h" +#include "Campaign.h" +#include "CampaignSaveGame.h" +#include "CombatGroup.h" +#include "ShipDesign.h" +#include "Player.h" + +#include "Game.h" +#include "DataLoader.h" +#include "Button.h" +#include "ListBox.h" +#include "Slider.h" +#include "Video.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "ParseUtil.h" +#include "FormatUtil.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +DEF_MAP_CLIENT(CmpSelectDlg, OnNew); +DEF_MAP_CLIENT(CmpSelectDlg, OnSaved); +DEF_MAP_CLIENT(CmpSelectDlg, OnDelete); +DEF_MAP_CLIENT(CmpSelectDlg, OnConfirmDelete); +DEF_MAP_CLIENT(CmpSelectDlg, OnAccept); +DEF_MAP_CLIENT(CmpSelectDlg, OnCancel); +DEF_MAP_CLIENT(CmpSelectDlg, OnCampaignSelect); + +// +--------------------------------------------------------------------+ + +CmpSelectDlg::CmpSelectDlg(Screen* s, FormDef& def, MenuScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), manager(mgr), + lst_campaigns(0), btn_new(0), btn_saved(0), btn_delete(0), + btn_accept(0), btn_cancel(0), description(0), stars(0), campaign(0), + selected_mission(-1), show_saved(false), loading(false), + loaded(false), hproc(0) +{ + stars = Starshatter::GetInstance(); + select_msg = Game::GetText("CmpSelectDlg.select_msg"); + Init(def); +} + +CmpSelectDlg::~CmpSelectDlg() +{ + StopLoadProc(); + images.destroy(); +} + +// +--------------------------------------------------------------------+ + +void +CmpSelectDlg::RegisterControls() +{ + btn_new = (Button*) FindControl(100); + btn_saved = (Button*) FindControl(101); + btn_delete = (Button*) FindControl(102); + btn_accept = (Button*) FindControl( 1); + btn_cancel = (Button*) FindControl( 2); + + if (btn_new) + REGISTER_CLIENT(EID_CLICK, btn_new, CmpSelectDlg, OnNew); + + if (btn_saved) + REGISTER_CLIENT(EID_CLICK, btn_saved, CmpSelectDlg, OnSaved); + + if (btn_delete) { + btn_delete->SetEnabled(false); + REGISTER_CLIENT(EID_CLICK, btn_delete, CmpSelectDlg, OnDelete); + REGISTER_CLIENT(EID_USER_1, btn_delete, CmpSelectDlg, OnConfirmDelete); + } + + if (btn_accept) { + btn_accept->SetEnabled(false); + REGISTER_CLIENT(EID_CLICK, btn_accept, CmpSelectDlg, OnAccept); + } + + if (btn_cancel) { + btn_cancel->SetEnabled(true); + REGISTER_CLIENT(EID_CLICK, btn_cancel, CmpSelectDlg, OnCancel); + } + + description = FindControl(200); + + lst_campaigns = (ListBox*) FindControl(201); + + if (lst_campaigns) + REGISTER_CLIENT(EID_SELECT, lst_campaigns, CmpSelectDlg, OnCampaignSelect); + + ShowNewCampaigns(); +} + +// +--------------------------------------------------------------------+ + +void +CmpSelectDlg::ExecFrame() +{ + if (Keyboard::KeyDown(VK_RETURN)) { + if (btn_accept && btn_accept->IsEnabled()) + OnAccept(0); + } + + AutoThreadSync a(sync); + + if (loaded) { + loaded = false; + + if (btn_cancel) + btn_cancel->SetEnabled(true); + + if (description && btn_accept) { + if (campaign) { + Campaign::SelectCampaign(campaign->Name()); + + if (load_index >= 0) { + if (lst_campaigns) { + images[load_index]->CopyBitmap(*campaign->GetImage(1)); + lst_campaigns->SetItemImage(load_index, images[load_index]); + } + + description->SetText(Text("") + + campaign->Name() + + Text("\n\n") + + Text("") + + Game::GetText("CmpSelectDlg.scenario") + + Text("\n\t") + + campaign->Description()); + } + else { + char time_buf[32]; + char score_buf[32]; + + double t = campaign->GetLoadTime() - campaign->GetStartTime(); + FormatDayTime(time_buf, t); + + sprintf(score_buf, "%d", campaign->GetPlayerTeamScore()); + + Text desc = Text("") + + campaign->Name() + + Text("\n\n") + + Text("") + + Game::GetText("CmpSelectDlg.scenario") + + Text("\n\t") + + campaign->Description() + + Text("\n\n") + + Game::GetText("CmpSelectDlg.campaign-time") + + Text("\n\t") + + time_buf + + Text("\n\n") + + Game::GetText("CmpSelectDlg.assignment") + + Text("\n\t"); + + if (campaign->GetPlayerGroup()) + desc += campaign->GetPlayerGroup()->GetDescription(); + else + desc += "n/a"; + + desc += Text("\n\n") + + Game::GetText("CmpSelectDlg.team-score") + + Text("\n\t") + + score_buf; + + description->SetText(desc); + } + + btn_accept->SetEnabled(true); + + if (btn_delete) + btn_delete->SetEnabled(show_saved); + } + else { + description->SetText(select_msg); + btn_accept->SetEnabled(true); + } + } + } +} + +bool +CmpSelectDlg::CanClose() +{ + AutoThreadSync a(sync); + return !loading; +} + +// +--------------------------------------------------------------------+ + +void +CmpSelectDlg::ShowNewCampaigns() +{ + AutoThreadSync a(sync); + + if (loading && description) { + description->SetText(Game::GetText("CmpSelectDlg.already-loading")); + Button::PlaySound(Button::SND_REJECT); + return; + } + + if (btn_new) + btn_new->SetButtonState(1); + + if (btn_saved) + btn_saved->SetButtonState(0); + + if (btn_delete) + btn_delete->SetEnabled(false); + + if (lst_campaigns) { + images.destroy(); + + lst_campaigns->SetSelectedStyle(ListBox::LIST_ITEM_STYLE_PLAIN); + lst_campaigns->SetLeading(0); + lst_campaigns->ClearItems(); + lst_campaigns->SetLineHeight(100); + + Player* player = Player::GetCurrentPlayer(); + if (!player) return; + + ListIter iter = Campaign::GetAllCampaigns(); + while (++iter) { + Campaign* c = iter.value(); + + if (c->GetCampaignId() < Campaign::SINGLE_MISSIONS) { + Bitmap* bmp = new(__FILE__,__LINE__) Bitmap; + bmp->CopyBitmap(*c->GetImage(0)); + images.append(bmp); + + int n = lst_campaigns->AddImage(bmp) - 1; + lst_campaigns->SetItemText(n, c->Name()); + + // if campaign is not available, show the grayed-out image + +#ifdef STARSHATTER_DEMO_RELEASE + // DEMO VERSION (only one campaign): + if (c->GetCampaignId() > 2) { + images[n]->CopyBitmap(*c->GetImage(2)); + lst_campaigns->SetItemImage(n, images[n]); + } + +#else + // FULL GAME CRITERIA (based on player record): + if (c->GetCampaignId() > 2 && c->GetCampaignId() < 10 && + !player->HasCompletedCampaign(c->GetCampaignId()-1)) { + images[n]->CopyBitmap(*c->GetImage(2)); + lst_campaigns->SetItemImage(n, images[n]); + } + + // Two additional sequences of ten campaigns (10-19 and 20-29) + // for mod authors to use: + else if (c->GetCampaignId() >= 10 && c->GetCampaignId() < 30 && + (c->GetCampaignId() % 10) != 0 && + !player->HasCompletedCampaign(c->GetCampaignId()-1)) { + images[n]->CopyBitmap(*c->GetImage(2)); + lst_campaigns->SetItemImage(n, images[n]); + } + + // NOTE: Campaigns 10, 20, and 30-99 are always enabled if they exist! +#endif + } + } + } + + if (description) + description->SetText(select_msg); + + if (btn_accept) + btn_accept->SetEnabled(false); + + show_saved = false; +} + +// +--------------------------------------------------------------------+ + +void +CmpSelectDlg::ShowSavedCampaigns() +{ + AutoThreadSync a(sync); + + if (loading && description) { + description->SetText(Game::GetText("CmpSelectDlg.already-loading")); + Button::PlaySound(Button::SND_REJECT); + return; + } + + if (btn_new) + btn_new->SetButtonState(0); + + if (btn_saved) + btn_saved->SetButtonState(1); + + if (btn_delete) + btn_delete->SetEnabled(false); + + if (lst_campaigns) { + lst_campaigns->SetSelectedStyle(ListBox::LIST_ITEM_STYLE_FILLED_BOX); + lst_campaigns->SetLeading(4); + lst_campaigns->ClearItems(); + lst_campaigns->SetLineHeight(12); + + List save_list; + + CampaignSaveGame::GetSaveGameList(save_list); + save_list.sort(); + + for (int i = 0; i < save_list.size(); i++) + lst_campaigns->AddItem(*save_list[i]); + + save_list.destroy(); + } + + if (description) + description->SetText(select_msg); + + if (btn_accept) + btn_accept->SetEnabled(false); + + show_saved = true; +} + +// +--------------------------------------------------------------------+ + +void +CmpSelectDlg::OnCampaignSelect(AWEvent* event) +{ + if (description && lst_campaigns) { + AutoThreadSync a(sync); + + if (loading) { + description->SetText(Game::GetText("CmpSelectDlg.already-loading")); + Button::PlaySound(Button::SND_REJECT); + return; + } + + load_index = -1; + load_file = ""; + + Player* player = Player::GetCurrentPlayer(); + if (!player) return; + + // NEW CAMPAIGN: + if (btn_new && btn_new->GetButtonState()) { + List& list = Campaign::GetAllCampaigns(); + + for (int i = 0; i < lst_campaigns->NumItems(); i++) { + Campaign* c = list[i]; + + // is campaign available? + +#ifdef STARSHATTER_DEMO_RELEASE + // DEMO VERSION (only one campaign): + if (c->GetCampaignId() <= 2) +#else + // FULL GAME CRITERIA (based on player record): + if (c->GetCampaignId() <= 2 || + player->HasCompletedCampaign(c->GetCampaignId()-1)) +#endif + { + + if (lst_campaigns->IsSelected(i)) { + images[i]->CopyBitmap(*c->GetImage(1)); + lst_campaigns->SetItemImage(i, images[i]); + + AutoThreadSync a(sync); + load_index = i; + } + else { + images[i]->CopyBitmap(*c->GetImage(0)); + lst_campaigns->SetItemImage(i, images[i]); + } + } + + // if not, then don't select + else { + images[i]->CopyBitmap(*c->GetImage(2)); + lst_campaigns->SetItemImage(i, images[i]); + + if (lst_campaigns->IsSelected(i)) { + description->SetText(select_msg); + } + } + } + } + + // SAVED CAMPAIGN: + else { + int seln = lst_campaigns->GetSelection(); + + if (seln < 0) { + description->SetText(select_msg); + } + + else { + load_index = -1; + load_file = lst_campaigns->GetItemText(seln); + } + } + + if (btn_accept) + btn_accept->SetEnabled(false); + } + + if (!loading && (load_index >= 0 || load_file.length() > 0)) { + if (btn_cancel) + btn_cancel->SetEnabled(false); + + StartLoadProc(); + } +} + +// +--------------------------------------------------------------------+ + +void +CmpSelectDlg::Show() +{ + FormWindow::Show(); + ShowNewCampaigns(); +} + +// +--------------------------------------------------------------------+ + +void +CmpSelectDlg::OnNew(AWEvent* event) +{ + ShowNewCampaigns(); +} + +void +CmpSelectDlg::OnSaved(AWEvent* event) +{ + ShowSavedCampaigns(); +} + +void +CmpSelectDlg::OnDelete(AWEvent* event) +{ + load_file = ""; + + if (lst_campaigns) { + int seln = lst_campaigns->GetSelection(); + + if (seln < 0) { + description->SetText(select_msg); + btn_accept->SetEnabled(false); + } + + else { + load_index = -1; + load_file = lst_campaigns->GetItemText(seln); + } + } + + if (load_file.length()) { + ConfirmDlg* confirm = manager->GetConfirmDlg(); + if (confirm) { + char msg[256]; + sprintf(msg, Game::GetText("CmpSelectDlg.are-you-sure"), load_file.data()); + confirm->SetMessage(msg); + confirm->SetTitle(Game::GetText("CmpSelectDlg.confirm")); + + manager->ShowConfirmDlg(); + } + + else { + OnConfirmDelete(event); + } + } + + ShowSavedCampaigns(); +} + +void +CmpSelectDlg::OnConfirmDelete(AWEvent* event) +{ + if (load_file.length()) { + CampaignSaveGame::Delete(load_file); + } + + ShowSavedCampaigns(); +} + +// +--------------------------------------------------------------------+ + +void +CmpSelectDlg::OnAccept(AWEvent* event) +{ + AutoThreadSync a(sync); + + if (loading) + return; + + // if this is to be a new campaign, + // re-instaniate the campaign object + if (btn_new->GetButtonState()) + Campaign::GetCampaign()->Load(); + else + Game::ResetGameTime(); + + Mouse::Show(false); + stars->SetGameMode(Starshatter::CLOD_MODE); +} + +void +CmpSelectDlg::OnCancel(AWEvent* event) +{ + manager->ShowMenuDlg(); +} + +// +--------------------------------------------------------------------+ + +DWORD WINAPI CmpSelectDlgLoadProc(LPVOID link); + +void +CmpSelectDlg::StartLoadProc() +{ + if (hproc != 0) { + DWORD result = 0; + GetExitCodeThread(hproc, &result); + + if (result != STILL_ACTIVE) { + CloseHandle(hproc); + hproc = 0; + } + else { + return; + } + } + + if (hproc == 0) { + campaign = 0; + loading = true; + loaded = false; + + if (description) + description->SetText(Game::GetText("CmpSelectDlg.loading")); + + DWORD thread_id = 0; + hproc = CreateThread(0, 4096, CmpSelectDlgLoadProc, (LPVOID) this, 0, &thread_id); + + if (hproc == 0) { + static int report = 10; + if (report > 0) { + ::Print("WARNING: CmpSelectDlg() failed to create thread (err=%08x)\n", GetLastError()); + report--; + + if (report == 0) + ::Print(" Further warnings of this type will be supressed.\n"); + } + } + } +} + +void +CmpSelectDlg::StopLoadProc() +{ + if (hproc != 0) { + WaitForSingleObject(hproc, 2500); + CloseHandle(hproc); + hproc = 0; + } +} + +DWORD WINAPI CmpSelectDlgLoadProc(LPVOID link) +{ + CmpSelectDlg* dlg = (CmpSelectDlg*) link; + + if (dlg) + return dlg->LoadProc(); + + return (DWORD) E_POINTER; +} + +DWORD +CmpSelectDlg::LoadProc() +{ + Campaign* c = 0; + + // NEW CAMPAIGN: + if (load_index >= 0) { + List& list = Campaign::GetAllCampaigns(); + + if (load_index < list.size()) { + c = list[load_index]; + c->Load(); + } + } + + // SAVED CAMPAIGN: + else { + CampaignSaveGame savegame; + savegame.Load(load_file); + c = savegame.GetCampaign(); + } + + sync.acquire(); + + loading = false; + loaded = true; + campaign = c; + + sync.release(); + + return 0; +} + +// +--------------------------------------------------------------------+ diff --git a/Stars45/CmpSelectDlg.h b/Stars45/CmpSelectDlg.h new file mode 100644 index 0000000..db4ba37 --- /dev/null +++ b/Stars45/CmpSelectDlg.h @@ -0,0 +1,91 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CmpSelectDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Mission Select Dialog Active Window class +*/ + +#ifndef CmpSelectDlg_h +#define CmpSelectDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "Bitmap.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Font.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class MenuScreen; +class Campaign; +class Starshatter; + +// +--------------------------------------------------------------------+ + +class CmpSelectDlg : public FormWindow +{ +public: + CmpSelectDlg(Screen* s, FormDef& def, MenuScreen* mgr); + virtual ~CmpSelectDlg(); + + virtual void RegisterControls(); + virtual void Show(); + virtual void ExecFrame(); + virtual bool CanClose(); + + // Operations: + virtual void OnCampaignSelect(AWEvent* event); + virtual void OnNew(AWEvent* event); + virtual void OnSaved(AWEvent* event); + virtual void OnDelete(AWEvent* event); + virtual void OnConfirmDelete(AWEvent* event); + virtual void OnAccept(AWEvent* event); + virtual void OnCancel(AWEvent* event); + + virtual DWORD LoadProc(); + +protected: + virtual void StartLoadProc(); + virtual void StopLoadProc(); + virtual void ShowNewCampaigns(); + virtual void ShowSavedCampaigns(); + + MenuScreen* manager; + + Button* btn_new; + Button* btn_saved; + Button* btn_delete; + Button* btn_accept; + Button* btn_cancel; + + ListBox* lst_campaigns; + + ActiveWindow* description; + + Starshatter* stars; + Campaign* campaign; + int selected_mission; + HANDLE hproc; + ThreadSync sync; + bool loading; + bool loaded; + Text load_file; + int load_index; + bool show_saved; + List images; + + Text select_msg; +}; + +#endif CmpSelectDlg_h + diff --git a/Stars45/CmpnScreen.cpp b/Stars45/CmpnScreen.cpp new file mode 100644 index 0000000..0a08a88 --- /dev/null +++ b/Stars45/CmpnScreen.cpp @@ -0,0 +1,647 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2006. All Rights Reserved. + + SUBSYSTEM: Stars + FILE: CmpnScreen.cpp + AUTHOR: John DiCamillo + +*/ + +#include "MemDebug.h" +#include "CmpnScreen.h" + +#include "CmdForceDlg.h" +#include "CmdMissionsDlg.h" +#include "CmdOrdersDlg.h" +#include "CmdTheaterDlg.h" +#include "CmdIntelDlg.h" +#include "CmpCompleteDlg.h" +#include "CmdMsgDlg.h" +#include "CmpFileDlg.h" +#include "CmpSceneDlg.h" +#include "Campaign.h" +#include "CombatEvent.h" +#include "Mission.h" +#include "Sim.h" +#include "Starshatter.h" +#include "StarSystem.h" +#include "Player.h" +#include "MusicDirector.h" + +#include "Game.h" +#include "Video.h" +#include "Screen.h" +#include "Window.h" +#include "ActiveWindow.h" +#include "FormDef.h" +#include "Mouse.h" +#include "Color.h" +#include "Bitmap.h" +#include "Font.h" +#include "FontMgr.h" +#include "EventDispatch.h" +#include "DataLoader.h" +#include "Resource.h" + +// +--------------------------------------------------------------------+ + +CmpnScreen::CmpnScreen() + : screen(0), + cmd_force_dlg(0), cmd_missions_dlg(0), cmd_orders_dlg(0), + cmd_intel_dlg(0), cmd_theater_dlg(0), cmd_msg_dlg(0), cmp_file_dlg(0), + cmp_end_dlg(0), cmp_scene_dlg(0), + isShown(false), campaign(0), stars(0), completion_stage(0) +{ + loader = DataLoader::GetLoader(); + stars = Starshatter::GetInstance(); +} + +CmpnScreen::~CmpnScreen() +{ + TearDown(); +} + +// +--------------------------------------------------------------------+ + +void +CmpnScreen::Setup(Screen* s) +{ + if (!s) + return; + + screen = s; + + loader->UseFileSystem(true); + + FormDef cmd_orders_def("CmdOrdersDlg", 0); + cmd_orders_def.Load("CmdOrdersDlg"); + cmd_orders_dlg = new(__FILE__,__LINE__) CmdOrdersDlg(screen, cmd_orders_def, this); + + FormDef cmd_force_def("CmdForceDlg", 0); + cmd_force_def.Load("CmdForceDlg"); + cmd_force_dlg = new(__FILE__,__LINE__) CmdForceDlg(screen, cmd_force_def, this); + + FormDef cmd_theater_def("CmdTheaterDlg", 0); + cmd_theater_def.Load("CmdTheaterDlg"); + cmd_theater_dlg = new(__FILE__,__LINE__) CmdTheaterDlg(screen, cmd_theater_def, this); + + FormDef cmd_intel_def("CmdIntelDlg", 0); + cmd_intel_def.Load("CmdIntelDlg"); + cmd_intel_dlg = new(__FILE__,__LINE__) CmdIntelDlg(screen, cmd_intel_def, this); + + FormDef cmd_missions_def("CmdMissionsDlg", 0); + cmd_missions_def.Load("CmdMissionsDlg"); + cmd_missions_dlg = new(__FILE__,__LINE__) CmdMissionsDlg(screen, cmd_missions_def, this); + + FormDef file_def("FileDlg", 0); + file_def.Load("FileDlg"); + cmp_file_dlg = new(__FILE__,__LINE__) CmpFileDlg(screen, file_def, this); + + FormDef msg_def("CmdMsgDlg", 0); + msg_def.Load("CmdMsgDlg"); + cmd_msg_dlg = new(__FILE__,__LINE__) CmdMsgDlg(screen, msg_def, this); + + FormDef end_def("CmpCompleteDlg", 0); + end_def.Load("CmpCompleteDlg"); + cmp_end_dlg = new(__FILE__,__LINE__) CmpCompleteDlg(screen, end_def, this); + + FormDef scene_def("CmpSceneDlg", 0); + scene_def.Load("CmpSceneDlg"); + cmp_scene_dlg = new(__FILE__,__LINE__) CmpSceneDlg(screen, scene_def, this); + + loader->UseFileSystem(Starshatter::UseFileSystem()); + + HideAll(); +} + +// +--------------------------------------------------------------------+ + +void +CmpnScreen::TearDown() +{ + if (screen) { + screen->DelWindow(cmd_force_dlg); + screen->DelWindow(cmd_missions_dlg); + screen->DelWindow(cmd_orders_dlg); + screen->DelWindow(cmd_intel_dlg); + screen->DelWindow(cmd_theater_dlg); + screen->DelWindow(cmd_msg_dlg); + screen->DelWindow(cmp_file_dlg); + screen->DelWindow(cmp_end_dlg); + screen->DelWindow(cmp_scene_dlg); + } + + delete cmd_force_dlg; + delete cmd_missions_dlg; + delete cmd_orders_dlg; + delete cmd_intel_dlg; + delete cmd_theater_dlg; + delete cmd_msg_dlg; + delete cmp_file_dlg; + delete cmp_end_dlg; + delete cmp_scene_dlg; + + cmd_force_dlg = 0; + cmd_missions_dlg = 0; + cmd_orders_dlg = 0; + cmd_intel_dlg = 0; + cmd_theater_dlg = 0; + cmd_msg_dlg = 0; + cmp_file_dlg = 0; + cmp_end_dlg = 0; + cmp_scene_dlg = 0; + screen = 0; +} + +// +--------------------------------------------------------------------+ + +void +CmpnScreen::SetFieldOfView(double fov) +{ + if (cmp_scene_dlg) + cmp_scene_dlg->GetCameraView()->SetFieldOfView(fov); +} + +// +--------------------------------------------------------------------+ + +double +CmpnScreen::GetFieldOfView() const +{ + if (cmp_scene_dlg) + return cmp_scene_dlg->GetCameraView()->GetFieldOfView(); + + return 2; +} + +// +--------------------------------------------------------------------+ + +void +CmpnScreen::ExecFrame() +{ + Game::SetScreenColor(Color::Black); + + if (cmd_orders_dlg && cmd_orders_dlg->IsShown()) { + cmd_orders_dlg->ExecFrame(); + } + + else if (cmd_force_dlg && cmd_force_dlg->IsShown()) { + cmd_force_dlg->ExecFrame(); + } + + else if (cmd_theater_dlg && cmd_theater_dlg->IsShown()) { + cmd_theater_dlg->ExecFrame(); + } + + else if (cmd_missions_dlg && cmd_missions_dlg->IsShown()) { + cmd_missions_dlg->ExecFrame(); + } + + else if (cmd_intel_dlg && cmd_intel_dlg->IsShown()) { + cmd_intel_dlg->ExecFrame(); + } + + if (cmp_file_dlg && cmp_file_dlg->IsShown()) { + cmp_file_dlg->ExecFrame(); + } + + if (cmd_msg_dlg && cmd_msg_dlg->IsShown()) { + cmd_msg_dlg->ExecFrame(); + } + + else if (cmp_end_dlg && cmp_end_dlg->IsShown()) { + cmp_end_dlg->ExecFrame(); + completion_stage = 2; + } + + else if (cmp_scene_dlg && cmp_scene_dlg->IsShown()) { + cmp_scene_dlg->ExecFrame(); + + if (completion_stage > 0) + completion_stage = 2; + } + + else if (campaign) { + // if campaign is complete + if (completion_stage == 0) { + Player* player = Player::GetCurrentPlayer(); + char msg_info[1024]; + + if (!player) + return; + + if (campaign->IsTraining()) { + int all_missions = (1<GetMissionList().size())-1; + + if (player->Trained() >= all_missions && player->Trained() < 255) { + player->SetTrained(255); + cmd_msg_dlg->Title()->SetText(Game::GetText("CmpnScreen.training")); + sprintf(msg_info, Game::GetText("CmpnScreen.congrats"), + Player::RankName(player->Rank()), + player->Name().data()); + + cmd_msg_dlg->Message()->SetText(msg_info); + cmd_msg_dlg->Message()->SetTextAlign(DT_LEFT); + + ShowCmdMsgDlg(); + completion_stage = 1; + } + } + + else if (campaign->IsComplete() || campaign->IsFailed()) { + bool cutscene = false; + CombatEvent* event = campaign->GetLastEvent(); + + if (event && !event->Visited() && event->SceneFile() && *event->SceneFile()) { + stars->ExecCutscene(event->SceneFile(), campaign->Path()); + + if (stars->InCutscene()) { + cutscene = true; + ShowCmpSceneDlg(); + } + } + + if (!cutscene) { + ShowCmpCompleteDlg(); + } + + if (campaign->IsComplete()) + MusicDirector::SetMode(MusicDirector::VICTORY); + else + MusicDirector::SetMode(MusicDirector::DEFEAT); + + completion_stage = 1; + } + } + + // if message has been shown, restart + else if (completion_stage > 1) { + completion_stage = 0; + + if (campaign->IsTraining()) { + List& list = Campaign::GetAllCampaigns(); + Campaign* c = list[1]; + + if (c) { + c->Load(); + Campaign::SelectCampaign(c->Name()); + stars->SetGameMode(Starshatter::CLOD_MODE); + return; + } + } + +#ifdef STARSHATTER_DEMO_RELEASE + if (!campaign->IsTraining()) { + Mouse::Show(false); + MusicDirector::SetMode(MusicDirector::MENU); + stars->SetGameMode(Starshatter::MENU_MODE); + return; + } +#endif + + // continue on to the next available campaign: + if (campaign->GetCampaignId() < Campaign::GetLastCampaignId()) { + stars->StartOrResumeGame(); + } + + // if this was the last campaign, just go back to the menu: + else { + Mouse::Show(false); + MusicDirector::SetMode(MusicDirector::MENU); + stars->SetGameMode(Starshatter::MENU_MODE); + return; + } + } + } + + if (completion_stage < 1) { + MusicDirector::SetMode(MusicDirector::MENU); + Mouse::Show(!IsCmpSceneShown()); + } +} + +// +--------------------------------------------------------------------+ + +bool +CmpnScreen::CloseTopmost() +{ + bool processed = false; + + if (IsCmdMsgShown()) { + HideCmdMsgDlg(); + processed = true; + } + + else if (IsCmpFileShown()) { + HideCmpFileDlg(); + processed = true; + } + + return processed; +} + +void +CmpnScreen::Show() +{ + if (!isShown) { + isShown = true; + + campaign = Campaign::GetCampaign(); + completion_stage = 0; + + bool cutscene = false; + CombatEvent* event = 0; + + if (campaign->IsActive() && !campaign->GetEvents().isEmpty()) { + ListIter iter = campaign->GetEvents(); + while (++iter) { + event = iter.value(); + + if (event && !event->Visited() && event->SceneFile() && *event->SceneFile()) { + stars->ExecCutscene(event->SceneFile(), campaign->Path()); + + if (stars->InCutscene()) { + cutscene = true; + ShowCmpSceneDlg(); + } + + event->SetVisited(true); + break; + } + } + } + + if (!cutscene) + ShowCmdDlg(); + } +} + +void +CmpnScreen::Hide() +{ + if (isShown) { + HideAll(); + isShown = false; + } +} + +void +CmpnScreen::HideAll() +{ + if (cmd_force_dlg) cmd_force_dlg->Hide(); + if (cmd_missions_dlg) cmd_missions_dlg->Hide(); + if (cmd_orders_dlg) cmd_orders_dlg->Hide(); + if (cmd_intel_dlg) cmd_intel_dlg->Hide(); + if (cmd_theater_dlg) cmd_theater_dlg->Hide(); + if (cmd_msg_dlg) cmd_msg_dlg->Hide(); + if (cmp_file_dlg) cmp_file_dlg->Hide(); + if (cmp_end_dlg) cmp_end_dlg->Hide(); + if (cmp_scene_dlg) cmp_scene_dlg->Hide(); +} + +// +--------------------------------------------------------------------+ + +void +CmpnScreen::ShowCmdDlg() +{ + ShowCmdOrdersDlg(); +} + +// +--------------------------------------------------------------------+ + +void +CmpnScreen::ShowCmdForceDlg() +{ + HideAll(); + cmd_force_dlg->Show(); + Mouse::Show(true); +} + +// +--------------------------------------------------------------------+ + +void +CmpnScreen::HideCmdForceDlg() +{ + if (IsCmdForceShown()) + cmd_force_dlg->Hide(); +} + +// +--------------------------------------------------------------------+ + +bool +CmpnScreen::IsCmdForceShown() +{ + return cmd_force_dlg && cmd_force_dlg->IsShown(); +} + +// +--------------------------------------------------------------------+ + +void +CmpnScreen::ShowCmdOrdersDlg() +{ + HideAll(); + cmd_orders_dlg->Show(); + Mouse::Show(true); +} + +// +--------------------------------------------------------------------+ + +void +CmpnScreen::HideCmdOrdersDlg() +{ + if (IsCmdOrdersShown()) + cmd_orders_dlg->Hide(); +} + +// +--------------------------------------------------------------------+ + +bool +CmpnScreen::IsCmdOrdersShown() +{ + return cmd_orders_dlg && cmd_orders_dlg->IsShown(); +} + +// +--------------------------------------------------------------------+ + +void +CmpnScreen::ShowCmdMissionsDlg() +{ + HideAll(); + cmd_missions_dlg->Show(); + Mouse::Show(true); +} + +// +--------------------------------------------------------------------+ + +void +CmpnScreen::HideCmdMissionsDlg() +{ + if (IsCmdMissionsShown()) + cmd_missions_dlg->Hide(); +} + +// +--------------------------------------------------------------------+ + +bool +CmpnScreen::IsCmdMissionsShown() +{ + return cmd_missions_dlg && cmd_missions_dlg->IsShown(); +} + +// +--------------------------------------------------------------------+ + +void +CmpnScreen::ShowCmdIntelDlg() +{ + HideAll(); + cmd_intel_dlg->Show(); + Mouse::Show(true); +} + +// +--------------------------------------------------------------------+ + +void +CmpnScreen::HideCmdIntelDlg() +{ + if (IsCmdIntelShown()) + cmd_intel_dlg->Hide(); +} + +// +--------------------------------------------------------------------+ + +bool +CmpnScreen::IsCmdIntelShown() +{ + return cmd_intel_dlg && cmd_intel_dlg->IsShown(); +} + +// +--------------------------------------------------------------------+ + +void +CmpnScreen::ShowCmdTheaterDlg() +{ + HideAll(); + cmd_theater_dlg->Show(); + Mouse::Show(true); +} + +// +--------------------------------------------------------------------+ + +void +CmpnScreen::HideCmdTheaterDlg() +{ + if (IsCmdTheaterShown()) + cmd_theater_dlg->Hide(); +} + +// +--------------------------------------------------------------------+ + +bool +CmpnScreen::IsCmdTheaterShown() +{ + return cmd_theater_dlg && cmd_theater_dlg->IsShown(); +} + +// +--------------------------------------------------------------------+ + +void +CmpnScreen::ShowCmpFileDlg() +{ + cmp_file_dlg->Show(); + Mouse::Show(true); +} + +// +--------------------------------------------------------------------+ + +void +CmpnScreen::HideCmpFileDlg() +{ + if (IsCmpFileShown()) + cmp_file_dlg->Hide(); +} + +// +--------------------------------------------------------------------+ + +bool +CmpnScreen::IsCmpFileShown() +{ + return cmp_file_dlg && cmp_file_dlg->IsShown(); +} + +// +--------------------------------------------------------------------+ + +void +CmpnScreen::ShowCmdMsgDlg() +{ + cmd_msg_dlg->Show(); + Mouse::Show(true); +} + +// +--------------------------------------------------------------------+ + +void +CmpnScreen::HideCmdMsgDlg() +{ + if (IsCmdMsgShown()) + cmd_msg_dlg->Hide(); +} + +// +--------------------------------------------------------------------+ + +bool +CmpnScreen::IsCmdMsgShown() +{ + return cmd_msg_dlg && cmd_msg_dlg->IsShown(); +} + +// +--------------------------------------------------------------------+ + +void +CmpnScreen::ShowCmpCompleteDlg() +{ + cmp_end_dlg->Show(); + Mouse::Show(true); +} + +// +--------------------------------------------------------------------+ + +void +CmpnScreen::HideCmpCompleteDlg() +{ + if (IsCmpCompleteShown()) + cmp_end_dlg->Hide(); +} + +// +--------------------------------------------------------------------+ + +bool +CmpnScreen::IsCmpCompleteShown() +{ + return cmp_end_dlg && cmp_end_dlg->IsShown(); +} + +// +--------------------------------------------------------------------+ + +void +CmpnScreen::ShowCmpSceneDlg() +{ + cmp_scene_dlg->Show(); + Mouse::Show(false); +} + +// +--------------------------------------------------------------------+ + +void +CmpnScreen::HideCmpSceneDlg() +{ + if (IsCmpSceneShown()) + cmp_scene_dlg->Hide(); +} + +// +--------------------------------------------------------------------+ + +bool +CmpnScreen::IsCmpSceneShown() +{ + return cmp_scene_dlg && cmp_scene_dlg->IsShown(); +} diff --git a/Stars45/CmpnScreen.h b/Stars45/CmpnScreen.h new file mode 100644 index 0000000..67ebbac --- /dev/null +++ b/Stars45/CmpnScreen.h @@ -0,0 +1,137 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CmpnScreen.h + AUTHOR: John DiCamillo + +*/ + +#ifndef CmpnScreen_h +#define CmpnScreen_h + +#include "Types.h" +#include "Bitmap.h" +#include "Screen.h" +#include "BaseScreen.h" + +// +--------------------------------------------------------------------+ + +class CmdForceDlg; +class CmdMissionsDlg; +class CmdOrdersDlg; +class CmdIntelDlg; +class CmdTheaterDlg; + +class CmdMsgDlg; +class CmpFileDlg; +class CmpCompleteDlg; +class CmpSceneDlg; + +class Campaign; +class Starshatter; + +class Bitmap; +class DataLoader; +class Font; +class FormEx; +class Screen; +class Video; + +// +--------------------------------------------------------------------+ + +class CmpnScreen +{ +public: + CmpnScreen(); + virtual ~CmpnScreen(); + + virtual void Setup(Screen* screen); + virtual void TearDown(); + virtual bool CloseTopmost(); + + virtual bool IsShown() const { return isShown; } + virtual void Show(); + virtual void Hide(); + + virtual void ShowCmdDlg(); + + virtual void ShowCmdForceDlg(); + virtual void HideCmdForceDlg(); + virtual bool IsCmdForceShown(); + virtual CmdForceDlg* GetCmdForceDlg() { return cmd_force_dlg; } + + virtual void ShowCmdMissionsDlg(); + virtual void HideCmdMissionsDlg(); + virtual bool IsCmdMissionsShown(); + virtual CmdMissionsDlg* GetCmdMissionsDlg() { return cmd_missions_dlg; } + + virtual void ShowCmdOrdersDlg(); + virtual void HideCmdOrdersDlg(); + virtual bool IsCmdOrdersShown(); + virtual CmdOrdersDlg* GetCmdOrdersDlg() { return cmd_orders_dlg; } + + virtual void ShowCmdIntelDlg(); + virtual void HideCmdIntelDlg(); + virtual bool IsCmdIntelShown(); + virtual CmdIntelDlg* GetCmdIntelDlg() { return cmd_intel_dlg; } + + virtual void ShowCmdTheaterDlg(); + virtual void HideCmdTheaterDlg(); + virtual bool IsCmdTheaterShown(); + virtual CmdTheaterDlg* GetCmdTheaterDlg() { return cmd_theater_dlg; } + + virtual void ShowCmpFileDlg(); + virtual void HideCmpFileDlg(); + virtual bool IsCmpFileShown(); + virtual CmpFileDlg* GetCmpFileDlg() { return cmp_file_dlg; } + + virtual void ShowCmdMsgDlg(); + virtual void HideCmdMsgDlg(); + virtual bool IsCmdMsgShown(); + virtual CmdMsgDlg* GetCmdMsgDlg() { return cmd_msg_dlg; } + + virtual void ShowCmpCompleteDlg(); + virtual void HideCmpCompleteDlg(); + virtual bool IsCmpCompleteShown(); + virtual CmpCompleteDlg* GetCmpCompleteDlg() { return cmp_end_dlg; } + + virtual void ShowCmpSceneDlg(); + virtual void HideCmpSceneDlg(); + virtual bool IsCmpSceneShown(); + virtual CmpSceneDlg* GetCmpSceneDlg() { return cmp_scene_dlg; } + + virtual void HideAll(); + virtual void ExecFrame(); + + void SetFieldOfView(double fov); + double GetFieldOfView() const; + +private: + Screen* screen; + + CmdForceDlg* cmd_force_dlg; + CmdOrdersDlg* cmd_orders_dlg; + CmdMissionsDlg* cmd_missions_dlg; + CmdIntelDlg* cmd_intel_dlg; + CmdTheaterDlg* cmd_theater_dlg; + + CmdMsgDlg* cmd_msg_dlg; + CmpFileDlg* cmp_file_dlg; + CmpCompleteDlg* cmp_end_dlg; + CmpSceneDlg* cmp_scene_dlg; + + DataLoader* loader; + + bool isShown; + + Campaign* campaign; + Starshatter* stars; + int completion_stage; +}; + +// +--------------------------------------------------------------------+ + +#endif CmpnScreen_h + diff --git a/Stars45/CombatAction.cpp b/Stars45/CombatAction.cpp new file mode 100644 index 0000000..7725ec4 --- /dev/null +++ b/Stars45/CombatAction.cpp @@ -0,0 +1,352 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CombatAction.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + A significant (newsworthy) event in the dynamic campaign. +*/ + +#include "MemDebug.h" +#include "CombatAction.h" +#include "CombatGroup.h" +#include "Campaign.h" +#include "Combatant.h" +#include "Player.h" +#include "Random.h" + +// +----------------------------------------------------------------------+ + +CombatAction::CombatAction(int n, int typ, int sub, int iff) + : id(n), type(typ), subtype(sub), opp_type(-1), team(iff), + status(PENDING), count(0), rval(-1), source(0), time(0), + start_before((int) 1e9), start_after(0), + min_rank(0), max_rank(100), + delay(0), probability(100), asset_type(0), target_type(0) +{ } + +CombatAction::~CombatAction() +{ + requirements.destroy(); + asset_kills.destroy(); + target_kills.destroy(); +} + +// +----------------------------------------------------------------------+ + +bool +CombatAction::IsAvailable() const +{ + CombatAction* pThis = (CombatAction*) this; + + if (rval < 0) { + pThis->rval = (int) Random(0, 100); + + if (rval > probability) + pThis->status = SKIPPED; + } + + if (status != PENDING) + return false; + + if (min_rank > 0 || max_rank < 100) { + Player* player = Player::GetCurrentPlayer(); + + if (player->Rank() < min_rank || player->Rank() > max_rank) + return false; + } + + Campaign* campaign = Campaign::GetCampaign(); + if (campaign) { + if (campaign->GetTime() < start_after) { + return false; + } + + if (campaign->GetTime() > start_before) { + pThis->status = FAILED; // too late! + return false; + } + + // check requirements against actions in current campaign: + ListIter iter = pThis->requirements; + while (++iter) { + CombatActionReq* r = iter.value(); + bool ok = false; + + if (r->action > 0) { + ListIter action = campaign->GetActions(); + while (++action) { + CombatAction* a = action.value(); + + if (a->Identity() == r->action) { + if (r->not) { + if (a->Status() == r->stat) + return false; + } + else { + if (a->Status() != r->stat) + return false; + } + } + } + } + + // group-based requirement + else if (r->group_type > 0) { + if (r->c1) { + CombatGroup* group = r->c1->FindGroup(r->group_type, r->group_id); + + if (group) { + int test = 0; + int comp = 0; + + if (r->intel) { + test = group->IntelLevel(); + comp = r->intel; + } + + else { + test = group->CalcValue(); + comp = r->score; + } + + switch (r->comp) { + case CombatActionReq::LT: ok = (test < comp); break; + case CombatActionReq::LE: ok = (test <= comp); break; + case CombatActionReq::GT: ok = (test > comp); break; + case CombatActionReq::GE: ok = (test >= comp); break; + case CombatActionReq::EQ: ok = (test == comp); break; + } + } + + if (!ok) + return false; + } + } + + // score-based requirement + else { + int test = 0; + + if (r->comp <= CombatActionReq::EQ) { // absolute + if (r->c1) { + int test = r->c1->Score(); + + switch (r->comp) { + case CombatActionReq::LT: ok = (test < r->score); break; + case CombatActionReq::LE: ok = (test <= r->score); break; + case CombatActionReq::GT: ok = (test > r->score); break; + case CombatActionReq::GE: ok = (test >= r->score); break; + case CombatActionReq::EQ: ok = (test == r->score); break; + } + } + } + + else { // relative + if (r->c1 && r->c2) { + int test = r->c1->Score() - r->c2->Score(); + + switch (r->comp) { + case CombatActionReq::RLT: ok = (test < r->score); break; + case CombatActionReq::RLE: ok = (test <= r->score); break; + case CombatActionReq::RGT: ok = (test > r->score); break; + case CombatActionReq::RGE: ok = (test >= r->score); break; + case CombatActionReq::REQ: ok = (test == r->score); break; + } + } + } + + if (!ok) + return false; + } + + if (delay > 0) { + pThis->start_after = (int) campaign->GetTime() + delay; + pThis->delay = 0; + return IsAvailable(); + } + } + } + + return true; +} + +// +----------------------------------------------------------------------+ + +void +CombatAction::FireAction() +{ + Campaign* campaign = Campaign::GetCampaign(); + if (campaign) + time = (int) campaign->GetTime(); + + if (count >= 1) + count--; + + if (count < 1) + status = COMPLETE; +} + +void +CombatAction::FailAction() +{ + Campaign* campaign = Campaign::GetCampaign(); + if (campaign) + time = (int) campaign->GetTime(); + + count = 0; + status = FAILED; +} + +// +----------------------------------------------------------------------+ + +void +CombatAction::AddRequirement(int action, int stat, bool not) +{ + requirements.append(new(__FILE__,__LINE__) CombatActionReq(action, stat, not)); +} + +void +CombatAction::AddRequirement(Combatant* c1, Combatant* c2, int comp, int score) +{ + requirements.append(new(__FILE__,__LINE__) CombatActionReq(c1, c2, comp, score)); +} + +void +CombatAction::AddRequirement(Combatant* c1, int group_type, int group_id, int comp, int score, int intel) +{ + requirements.append(new(__FILE__,__LINE__) CombatActionReq(c1, group_type, group_id, comp, score, intel)); +} + +// +----------------------------------------------------------------------+ + +int +CombatAction::TypeFromName(const char* n) +{ + int type = 0; + + if (!stricmp(n, "NO_ACTION")) + type = NO_ACTION; + + else if (!stricmp(n, "MARKER")) + type = NO_ACTION; + + else if (!stricmp(n, "STRATEGIC_DIRECTIVE")) + type = STRATEGIC_DIRECTIVE; + + else if (!stricmp(n, "STRATEGIC")) + type = STRATEGIC_DIRECTIVE; + + else if (!stricmp(n, "ZONE_ASSIGNMENT")) + type = ZONE_ASSIGNMENT; + + else if (!stricmp(n, "ZONE")) + type = ZONE_ASSIGNMENT; + + else if (!stricmp(n, "SYSTEM_ASSIGNMENT")) + type = SYSTEM_ASSIGNMENT; + + else if (!stricmp(n, "SYSTEM")) + type = SYSTEM_ASSIGNMENT; + + else if (!stricmp(n, "MISSION_TEMPLATE")) + type = MISSION_TEMPLATE; + + else if (!stricmp(n, "MISSION")) + type = MISSION_TEMPLATE; + + else if (!stricmp(n, "COMBAT_EVENT")) + type = COMBAT_EVENT; + + else if (!stricmp(n, "EVENT")) + type = COMBAT_EVENT; + + else if (!stricmp(n, "INTEL_EVENT")) + type = INTEL_EVENT; + + else if (!stricmp(n, "INTEL")) + type = INTEL_EVENT; + + else if (!stricmp(n, "CAMPAIGN_SITUATION")) + type = CAMPAIGN_SITUATION; + + else if (!stricmp(n, "SITREP")) + type = CAMPAIGN_SITUATION; + + else if (!stricmp(n, "CAMPAIGN_ORDERS")) + type = CAMPAIGN_ORDERS; + + else if (!stricmp(n, "ORDERS")) + type = CAMPAIGN_ORDERS; + + return type; +} + +int +CombatAction::StatusFromName(const char* n) +{ + int stat = 0; + + if (!stricmp(n, "PENDING")) + stat = PENDING; + + else if (!stricmp(n, "ACTIVE")) + stat = ACTIVE; + + else if (!stricmp(n, "SKIPPED")) + stat = SKIPPED; + + else if (!stricmp(n, "FAILED")) + stat = FAILED; + + else if (!stricmp(n, "COMPLETE")) + stat = COMPLETE; + + return stat; +} + + +// +----------------------------------------------------------------------+ + +int +CombatActionReq::CompFromName(const char* n) +{ + int comp = 0; + + if (!stricmp(n, "LT")) + comp = LT; + + else if (!stricmp(n, "LE")) + comp = LE; + + else if (!stricmp(n, "GT")) + comp = GT; + + else if (!stricmp(n, "GE")) + comp = GE; + + else if (!stricmp(n, "EQ")) + comp = EQ; + + else if (!stricmp(n, "RLT")) + comp = RLT; + + else if (!stricmp(n, "RLE")) + comp = RLE; + + else if (!stricmp(n, "RGT")) + comp = RGT; + + else if (!stricmp(n, "RGE")) + comp = RGE; + + else if (!stricmp(n, "REQ")) + comp = REQ; + + return comp; +} diff --git a/Stars45/CombatAction.h b/Stars45/CombatAction.h new file mode 100644 index 0000000..0de85f2 --- /dev/null +++ b/Stars45/CombatAction.h @@ -0,0 +1,205 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CombatAction.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + A planned action (mission/story/strategy) in a dynamic campaign. +*/ + +#ifndef CombatAction_h +#define CombatAction_h + +#include "Types.h" +#include "Geometry.h" +#include "Text.h" +#include "List.h" + +// +--------------------------------------------------------------------+ + +class Combatant; +class CombatAction; +class CombatActionReq; + +// +--------------------------------------------------------------------+ + +class CombatAction +{ +public: + static const char* TYPENAME() { return "CombatAction"; } + + enum TYPE + { + NO_ACTION, + STRATEGIC_DIRECTIVE, + ZONE_ASSIGNMENT, + SYSTEM_ASSIGNMENT, + MISSION_TEMPLATE, + COMBAT_EVENT, + INTEL_EVENT, + CAMPAIGN_SITUATION, + CAMPAIGN_ORDERS + }; + + enum STATUS + { + PENDING, + ACTIVE, + SKIPPED, + FAILED, + COMPLETE + }; + + CombatAction(int id, int type, int subtype, int team); + ~CombatAction(); + + int operator == (const CombatAction& a) const { return id == a.id; } + + bool IsAvailable() const; + void FireAction(); + void FailAction(); + void AddRequirement(int action, int stat, bool not = false); + void AddRequirement(Combatant* c1, Combatant* c2, int comp, int score); + void AddRequirement(Combatant* c1, int group_type, int group_id, int comp, int score, int intel=0); + static int TypeFromName(const char* n); + static int StatusFromName(const char* n); + + // accessors/mutators: + int Identity() const { return id; } + int Type() const { return type; } + int Subtype() const { return subtype; } + int OpposingType() const { return opp_type; } + int GetIFF() const { return team; } + int Status() const { return status; } + int Source() const { return source; } + Point Location() const { return loc; } + const char* System() const { return system; } + const char* Region() const { return region; } + const char* Filename() const { return text_file; } + const char* ImageFile() const { return image_file; } + const char* SceneFile() const { return scene_file; } + int Count() const { return count; } + int ExecTime() const { return time; } + int StartBefore() const { return start_before; } + int StartAfter() const { return start_after; } + int MinRank() const { return min_rank; } + int MaxRank() const { return max_rank; } + int Delay() const { return delay; } + int Probability() const { return probability; } + int AssetType() const { return asset_type; } + int AssetId() const { return asset_id; } + List& AssetKills() { return asset_kills; } + int TargetType() const { return target_type; } + int TargetId() const { return target_id; } + int TargetIFF() const { return target_iff; } + List& TargetKills() { return target_kills; } + const char* GetText() const { return text; } + + void SetType(int t) { type = (char) t; } + void SetSubtype(int s) { subtype = (char) s; } + void SetOpposingType(int t){ opp_type = (char) t; } + void SetIFF(int t) { team = (char) t; } + void SetStatus(int s) { status = (char) s; } + void SetSource(int s) { source = s; } + void SetLocation(const Point& p) { loc = p; } + void SetSystem(Text sys) { system = sys; } + void SetRegion(Text rgn) { region = rgn; } + void SetFilename(Text f) { text_file = f; } + void SetImageFile(Text f) { image_file = f; } + void SetSceneFile(Text f) { scene_file = f; } + void SetCount(int n) { count = (char) n; } + void SetExecTime(int t) { time = t; } + void SetStartBefore(int s) { start_before = s; } + void SetStartAfter(int s) { start_after = s; } + void SetMinRank(int n) { min_rank = (char) n; } + void SetMaxRank(int n) { max_rank = (char) n; } + void SetDelay(int d) { delay = d; } + void SetProbability(int n) { probability = n; } + void SetAssetType(int t) { asset_type = t; } + void SetAssetId(int n) { asset_id = n; } + void SetTargetType(int t) { target_type = t; } + void SetTargetId(int n) { target_id = n; } + void SetTargetIFF(int n) { target_iff = n; } + void SetText(Text t) { text = t; } + + +private: + int id; + char type; + char subtype; + char opp_type; + char team; + char status; + char min_rank; + char max_rank; + int source; + Point loc; + Text system; + Text region; + Text text_file; + Text image_file; + Text scene_file; + char count; + int start_before; + int start_after; + int delay; + int probability; + int rval; + int time; + + Text text; + int asset_type; + int asset_id; + List asset_kills; + int target_type; + int target_id; + int target_iff; + List target_kills; + + List requirements; +}; + +// +--------------------------------------------------------------------+ + +class CombatActionReq { +public: + static const char* TYPENAME() { return "CombatActionReq"; } + + enum COMPARISON_OPERATOR { + LT, LE, GT, GE, EQ, // absolute score comparison + RLT, RLE, RGT, RGE, REQ // delta score comparison + }; + + CombatActionReq(int a, int s, bool n = false) + : action(a), stat(s), not(n), c1(0), c2(0), comp(0), score(0), intel(0) { } + + CombatActionReq(Combatant* a1, Combatant* a2, int comparison, int value) + : action(0), stat(0), not(0), c1(a1), c2(a2), group_type(0), group_id(0), + comp(comparison), score(value), intel(0) { } + + CombatActionReq(Combatant* a1, int gtype, int gid, int comparison, int value, int intel_level=0) + : action(0), stat(0), not(0), c1(a1), c2(0), group_type(gtype), group_id(gid), + comp(comparison), score(value), intel(intel_level) { } + + static int CompFromName(const char* sym); + + int action; + int stat; + bool not; + + Combatant* c1; + Combatant* c2; + int comp; + int score; + int intel; + int group_type; + int group_id; +}; + +#endif CombatAction_h + diff --git a/Stars45/CombatAssignment.cpp b/Stars45/CombatAssignment.cpp new file mode 100644 index 0000000..b4687cd --- /dev/null +++ b/Stars45/CombatAssignment.cpp @@ -0,0 +1,68 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CombatAssignment.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + High level assignment of one group to damage another +*/ + +#include "MemDebug.h" +#include "CombatAssignment.h" +#include "CombatGroup.h" +#include "Mission.h" + +// +--------------------------------------------------------------------+ + +CombatAssignment::CombatAssignment(int t, CombatGroup* obj, CombatGroup* rsc) + : type(t), objective(obj), resource(rsc) +{ +} + +// +--------------------------------------------------------------------+ + +CombatAssignment::~CombatAssignment() +{ +} + +// +--------------------------------------------------------------------+ +// This is used to sort assignments into a priority list. +// Higher priorities should come first in the list, so the +// sense of the operator is "backwards" from the usual. + +int +CombatAssignment::operator < (const CombatAssignment& a) const +{ + if (!objective) + return 0; + + if (!a.objective) + return 1; + + return objective->GetPlanValue() > a.objective->GetPlanValue(); +} + +// +--------------------------------------------------------------------+ + +const char* +CombatAssignment::GetDescription() const +{ + static char desc[256]; + + if (!resource) + sprintf(desc, "%s %s", + (const char*) Mission::RoleName(type), + (const char*) objective->Name()); + else + sprintf(desc, "%s %s %s", + (const char*) resource->Name(), + (const char*) Mission::RoleName(type), + (const char*) objective->Name()); + + return desc; +} diff --git a/Stars45/CombatAssignment.h b/Stars45/CombatAssignment.h new file mode 100644 index 0000000..cd4cebb --- /dev/null +++ b/Stars45/CombatAssignment.h @@ -0,0 +1,57 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CombatAssignment.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + High level assignment of one group to damage another +*/ + +#ifndef CombatAssignment_h +#define CombatAssignment_h + +#include "Types.h" +#include "List.h" + +// +--------------------------------------------------------------------+ + +class CombatGroup; +class SimRegion; + +// +--------------------------------------------------------------------+ + +class CombatAssignment +{ +public: + static const char* TYPENAME() { return "CombatAssignment"; } + + CombatAssignment(int t, CombatGroup* obj, CombatGroup* rsc=0); + ~CombatAssignment(); + + int operator < (const CombatAssignment& a) const; + + // operations: + void SetObjective(CombatGroup* o) { objective = o; } + void SetResource(CombatGroup* r) { resource = r; } + + // accessors: + int Type() { return type; } + CombatGroup* GetObjective() { return objective; } + CombatGroup* GetResource() { return resource; } + + const char* GetDescription() const; + bool IsActive() const { return resource != 0; } + +private: + int type; + CombatGroup* objective; + CombatGroup* resource; +}; + + +#endif CombatAssignment_h diff --git a/Stars45/CombatEvent.cpp b/Stars45/CombatEvent.cpp new file mode 100644 index 0000000..eea73a9 --- /dev/null +++ b/Stars45/CombatEvent.cpp @@ -0,0 +1,153 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CombatEvent.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + A significant (newsworthy) event in the dynamic campaign. +*/ + +#include "MemDebug.h" +#include "CombatEvent.h" +#include "CombatGroup.h" +#include "Campaign.h" +#include "Player.h" +#include "ShipDesign.h" +#include "Ship.h" + +#include "Term.h" +#include "ParseUtil.h" +#include "FormatUtil.h" +#include "DataLoader.h" + +// +----------------------------------------------------------------------+ + +CombatEvent::CombatEvent(Campaign* c, int typ, int tim, int tem, + int src, const char* rgn) + : campaign(c), type(typ), time(tim), team(tem), source(src), + region(rgn), points(0), visited(false) +{ } + +// +----------------------------------------------------------------------+ + +const char* +CombatEvent::SourceName() const +{ + return SourceName(source); +} + +// +----------------------------------------------------------------------+ + +const char* +CombatEvent::TypeName() const +{ + return TypeName(type); +} + +// +----------------------------------------------------------------------+ + +const char* +CombatEvent::SourceName(int n) +{ + switch (n) { + case FORCOM: return "FORCOM"; + case TACNET: return "TACNET"; + case INTEL: return "SECURE"; + case MAIL: return "Mail"; + case NEWS: return "News"; + } + + return "Unknown"; +} + +int +CombatEvent::SourceFromName(const char* n) +{ + for (int i = FORCOM; i <= NEWS; i++) + if (!stricmp(n, SourceName(i))) + return i; + + return -1; +} + +// +----------------------------------------------------------------------+ + +const char* +CombatEvent::TypeName(int n) +{ + switch (n) { + case ATTACK: return "ATTACK"; + case DEFEND: return "DEFEND"; + case MOVE_TO: return "MOVE_TO"; + case CAPTURE: return "CAPTURE"; + case STRATEGY: return "STRATEGY"; + case STORY: return "STORY"; + case CAMPAIGN_START: return "CAMPAIGN_START"; + case CAMPAIGN_END: return "CAMPAIGN_END"; + case CAMPAIGN_FAIL: return "CAMPAIGN_FAIL"; + } + + return "Unknown"; +} + +int +CombatEvent::TypeFromName(const char* n) +{ + for (int i = ATTACK; i <= CAMPAIGN_FAIL; i++) + if (!stricmp(n, TypeName(i))) + return i; + + return -1; +} + +// +----------------------------------------------------------------------+ + +void +CombatEvent::Load() +{ + DataLoader* loader = DataLoader::GetLoader(); + + if (!campaign || !loader) + return; + + loader->SetDataPath(campaign->Path()); + + if (file.length() > 0) { + const char* filename = file.data(); + BYTE* block = 0; + + loader->LoadBuffer(filename, block, true); + info = (const char*) block; + loader->ReleaseBuffer(block); + + if (info.contains('$')) { + Player* player = Player::GetCurrentPlayer(); + CombatGroup* group = campaign->GetPlayerGroup(); + + if (player) { + info = FormatTextReplace(info, "$NAME", player->Name().data()); + info = FormatTextReplace(info, "$RANK", Player::RankName(player->Rank())); + } + + if (group) { + info = FormatTextReplace(info, "$GROUP", group->GetDescription()); + } + + char timestr[32]; + FormatDayTime(timestr, campaign->GetTime(), true); + info = FormatTextReplace(info, "$TIME", timestr); + } + } + + if (type < CAMPAIGN_END && image_file.length() > 0) { + loader->LoadBitmap(image_file, image); + } + + loader->SetDataPath(0); +} + diff --git a/Stars45/CombatEvent.h b/Stars45/CombatEvent.h new file mode 100644 index 0000000..35dd7c0 --- /dev/null +++ b/Stars45/CombatEvent.h @@ -0,0 +1,122 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CombatEvent.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + A significant (newsworthy) event in the dynamic campaign. +*/ + +#ifndef CombatEvent_h +#define CombatEvent_h + +#include "Types.h" +#include "Geometry.h" +#include "Bitmap.h" +#include "Text.h" +#include "List.h" + +// +--------------------------------------------------------------------+ + +class Campaign; +class CombatGroup; +class CombatUnit; + +// +--------------------------------------------------------------------+ + +class CombatEvent +{ +public: + static const char* TYPENAME() { return "CombatEvent"; } + + enum EVENT_TYPE { + ATTACK, + DEFEND, + MOVE_TO, + CAPTURE, + STRATEGY, + + CAMPAIGN_START, + STORY, + CAMPAIGN_END, + CAMPAIGN_FAIL + }; + + enum EVENT_SOURCE { + FORCOM, + TACNET, + INTEL, + MAIL, + NEWS + }; + + CombatEvent(Campaign* c, int type, int time, int team, int source, const char* rgn); + + int operator == (const CombatEvent& u) const { return this == &u; } + + // accessors/mutators: + int Type() const { return type; } + int Time() const { return time; } + int GetIFF() const { return team; } + int Points() const { return points; } + int Source() const { return source; } + Point Location() const { return loc; } + const char* Region() const { return region; } + const char* Title() const { return title; } + const char* Information() const { return info; } + const char* Filename() const { return file; } + const char* ImageFile() const { return image_file; } + const char* SceneFile() const { return scene_file; } + Bitmap& Image() { return image; } + const char* SourceName() const; + const char* TypeName() const; + bool Visited() const { return visited; } + + void SetType(int t) { type = t; } + void SetTime(int t) { time = t; } + void SetIFF(int t) { team = t; } + void SetPoints(int p) { points = p; } + void SetSource(int s) { source = s; } + void SetLocation(const Point& p) { loc = p; } + void SetRegion(Text rgn) { region = rgn; } + void SetTitle(Text t) { title = t; } + void SetInformation(Text t) { info = t; } + void SetFilename(Text f) { file = f; } + void SetImageFile(Text f) { image_file = f; } + void SetSceneFile(Text f) { scene_file = f; } + void SetVisited(bool v) { visited = v; } + + // operations: + void Load(); + + // utilities: + static int TypeFromName(const char* n); + static int SourceFromName(const char* n); + static const char* TypeName(int n); + static const char* SourceName(int n); + +private: + Campaign* campaign; + int type; + int time; + int team; + int points; + int source; + bool visited; + Point loc; + Text region; + Text title; + Text info; + Text file; + Text image_file; + Text scene_file; + Bitmap image; +}; + +#endif CombatEvent_h + diff --git a/Stars45/CombatGroup.cpp b/Stars45/CombatGroup.cpp new file mode 100644 index 0000000..2e8e270 --- /dev/null +++ b/Stars45/CombatGroup.cpp @@ -0,0 +1,1589 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CombatGroup.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + An element in the dynamic campaign +*/ + +#include "MemDebug.h" +#include "CombatGroup.h" +#include "CombatUnit.h" +#include "CombatZone.h" +#include "Combatant.h" +#include "CombatAssignment.h" +#include "Campaign.h" +#include "ShipDesign.h" +#include "Ship.h" + +#include "Game.h" +#include "DataLoader.h" +#include "ParseUtil.h" + +// +----------------------------------------------------------------------+ + +CombatGroup::CombatGroup(int t, int n, const char* s, int iff_code, int e, CombatGroup* p) + : type(t), id(n), name(s), iff(iff_code), enemy_intel(e), + parent(p), value(0), plan_value(0), unit_index(0), combatant(0), + expanded(false), sorties(0), kills(0), points(0), + current_zone(0), assigned_zone(0), zone_lock(false) +{ + if (parent) + parent->AddComponent(this); +} + +CombatGroup::~CombatGroup() +{ + assignments.destroy(); + components.destroy(); + units.destroy(); +} + +// +--------------------------------------------------------------------+ + +void +CombatGroup::AddComponent(CombatGroup* g) +{ + if (g) { + g->parent = this; + components.append(g); + } +} + +// +--------------------------------------------------------------------+ + +bool +CombatGroup::IsAssignable() const +{ + switch (type) { + case CARRIER_GROUP: + case BATTLE_GROUP: + case DESTROYER_SQUADRON: + case ATTACK_SQUADRON: + case FIGHTER_SQUADRON: + case INTERCEPT_SQUADRON: + case LCA_SQUADRON: + return ((CombatGroup*) this)->CalcValue() > 0; + } + + return false; +} + +bool +CombatGroup::IsTargetable() const +{ + // neutral / non-combatants are not *strategic* targets + // for any combatant: + if (iff < 1 || iff >= 100) + return false; + + // civilian / non-combatant are not strategic targets: + if (type == PASSENGER || + type == PRIVATE || + type == MEDICAL || + type == HABITAT) + return false; + + // must have units of our own to be targetable: + if (units.size() < 1) + return false; + + return ((CombatGroup*) this)->CalcValue() > 0; +} + +bool +CombatGroup::IsDefensible() const +{ + if (type >= SUPPORT) + return ((CombatGroup*) this)->CalcValue() > 0; + + return false; +} + +bool +CombatGroup::IsStrikeTarget() const +{ + if (type < BATTALION || + type == MINEFIELD || // assault, not strike + type == PASSENGER || + type == PRIVATE || + type == MEDICAL || + type == HABITAT) + return false; + + return ((CombatGroup*) this)->CalcValue() > 0; +} + +// +--------------------------------------------------------------------+ + +bool +CombatGroup::IsMovable() const +{ + switch (type) { + case CARRIER_GROUP: + case BATTLE_GROUP: + case DESTROYER_SQUADRON: + case ATTACK_SQUADRON: + case FIGHTER_SQUADRON: + case INTERCEPT_SQUADRON: + case LCA_SQUADRON: + case COURIER: + case MEDICAL: + case SUPPLY: + case REPAIR: + case FREIGHT: + case PASSENGER: + case PRIVATE: + return true; + } + + return false; +} + +// +--------------------------------------------------------------------+ + +bool +CombatGroup::IsFighterGroup() const +{ + switch (type) { + case WING: + case INTERCEPT_SQUADRON: + case FIGHTER_SQUADRON: + case ATTACK_SQUADRON: + return true; + } + + return false; +} + +bool +CombatGroup::IsStarshipGroup() const +{ + switch (type) { + case DESTROYER_SQUADRON: + case BATTLE_GROUP: + case CARRIER_GROUP: + return true; + } + + return false; +} + +// +--------------------------------------------------------------------+ + +bool +CombatGroup::IsReserve() const +{ + if (enemy_intel <= Intel::RESERVE) + return true; + + if (parent) + return parent->IsReserve(); + + return false; +} + +// +--------------------------------------------------------------------+ + +const int* +CombatGroup::PreferredAttacker(int type) +{ + static int p[8]; + + ZeroMemory(p, sizeof(p)); + + switch (type) { + //case FLEET: + case DESTROYER_SQUADRON: p[0] = DESTROYER_SQUADRON; + p[1] = BATTLE_GROUP; + p[2] = CARRIER_GROUP; + p[3] = ATTACK_SQUADRON; + break; + + case BATTLE_GROUP: p[0] = BATTLE_GROUP; + p[1] = DESTROYER_SQUADRON; + p[2] = CARRIER_GROUP; + p[3] = ATTACK_SQUADRON; + break; + + case CARRIER_GROUP: p[0] = ATTACK_SQUADRON; + p[1] = BATTLE_GROUP; + p[2] = DESTROYER_SQUADRON; + p[3] = CARRIER_GROUP; + break; + + //case WING: + case LCA_SQUADRON: + case ATTACK_SQUADRON: + case INTERCEPT_SQUADRON: + case FIGHTER_SQUADRON: p[0] = INTERCEPT_SQUADRON; + p[1] = FIGHTER_SQUADRON; + break; + + //case BATTALION: + case STATION: p[0] = BATTLE_GROUP; + p[1] = CARRIER_GROUP; + break; + + case STARBASE: + case BATTERY: + case MISSILE: p[0] = ATTACK_SQUADRON; + p[1] = FIGHTER_SQUADRON; + break; + + //case C3I: + case MINEFIELD: + case COMM_RELAY: + case EARLY_WARNING: + case FWD_CONTROL_CTR: + case ECM: p[0] = ATTACK_SQUADRON; + p[1] = FIGHTER_SQUADRON; + p[2] = DESTROYER_SQUADRON; + break; + + //case SUPPORT: + case COURIER: + case MEDICAL: + case SUPPLY: + case REPAIR: p[0] = DESTROYER_SQUADRON; + p[1] = BATTLE_GROUP; + p[2] = ATTACK_SQUADRON; + break; + + //case CIVILIAN: + + //case WAR_PRODuCTION: + case FACTORY: + case REFINERY: + case RESOURCE: p[0] = ATTACK_SQUADRON; + p[1] = FIGHTER_SQUADRON; + break; + + //case INFRASTRUCTURE: + case TRANSPORT: + case NETWORK: + case HABITAT: + case STORAGE: p[0] = ATTACK_SQUADRON; + p[1] = FIGHTER_SQUADRON; + break; + + //case NON_COM: + case FREIGHT: + case PASSENGER: + case PRIVATE: p[0] = DESTROYER_SQUADRON; + p[1] = ATTACK_SQUADRON; + break; + } + + return p; +} + +// +--------------------------------------------------------------------+ + +const int* +CombatGroup::PreferredDefender(int type) +{ + static int p[8]; + + ZeroMemory(p, sizeof(p)); + + switch (type) { + //case FLEET: + case CARRIER_GROUP: + case BATTLE_GROUP: + case DESTROYER_SQUADRON: + + //case WING: + case LCA_SQUADRON: + case ATTACK_SQUADRON: + case INTERCEPT_SQUADRON: + case FIGHTER_SQUADRON: break; + + //case BATTALION: + case STATION: p[0] = BATTLE_GROUP; + p[1] = CARRIER_GROUP; + p[2] = DESTROYER_SQUADRON; + break; + case STARBASE: + case MINEFIELD: + case BATTERY: + case MISSILE: p[0] = FIGHTER_SQUADRON; + p[1] = INTERCEPT_SQUADRON; + break; + + //case C3I: + case COMM_RELAY: + case EARLY_WARNING: + case FWD_CONTROL_CTR: + case ECM: p[0] = FIGHTER_SQUADRON; + p[1] = INTERCEPT_SQUADRON; + break; + + //case SUPPORT: + case COURIER: + case MEDICAL: + case SUPPLY: + case REPAIR: p[0] = DESTROYER_SQUADRON; + p[1] = BATTLE_GROUP; + p[2] = ATTACK_SQUADRON; + break; + + //case CIVILIAN: + + //case WAR_PRODuCTION: + case FACTORY: + case REFINERY: + case RESOURCE: p[0] = FIGHTER_SQUADRON; + p[1] = INTERCEPT_SQUADRON; + break; + + //case INFRASTRUCTURE: + case TRANSPORT: + case NETWORK: + case HABITAT: + case STORAGE: p[0] = FIGHTER_SQUADRON; + p[1] = INTERCEPT_SQUADRON; + break; + + //case NON_COM: + case FREIGHT: + case PASSENGER: + case PRIVATE: p[0] = DESTROYER_SQUADRON; + p[1] = BATTLE_GROUP; + break; + } + + return p; +} + +// +--------------------------------------------------------------------+ + +CombatGroup* +CombatGroup::FindGroup(int t, int n) +{ + CombatGroup* result = 0; + + if (type == t && (n < 0 || id == n)) + result = this; + + ListIter group = components; + while (!result && ++group) { + result = group->FindGroup(t, n); + } + + return result; +} + +// +--------------------------------------------------------------------+ + +CombatGroup* +CombatGroup::Clone(bool deep) +{ + CombatGroup* clone = new(__FILE__,__LINE__) + CombatGroup(type, id, name, iff, enemy_intel); + + clone->combatant = combatant; + clone->region = region; + clone->location = location; + clone->value = value; + clone->expanded = expanded; + + for (int i = 0; i < units.size(); i++) { + CombatUnit* u = new(__FILE__,__LINE__) CombatUnit(*units[i]); + u->SetCombatGroup(clone); + clone->units.append(u); + } + + if (deep) { + for (i = 0; i < components.size(); i++) { + CombatGroup* g = components[i]->Clone(deep); + clone->AddComponent(g); + + if (g->Type() == FIGHTER_SQUADRON || + g->Type() == INTERCEPT_SQUADRON || + g->Type() == ATTACK_SQUADRON || + g->Type() == LCA_SQUADRON) { + + if (units.size() > 0) { + CombatUnit* carrier = units[0]; + + for (int u = 0; u < g->GetUnits().size(); u++) { + CombatUnit* unit = g->GetUnits()[u]; + + if (unit->Type() >= Ship::FIGHTER || + unit->Type() <= Ship::LCA) { + unit->SetCarrier(carrier); + unit->SetRegion(carrier->GetRegion()); + } + } + } + } + } + } + + return clone; +} + +// +--------------------------------------------------------------------+ + +const char* +CombatGroup::GetOrdinal() const +{ + static char ordinal[16]; + + int last_two_digits = id % 100; + + if (last_two_digits > 10 && last_two_digits < 20) { + sprintf(ordinal, "ordinal.%d", last_two_digits); + Text suffix = Game::GetText(ordinal); + if (suffix != ordinal) + sprintf(ordinal, "%d%s", id, suffix.data()); + else + sprintf(ordinal, "%dth", id); + } + else { + int last_digit = last_two_digits % 10; + sprintf(ordinal, "ordinal.%d", last_digit); + Text suffix = Game::GetText(ordinal); + if (suffix != ordinal) + sprintf(ordinal, "%d%s", id, suffix.data()); + else if (last_digit == 1) + sprintf(ordinal, "%dst", id); + else if (last_digit == 2) + sprintf(ordinal, "%dnd", id); + else if (last_digit == 3) + sprintf(ordinal, "%drd", id); + else + sprintf(ordinal, "%dth", id); + } + + return ordinal; +} + +const char* +CombatGroup::GetDescription() const +{ + static char desc[256]; + static char name_desc[256]; + + if (name.length()) + sprintf(name_desc, " \"%s\"", (const char*) name); + else + name_desc[0] = 0; + + switch (type) { + case FORCE: strcpy(desc, (const char*) name); break; + + case FLEET: sprintf(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.FLEET").data(), name_desc); break; + case CARRIER_GROUP: sprintf(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.CARRIER_GROUP").data(), name_desc); break; + case BATTLE_GROUP: sprintf(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.BATTLE_GROUP").data(), name_desc); break; + case DESTROYER_SQUADRON: sprintf(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.DESTROYER_SQUADRON").data(), name_desc); break; + + case WING: sprintf(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.WING").data(), name_desc); break; + case ATTACK_SQUADRON: sprintf(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.ATTACK_SQUADRON").data(), name_desc); break; + case FIGHTER_SQUADRON: sprintf(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.FIGHTER_SQUADRON").data(), name_desc); break; + case INTERCEPT_SQUADRON: sprintf(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.INTERCEPT_SQUADRON").data(), name_desc); break; + case LCA_SQUADRON: sprintf(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.LCA_SQUADRON").data(), name_desc); break; + + case BATTALION: sprintf(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.BATTALION").data(), name_desc); break; + case STATION: sprintf(desc, "%s %s", Game::GetText("CombatGroup.STATION").data(), name); break; + case STARBASE: sprintf(desc, "%s %d%s", Game::GetText("CombatGroup.STARBASE").data(), id, name_desc); break; + case MINEFIELD: sprintf(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.MINEFIELD").data(), name_desc); break; + case BATTERY: sprintf(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.BATTERY").data(), name_desc); break; + case MISSILE: sprintf(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.MISSILE").data(), name_desc); break; + + case C3I: sprintf(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.C3I").data(), name_desc); break; + case COMM_RELAY: sprintf(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.COMM_RELAY").data(), name_desc); break; + case EARLY_WARNING: sprintf(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.EARLY_WARNING").data(), name_desc); break; + case FWD_CONTROL_CTR: sprintf(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.FWD_CONTROL_CTR").data(), name_desc); break; + case ECM: sprintf(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.ECM").data(), name_desc); break; + + case SUPPORT: sprintf(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.SUPPORT").data(), name_desc); break; + case COURIER: sprintf(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.COURIER").data(), name_desc); break; + case SUPPLY: sprintf(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.SUPPLY").data(), name_desc); break; + case REPAIR: sprintf(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.REPAIR").data(), name_desc); break; + case MEDICAL: sprintf(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.MEDICAL").data(), name_desc); break; + + case CIVILIAN: + case WAR_PRODUCTION: + case FACTORY: + case REFINERY: + case RESOURCE: strcpy(desc, (const char*) name); break; + + case INFRASTRUCTURE: + case TRANSPORT: + case NETWORK: + case HABITAT: + case STORAGE: + case FREIGHT: + case PASSENGER: + case PRIVATE: strcpy(desc, (const char*) name); break; + + default: sprintf(desc, "%s%s", Game::GetText("CombatGroup.default").data(), name_desc); break; + } + + return desc; +} + +const char* +CombatGroup::GetShortDescription() const +{ + static char desc[256]; + + switch (type) { + case FORCE: strcpy(desc, (const char*) name); break; + + case FLEET: sprintf(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.FLEET").data()); break; + case CARRIER_GROUP: sprintf(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.CARRIER_GROUP").data()); break; + case BATTLE_GROUP: sprintf(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.BATTLE_GROUP").data()); break; + case DESTROYER_SQUADRON: sprintf(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.DESTROYER_SQUADRON").data()); break; + + case WING: sprintf(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.WING").data()); break; + case ATTACK_SQUADRON: sprintf(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.ATTACK_SQUADRON").data()); break; + case FIGHTER_SQUADRON: sprintf(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.FIGHTER_SQUADRON").data()); break; + case INTERCEPT_SQUADRON: sprintf(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.INTERCEPT_SQUADRON").data()); break; + case LCA_SQUADRON: sprintf(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.LCA_SQUADRON").data()); break; + + case BATTALION: sprintf(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.BATTALION").data()); break; + case STATION: sprintf(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.STATION").data()); break; + case STARBASE: sprintf(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.STARBASE").data()); break; + case MINEFIELD: sprintf(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.MINEFIELD").data()); break; + case BATTERY: sprintf(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.BATTERY").data()); break; + + case C3I: sprintf(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.C3I").data()); break; + case COMM_RELAY: sprintf(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.COMM_RELAY").data()); break; + case EARLY_WARNING: sprintf(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.EARLY_WARNING").data()); break; + case FWD_CONTROL_CTR: sprintf(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.FWD_CONTROL_CTR").data()); break; + case ECM: sprintf(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.ECM").data()); break; + + case SUPPORT: sprintf(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.SUPPORT").data()); break; + case COURIER: sprintf(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.COURIER").data()); break; + case MEDICAL: sprintf(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.MEDICAL").data()); break; + case SUPPLY: sprintf(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.SUPPLY").data()); break; + case REPAIR: sprintf(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.REPAIR").data()); break; + + case CIVILIAN: + case WAR_PRODUCTION: + case FACTORY: + case REFINERY: + case RESOURCE: strcpy(desc, (const char*) name); break; + + case INFRASTRUCTURE: + case TRANSPORT: + case NETWORK: + case HABITAT: + case STORAGE: + case FREIGHT: + case PASSENGER: + case PRIVATE: strcpy(desc, (const char*) name); break; + + default: sprintf(desc, "%s", Game::GetText("CombatGroup.abrv.default").data()); break; + } + + return desc; +} + +// +--------------------------------------------------------------------+ + +double +CombatGroup::GetNextJumpTime() const +{ + double t = 0; + + ListIter unit = ((CombatGroup*) this)->units; + while (++unit) + if (unit->GetNextJumpTime() > t) + t = unit->GetNextJumpTime(); + + return t; +} + +// +--------------------------------------------------------------------+ + +void +CombatGroup::MoveTo(const Point& loc) +{ + location = loc; +} + +// +--------------------------------------------------------------------+ + +void +CombatGroup::SetAssignedSystem(const char* s) +{ + assigned_system = s; + assigned_zone = 0; + zone_lock = false; + + ListIter iter = components; + while (++iter) { + CombatGroup* g = iter.value(); + g->SetAssignedSystem(s); + } +} + +void +CombatGroup::SetAssignedZone(CombatZone* z) +{ + assigned_zone = z; + + if (!assigned_zone) + zone_lock = false; + + ListIter iter = components; + while (++iter) { + CombatGroup* g = iter.value(); + g->SetAssignedZone(z); + } +} + +void +CombatGroup::ClearUnlockedZones() +{ + if (!zone_lock) + assigned_zone = 0; + + ListIter iter = components; + while (++iter) { + CombatGroup* g = iter.value(); + g->ClearUnlockedZones(); + } +} + +void +CombatGroup::SetZoneLock(bool lock) +{ + if (!assigned_zone) + zone_lock = false; + else + zone_lock = lock; + + if (zone_lock) + assigned_system = Text(); + + ListIter iter = components; + while (++iter) { + CombatGroup* g = iter.value(); + g->SetZoneLock(lock); + } +} + +// +--------------------------------------------------------------------+ + +void +CombatGroup::SetIntelLevel(int n) +{ + if (n < Intel::RESERVE || n > Intel::TRACKED) return; + + enemy_intel = n; + + // if this group has been discovered, the entire + // branch of the OOB tree must be exposed. Otherwise, + // no missions would ever be planned against this + // combat group. + + if (n > Intel::SECRET) { + CombatGroup* p = parent; + while (p) { + if (p->enemy_intel < Intel::KNOWN) + p->enemy_intel = Intel::KNOWN; + + p = p->parent; + } + } +} + +// +--------------------------------------------------------------------+ + +int +CombatGroup::CalcValue() +{ + int val = 0; + + ListIter unit = units; + while (++unit) + val += unit->GetValue(); + + ListIter comp = components; + while (++comp) + val += comp->CalcValue(); + + value = val; + return value; +} + +int +CombatGroup::CountUnits() const +{ + int n = 0; + + CombatGroup* g = (CombatGroup*) this; + + ListIter unit = g->units; + while (++unit) + n += unit->Count() - unit->DeadCount(); + + CombatGroup* pThis = ((CombatGroup*) this); + pThis->live_comp.clear(); + ListIter iter = g->components; + while (++iter) { + CombatGroup* comp = iter.value(); + + if (!comp->IsReserve()) { + int unit_count = comp->CountUnits(); + if (unit_count > 0) + pThis->live_comp.append(comp); + + n += unit_count; + } + } + + return n; +} + +// +--------------------------------------------------------------------+ + +void +CombatGroup::ClearAssignments() +{ + assignments.destroy(); + + ListIter comp = components; + while (++comp) + comp->ClearAssignments(); +} + +// +--------------------------------------------------------------------+ + +CombatGroup* +CombatGroup::FindCarrier() +{ + CombatGroup* p = GetParent(); + + while (p != 0 && + p->Type() != CombatGroup::CARRIER_GROUP && + p->Type() != CombatGroup::STATION && + p->Type() != CombatGroup::STARBASE) + p = p->GetParent(); + + if (p && p->GetUnits().size()) + return p; + + return 0; +} + +CombatUnit* +CombatGroup::GetRandomUnit() +{ + CombatUnit* result = 0; + List live; + + ListIter unit = units; + while (++unit) { + if (unit->Count() - unit->DeadCount() > 0) + live.append(unit.value()); + } + + if (live.size() > 0) { + int ntries = 5; + while (!result && ntries-- > 0) { + int index = rand() % live.size(); + result = live[index]; + + int ship_class = result->GetShipClass(); + if (ship_class >= Ship::CRUISER && + ship_class <= Ship::FARCASTER) + result = 0; + } + } + + if (!result) { + ListIter comp = components; + while (++comp && !result) { + CombatUnit* u = comp->GetRandomUnit(); + if (u) + result = u; + } + } + + return result; +} + +CombatUnit* +CombatGroup::GetFirstUnit() +{ + int tmp_index = unit_index; + unit_index = 0; + CombatUnit* result = GetNextUnit(); + unit_index = tmp_index; + + return result; +} + +CombatUnit* +CombatGroup::GetNextUnit() +{ + if (units.size() > 0) { + List live; + + ListIter unit = units; + while (++unit) { + if (unit->Count() - unit->DeadCount() > 0) + live.append(unit.value()); + } + + if (live.size() > 0) { + return live[unit_index++ % live.size()]; + } + } + + if (components.size() > 0) { + return components[unit_index % components.size()]->GetNextUnit(); + } + + return 0; +} + +CombatUnit* +CombatGroup::FindUnit(const char* name) +{ + if (units.size() > 0) { + ListIter iter = units; + while (++iter) { + CombatUnit* unit = iter.value(); + if (unit->Name() == name) { + if (unit->Count() - unit->DeadCount() > 0) + return unit; + else + return 0; + } + } + } + + return 0; +} + +void +CombatGroup::AssignRegion(Text rgn) +{ + region = rgn; + + ListIter comp = components; + while (++comp) + comp->AssignRegion(rgn); + + ListIter unit = units; + while (++unit) + unit->SetRegion(rgn); +} + +// +--------------------------------------------------------------------+ + +static const char* group_name[] = { + "", + "force", + "wing", + "intercept_squadron", + "fighter_squadron", + "attack_squadron", + "lca_squadron", + "fleet", + "destroyer_squadron", + "battle_group", + "carrier_group", + "battalion", + "minefield", + "battery", + "missile", + "station", + "starbase", + "c3i", + "comm_relay", + "early_warning", + "fwd_control_ctr", + "ecm", + "support", + "courier", + "medical", + "supply", + "repair", + "civilian", + "war_production", + "factory", + "refinery", + "resource", + "infrastructure", + "transport", + "network", + "habitat", + "storage", + "non_com", + "freight", + "passenger", + "private" +}; + +// +--------------------------------------------------------------------+ + +int +CombatGroup::TypeFromName(const char* type_name) +{ + for (int i = FORCE; i < PRIVATE; i++) + if (!stricmp(type_name, group_name[i])) + return i; + + return 0; +} + +const char* +CombatGroup::NameFromType(int type) +{ + return group_name[type]; +} + +// +--------------------------------------------------------------------+ + +int ShipClassFromName(const char* type_name) +{ + return Ship::ClassForName(type_name); +} + +// +--------------------------------------------------------------------+ + +#define GET_DEF_BOOL(n) if (pdef->name()->value()==(#n)) GetDefBool((n), pdef, filename) +#define GET_DEF_TEXT(n) if (pdef->name()->value()==(#n)) GetDefText((n), pdef, filename) +#define GET_DEF_NUM(n) if (pdef->name()->value()==(#n)) GetDefNumber((n), pdef, filename) +#define GET_DEF_VEC(n) if (pdef->name()->value()==(#n)) GetDefVec((n), pdef, filename) + +CombatGroup* +CombatGroup::LoadOrderOfBattle(const char* filename, int team, Combatant* combatant) +{ + CombatGroup* force = 0; + DataLoader* loader = DataLoader::GetLoader(); + BYTE* block; + loader->LoadBuffer(filename, block, true); + + Parser parser(new(__FILE__,__LINE__) BlockReader((const char*) block)); + Term* term = parser.ParseTerm(); + + if (!term) { + Print("ERROR: could not parse order of battle '%s'\n", filename); + return 0; + } + else { + TermText* file_type = term->isText(); + if (!file_type || file_type->value() != "ORDER_OF_BATTLE") { + Print("ERROR: invalid Order of Battle file '%s'\n", filename); + term->print(10); + return 0; + } + } + + + do { + delete term; term = 0; + term = parser.ParseTerm(); + + if (term) { + TermDef* def = term->isDef(); + if (def) { + if (def->name()->value() == "group") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: group struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + + char name[256]; + char type[64]; + char intel[64]; + char region[64]; + char system[64]; + char parent_type[64]; + int parent_id = 0; + int id = 0; + int iff = -1; + Vec3 loc = Vec3(1.0e9f,0.0f,0.0f); + + List unit_list; + char unit_name[64]; + char unit_regnum[16]; + char unit_design[64]; + char unit_skin[64]; + int unit_class = 0; + int unit_count = 1; + int unit_dead = 0; + int unit_damage = 0; + int unit_heading= 0; + int unit_index = 0; + + *name = 0; + *type = 0; + *intel = 0; + *region = 0; + *system = 0; + *parent_type = 0; + *unit_name = 0; + *unit_regnum = 0; + *unit_design = 0; + *unit_skin = 0; + + strcpy(intel, "KNOWN"); + + // all groups in this OOB default to the IFF of the main force + if (force) + iff = force->GetIFF(); + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef && (iff < 0 || team < 0 || iff == team)) { + GET_DEF_TEXT(name); + else GET_DEF_TEXT(type); + else GET_DEF_TEXT(intel); + else GET_DEF_TEXT(region); + else GET_DEF_TEXT(system); + else GET_DEF_VEC(loc); + else GET_DEF_TEXT(parent_type); + else GET_DEF_NUM(parent_id); + else GET_DEF_NUM(iff); + else GET_DEF_NUM(id); + else GET_DEF_NUM(unit_index); + + else if ((iff == team || team < 0) && pdef->name()->value() == "unit") { + if (!pdef->term() || !pdef->term()->isStruct()) { + Print("WARNING: unit struct missing for group '%s' in '%s'\n", name, filename); + } + else { + TermStruct* val = pdef->term()->isStruct(); + + char unit_region[64]; + char design[256]; + Vec3 unit_loc = Vec3(1.0e9f,0.0f,0.0f); + unit_count = 1; + + ZeroMemory(unit_region, sizeof(unit_region)); + ZeroMemory(design, sizeof(design)); + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + if (pdef->name()->value() == "name") { + GetDefText(unit_name, pdef, filename); + } + else if (pdef->name()->value() == "regnum") { + GetDefText(unit_regnum, pdef, filename); + } + else if (pdef->name()->value() == "region") { + GetDefText(unit_region, pdef, filename); + } + else if (pdef->name()->value() == "loc") { + GetDefVec(unit_loc, pdef, filename); + } + else if (pdef->name()->value() == "type") { + char typestr[32]; + GetDefText(typestr, pdef, filename); + unit_class = ShipDesign::ClassForName(typestr); + } + else if (pdef->name()->value() == "design") { + GetDefText(unit_design, pdef, filename); + } + else if (pdef->name()->value() == "skin") { + GetDefText(unit_skin, pdef, filename); + } + else if (pdef->name()->value() == "count") { + GetDefNumber(unit_count, pdef, filename); + } + else if (pdef->name()->value() == "dead_count") { + GetDefNumber(unit_dead, pdef, filename); + } + else if (pdef->name()->value() == "damage") { + GetDefNumber(unit_damage, pdef, filename); + } + else if (pdef->name()->value() == "heading") { + GetDefNumber(unit_heading, pdef, filename); + } + } + } + + if (!ShipDesign::CheckName(unit_design)) { + Print("ERROR: invalid design '%s' for unit '%s' in '%s'\n", unit_design, unit_name, filename); + return 0; + } + + CombatUnit* cu = new(__FILE__,__LINE__) CombatUnit(unit_name, unit_regnum, unit_class, unit_design, unit_count, iff); + cu->SetRegion(unit_region); + cu->SetSkin(unit_skin); + cu->MoveTo(unit_loc); + cu->Kill(unit_dead); + cu->SetSustainedDamage(unit_damage); + cu->SetHeading(unit_heading * DEGREES); + unit_list.append(cu); + } + } + } + } // elements + + if (iff >= 0 && (iff == team || team < 0)) { + CombatGroup* parent_group = 0; + + if (force) { + parent_group = force->FindGroup(TypeFromName(parent_type), parent_id); + } + + CombatGroup* g = new(__FILE__,__LINE__) + CombatGroup(TypeFromName(type), id, name, iff, Intel::IntelFromName(intel), parent_group); + + g->region = region; + g->combatant = combatant; + g->unit_index = unit_index; + + if (loc.x >= 1e9) { + if (parent_group) + g->location = parent_group->location; + else + g->location = Vec3(0,0,0); + } + else { + g->location = loc; + } + + if (unit_list.size()) { + unit_list[0]->SetLeader(true); + + ListIter u = unit_list; + while (++u) { + u->SetCombatGroup(g); + + if (u->GetRegion().length() < 1) { + u->SetRegion(g->GetRegion()); + u->MoveTo(g->Location()); + } + + if (parent_group && + (u->Type() == Ship::FIGHTER || + u->Type() == Ship::ATTACK)) { + + CombatUnit* carrier = 0; + CombatGroup* p = parent_group; + + while (p && !carrier) { + if (p->units.size() && p->units[0]->Type() == Ship::CARRIER) { + carrier = p->units[0]; + u->SetCarrier(carrier); + u->SetRegion(carrier->GetRegion()); + } + + p = p->parent; + } + } + } + + g->units.append(unit_list); + } + + if (!force) + force = g; + } // iff == team? + } // group-struct + } // group + } // def + } // term + } + while (term); + + loader->ReleaseBuffer(block); + Print("Order of Battle Loaded (%s).\n", force ? force->Name() : "unknown force"); + + if (force) + force->CalcValue(); + + return force; +} + +// +--------------------------------------------------------------------+ + +void +CombatGroup::MergeOrderOfBattle(BYTE* block, const char* filename, int team, Combatant* combatant, Campaign* campaign) +{ + CombatGroup* force = 0; + + Parser parser(new(__FILE__,__LINE__) BlockReader((const char*) block)); + Term* term = parser.ParseTerm(); + + if (!term) { + Print("ERROR: could not parse order of battle '%s'\n", filename); + return; + } + else { + TermText* file_type = term->isText(); + if (!file_type || file_type->value() != "SAVEGAME") { + Print("ERROR: invalid Save Game file '%s'\n", filename); + term->print(10); + return; + } + } + + + do { + delete term; term = 0; + term = parser.ParseTerm(); + + if (term) { + TermDef* def = term->isDef(); + if (def) { + if (def->name()->value() == "group") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: group struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + + char name[256]; + char type[64]; + char intel[64]; + char region[64]; + char system[64]; + char zone[64]; + bool zone_locked = false; + int id = 0; + int iff = -1; + int sorties = -1; + int kills = -1; + int points = -1; + Vec3 loc = Vec3(1.0e9f,0.0f,0.0f); + + List unit_list; + char unit_name[64]; + char unit_regnum[16]; + char unit_design[64]; + int unit_class = 0; + int unit_count = 1; + int unit_dead = 0; + int unit_damage = 0; + int unit_heading= 0; + int unit_index = 0; + + *name = 0; + *type = 0; + *intel = 0; + *region = 0; + *system = 0; + *zone = 0; + *unit_name = 0; + *unit_regnum = 0; + *unit_design = 0; + + strcpy(intel, "KNOWN"); + + // all groups in this OOB default to the IFF of the main force + if (force) + iff = force->GetIFF(); + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef && (iff < 0 || team < 0 || iff == team)) { + GET_DEF_TEXT(name); + else GET_DEF_TEXT(type); + else GET_DEF_TEXT(intel); + else GET_DEF_TEXT(region); + else GET_DEF_TEXT(system); + else GET_DEF_TEXT(zone); + else GET_DEF_BOOL(zone_locked); + else GET_DEF_VEC(loc); + else GET_DEF_NUM(iff); + else GET_DEF_NUM(id); + else GET_DEF_NUM(sorties); + else GET_DEF_NUM(kills); + else GET_DEF_NUM(points); + else GET_DEF_NUM(unit_index); + + else if ((iff == team || team < 0) && pdef->name()->value() == "unit") { + if (!pdef->term() || !pdef->term()->isStruct()) { + Print("WARNING: unit struct missing for group '%s' in '%s'\n", name, filename); + } + else { + TermStruct* val = pdef->term()->isStruct(); + + char unit_region[64]; + char design[256]; + Vec3 unit_loc=Vec3(0.0f,0.0f,0.0f); + unit_count = 1; + + ZeroMemory(unit_region, sizeof(unit_region)); + ZeroMemory(design, sizeof(design)); + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + if (pdef->name()->value() == "name") { + GetDefText(unit_name, pdef, filename); + } + else if (pdef->name()->value() == "regnum") { + GetDefText(unit_regnum, pdef, filename); + } + else if (pdef->name()->value() == "region") { + GetDefText(unit_region, pdef, filename); + } + else if (pdef->name()->value() == "loc") { + GetDefVec(unit_loc, pdef, filename); + } + else if (pdef->name()->value() == "type") { + char typestr[32]; + GetDefText(typestr, pdef, filename); + unit_class = ShipDesign::ClassForName(typestr); + } + else if (pdef->name()->value() == "design") { + GetDefText(unit_design, pdef, filename); + } + else if (pdef->name()->value() == "count") { + GetDefNumber(unit_count, pdef, filename); + } + else if (pdef->name()->value() == "dead_count") { + GetDefNumber(unit_dead, pdef, filename); + } + else if (pdef->name()->value() == "damage") { + GetDefNumber(unit_damage, pdef, filename); + } + else if (pdef->name()->value() == "heading") { + GetDefNumber(unit_heading, pdef, filename); + } + } + } + + if (!ShipDesign::CheckName(unit_design)) { + Print("ERROR: invalid design '%s' for unit '%s' in '%s'\n", unit_design, unit_name, filename); + return; + } + + if (force) { + CombatUnit* cu = new(__FILE__,__LINE__) CombatUnit(unit_name, unit_regnum, unit_class, unit_design, unit_count, iff); + cu->SetRegion(unit_region); + cu->MoveTo(unit_loc); + cu->Kill(unit_dead); + cu->SetSustainedDamage(unit_damage); + cu->SetHeading(unit_heading * DEGREES); + unit_list.append(cu); + } + } + } + } + } // elements + + if (iff >= 0 && (iff == team || team < 0)) { + // have we found the force group we are looking for yet? + if (!force && !stricmp(name, combatant->Name())) { + force = combatant->GetForce(); + } + + else { + if (!force) + continue; + + // if we already have a force, and we find a second one, + // it must be the start of a different combatant. + // So don't process any further: + if (TypeFromName(type) == CombatGroup::FORCE) { + break; + } + } + + CombatGroup* g = force->FindGroup(TypeFromName(type), id); + + if (!g) { + ::Print("WARNING: unexpected combat group %s %d '%s' in '%s'\n", type, id, name, filename); + continue; + } + + g->region = region; + g->combatant = combatant; + g->location = loc; + g->enemy_intel = Intel::IntelFromName(intel); + g->unit_index = unit_index; + + if (*zone) { + CombatZone* combat_zone = campaign->GetZone(zone); + + if (combat_zone) { + g->SetAssignedZone(combat_zone); + g->SetZoneLock(zone_locked); + } + else { + ::Print("WARNING: could not find combat zone '%s' for group %s %d '%s' in '%s'\n", zone, type, id, name, filename); + } + } + else if (*system) { + g->SetAssignedSystem(system); + } + + if (sorties >= 0) g->SetSorties(sorties); + if (kills >= 0) g->SetKills(kills); + if (points >= 0) g->SetPoints(points); + + if (unit_list.size()) { + ListIter u_iter = unit_list; + while (++u_iter) { + CombatUnit* load_unit = u_iter.value(); + CombatUnit* u = g->FindUnit(load_unit->Name()); + + if (u) { + if (load_unit->GetRegion().length() > 0) { + u->SetRegion(load_unit->GetRegion()); + u->MoveTo(load_unit->Location()); + } + else { + u->SetRegion(g->GetRegion()); + u->MoveTo(g->Location()); + } + u->SetDeadCount(load_unit->DeadCount()); + u->SetSustainedDamage(load_unit->GetSustainedDamage()); + u->SetHeading(load_unit->GetHeading()); + } + } + + unit_list.destroy(); + } + + if (!force) + force = g; + } // iff == team? + } // group-struct + } // group + } // def + } // term + } + while (term); + + Print("Order of Battle Loaded (%s).\n", force ? force->Name() : "unknown force"); + + if (force) + force->CalcValue(); +} + +// +--------------------------------------------------------------------+ + +Text FormatNumber(double n) +{ + char buffer[64]; + + if (fabs(n) < 1000) + sprintf(buffer, "%d", (int) n); + + else if (fabs(n) < 1e6) { + int nn = (int) n / 1000; + sprintf(buffer, "%de3", nn); + } + + else + sprintf(buffer, "%g", n); + + return buffer; +} + +void +SaveCombatUnit(FILE* f, CombatUnit* u) +{ + int type = u->Type(); + + if (type == 0 && u->GetDesign()) + type = u->GetDesign()->type; + + fprintf(f, "\n unit: {"); + fprintf(f, " name: \"%s\",", u->Name().data()); + fprintf(f, " type: \"%s\",", Ship::ClassName(type)); + fprintf(f, " design: \"%s\",", u->DesignName().data()); + + if (u->Count() > 1) { + fprintf(f, " count: %d,", u->Count()); + } + else { + fprintf(f, " regnum:\"%s\",", u->Registry().data()); + } + + if (u->GetRegion().length() > 0) { + fprintf(f, " region:\"%s\",", u->GetRegion().data()); + + Text x = FormatNumber(u->Location().x); + Text y = FormatNumber(u->Location().y); + Text z = FormatNumber(u->Location().z); + + fprintf(f, " loc:(%s, %s, %s),", x.data(), y.data(), z.data()); + } + + fprintf(f, " dead_count: %d, damage: %d, heading: %d },", + (int) u->DeadCount(), + (int) u->GetSustainedDamage(), + (int) (u->GetHeading() / DEGREES)); +} + +void +SaveCombatGroup(FILE* f, CombatGroup* g) +{ + fprintf(f, "group: {"); + fprintf(f, " type: %s,", CombatGroup::NameFromType(g->Type())); + fprintf(f, " id: %d,", g->GetID()); + fprintf(f, " name: \"%s\",", g->Name()); + fprintf(f, " intel: %s,", Intel::NameFromIntel(g->IntelLevel())); + fprintf(f, " iff: %d,", g->GetIFF()); + fprintf(f, " unit_index: %d,", g->UnitIndex()); + + if (g->GetRegion().length()) { + fprintf(f, " region:\"%s\",", g->GetRegion()); + } + + if (g->GetAssignedSystem().length()) { + fprintf(f, " system: \"%s\",", g->GetAssignedSystem().data()); + } + + if (g->GetAssignedZone()) { + fprintf(f, " zone: \"%s\",", g->GetAssignedZone()->Name().data()); + if (g->IsZoneLocked()) { + fprintf(f, " zone_locked: true,"); + } + } + + Text x = FormatNumber(g->Location().x); + Text y = FormatNumber(g->Location().y); + Text z = FormatNumber(g->Location().z); + + fprintf(f, " loc: (%s, %s, %s),", x.data(), y.data(), z.data()); + + CombatGroup* parent = g->GetParent(); + if (parent) { + fprintf(f, " parent_type:%s,", CombatGroup::NameFromType(parent->Type())); + fprintf(f, " parent_id:%d,", parent->GetID()); + } + + fprintf(f, " sorties: %d,", g->Sorties()); + fprintf(f, " kills: %d,", g->Kills()); + fprintf(f, " points: %d,", g->Points()); + + ListIter u = g->GetUnits(); + while (++u) { + SaveCombatUnit(f, u.value()); + } + + fprintf(f, " }\n"); + + ListIter c = g->GetComponents(); + while (++c) { + SaveCombatGroup(f, c.value()); + } +} + +void +CombatGroup::SaveOrderOfBattle(const char* filename, CombatGroup* force) +{ + FILE* f = ::fopen(filename, "a+"); + + if (f) { + SaveCombatGroup(f, force); + fprintf(f, "\n"); + fclose(f); + } +} diff --git a/Stars45/CombatGroup.h b/Stars45/CombatGroup.h new file mode 100644 index 0000000..54c4119 --- /dev/null +++ b/Stars45/CombatGroup.h @@ -0,0 +1,231 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CombatGroup.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== +*/ + +#ifndef CombatGroup_h +#define CombatGroup_h + +#include "Types.h" +#include "Geometry.h" +#include "Text.h" +#include "List.h" +#include "Intel.h" + +// +--------------------------------------------------------------------+ + +class Campaign; +class Combatant; +class CombatGroup; +class CombatUnit; +class CombatZone; +class CombatAssignment; + +// +--------------------------------------------------------------------+ + +class CombatGroup +{ +public: + static const char* TYPENAME() { return "CombatGroup"; } + + enum GROUP_TYPE { + FORCE = 1, // Commander In Chief + + WING, // Air Force + INTERCEPT_SQUADRON, // a2a fighter + FIGHTER_SQUADRON, // multi-role fighter + ATTACK_SQUADRON, // strike / attack + LCA_SQUADRON, // landing craft + + FLEET, // Navy + DESTROYER_SQUADRON, // destroyer + BATTLE_GROUP, // heavy cruiser(s) + CARRIER_GROUP, // fleet carrier + + BATTALION, // Army + MINEFIELD, + BATTERY, + MISSILE, + STATION, // orbital station + STARBASE, // planet-side base + + C3I, // Command, Control, Communications, Intelligence + COMM_RELAY, + EARLY_WARNING, + FWD_CONTROL_CTR, + ECM, + + SUPPORT, + COURIER, + MEDICAL, + SUPPLY, + REPAIR, + + CIVILIAN, // root for civilian groups + + WAR_PRODUCTION, + FACTORY, + REFINERY, + RESOURCE, + + INFRASTRUCTURE, + TRANSPORT, + NETWORK, + HABITAT, + STORAGE, + + NON_COM, // other civilian traffic + FREIGHT, + PASSENGER, + PRIVATE + }; + + CombatGroup(int t, int n, const char* s, int i, int e, CombatGroup* p=0); + ~CombatGroup(); + + // comparison operators are used to sort combat groups into a priority list + // in DESCENDING order, so the sense of the comparison is backwards from + // usual... + int operator < (const CombatGroup& g) const { return value > g.value; } + int operator <= (const CombatGroup& g) const { return value >= g.value; } + int operator == (const CombatGroup& g) const { return this == &g; } + + // operations: + static CombatGroup* LoadOrderOfBattle(const char* fname, int iff, Combatant* combatant); + static void SaveOrderOfBattle(const char* fname, CombatGroup* force); + static void MergeOrderOfBattle(BYTE* block, const char* fname, int iff, Combatant* combatant, Campaign* campaign); + + void AddComponent(CombatGroup* g); + CombatGroup* FindGroup(int t, int n=-1); + CombatGroup* Clone(bool deep=true); + + // accessors and mutators: + const char* GetDescription() const; + const char* GetShortDescription() const; + + void SetCombatant(Combatant* c) { combatant = c; } + + Combatant* GetCombatant() { return combatant; } + CombatGroup* GetParent() { return parent; } + List& GetComponents() { return components; } + List& GetLiveComponents() { return live_comp; } + List& GetUnits() { return units; } + CombatUnit* GetRandomUnit(); + CombatUnit* GetFirstUnit(); + CombatUnit* GetNextUnit(); + CombatUnit* FindUnit(const char* name); + CombatGroup* FindCarrier(); + + const Text& Name() const { return name; } + int Type() const { return type; } + int CountUnits() const; + int IntelLevel() const { return enemy_intel;} + int GetID() const { return id; } + int GetIFF() const { return iff; } + Point Location() const { return location; } + void MoveTo(const Point& loc); + const Text& GetRegion() const { return region; } + void SetRegion(Text rgn) { region = rgn; } + void AssignRegion(Text rgn); + int Value() const { return value; } + int Sorties() const { return sorties; } + void SetSorties(int n) { sorties = n; } + int Kills() const { return kills; } + void SetKills(int n) { kills = n; } + int Points() const { return points; } + void SetPoints(int n) { points = n; } + int UnitIndex() const { return unit_index; } + + double GetNextJumpTime() const; + + double GetPlanValue() const { return plan_value; } + void SetPlanValue(double v) { plan_value = v; } + + bool IsAssignable() const; + bool IsTargetable() const; + bool IsDefensible() const; + bool IsStrikeTarget() const; + bool IsMovable() const; + bool IsFighterGroup() const; + bool IsStarshipGroup() const; + bool IsReserve() const; + + // these two methods return zero terminated arrays of + // integers identifying the preferred assets for attack + // or defense in priority order: + static const int* PreferredAttacker(int type); + static const int* PreferredDefender(int type); + + bool IsExpanded() const { return expanded; } + void SetExpanded(bool e) { expanded = e; } + + const Text& GetAssignedSystem() const { return assigned_system; } + void SetAssignedSystem(const char* s); + CombatZone* GetCurrentZone() const { return current_zone; } + void SetCurrentZone(CombatZone* z) { current_zone = z; } + CombatZone* GetAssignedZone() const { return assigned_zone; } + void SetAssignedZone(CombatZone* z); + void ClearUnlockedZones(); + bool IsZoneLocked() const { return assigned_zone && zone_lock; } + void SetZoneLock(bool lock=true); + bool IsSystemLocked() const { return assigned_system.length() > 0; } + + const Text& GetStrategicDirection() const { return strategic_direction; } + void SetStrategicDirection(Text dir) { strategic_direction = dir; } + + void SetIntelLevel(int n); + int CalcValue(); + + List& GetAssignments() { return assignments; } + void ClearAssignments(); + + static int TypeFromName(const char* name); + static const char* NameFromType(int type); + +private: + const char* GetOrdinal() const; + + // attributes: + int type; + int id; + Text name; + int iff; + int enemy_intel; + + double plan_value; // scratch pad for plan modules + + List units; + List components; + List live_comp; + Combatant* combatant; + CombatGroup* parent; + Text region; + Point location; + int value; + int unit_index; + + int sorties; + int kills; + int points; + + bool expanded; // for tree control + + Text assigned_system; + CombatZone* current_zone; + CombatZone* assigned_zone; + bool zone_lock; + List assignments; + + Text strategic_direction; +}; + +#endif CombatGroup_h + diff --git a/Stars45/CombatRoster.cpp b/Stars45/CombatRoster.cpp new file mode 100644 index 0000000..66f7455 --- /dev/null +++ b/Stars45/CombatRoster.cpp @@ -0,0 +1,93 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CombatRoster.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + The complete roster of all known persistent entities + for all combatants in the game. +*/ + +#include "MemDebug.h" +#include "CombatRoster.h" +#include "CombatGroup.h" + +#include "Game.h" +#include "DataLoader.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +static CombatRoster* roster = 0; + +// +--------------------------------------------------------------------+ + +CombatRoster::CombatRoster() +{ + DataLoader* loader = DataLoader::GetLoader(); + loader->SetDataPath("Campaigns/"); + + List files; + loader->ListFiles("*.def", files); + + for (int i = 0; i < files.size(); i++) { + Text filename = *files[i]; + + if (!filename.contains("/") && !filename.contains("\\")) { + loader->SetDataPath("Campaigns/"); + CombatGroup* g = CombatGroup::LoadOrderOfBattle(filename, -1, 0); + forces.append(g); + } + } + + files.destroy(); +} + +// +--------------------------------------------------------------------+ + +CombatRoster::~CombatRoster() +{ + forces.destroy(); +} + +// +--------------------------------------------------------------------+ + +CombatGroup* +CombatRoster::GetForce(const char* name) +{ + ListIter iter = forces; + while (++iter) { + CombatGroup* f = iter.value(); + + if (f->Name() == name) + return f; + } + + return 0; +} + +// +--------------------------------------------------------------------+ + +void +CombatRoster::Initialize() +{ + roster = new(__FILE__,__LINE__) CombatRoster(); +} + +void +CombatRoster::Close() +{ + delete roster; + roster = 0; +} + +CombatRoster* +CombatRoster::GetInstance() +{ + return roster; +} \ No newline at end of file diff --git a/Stars45/CombatRoster.h b/Stars45/CombatRoster.h new file mode 100644 index 0000000..2c1f4c8 --- /dev/null +++ b/Stars45/CombatRoster.h @@ -0,0 +1,48 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CombatRoster.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + The complete roster of all known persistent entities + for all combatants in the game. +*/ + +#ifndef CombatRoster_h +#define CombatRoster_h + +#include "Types.h" +#include "Geometry.h" +#include "List.h" + +// +--------------------------------------------------------------------+ + +class CombatGroup; + +// +--------------------------------------------------------------------+ + +class CombatRoster +{ + CombatRoster(); + ~CombatRoster(); + +public: + static const char* TYPENAME() { return "CombatRoster"; } + + static void Initialize(); + static void Close(); + static CombatRoster* GetInstance(); + + CombatGroup* GetForce(const char* name); + +private: + List forces; +}; + + +#endif CombatRoster_h diff --git a/Stars45/CombatUnit.cpp b/Stars45/CombatUnit.cpp new file mode 100644 index 0000000..cdbf993 --- /dev/null +++ b/Stars45/CombatUnit.cpp @@ -0,0 +1,402 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CombatUnit.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + A ship, station, or ground unit in the dynamic campaign. +*/ + +#include "MemDebug.h" +#include "CombatUnit.h" +#include "CombatGroup.h" +#include "Campaign.h" +#include "ShipDesign.h" +#include "Ship.h" + +#include "Game.h" + +// +----------------------------------------------------------------------+ + +inline double random() { return (double) rand() / (double) RAND_MAX; } + +// +----------------------------------------------------------------------+ + +CombatUnit::CombatUnit(const char* n, const char* reg, int t, const char* d, int c, int i) + : name(n), regnum(reg), type(t), design_name(d), design(0), + count(c), iff(i), leader(false), dead_count(0), available(c), + carrier(0), plan_value(0), launch_time(-1e6), jump_time(0), + sustained_damage(0), target(0), group(0), heading(0) +{ } + +CombatUnit::CombatUnit(const CombatUnit& u) + : name(u.name), regnum(u.regnum), type(u.type), design_name(u.design_name), + design(u.design), count(u.count), iff(u.iff), + dead_count(u.dead_count), available(u.available), + leader(u.leader), region(u.region), location(u.location), + carrier(u.carrier), plan_value(0), launch_time(u.launch_time), + jump_time(u.jump_time), sustained_damage(u.sustained_damage), + target(0), group(0), heading(u.heading) +{ } + +// +----------------------------------------------------------------------+ + +const ShipDesign* +CombatUnit::GetDesign() +{ + if (!design) + design = ShipDesign::Get(design_name); + + return design; +} + +int +CombatUnit::GetShipClass() const +{ + if (design) + return design->type; + + return type; +} + +int +CombatUnit::GetValue() const +{ + return GetSingleValue() * LiveCount(); +} + +int +CombatUnit::GetSingleValue() const +{ + return Ship::Value(GetShipClass()); +} + +// +----------------------------------------------------------------------+ + +const char* +CombatUnit::GetDescription() const +{ + if (!design) { + CombatUnit* pThis = (CombatUnit*) this; // cast-away const + pThis->GetDesign(); + } + + static char desc[256]; + + if (!design) { + strcpy(desc, Game::GetText("[unknown]").data()); + } + + else if (count > 1) { + sprintf(desc, "%dx %s %s", LiveCount(), design->abrv, design->DisplayName()); + } + + else { + if (regnum.length() > 0) + sprintf(desc, "%s-%s %s", design->abrv, (const char*) regnum, (const char*) name); + else + sprintf(desc, "%s %s", design->abrv, (const char*) name); + + if (dead_count > 0) { + strcat(desc, " "); + strcat(desc, Game::GetText("killed.in.action")); + } + } + + return desc; +} + +// +----------------------------------------------------------------------+ + +bool +CombatUnit::CanAssign() const +{ + bool result = false; + + switch (type) { + case Ship::FIGHTER: + case Ship::ATTACK: + case Ship::CORVETTE: + case Ship::FRIGATE: + case Ship::DESTROYER: + case Ship::CRUISER: + case Ship::CARRIER: result = true; break; + } + + return result; +} + +// +----------------------------------------------------------------------+ + +bool +CombatUnit::CanLaunch() const +{ + bool result = false; + + switch (type) { + case Ship::FIGHTER: + case Ship::ATTACK: result = (Campaign::Stardate() - launch_time) >= 300; + break; + + case Ship::CORVETTE: + case Ship::FRIGATE: + case Ship::DESTROYER: + case Ship::CRUISER: + case Ship::CARRIER: result = true; + break; + } + + return result; +} + +// +----------------------------------------------------------------------+ + +Color +CombatUnit::MarkerColor() const +{ + return Ship::IFFColor(iff); +} + +bool +CombatUnit::IsGroundUnit() const +{ + return (design && (design->type & Ship::GROUND_UNITS)) ? true : false; +} + +bool +CombatUnit::IsStarship() const +{ + return (design && (design->type & Ship::STARSHIPS)) ? true : false; +} + +bool +CombatUnit::IsDropship() const +{ + return (design && (design->type & Ship::DROPSHIPS)) ? true : false; +} + +bool +CombatUnit::IsStatic() const +{ + return design && (design->type >= Ship::STATION); +} + +// +----------------------------------------------------------------------+ + +double +CombatUnit::MaxRange() const +{ + return 100e3; +} + +double CombatUnit::MaxEffectiveRange() const +{ + return 50e3; +} + +double CombatUnit::OptimumRange() const +{ + if (type == Ship::FIGHTER || type == Ship::ATTACK) + return 15e3; + + return 30e3; +} + +// +----------------------------------------------------------------------+ + +bool CombatUnit::CanDefend(CombatUnit* unit) const +{ + if (unit == 0 || unit == this) + return false; + + if (type > Ship::STATION) + return false; + + double distance = (location - unit->location).length(); + + if (type > unit->type) + return false; + + if (distance > MaxRange()) + return false; + + return true; +} + +// +----------------------------------------------------------------------+ + +double CombatUnit::PowerVersus(CombatUnit* tgt) const +{ + if (tgt == 0 || tgt == this || available < 1) + return 0; + + if (type > Ship::STATION) + return 0; + + double effectiveness = 1; + double distance = (location - tgt->location).length(); + + if (distance > MaxRange()) + return 0; + + if (distance > MaxEffectiveRange()) + effectiveness = 0.5; + + if (type == Ship::FIGHTER) { + if (tgt->type == Ship::FIGHTER || tgt->type == Ship::ATTACK) + return Ship::FIGHTER * 2 * available * effectiveness; + else + return 0; + } + else if (type == Ship::ATTACK) { + if (tgt->type > Ship::ATTACK) + return Ship::ATTACK * 3 * available * effectiveness; + else + return 0; + } + else if (type == Ship::CARRIER) { + return 0; + } + else if (type == Ship::CRUISER) { + if (tgt->type <= Ship::ATTACK) + return type * effectiveness; + + else + return 0; + } + else { + if (tgt->type > Ship::ATTACK) + return type * effectiveness; + else + return type * 0.1 * effectiveness; + } + + return 0; +} + +// +----------------------------------------------------------------------+ + +int +CombatUnit::AssignMission() +{ + int assign = count; + + if (count > 4) + assign = 4; + + if (assign > 0) { + available -= assign; + launch_time = Campaign::Stardate(); + return assign; + } + + return 0; +} + +// +----------------------------------------------------------------------+ + +void +CombatUnit::CompleteMission() +{ + Disengage(); + + if (count > 4) + available += 4; + + else + available += count; +} + +// +----------------------------------------------------------------------+ + +void +CombatUnit::MoveTo(const Point& loc) +{ + if (!carrier) + location = loc; + else + location = carrier->location; +} + +// +----------------------------------------------------------------------+ + +void +CombatUnit::Engage(CombatUnit* tgt) +{ + if (!tgt) + Disengage(); + + else if (!tgt->attackers.contains(this)) + tgt->attackers.append(this); + + target = tgt; +} + +void +CombatUnit::Disengage() +{ + if (target) + target->attackers.remove(this); + + target = 0; +} + +// +----------------------------------------------------------------------+ + +static int KillGroup(CombatGroup* group) +{ + int value_killed = 0; + + if (group) { + ListIter u_iter = group->GetUnits(); + while (++u_iter) { + CombatUnit* u = u_iter.value(); + value_killed += u->Kill(u->LiveCount()); + } + + ListIter g_iter = group->GetComponents(); + while (++g_iter) { + CombatGroup* g = g_iter.value(); + value_killed += KillGroup(g); + } + } + + return value_killed; +} + +int +CombatUnit::Kill(int n) +{ + int killed = n; + + if (killed > LiveCount()) + killed = LiveCount(); + + dead_count += killed; + + int value_killed = killed * GetSingleValue(); + + if (killed) { + // if unit could support children, kill them too: + if (type == Ship::CARRIER || + type == Ship::STATION || + type == Ship::STARBASE) { + + if (group) { + ListIter iter = group->GetComponents(); + while (++iter) { + CombatGroup* g = iter.value(); + value_killed += KillGroup(g); + } + } + } + } + + return value_killed; +} + diff --git a/Stars45/CombatUnit.h b/Stars45/CombatUnit.h new file mode 100644 index 0000000..13af80a --- /dev/null +++ b/Stars45/CombatUnit.h @@ -0,0 +1,134 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CombatUnit.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + A ship, station, or ground unit in the dynamic campaign. +*/ + +#ifndef CombatUnit_h +#define CombatUnit_h + +#include "Types.h" +#include "Geometry.h" +#include "Color.h" +#include "Text.h" +#include "List.h" + +// +--------------------------------------------------------------------+ + +class CombatGroup; +class ShipDesign; + +// +--------------------------------------------------------------------+ + +class CombatUnit +{ +public: + static const char* TYPENAME() { return "CombatUnit"; } + + CombatUnit(const char* n, const char* reg, int t, const char* dname, int number, int i); + CombatUnit(const CombatUnit& unit); + + int operator == (const CombatUnit& u) const { return this == &u; } + + const char* GetDescription() const; + + int GetValue() const; + int GetSingleValue() const; + bool CanDefend(CombatUnit* unit) const; + bool CanAssign() const; + bool CanLaunch() const; + double PowerVersus(CombatUnit* tgt) const; + int AssignMission(); + void CompleteMission(); + + double MaxRange() const; + double MaxEffectiveRange() const; + double OptimumRange() const; + + void Engage(CombatUnit* tgt); + void Disengage(); + + // accessors and mutators: + const Text& Name() const { return name; } + const Text& Registry() const { return regnum; } + const Text& DesignName() const { return design_name; } + const Text& Skin() const { return skin; } + void SetSkin(const char* s) { skin = s; } + int Type() const { return type; } + int Count() const { return count; } + int LiveCount() const { return count - dead_count; } + int DeadCount() const { return dead_count; } + void SetDeadCount(int n) { dead_count = n; } + int Kill(int n); + int Available() const { return available; } + int GetIFF() const { return iff; } + bool IsLeader() const { return leader; } + void SetLeader(bool l) { leader = l; } + Point Location() const { return location; } + void MoveTo(const Point& loc); + Text GetRegion() const { return region; } + void SetRegion(Text rgn) { region = rgn; } + CombatGroup* GetCombatGroup() const { return group; } + void SetCombatGroup(CombatGroup* g){ group = g; } + + Color MarkerColor() const; + bool IsGroundUnit() const; + bool IsStarship() const; + bool IsDropship() const; + bool IsStatic() const; + + CombatUnit* GetCarrier() const { return carrier; } + void SetCarrier(CombatUnit* c) { carrier = c; } + + const ShipDesign* GetDesign(); + int GetShipClass() const; + + List& GetAttackers() { return attackers; } + + double GetPlanValue() const { return plan_value; } + void SetPlanValue(int v) { plan_value = v; } + + double GetSustainedDamage() const { return sustained_damage; } + void SetSustainedDamage(double d) { sustained_damage = d; } + + double GetHeading() const { return heading; } + void SetHeading(double d) { heading = d; } + + double GetNextJumpTime() const { return jump_time; } + +private: + Text name; + Text regnum; + Text design_name; + Text skin; + int type; + const ShipDesign* design; + int count; + int dead_count; + int available; + int iff; + bool leader; + Text region; + Point location; + double plan_value; // scratch pad for plan modules + double launch_time; + double jump_time; + double sustained_damage; + double heading; + + CombatUnit* carrier; + List attackers; + CombatUnit* target; + CombatGroup* group; +}; + +#endif CombatUnit_h + diff --git a/Stars45/CombatZone.cpp b/Stars45/CombatZone.cpp new file mode 100644 index 0000000..d829074 --- /dev/null +++ b/Stars45/CombatZone.cpp @@ -0,0 +1,282 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CombatZone.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + CombatZone is used by the dynamic campaign strategy + and logistics algorithms to assign forces to locations + within the campaign. A CombatZone is a collection of + closely related sectors, and the assets contained + within them. +*/ + +#include "MemDebug.h" +#include "CombatZone.h" +#include "CombatGroup.h" +#include "CombatUnit.h" +#include "Campaign.h" +#include "ShipDesign.h" +#include "Ship.h" + +#include "Game.h" +#include "DataLoader.h" +#include "ParseUtil.h" + +// +----------------------------------------------------------------------+ + +CombatZone::CombatZone() +{ +} + +CombatZone::~CombatZone() +{ + regions.destroy(); + forces.destroy(); +} + +// +--------------------------------------------------------------------+ + +void +CombatZone::Clear() +{ + forces.destroy(); +} + +// +--------------------------------------------------------------------+ + +void +CombatZone::AddGroup(CombatGroup* group) +{ + if (group) { + int iff = group->GetIFF(); + ZoneForce* f = FindForce(iff); + f->AddGroup(group); + group->SetCurrentZone(this); + } +} + +void +CombatZone::RemoveGroup(CombatGroup* group) +{ + if (group) { + int iff = group->GetIFF(); + ZoneForce* f = FindForce(iff); + f->RemoveGroup(group); + group->SetCurrentZone(0); + } +} + +bool +CombatZone::HasGroup(CombatGroup* group) +{ + if (group) { + int iff = group->GetIFF(); + ZoneForce* f = FindForce(iff); + return f->HasGroup(group); + } + + return false; +} + +// +--------------------------------------------------------------------+ + +void +CombatZone::AddRegion(const char* rgn) +{ + if (rgn && *rgn) { + regions.append(new (__FILE__,__LINE__) Text(rgn)); + + if (name.length() < 1) + name = rgn; + } +} + +// +--------------------------------------------------------------------+ + +bool +CombatZone::HasRegion(const char* rgn) +{ + if (rgn && *rgn && regions.size()) { + Text test(rgn); + return regions.contains(&test); + } + + return false; +} + +// +--------------------------------------------------------------------+ + +ZoneForce* +CombatZone::FindForce(int iff) +{ + ListIter f = forces; + while (++f) { + if (f->GetIFF() == iff) + return f.value(); + } + + return MakeForce(iff); +} + +// +--------------------------------------------------------------------+ + +ZoneForce* +CombatZone::MakeForce(int iff) +{ + ZoneForce* f = new(__FILE__,__LINE__) ZoneForce(iff); + forces.append(f); + return f; +} + +// +--------------------------------------------------------------------+ + +static List zonelist; + +List& +CombatZone::Load(const char* filename) +{ + zonelist.clear(); + + DataLoader* loader = DataLoader::GetLoader(); + BYTE* block = 0; + + loader->LoadBuffer(filename, block, true); + Parser parser(new(__FILE__,__LINE__) BlockReader((const char*) block)); + + Term* term = parser.ParseTerm(); + + if (!term) { + return zonelist; + } + else { + TermText* file_type = term->isText(); + if (!file_type || file_type->value() != "ZONES") { + return zonelist; + } + } + + do { + delete term; term = 0; + term = parser.ParseTerm(); + + if (term) { + TermDef* def = term->isDef(); + if (def) { + if (def->name()->value() == "zone") { + if (!def->term() || !def->term()->isStruct()) { + ::Print("WARNING: zone struct missing in '%s%s'\n", loader->GetDataPath(), filename); + } + else { + TermStruct* val = def->term()->isStruct(); + + CombatZone* zone = new(__FILE__,__LINE__) CombatZone(); + char rgn[64]; + ZeroMemory(rgn, sizeof(rgn)); + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + if (pdef->name()->value() == "region") { + GetDefText(rgn, pdef, filename); + zone->AddRegion(rgn); + } + else if (pdef->name()->value() == "system") { + GetDefText(rgn, pdef, filename); + zone->system = rgn; + } + } + } + + zonelist.append(zone); + } + } + } + } + } + while (term); + + loader->ReleaseBuffer(block); + + return zonelist; +} + +// +--------------------------------------------------------------------+ + +ZoneForce::ZoneForce(int i) +{ + iff = i; + + for (int n = 0; n < 8; n++) + need[n] = 0; +} + +void +ZoneForce::AddGroup(CombatGroup* group) +{ + if (group) + groups.append(group); +} + +void +ZoneForce::RemoveGroup(CombatGroup* group) +{ + if (group) + groups.remove(group); +} + +bool +ZoneForce::HasGroup(CombatGroup* group) +{ + if (group) + return groups.contains(group); + + return false; +} + +int +ZoneForce::GetNeed(int group_type) const +{ + switch (group_type) { + case CombatGroup::CARRIER_GROUP: return need[0]; + case CombatGroup::BATTLE_GROUP: return need[1]; + case CombatGroup::DESTROYER_SQUADRON: return need[2]; + case CombatGroup::ATTACK_SQUADRON: return need[3]; + case CombatGroup::FIGHTER_SQUADRON: return need[4]; + case CombatGroup::INTERCEPT_SQUADRON: return need[5]; + } + + return 0; +} + +void +ZoneForce::SetNeed(int group_type, int needed) +{ + switch (group_type) { + case CombatGroup::CARRIER_GROUP: need[0] = needed; break; + case CombatGroup::BATTLE_GROUP: need[1] = needed; break; + case CombatGroup::DESTROYER_SQUADRON: need[2] = needed; break; + case CombatGroup::ATTACK_SQUADRON: need[3] = needed; break; + case CombatGroup::FIGHTER_SQUADRON: need[4] = needed; break; + case CombatGroup::INTERCEPT_SQUADRON: need[5] = needed; break; + } +} + +void +ZoneForce::AddNeed(int group_type, int needed) +{ + switch (group_type) { + case CombatGroup::CARRIER_GROUP: need[0] += needed; break; + case CombatGroup::BATTLE_GROUP: need[1] += needed; break; + case CombatGroup::DESTROYER_SQUADRON: need[2] += needed; break; + case CombatGroup::ATTACK_SQUADRON: need[3] += needed; break; + case CombatGroup::FIGHTER_SQUADRON: need[4] += needed; break; + case CombatGroup::INTERCEPT_SQUADRON: need[5] += needed; break; + } +} + diff --git a/Stars45/CombatZone.h b/Stars45/CombatZone.h new file mode 100644 index 0000000..3c1058d --- /dev/null +++ b/Stars45/CombatZone.h @@ -0,0 +1,103 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CombatZone.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + CombatZone is used by the dynamic campaign strategy + and logistics algorithms to assign forces to locations + within the campaign. A CombatZone is a collection of + closely related sectors, and the assets contained + within them. +*/ + +#ifndef CombatZone_h +#define CombatZone_h + +#include "Types.h" +#include "Geometry.h" +#include "Text.h" +#include "List.h" + +// +--------------------------------------------------------------------+ + +class CombatGroup; +class CombatUnit; +class ZoneForce; + +// +--------------------------------------------------------------------+ + +class CombatZone +{ +public: + static const char* TYPENAME() { return "CombatZone"; } + + CombatZone(); + ~CombatZone(); + + int operator == (const CombatZone& g) const { return this == &g; } + + const Text& Name() const { return name; } + const Text& System() const { return system; } + void AddGroup(CombatGroup* g); + void RemoveGroup(CombatGroup* g); + bool HasGroup(CombatGroup* g); + void AddRegion(const char* rgn); + bool HasRegion(const char* rgn); + List& GetRegions() { return regions; } + List& GetForces() { return forces; } + + ZoneForce* FindForce(int iff); + ZoneForce* MakeForce(int iff); + + void Clear(); + + static List& + Load(const char* filename); + +private: + // attributes: + Text name; + Text system; + List regions; + List forces; +}; + +// +--------------------------------------------------------------------+ + +class ZoneForce +{ +public: + ZoneForce(int i); + + int GetIFF() { return iff; } + List& GetGroups() { return groups; } + List& GetTargetList() { return target_list; } + List& GetDefendList() { return defend_list; } + + void AddGroup(CombatGroup* g); + void RemoveGroup(CombatGroup* g); + bool HasGroup(CombatGroup* g); + + int GetNeed(int group_type) const; + void SetNeed(int group_type, int needed); + void AddNeed(int group_type, int needed); + +private: + // attributes: + int iff; + List groups; + List defend_list; + List target_list; + int need[8]; +}; + +// +--------------------------------------------------------------------+ + +#endif CombatZone_h + diff --git a/Stars45/Combatant.cpp b/Stars45/Combatant.cpp new file mode 100644 index 0000000..21fcc78 --- /dev/null +++ b/Stars45/Combatant.cpp @@ -0,0 +1,172 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Combatant.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + One side in a military conflict +*/ + +#include "MemDebug.h" +#include "Combatant.h" +#include "CombatGroup.h" +#include "Mission.h" + +#include "Game.h" + +// +--------------------------------------------------------------------+ + +static void SetCombatant(CombatGroup* g, Combatant* c) +{ + if (!g) return; + + g->SetCombatant(c); + + ListIter iter = g->GetComponents(); + while (++iter) + SetCombatant(iter.value(), c); +} + +// +--------------------------------------------------------------------+ + +Combatant::Combatant(const char* com_name, const char* fname, int team) + : name(com_name), iff(team), score(0), force(0) +{ + for (int i = 0; i < 6; i++) + target_factor[i] = 1; + + target_factor[2] = 1000; + + if (fname) + force = CombatGroup::LoadOrderOfBattle(fname, iff, this); +} + +Combatant::Combatant(const char* com_name, CombatGroup* f) + : name(com_name), iff(0), score(0), force(f) +{ + for (int i = 0; i < 6; i++) + target_factor[i] = 1; + + target_factor[2] = 1000; + + if (force) { + SetCombatant(force, this); + iff = force->GetIFF(); + } +} + +// +--------------------------------------------------------------------+ + +Combatant::~Combatant() +{ + mission_list.clear(); + target_list.clear(); + defend_list.clear(); + delete force; +} + +// +--------------------------------------------------------------------+ + +CombatGroup* +Combatant::FindGroup(int type, int id) +{ + if (force) + return force->FindGroup(type, id); + + return 0; +} + +// +--------------------------------------------------------------------+ + +void +Combatant::AddMission(Mission* mission) +{ + mission_list.append(mission); +} + +// +--------------------------------------------------------------------+ + +double +Combatant::GetTargetStratFactor(int type) +{ + switch (type) { + case CombatGroup::FLEET: + case CombatGroup::CARRIER_GROUP: + case CombatGroup::BATTLE_GROUP: + case CombatGroup::DESTROYER_SQUADRON: return target_factor[0]; + + case CombatGroup::WING: + case CombatGroup::ATTACK_SQUADRON: + case CombatGroup::INTERCEPT_SQUADRON: + case CombatGroup::FIGHTER_SQUADRON: return target_factor[1]; + + case CombatGroup::BATTERY: + case CombatGroup::MISSILE: return target_factor[2]; + + case CombatGroup::BATTALION: + case CombatGroup::STARBASE: + case CombatGroup::C3I: + case CombatGroup::COMM_RELAY: + case CombatGroup::EARLY_WARNING: + case CombatGroup::FWD_CONTROL_CTR: + case CombatGroup::ECM: return target_factor[3]; + + case CombatGroup::SUPPORT: + case CombatGroup::COURIER: + case CombatGroup::MEDICAL: + case CombatGroup::SUPPLY: + case CombatGroup::REPAIR: return target_factor[4]; + } + + return target_factor[5]; +} + +// +--------------------------------------------------------------------+ + +void +Combatant::SetTargetStratFactor(int type, double factor) +{ + switch (type) { + case CombatGroup::FLEET: + case CombatGroup::CARRIER_GROUP: + case CombatGroup::BATTLE_GROUP: + case CombatGroup::DESTROYER_SQUADRON: target_factor[0] = factor; + break; + + case CombatGroup::WING: + case CombatGroup::ATTACK_SQUADRON: + case CombatGroup::INTERCEPT_SQUADRON: + case CombatGroup::FIGHTER_SQUADRON: target_factor[1] = factor; + break; + + case CombatGroup::BATTALION: + case CombatGroup::STARBASE: + case CombatGroup::BATTERY: + case CombatGroup::MISSILE: target_factor[2] = factor; + break; + + case CombatGroup::C3I: + case CombatGroup::COMM_RELAY: + case CombatGroup::EARLY_WARNING: + case CombatGroup::FWD_CONTROL_CTR: + case CombatGroup::ECM: target_factor[3] = factor; + break; + + case CombatGroup::SUPPORT: + case CombatGroup::COURIER: + case CombatGroup::MEDICAL: + case CombatGroup::SUPPLY: + case CombatGroup::REPAIR: target_factor[4] = factor; + break; + + default: target_factor[5] = factor; + break; + } +} + + diff --git a/Stars45/Combatant.h b/Stars45/Combatant.h new file mode 100644 index 0000000..c449bea --- /dev/null +++ b/Stars45/Combatant.h @@ -0,0 +1,73 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Combatant.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + One side in a military conflict +*/ + +#ifndef Combatant_h +#define Combatant_h + +#include "Types.h" +#include "Geometry.h" +#include "Text.h" +#include "List.h" + +// +--------------------------------------------------------------------+ + +class CombatGroup; +class Mission; + +// +--------------------------------------------------------------------+ + +class Combatant +{ +public: + static const char* TYPENAME() { return "Combatant"; } + + Combatant(const char* com_name, const char* file_name, int iff); + Combatant(const char* com_name, CombatGroup* force); + ~Combatant(); + + // operations: + + // accessors: + const char* Name() const { return name; } + int GetIFF() const { return iff; } + int Score() const { return score; } + const char* GetDescription() const { return name; } + CombatGroup* GetForce() { return force; } + CombatGroup* FindGroup(int type, int id=-1); + List& GetTargetList() { return target_list; } + List& GetDefendList() { return defend_list; } + List& GetMissionList() { return mission_list; } + + void AddMission(Mission* m); + void SetScore(int points) { score = points; } + void AddScore(int points) { score += points; } + + double GetTargetStratFactor(int type); + void SetTargetStratFactor(int type, double f); + +private: + Text name; + int iff; + int score; + + CombatGroup* force; + List target_list; + List defend_list; + List mission_list; + + double target_factor[8]; +}; + + +#endif Combatant_h diff --git a/Stars45/Component.cpp b/Stars45/Component.cpp new file mode 100644 index 0000000..c9ad2cb --- /dev/null +++ b/Stars45/Component.cpp @@ -0,0 +1,177 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Component.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Generic ship system sub-component class +*/ + +#include "MemDebug.h" +#include "Component.h" +#include "System.h" +#include "Game.h" + +// +----------------------------------------------------------------------+ + +ComponentDesign::ComponentDesign() + : repair_time(0.0f), replace_time(0.0f), spares(0), affects(0) +{ } + +// +----------------------------------------------------------------------+ + +ComponentDesign::~ComponentDesign() +{ } + +// +----------------------------------------------------------------------+ + +Component::Component(ComponentDesign* d, System* s) + : design(d), system(s), + status(NOMINAL), availability(100.0f), time_remaining(0.0f), + spares(0), jerried(0) +{ + if (design) + spares = design->spares; +} + +// +----------------------------------------------------------------------+ + +Component::Component(const Component& c) + : design(c.design), system(c.system), + status(c.status), availability(c.availability), time_remaining(c.time_remaining), + spares(c.spares), jerried(c.jerried) +{ +} + +// +--------------------------------------------------------------------+ + +Component::~Component() +{ } + +// +--------------------------------------------------------------------+ + +void +Component::ExecMaintFrame(double seconds) +{ + if (status > NOMINAL) { + time_remaining -= (float) seconds; + + // when repairs are complete: + if (time_remaining <= 0) { + if (status == REPAIR) { + // did we just jerry-rig a failed component? + if (availability < 50) + jerried++; + + if (jerried < 5) + availability += 50.0f - 10 * jerried; + if (availability > 100) availability = 100.0f; + } + else { + availability = 100.0f; + } + + if (availability > 99) + status = NOMINAL; + else if (availability > 49) + status = DEGRADED; + else + status = CRITICAL; + + time_remaining = 0.0f; + + if (system) + system->CalcStatus(); + } + } +} + +// +--------------------------------------------------------------------+ + +void +Component::ApplyDamage(double damage) +{ + availability -= (float) damage; + if (availability < 1) availability = 0.0f; + + if (status < REPLACE) { + if (availability > 99) + status = NOMINAL; + else if (availability > 49) + status = DEGRADED; + else + status = CRITICAL; + } + + if (system) + system->CalcStatus(); +} + +// +--------------------------------------------------------------------+ + +void +Component::Repair() +{ + if (status < NOMINAL) { + status = REPAIR; + time_remaining = design->repair_time; + + if (system) + system->CalcStatus(); + } +} + +// +--------------------------------------------------------------------+ + +void +Component::Replace() +{ + if (status <= NOMINAL) { + status = REPLACE; + spares--; + time_remaining = design->replace_time; + + if (system) + system->CalcStatus(); + } +} + +// +--------------------------------------------------------------------+ + +float +Component::Availability() const +{ + if (status > NOMINAL && availability > 50) + return 50.0f; + + return availability; +} + +float +Component::TimeRemaining() const +{ + return (float) time_remaining; +} + +int +Component::SpareCount() const +{ + return spares; +} + +bool +Component::IsJerried() const +{ + return jerried?true:false; +} + +int +Component::NumJerried() const +{ + return jerried; +} \ No newline at end of file diff --git a/Stars45/Component.h b/Stars45/Component.h new file mode 100644 index 0000000..c5bbdc2 --- /dev/null +++ b/Stars45/Component.h @@ -0,0 +1,100 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Component.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Generic ship system sub-component class +*/ + +#ifndef Component_h +#define Component_h + +#include "Types.h" +#include "Geometry.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class ComponentDesign +{ +public: + static const char* TYPENAME() { return "ComponentDesign"; } + + ComponentDesign(); + ~ComponentDesign(); + int operator == (const ComponentDesign& rhs) const { return (name == rhs.name); } + + // identification: + Text name; + Text abrv; + + float repair_time; + float replace_time; + int spares; + DWORD affects; +}; + +// +--------------------------------------------------------------------+ + +class System; + +// +--------------------------------------------------------------------+ + +class Component +{ +public: + static const char* TYPENAME() { return "Component"; } + + enum STATUS { DESTROYED, CRITICAL, DEGRADED, NOMINAL, REPLACE, REPAIR }; + enum DAMAGE { DAMAGE_EFFICIENCY = 0x01, + DAMAGE_SAFETY = 0x02, + DAMAGE_STABILITY = 0x04 }; + + Component(ComponentDesign* d, System* s); + Component(const Component& c); + virtual ~Component(); + + const char* Name() const { return design->name; } + const char* Abbreviation() const { return design->abrv; } + float RepairTime() const { return design->repair_time; } + float ReplaceTime() const { return design->replace_time; } + + bool DamageEfficiency() const { return (design->affects & DAMAGE_EFFICIENCY)?true:false; } + bool DamageSafety() const { return (design->affects & DAMAGE_SAFETY)?true:false; } + bool DamageStability() const { return (design->affects & DAMAGE_STABILITY)?true:false; } + + STATUS Status() const { return status; } + float Availability() const; + float TimeRemaining() const; + int SpareCount() const; + bool IsJerried() const; + int NumJerried() const; + + void SetSystem(System* s) { system = s; } + System* GetSystem() const { return system; } + + virtual void ApplyDamage(double damage); + virtual void ExecMaintFrame(double seconds); + virtual void Repair(); + virtual void Replace(); + +protected: + ComponentDesign* design; + + // Component health status: + STATUS status; + float availability; + float time_remaining; + int spares; + int jerried; + System* system; +}; + +#endif Component_h + diff --git a/Stars45/Computer.cpp b/Stars45/Computer.cpp new file mode 100644 index 0000000..9661ee3 --- /dev/null +++ b/Stars45/Computer.cpp @@ -0,0 +1,93 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Computer.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Computer System class +*/ + +#include "MemDebug.h" +#include "Computer.h" +#include "Game.h" + +// +----------------------------------------------------------------------+ + +static int computer_value[] = { + 0, 1, 1, 1, 1 +}; + +// +----------------------------------------------------------------------+ + +Computer::Computer(int comp_type, const char* comp_name) + : System(COMPUTER, comp_type, comp_name, 1, 1, 1, 1) +{ + SetAbbreviation(Game::GetText("sys.computer.abrv")); + power_flags = POWER_WATTS | POWER_CRITICAL; + + if (subtype == FLIGHT) { + crit_level = -1.0f; + } +} + +// +----------------------------------------------------------------------+ + +Computer::Computer(const Computer& c) + : System(c) +{ + Mount(c); + SetAbbreviation(c.Abbreviation()); + power_flags = POWER_WATTS | POWER_CRITICAL; + + if (subtype == FLIGHT) { + crit_level = -1.0f; + } +} + +// +--------------------------------------------------------------------+ + +Computer::~Computer() +{ } + +// +--------------------------------------------------------------------+ + +void +Computer::ApplyDamage(double damage) +{ + System::ApplyDamage(damage); +} + +// +--------------------------------------------------------------------+ + +void +Computer::ExecFrame(double seconds) +{ + energy = 0.0f; + System::ExecFrame(seconds); +} + +// +--------------------------------------------------------------------+ + +void +Computer::Distribute(double delivered_energy, double seconds) +{ + if (IsPowerOn()) { + // convert Joules to Watts: + energy = (float) (delivered_energy/seconds); + + // brown out: + if (energy < capacity*0.75f) + power_on = false; + + // spike: + else if (energy > capacity*1.5f) { + power_on = false; + ApplyDamage(50); + } + } +} diff --git a/Stars45/Computer.h b/Stars45/Computer.h new file mode 100644 index 0000000..18452ec --- /dev/null +++ b/Stars45/Computer.h @@ -0,0 +1,41 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Computer.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Various computer systems class +*/ + +#ifndef Computer_h +#define Computer_h + +#include "Types.h" +#include "System.h" +#include "Geometry.h" + +// +--------------------------------------------------------------------+ + +class Computer : public System +{ +public: + enum CompType { AVIONICS=1, FLIGHT, TACTICAL }; + + Computer(int comp_type, const char* comp_name); + Computer(const Computer& rhs); + virtual ~Computer(); + + virtual void ApplyDamage(double damage); + virtual void ExecFrame(double seconds); + virtual void Distribute(double delivered_energy, double seconds); + +protected: +}; + +#endif Computer_h + diff --git a/Stars45/ConfirmDlg.cpp b/Stars45/ConfirmDlg.cpp new file mode 100644 index 0000000..858faee --- /dev/null +++ b/Stars45/ConfirmDlg.cpp @@ -0,0 +1,150 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: ConfirmDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + General-purpose confirmation dialog class +*/ + +#include "MemDebug.h" +#include "ConfirmDlg.h" +#include "MenuScreen.h" +#include "Starshatter.h" +#include "FormatUtil.h" + +#include "Game.h" +#include "Keyboard.h" +#include "Button.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +DEF_MAP_CLIENT(ConfirmDlg, OnApply); +DEF_MAP_CLIENT(ConfirmDlg, OnCancel); + +// +--------------------------------------------------------------------+ + +ConfirmDlg::ConfirmDlg(Screen* s, FormDef& def, MenuScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), manager(mgr), + parent_control(0), btn_apply(0), btn_cancel(0) +{ + Init(def); +} + +ConfirmDlg::~ConfirmDlg() +{ +} + +void +ConfirmDlg::RegisterControls() +{ + if (btn_apply) + return; + + btn_apply = (Button*) FindControl(1); + REGISTER_CLIENT(EID_CLICK, btn_apply, ConfirmDlg, OnApply); + + btn_cancel = (Button*) FindControl(2); + REGISTER_CLIENT(EID_CLICK, btn_cancel, ConfirmDlg, OnCancel); + + lbl_title = FindControl(100); + lbl_message = FindControl(101); +} + +// +--------------------------------------------------------------------+ + +ActiveWindow* +ConfirmDlg::GetParentControl() +{ + return parent_control; +} + +void +ConfirmDlg::SetParentControl(ActiveWindow* p) +{ + parent_control = p; +} + +Text +ConfirmDlg::GetTitle() +{ + if (lbl_title) + return lbl_title->GetText(); + + return ""; +} + +void +ConfirmDlg::SetTitle(const char* t) +{ + if (lbl_title) + lbl_title->SetText(t); +} + +Text +ConfirmDlg::GetMessage() +{ + if (lbl_message) + return lbl_message->GetText(); + + return ""; +} + +void +ConfirmDlg::SetMessage(const char* m) +{ + if (lbl_message) + lbl_message->SetText(m); +} + +// +--------------------------------------------------------------------+ + +void +ConfirmDlg::ExecFrame() +{ + if (Keyboard::KeyDown(VK_RETURN)) { + OnApply(0); + } + + if (Keyboard::KeyDown(VK_ESCAPE)) { + OnCancel(0); + } +} + +// +--------------------------------------------------------------------+ + +void +ConfirmDlg::Show() +{ + if (!IsShown()) { + Button::PlaySound(Button::SND_CONFIRM); + } + + FormWindow::Show(); + SetFocus(); +} + +// +--------------------------------------------------------------------+ + +void +ConfirmDlg::OnApply(AWEvent* event) +{ + manager->HideConfirmDlg(); + + if (parent_control) + parent_control->ClientEvent(EID_USER_1); +} + +void +ConfirmDlg::OnCancel(AWEvent* event) +{ + manager->HideConfirmDlg(); +} + +// +--------------------------------------------------------------------+ diff --git a/Stars45/ConfirmDlg.h b/Stars45/ConfirmDlg.h new file mode 100644 index 0000000..0991518 --- /dev/null +++ b/Stars45/ConfirmDlg.h @@ -0,0 +1,64 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: ConfirmDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + General-purpose confirmation dialog class +*/ + +#ifndef ConfirmDlg_h +#define ConfirmDlg_h + +#include "Types.h" +#include "FormWindow.h" + +// +--------------------------------------------------------------------+ + +class MenuScreen; + +// +--------------------------------------------------------------------+ + +class ConfirmDlg : public FormWindow +{ +public: + ConfirmDlg(Screen* s, FormDef& def, MenuScreen* mgr); + virtual ~ConfirmDlg(); + + virtual void RegisterControls(); + virtual void Show(); + + ActiveWindow* GetParentControl(); + void SetParentControl(ActiveWindow* p); + + Text GetTitle(); + void SetTitle(const char* t); + + Text GetMessage(); + void SetMessage(const char* m); + + // Operations: + virtual void ExecFrame(); + + virtual void OnApply(AWEvent* event); + virtual void OnCancel(AWEvent* event); + +protected: + MenuScreen* manager; + + ActiveWindow* lbl_title; + ActiveWindow* lbl_message; + + Button* btn_apply; + Button* btn_cancel; + + ActiveWindow* parent_control; +}; + +#endif ConfirmDlg_h + diff --git a/Stars45/Contact.cpp b/Stars45/Contact.cpp new file mode 100644 index 0000000..f77c1e3 --- /dev/null +++ b/Stars45/Contact.cpp @@ -0,0 +1,365 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Contact.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Integrated (Passive and Active) Sensor Package class implementation +*/ + +#include "MemDebug.h" +#include "Contact.h" +#include "Drone.h" +#include "Sensor.h" +#include "Ship.h" +#include "Sim.h" +#include "WeaponDesign.h" + +#include "Game.h" + +// +----------------------------------------------------------------------+ + +const int DEFAULT_TRACK_UPDATE = 500; // milliseconds +const int DEFAULT_TRACK_LENGTH = 20; // 10 seconds +const double DEFAULT_TRACK_AGE = 10; // 10 seconds +const double SENSOR_THRESHOLD = 0.25; + +// +----------------------------------------------------------------------+ + +Contact::Contact() + : ship(0), shot(0), d_pas(0.0f), d_act(0.0f), + track(0), ntrack(0), time(0), track_time(0), probe(false) +{ + acquire_time = Game::GameTime(); +} + +Contact::Contact(Ship* s, float p, float a) + : ship(s), shot(0), d_pas(p), d_act(a), + track(0), ntrack(0), time(0), track_time(0), probe(false) +{ + acquire_time = Game::GameTime(); + Observe(ship); +} + +Contact::Contact(Shot* s, float p, float a) + : ship(0), shot(s), d_pas(p), d_act(a), + track(0), ntrack(0), time(0), track_time(0), probe(false) +{ + acquire_time = Game::GameTime(); + Observe(shot); +} + +// +----------------------------------------------------------------------+ + +Contact::~Contact() +{ + delete [] track; +} + +// +----------------------------------------------------------------------+ + +int +Contact::operator == (const Contact& c) const +{ + if (ship) + return ship == c.ship; + + else if (shot) + return shot == c.shot; + + return 0; +} + +// +----------------------------------------------------------------------+ + +bool +Contact::Update(SimObject* obj) +{ + if (obj == ship || obj == shot) { + ship = 0; + shot = 0; + d_act = 0; + d_pas = 0; + + ClearTrack(); + } + + return SimObserver::Update(obj); +} + +const char* +Contact::GetObserverName() const +{ + static char name[128]; + + if (ship) + sprintf(name, "Contact Ship='%s'", ship->Name()); + else if (shot) + sprintf(name, "Contact Shot='%s' %s", shot->Name(), shot->DesignName()); + else + sprintf(name, "Contact (unknown)"); + + return name; +} + +// +----------------------------------------------------------------------+ + +double +Contact::Age() const +{ + double age = 0; + + if (!ship && !shot) + return age; + + double seconds = (Game::GameTime() - time) / 1000.0; + + age = 1.0 - seconds/DEFAULT_TRACK_AGE; + + if (age < 0) + age = 0; + + return age; +} + +int +Contact::GetIFF(const Ship* observer) const +{ + int i = 0; + + if (ship) { + i = ship->GetIFF(); + + // if the contact is on our side or has locked us up, + // we know whose side he's on. + + // Otherwise: + if (i != observer->GetIFF() && !Threat(observer)) { + if (d_pas < 2*SENSOR_THRESHOLD && d_act < SENSOR_THRESHOLD && !Visible(observer)) + i = -1000; // indeterminate iff reading + } + } + + else if (shot && shot->Owner()) { + i = shot->Owner()->GetIFF(); + } + + return i; +} + +bool +Contact::ActLock() const +{ + return d_act >= SENSOR_THRESHOLD; +} + +bool +Contact::PasLock() const +{ + return d_pas >= SENSOR_THRESHOLD; +} + +// +----------------------------------------------------------------------+ + +void +Contact::GetBearing(const Ship* observer, double& az, double& el, double& rng) const +{ + // translate: + Point targ_pt = loc - observer->Location(); + + // rotate: + const Camera* cam = &observer->Cam(); + double tx = targ_pt * cam->vrt(); + double ty = targ_pt * cam->vup(); + double tz = targ_pt * cam->vpn(); + + // convert to spherical coords: + rng = targ_pt.length(); + az = asin(fabs(tx) / rng); + el = asin(fabs(ty) / rng); + + if (tx < 0) az = -az; + if (ty < 0) el = -el; + + // correct az/el for back hemisphere: + if (tz < 0) { + if (az < 0) az = -PI - az; + else az = PI - az; + } +} + +double +Contact::Range(const Ship* observer, double limit) const +{ + double r = Point(loc - observer->Location()).length(); + + // if passive only, return approximate range: + if (!ActLock()) { + const int chunk = 25000; + + if (!PasLock()) { + r = (int) limit; + } + + else if (r <= chunk) { + r = chunk; + } + + else { + int r1 = (int) (r + chunk/2) / chunk; + r = r1 * chunk; + } + } + + return r; +} + +// +----------------------------------------------------------------------+ + +bool +Contact::InFront(const Ship* observer) const +{ + // translate: + Point targ_pt = loc - observer->Location(); + + // rotate: + const Camera* cam = &observer->Cam(); + double tz = targ_pt * cam->vpn(); + + if (tz > 1.0) + return true; + + return false; +} + +bool +Contact::Threat(const Ship* observer) const +{ + bool threat = false; + + if (observer && observer->Life() != 0) { + if (ship && ship->Life() != 0) { + threat = (ship->GetIFF() && + ship->GetIFF() != observer->GetIFF() && + ship->GetEMCON() > 2 && + ship->IsTracking((Ship*) observer) && + ship->Weapons().size() > 0); + + if (threat && observer->GetIFF() == 0) + threat = ship->GetIFF() > 1; + } + + else if (shot) { + threat = shot->IsTracking((Ship*) observer); + + if (!threat && shot->Design()->probe && shot->GetIFF() != observer->GetIFF()) { + Point probe_pt = shot->Location() - observer->Location(); + double prng = probe_pt.length(); + + threat = (prng < shot->Design()->lethal_radius); + } + } + } + + return threat; +} + +bool +Contact::Visible(const Ship* observer) const +{ + // translate: + Point targ_pt = loc - observer->Location(); + double radius = 0; + + if (ship) + radius = ship->Radius(); + + else if (shot) + radius = shot->Radius(); + + // rotate: + const Camera* cam = &observer->Cam(); + double rng = targ_pt.length(); + + return radius/rng > 0.002; +} + +// +----------------------------------------------------------------------+ + +void +Contact::Reset() +{ + if (Game::Paused()) return; + + float step_down = (float) (Game::FrameTime() / 10); + + if (d_pas > 0) + d_pas -= step_down; + + if (d_act > 0) + d_act -= step_down; +} + +void +Contact::Merge(Contact* c) +{ + if (c->GetShip() == ship && c->GetShot() == shot) { + if (c->d_pas > d_pas) + d_pas = c->d_pas; + + if (c->d_act > d_act) + d_act = c->d_act; + } +} + +void +Contact::ClearTrack() +{ + delete [] track; + track = 0; + ntrack = 0; +} + +void +Contact::UpdateTrack() +{ + time = Game::GameTime(); + + if (shot || (ship && ship->IsGroundUnit())) + return; + + if (!track) { + track = new(__FILE__,__LINE__) Point[DEFAULT_TRACK_LENGTH]; + track[0] = loc; + ntrack = 1; + track_time = time; + } + + else if (time - track_time > DEFAULT_TRACK_UPDATE) { + if (loc != track[0]) { + for (int i = DEFAULT_TRACK_LENGTH-2; i >= 0; i--) + track[i+1] = track[i]; + + track[0] = loc; + if (ntrack < DEFAULT_TRACK_LENGTH) ntrack++; + } + + track_time = time; + } +} + +// +----------------------------------------------------------------------+ + +Point +Contact::TrackPoint(int i) const +{ + if (track && i < ntrack) + return track[i]; + + return Point(); +} diff --git a/Stars45/Contact.h b/Stars45/Contact.h new file mode 100644 index 0000000..04e7d0b --- /dev/null +++ b/Stars45/Contact.h @@ -0,0 +1,91 @@ +/* Project STARSHATTER + John DiCamillo + Copyright © 1997-2002. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Contact.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Sensor Contact class +*/ + +#ifndef Contact_h +#define Contact_h + +#include "Types.h" +#include "SimObject.h" +#include "System.h" +#include "Geometry.h" + +// +--------------------------------------------------------------------+ + +class Ship; +class Shot; + +class Contact : public SimObserver +{ + friend class Sensor; + +public: + static const char* TYPENAME() { return "Contact"; } + + Contact(); + Contact(Ship* s, float p, float a); + Contact(Shot* s, float p, float a); + virtual ~Contact(); + + int operator == (const Contact& c) const; + + Ship* GetShip() const { return ship; } + Shot* GetShot() const { return shot; } + Point Location() const { return loc; } + + double PasReturn() const { return d_pas; } + double ActReturn() const { return d_act; } + bool PasLock() const; + bool ActLock() const; + double Age() const; + bool IsProbed() const { return probe; } + + DWORD AcquisitionTime() const { return acquire_time; } + + int GetIFF(const Ship* observer) const; + void GetBearing(const Ship* observer, double& az, double& el, double& r) const; + double Range(const Ship* observer, + double limit=75e3) const; + + bool InFront(const Ship* observer) const; + bool Threat(const Ship* observer) const; + bool Visible(const Ship* observer) const; + + void Reset(); + void Merge(Contact* c); + void ClearTrack(); + void UpdateTrack(); + int TrackLength() const { return ntrack; } + Point TrackPoint(int i) const; + + virtual bool Update(SimObject* obj); + virtual const char* GetObserverName() const; + +private: + Ship* ship; + Shot* shot; + Point loc; + DWORD acquire_time; + DWORD time; + + Point* track; + int ntrack; + DWORD track_time; + + float d_pas; // power output + float d_act; // mass, size + bool probe; // scanned by probe +}; + +#endif Contact_h + diff --git a/Stars45/CtlDlg.cpp b/Stars45/CtlDlg.cpp new file mode 100644 index 0000000..230cc29 --- /dev/null +++ b/Stars45/CtlDlg.cpp @@ -0,0 +1,461 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CtlDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Control Options (keyboard/joystick) Dialog Active Window class +*/ + +#include "MemDebug.h" +#include "CtlDlg.h" +#include "KeyDlg.h" +#include "MenuScreen.h" +#include "Starshatter.h" +#include "Ship.h" + +#include "DataLoader.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Slider.h" +#include "Video.h" +#include "Keyboard.h" +#include "Joystick.h" +#include "MachineInfo.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +DEF_MAP_CLIENT(CtlDlg, OnCommand); +DEF_MAP_CLIENT(CtlDlg, OnCategory); +DEF_MAP_CLIENT(CtlDlg, OnControlModel); +DEF_MAP_CLIENT(CtlDlg, OnJoySelect); +DEF_MAP_CLIENT(CtlDlg, OnJoyThrottle); +DEF_MAP_CLIENT(CtlDlg, OnJoyRudder); +DEF_MAP_CLIENT(CtlDlg, OnJoySensitivity); +DEF_MAP_CLIENT(CtlDlg, OnJoyAxis); +DEF_MAP_CLIENT(CtlDlg, OnMouseSelect); +DEF_MAP_CLIENT(CtlDlg, OnMouseSensitivity); +DEF_MAP_CLIENT(CtlDlg, OnMouseInvert); +DEF_MAP_CLIENT(CtlDlg, OnApply); +DEF_MAP_CLIENT(CtlDlg, OnCancel); +DEF_MAP_CLIENT(CtlDlg, OnAudio); +DEF_MAP_CLIENT(CtlDlg, OnVideo); +DEF_MAP_CLIENT(CtlDlg, OnOptions); +DEF_MAP_CLIENT(CtlDlg, OnControls); +DEF_MAP_CLIENT(CtlDlg, OnMod); + +// +--------------------------------------------------------------------+ + +CtlDlg::CtlDlg(Screen* s, FormDef& def, BaseScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), manager(mgr), + selected_category(0), command_index(0), control_model(0), + joy_select(0), joy_throttle(0), joy_rudder(0), joy_sensitivity(0), + mouse_select(0), mouse_sensitivity(0), mouse_invert(0), + apply(0), cancel(0), vid_btn(0), aud_btn(0), ctl_btn(0), opt_btn(0), mod_btn(0), + closed(true) +{ + Init(def); +} + +CtlDlg::~CtlDlg() +{ +} + +// +--------------------------------------------------------------------+ + +void +CtlDlg::RegisterControls() +{ + if (apply) + return; + + for (int i = 0; i < 4; i++) { + category[i] = (Button*) FindControl(101 + i); + REGISTER_CLIENT(EID_CLICK, category[i], CtlDlg, OnCategory); + } + + commands = (ListBox*) FindControl(200); + REGISTER_CLIENT(EID_SELECT, commands, CtlDlg, OnCommand); + + control_model_combo = (ComboBox*) FindControl(210); + REGISTER_CLIENT(EID_SELECT, control_model_combo, CtlDlg, OnControlModel); + + joy_select_combo = (ComboBox*) FindControl(211); + REGISTER_CLIENT(EID_SELECT, joy_select_combo, CtlDlg, OnJoySelect); + + joy_throttle_combo = (ComboBox*) FindControl(212); + REGISTER_CLIENT(EID_SELECT, joy_throttle_combo, CtlDlg, OnJoyThrottle); + + joy_rudder_combo = (ComboBox*) FindControl(213); + REGISTER_CLIENT(EID_SELECT, joy_rudder_combo, CtlDlg, OnJoyRudder); + + joy_sensitivity_slider = (Slider*) FindControl(214); + + if (joy_sensitivity_slider) { + joy_sensitivity_slider->SetRangeMin(0); + joy_sensitivity_slider->SetRangeMax(10); + joy_sensitivity_slider->SetStepSize(1); + REGISTER_CLIENT(EID_CLICK, joy_sensitivity_slider, CtlDlg, OnJoySensitivity); + } + + joy_axis_button = (Button*) FindControl(215); + REGISTER_CLIENT(EID_CLICK, joy_axis_button, CtlDlg, OnJoyAxis); + + mouse_select_combo = (ComboBox*) FindControl(511); + REGISTER_CLIENT(EID_SELECT, mouse_select_combo, CtlDlg, OnMouseSelect); + + mouse_sensitivity_slider = (Slider*) FindControl(514); + if (mouse_sensitivity_slider) { + mouse_sensitivity_slider->SetRangeMin(0); + mouse_sensitivity_slider->SetRangeMax(50); + mouse_sensitivity_slider->SetStepSize(1); + REGISTER_CLIENT(EID_CLICK, mouse_sensitivity_slider, CtlDlg, OnMouseSensitivity); + } + + mouse_invert_checkbox = (Button*) FindControl(515); + REGISTER_CLIENT(EID_CLICK, mouse_invert_checkbox, CtlDlg, OnMouseInvert); + + apply = (Button*) FindControl(1); + REGISTER_CLIENT(EID_CLICK, apply, CtlDlg, OnApply); + + cancel = (Button*) FindControl(2); + REGISTER_CLIENT(EID_CLICK, cancel, CtlDlg, OnCancel); + + vid_btn = (Button*) FindControl(901); + REGISTER_CLIENT(EID_CLICK, vid_btn, CtlDlg, OnVideo); + + aud_btn = (Button*) FindControl(902); + REGISTER_CLIENT(EID_CLICK, aud_btn, CtlDlg, OnAudio); + + ctl_btn = (Button*) FindControl(903); + REGISTER_CLIENT(EID_CLICK, ctl_btn, CtlDlg, OnControls); + + opt_btn = (Button*) FindControl(904); + REGISTER_CLIENT(EID_CLICK, opt_btn, CtlDlg, OnOptions); + + mod_btn = (Button*) FindControl(905); + if (mod_btn) { + REGISTER_CLIENT(EID_CLICK, mod_btn, CtlDlg, OnMod); + } +} + +// +--------------------------------------------------------------------+ + +void +CtlDlg::Show() +{ + if (!IsShown()) + FormWindow::Show(); + + if (closed) + ShowCategory(); + else + UpdateCategory(); + + if (vid_btn) vid_btn->SetButtonState(0); + if (aud_btn) aud_btn->SetButtonState(0); + if (ctl_btn) ctl_btn->SetButtonState(1); + if (opt_btn) opt_btn->SetButtonState(0); + if (mod_btn) mod_btn->SetButtonState(0); + + closed = false; +} + +// +--------------------------------------------------------------------+ + +void +CtlDlg::ExecFrame() +{ + if (Keyboard::KeyDown(VK_RETURN)) { + OnApply(0); + } +} + +// +--------------------------------------------------------------------+ + +void +CtlDlg::ShowCategory() +{ + if (!commands || !category[0]) + return; + + for (int i = 0; i < 4; i++) { + if (i == selected_category) + category[i]->SetButtonState(1); + else + category[i]->SetButtonState(0); + } + + commands->ClearItems(); + commands->SetSelectedStyle(ListBox::LIST_ITEM_STYLE_FILLED_BOX); + commands->SetLeading(2); + + Starshatter* stars = Starshatter::GetInstance(); + + if (stars) { + KeyMap& keymap = stars->GetKeyMap(); + int n = 0; + + for (int i = 0; i < 256; i++) { + KeyMapEntry* k = keymap.GetKeyMap(i); + + switch (k->act) { + case 0: + break; + + case KEY_CONTROL_MODEL: + control_model = k->key; + control_model_combo->SetSelection(control_model); + break; + + case KEY_JOY_SELECT: + joy_select = k->key; + joy_select_combo->SetSelection(joy_select); + break; + + case KEY_JOY_RUDDER: + joy_rudder = k->key; + joy_rudder_combo->SetSelection(joy_rudder); + break; + + case KEY_JOY_THROTTLE: + joy_throttle = k->key; + joy_throttle_combo->SetSelection(joy_throttle); + break; + + case KEY_JOY_SENSE: + joy_sensitivity = k->key; + joy_sensitivity_slider->SetValue(joy_sensitivity); + break; + + case KEY_JOY_SWAP: + break; + + case KEY_JOY_DEAD_ZONE: + break; + + case KEY_MOUSE_SELECT: + mouse_select = k->key; + mouse_select_combo->SetSelection(mouse_select); + break; + + case KEY_MOUSE_SENSE: + mouse_sensitivity = k->key; + mouse_sensitivity_slider->SetValue(mouse_sensitivity); + break; + + case KEY_MOUSE_INVERT: + mouse_invert = k->key; + mouse_invert_checkbox->SetButtonState(mouse_invert > 0); + break; + + case KEY_AXIS_YAW: + case KEY_AXIS_PITCH: + case KEY_AXIS_ROLL: + case KEY_AXIS_THROTTLE: + + case KEY_AXIS_YAW_INVERT: + case KEY_AXIS_PITCH_INVERT: + case KEY_AXIS_ROLL_INVERT: + case KEY_AXIS_THROTTLE_INVERT: + break; + + default: + if (keymap.GetCategory(i) == selected_category) { + commands->AddItemWithData(keymap.DescribeAction(i), (DWORD) i); + commands->SetItemText(n++, 1, keymap.DescribeKey(i)); + } + break; + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +CtlDlg::UpdateCategory() +{ + if (!commands) + return; + + Starshatter* stars = Starshatter::GetInstance(); + KeyMap& keymap = stars->GetKeyMap(); + + for (int i = 0; i < commands->NumItems(); i++) { + int key = commands->GetItemData(i); + commands->SetItemText(i, 1, keymap.DescribeKey(key)); + } +} + +// +--------------------------------------------------------------------+ + +void +CtlDlg::OnCategory(AWEvent* event) +{ + selected_category = 0; + + for (int i = 0; i < 4; i++) + if (event->window == category[i]) + selected_category = i; + + ShowCategory(); +} + +// +--------------------------------------------------------------------+ + +static DWORD command_click_time = 0; + +void +CtlDlg::OnCommand(AWEvent* event) +{ + int list_index = commands->GetListIndex(); + + // double-click: + if (list_index == command_index && Game::RealTime() - command_click_time < 350) { + KeyDlg* key_dlg = 0; + + if (manager) + key_dlg = manager->GetKeyDlg(); + + if (key_dlg) { + key_dlg->SetKeyMapIndex(commands->GetItemData(list_index)); + } + + if (manager) + manager->ShowKeyDlg(); + } + + command_click_time = Game::RealTime(); + command_index = list_index; +} + +// +--------------------------------------------------------------------+ + +void +CtlDlg::OnControlModel(AWEvent* event) +{ + control_model = control_model_combo->GetSelectedIndex(); +} + +// +--------------------------------------------------------------------+ + +void +CtlDlg::OnJoySelect(AWEvent* event) +{ + joy_select = joy_select_combo->GetSelectedIndex(); +} + +void +CtlDlg::OnJoyThrottle(AWEvent* event) +{ + joy_throttle = joy_throttle_combo->GetSelectedIndex(); +} + +void +CtlDlg::OnJoyRudder(AWEvent* event) +{ + joy_rudder = joy_rudder_combo->GetSelectedIndex(); +} + +void +CtlDlg::OnJoySensitivity(AWEvent* event) +{ + joy_sensitivity = joy_sensitivity_slider->GetValue(); +} + +void +CtlDlg::OnJoyAxis(AWEvent* event) +{ + if (manager) + manager->ShowJoyDlg(); +} + +// +--------------------------------------------------------------------+ + +void +CtlDlg::OnMouseSelect(AWEvent* event) +{ + mouse_select = mouse_select_combo->GetSelectedIndex(); +} + +void +CtlDlg::OnMouseSensitivity(AWEvent* event) +{ + mouse_sensitivity = mouse_sensitivity_slider->GetValue(); +} + +void +CtlDlg::OnMouseInvert(AWEvent* event) +{ + mouse_invert = mouse_invert_checkbox->GetButtonState() > 0; +} + +// +--------------------------------------------------------------------+ + +void CtlDlg::OnAudio(AWEvent* event) { manager->ShowAudDlg(); } +void CtlDlg::OnVideo(AWEvent* event) { manager->ShowVidDlg(); } +void CtlDlg::OnOptions(AWEvent* event) { manager->ShowOptDlg(); } +void CtlDlg::OnControls(AWEvent* event) { manager->ShowCtlDlg(); } +void CtlDlg::OnMod(AWEvent* event) { manager->ShowModDlg(); } + +// +--------------------------------------------------------------------+ + +void +CtlDlg::OnApply(AWEvent* event) +{ + if (manager) + manager->ApplyOptions(); +} + +void +CtlDlg::OnCancel(AWEvent* event) +{ + if (manager) + manager->CancelOptions(); +} + +// +--------------------------------------------------------------------+ + +void +CtlDlg::Apply() +{ + if (closed) return; + + Starshatter* stars = Starshatter::GetInstance(); + + if (stars) { + KeyMap& keymap = stars->GetKeyMap(); + + keymap.Bind(KEY_CONTROL_MODEL, control_model, 0); + + keymap.Bind(KEY_JOY_SELECT, joy_select, 0); + keymap.Bind(KEY_JOY_RUDDER, joy_rudder, 0); + keymap.Bind(KEY_JOY_THROTTLE, joy_throttle, 0); + keymap.Bind(KEY_JOY_SENSE, joy_sensitivity, 0); + + keymap.Bind(KEY_MOUSE_SELECT, mouse_select, 0); + keymap.Bind(KEY_MOUSE_SENSE, mouse_sensitivity, 0); + keymap.Bind(KEY_MOUSE_INVERT, mouse_invert, 0); + + keymap.SaveKeyMap("key.cfg", 256); + + stars->MapKeys(); + Ship::SetControlModel(control_model); + } + + closed = true; +} + +void +CtlDlg::Cancel() +{ + closed = true; +} diff --git a/Stars45/CtlDlg.h b/Stars45/CtlDlg.h new file mode 100644 index 0000000..4110c96 --- /dev/null +++ b/Stars45/CtlDlg.h @@ -0,0 +1,119 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: CtlDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Control Options (keyboard/joystick) Dialog Active Window class +*/ + +#ifndef CtlDlg_h +#define CtlDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "Bitmap.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Font.h" + +// +--------------------------------------------------------------------+ + +class BaseScreen; +class MenuScreen; +class GameScreen; + +// +--------------------------------------------------------------------+ + +class CtlDlg : public FormWindow +{ +public: + CtlDlg(Screen* s, FormDef& def, BaseScreen* mgr); + virtual ~CtlDlg(); + + virtual void RegisterControls(); + virtual void Show(); + virtual void ExecFrame(); + + // Operations: + virtual void OnCommand(AWEvent* event); + virtual void OnCategory(AWEvent* event); + + virtual void OnControlModel(AWEvent* event); + + virtual void OnJoySelect(AWEvent* event); + virtual void OnJoyThrottle(AWEvent* event); + virtual void OnJoyRudder(AWEvent* event); + virtual void OnJoySensitivity(AWEvent* event); + virtual void OnJoyAxis(AWEvent* event); + + virtual void OnMouseSelect(AWEvent* event); + virtual void OnMouseSensitivity(AWEvent* event); + virtual void OnMouseInvert(AWEvent* event); + + virtual void Apply(); + virtual void Cancel(); + + virtual void OnApply(AWEvent* event); + virtual void OnCancel(AWEvent* event); + + virtual void OnAudio(AWEvent* event); + virtual void OnVideo(AWEvent* event); + virtual void OnOptions(AWEvent* event); + virtual void OnControls(AWEvent* event); + virtual void OnMod(AWEvent* event); + +protected: + void ShowCategory(); + void UpdateCategory(); + + BaseScreen* manager; + + Button* category[4]; + ListBox* commands; + int command_index; + + ComboBox* control_model_combo; + + ComboBox* joy_select_combo; + ComboBox* joy_throttle_combo; + ComboBox* joy_rudder_combo; + Slider* joy_sensitivity_slider; + Button* joy_axis_button; + + ComboBox* mouse_select_combo; + Slider* mouse_sensitivity_slider; + Button* mouse_invert_checkbox; + + Button* aud_btn; + Button* vid_btn; + Button* opt_btn; + Button* ctl_btn; + Button* mod_btn; + + Button* apply; + Button* cancel; + + int selected_category; + int control_model; + + int joy_select; + int joy_throttle; + int joy_rudder; + int joy_sensitivity; + + int mouse_select; + int mouse_sensitivity; + int mouse_invert; + + bool closed; +}; + +#endif CtlDlg_h + diff --git a/Stars45/DebriefDlg.cpp b/Stars45/DebriefDlg.cpp new file mode 100644 index 0000000..ea2fab2 --- /dev/null +++ b/Stars45/DebriefDlg.cpp @@ -0,0 +1,375 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: DebriefDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Mission Debriefing Dialog Active Window class +*/ + +#include "MemDebug.h" +#include "DebriefDlg.h" +#include "PlanScreen.h" +#include "Starshatter.h" +#include "Campaign.h" +#include "Element.h" +#include "Instruction.h" +#include "Mission.h" +#include "Sim.h" +#include "SimEvent.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "StarSystem.h" +#include "FormatUtil.h" +#include "Player.h" +#include "Campaign.h" + +#include "NetLobby.h" +#include "HttpServer.h" + +#include "Game.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "Button.h" +#include "ListBox.h" +#include "Slider.h" +#include "ParseUtil.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +DEF_MAP_CLIENT(DebriefDlg, OnClose); +DEF_MAP_CLIENT(DebriefDlg, OnUnit); + +// +--------------------------------------------------------------------+ + +DebriefDlg::DebriefDlg(Screen* s, FormDef& def, PlanScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), manager(mgr), + close_btn(0), campaign(0), mission(0), + unit_index(0), info(0), sim(0), ship(0) +{ + campaign = Campaign::GetCampaign(); + + if (campaign) + mission = campaign->GetMission(); + + Init(def); +} + +DebriefDlg::~DebriefDlg() +{ +} + +// +--------------------------------------------------------------------+ + +void +DebriefDlg::RegisterControls() +{ + mission_name = FindControl(200); + mission_system = FindControl(202); + mission_sector = FindControl(204); + mission_time_start = FindControl(206); + + objectives = FindControl(210); + situation = FindControl(240); + mission_score = FindControl(211); + unit_list = (ListBox*) FindControl(320); + summary_list = (ListBox*) FindControl(330); + event_list = (ListBox*) FindControl(340); + + if (unit_list) + REGISTER_CLIENT(EID_SELECT, unit_list, DebriefDlg, OnUnit); + + close_btn = (Button*) FindControl(1); + + if (close_btn) + REGISTER_CLIENT(EID_CLICK, close_btn, DebriefDlg, OnClose); +} + +// +--------------------------------------------------------------------+ + +void +DebriefDlg::Show() +{ + FormWindow::Show(); + Game::SetTimeCompression(1); + + mission = 0; + campaign = Campaign::GetCampaign(); + sim = Sim::GetSim(); + + if (sim) + ship = sim->GetPlayerShip(); + + if (campaign) + mission = campaign->GetMission(); + + if (mission_name) { + if (mission) + mission_name->SetText(mission->Name()); + else + mission_name->SetText(Game::GetText("DebriefDlg.mission-name")); + } + + if (mission_system) { + mission_system->SetText(""); + + if (mission) { + StarSystem* sys = mission->GetStarSystem(); + + if (sys) + mission_system->SetText(sys->Name()); + } + } + + if (mission_sector) { + mission_sector->SetText(""); + + if (mission) { + MissionElement* elem = mission->GetElements()[0]; + + if (elem) + mission_sector->SetText(elem->Region()); + } + } + + if (mission_time_start) { + if (mission) { + char txt[32]; + FormatDayTime(txt, mission->Start()); + mission_time_start->SetText(txt); + } + } + + if (objectives) { + bool found_objectives = false; + + if (sim && sim->GetPlayerElement()) { + Text text; + Element* elem = sim->GetPlayerElement(); + + for (int i = 0; i < elem->NumObjectives(); i++) { + Instruction* obj = elem->GetObjective(i); + text += Text("* ") + obj->GetDescription() + Text("\n"); + + found_objectives = true; + } + + objectives->SetText(text); + } + + if (!found_objectives) { + if (mission) + objectives->SetText(mission->Objective()); + else + objectives->SetText(Game::GetText("DebriefDlg.unspecified")); + } + } + + if (situation) { + if (mission) + situation->SetText(mission->Situation()); + else + situation->SetText(Game::GetText("DebriefDlg.unknown")); + } + + if (mission_score) { + mission_score->SetText(Game::GetText("DebriefDlg.no-stats")); + + if (ship) { + for (int i = 0; i < ShipStats::NumStats(); i++) { + ShipStats* stats = ShipStats::GetStats(i); + if (stats && !strcmp(ship->Name(), stats->GetName())) { + stats->Summarize(); + + Player* player = Player::GetCurrentPlayer(); + int points = stats->GetPoints() + + stats->GetCommandPoints(); + + if (player && sim) + points = player->GetMissionPoints(stats, sim->StartTime()) + + stats->GetCommandPoints(); + + char score[32]; + sprintf(score, "%d %s", points, Game::GetText("DebriefDlg.points").data()); + mission_score->SetText(score); + break; + } + } + } + } + + DrawUnits(); +} + +// +--------------------------------------------------------------------+ + +void +DebriefDlg::DrawUnits() +{ + if (mission) { + if (unit_list) { + unit_list->ClearItems(); + + int seln = -1; + bool netgame = false; + + if (sim && sim->IsNetGame()) + netgame = true; + + for (int i = 0; i < ShipStats::NumStats(); i++) { + ShipStats* stats = ShipStats::GetStats(i); + stats->Summarize(); + + if (netgame || (stats->GetIFF() == mission->Team() && + !strcmp(stats->GetRegion(), mission->GetRegion()))) { + int n = unit_list->AddItemWithData(" ", i) - 1; + unit_list->SetItemText(n, 1, stats->GetName()); + unit_list->SetItemText(n, 2, stats->GetRole()); + unit_list->SetItemText(n, 3, stats->GetType()); + + if (ship && !strcmp(ship->Name(), stats->GetName())) + seln = n; + } + } + + if (seln >= 0) { + unit_list->SetSelected(seln); + OnUnit(0); + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +DebriefDlg::ExecFrame() +{ + if (unit_list && unit_list->NumItems() && unit_list->GetSelCount() < 1) { + unit_list->SetSelected(0); + OnUnit(0); + } + + if (Keyboard::KeyDown(VK_RETURN)) { + OnClose(0); + } +} + +// +--------------------------------------------------------------------+ + +void +DebriefDlg::OnUnit(AWEvent* event) +{ + if (!unit_list || !event_list || !summary_list) + return; + + summary_list->ClearItems(); + event_list->ClearItems(); + + int seln = unit_list->GetSelection(); + int unit = unit_list->GetItemData(seln); + + ShipStats* stats = ShipStats::GetStats(unit); + if (stats) { + stats->Summarize(); + + char txt[64]; + int i = 0; + + sprintf(txt, "%d", stats->GetGunShots()); + summary_list->AddItem("Guns Fired: "); + summary_list->SetItemText(i++, 1, txt); + + sprintf(txt, "%d", stats->GetGunHits()); + summary_list->AddItem("Gun Hits: "); + summary_list->SetItemText(i++, 1, txt); + + sprintf(txt, "%d", stats->GetGunKills()); + summary_list->AddItem("Gun Kills: "); + summary_list->SetItemText(i++, 1, txt); + + // one line spacer: + summary_list->AddItem(" "); + i++; + + sprintf(txt, "%d", stats->GetMissileShots()); + summary_list->AddItem("Missiles Fired: "); + summary_list->SetItemText(i++, 1, txt); + + sprintf(txt, "%d", stats->GetMissileHits()); + summary_list->AddItem("Missile Hits: "); + summary_list->SetItemText(i++, 1, txt); + + sprintf(txt, "%d", stats->GetMissileKills()); + summary_list->AddItem("Missile Kills: "); + summary_list->SetItemText(i++, 1, txt); + + i = 0; + ListIter iter = stats->GetEvents(); + while (++iter) { + SimEvent* event = iter.value(); + + char txt[64]; + int time = event->GetTime(); + + if (time > 24 * 60 * 60) + FormatDayTime(txt, time); + else + FormatTime(txt, time); + + event_list->AddItem(txt); + event_list->SetItemText(i, 1, event->GetEventDesc()); + + if (event->GetTarget()) + event_list->SetItemText(i, 2, event->GetTarget()); + + i++; + } + } +} + +// +--------------------------------------------------------------------+ + +void +DebriefDlg::OnClose(AWEvent* event) +{ + Sim* sim = Sim::GetSim(); + + sim->CommitMission(); + sim->UnloadMission(); + + NetLobby* lobby = NetLobby::GetInstance(); + if (lobby && lobby->IsHost()) { + lobby->SelectMission(0); + lobby->ExecFrame(); + } + + Player* player = Player::GetCurrentPlayer(); + if (player && player->ShowAward()) { + manager->ShowAwardDlg(); + } + + else { + Starshatter* stars = Starshatter::GetInstance(); + + if (stars) { + Mouse::Show(false); + + Campaign* campaign = Campaign::GetCampaign(); + if (campaign && campaign->GetCampaignId() < Campaign::SINGLE_MISSIONS) + stars->SetGameMode(Starshatter::CMPN_MODE); + else + stars->SetGameMode(Starshatter::MENU_MODE); + } + + else { + Game::Panic("DebriefDlg::OnClose() - Game instance not found"); + } + } +} diff --git a/Stars45/DebriefDlg.h b/Stars45/DebriefDlg.h new file mode 100644 index 0000000..8c0fce4 --- /dev/null +++ b/Stars45/DebriefDlg.h @@ -0,0 +1,81 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: DebriefDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Mission Debriefing Dialog Active Window class +*/ + +#ifndef DebriefDlg_h +#define DebriefDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "Bitmap.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Font.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class PlanScreen; +class Campaign; +class Mission; +class MissionInfo; +class Sim; +class Ship; + +// +--------------------------------------------------------------------+ + +class DebriefDlg : public FormWindow +{ +public: + DebriefDlg(Screen* s, FormDef& def, PlanScreen* mgr); + virtual ~DebriefDlg(); + + virtual void RegisterControls(); + virtual void ExecFrame(); + virtual void Show(); + + // Operations: + virtual void OnClose(AWEvent* event); + virtual void OnUnit(AWEvent* event); + +protected: + virtual void DrawUnits(); + + PlanScreen* manager; + Button* close_btn; + + ActiveWindow* mission_name; + ActiveWindow* mission_system; + ActiveWindow* mission_sector; + ActiveWindow* mission_time_start; + + ActiveWindow* objectives; + ActiveWindow* situation; + ActiveWindow* mission_score; + + ListBox* unit_list; + ListBox* summary_list; + ListBox* event_list; + + Campaign* campaign; + Mission* mission; + MissionInfo* info; + int unit_index; + + Sim* sim; + Ship* ship; +}; + +#endif DebriefDlg_h + diff --git a/Stars45/Debris.cpp b/Stars45/Debris.cpp new file mode 100644 index 0000000..18624b7 --- /dev/null +++ b/Stars45/Debris.cpp @@ -0,0 +1,220 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Debris.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Debris Sprite animation class +*/ + +#include "MemDebug.h" +#include "Debris.h" +#include "Shot.h" +#include "Explosion.h" +#include "Sim.h" +#include "StarSystem.h" +#include "Terrain.h" + +#include "Solid.h" +#include "Bitmap.h" +#include "DataLoader.h" +#include "Game.h" +#include "Random.h" + +// +--------------------------------------------------------------------+ + +Debris::Debris(Model* model, const Vec3& pos, const Vec3& vel, double m) + : SimObject("Debris", SimObject::SIM_DEBRIS) +{ + MoveTo(pos); + + velocity = vel; + mass = (float) m; + integrity = mass * 10.0f; + life = 300; + + Solid* solid = new(__FILE__,__LINE__) Solid; + + if (solid) { + solid->UseModel(model); + solid->MoveTo(pos); + + rep = solid; + + radius = solid->Radius(); + } + + Point torque = RandomVector(Mass()/2); + + if (Mass() < 10) + torque *= (rand() / 3200); + else if (Mass() > 10e3) + torque *= 0.25; + else if (Mass() > 10e6) + torque *= 0.005; + + ApplyTorque(torque); +} + +// +--------------------------------------------------------------------+ + +int +Debris::HitBy(Shot* shot, Point& impact) +{ + if (!shot->IsArmed()) return 0; + + const int HIT_NOTHING = 0; + const int HIT_HULL = 1; + + Point hull_impact; + int hit_type = HIT_NOTHING; + bool hit_hull = true; + Point shot_loc = shot->Location(); + Point delta = shot_loc - Location(); + double dlen = delta.length(); + double dscale = 1; + float scale = 1.0f; + Sim* sim = Sim::GetSim(); + + // MISSILE PROCESSING ------------------------------------------------ + + if (shot->IsMissile()) { + if (dlen < Radius()) { + hull_impact = impact = shot_loc; + sim->CreateExplosion(impact, Velocity(), Explosion::HULL_FLASH, 0.3f * scale, scale, region, this); + sim->CreateExplosion(impact, Point(), Explosion::SHOT_BLAST, 2.0f, scale, region); + hit_type = HIT_HULL; + } + } + + // ENERGY WEP PROCESSING --------------------------------------------- + + else { + + Solid* solid = (Solid*) rep; + + Point shot_loc = shot->Location(); + Point shot_vpn = shot_loc - shot->Origin(); + double shot_len = shot_vpn.Normalize(); + if (shot_len == 0) shot_len = 1000; + + // impact: + if (solid) { + if (solid->CheckRayIntersection(shot->Origin(), shot_vpn, shot_len, impact)) { + // trim beam shots to impact point: + if (shot->IsBeam()) + shot->SetBeamPoints(shot->Origin(), impact); + + hull_impact = impact; + + if (shot->IsBeam()) + sim->CreateExplosion(impact, Velocity(), Explosion::BEAM_FLASH, 0.30f * scale, scale, region, this); + else + sim->CreateExplosion(impact, Velocity(), Explosion::HULL_FLASH, 0.30f * scale, scale, region, this); + + Point burst_vel = hull_impact - Location(); + burst_vel.Normalize(); + burst_vel *= Radius() * 0.5; + burst_vel += Velocity(); + + sim->CreateExplosion(hull_impact, burst_vel, Explosion::HULL_BURST, 0.50f * scale, scale, region, this); + + hit_type = HIT_HULL; + hit_hull = true; + } + } + + else { + if (dlen < Radius()) { + hull_impact = impact = shot_loc; + + if (shot->IsBeam()) + sim->CreateExplosion(impact, Velocity(), Explosion::BEAM_FLASH, 0.30f * scale, scale, region, this); + else + sim->CreateExplosion(impact, Velocity(), Explosion::HULL_FLASH, 0.30f * scale, scale, region, this); + + hit_type = HIT_HULL; + } + } + } + + // DAMAGE RESOLUTION ------------------------------------------------- + + if (hit_type != HIT_NOTHING) { + double effective_damage = shot->Damage() * dscale; + + if (shot->IsBeam()) { + effective_damage *= Game::FrameTime(); + } + else { + ApplyTorque(shot->Velocity() * (float) effective_damage * 1e-6f); + } + + if (effective_damage > 0) + Physical::InflictDamage(effective_damage); + } + + return hit_type; +} + +// +--------------------------------------------------------------------+ + +void +Debris::ExecFrame(double seconds) +{ + if (GetRegion()->Type() == SimRegion::AIR_SPACE) { + if (AltitudeAGL() < Radius()) { + velocity = Point(); + arcade_velocity = Point(); + + Terrain* terrain = region->GetTerrain(); + + if (terrain) { + Point loc = Location(); + MoveTo(Point(loc.x, terrain->Height(loc.x, loc.z), loc.z)); + } + } + else { + if (mass > 100) { + Orbital* primary = GetRegion()->GetOrbitalRegion()->Primary(); + + const double GRAV = 6.673e-11; + double m0 = primary->Mass(); + double r = primary->Radius(); + + SetDrag(0.001); + SetGravity(6 * GRAV * m0 / (r*r)); // accentuate gravity + SetBaseDensity(1.0f); + } + + AeroFrame(seconds); + } + } + else { + Physical::ExecFrame(seconds); + } +} + +// +--------------------------------------------------------------------+ + +double +Debris::AltitudeAGL() const +{ + Point loc = Location(); + double altitude_agl = loc.y; + + Terrain* terrain = region->GetTerrain(); + + if (terrain) + altitude_agl -= terrain->Height(loc.x, loc.z); + + if (!_finite(altitude_agl)) + altitude_agl = 0; + + return altitude_agl; +} \ No newline at end of file diff --git a/Stars45/Debris.h b/Stars45/Debris.h new file mode 100644 index 0000000..03802b0 --- /dev/null +++ b/Stars45/Debris.h @@ -0,0 +1,43 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Debris.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Debris Sprite class +*/ + +#ifndef Debris_h +#define Debris_h + +#include "Types.h" +#include "Geometry.h" +#include "SimObject.h" + +// +--------------------------------------------------------------------+ + +class Solid; +class Model; +class Shot; + +// +--------------------------------------------------------------------+ + +class Debris : public SimObject +{ +public: + Debris(Model* model, const Vec3& pos, const Vec3& vel, double mass); + + void SetLife(int seconds) { life = seconds; } + virtual int HitBy(Shot* shot, Point& impact); + + virtual void ExecFrame(double seconds); + virtual double AltitudeAGL() const; +}; + +#endif Debris_h + diff --git a/Stars45/DetailSet.cpp b/Stars45/DetailSet.cpp new file mode 100644 index 0000000..39f49d0 --- /dev/null +++ b/Stars45/DetailSet.cpp @@ -0,0 +1,228 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: DetailSet.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Level of Detail manager class +*/ + +#include "MemDebug.h" +#include "DetailSet.h" +#include "Random.h" + +#include "Game.h" + +// +--------------------------------------------------------------------+ + +SimRegion* DetailSet::ref_rgn = 0; +Point DetailSet::ref_loc; + +// +--------------------------------------------------------------------+ + +DetailSet::DetailSet() +{ + for (int i = 0; i < MAX_DETAIL; i++) + rad[i] = 0; + + index = -1; + levels = 0; + rgn = 0; +} + +DetailSet::~DetailSet() +{ + Destroy(); +} + +// +--------------------------------------------------------------------+ + +int +DetailSet::DefineLevel(double r, Graphic* g, Point* o, Point* spin_rate) +{ + if (levels < MAX_DETAIL && rep[levels].size() == 0) { + rad[levels] = r; + + if (g) + rep[levels].append(g); + + if (o) + off[levels].append(o); + + else if (g) + off[levels].append(new(__FILE__,__LINE__) Point(0,0,0)); + + if (rate.size() == 0) { + if (spin_rate) { + rate.append(spin_rate); + + // randomize the initial orientation: + Point* initial_spin = new(__FILE__,__LINE__) Point(); + if (spin_rate->x != 0) initial_spin->x = Random(-PI, PI); + if (spin_rate->y != 0) initial_spin->y = Random(-PI, PI); + if (spin_rate->z != 0) initial_spin->z = Random(-PI, PI); + + spin.append(initial_spin); + } + else { + rate.append(new(__FILE__,__LINE__) Point()); + spin.append(new(__FILE__,__LINE__) Point()); + } + } + else { + if (spin_rate) + rate[ rep[levels].size() - 1 ] = spin_rate; + } + + levels++; + } + + return levels-1; +} + +void +DetailSet::AddToLevel(int level, Graphic* g, Point* offset, Point* spin_rate) +{ + if (g && level >= 0 && level < levels) { + rep[level].append(g); + + if (!offset) + offset = new(__FILE__,__LINE__) Point(0,0,0); + + off[level].append(offset); + + if (spin_rate) { + int nrep = rep[level].size(); + if (nrep > rate.size()) + rate.append(spin_rate); + else + rate[ nrep-1 ] = spin_rate; + } + + if (spin.size() < rep[level].size()) { + Point* initial_spin = new(__FILE__,__LINE__) Point(); + + if (spin_rate) { + // randomize the initial orientation: + if (spin_rate->x != 0) initial_spin->x = Random(-PI, PI); + if (spin_rate->y != 0) initial_spin->y = Random(-PI, PI); + if (spin_rate->z != 0) initial_spin->z = Random(-PI, PI); + } + + spin.append(initial_spin); + } + } +} + +int +DetailSet::NumModels(int level) const +{ + if (level >= 0 && level < levels) + return rep[level].size(); + + return 0; +} + +// +--------------------------------------------------------------------+ + +void +DetailSet::ExecFrame(double seconds) +{ + for (int i = 0; i < spin.size() && i < rate.size(); i++) + (*spin[i]) += (*rate[i]) * seconds; +} + +// +--------------------------------------------------------------------+ + +void +DetailSet::SetLocation(SimRegion* r, const Point& p) +{ + rgn = r; + loc = p; +} + +// +--------------------------------------------------------------------+ + +void +DetailSet::SetReference(SimRegion* r, const Point& p) +{ + ref_rgn = r; + ref_loc = p; +} + +// +--------------------------------------------------------------------+ + +int +DetailSet::GetDetailLevel() +{ + index = 0; + + if (rgn == ref_rgn) { + double screen_width = Game::GetScreenWidth(); + Point delta = loc - ref_loc; + double distance = delta.length(); + + for (int i = 1; i < levels && rad[i] > 0; i++) { + double apparent_feature_size = (rad[i] * screen_width) / distance; + + if (apparent_feature_size > 0.4) + index = i; + } + } + + return index; +} + +// +--------------------------------------------------------------------+ + +Graphic* +DetailSet::GetRep(int level, int n) +{ + if (level >= 0 && level < levels && n >= 0 && n < rep[level].size()) + return rep[level].at(n); + + return 0; +} + +Point +DetailSet::GetOffset(int level, int n) +{ + if (level >= 0 && level < levels && n >= 0 && n < off[level].size()) + return *(off[level].at(n)); + + return Point(); +} + +Point +DetailSet::GetSpin(int level, int n) +{ + if (n >= 0 && n < spin.size()) + return *(spin.at(n)); + + return Point(); +} + +// +--------------------------------------------------------------------+ + +void +DetailSet::Destroy() +{ + for (int i = 0; i < levels; i++) { + ListIter iter = rep[i]; + + while (++iter) { + Graphic* g = iter.removeItem(); + g->Destroy(); // this will delete the object (g) + } + + off[i].destroy(); + } + + rate.destroy(); + spin.destroy(); +} diff --git a/Stars45/DetailSet.h b/Stars45/DetailSet.h new file mode 100644 index 0000000..8a847ed --- /dev/null +++ b/Stars45/DetailSet.h @@ -0,0 +1,73 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: DetailSet.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Level of Detail Manger class +*/ + +#ifndef DetailSet_h +#define DetailSet_h + +#include "Types.h" +#include "Geometry.h" +#include "Graphic.h" +#include "List.h" + +// +--------------------------------------------------------------------+ + +class Sim; +class SimRegion; + +// +--------------------------------------------------------------------+ + +class DetailSet +{ +public: + enum { MAX_DETAIL = 4 }; + + DetailSet(); + virtual ~DetailSet(); + + int DefineLevel(double r, Graphic* g=0, Point* offset=0, Point* spin=0); + void AddToLevel(int level, Graphic* g, Point* offset=0, Point* spin=0); + int NumLevels() const { return levels; } + int NumModels(int level) const; + + void ExecFrame(double seconds); + void SetLocation(SimRegion* rgn, const Point& loc); + static void SetReference(SimRegion* rgn, const Point& loc); + + int GetDetailLevel(); + Graphic* GetRep(int level, int n=0); + Point GetOffset(int level, int n=0); + Point GetSpin(int level, int n=0); + void Destroy(); + +protected: + List rep[MAX_DETAIL]; + List off[MAX_DETAIL]; + double rad[MAX_DETAIL]; + + List spin; + List rate; + + int index; + int levels; + SimRegion* rgn; + Point loc; + + static SimRegion* ref_rgn; + static Point ref_loc; +}; + +// +--------------------------------------------------------------------+ + +#endif DetailSet_h + diff --git a/Stars45/DisplayView.cpp b/Stars45/DisplayView.cpp new file mode 100644 index 0000000..64088df --- /dev/null +++ b/Stars45/DisplayView.cpp @@ -0,0 +1,239 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: DisplayView.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + View class for Quantum Destination HUD Overlay +*/ + +#include "MemDebug.h" +#include "DisplayView.h" +#include "QuantumDrive.h" +#include "HUDView.h" +#include "Ship.h" +#include "Element.h" +#include "Sim.h" +#include "StarSystem.h" +#include "FormatUtil.h" + +#include "Color.h" +#include "Window.h" +#include "Video.h" +#include "Screen.h" +#include "DataLoader.h" +#include "Scene.h" +#include "Font.h" +#include "FontMgr.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "Game.h" +#include "Menu.h" + +// +====================================================================+ + +class DisplayElement +{ +public: + static const char* TYPENAME() { return "DisplayElement"; } + + DisplayElement() : image(0), font(0), blend(0), hold(0), fade_in(0), fade_out(0) { } + + Text text; + Bitmap* image; + Font* font; + Color color; + Rect rect; + int blend; + double hold; + double fade_in; + double fade_out; +}; + +// +====================================================================+ + +DisplayView* display_view = 0; + +DisplayView::DisplayView(Window* c) + : View(c), width(0), height(0), xcenter(0), ycenter(0) +{ + display_view = this; + DisplayView::OnWindowMove(); +} + +DisplayView::~DisplayView() +{ + if (display_view == this) + display_view = 0; + + elements.destroy(); +} + +DisplayView* +DisplayView::GetInstance() +{ + if (display_view == 0) + display_view = new DisplayView(0); + + return display_view; +} + +void +DisplayView::OnWindowMove() +{ + if (window) { + width = window->Width(); + height = window->Height(); + xcenter = (width / 2.0) - 0.5; + ycenter = (height / 2.0) + 0.5; + } +} + +// +--------------------------------------------------------------------+ + +void +DisplayView::Refresh() +{ + ListIter iter = elements; + while (++iter) { + DisplayElement* elem = iter.value(); + + // convert relative rect to window rect: + Rect elem_rect = elem->rect; + if (elem_rect.x == 0 && elem_rect.y == 0 && elem_rect.w == 0 && elem_rect.h == 0) { + // stretch to fit + elem_rect.w = width; + elem_rect.h = height; + } + else if (elem_rect.w < 0 && elem_rect.h < 0) { + // center image in window + elem_rect.w *= -1; + elem_rect.h *= -1; + + elem_rect.x = (width - elem_rect.w)/2; + elem_rect.y = (height - elem_rect.h)/2; + } + else { + // offset from right or bottom + if (elem_rect.x < 0) elem_rect.x += width; + if (elem_rect.y < 0) elem_rect.y += height; + } + + // compute current fade, + // assumes fades are 1 second or less: + double fade = 0; + if (elem->fade_in > 0) fade = 1 - elem->fade_in; + else if (elem->hold > 0) fade = 1; + else if (elem->fade_out > 0) fade = elem->fade_out; + + // draw text: + if (elem->text.length() && elem->font) { + elem->font->SetColor(elem->color); + elem->font->SetAlpha(fade); + window->SetFont(elem->font); + window->DrawText(elem->text, elem->text.length(), elem_rect, DT_WORDBREAK); + } + + // draw image: + else if (elem->image) { + window->FadeBitmap( elem_rect.x, + elem_rect.y, + elem_rect.x + elem_rect.w, + elem_rect.y + elem_rect.h, + elem->image, + elem->color * fade, + elem->blend ); + + } + } +} + +// +--------------------------------------------------------------------+ + +void +DisplayView::ExecFrame() +{ + double seconds = Game::GUITime(); + + ListIter iter = elements; + while (++iter) { + DisplayElement* elem = iter.value(); + + if (elem->fade_in > 0) + elem->fade_in -= seconds; + + else if (elem->hold > 0) + elem->hold -= seconds; + + else if (elem->fade_out > 0) + elem->fade_out -= seconds; + + else + delete iter.removeItem(); + } +} + +// +--------------------------------------------------------------------+ + +void +DisplayView::ClearDisplay() +{ + elements.destroy(); +} + +// +--------------------------------------------------------------------+ + +void +DisplayView::AddText( const char* text, + Font* font, + Color color, + const Rect& rect, + double hold, + double fade_in, + double fade_out) +{ + DisplayElement* elem = new(__FILE__,__LINE__) DisplayElement; + + if (fade_in == 0 && fade_out == 0 && hold == 0) + hold = 300; + + elem->text = text; + elem->font = font; + elem->color = color; + elem->rect = rect; + elem->hold = hold; + elem->fade_in = fade_in; + elem->fade_out = fade_out; + + elements.append(elem); +} + +void +DisplayView::AddImage(Bitmap* bmp, + Color color, + int blend, + const Rect& rect, + double hold, + double fade_in, + double fade_out) +{ + DisplayElement* elem = new(__FILE__,__LINE__) DisplayElement; + + if (fade_in == 0 && fade_out == 0 && hold == 0) + hold = 300; + + elem->image = bmp; + elem->rect = rect; + elem->color = color; + elem->blend = blend; + elem->hold = hold; + elem->fade_in = fade_in; + elem->fade_out = fade_out; + + elements.append(elem); +} diff --git a/Stars45/DisplayView.h b/Stars45/DisplayView.h new file mode 100644 index 0000000..616e5f7 --- /dev/null +++ b/Stars45/DisplayView.h @@ -0,0 +1,71 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: DisplayView.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + View class for Radio Communications HUD Overlay +*/ + +#ifndef DisplayView_h +#define DisplayView_h + +#include "Types.h" +#include "View.h" +#include "SimObject.h" +#include "Color.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class Bitmap; +class DisplayElement; +class Font; + +// +--------------------------------------------------------------------+ + +class DisplayView : public View +{ +public: + DisplayView(Window* c); + virtual ~DisplayView(); + + // Operations: + virtual void Refresh(); + virtual void OnWindowMove(); + virtual void ExecFrame(); + virtual void ClearDisplay(); + + virtual void AddText(const char* txt, + Font* font, + Color color, + const Rect& rect, + double hold = 1e9, + double fade_in = 0, + double fade_out = 0); + + virtual void AddImage(Bitmap* bmp, + Color color, + int blend, + const Rect& rect, + double hold = 1e9, + double fade_in = 0, + double fade_out = 0); + + static DisplayView* GetInstance(); + +protected: + int width, height; + double xcenter, ycenter; + + List + elements; +}; + +#endif DisplayView_h + diff --git a/Stars45/Drive.cpp b/Stars45/Drive.cpp new file mode 100644 index 0000000..21748c1 --- /dev/null +++ b/Stars45/Drive.cpp @@ -0,0 +1,497 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Drive.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Weapon class +*/ + +#include "MemDebug.h" +#include "Drive.h" +#include "Power.h" +#include "Ship.h" +#include "Sim.h" +#include "DriveSprite.h" +#include "CameraDirector.h" +#include "AudioConfig.h" + +#include "Light.h" +#include "Bitmap.h" +#include "Sound.h" +#include "DataLoader.h" +#include "Bolt.h" +#include "Solid.h" +#include "Game.h" + +// +--------------------------------------------------------------------+ + +static int drive_value[] = { + 1, 1, 1, 1, 1, 1, 1, 1 +}; + +static float drive_light[] = { + 10.0f, 100.0f, 5.0f, 1.0e3f, 100.0f, 10.0f, 0.0f, 0.0f +}; + +Bitmap* drive_flare_bitmap[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; +Bitmap* drive_trail_bitmap[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; +Bitmap* drive_glow_bitmap[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + +static Sound* sound_resource[3] = { 0, 0, 0 }; + +#define CLAMP(x, a, b) if (x < (a)) x = (a); else if (x > (b)) x = (b); + +// +----------------------------------------------------------------------+ + +DrivePort::DrivePort(const Point& l, float s) + : loc(l), flare(0), trail(0), scale(s) +{ } + +DrivePort::~DrivePort() +{ + GRAPHIC_DESTROY(flare); + GRAPHIC_DESTROY(trail); +} + +// +----------------------------------------------------------------------+ + +Drive::Drive(SUBTYPE drive_type, float max_thrust, float max_aug, bool show) + : System(DRIVE, drive_type, "Drive", drive_value[drive_type], + max_thrust*2, max_thrust*2, max_thrust*2), + thrust(max_thrust), augmenter(max_aug), scale(0.0f), + throttle(0.0f), augmenter_throttle(0.0f), intensity(0.0f), + sound(0), burner_sound(0), show_trail(show) +{ + power_flags = POWER_WATTS; + + switch (drive_type) { + default: + case PLASMA: name = Game::GetText("sys.drive.plasma"); break; + case FUSION: name = Game::GetText("sys.drive.fusion"); break; + case GREEN: name = Game::GetText("sys.drive.green"); break; + case RED: name = Game::GetText("sys.drive.red"); break; + case BLUE: name = Game::GetText("sys.drive.blue"); break; + case YELLOW: name = Game::GetText("sys.drive.yellow"); break; + case STEALTH: name = Game::GetText("sys.drive.stealth"); break; + } + + abrv = Game::GetText("sys.drive.abrv"); + + emcon_power[0] = 0; + emcon_power[1] = 50; + emcon_power[2] = 100; +} + +// +----------------------------------------------------------------------+ + +Drive::Drive(const Drive& d) + : System(d), thrust(d.thrust), augmenter(d.augmenter), scale(d.scale), + throttle(0.0f), augmenter_throttle(0.0f), intensity(0.0f), + sound(0), burner_sound(0), show_trail(d.show_trail) +{ + power_flags = POWER_WATTS; + + Mount(d); + + if (subtype != Drive::STEALTH) { + for (int i = 0; i < d.ports.size(); i++) { + DrivePort* p = d.ports[i]; + CreatePort(p->loc, p->scale); + } + } +} + +// +--------------------------------------------------------------------+ + +Drive::~Drive() +{ + if (sound) { + sound->Stop(); + sound->Release(); + sound = 0; + } + + if (burner_sound) { + burner_sound->Stop(); + burner_sound->Release(); + burner_sound = 0; + } + + ports.destroy(); +} + +// +--------------------------------------------------------------------+ + +void +Drive::Initialize() +{ + static int initialized = 0; + if (initialized) return; + + DataLoader* loader = DataLoader::GetLoader(); + loader->SetDataPath("Drive/"); + loader->LoadTexture("Drive0.pcx", drive_flare_bitmap[0], Bitmap::BMP_TRANSLUCENT); + loader->LoadTexture("Drive1.pcx", drive_flare_bitmap[1], Bitmap::BMP_TRANSLUCENT); + loader->LoadTexture("Drive2.pcx", drive_flare_bitmap[2], Bitmap::BMP_TRANSLUCENT); + loader->LoadTexture("Drive3.pcx", drive_flare_bitmap[3], Bitmap::BMP_TRANSLUCENT); + loader->LoadTexture("Drive4.pcx", drive_flare_bitmap[4], Bitmap::BMP_TRANSLUCENT); + loader->LoadTexture("Drive5.pcx", drive_flare_bitmap[5], Bitmap::BMP_TRANSLUCENT); + + loader->LoadTexture("Trail0.pcx", drive_trail_bitmap[0], Bitmap::BMP_TRANSLUCENT); + loader->LoadTexture("Trail1.pcx", drive_trail_bitmap[1], Bitmap::BMP_TRANSLUCENT); + loader->LoadTexture("Trail2.pcx", drive_trail_bitmap[2], Bitmap::BMP_TRANSLUCENT); + loader->LoadTexture("Trail3.pcx", drive_trail_bitmap[3], Bitmap::BMP_TRANSLUCENT); + loader->LoadTexture("Trail4.pcx", drive_trail_bitmap[4], Bitmap::BMP_TRANSLUCENT); + loader->LoadTexture("Trail5.pcx", drive_trail_bitmap[5], Bitmap::BMP_TRANSLUCENT); + + loader->LoadTexture("Glow0.pcx", drive_glow_bitmap[0], Bitmap::BMP_TRANSLUCENT); + loader->LoadTexture("Glow1.pcx", drive_glow_bitmap[1], Bitmap::BMP_TRANSLUCENT); + loader->LoadTexture("Glow2.pcx", drive_glow_bitmap[2], Bitmap::BMP_TRANSLUCENT); + loader->LoadTexture("Glow3.pcx", drive_glow_bitmap[3], Bitmap::BMP_TRANSLUCENT); + loader->LoadTexture("Glow4.pcx", drive_glow_bitmap[4], Bitmap::BMP_TRANSLUCENT); + loader->LoadTexture("Glow5.pcx", drive_glow_bitmap[5], Bitmap::BMP_TRANSLUCENT); + + const int SOUND_FLAGS = Sound::LOCALIZED | + Sound::LOC_3D | + Sound::LOOP | + Sound::LOCKED; + + loader->SetDataPath("Sounds/"); + loader->LoadSound("engine.wav", sound_resource[0], SOUND_FLAGS); + loader->LoadSound("burner2.wav", sound_resource[1], SOUND_FLAGS); + loader->LoadSound("rumble.wav", sound_resource[2], SOUND_FLAGS); + loader->SetDataPath(0); + + if (sound_resource[0]) + sound_resource[0]->SetMaxDistance(30.0e3f); + + if (sound_resource[1]) + sound_resource[1]->SetMaxDistance(30.0e3f); + + if (sound_resource[2]) + sound_resource[2]->SetMaxDistance(50.0e3f); + + initialized = 1; +} + +// +--------------------------------------------------------------------+ + +void +Drive::Close() +{ + for (int i = 0; i < 3; i++) { + delete sound_resource[i]; + sound_resource[i] = 0; + } +} + +// +--------------------------------------------------------------------+ + +void +Drive::StartFrame() +{ +} + +// +--------------------------------------------------------------------+ + +void +Drive::AddPort(const Point& loc, float flare_scale) +{ + if (flare_scale == 0) flare_scale = scale; + DrivePort* port = new(__FILE__,__LINE__) DrivePort(loc, flare_scale); + ports.append(port); +} + +// +--------------------------------------------------------------------+ + +void +Drive::CreatePort(const Point& loc, float flare_scale) +{ + Bitmap* flare_bmp = drive_flare_bitmap[subtype]; + Bitmap* trail_bmp = drive_trail_bitmap[subtype]; + Bitmap* glow_bmp = 0; + + if (flare_scale <= 0) + flare_scale = scale; + + if (augmenter <= 0) + glow_bmp = drive_glow_bitmap[subtype]; + + if (subtype != Drive::STEALTH && flare_scale > 0) { + DrivePort* port = new(__FILE__,__LINE__) DrivePort(loc, flare_scale); + + if (flare_bmp) { + DriveSprite* flare_rep = new(__FILE__,__LINE__) DriveSprite(flare_bmp, glow_bmp); + flare_rep->Scale(flare_scale * 1.5); + flare_rep->SetShade(0); + port->flare = flare_rep; + } + + if (trail_bmp && show_trail) { + Bolt* trail_rep = new(__FILE__,__LINE__) Bolt(flare_scale * 30, flare_scale * 8, trail_bmp, true); + port->trail = trail_rep; + } + + ports.append(port); + } +} + +// +--------------------------------------------------------------------+ + +void +Drive::Orient(const Physical* rep) +{ + System::Orient(rep); + + const Matrix& orientation = rep->Cam().Orientation(); + Point ship_loc = rep->Location(); + + for (int i = 0; i < ports.size(); i++) { + DrivePort* p = ports[i]; + + Point projector = (p->loc * orientation) + ship_loc; + + if (p->flare) { + p->flare->MoveTo(projector); + p->flare->SetFront(rep->Cam().vpn() * -10 * p->scale); + } + + if (p->trail) { + if (intensity > 0.5) { + double len = -60 * p->scale * intensity; + + if (augmenter > 0 && augmenter_throttle > 0) + len += len * augmenter_throttle; + + p->trail->Show(); + p->trail->SetEndPoints(projector, projector + rep->Cam().vpn() * len); + } + else { + p->trail->Hide(); + } + } + } +} + +// +--------------------------------------------------------------------+ + +static double drive_seconds=0; + +void +Drive::SetThrottle(double t, bool aug) +{ + double spool = 1.2 * drive_seconds; + double throttle_request = t / 100; + + if (throttle < throttle_request) { + if (throttle_request-throttle < spool) { + throttle = (float) throttle_request; + } + else { + throttle += (float) spool; + } + } + + else if (throttle > throttle_request) { + if (throttle - throttle_request < spool) { + throttle = (float) throttle_request; + } + else { + throttle -= (float) spool; + } + } + + if (throttle < 0.5) + aug = false; + + if (aug && augmenter_throttle < 1) { + augmenter_throttle += (float) spool; + + if (augmenter_throttle > 1) + augmenter_throttle = 1.0f; + } + else if (!aug && augmenter_throttle > 0) { + augmenter_throttle -= (float) spool; + + if (augmenter_throttle < 0) + augmenter_throttle = 0.0f; + } +} + +// +----------------------------------------------------------------------+ + +double +Drive::GetRequest(double seconds) const +{ + if (!power_on) return 0; + + double t_factor = max(throttle + 0.5 * augmenter_throttle, 0.3); + + return t_factor * power_level * sink_rate * seconds; +} + +bool +Drive::IsAugmenterOn() const +{ + return augmenter > 0 && + augmenter_throttle > 0.05 && + IsPowerOn() && + status > CRITICAL; +} + +// +--------------------------------------------------------------------+ + +int +Drive::NumEngines() const +{ + return ports.size(); +} + +DriveSprite* +Drive::GetFlare(int port) const +{ + if (port >= 0 && port < ports.size()) { + return ports[port]->flare; + } + + return 0; +} + +Bolt* +Drive::GetTrail(int port) const +{ + if (port >= 0 && port < ports.size()) { + return ports[port]->trail; + } + + return 0; +} + +// +--------------------------------------------------------------------+ + +float +Drive::Thrust(double seconds) +{ + drive_seconds = seconds; + + float eff = (energy/capacity) * availability * 100.0f; + float output = throttle * thrust * eff; + bool aug_on = IsAugmenterOn(); + + if (aug_on) { + output += augmenter * augmenter_throttle * eff; + + // augmenter burns extra fuel: + PowerSource* reac = ship->Reactors()[source_index]; + reac->SetCapacity(reac->GetCapacity() - (0.1 * drive_seconds)); + } + + energy = 0.0f; + + if (output < 0 || GetPowerLevel() < 0.01) + output = 0.0f; + + int vol = -10000; + int vol_aug = -10000; + double fraction = output / thrust; + + for (int i = 0; i < ports.size(); i++) { + DrivePort* p = ports[i]; + + if (p->flare) { + if (i == 0) { + if (fraction > 0) + intensity += (float) seconds; + else + intensity -= (float) seconds; + + // capture volume based on actual output: + CLAMP(intensity, 0.0f, 1.0f); + + if (intensity > 0.25) { + vol = (int) ((intensity - 1.0) * 10000.0); + CLAMP(vol, -10000, -1500); + + if (aug_on && intensity > 0.5) { + vol_aug = (int) ((5 * augmenter_throttle - 1.0) * 10000.0); + CLAMP(vol_aug, -10000, -1000); + } + } + } + + p->flare->SetShade(intensity); + } + + if (p->trail) { + p->trail->SetShade(intensity); + } + } + + CameraDirector* cam_dir = CameraDirector::GetInstance(); + + // no sound when paused! + if (!Game::Paused() && subtype != STEALTH && cam_dir && cam_dir->GetCamera()) { + if (ship && ship->GetRegion() == Sim::GetSim()->GetActiveRegion()) { + if (!sound) { + int sound_index = 0; + if (thrust > 100) + sound_index = 2; + + if (sound_resource[sound_index]) + sound = sound_resource[sound_index]->Duplicate(); + } + + if (aug_on && !burner_sound) { + if (sound_resource[1]) + burner_sound = sound_resource[1]->Duplicate(); + } + + Point cam_loc = cam_dir->GetCamera()->Pos(); + double dist = (ship->Location() - cam_loc).length(); + + if (sound && dist < sound->GetMaxDistance()) { + long max_vol = AudioConfig::EfxVolume(); + + if (vol > max_vol) + vol = max_vol; + + if (sound) { + sound->SetLocation(ship->Location()); + sound->SetVolume(vol); + sound->Play(); + } + + if (burner_sound) { + if (vol_aug > max_vol) + vol_aug = max_vol; + + burner_sound->SetLocation(ship->Location()); + burner_sound->SetVolume(vol_aug); + burner_sound->Play(); + } + } + else { + if (sound && sound->IsPlaying()) + sound->Stop(); + + if (burner_sound && burner_sound->IsPlaying()) + burner_sound->Stop(); + } + } + else { + if (sound && sound->IsPlaying()) + sound->Stop(); + + if (burner_sound && burner_sound->IsPlaying()) + burner_sound->Stop(); + } + } + + return output; +} diff --git a/Stars45/Drive.h b/Stars45/Drive.h new file mode 100644 index 0000000..1bf3873 --- /dev/null +++ b/Stars45/Drive.h @@ -0,0 +1,93 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Drive.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Conventional Drive (system) class +*/ + +#ifndef Drive_h +#define Drive_h + +#include "Types.h" +#include "System.h" +#include "Geometry.h" + +// +--------------------------------------------------------------------+ + +class Bolt; +class DriveSprite; +class Light; +class Sound; +class Ship; + +// +--------------------------------------------------------------------+ + +struct DrivePort { + static const char* TYPENAME() { return "DrivePort"; } + + DrivePort(const Point& l, float s); + ~DrivePort(); + + Point loc; + float scale; + + DriveSprite* flare; + Bolt* trail; +}; + +// +--------------------------------------------------------------------+ + +class Drive : public System +{ +public: + enum SUBTYPE { PLASMA, FUSION, GREEN, RED, BLUE, YELLOW, STEALTH }; + enum Constants { MAX_ENGINES=16 }; + + Drive(SUBTYPE s, float max_thrust, float max_aug, bool show_trail=true); + Drive(const Drive& rhs); + virtual ~Drive(); + + static void Initialize(); + static void Close(); + static void StartFrame(); + + float Thrust(double seconds); + float MaxThrust() const { return thrust; } + float MaxAugmenter() const { return augmenter; } + int NumEngines() const; + DriveSprite* GetFlare(int port) const; + Bolt* GetTrail(int port) const; + bool IsAugmenterOn() const; + + virtual void AddPort(const Point& loc, float flare_scale=0); + virtual void CreatePort(const Point& loc, float flare_scale); + + virtual void Orient(const Physical* rep); + + void SetThrottle(double t, bool aug=false); + virtual double GetRequest(double seconds) const; + +protected: + float thrust; + float augmenter; + float scale; + float throttle; + float augmenter_throttle; + float intensity; + + List ports; + + Sound* sound; + Sound* burner_sound; + bool show_trail; +}; + +#endif Drive_h + diff --git a/Stars45/DriveSprite.cpp b/Stars45/DriveSprite.cpp new file mode 100644 index 0000000..c38adc7 --- /dev/null +++ b/Stars45/DriveSprite.cpp @@ -0,0 +1,143 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2005. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: DriveSprite.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Sprite for rendering drive flares. Remains visible at extreme ranges. +*/ + +#include "MemDebug.h" +#include "DriveSprite.h" + +#include "Bitmap.h" +#include "Camera.h" +#include "Scene.h" +#include "Video.h" + +// +--------------------------------------------------------------------+ + +DriveSprite::DriveSprite() + : Sprite(), glow(0), effective_radius(0), front(0,0,0), bias(0) +{ luminous = true; } + +DriveSprite::DriveSprite(Bitmap* animation, Bitmap* g) + : Sprite(animation), glow(g), effective_radius(0), front(0,0,0), bias(0) +{ luminous = true; } + +DriveSprite::DriveSprite(Bitmap* animation, int length, int repeat, int share) + : Sprite(animation, length, repeat, share), glow(0), effective_radius(0), + front(0,0,0), bias(0) +{ luminous = true; } + +DriveSprite::~DriveSprite() +{ } + +// +--------------------------------------------------------------------+ + +void +DriveSprite::SetFront(const Vec3& f) +{ + front = f; + front.Normalize(); +} + +void +DriveSprite::SetBias(DWORD b) +{ + bias = b; +} + +// +--------------------------------------------------------------------+ + +void +DriveSprite::Render(Video* video, DWORD flags) +{ + if (!video || ((flags & RENDER_ADDITIVE) == 0)) + return; + + if (shade > 0 && !hidden && (life > 0 || loop)) { + const Camera* cam = video->GetCamera(); + bool z_disable = false; + + if (bias) + video->SetRenderState(Video::Z_BIAS, bias); + + if (front.length()) { + Point test = loc; + + if (scene && cam) { + Vec3 dir = front; + + double intensity = cam->vpn() * dir * -1; + double distance = Point(cam->Pos() - test).length(); + + if (intensity > 0.05) { + if (!scene->IsLightObscured(cam->Pos(), test, 8)) { + video->SetRenderState(Video::Z_ENABLE, false); + z_disable = true; + + if (glow) { + intensity = pow(intensity, 3); + + if (distance > 5e3) + intensity *= (1 - (distance-5e3)/45e3); + + if (intensity > 0) { + Bitmap* tmp_frame = frames; + double tmp_shade = shade; + int tmp_w = w; + int tmp_h = h; + + if (glow->Width() != frames->Width()) { + double wscale = glow->Width() / frames->Width(); + double hscale = glow->Height() / frames->Height(); + + w = (int) (w * wscale); + h = (int) (h * hscale); + } + + shade = intensity; + frames = glow; + + Sprite::Render(video, flags); + + frames = tmp_frame; + shade = tmp_shade; + w = tmp_w; + h = tmp_h; + } + } + } + } + } + } + + if (effective_radius-radius > 0.1) { + double scale_up = effective_radius / radius; + int tmp_w = w; + int tmp_h = h; + + w = (int) (w * scale_up); + h = (int) (h * scale_up); + + Sprite::Render(video, flags); + + w = tmp_w; + h = tmp_h; + } + + else { + Sprite::Render(video, flags); + } + + if (bias) video->SetRenderState(Video::Z_BIAS, 0); + if (z_disable) video->SetRenderState(Video::Z_ENABLE, true); + } +} + diff --git a/Stars45/DriveSprite.h b/Stars45/DriveSprite.h new file mode 100644 index 0000000..0556c59 --- /dev/null +++ b/Stars45/DriveSprite.h @@ -0,0 +1,47 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: DriveSprite.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Specialized Drive Sprite Object +*/ + +#ifndef DriveSprite_h +#define DriveSprite_h + +#include "Types.h" +#include "Geometry.h" +#include "Sprite.h" + +// +--------------------------------------------------------------------+ + +class DriveSprite : public Sprite +{ +public: + DriveSprite(); + DriveSprite(Bitmap* animation, Bitmap* glow); + DriveSprite(Bitmap* animation, int length=1, int repeat=1, int share=1); + virtual ~DriveSprite(); + + // operations + virtual void Render(Video* video, DWORD flags); + virtual void SetFront(const Vec3& f); + virtual void SetBias(DWORD b); + +protected: + double effective_radius; + Vec3 front; + Bitmap* glow; + DWORD bias; +}; + +// +--------------------------------------------------------------------+ + +#endif DriveSprite_h + diff --git a/Stars45/Drone.cpp b/Stars45/Drone.cpp new file mode 100644 index 0000000..49b06fc --- /dev/null +++ b/Stars45/Drone.cpp @@ -0,0 +1,196 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Drone.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Laser and Missile class +*/ + +#include "MemDebug.h" +#include "Drone.h" +#include "Weapon.h" +#include "Ship.h" +#include "Sim.h" +#include "Explosion.h" + +#include "Game.h" +#include "Bolt.h" +#include "Sprite.h" +#include "Solid.h" +#include "Light.h" +#include "Bitmap.h" +#include "DataLoader.h" +#include "Sound.h" + +// +--------------------------------------------------------------------+ + +Drone::Drone(const Point& pos, const Camera& shot_cam, WeaponDesign* dsn, const Ship* ship) + : Shot(pos, shot_cam, dsn, ship), + decoy_type(0), iff_code(0) +{ + obj_type = SimObject::SIM_DRONE; + + if (dsn) { + decoy_type = dsn->decoy_type; + probe = dsn->probe; + integrity = dsn->integrity; + sprintf(name, "Drone %04d", Identity()); + } +} + +// +--------------------------------------------------------------------+ + +Drone::~Drone() +{ +} + +// +--------------------------------------------------------------------+ + +void +Drone::SeekTarget(SimObject* target, System* sub) +{ + if (!probe) + Shot::SeekTarget(target, sub); +} + +// +--------------------------------------------------------------------+ + +void +Drone::ExecFrame(double seconds) +{ + Shot::ExecFrame(seconds); +} + +// +--------------------------------------------------------------------+ + +void +Drone::Disarm() +{ + Shot::Disarm(); +} + +// +--------------------------------------------------------------------+ + +void +Drone::Destroy() +{ + Shot::Destroy(); +} + +// +--------------------------------------------------------------------+ + +double +Drone::PCS() const +{ + if (decoy_type == 0 && !probe) + return 10e3; + + return 0; +} + +double +Drone::ACS() const +{ + if (decoy_type == 0 && !probe) + return 1e3; + + return 0; +} + +// +--------------------------------------------------------------------+ + +const char* +Drone::ClassName() const +{ + return Ship::ClassName(decoy_type); +} + +int +Drone::Class() const +{ + return decoy_type; +} + +// +--------------------------------------------------------------------+ + +int +Drone::HitBy(Shot* shot, Point& impact) +{ + if (life == 0 || !shot->IsArmed()) return 0; + + const int HIT_NOTHING = 0; + const int HIT_HULL = 1; + + Point hull_impact; + int hit_type = HIT_NOTHING; + Point shot_loc = shot->Location(); + Point shot_org = shot->Origin(); + Point delta = shot_loc - Location(); + double dlen = delta.length(); + double dscale = 1; + float scale = design->explosion_scale; + Sim* sim = Sim::GetSim(); + + if (scale <= 0) + scale = design->scale; + + // MISSILE PROCESSING ------------------------------------------------ + + if (shot->IsMissile()) { + if (dlen < 10 * Radius()) { + hull_impact = impact = shot_loc; + sim->CreateExplosion(impact, Velocity(), Explosion::HULL_FLASH, 0.3f * scale, scale, region); + sim->CreateExplosion(impact, Point(), Explosion::SHOT_BLAST, 2.0f, scale, region); + hit_type = HIT_HULL; + } + } + + // ENERGY WEP PROCESSING --------------------------------------------- + + else { + if (shot->IsBeam()) { + // check right angle spherical distance: + Point d0 = Location() - shot_org; + Point w = shot_loc - shot_org; w.Normalize(); + Point test = shot_org + w * (d0 * w); + Point d1 = test - Location(); + double dlen = d1.length(); // distance of point from line + + if (dlen < 2*Radius()) { + hull_impact = impact = test; + shot->SetBeamPoints(shot_org, impact); + sim->CreateExplosion(impact, Velocity(), Explosion::BEAM_FLASH, 0.30f * scale, scale, region); + hit_type = HIT_HULL; + } + } + else if (dlen < 2*Radius()) { + hull_impact = impact = shot_loc; + sim->CreateExplosion(impact, Velocity(), Explosion::HULL_FLASH, 0.30f * scale, scale, region); + hit_type = HIT_HULL; + } + } + + // DAMAGE RESOLUTION ------------------------------------------------- + + if (hit_type != HIT_NOTHING) { + double effective_damage = shot->Damage() * dscale; + + if (shot->IsBeam()) { + effective_damage *= Game::FrameTime(); + } + else { + ApplyTorque(shot->Velocity() * (float) effective_damage * 1e-6f); + } + + if (effective_damage > 0) + Physical::InflictDamage(effective_damage); + } + + return hit_type; +} diff --git a/Stars45/Drone.h b/Stars45/Drone.h new file mode 100644 index 0000000..cac3a09 --- /dev/null +++ b/Stars45/Drone.h @@ -0,0 +1,69 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Drone.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Decoy / Weapons Drone class +*/ + +#ifndef Drone_h +#define Drone_h + +#include "Types.h" +#include "Geometry.h" +#include "Shot.h" + +// +--------------------------------------------------------------------+ + +class Camera; +class Ship; +class Trail; +class System; +class WeaponDesign; +class Sprite3D; +class Sound; + +// +--------------------------------------------------------------------+ + +class Drone : public Shot +{ +public: + static const char* TYPENAME() { return "Drone"; } + + Drone(const Point& pos, const Camera& cam, WeaponDesign* design, const Ship* ship=0); + virtual ~Drone(); + + virtual void SeekTarget(SimObject* target, System* sub=0); + virtual void ExecFrame(double factor); + + virtual bool IsDrone() const { return true; } + virtual bool IsDecoy() const { return decoy_type != 0; } + virtual bool IsProbe() const { return probe?true:false; } + + virtual void Disarm(); + virtual void Destroy(); + + // SENSORS AND VISIBILITY: + virtual double PCS() const; + virtual double ACS() const; + virtual const char* ClassName() const; + virtual int Class() const; + + // DAMAGE RESOLUTION: + void SetLife(int seconds) { life = seconds; } + virtual int HitBy(Shot* shot, Point& impact); + +protected: + int iff_code; + int decoy_type; + int probe; +}; + +#endif Drone_h + diff --git a/Stars45/DropShipAI.cpp b/Stars45/DropShipAI.cpp new file mode 100644 index 0000000..41672cc --- /dev/null +++ b/Stars45/DropShipAI.cpp @@ -0,0 +1,122 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: DropShipAI.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Drop Ship (orbit/surface and surface/orbit) AI class +*/ + +#include "MemDebug.h" +#include "DropShipAI.h" +#include "TacticalAI.h" +#include "Ship.h" +#include "ShipCtrl.h" +#include "Drive.h" +#include "Sim.h" +#include "StarSystem.h" +#include "KeyMap.h" + +#include "Game.h" + +// +----------------------------------------------------------------------+ + +DropShipAI::DropShipAI(Ship* s) + : ShipAI(s) +{ + seek_gain = 20; + seek_damp = 0.5; + + delete tactical; + tactical = 0; +} + +DropShipAI::~DropShipAI() +{ +} + +// +--------------------------------------------------------------------+ + +void +DropShipAI::FindObjective() +{ + distance = 0; + + if (!ship) return; + + Sim* sim = Sim::GetSim(); + SimRegion* self_rgn = ship->GetRegion(); + + // if making orbit, go up: + if (self_rgn->Type() == Sim::AIR_SPACE) { + obj_w = self->Location() + Point(0, 1e3, 0); + } + + // if breaking orbit, head for terrain region: + else { + SimRegion* dst_rgn = sim->FindNearestTerrainRegion(ship); + Point dst = dst_rgn->GetOrbitalRegion()->Location() - + self_rgn->GetOrbitalRegion()->Location() + + Point(0, 0, -1e6); + + obj_w = dst.OtherHand(); + } + + // distance from self to navpt: + distance = Point(obj_w - self->Location()).length(); + + // transform into camera coords: + objective = Transform(obj_w); + objective.Normalize(); +} + +// +--------------------------------------------------------------------+ + +void +DropShipAI::Navigator() +{ + accumulator.Clear(); + magnitude = 0; + + if (other) + ship->SetFLCSMode(Ship::FLCS_AUTO); + else + ship->SetFLCSMode(Ship::FLCS_MANUAL); + + Accumulate(AvoidCollision()); + Accumulate(Seek(objective)); + + // are we being asked to flee? + if (fabs(accumulator.yaw) == 1.0 && accumulator.pitch == 0.0) { + accumulator.pitch = -0.7f; + accumulator.yaw *= 0.25f; + } + + self->ApplyRoll((float) (accumulator.yaw * -0.4)); + self->ApplyYaw((float) (accumulator.yaw * 0.2)); + + if (fabs(accumulator.yaw) > 0.5 && fabs(accumulator.pitch) < 0.1) + accumulator.pitch -= 0.1f; + + if (accumulator.pitch != 0) + self->ApplyPitch((float) accumulator.pitch); + + // if not turning, roll to orient with world coords: + if (fabs(accumulator.yaw) < 0.1) { + Point vrt = ((Camera*) &(self->Cam()))->vrt(); + double deflection = vrt.y; + if (deflection != 0) { + double theta = asin(deflection/vrt.length()); + self->ApplyRoll(-theta); + } + } + + ship->SetThrottle(100); + ship->ExecFLCSFrame(); +} + diff --git a/Stars45/DropShipAI.h b/Stars45/DropShipAI.h new file mode 100644 index 0000000..fe34571 --- /dev/null +++ b/Stars45/DropShipAI.h @@ -0,0 +1,47 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: DropShipAI.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Drop Ship (orbit/surface and surface/orbit) AI class +*/ + +#ifndef DropShipAI_h +#define DropShipAI_h + +#include "Types.h" +#include "Geometry.h" +#include "System.h" +#include "ShipAI.h" + +// +--------------------------------------------------------------------+ + +class Ship; + +// +--------------------------------------------------------------------+ + +class DropShipAI : public ShipAI +{ +public: + DropShipAI(Ship* s); + virtual ~DropShipAI(); + + enum { DIR_TYPE = 2001 }; + virtual int Type() const { return DIR_TYPE; } + +protected: + // accumulate behaviors: + virtual void Navigator(); + virtual void FindObjective(); +}; + +// +--------------------------------------------------------------------+ + +#endif DropShipAI_h + diff --git a/Stars45/Element.cpp b/Stars45/Element.cpp new file mode 100644 index 0000000..a375cc2 --- /dev/null +++ b/Stars45/Element.cpp @@ -0,0 +1,667 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars + FILE: Element.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Package Element (e.g. Flight) class implementation +*/ + +#include "MemDebug.h" +#include "Element.h" +#include "Instruction.h" +#include "RadioMessage.h" +#include "RadioHandler.h" +#include "Sim.h" +#include "Ship.h" +#include "NetUtil.h" + +#include "Game.h" + +// +----------------------------------------------------------------------+ + +static int id_key = 1000; + +Element::Element(const char* call_sign, int a_iff, int a_type) + : id(id_key++), name(call_sign), type(a_type), iff(a_iff), + player(0), command_ai(1), commander(0), assignment(0), carrier(0), + combat_group(0), combat_unit(0), launch_time(0), hold_time(0), + zone_lock(0), respawns(0), count(0), rogue(false), playable(true), intel(0) +{ + if (!call_sign) { + char buf[32]; + sprintf(buf, "Pkg %d", id); + name = buf; + } + + SetLoadout(0); +} + +Element::~Element() +{ + flight_plan.destroy(); + objectives.destroy(); + instructions.destroy(); + + for (int i = 0; i < ships.size(); i++) + ships[i]->SetElement(0); + + respawns = 0; +} + +// +----------------------------------------------------------------------+ + +int +Element::AddShip(Ship* ship, int index) +{ + if (ship && !ships.contains(ship)) { + Observe(ship); + + if (index < 0) { + ships.append(ship); + index = ships.size(); + } + else { + ships.insert(ship, index-1); + } + + ship->SetElement(this); + + if (respawns < ship->RespawnCount()) + respawns = ship->RespawnCount(); + } + + return index; +} + +void +Element::DelShip(Ship* ship) +{ + if (ship && ships.contains(ship)) { + ships.remove(ship); + ship->SetElement(0); + + if (ships.isEmpty()) + respawns = ship->RespawnCount(); + } +} + +Ship* +Element::GetShip(int index) +{ + if (index >= 1 && index <= ships.size()) + return ships[index-1]; + + return 0; +} + +int +Element::GetShipClass() +{ + if (ships.size()) + return ships[0]->Class(); + + return 0; +} + +int +Element::FindIndex(const Ship* s) +{ + return ships.index(s) + 1; +} + +bool +Element::Contains(const Ship* s) +{ + return ships.contains(s); +} + +bool +Element::IsActive() const +{ + bool active = false; + + for (int i = 0; i < ships.size() && !active; i++) { + Ship* s = ships[i]; + if (s->Life() && s->MissionClock()) + active = true; + } + + return active; +} + +bool +Element::IsFinished() const +{ + bool finished = false; + + if (launch_time > 0 && respawns < 1) { + finished = true; + + if (ships.size() > 0) { + for (int i = 0; i < ships.size() && finished; i++) { + Ship* s = ships[i]; + if (s->RespawnCount() > 0 || + s->MissionClock() == 0 || + s->Life() && !s->GetInbound()) + finished = false; + } + } + } + + return finished; +} + +bool +Element::IsNetObserver() const +{ + bool observer = !IsSquadron(); + + for (int i = 0; i < ships.size() && observer; i++) { + Ship* s = ships[i]; + + if (!s->IsNetObserver()) + observer = false; + } + + return observer; +} + +bool +Element::IsSquadron() const +{ + return count > 0; +} + +bool +Element::IsStatic() const +{ + if (IsSquadron() || IsFinished()) + return false; + + const Ship* s = ships.at(0); + if (s && s->IsStatic()) + return true; + + return false; +} + +// +----------------------------------------------------------------------+ + +bool +Element::IsHostileTo(const Ship* s) const +{ + if (iff <= 0 || iff >= 100 || !s || launch_time == 0 || IsFinished()) + return false; + + if (IsSquadron()) + return false; + + if (s->IsRogue()) + return true; + + int s_iff = s->GetIFF(); + + if (s_iff <= 0 || s_iff >= 100 || s_iff == iff) + return false; + + if (ships.size() > 0 && ships[0]->GetRegion() != s->GetRegion()) + return false; + + return true; +} + +bool +Element::IsHostileTo(int iff_code) const +{ + if (iff <= 0 || iff >= 100 || launch_time == 0 || IsFinished()) + return false; + + if (IsSquadron()) + return false; + + if (iff_code <= 0 || iff_code >= 100 || iff_code == iff) + return false; + + return true; +} + +bool +Element::IsObjectiveTargetOf(const Ship* s) const +{ + if (!s || launch_time == 0 || IsFinished()) + return false; + + const char* e_name = Name().data(); + int e_len = Name().length(); + + Instruction* orders = s->GetRadioOrders(); + if (orders && orders->Action() > Instruction::SWEEP) { + const char* o_name = orders->TargetName(); + int o_len = 0; + + if (o_name && *o_name) + o_len = strlen(o_name); + + if (e_len < o_len) + o_len = e_len; + + if (!strncmp(e_name, o_name, o_len)) + return true; + } + + Element* elem = s->GetElement(); + if (elem) { + for (int i = 0; i < elem->NumObjectives(); i++) { + Instruction* obj = elem->GetObjective(i); + + if (obj) { + const char* o_name = obj->TargetName(); + int o_len = 0; + + if (o_name && *o_name) + o_len = strlen(o_name); + + if (e_len < o_len) + o_len = e_len; + + if (!strncmp(e_name, o_name, o_len)) + return true; + } + } + } + + return false; +} + +// +----------------------------------------------------------------------+ + +void +Element::SetLaunchTime(DWORD t) +{ + if (launch_time == 0 || t == 0) + launch_time = t; +} + +double +Element::GetHoldTime() +{ + return hold_time; +} + +void +Element::SetHoldTime(double t) +{ + if (t >= 0) + hold_time = t; +} + +bool +Element::GetZoneLock() +{ + return zone_lock; +} + +void +Element::SetZoneLock(bool z) +{ + zone_lock = z; +} + +void +Element::SetLoadout(int* l) +{ + if (l) { + CopyMemory(load, l, sizeof(load)); + } + else { + for (int i = 0; i < 16; i++) + load[i] = -1; + } +} + +// +----------------------------------------------------------------------+ + +bool +Element::Update(SimObject* obj) +{ + // false alarm, keep watching: + if (obj->Life() != 0) { + ::Print("Element (%s) false update on (%s) life = %f\n", Name().data(), obj->Name(), obj->Life()); + return false; + } + + Ship* s = (Ship*) obj; + ships.remove(s); + + if (ships.isEmpty()) + respawns = s->RespawnCount(); + + return SimObserver::Update(obj); +} + +const char* +Element::GetObserverName() const +{ + return (const char*) (Text("Element ") + Name()); +} + +// +----------------------------------------------------------------------+ + +void +Element::AddNavPoint(Instruction* pt, Instruction* afterPoint, bool send) +{ + if (pt && !flight_plan.contains(pt)) { + int index = -1; + + if (afterPoint) { + index = flight_plan.index(afterPoint); + + if (index > -1) + flight_plan.insert(pt, index+1); + else + flight_plan.append(pt); + } + + else { + flight_plan.append(pt); + } + + if (send) { + NetUtil::SendNavData(true, this, index, pt); + } + } +} + +void +Element::DelNavPoint(Instruction* pt, bool send) +{ + // XXX MEMORY LEAK + // This is a small memory leak, but I'm not sure if it is + // safe to delete the navpoint when removing it from the + // flight plan. Other ships in the element might have + // pointers to the object...? + + if (pt) { + int index = flight_plan.index(pt); + flight_plan.remove(pt); + + if (send) { + NetUtil::SendNavDelete(this, index); + } + } +} + +// +----------------------------------------------------------------------+ + +void +Element::ClearFlightPlan(bool send) +{ + hold_time = 0; + flight_plan.destroy(); + objectives.destroy(); + instructions.destroy(); + + if (send) { + NetUtil::SendNavDelete(this, -1); + } +} + +// +----------------------------------------------------------------------+ + +Instruction* +Element::GetNextNavPoint() +{ + if (hold_time <= 0 && flight_plan.size() > 0) { + ListIter iter = flight_plan; + while (++iter) { + Instruction* navpt = iter.value(); + + if (navpt->Status() == Instruction::COMPLETE && navpt->HoldTime() > 0) + return navpt; + + if (navpt->Status() <= Instruction::ACTIVE) + return navpt; + } + } + + return 0; +} + +// +----------------------------------------------------------------------+ + +int +Element::GetNavIndex(const Instruction* n) +{ + int index = 0; + + if (flight_plan.size() > 0) { + ListIter navpt = flight_plan; + while (++navpt) { + index++; + if (navpt.value() == n) + return index; + } + } + + return 0; +} + +// +----------------------------------------------------------------------+ + +List& +Element::GetFlightPlan() +{ + return flight_plan; +} + +int +Element::FlightPlanLength() +{ + return flight_plan.size(); +} + +// +----------------------------------------------------------------------+ + +void +Element::ClearObjectives() +{ + objectives.destroy(); +} + +void +Element::AddObjective(Instruction* obj) +{ + objectives.append(obj); +} + +Instruction* +Element::GetObjective(int index) +{ + if (objectives.isEmpty()) + return 0; + + if (index < 0) + index = 0; + + else if (index >= objectives.size()) + index = index % objectives.size(); + + return objectives.at(index); +} + +Instruction* +Element::GetTargetObjective() +{ + for (int i = 0; i < objectives.size(); i++) { + Instruction* obj = objectives[i]; + + if (obj->Status() <= Instruction::ACTIVE) { + switch (obj->Action()) { + case Instruction::INTERCEPT: + case Instruction::STRIKE: + case Instruction::ASSAULT: + case Instruction::SWEEP: + case Instruction::PATROL: + case Instruction::RECON: + case Instruction::ESCORT: + case Instruction::DEFEND: + return obj; + + default: + break; + } + } + } + + return 0; +} + +// +----------------------------------------------------------------------+ + +void +Element::ClearInstructions() +{ + instructions.clear(); +} + +void +Element::AddInstruction(const char* instr) +{ + instructions.append(new(__FILE__,__LINE__) Text(instr)); +} + +Text +Element::GetInstruction(int index) +{ + if (instructions.isEmpty()) + return Text(); + + if (index < 0) + index = 0; + + if (index >= instructions.size()) + index = index % instructions.size(); + + return *instructions.at(index); +} + +// +----------------------------------------------------------------------+ + +void +Element::ResumeAssignment() +{ + SetAssignment(0); + + if (objectives.isEmpty()) + return; + + Instruction* objective = 0; + + for (int i = 0; i < objectives.size() && !objective; i++) { + Instruction* instr = objectives[i]; + + if (instr->Status() <= Instruction::ACTIVE) { + switch (instr->Action()) { + case Instruction::INTERCEPT: + case Instruction::STRIKE: + case Instruction::ASSAULT: + objective = instr; + break; + } + } + } + + if (objective) { + Sim* sim = Sim::GetSim(); + + ListIter iter = sim->GetElements(); + while (++iter) { + Element* elem = iter.value(); + SimObject* tgt = objective->GetTarget(); + + if (tgt && tgt->Type() == SimObject::SIM_SHIP && elem->Contains((const Ship*) tgt)) { + SetAssignment(elem); + return; + } + } + } +} + +// +----------------------------------------------------------------------+ + +void +Element::HandleRadioMessage(RadioMessage* msg) +{ + if (!msg) return; + + static RadioHandler rh; + + // if this is a message from within the element, + // then all ships should report in. Otherwise, + // just the leader will acknowledge the message. + int full_report = ships.contains(msg->Sender()); + int reported = false; + + ListIter s = ships; + while (++s) { + if (rh.ProcessMessage(msg, s.value())) { + if (full_report) { + if (s.value() != msg->Sender()) + rh.AcknowledgeMessage(msg, s.value()); + } + + else if (!reported) { + rh.AcknowledgeMessage(msg, s.value()); + reported = true; + } + } + } +} + +// +----------------------------------------------------------------------+ + +bool +Element::CanCommand(Element* e) +{ + while (e) { + if (e->commander == this) + return true; + e = e->commander; + } + + return false; +} + +// +----------------------------------------------------------------------+ + +void +Element::ExecFrame(double seconds) +{ + if (hold_time > 0) { + hold_time -= seconds; + return; + } + + ListIter iter = flight_plan; + while (++iter) { + Instruction* instr = iter.value(); + + if (instr->Status() == Instruction::COMPLETE && instr->HoldTime() > 0) + instr->SetHoldTime(instr->HoldTime() - seconds); + } +} + +// +----------------------------------------------------------------------+ + +void +Element::SetIFF(int iff) +{ + for (int i = 0; i < ships.size(); i++) + ships[i]->SetIFF(iff); +} diff --git a/Stars45/Element.h b/Stars45/Element.h new file mode 100644 index 0000000..967d712 --- /dev/null +++ b/Stars45/Element.h @@ -0,0 +1,172 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Element.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Package Element (e.g. Flight) class +*/ + +#ifndef Element_h +#define Element_h + +#include "Types.h" +#include "Geometry.h" +#include "SimObject.h" +#include "List.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class Ship; +class Instruction; +class RadioMessage; + +class CombatGroup; +class CombatUnit; + +// +--------------------------------------------------------------------+ + +class Element : public SimObserver +{ +public: + // CONSTRUCTORS: + Element(const char* call_sign, int iff, int type=0 /*PATROL*/); + virtual ~Element(); + + int operator == (const Element& e) const { return id == e.id; } + + // GENERAL ACCESSORS: + int Identity() const { return id; } + int Type() const { return type; } + const Text& Name() const { return name; } + void SetName(const char* s) { name = s; } + virtual int GetIFF() const { return iff; } + int Player() const { return player; } + void SetPlayer(int p) { player = p; } + DWORD GetLaunchTime() const { return launch_time; } + void SetLaunchTime(DWORD t); + int IntelLevel() const { return intel; } + void SetIntelLevel(int i) { intel = i; } + + // ELEMENT COMPONENTS: + int NumShips() const { return ships.size(); } + int AddShip(Ship*, int index=-1); + void DelShip(Ship*); + Ship* GetShip(int index); + int GetShipClass(); + int FindIndex(const Ship* s); + bool Contains(const Ship* s); + bool IsActive() const; + bool IsFinished() const; + bool IsNetObserver() const; + bool IsSquadron() const; + bool IsStatic() const; + bool IsHostileTo(const Ship* s) const; + bool IsHostileTo(int iff_code) const; + bool IsObjectiveTargetOf(const Ship* s) const; + bool IsRogue() const { return rogue; } + bool IsPlayable() const { return playable; } + int* Loadout() { return load; } + + void SetRogue(bool r) { rogue = r; } + void SetPlayable(bool p) { playable = p; } + void SetLoadout(int* l); + virtual void SetIFF(int iff); + + virtual void ExecFrame(double seconds); + virtual bool Update(SimObject* obj); + virtual const char* GetObserverName() const; + + // OBJECTIVES: + void ClearObjectives(); + void AddObjective(Instruction* obj); + Instruction* GetObjective(int index); + Instruction* GetTargetObjective(); + int NumObjectives() const { return objectives.size(); } + + void ClearInstructions(); + void AddInstruction(const char* instr); + Text GetInstruction(int index); + int NumInstructions() const { return instructions.size(); } + + // ORDERS AND NAVIGATION: + double GetHoldTime(); + void SetHoldTime(double t); + bool GetZoneLock(); + void SetZoneLock(bool z); + void AddNavPoint(Instruction* pt, Instruction* afterPoint=0, bool send=true); + void DelNavPoint(Instruction* pt, bool send=true); + void ClearFlightPlan(bool send=true); + Instruction* GetNextNavPoint(); + int GetNavIndex(const Instruction* n); + List& GetFlightPlan(); + int FlightPlanLength(); + virtual void HandleRadioMessage(RadioMessage* msg); + + // CHAIN OF COMMAND: + Element* GetCommander() const { return commander; } + void SetCommander(Element* e) { commander = e; } + Element* GetAssignment() const { return assignment; } + void SetAssignment(Element* e) { assignment = e; } + void ResumeAssignment(); + bool CanCommand(Element* e); + Ship* GetCarrier() const { return carrier; } + void SetCarrier(Ship* c) { carrier = c; } + int GetCommandAILevel() const { return command_ai; } + void SetCommandAILevel(int n) { command_ai = n; } + const Text& GetSquadron() const { return squadron; } + void SetSquadron(const char* s) { squadron = s; } + + // DYNAMIC CAMPAIGN: + CombatGroup* GetCombatGroup() { return combat_group; } + void SetCombatGroup(CombatGroup* g) { combat_group = g; } + CombatUnit* GetCombatUnit() { return combat_unit; } + void SetCombatUnit(CombatUnit* u) { combat_unit = u; } + + // SQUADRON STUFF: + int GetCount() const { return count; } + void SetCount(int n) { count = n; } + +protected: + int id; + int iff; + int type; + int player; + int command_ai; + int respawns; + int intel; + Text name; + + // squadron elements only: + int count; + + List ships; + List ship_names; + List instructions; + List objectives; + List flight_plan; + + Element* commander; + Element* assignment; + Ship* carrier; + Text squadron; + + CombatGroup* combat_group; + CombatUnit* combat_unit; + DWORD launch_time; + double hold_time; + + bool rogue; + bool playable; + bool zone_lock; + int load[16]; +}; + +#endif Element_h + diff --git a/Stars45/EngDlg.cpp b/Stars45/EngDlg.cpp new file mode 100644 index 0000000..9af1fce --- /dev/null +++ b/Stars45/EngDlg.cpp @@ -0,0 +1,1043 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: EngDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Engineering (Power/Maint) Dialog Active Window class +*/ + +#include "MemDebug.h" +#include "EngDlg.h" +#include "GameScreen.h" +#include "Ship.h" +#include "Power.h" +#include "Component.h" +#include "Sim.h" + +#include "DataLoader.h" +#include "Button.h" +#include "ListBox.h" +#include "Slider.h" +#include "Video.h" +#include "Mouse.h" +#include "Keyboard.h" +#include "Game.h" +#include "FormatUtil.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +DEF_MAP_CLIENT(EngDlg, OnSource); +DEF_MAP_CLIENT(EngDlg, OnClient); +DEF_MAP_CLIENT(EngDlg, OnRouteStart); +DEF_MAP_CLIENT(EngDlg, OnRouteComplete); +DEF_MAP_CLIENT(EngDlg, OnPowerOff); +DEF_MAP_CLIENT(EngDlg, OnPowerOn); +DEF_MAP_CLIENT(EngDlg, OnOverride); +DEF_MAP_CLIENT(EngDlg, OnPowerLevel); +DEF_MAP_CLIENT(EngDlg, OnComponent); +DEF_MAP_CLIENT(EngDlg, OnAutoRepair); +DEF_MAP_CLIENT(EngDlg, OnRepair); +DEF_MAP_CLIENT(EngDlg, OnReplace); +DEF_MAP_CLIENT(EngDlg, OnQueue); +DEF_MAP_CLIENT(EngDlg, OnPriorityIncrease); +DEF_MAP_CLIENT(EngDlg, OnPriorityDecrease); +DEF_MAP_CLIENT(EngDlg, OnClose); + +// +--------------------------------------------------------------------+ + +EngDlg::EngDlg(Screen* s, FormDef& def, GameScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), manager(mgr), + ship(0), route_source(0), selected_source(0), + selected_repair(0), selected_component(0) +{ + Init(def); +} + +EngDlg::~EngDlg() +{ +} + +// +--------------------------------------------------------------------+ + +void +EngDlg::RegisterControls() +{ + for (int i = 0; i < 4; i++) { + sources[i] = (Button*) FindControl(201 + i); + REGISTER_CLIENT(EID_CLICK, sources[i], EngDlg, OnSource); + + source_levels[i] = (Slider*) FindControl(211 + i); + + clients[i] = (ListBox*) FindControl(301 + i); + + if (clients[i]) { + clients[i]->SetSelectedStyle(ListBox::LIST_ITEM_STYLE_FILLED_BOX); + clients[i]->SetSortColumn(0); + + REGISTER_CLIENT(EID_SELECT, clients[i], EngDlg, OnClient); + REGISTER_CLIENT(EID_DRAG_START, clients[i], EngDlg, OnRouteStart); + REGISTER_CLIENT(EID_DRAG_DROP, clients[i], EngDlg, OnRouteComplete); + } + } + + close_btn = (Button*) FindControl(1); + selected_name = (ActiveWindow*) FindControl(401); + power_off = (Button*) FindControl(402); + power_on = (Button*) FindControl(403); + override = (Button*) FindControl(410); + power_level = (Slider*) FindControl(404); + capacity = (Slider*) FindControl(405); + + if (close_btn) + REGISTER_CLIENT(EID_CLICK, close_btn, EngDlg, OnClose); + + if (power_off) + REGISTER_CLIENT(EID_CLICK, power_off, EngDlg, OnPowerOff); + + if (power_on) + REGISTER_CLIENT(EID_CLICK, power_on, EngDlg, OnPowerOn); + + if (override) + REGISTER_CLIENT(EID_CLICK, override, EngDlg, OnOverride); + + if (power_level) + REGISTER_CLIENT(EID_CLICK, power_level, EngDlg, OnPowerLevel); + + components = (ListBox*) FindControl(501); + + if (components) { + components->SetSelectedStyle(ListBox::LIST_ITEM_STYLE_FILLED_BOX); + components->SetSortColumn(0); + REGISTER_CLIENT(EID_SELECT, components, EngDlg, OnComponent); + } + + auto_repair = (Button*) FindControl(700); + repair = (Button*) FindControl(502); + replace = (Button*) FindControl(503); + repair_time = FindControl(512); + replace_time = FindControl(513); + priority_increase = (Button*) FindControl(602); + priority_decrease = (Button*) FindControl(603); + + if (auto_repair) + REGISTER_CLIENT(EID_CLICK, auto_repair, EngDlg, OnAutoRepair); + + if (repair) + REGISTER_CLIENT(EID_CLICK, repair, EngDlg, OnRepair); + + if (replace) + REGISTER_CLIENT(EID_CLICK, replace, EngDlg, OnReplace); + + if (repair_time) + repair_time->Hide(); + + if (replace_time) + replace_time->Hide(); + + if (priority_increase) { + char up_arrow[2]; + up_arrow[0] = Font::ARROW_UP; + up_arrow[1] = 0; + priority_increase->SetText(up_arrow); + + REGISTER_CLIENT(EID_CLICK, priority_increase, EngDlg, OnPriorityIncrease); + } + + if (priority_decrease) { + char dn_arrow[2]; + dn_arrow[0] = Font::ARROW_DOWN; + dn_arrow[1] = 0; + priority_decrease->SetText(dn_arrow); + + REGISTER_CLIENT(EID_CLICK, priority_decrease, EngDlg, OnPriorityDecrease); + } + + repair_queue = (ListBox*) FindControl(601); + + if (repair_queue) { + repair_queue->SetSelectedStyle(ListBox::LIST_ITEM_STYLE_FILLED_BOX); + REGISTER_CLIENT(EID_SELECT, repair_queue, EngDlg, OnQueue); + } +} + +// +--------------------------------------------------------------------+ + +void +EngDlg::Show() +{ + FormWindow::Show(); + + if (ship) { + int nsources = ship->Reactors().size(); + + for (int i = 0; i < 4; i++) { + if (i >= nsources) { + sources[i]->Hide(); + source_levels[i]->Hide(); + clients[i]->Hide(); + } + } + } +} + +void +EngDlg::Hide() +{ + FormWindow::Hide(); +} + +// +--------------------------------------------------------------------+ + +void +EngDlg::SetShip(Ship* s) +{ + if (IsShown() && ship != s) { + selected_source = 0; + selected_repair = 0; + selected_component = 0; + selected_clients.clear(); + + ship = s; + + UpdateRouteTables(); + ExecFrame(); + } + + // make sure we clear the ship, even if not shown: + else if (!s) { + ship = 0; + } +} + +void +EngDlg::UpdateRouteTables() +{ + for (int i = 0; i < 4; i++) + clients[i]->ClearSelection(); + + if (components) + components->ClearItems(); + + if (priority_increase) + priority_increase->SetEnabled(false); + + if (priority_decrease) + priority_decrease->SetEnabled(false); + + if (ship) { + for (int i = 0; i < 4; i++) { + if (sources[i]) + sources[i]->Hide(); + + if (source_levels[i]) + source_levels[i]->Hide(); + + if (clients[i]) { + clients[i]->ClearItems(); + clients[i]->Hide(); + } + } + + int reactor_index = 0; + ListIter reactor_iter = ship->Reactors(); + + while (++reactor_iter) { + PowerSource* reactor = reactor_iter.value(); + + if (sources[reactor_index] && clients[reactor_index]) { + sources[reactor_index]->SetText(Game::GetText(reactor->Abbreviation())); + sources[reactor_index]->Show(); + + source_levels[reactor_index]->Show(); + source_levels[reactor_index]->SetValue((int) reactor->GetPowerLevel()); + + clients[reactor_index]->Show(); + + int index = 0; + ListIter client = reactor->Clients(); + while (++client) { + char abrv[64], num[20]; + FormatNumber(num, client->GetPowerLevel()); + strcpy(abrv, Game::GetText(client->Name())); + + clients[reactor_index]->AddItemWithData(Game::GetText(abrv), index); + clients[reactor_index]->SetItemText(index, 1, num); + clients[reactor_index]->SetItemData(index, 1, (DWORD) client->GetPowerLevel()); + + index++; + } + + clients[reactor_index]->SortItems(); + } + + reactor->RouteScanned(); + reactor_index++; + } + } +} + +// +--------------------------------------------------------------------+ + +void +EngDlg::ExecFrame() +{ + if (IsShown()) + UpdateSelection(); +} + +void +EngDlg::UpdateSelection() +{ + if (!ship) return; + + char num[20]; + int nsources = ship->Reactors().size(); + + // update the route tables: + for (int source_index = 0; source_index < nsources; source_index++) { + PowerSource* reac = ship->Reactors()[source_index]; + + if (reac->RouteChanged()) + UpdateRouteTables(); + + Color c(62,106,151); + + if (reac->IsPowerOn()) { + switch (reac->Status()) { + default: + case System::NOMINAL: break; + case System::DEGRADED: c = Color::Yellow; break; + case System::CRITICAL: c = Color::BrightRed; break; + case System::DESTROYED: c = Color::DarkRed; break; + } + } + else { + c = Color::Gray; + } + + sources[source_index]->SetBackColor(c); + source_levels[source_index]->SetEnabled(reac->IsPowerOn()); + source_levels[source_index]->SetValue((int) reac->GetPowerLevel()); + + ListBox* client_list = clients[source_index]; + + for (int i = 0; i < client_list->NumItems(); i++) { + int index = client_list->GetItemData(i); + + System* client = reac->Clients()[index]; + FormatNumber(num, client->GetPowerLevel()); + client_list->SetItemText(i, 1, num); + client_list->SetItemData(i, 1, (DWORD) client->GetPowerLevel()); + + if (client->IsPowerOn()) { + Color c; + + switch (client->Status()) { + default: + case System::NOMINAL: c = Color::White; break; + case System::DEGRADED: c = Color::Yellow; break; + case System::CRITICAL: c = Color::BrightRed; break; + case System::DESTROYED: c = Color::DarkRed; break; + } + + client_list->SetItemColor(i, c); + } + else { + client_list->SetItemColor(i, Color::Gray); + } + + } + } + + // update the detail info: + if (selected_source) { + selected_name->SetText(Game::GetText(selected_source->Name())); + power_off->SetEnabled(true); + power_on->SetEnabled(true); + override->SetEnabled(true); + if (override->GetButtonState() != 2) + override->SetButtonState(selected_source->GetPowerLevel() > 100 ? 1 : 0); + power_level->SetEnabled(selected_source->IsPowerOn()); + power_level->SetValue((int) selected_source->GetPowerLevel()); + + if (selected_source->Safety() < 100) { + power_level->SetMarker((int) selected_source->Safety(), 0); + power_level->SetMarker((int) selected_source->Safety(), 1); + } + else { + power_level->SetMarker(-1, 0); + power_level->SetMarker(-1, 1); + } + + capacity->SetEnabled(true); + capacity->SetValue((int) selected_source->Charge()); + + if (selected_source->IsPowerOn()) { + if (power_on->GetButtonState() != 2) + power_on->SetButtonState(1); + if (power_off->GetButtonState() != 2) + power_off->SetButtonState(0); + } + else { + if (power_on->GetButtonState() != 2) + power_on->SetButtonState(0); + if (power_off->GetButtonState() != 2) + power_off->SetButtonState(1); + } + + if (components) { + for (int i = 0; i < components->NumItems(); i++) { + int index = components->GetItemData(i); + + Component* comp = selected_source->GetComponents()[index]; + + Text stat = "OK"; + Color c; + + switch (comp->Status()) { + case Component::DESTROYED: + case Component::CRITICAL: stat = "FAIL"; c = Color::BrightRed; break; + case Component::DEGRADED: stat = "WARN"; c = Color::Yellow; break; + case Component::NOMINAL: stat = "OK"; c = Color::White; break; + case Component::REPLACE: + case Component::REPAIR: stat = "MAINT"; c = Color::Cyan; break; + } + + stat = Game::GetText(Text("EngDlg.") + stat); + components->SetItemText(i, 1, stat); + components->SetItemData(i, 1, (int) comp->Status()); + + FormatNumber(num, comp->SpareCount()); + components->SetItemText(i, 2, num); + components->SetItemData(i, 2, comp->SpareCount()); + components->SetItemColor(i, c); + } + } + } + + else if (selected_clients.size() == 1) { + System* sink = selected_clients[0]; + + selected_name->SetText(Game::GetText(sink->Name())); + power_off->SetEnabled(true); + power_on->SetEnabled(true); + override->SetEnabled(true); + if (override->GetButtonState() != 2) + override->SetButtonState(sink->GetPowerLevel() > 100 ? 1 : 0); + power_level->SetEnabled(sink->IsPowerOn()); + power_level->SetValue((int) sink->GetPowerLevel()); + + if (sink->Safety() < 100) { + power_level->SetMarker((int) sink->Safety(), 0); + power_level->SetMarker((int) sink->Safety(), 1); + } + else { + power_level->SetMarker(-1, 0); + power_level->SetMarker(-1, 1); + } + + capacity->SetEnabled(true); + capacity->SetValue((int) sink->Charge()); + + if (sink->IsPowerOn()) { + if (power_on->GetButtonState() != 2) + power_on->SetButtonState(1); + if (power_off->GetButtonState() != 2) + power_off->SetButtonState(0); + } + else { + if (power_on->GetButtonState() != 2) + power_on->SetButtonState(0); + if (power_off->GetButtonState() != 2) + power_off->SetButtonState(1); + } + + if (components) { + for (int i = 0; i < components->NumItems(); i++) { + int index = components->GetItemData(i); + + Component* comp = sink->GetComponents()[index]; + + Text stat = "OK"; + Color c; + + switch (comp->Status()) { + case Component::DESTROYED: + case Component::CRITICAL: stat = "FAIL"; c = Color::BrightRed; break; + case Component::DEGRADED: stat = "WARN"; c = Color::Yellow; break; + case Component::NOMINAL: stat = "OK"; c = Color::White; break; + case Component::REPLACE: + case Component::REPAIR: stat = "MAINT"; c = Color::Cyan; break; + } + + stat = Game::GetText(Text("EngDlg.") + stat); + components->SetItemText(i, 1, stat); + components->SetItemData(i, 1, (int) comp->Status()); + + FormatNumber(num, comp->SpareCount()); + components->SetItemText(i, 2, num); + components->SetItemData(i, 2, comp->SpareCount()); + components->SetItemColor(i, c); + } + } + } + + else if (selected_clients.size() > 1) { + System* sink = selected_clients[0]; + + selected_name->SetText(Game::GetText("[Multiple]")); + power_off->SetEnabled(true); + power_on->SetEnabled(true); + override->SetEnabled(true); + if (override->GetButtonState() != 2) + override->SetButtonState(sink->GetPowerLevel() > 100 ? 1 : 0); + power_level->SetEnabled(true); + power_level->SetValue((int) sink->GetPowerLevel()); + + if (sink->Safety() < 100) { + power_level->SetMarker((int) sink->Safety(), 0); + power_level->SetMarker((int) sink->Safety(), 1); + } + else { + power_level->SetMarker(-1, 0); + power_level->SetMarker(-1, 1); + } + + capacity->SetEnabled(true); + capacity->SetValue((int) sink->Charge()); + + if (sink->IsPowerOn()) { + if (power_on->GetButtonState() != 2) + power_on->SetButtonState(1); + if (power_off->GetButtonState() != 2) + power_off->SetButtonState(0); + } + else { + if (power_on->GetButtonState() != 2) + power_on->SetButtonState(0); + if (power_off->GetButtonState() != 2) + power_off->SetButtonState(1); + } + } + + else { + selected_name->SetText(Game::GetText("No Selection")); + power_off->SetEnabled(false); + power_off->SetButtonState(0); + power_on->SetEnabled(false); + power_on->SetButtonState(0); + override->SetEnabled(false); + override->SetButtonState(0); + power_level->SetEnabled(false); + power_level->SetValue(0); + power_level->SetMarker(-1, 0); + power_level->SetMarker(-1, 1); + capacity->SetEnabled(false); + capacity->SetValue(0); + } + + // display the repair queue: + if (repair_queue) { + // if adding to the queue, dump and reload: + if (repair_queue->NumItems() < ship->RepairQueue().size()) { + repair_queue->ClearItems(); + + int i = 0; + ListIter iter = ship->RepairQueue(); + while (++iter) { + double time_remaining = 0; + char etr[20]; + + System* sys = iter.value(); + + ListIter comp = sys->GetComponents(); + while (++comp) { + Component* c = comp.value(); + if (c->TimeRemaining() > time_remaining) + time_remaining = c->TimeRemaining(); + } + + FormatTime(etr, (int) (time_remaining / ship->RepairSpeed())); + repair_queue->AddItem(Game::GetText(sys->Name())); + repair_queue->SetItemText(i, 1, etr); + i++; + } + } + + // otherwise, update in place: + else { + while (repair_queue->NumItems() > ship->RepairQueue().size()) + repair_queue->RemoveItem(0); + + bool found = false; + + for (int i = 0; i < repair_queue->NumItems(); i++) { + double time_remaining = 0; + char etr[20]; + + System* sys = ship->RepairQueue().at(i); + + ListIter comp = sys->GetComponents(); + while (++comp) { + Component* c = comp.value(); + if (c->TimeRemaining() > time_remaining) + time_remaining = c->TimeRemaining(); + } + + FormatTime(etr, (int) time_remaining); + repair_queue->SetItemText(i, sys->Name()); + repair_queue->SetItemText(i, 1, etr); + + if (sys == selected_repair) { + found = true; + + if (!Mouse::LButton()) + repair_queue->SetSelected(i); + } + } + + if (!found) + selected_repair = 0; + } + } + + if (auto_repair && auto_repair->GetButtonState() != 2) + auto_repair->SetButtonState(ship->AutoRepair() ? 1 : 0); +} + +// +--------------------------------------------------------------------+ + +void +EngDlg::OnSource(AWEvent* event) +{ + selected_source = 0; + selected_clients.clear(); + selected_component = 0; + + if (!ship) return; + + int source_index = -1; + + for (int i = 0; i < 4; i++) + if (event->window == sources[i]) + source_index = i; + + // found the source list: + if (source_index >= 0) { + selected_source = ship->Reactors()[source_index]; + } + + for (i = 0; i < 4; i++) { + clients[i]->ClearSelection(); + } + + if (components) { + components->ClearItems(); + + if (repair) repair->SetEnabled(false); + if (replace) replace->SetEnabled(false); + if (repair_time) repair_time->Hide(); + if (replace_time) replace_time->Hide(); + + if (selected_source && selected_source->GetComponents().size()) { + int index = 0; + ListIter comp = selected_source->GetComponents(); + while (++comp) + components->AddItemWithData(Game::GetText(comp->Abbreviation()), index++); + } + } +} + +void +EngDlg::OnClient(AWEvent* event) +{ + selected_source = 0; + selected_clients.clear(); + selected_component = 0; + + if (!ship) return; + + int source_index = -1; + + for (int i = 0; i < 4; i++) { + if (event->window == clients[i]) + source_index = i; + else + clients[i]->ClearSelection(); + } + + // found the source list: + if (source_index >= 0) { + // find the power source: + PowerSource* src = ship->Reactors()[source_index]; + + // build a list of the clients to be manipulated: + List& client_list = src->Clients(); + for (int i = 0; i < clients[source_index]->NumItems(); i++) { + if (clients[source_index]->IsSelected(i)) { + int index = clients[source_index]->GetItemData(i); + selected_clients.append(client_list[index]); + } + } + } + + if (components) { + components->ClearItems(); + + if (repair) repair->SetEnabled(false); + if (replace) replace->SetEnabled(false); + if (repair_time) repair_time->Hide(); + if (replace_time) replace_time->Hide(); + + if (selected_clients.size() == 1) { + System* sink = selected_clients[0]; + + if (sink->GetComponents().size()) { + int index = 0; + ListIter comp = sink->GetComponents(); + while (++comp) + components->AddItemWithData(Game::GetText(comp->Abbreviation()), index++); + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +EngDlg::OnRouteStart(AWEvent* event) +{ + if (!ship) return; + + int source_index = -1; + + for (int i = 0; i < 4; i++) + if (event->window == clients[i]) + source_index = i; + + // found the source list: + if (source_index >= 0) { + // remember the power source: + route_source = ship->Reactors()[source_index]; + route_list.clear(); + + // build a list of the clients to be moved: + List& rsc = route_source->Clients(); + for (int i = 0; i < clients[source_index]->NumItems(); i++) { + if (clients[source_index]->IsSelected(i)) { + int index = clients[source_index]->GetItemData(i); + route_list.append(rsc[index]); + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +EngDlg::OnRouteComplete(AWEvent* event) +{ + if (!ship || !route_source) return; + + int dest_index = -1; + + for (int i = 0; i < 4; i++) + if (event->window == clients[i]) + dest_index = i; + + // found the destination list, copy the clients over: + if (dest_index >= 0) { + PowerSource* route_dest = ship->Reactors()[dest_index]; + + if (!route_dest) + return; + + ListIter iter = route_list; + while (++iter) { + System* client = iter.value(); + + route_source->RemoveClient(client); + route_dest->AddClient(client); + } + } +} + +// +--------------------------------------------------------------------+ + +void +EngDlg::OnPowerOff(AWEvent* event) +{ + if (selected_source) { + selected_source->PowerOff(); + + power_off->SetButtonState(1); + power_on->SetButtonState(0); + override->SetButtonState(0); + } + + else if (selected_clients.size() > 0) { + ListIter iter = selected_clients; + while (++iter) + iter->PowerOff(); + + power_off->SetButtonState(1); + power_on->SetButtonState(0); + override->SetButtonState(0); + } +} + +void +EngDlg::OnPowerOn(AWEvent* event) +{ + if (selected_source) { + selected_source->PowerOn(); + + power_off->SetButtonState(0); + power_on->SetButtonState(1); + override->SetButtonState(0); + } + + else if (selected_clients.size() > 0) { + ListIter iter = selected_clients; + while (++iter) + iter->PowerOn(); + + power_off->SetButtonState(0); + power_on->SetButtonState(1); + override->SetButtonState(0); + } +} + +void +EngDlg::OnOverride(AWEvent* event) +{ + bool over = false; + + if (override->GetButtonState() > 0) + over = true; + + if (selected_source) { + selected_source->SetOverride(over); + } + + else if (selected_clients.size() > 0) { + ListIter iter = selected_clients; + while (++iter) + iter->SetOverride(over); + } + + if (over) { + power_off->SetButtonState(0); + power_on->SetButtonState(1); + } +} + +void +EngDlg::OnPowerLevel(AWEvent* event) +{ + int level = power_level->GetValue(); + + if (level < 0) + level = 0; + else if (level > 100) + level = 100; + + if (selected_source) + selected_source->SetPowerLevel(level); + + else if (selected_clients.size() > 0) { + ListIter iter = selected_clients; + while (++iter) + iter->SetPowerLevel(level); + } + + if (override) + override->SetButtonState(0); +} + +// +--------------------------------------------------------------------+ + +void +EngDlg::OnComponent(AWEvent* event) +{ + selected_component = 0; + + if (repair) repair->SetEnabled(false); + if (replace) replace->SetEnabled(false); + if (repair_time) repair_time->Hide(); + if (replace_time) replace_time->Hide(); + + // find index of selected component: + int component_index = -1; + + if (components) { + int n = components->GetSelection(); + if (n >= 0) + component_index = components->GetItemData(n); + } + + // now examine the component info: + if (component_index >= 0) { + repair->SetEnabled(true); + + if (selected_source) { + List& comps = selected_source->GetComponents(); + selected_component = comps[component_index]; + + if (repair_time) { + char t[32]; + int s = (int) (selected_component->RepairTime() / + ship->RepairSpeed()); + FormatTime(t, s); + repair_time->SetText(t); + repair_time->Show(); + } + + if (selected_component->SpareCount() > 0) { + if (replace) replace->SetEnabled(true); + + if (replace_time) { + char t[32]; + int s = (int) (selected_component->ReplaceTime() / + ship->RepairSpeed()); + FormatTime(t, s); + replace_time->SetText(t); + replace_time->Show(); + } + } + } + else if (selected_clients.size() > 0) { + List& comps = selected_clients[0]->GetComponents(); + selected_component = comps[component_index]; + + if (repair_time) { + char t[32]; + int s = (int) (selected_component->RepairTime() / + ship->RepairSpeed()); + FormatTime(t, s); + repair_time->SetText(t); + repair_time->Show(); + } + + if (selected_component->SpareCount() > 0) { + if (replace) replace->SetEnabled(true); + + if (replace_time) { + char t[32]; + int s = (int) (selected_component->ReplaceTime() / + ship->RepairSpeed()); + FormatTime(t, s); + replace_time->SetText(t); + replace_time->Show(); + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +EngDlg::OnAutoRepair(AWEvent* event) +{ + if (ship) + ship->EnableRepair(auto_repair->GetButtonState() > 0); +} + +// +--------------------------------------------------------------------+ + +void +EngDlg::OnRepair(AWEvent* event) +{ + if (selected_component) { + selected_component->Repair(); + + if (ship) + ship->RepairSystem(selected_component->GetSystem()); + } +} + +void +EngDlg::OnReplace(AWEvent* event) +{ + if (selected_component) { + selected_component->Replace(); + + if (ship) + ship->RepairSystem(selected_component->GetSystem()); + } +} + +void +EngDlg::OnQueue(AWEvent* event) +{ + selected_repair = 0; + + if (priority_increase) priority_increase->SetEnabled(false); + if (priority_decrease) priority_decrease->SetEnabled(false); + + if (repair_queue) { + int n = repair_queue->GetSelection(); + + if (n >= 0) + selected_repair = ship->RepairQueue().at(n); + } + + if (!selected_repair) + return; + + selected_clients.clear(); + selected_clients.append(selected_repair); + + if (components) { + components->ClearItems(); + + if (repair) repair->SetEnabled(false); + if (replace) replace->SetEnabled(false); + if (repair_time) repair_time->Hide(); + if (replace_time) replace_time->Hide(); + + if (selected_repair->GetComponents().size()) { + int index = 0; + ListIter comp = selected_repair->GetComponents(); + while (++comp) + components->AddItemWithData(Game::GetText(comp->Abbreviation()), index++); + } + } + + if (priority_increase) priority_increase->SetEnabled(true); + if (priority_decrease) priority_decrease->SetEnabled(true); +} + +void +EngDlg::OnPriorityIncrease(AWEvent* event) +{ + if (ship && repair_queue) + ship->IncreaseRepairPriority(repair_queue->GetSelection()); +} + +void +EngDlg::OnPriorityDecrease(AWEvent* event) +{ + if (ship && repair_queue) + ship->DecreaseRepairPriority(repair_queue->GetSelection()); +} + +// +--------------------------------------------------------------------+ + +void +EngDlg::OnClose(AWEvent* event) +{ + if (manager) + manager->CloseTopmost(); +} + + + diff --git a/Stars45/EngDlg.h b/Stars45/EngDlg.h new file mode 100644 index 0000000..0ca2737 --- /dev/null +++ b/Stars45/EngDlg.h @@ -0,0 +1,106 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: EngDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Engineering (Power/Maint) Dialog Active Window class +*/ + +#ifndef EngDlg_h +#define EngDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "Bitmap.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Font.h" + +// +--------------------------------------------------------------------+ + +class GameScreen; +class Ship; +class PowerSource; +class System; +class Component; + +class PowerClient; + +// +--------------------------------------------------------------------+ + +class EngDlg : public FormWindow +{ +public: + EngDlg(Screen* s, FormDef& def, GameScreen* mgr); + virtual ~EngDlg(); + + virtual void Show(); + virtual void Hide(); + virtual void RegisterControls(); + + // Operations: + virtual void OnSource(AWEvent* event); + virtual void OnClient(AWEvent* event); + virtual void OnRouteStart(AWEvent* event); + virtual void OnRouteComplete(AWEvent* event); + virtual void OnPowerOff(AWEvent* event); + virtual void OnPowerOn(AWEvent* event); + virtual void OnOverride(AWEvent* event); + virtual void OnPowerLevel(AWEvent* event); + virtual void OnComponent(AWEvent* event); + virtual void OnAutoRepair(AWEvent* event); + virtual void OnRepair(AWEvent* event); + virtual void OnReplace(AWEvent* event); + virtual void OnQueue(AWEvent* event); + virtual void OnPriorityIncrease(AWEvent* event); + virtual void OnPriorityDecrease(AWEvent* event); + virtual void OnClose(AWEvent* event); + + virtual void ExecFrame(); + void UpdateRouteTables(); + void UpdateSelection(); + void SetShip(Ship* s); + +protected: + Ship* ship; + GameScreen* manager; + + Button* close_btn; + Button* sources[4]; + Slider* source_levels[4]; + ListBox* clients[4]; + ListBox* components; + ListBox* repair_queue; + ActiveWindow* selected_name; + Button* power_off; + Button* power_on; + Button* override; + Slider* power_level; + Slider* capacity; + Button* auto_repair; + Button* repair; + Button* replace; + ActiveWindow* repair_time; + ActiveWindow* replace_time; + Button* priority_increase; + Button* priority_decrease; + + PowerSource* route_source; + List route_list; + + PowerSource* selected_source; + List selected_clients; + + System* selected_repair; + Component* selected_component; +}; + +#endif EngDlg_h + diff --git a/Stars45/ExceptionHandler.cpp b/Stars45/ExceptionHandler.cpp new file mode 100644 index 0000000..8946c26 --- /dev/null +++ b/Stars45/ExceptionHandler.cpp @@ -0,0 +1,429 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2005. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: ExceptionHandler.cpp + AUTHOR: John DiCamillo + +*/ + + +#include +#include + +extern void Print(const char* fmt, ...); + +// +--------------------------------------------------------------------+ + +class ExceptionHandler +{ +public: + ExceptionHandler(); + ~ExceptionHandler(); + +private: + static LPTOP_LEVEL_EXCEPTION_FILTER old_filter; + + static LONG WINAPI ExceptionFilter(EXCEPTION_POINTERS* info); + + static void PrintReport(EXCEPTION_POINTERS* info); + + // Helper functions + static const char* GetExceptionString(DWORD dwCode); + static BOOL GetLogicalAddress(VOID* addr, char* module, int len, + DWORD& section, DWORD& offset); + + static BOOL InitImageHelp(); + static void ImageStackTrace(CONTEXT* context); + static void IntelStackTrace(CONTEXT* context); + + + // Make typedefs for some IMAGEHLP.DLL functions so that we can use them + // with GetProcAddress + typedef BOOL (__stdcall * SYMINITIALIZEPROC)(HANDLE, LPSTR, BOOL); + typedef BOOL (__stdcall * SYMCLEANUPPROC)(HANDLE); + + typedef LPVOID (__stdcall *SYMFUNCTIONTABLEACCESSPROC)(HANDLE, DWORD); + typedef DWORD (__stdcall *SYMGETMODULEBASEPROC)(HANDLE, DWORD); + typedef BOOL (__stdcall *SYMGETSYMFROMADDRPROC)(HANDLE, DWORD, PDWORD, PIMAGEHLP_SYMBOL); + + typedef BOOL (__stdcall * STACKWALKPROC)(DWORD, + HANDLE, + HANDLE, + LPSTACKFRAME, + LPVOID, + PREAD_PROCESS_MEMORY_ROUTINE, + PFUNCTION_TABLE_ACCESS_ROUTINE, + PGET_MODULE_BASE_ROUTINE, + PTRANSLATE_ADDRESS_ROUTINE); + + static SYMINITIALIZEPROC SymInitialize; + static SYMCLEANUPPROC SymCleanup; + static STACKWALKPROC StackTrace; + static SYMFUNCTIONTABLEACCESSPROC SymFunctionTableAccess; + static SYMGETMODULEBASEPROC SymGetModuleBase; + static SYMGETSYMFROMADDRPROC SymGetSymFromAddr; +}; + +// +--------------------------------------------------------------------+ + +LPTOP_LEVEL_EXCEPTION_FILTER ExceptionHandler::old_filter = 0; + +ExceptionHandler::SYMINITIALIZEPROC ExceptionHandler::SymInitialize = 0; +ExceptionHandler::SYMCLEANUPPROC ExceptionHandler::SymCleanup = 0; +ExceptionHandler::STACKWALKPROC ExceptionHandler::StackTrace = 0; + +ExceptionHandler::SYMFUNCTIONTABLEACCESSPROC + ExceptionHandler::SymFunctionTableAccess = 0; + +ExceptionHandler::SYMGETMODULEBASEPROC + ExceptionHandler::SymGetModuleBase = 0; + +ExceptionHandler::SYMGETSYMFROMADDRPROC + ExceptionHandler::SymGetSymFromAddr = 0; + +ExceptionHandler global_exception_handler; + + +// +--------------------------------------------------------------------+ + +ExceptionHandler::ExceptionHandler() +{ + old_filter = SetUnhandledExceptionFilter(ExceptionFilter); +} + +ExceptionHandler::~ExceptionHandler() +{ + SetUnhandledExceptionFilter(old_filter); +} + +// +--------------------------------------------------------------------+ + +static bool in_filter = false; + +LONG WINAPI +ExceptionHandler::ExceptionFilter(EXCEPTION_POINTERS* info) +{ + if (in_filter) { + Print("\n\n*********************************************\n"); + Print("SECOND EXCEPTION CAUGHT: TERMINATING.\n"); + Print("*********************************************\n"); + } + + else { + in_filter = true; + PrintReport(info); + in_filter = false; + } + + if (old_filter) + return old_filter(info); + else + return EXCEPTION_CONTINUE_SEARCH; +} + +// +--------------------------------------------------------------------+ + +void +ExceptionHandler::PrintReport(EXCEPTION_POINTERS* info) +{ + EXCEPTION_RECORD* record = info->ExceptionRecord; + CONTEXT* context = info->ContextRecord; + DWORD code = record->ExceptionCode; + + Print("\n*********************************************\n"); + Print("FATAL EXCEPTION:\n"); + + Print("\nRegisters:\n"); + Print("EAX: %08x\n", context->Eax); + Print("EBX: %08x\n", context->Ebx); + Print("ECX: %08x\n", context->Ecx); + Print("EDX: %08x\n", context->Edx); + Print("EDI: %08x\n", context->Edi); + Print("ESI: %08x\n", context->Esi); + Print("EBP: %08x\n", context->Ebp); + Print("\n"); + Print("CS:EIP: %04x:%08x\n", context->SegCs, context->Eip); + Print("SS:ESP: %04x:%08x\n", context->SegSs, context->Esp); + Print("DS: %04x\n", context->SegDs); + Print("ES: %04x\n", context->SegEs); + Print("FS: %04x\n", context->SegFs); + Print("GS: %04x\n", context->SegGs); + Print("Flags: %08x\n", context->EFlags ); + Print("\n"); + + Print("Exception Code: %08x %s\n",code, GetExceptionString(code)); + Print("Exception Addr: %08x \n", record->ExceptionAddress); + + if (code == EXCEPTION_ACCESS_VIOLATION && record->NumberParameters >= 2) { + if (record->ExceptionInformation[0]) + Print(" Program attempted to WRITE to address 0x%08x\n", record->ExceptionInformation[1]); + else + Print(" Program attempted to READ from address 0x%08x\n", record->ExceptionInformation[1]); + } + + if (InitImageHelp()) { + ImageStackTrace(context); + SymCleanup(GetCurrentProcess()); + } + else { + IntelStackTrace(context); + } + + Print("\n*********************************************\nPROGRAM TERMINATED.\n"); +} + +// +--------------------------------------------------------------------+ + +const char* +ExceptionHandler::GetExceptionString(DWORD code) +{ + #define EXCEPTION( x ) case EXCEPTION_##x: return #x; + + switch (code) { + EXCEPTION( ACCESS_VIOLATION ) + EXCEPTION( DATATYPE_MISALIGNMENT ) + EXCEPTION( BREAKPOINT ) + EXCEPTION( SINGLE_STEP ) + EXCEPTION( ARRAY_BOUNDS_EXCEEDED ) + EXCEPTION( FLT_DENORMAL_OPERAND ) + EXCEPTION( FLT_DIVIDE_BY_ZERO ) + EXCEPTION( FLT_INEXACT_RESULT ) + EXCEPTION( FLT_INVALID_OPERATION ) + EXCEPTION( FLT_OVERFLOW ) + EXCEPTION( FLT_STACK_CHECK ) + EXCEPTION( FLT_UNDERFLOW ) + EXCEPTION( INT_DIVIDE_BY_ZERO ) + EXCEPTION( INT_OVERFLOW ) + EXCEPTION( PRIV_INSTRUCTION ) + EXCEPTION( IN_PAGE_ERROR ) + EXCEPTION( ILLEGAL_INSTRUCTION ) + EXCEPTION( NONCONTINUABLE_EXCEPTION ) + EXCEPTION( STACK_OVERFLOW ) + EXCEPTION( INVALID_DISPOSITION ) + EXCEPTION( GUARD_PAGE ) + EXCEPTION( INVALID_HANDLE ) + } + + static char buffer[512] = { 0 }; + + FormatMessage(FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE, + GetModuleHandle("NTDLL.DLL"), + code, 0, buffer, sizeof(buffer), 0 ); + + return buffer; +} + +// +--------------------------------------------------------------------+ + +BOOL +ExceptionHandler::GetLogicalAddress(void* addr, char* mod_name, int len, DWORD& section, DWORD& offset) +{ + MEMORY_BASIC_INFORMATION mbi; + + if (!VirtualQuery(addr, &mbi, sizeof(mbi))) + return FALSE; + + DWORD hMod = (DWORD)mbi.AllocationBase; + + if (!GetModuleFileName((HMODULE)hMod, mod_name, len)) + return FALSE; + + PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER) hMod; + PIMAGE_NT_HEADERS pNtHdr = (PIMAGE_NT_HEADERS)(hMod + pDosHdr->e_lfanew); + PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION( pNtHdr ); + + DWORD rva = (DWORD)addr - hMod; // RVA is offset from module load address + + // Iterate through the section table, looking for the one that encompasses + // the linear address. + for (unsigned i = 0; i < pNtHdr->FileHeader.NumberOfSections; i++, pSection++ ) { + DWORD sectionStart = pSection->VirtualAddress; + DWORD sectionEnd = sectionStart + + max(pSection->SizeOfRawData, pSection->Misc.VirtualSize); + + // Is the address in this section??? + if ((rva >= sectionStart) && (rva <= sectionEnd)) { + // Yes, address is in the section. Calculate section and offset, + // and store in the "section" & "offset" params, which were + // passed by reference. + section = i+1; + offset = rva - sectionStart; + return TRUE; + } + } + + return FALSE; // Should never get here! +} + +// +--------------------------------------------------------------------+ + +void +ExceptionHandler::IntelStackTrace(CONTEXT* context) +{ + Print("\nStack Trace (Intel):\n"); + Print("Address Frame Logical addr Module\n"); + + DWORD pc = context->Eip; + DWORD* pFrame; + DWORD* pPrevFrame; + + pFrame = (DWORD*)context->Ebp; + + do { + char mod_name[256] = { 0 }; + DWORD section = 0, offset = 0; + + GetLogicalAddress((void*)pc, mod_name, 256, section, offset); + + Print("%08X %08X %04X:%08X %s\n", + pc, pFrame, section, offset, mod_name); + + pc = pFrame[1]; + pPrevFrame = pFrame; + pFrame = (PDWORD)pFrame[0]; // proceed to next higher frame on stack + + if ((DWORD)pFrame & 3) // Frame pointer must be aligned on a + break; // DWORD boundary. Bail if not so. + + if (pFrame <= pPrevFrame) + break; + + // Can two DWORDs be read from the supposed frame address? + if (IsBadWritePtr(pFrame, sizeof(PVOID)*2)) + break; + + } + while ( 1 ); +} + +// +--------------------------------------------------------------------+ + +void ExceptionHandler::ImageStackTrace(CONTEXT* context) +{ + Print("\nStack Trace (Symbolic):\n"); + Print("Address Frame\n"); + + // Could use SymSetOptions here to add the SYMOPT_DEFERRED_LOADS flag + STACKFRAME sf; + memset(&sf, 0, sizeof(sf)); + + // Initialize the STACKFRAME structure for the first call. This is only + // necessary for Intel CPUs, and isn't mentioned in the documentation. + sf.AddrPC.Offset = context->Eip; + sf.AddrPC.Mode = AddrModeFlat; + sf.AddrStack.Offset = context->Esp; + sf.AddrStack.Mode = AddrModeFlat; + sf.AddrFrame.Offset = context->Ebp; + sf.AddrFrame.Mode = AddrModeFlat; + + while ( 1 ) { + if (!StackTrace( IMAGE_FILE_MACHINE_I386, + GetCurrentProcess(), + GetCurrentThread(), + &sf, + context, + 0, + SymFunctionTableAccess, + SymGetModuleBase, + 0)) + break; + + if (sf.AddrFrame.Offset == 0) // Basic sanity check to make sure + break; // the frame is OK. Bail if not. + + Print("%08x %08x ", sf.AddrPC.Offset, sf.AddrFrame.Offset); + + // IMAGEHLP is wacky, and requires you to pass in a pointer to an + // IMAGEHLP_SYMBOL structure. The problem is that this structure is + // variable length. That is, you determine how big the structure is + // at runtime. This means that you can't use sizeof(struct). + // So...make a buffer that's big enough, and make a pointer + // to the buffer. We also need to initialize not one, but TWO + // members of the structure before it can be used. + + BYTE symbolBuffer[sizeof(IMAGEHLP_SYMBOL) + 512]; + PIMAGEHLP_SYMBOL pSymbol = (PIMAGEHLP_SYMBOL)symbolBuffer; + pSymbol->SizeOfStruct = sizeof(symbolBuffer); + pSymbol->MaxNameLength = 512; + + DWORD symDisplacement = 0; // Displacement of the input address, + // relative to the start of the symbol + + if (SymGetSymFromAddr(GetCurrentProcess(), sf.AddrPC.Offset, + &symDisplacement, pSymbol)) { + Print("%-40s [%04X]\n", pSymbol->Name, symDisplacement); + } + else { + char mod_name[256] = { 0 }; + DWORD section = 0, offset = 0; + + GetLogicalAddress((PVOID)sf.AddrPC.Offset, + mod_name, 256, section, offset ); + + Print("%04X:%08X %s\n", section, offset, mod_name); + } + } +} + +// +--------------------------------------------------------------------+ + +BOOL +ExceptionHandler::InitImageHelp() +{ + Print("\n"); + + HMODULE h = LoadLibrary("IMAGEHLP.DLL"); + if (!h) { + Print("--- could not load IMAGEHLP.DLL (%08x) ---\n", GetLastError()); + return FALSE; + } + + SymInitialize = (SYMINITIALIZEPROC) GetProcAddress(h, "SymInitialize"); + if (!SymInitialize) { + Print("--- could not find SymInitialize ---\n"); + return FALSE; + } + + SymCleanup = (SYMCLEANUPPROC) GetProcAddress(h, "SymCleanup"); + if (!SymCleanup) { + Print("--- could not find SymCleanup ---\n"); + return FALSE; + } + + StackTrace = (STACKWALKPROC) GetProcAddress(h, "StackWalk"); + if (!StackTrace) { + Print("--- could not find StackWalk ---\n"); + return FALSE; + } + + SymFunctionTableAccess = (SYMFUNCTIONTABLEACCESSPROC) + GetProcAddress(h, "SymFunctionTableAccess"); + + if (!SymFunctionTableAccess) { + Print("--- could not find SymFunctionTableAccess ---\n"); + return FALSE; + } + + SymGetModuleBase = (SYMGETMODULEBASEPROC) GetProcAddress(h, "SymGetModuleBase"); + if (!SymGetModuleBase) { + Print("--- could not find SymGetModuleBase ---\n"); + return FALSE; + } + + SymGetSymFromAddr = (SYMGETSYMFROMADDRPROC) GetProcAddress(h, "SymGetSymFromAddr"); + if (!SymGetSymFromAddr) { + Print("--- could not find SymGetSymFromAddr ---\n"); + return FALSE; + } + + if (!SymInitialize(GetCurrentProcess(), 0, TRUE)) { + Print("--- could not Initialize IMAGEHLP.DLL (%08x) ---\n", GetLastError()); + return FALSE; + } + + Print("Loaded IMAGEHLP.DLL\n"); + return TRUE; +} + diff --git a/Stars45/ExitDlg.cpp b/Stars45/ExitDlg.cpp new file mode 100644 index 0000000..189928a --- /dev/null +++ b/Stars45/ExitDlg.cpp @@ -0,0 +1,152 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: ExitDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== +*/ + +#include "MemDebug.h" +#include "ExitDlg.h" +#include "MenuScreen.h" +#include "MusicDirector.h" +#include "Starshatter.h" +#include "FormatUtil.h" + +#include "Game.h" +#include "Keyboard.h" +#include "Button.h" +#include "RichTextBox.h" +#include "DataLoader.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +DEF_MAP_CLIENT(ExitDlg, OnApply); +DEF_MAP_CLIENT(ExitDlg, OnCancel); + +// +--------------------------------------------------------------------+ + +ExitDlg::ExitDlg(Screen* s, FormDef& def, MenuScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), + manager(mgr), exit_latch(false), + credits(0), apply(0), cancel(0), + def_rect(def.GetRect()) +{ + Init(def); +} + +ExitDlg::~ExitDlg() +{ +} + +void +ExitDlg::RegisterControls() +{ + if (apply) + return; + + credits = (RichTextBox*) FindControl(201); + + apply = (Button*) FindControl(1); + REGISTER_CLIENT(EID_CLICK, apply, ExitDlg, OnApply); + + cancel = (Button*) FindControl(2); + REGISTER_CLIENT(EID_CLICK, cancel, ExitDlg, OnCancel); +} + +// +--------------------------------------------------------------------+ + +void +ExitDlg::ExecFrame() +{ + if (credits && credits->GetLineCount() > 0) { + credits->SmoothScroll(ScrollWindow::SCROLL_DOWN, Game::GUITime()); + + if (credits->GetTopIndex() >= credits->GetLineCount()-1) { + credits->ScrollTo(0); + } + } + + if (Keyboard::KeyDown(VK_RETURN)) { + OnApply(0); + } + + if (Keyboard::KeyDown(VK_ESCAPE)) { + if (!exit_latch) + OnCancel(0); + } + else { + exit_latch = false; + } +} + +// +--------------------------------------------------------------------+ + +void +ExitDlg::Show() +{ + if (!IsShown()) { + Rect r = def_rect; + + if (r.w > screen->Width()) { + int extra = r.w - screen->Width(); + r.w -= extra; + } + + if (r.h > screen->Height()) { + int extra = r.h - screen->Height(); + r.h -= extra; + } + + r.x = (screen->Width() - r.w) / 2; + r.y = (screen->Height() - r.h) / 2; + + MoveTo(r); + + exit_latch = true; + Button::PlaySound(Button::SND_CONFIRM); + MusicDirector::SetMode(MusicDirector::CREDITS); + + DataLoader* loader = DataLoader::GetLoader(); + BYTE* block = 0; + + loader->SetDataPath(0); + loader->LoadBuffer("credits.txt", block, true); + + if (block && credits) { + credits->SetText((const char*) block); + } + + loader->ReleaseBuffer(block); + } + + FormWindow::Show(); +} + +// +--------------------------------------------------------------------+ + +void +ExitDlg::OnApply(AWEvent* event) +{ + Starshatter* stars = Starshatter::GetInstance(); + + if (stars) { + ::Print("Exit Confirmed.\n"); + stars->Exit(); + } +} + +void +ExitDlg::OnCancel(AWEvent* event) +{ + manager->ShowMenuDlg(); + MusicDirector::SetMode(MusicDirector::MENU); +} + +// +--------------------------------------------------------------------+ diff --git a/Stars45/ExitDlg.h b/Stars45/ExitDlg.h new file mode 100644 index 0000000..63227a0 --- /dev/null +++ b/Stars45/ExitDlg.h @@ -0,0 +1,60 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: ExitDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Navigation Active Window class +*/ + +#ifndef ExitDlg_h +#define ExitDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "Bitmap.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Font.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class MenuScreen; + +// +--------------------------------------------------------------------+ + +class ExitDlg : public FormWindow +{ +public: + ExitDlg(Screen* s, FormDef& def, MenuScreen* mgr); + virtual ~ExitDlg(); + + virtual void RegisterControls(); + virtual void Show(); + + // Operations: + virtual void ExecFrame(); + + virtual void OnApply(AWEvent* event); + virtual void OnCancel(AWEvent* event); + +protected: + MenuScreen* manager; + + RichTextBox* credits; + Button* apply; + Button* cancel; + Rect def_rect; + + bool exit_latch; +}; + +#endif ExitDlg_h + diff --git a/Stars45/Explosion.cpp b/Stars45/Explosion.cpp new file mode 100644 index 0000000..ec21c36 --- /dev/null +++ b/Stars45/Explosion.cpp @@ -0,0 +1,611 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Explos.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Explosion Sprite animation class +*/ + +#include "MemDebug.h" +#include "Explosion.h" +#include "QuantumFlash.h" +#include "Particles.h" +#include "Ship.h" +#include "Sim.h" +#include "CameraDirector.h" +#include "AudioConfig.h" + +#include "Light.h" +#include "Sprite.h" +#include "Solid.h" +#include "Bitmap.h" +#include "DataLoader.h" +#include "Game.h" +#include "Scene.h" +#include "ParseUtil.h" + +// +--------------------------------------------------------------------+ + +const int MAX_EXPLOSION_TYPES = 32; + +static float lifetimes[MAX_EXPLOSION_TYPES]; +static float scales[MAX_EXPLOSION_TYPES]; +static Bitmap* bitmaps[MAX_EXPLOSION_TYPES]; +static Bitmap* particle_bitmaps[MAX_EXPLOSION_TYPES]; +static int part_frames[MAX_EXPLOSION_TYPES]; +static int lengths[MAX_EXPLOSION_TYPES]; +static float light_levels[MAX_EXPLOSION_TYPES]; +static float light_decays[MAX_EXPLOSION_TYPES]; +static Color light_colors[MAX_EXPLOSION_TYPES]; +static int num_parts[MAX_EXPLOSION_TYPES]; +static int part_types[MAX_EXPLOSION_TYPES]; +static float part_speeds[MAX_EXPLOSION_TYPES]; +static float part_drags[MAX_EXPLOSION_TYPES]; +static float part_scales[MAX_EXPLOSION_TYPES]; +static float part_blooms[MAX_EXPLOSION_TYPES]; +static float part_rates[MAX_EXPLOSION_TYPES]; +static float part_decays[MAX_EXPLOSION_TYPES]; +static bool part_trails[MAX_EXPLOSION_TYPES]; +static int part_alphas[MAX_EXPLOSION_TYPES]; +static Sound* sounds[MAX_EXPLOSION_TYPES]; +static bool recycles[MAX_EXPLOSION_TYPES]; + +// +--------------------------------------------------------------------+ + +Explosion::Explosion(int t, const Vec3& pos, const Vec3& vel, + float exp_scale, float part_scale, + SimRegion* rgn, SimObject* src) + : SimObject("Explosion", t), type(t), particles(0), source(src) +{ + Observe(source); + + MoveTo(pos); + velocity = vel; + drag = 0.3f; + rep = 0; + light = 0; + life = 0; + + if (type == QUANTUM_FLASH) { + life = 1.1; + + QuantumFlash* q = new(__FILE__,__LINE__) QuantumFlash(); + rep = q; + + light = new(__FILE__,__LINE__) Light(1e9, 0.66f); + light->SetColor(Color(180, 200, 255)); + } + + else if (type >= 0 && type < MAX_EXPLOSION_TYPES) { + life = lifetimes[type]; + + if (source) { + Matrix src_orient = source->Cam().Orientation(); + src_orient.Transpose(); + mount_rel = (pos - source->Location()) * src_orient; + } + + if (lengths[type] > 0) { + int repeat = (lengths[type] == 1); + Sprite* s = new(__FILE__,__LINE__) Sprite(bitmaps[type], lengths[type], repeat); + s->Scale(exp_scale * scales[type]); + s->SetAngle(PI * rand()/16384.0); + s->SetLuminous(true); + rep = s; + } + + if (light_levels[type] > 0) { + light = new(__FILE__,__LINE__) Light(light_levels[type], light_decays[type]); + light->SetColor(light_colors[type]); + } + + if (num_parts[type] > 0) { + particles = new(__FILE__,__LINE__) Particles(particle_bitmaps[type], + num_parts[type], + pos, + Vec3(0.0f, 0.0f, 0.0f), + part_speeds[type] * part_scale, + part_drags[type], + part_scales[type] * part_scale, + part_blooms[type] * part_scale, + part_decays[type], + part_rates[type], + recycles[type], + part_trails[type], + (rgn && rgn->IsAirSpace()), + part_alphas[type], + part_frames[type]); + } + } + + if (rep) + rep->MoveTo(pos); + + if (light) + light->MoveTo(pos); +} + +// +--------------------------------------------------------------------+ + +Explosion::~Explosion() +{ + GRAPHIC_DESTROY(particles); +} + +// +--------------------------------------------------------------------+ + +bool +Explosion::Update(SimObject* obj) +{ + if (obj == source) { + source = 0; + + if (life < 0 || life > 4) + life = 4; + + if (particles) + particles->StopEmitting(); + } + + return SimObserver::Update(obj); +} + +const char* +Explosion::GetObserverName() const +{ + static char name[128]; + if (source) + sprintf(name, "Explosion(%s)", source->Name()); + else + sprintf(name, "Explosion"); + return name; +} + +// +--------------------------------------------------------------------+ + +void +Explosion::Initialize() +{ + static int initialized = 0; + if (initialized) return; + + ZeroMemory(lifetimes, sizeof(lifetimes)); + ZeroMemory(scales, sizeof(scales)); + ZeroMemory(bitmaps, sizeof(bitmaps)); + ZeroMemory(particle_bitmaps, sizeof(particle_bitmaps)); + ZeroMemory(lengths, sizeof(lengths)); + ZeroMemory(light_levels, sizeof(light_levels)); + ZeroMemory(light_decays, sizeof(light_decays)); + ZeroMemory(light_colors, sizeof(light_colors)); + ZeroMemory(num_parts, sizeof(num_parts)); + ZeroMemory(part_types, sizeof(part_types)); + ZeroMemory(part_speeds, sizeof(part_speeds)); + ZeroMemory(part_drags, sizeof(part_drags)); + ZeroMemory(part_scales, sizeof(part_scales)); + ZeroMemory(part_blooms, sizeof(part_blooms)); + ZeroMemory(part_decays, sizeof(part_decays)); + ZeroMemory(part_rates, sizeof(part_rates)); + ZeroMemory(part_trails, sizeof(part_trails)); + ZeroMemory(part_alphas, sizeof(part_alphas)); + ZeroMemory(sounds, sizeof(sounds)); + ZeroMemory(recycles, sizeof(recycles)); + + const char* filename = "Explosions.def"; + Print("Loading Explosion Defs '%s'\n", filename); + + // Load Design File: + DataLoader* loader = DataLoader::GetLoader(); + BYTE* block; + + loader->SetDataPath("Explosions/"); + int blocklen = loader->LoadBuffer(filename, block, true); + Parser parser(new(__FILE__,__LINE__) BlockReader((const char*) block, blocklen)); + Term* term = parser.ParseTerm(); + + if (!term) { + Print("ERROR: could not parse '%s'\n", filename); + exit(-3); + } + else { + TermText* file_type = term->isText(); + if (!file_type || file_type->value() != "EXPLOSION") { + Print("ERROR: invalid explosion def file '%s'\n", filename); + exit(-4); + } + } + + do { + delete term; + + term = parser.ParseTerm(); + + if (term) { + TermDef* def = term->isDef(); + if (def) { + if (def->name()->value() == "explosion") { + + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: explosion structure missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + int type = -1; + char type_name[32]; + char bitmap[32]; + char particle_bitmap[32]; + char sound_file[32]; + float lifetime = 0.0f; + float scale = 1.0f; + int length = 0; + float light_level = 0.0f; + float light_decay = 1.0f; + Color light_color; + int num_part = 0; + int part_type = 0; + float part_speed = 0.0f; + float part_drag = 1.0f; + float part_scale = 1.0f; + float part_bloom = 0.0f; + float part_decay = 100.0f; + float part_rate = 1.0f; + bool part_trail = true; + bool continuous = false; + int part_alpha = 4; + int part_nframes = 1; + float sound_min_dist = 1.0f; + float sound_max_dist = 1.0e5f; + + type_name[0] = 0; + bitmap[0] = 0; + particle_bitmap[0] = 0; + sound_file[0] = 0; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + + if (pdef->name()->value() == "type") { + if (pdef->term()->isText()) { + GetDefText(type_name, pdef, filename); + + const char* names[15] = { + "SHIELD_FLASH", + "HULL_FLASH", + "BEAM_FLASH", + "SHOT_BLAST", + "HULL_BURST", + "HULL_FIRE", + "PLASMA_LEAK", + "SMOKE_TRAIL", + "SMALL_FIRE", + "SMALL_EXPLOSION", + "LARGE_EXPLOSION", + "LARGE_BURST", + "NUKE_EXPLOSION", + "QUANTUM_FLASH", + "HYPER_FLASH" + }; + + for (int n = 0; n < 15; n++) + if (!stricmp(type_name, names[n])) + type = n + 1; + } + + else if (pdef->term()->isNumber()) { + GetDefNumber(type, pdef, filename); + + if (type < 0 || type >= MAX_EXPLOSION_TYPES) { + ::Print("Warning - invalid explosion type %d ignored\n", type); + } + } + + else { + ::Print("Warning - weird explosion type:\n"); + pdef->print(); + } + } + + else if (pdef->name()->value() == "image" || + pdef->name()->value() == "bitmap") + GetDefText(bitmap, pdef, filename); + + else if (pdef->name()->value() == "particles" || + pdef->name()->value() == "particle_bitmap") + GetDefText(particle_bitmap, pdef, filename); + + else if (pdef->name()->value() == "sound") + GetDefText(sound_file, pdef, filename); + + else if (pdef->name()->value() == "lifetime") + GetDefNumber(lifetime, pdef, filename); + + else if (pdef->name()->value() == "scale") + GetDefNumber(scale, pdef, filename); + + else if (pdef->name()->value() == "length") + GetDefNumber(length, pdef, filename); + + else if (pdef->name()->value() == "light_level") + GetDefNumber(light_level, pdef, filename); + + else if (pdef->name()->value() == "light_decay") + GetDefNumber(light_decay, pdef, filename); + + else if (pdef->name()->value() == "light_color") + GetDefColor(light_color, pdef, filename); + + else if (pdef->name()->value() == "num_parts") + GetDefNumber(num_part, pdef, filename); + + else if (pdef->name()->value() == "part_frames") + GetDefNumber(part_nframes, pdef, filename); + + else if (pdef->name()->value() == "part_type") + GetDefNumber(part_type, pdef, filename); + + else if (pdef->name()->value() == "part_speed") + GetDefNumber(part_speed, pdef, filename); + + else if (pdef->name()->value() == "part_drag") + GetDefNumber(part_drag, pdef, filename); + + else if (pdef->name()->value() == "part_scale") + GetDefNumber(part_scale, pdef, filename); + + else if (pdef->name()->value() == "part_bloom") + GetDefNumber(part_bloom, pdef, filename); + + else if (pdef->name()->value() == "part_decay") + GetDefNumber(part_decay, pdef, filename); + + else if (pdef->name()->value() == "part_rate") + GetDefNumber(part_rate, pdef, filename); + + else if (pdef->name()->value() == "part_trail") + GetDefBool(part_trail, pdef, filename); + + else if (pdef->name()->value() == "part_alpha") + GetDefNumber(part_alpha, pdef, filename); + + else if (pdef->name()->value() == "continuous") + GetDefBool(continuous, pdef, filename); + + else if (pdef->name()->value() == "sound_min_dist") + GetDefNumber(sound_min_dist, pdef, filename); + + else if (pdef->name()->value() == "sound_max_dist") + GetDefNumber(sound_max_dist, pdef, filename); + + else { + Print("WARNING: parameter '%s' ignored in '%s'\n", + pdef->name()->value().data(), filename); + } + } + else { + Print("WARNING: term ignored in '%s'\n", filename); + val->elements()->at(i)->print(); + } + } + + if (type >= 0 && type < MAX_EXPLOSION_TYPES) { + if (part_alpha > 2) + part_alpha = 4; + + lengths[type] = length; + lifetimes[type] = lifetime; + scales[type] = scale; + light_levels[type] = light_level; + light_decays[type] = light_decay; + light_colors[type] = light_color; + num_parts[type] = num_part; + part_types[type] = part_type; + part_speeds[type] = part_speed; + part_drags[type] = part_drag; + part_scales[type] = part_scale; + part_blooms[type] = part_bloom; + part_frames[type] = part_nframes; + part_decays[type] = part_decay; + part_rates[type] = part_rate; + part_trails[type] = part_trail; + part_alphas[type] = part_alpha; + recycles[type] = continuous; + + if (length > 0) { + bitmaps[type] = new(__FILE__,__LINE__) Bitmap[length]; + + if (length > 1) { + for (int n = 0; n < length; n++) { + char img_name[64]; + sprintf(img_name, "%s%02d.pcx", bitmap, n); + loader->LoadBitmap(img_name, bitmaps[type][n], Bitmap::BMP_TRANSLUCENT); + } + } + + else { + loader->LoadBitmap(bitmap, bitmaps[type][0], Bitmap::BMP_TRANSLUCENT); + } + } + + if (particle_bitmap[0]) { + particle_bitmaps[type] = new(__FILE__,__LINE__) Bitmap[part_nframes]; + + if (part_nframes > 1) { + for (int i = 0; i < part_nframes; i++) { + char fname[64]; + sprintf(fname, "%s%02d.pcx", particle_bitmap, i); + loader->LoadBitmap(fname, particle_bitmaps[type][i], Bitmap::BMP_TRANSLUCENT); + particle_bitmaps[type][i].MakeTexture(); + } + } + + else { + loader->LoadBitmap(particle_bitmap, particle_bitmaps[type][0], Bitmap::BMP_TRANSLUCENT); + particle_bitmaps[type][0].MakeTexture(); + } + } + + if (sound_file[0]) { + loader->SetDataPath("Sounds/"); + loader->LoadSound(sound_file, sounds[type], Sound::LOCALIZED | Sound::LOC_3D); + loader->SetDataPath("Explosions/"); + + if (sounds[type]) { + sounds[type]->SetMinDistance(sound_min_dist); + sounds[type]->SetMaxDistance(sound_max_dist); + } + } + } + } + } + + else + Print("WARNING: unknown definition '%s' in '%s'\n", + def->name()->value().data(), filename); + } + else { + Print("WARNING: term ignored in '%s'\n", filename); + term->print(); + } + } + } + while (term); + + loader->ReleaseBuffer(block); + loader->SetDataPath(0); + initialized = 1; +} + +// +--------------------------------------------------------------------+ + +void +Explosion::Close() +{ + for (int t = 0; t < MAX_EXPLOSION_TYPES; t++) { + if (lengths[t] && bitmaps[t]) { + for (int i = 0; i < lengths[t]; i++) + bitmaps[t][i].ClearImage(); + delete [] bitmaps[t]; + } + + if (particle_bitmaps[t]) { + for (int i = 0; i < part_frames[t]; i++) + particle_bitmaps[t][i].ClearImage(); + delete [] particle_bitmaps[t]; + } + + delete sounds[t]; + } +} + +// +--------------------------------------------------------------------+ + +void +Explosion::ExecFrame(double seconds) +{ + if (source) { + const Matrix& orientation = source->Cam().Orientation(); + MoveTo((mount_rel * orientation) + source->Location()); + + if (rep) rep->Show(); + + if (particles) particles->Show(); + + if (source == Sim::GetSim()->GetPlayerShip()) { + Ship* ship = (Ship*) source; + if (CameraDirector::GetCameraMode() == CameraDirector::MODE_COCKPIT && + !ship->IsDying()) { + if (rep) rep->Hide(); + if (particles) particles->Hide(); + } + } + } + + life -= seconds; + + if (rep) { + rep->MoveTo(Location()); + + if (rep->Life() == 0) { + rep = 0; // about to be GC'd + } + + else if (rep->IsSprite()) { + Sprite* s = (Sprite*) rep; + s->SetAngle(s->Angle() + seconds * 0.5); + } + + else if (type == QUANTUM_FLASH) { + QuantumFlash* q = (QuantumFlash*) rep; + q->SetShade(q->Shade() - seconds); + } + } + + if (light) { + light->Update(); + + if (light->Life() == 0) + light = 0; // about to be GC'd + } + + if (particles) { + particles->MoveTo(Location()); + particles->ExecFrame(seconds); + } + + if (source && source->Life() == 0) + life = 0; +} + +// +--------------------------------------------------------------------+ + +void +Explosion::Activate(Scene& scene) +{ + bool filter = false; + + CameraDirector* cam_dir = CameraDirector::GetInstance(); + if (cam_dir && cam_dir->GetCamera()) { + if (Point(cam_dir->GetCamera()->Pos() - Location()).length() < 100) + filter = true; + } + + if (rep && !filter) + scene.AddGraphic(rep); + + if (light) + scene.AddLight(light); + + if (particles && !filter) + scene.AddGraphic(particles); + + if (sounds[obj_type]) { + Sound* sound = sounds[obj_type]->Duplicate(); + + // fire and forget: + if (sound) { + sound->SetLocation(Location()); + sound->SetVolume(AudioConfig::EfxVolume()); + sound->Play(); + } + } + + active = true; +} + +void +Explosion::Deactivate(Scene& scene) +{ + SimObject::Deactivate(scene); + + if (particles) + scene.DelGraphic(particles); +} + +// +--------------------------------------------------------------------+ + diff --git a/Stars45/Explosion.h b/Stars45/Explosion.h new file mode 100644 index 0000000..baab278 --- /dev/null +++ b/Stars45/Explosion.h @@ -0,0 +1,84 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Explosion.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Explosion Sprite class +*/ + +#ifndef Explosion_h +#define Explosion_h + +#include "Types.h" +#include "Geometry.h" +#include "SimObject.h" +#include "Sound.h" + +// +--------------------------------------------------------------------+ + +class Solid; +class Particles; +class System; + +// +--------------------------------------------------------------------+ + +class Explosion : public SimObject, + public SimObserver +{ +public: + static const char* TYPENAME() { return "Explosion"; } + + enum Type { SHIELD_FLASH = 1, + HULL_FLASH = 2, + BEAM_FLASH = 3, + SHOT_BLAST = 4, + HULL_BURST = 5, + HULL_FIRE = 6, + PLASMA_LEAK = 7, + SMOKE_TRAIL = 8, + SMALL_FIRE = 9, + SMALL_EXPLOSION = 10, + LARGE_EXPLOSION = 11, + LARGE_BURST = 12, + NUKE_EXPLOSION = 13, + QUANTUM_FLASH = 14, + HYPER_FLASH = 15 + }; + + Explosion(int type, const Vec3& pos, const Vec3& vel, + float exp_scale, float part_scale, + SimRegion* rgn=0, SimObject* source=0); + virtual ~Explosion(); + + static void Initialize(); + static void Close(); + + virtual void ExecFrame(double seconds); + Particles* GetParticles() { return particles; } + + virtual void Activate(Scene& scene); + virtual void Deactivate(Scene& scene); + + virtual bool Update(SimObject* obj); + virtual const char* GetObserverName() const; + +protected: + int type; + Particles* particles; + + float scale; + float scale1; + float scale2; + + SimObject* source; + Point mount_rel; +}; + +#endif Explosion_h + diff --git a/Stars45/Farcaster.cpp b/Stars45/Farcaster.cpp new file mode 100644 index 0000000..b0777ff --- /dev/null +++ b/Stars45/Farcaster.cpp @@ -0,0 +1,288 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Farcaster.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== +*/ + +#include "MemDebug.h" +#include "Farcaster.h" +#include "QuantumDrive.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "Explosion.h" +#include "Sim.h" +#include "Element.h" +#include "Instruction.h" + +#include "Game.h" +#include "Solid.h" +#include "Light.h" +#include "Sound.h" +#include "DataLoader.h" + +// +======================================================================+ + +Farcaster::Farcaster(double cap, double rate) + : System(FARCASTER, 0, "Farcaster", 1, (float) cap, (float) cap, (float) rate), + ship(0), dest(0), jumpship(0), cycle_time(10), + active_state(QuantumDrive::ACTIVE_READY), warp_fov(1), no_dest(false) +{ + name = Game::GetText("sys.farcaster"); + abrv = Game::GetText("sys.farcaster.abrv"); +} + +// +----------------------------------------------------------------------+ + +Farcaster::Farcaster(const Farcaster& s) + : System(s), + ship(0), dest(0), start_rel(s.start_rel), + end_rel(s.end_rel), jumpship(0), cycle_time(s.cycle_time), + active_state(QuantumDrive::ACTIVE_READY), warp_fov(1), no_dest(false) +{ + Mount(s); + SetAbbreviation(s.Abbreviation()); + + for (int i = 0; i < NUM_APPROACH_PTS; i++) + approach_rel[i] = s.approach_rel[i]; +} + +// +--------------------------------------------------------------------+ + +Farcaster::~Farcaster() +{ +} + +// +--------------------------------------------------------------------+ + +void +Farcaster::ExecFrame(double seconds) +{ + System::ExecFrame(seconds); + + if (ship && !no_dest) { + if (!dest) { + Element* elem = ship->GetElement(); + + if (elem->NumObjectives()) { + Sim* sim = Sim::GetSim(); + Instruction* obj = elem->GetObjective(0); + + if (obj) + dest = sim->FindShip(obj->TargetName()); + } + + if (!dest) + no_dest = true; + } + else { + if (dest->IsDying() || dest->IsDead()) { + dest = 0; + no_dest = true; + } + } + } + + // if no destination, show red nav lights: + if (no_dest) + energy = 0.0f; + + if (active_state == QuantumDrive::ACTIVE_READY && energy >= capacity && + ship && ship->GetRegion() && dest && dest->GetRegion()) { + SimRegion* rgn = ship->GetRegion(); + SimRegion* dst = dest->GetRegion(); + ListIter s_iter = rgn->Ships(); + + jumpship = 0; + + while (++s_iter) { + Ship* s = s_iter.value(); + + if (s == ship || s->IsStatic() || s->WarpFactor() > 1) + continue; + + Point delta = s->Location() - ship->Location(); + + // activate: + if (delta.length() < 1000) { + active_state = QuantumDrive::ACTIVE_PREWARP; + jumpship = s; + Observe(jumpship); + break; + } + } + } + + if (active_state == QuantumDrive::ACTIVE_READY) + return; + + if (ship) { + bool warping = false; + + if (active_state == QuantumDrive::ACTIVE_PREWARP) { + if (warp_fov < 5000) { + warp_fov *= 1.5; + } + else { + Jump(); + } + + warping = true; + } + + else if (active_state == QuantumDrive::ACTIVE_POSTWARP) { + if (warp_fov > 1) { + warp_fov *= 0.75; + } + else { + warp_fov = 1; + active_state = QuantumDrive::ACTIVE_READY; + } + + warping = true; + } + + if (jumpship) { + if (warping) { + jumpship->SetWarp(warp_fov); + + SimRegion* r = ship->GetRegion(); + ListIter neighbor = r->Ships(); + + while (++neighbor) { + if (neighbor->IsDropship()) { + Ship* s = neighbor.value(); + Point delta = s->Location() - ship->Location(); + + if (delta.length() < 5e3) + s->SetWarp(warp_fov); + } + } + } + else { + warp_fov = 1; + jumpship->SetWarp(warp_fov); + } + } + } +} + +void +Farcaster::Jump() +{ + Sim* sim = Sim::GetSim(); + SimRegion* rgn = ship->GetRegion(); + SimRegion* dst = dest->GetRegion(); + + sim->CreateExplosion(jumpship->Location(), Point(0,0,0), + Explosion::QUANTUM_FLASH, 1.0f, 0, rgn); + sim->RequestHyperJump(jumpship, dst, dest->Location().OtherHand(), 0, ship, dest); + + energy = 0.0f; + + Farcaster* f = dest->GetFarcaster(); + if (f) f->Arrive(jumpship); + + active_state = QuantumDrive::ACTIVE_READY; + warp_fov = 1; + jumpship = 0; +} + +void +Farcaster::Arrive(Ship* s) +{ + energy = 0.0f; + + active_state = QuantumDrive::ACTIVE_POSTWARP; + warp_fov = 5000; + jumpship = s; + + if (jumpship && jumpship->Velocity().length() < 500) { + jumpship->SetVelocity(jumpship->Heading() * 500); + } +} + +// +----------------------------------------------------------------------+ + +void +Farcaster::SetApproachPoint(int i, Point loc) +{ + if (i >= 0 && i < NUM_APPROACH_PTS) + approach_rel[i] = loc; +} + +void +Farcaster::SetStartPoint(Point loc) +{ + start_rel = loc; +} + +void +Farcaster::SetEndPoint(Point loc) +{ + end_rel = loc; +} + +// +----------------------------------------------------------------------+ + +void +Farcaster::SetCycleTime(double t) +{ + cycle_time = t; +} + +// +----------------------------------------------------------------------+ + +void +Farcaster::Orient(const Physical* rep) +{ + System::Orient(rep); + + Matrix orientation = rep->Cam().Orientation(); + Point loc = rep->Location(); + + start_point = (start_rel * orientation) + loc; + end_point = (end_rel * orientation) + loc; + + for (int i = 0; i < NUM_APPROACH_PTS; i++) + approach_point[i] = (approach_rel[i] * orientation) + loc; +} + +// +----------------------------------------------------------------------+ + +bool +Farcaster::Update(SimObject* obj) +{ + if (obj == jumpship) { + jumpship->SetWarp(1); + + SimRegion* r = ship->GetRegion(); + ListIter neighbor = r->Ships(); + + while (++neighbor) { + if (neighbor->IsDropship()) { + Ship* s = neighbor.value(); + Point delta = s->Location() - ship->Location(); + + if (delta.length() < 5e3) + s->SetWarp(1); + } + } + + jumpship = 0; + } + + return SimObserver::Update(obj); +} + +const char* +Farcaster::GetObserverName() const +{ + return Name(); +} diff --git a/Stars45/Farcaster.h b/Stars45/Farcaster.h new file mode 100644 index 0000000..d1b922a --- /dev/null +++ b/Stars45/Farcaster.h @@ -0,0 +1,92 @@ +/* Project STARSHATTER + John DiCamillo + Copyright © 1997-2002. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Farcaster.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== +*/ + +#ifndef Farcaster_h +#define Farcaster_h + +#include "Types.h" +#include "Geometry.h" +#include "System.h" +#include "SimObject.h" +#include "text.h" + +// +----------------------------------------------------------------------+ + +class Ship; +class ShipDesign; +class Farcaster; + +// +----------------------------------------------------------------------+ + +class Farcaster : public System, public SimObserver +{ +public: + Farcaster(double capacity, double sink_rate); + Farcaster(const Farcaster& rhs); + virtual ~Farcaster(); + + enum CONSTANTS { NUM_APPROACH_PTS = 4 }; + + virtual void ExecFrame(double seconds); + void SetShip(Ship* s) { ship = s; } + void SetDest(Ship* d) { dest = d; } + + Point ApproachPoint(int i) const { return approach_point[i]; } + Point StartPoint() const { return start_point; } + Point EndPoint() const { return end_point; } + + virtual void SetApproachPoint(int i, Point loc); + virtual void SetStartPoint(Point loc); + virtual void SetEndPoint(Point loc); + virtual void SetCycleTime(double time); + + virtual void Orient(const Physical* rep); + + // SimObserver: + virtual bool Update(SimObject* obj); + virtual const char* GetObserverName() const; + + // accessors: + const Ship* GetShip() const { return ship; } + const Ship* GetDest() const { return dest; } + + int ActiveState() const { return active_state; } + double WarpFactor() const { return warp_fov; } + +protected: + virtual void Jump(); + virtual void Arrive(Ship* s); + + Ship* ship; + Ship* dest; + Ship* jumpship; + + Point start_rel; + Point end_rel; + Point approach_rel[NUM_APPROACH_PTS]; + + Point start_point; + Point end_point; + Point approach_point[NUM_APPROACH_PTS]; + + double cycle_time; + int active_state; + double warp_fov; + + bool no_dest; +}; + +// +----------------------------------------------------------------------+ + +#endif Farcaster_h + diff --git a/Stars45/FighterAI.cpp b/Stars45/FighterAI.cpp new file mode 100644 index 0000000..c38679e --- /dev/null +++ b/Stars45/FighterAI.cpp @@ -0,0 +1,1831 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: FighterAI.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Fighter (low-level) Artificial Intelligence class +*/ + +#include "MemDebug.h" +#include "FighterAI.h" +#include "FighterTacticalAI.h" +#include "Ship.h" +#include "Shot.h" +#include "Sensor.h" +#include "Element.h" +#include "ShipDesign.h" +#include "Instruction.h" +#include "Weapon.h" +#include "WeaponGroup.h" +#include "Drive.h" +#include "QuantumDrive.h" +#include "Farcaster.h" +#include "FlightComp.h" +#include "FlightDeck.h" +#include "Hangar.h" +#include "Sim.h" +#include "StarSystem.h" +#include "RadioMessage.h" +#include "RadioTraffic.h" + +#include "Game.h" + +static const double TIME_TO_DOCK = 30; + +// +----------------------------------------------------------------------+ + +FighterAI::FighterAI(SimObject* s) + : ShipAI(s), brakes(0), drop_state(0), jink_time(0), evading(false), + decoy_missile(0), missile_time(0), terrain_warning(false), inbound(0), + rtb_code(0), form_up(false), over_threshold(false), time_to_dock(0), + go_manual(false) +{ + ai_type = FIGHTER; + seek_gain = 22; + seek_damp = 0.55; + brakes = 0; + z_shift = 0; + + tactical = new(__FILE__,__LINE__) FighterTacticalAI(this); +} + + +// +--------------------------------------------------------------------+ + +FighterAI::~FighterAI() +{ } + +// +--------------------------------------------------------------------+ + +static double frame_time = 0; + +void +FighterAI::ExecFrame(double s) +{ + if (!ship) return; + + evading = false; + inbound = ship->GetInbound(); + missile_time -= s; + + int order = 0; + + if (navpt) + order = navpt->Action(); + + if (inbound) { + form_up = false; + rtb_code = 1; + + // CHEAT LANDING: + if (inbound->Final() && time_to_dock > 0) { + FlightDeck* deck = inbound->GetDeck(); + if (deck) { + Point dst = deck->EndPoint(); + Point approach = deck->StartPoint() - dst; + + const Ship* carrier = deck->GetCarrier(); + + Camera landing_cam; + landing_cam.Clone(carrier->Cam()); + landing_cam.Yaw(deck->Azimuth()); + + if (time_to_dock > TIME_TO_DOCK/2) { + double lr, lp, lw; + double sr, sp, sw; + + landing_cam.Orientation().ComputeEulerAngles(lr, lp, lw); + ship->Cam().Orientation().ComputeEulerAngles(sr, sp, sw); + + double nr = sr + s*(lr-sr); + double np = sp + s*(lp-sp); + double nw = sw + s*(lw-sw)*0.5; + + Camera work; + work.Aim(nr,np,nw); + landing_cam.Clone(work); + } + + ship->CloneCam(landing_cam); + ship->MoveTo(dst + approach * (time_to_dock / TIME_TO_DOCK)); + ship->SetVelocity(carrier->Velocity() + ship->Heading() * 50); + ship->SetThrottle(50); + ship->ExecFLCSFrame(); + + time_to_dock -= s; + + if (time_to_dock <= 0) { + deck->Dock(ship); + time_to_dock = 0; + } + + return; + } + } + + else if (ship->GetFlightPhase() == Ship::DOCKING) { + // deal with (pathological) moving carrier deck: + + FlightDeck* deck = inbound->GetDeck(); + if (deck) { + Point dst = deck->EndPoint(); + + if (ship->IsAirborne()) { + double alt = dst.y; + dst = ship->Location(); + dst.y = alt; + } + + const Ship* carrier = deck->GetCarrier(); + + Camera landing_cam; + landing_cam.Clone(carrier->Cam()); + landing_cam.Yaw(deck->Azimuth()); + + ship->CloneCam(landing_cam); + ship->MoveTo(dst); + + if (!ship->IsAirborne()) { + ship->SetVelocity(carrier->Velocity()); + } + else { + Point taxi(landing_cam.vpn()); + ship->SetVelocity(taxi * 95); + } + + ship->SetThrottle(0); + ship->ExecFLCSFrame(); + } + + return; + } + } + else { + Instruction* orders = ship->GetRadioOrders(); + + if (orders && + (orders->Action() == RadioMessage::WEP_HOLD || + orders->Action() == RadioMessage::FORM_UP)) { + form_up = true; + rtb_code = 0; + } + else { + form_up = false; + } + } + + if (!target && order != Instruction::STRIKE) + ship->SetSensorMode(Sensor::STD); + + ShipAI::ExecFrame(s); // this must be the last line of this method + + // IT IS NOT SAFE TO PLACE CODE HERE + // if this class decides to break orbit, + // this object will be deleted during + // ShipAI::ExecFrame() (which calls + // FighterAI::Navigator() - see below) +} + +// +--------------------------------------------------------------------+ + +void +FighterAI::FindObjective() +{ + distance = 0; + + // ALWAYS complete initial launch navpt: + if (!navpt) { + navpt = ship->GetNextNavPoint(); + if (navpt && (navpt->Action() != Instruction::LAUNCH || navpt->Status() == Instruction::COMPLETE)) + navpt = 0; + } + + if (navpt && navpt->Action() == Instruction::LAUNCH) { + if (navpt->Status() != Instruction::COMPLETE) { + FindObjectiveNavPoint(); + + // transform into camera coords: + objective = Transform(obj_w); + ship->SetDirectorInfo(Game::GetText("ai.launch")); + return; + } + else { + navpt = 0; + } + } + + // runway takeoff: + else if (takeoff) { + obj_w = ship->Location() + ship->Heading() * 10e3; + obj_w.y = ship->Location().y + 2e3; + + // transform into camera coords: + objective = Transform(obj_w); + ship->SetDirectorInfo(Game::GetText("ai.takeoff")); + return; + } + + // approaching a carrier or runway: + else if (inbound) { + FlightDeck* deck = inbound->GetDeck(); + + if (!deck) { + objective = Point(); + return; + } + + // initial approach + if (inbound->Approach() > 0 || !inbound->Cleared()) { + obj_w = deck->ApproachPoint(inbound->Approach()) + inbound->Offset(); + + distance = (obj_w - ship->Location()).length(); + + // transform into camera coords: + objective = Transform(obj_w); + ship->SetDirectorInfo(Game::GetText("ai.inbound")); + + return; + } + + // final approach + else { + ship->SetDirectorInfo(Game::GetText("ai.finals")); + + obj_w = deck->StartPoint(); + if (inbound->Final()) { + obj_w = deck->EndPoint(); + + if (deck->OverThreshold(ship)) { + obj_w = deck->MountLocation(); + over_threshold = true; + } + } + + distance = (obj_w - ship->Location()).length(); + + // transform into camera coords: + objective = Transform(obj_w); + + return; + } + } + + // not inbound yet, check for RTB order: + else { + Instruction* orders = (Instruction*) ship->GetRadioOrders(); + int action = 0; + + if (orders) + action = orders->Action(); + + if (navpt && !action) { + FindObjectiveNavPoint(); + if (distance < 5e3) { + action = navpt->Action(); + } + } + + if (action == RadioMessage::RTB || + action == RadioMessage::DOCK_WITH) { + + Ship* controller = ship->GetController(); + + if (orders && orders->Action() == RadioMessage::DOCK_WITH && orders->GetTarget()) { + controller = (Ship*) orders->GetTarget(); + } + + else if (navpt && navpt->Action() == RadioMessage::DOCK_WITH && navpt->GetTarget()) { + controller = (Ship*) navpt->GetTarget(); + } + + ReturnToBase(controller); + + if (rtb_code) + return; + } + } + + ShipAI::FindObjective(); +} + +void +FighterAI::ReturnToBase(Ship* controller) +{ + rtb_code = 0; + + if (controller) { + SimRegion* self_rgn = ship->GetRegion(); + SimRegion* rtb_rgn = controller->GetRegion(); + + if (self_rgn && !rtb_rgn) { + rtb_rgn = self_rgn; + } + + if (self_rgn && rtb_rgn && self_rgn != rtb_rgn) { + // is the carrier in orbit above us + // (or on the ground below us)? + + if (rtb_rgn->GetOrbitalRegion()->Primary() == + self_rgn->GetOrbitalRegion()->Primary()) { + + Point npt = rtb_rgn->Location() - self_rgn->Location(); + obj_w = npt.OtherHand(); + + // distance from self to navpt: + distance = Point(obj_w - ship->Location()).length(); + + // transform into camera coords: + objective = Transform(obj_w); + + if (rtb_rgn->IsAirSpace()) { + drop_state = -1; + } + else if (rtb_rgn->IsOrbital()) { + drop_state = 1; + } + + rtb_code = 2; + } + + // try to find a jumpgate that will take us home: + else { + QuantumDrive* qdrive = ship->GetQuantumDrive(); + bool use_farcaster = !qdrive || + !qdrive->IsPowerOn() || + qdrive->Status() < System::DEGRADED; + + if (use_farcaster) { + if (!farcaster) { + ListIter s = self_rgn->Ships(); + while (++s && !farcaster) { + if (s->GetFarcaster()) { + const Ship* dest = s->GetFarcaster()->GetDest(); + if (dest && dest->GetRegion() == rtb_rgn) { + farcaster = s->GetFarcaster(); + } + } + } + } + + if (farcaster) { + Point apt = farcaster->ApproachPoint(0); + Point npt = farcaster->StartPoint(); + double r1 = (ship->Location() - npt).length(); + + if (r1 > 50e3) { + obj_w = apt; + distance = r1; + objective = Transform(obj_w); + } + + else { + double r2 = (ship->Location() - apt).length(); + double r3 = (npt - apt).length(); + + if (r1+r2 < 1.2*r3) { + obj_w = npt; + distance = r1; + objective = Transform(obj_w); + } + else { + obj_w = apt; + distance = r2; + objective = Transform(obj_w); + } + } + + rtb_code = 3; + } + + // can't find a way back home, ignore the RTB order: + else { + ship->ClearRadioOrders(); + rtb_code = 0; + return; + } + } + else if (qdrive) { + if (qdrive->ActiveState() == QuantumDrive::ACTIVE_READY) { + qdrive->SetDestination(rtb_rgn, controller->Location()); + qdrive->Engage(); + } + + rtb_code = 3; + } + } + } + + else { + obj_w = controller->Location(); + + distance = (obj_w - ship->Location()).length(); + + // transform into camera coords: + objective = Transform(obj_w); + ship->SetDirectorInfo(Game::GetText("ai.return-to-base")); + + rtb_code = 1; + } + } +} + +// +--------------------------------------------------------------------+ + +void +FighterAI::FindObjectiveNavPoint() +{ + SimRegion* self_rgn = ship->GetRegion(); + SimRegion* nav_rgn = navpt->Region(); + + if (self_rgn && !nav_rgn) { + nav_rgn = self_rgn; + navpt->SetRegion(nav_rgn); + } + + if (self_rgn && nav_rgn && self_rgn != nav_rgn) { + if (nav_rgn->GetOrbitalRegion()->Primary() == + self_rgn->GetOrbitalRegion()->Primary()) { + + Point npt = nav_rgn->Location() - self_rgn->Location(); + obj_w = npt.OtherHand(); + + // distance from self to navpt: + distance = Point(obj_w - ship->Location()).length(); + + // transform into camera coords: + objective = Transform(obj_w); + + if (nav_rgn->IsAirSpace()) { + drop_state = -1; + } + else if (nav_rgn->IsOrbital()) { + drop_state = 1; + } + + return; + } + + else { + QuantumDrive* q = ship->GetQuantumDrive(); + + if (q) { + if (q->ActiveState() == QuantumDrive::ACTIVE_READY) { + q->SetDestination(navpt->Region(), navpt->Location()); + q->Engage(); + return; + } + } + } + } + + ShipAI::FindObjectiveNavPoint(); +} + +// +--------------------------------------------------------------------+ + +Point +FighterAI::ClosingVelocity() +{ + if (ship) { + WeaponDesign* wep_design = ship->GetPrimaryDesign(); + + if (target && wep_design) { + Point aim_vec = ship->Heading(); + aim_vec.Normalize(); + + Point shot_vel = ship->Velocity() + aim_vec * wep_design->speed; + return shot_vel - target->Velocity(); + } + + else if (target) { + return ship->Velocity() - target->Velocity(); + } + + else { + return ship->Velocity(); + } + } + + return Point(1,0,0); +} + +// +--------------------------------------------------------------------+ + +void +FighterAI::Navigator() +{ + go_manual = false; + + if (takeoff) { + accumulator.Clear(); + magnitude = 0; + brakes = 0; + z_shift = 0; + + Accumulate(SeekTarget()); + HelmControl(); + ThrottleControl(); + ship->ExecFLCSFrame(); + return; + } + + Element* elem = ship->GetElement(); + + if (elem) { + Ship* lead = elem->GetShip(1); + + if (lead && lead != ship) { + if (lead->IsDropping() && !ship->IsDropping()) { + ship->DropOrbit(); + // careful: this object has just been deleted! + return; + } + + if (lead->IsAttaining() && !ship->IsAttaining()) { + ship->MakeOrbit(); + // careful: this object has just been deleted! + return; + } + } + + else { + if (drop_state < 0) { + ship->DropOrbit(); + // careful: this object has just been deleted! + return; + } + + if (drop_state > 0) { + ship->MakeOrbit(); + // careful: this object has just been deleted! + return; + } + } + } + + int order = 0; + + if (navpt) + order = navpt->Action(); + + if (rtb_code == 1 && navpt && navpt->Status() < Instruction::SKIPPED && + !inbound && distance < 35e3) { // (this should be distance to the ship) + + if (order == Instruction::RTB) { + Ship* controller = ship->GetController(); + Hangar* hangar = controller ? controller->GetHangar() : 0; + + if (hangar && hangar->CanStow(ship)) { + for (int i = 0; i < elem->NumShips(); i++) { + Ship* s = elem->GetShip(i+1); + + if (s && s->GetDirector() && s->GetDirector()->Type() >= ShipAI::FIGHTER) + RadioTraffic::SendQuickMessage(s, RadioMessage::CALL_INBOUND); + } + + if (element_index == 1) + ship->SetNavptStatus(navpt, Instruction::COMPLETE); + } + + else { + if (element_index == 1) { + ::Print("WARNING: FighterAI NAVPT RTB, but no controller or hangar found for ship '%s'\n", ship->Name()); + ship->SetNavptStatus(navpt, Instruction::SKIPPED); + } + } + } + + else { + Ship* dock_target = (Ship*) navpt->GetTarget(); + if (dock_target) { + for (int i = 0; i < elem->NumShips(); i++) { + Ship* s = elem->GetShip(i+1); + + if (s) { + RadioMessage* msg = new(__FILE__,__LINE__) RadioMessage(dock_target, s, RadioMessage::CALL_INBOUND); + RadioTraffic::Transmit(msg); + } + } + + if (element_index == 1) + ship->SetNavptStatus(navpt, Instruction::COMPLETE); + } + + else { + if (element_index == 1) { + ::Print("WARNING: FighterAI NAVPT DOCK, but no dock target found for ship '%s'\n", ship->Name()); + ship->SetNavptStatus(navpt, Instruction::SKIPPED); + } + } + } + } + + if (target) + ship->SetDirectorInfo(Game::GetText("ai.seek-target")); + + accumulator.Clear(); + magnitude = 0; + brakes = 0; + z_shift = 0; + + hold = false; + if ((ship->GetElement() && ship->GetElement()->GetHoldTime() > 0) || + (navpt && navpt->Status() == Instruction::COMPLETE && navpt->HoldTime() > 0)) + hold = true; + + if (ship->MissionClock() < 10000) { + if (ship->IsAirborne()) + Accumulate(SeekTarget()); + } + + else if ((farcaster && distance < 20e3) || (inbound && inbound->Final())) { + Accumulate(SeekTarget()); + } + + else { + if (!ship->IsAirborne() || ship->AltitudeAGL() > 100) + ship->RaiseGear(); + + Accumulate(AvoidTerrain()); + Steer avoid = AvoidCollision(); + + if (other && inbound && inbound->GetDeck() && inbound->Cleared()) { + if (other != (SimObject*) inbound->GetDeck()->GetCarrier()) + Accumulate(avoid); + } + else { + Accumulate(avoid); + } + + if (!too_close && !hold && !terrain_warning) { + Accumulate(SeekTarget()); + Accumulate(EvadeThreat()); + } + } + + HelmControl(); + ThrottleControl(); + FireControl(); + AdjustDefenses(); + + ship->ExecFLCSFrame(); +} + +// +--------------------------------------------------------------------+ + +void +FighterAI::HelmControl() +{ + Camera* cam = ((Camera*) &(ship->Cam())); + Point vrt = cam->vrt(); + double deflection = vrt.y; + double theta = 0; + bool formation = element_index > 1; + bool station_keeping = distance < 0; + bool inverted = cam->vup().y < -0.5; + Ship* ward = ship->GetWard(); + + if (takeoff || inbound || station_keeping) + formation = false; + + if (takeoff || navpt || farcaster || patrol || inbound || rtb_code || target || ward || threat || formation) { + // are we being asked to flee? + if (fabs(accumulator.yaw) == 1.0 && accumulator.pitch == 0.0) { + accumulator.pitch = -0.7f; + accumulator.yaw *= 0.25f; + + if (ship->IsAirborne() && ship->GetFlightModel() == 0) + accumulator.pitch = -0.45f; + + // low ai -> lower turning rate + accumulator.pitch += 0.1f * (2-ai_level); + } + + ship->ApplyRoll((float) (accumulator.yaw * -0.7)); + ship->ApplyYaw((float) (accumulator.yaw * 0.2)); + + if (fabs(accumulator.yaw) > 0.5 && fabs(accumulator.pitch) < 0.1) + accumulator.pitch -= 0.1f; + + ship->ApplyPitch((float) accumulator.pitch); + } + + else { + ship->SetDirectorInfo(Game::GetText("ai.station-keeping")); + station_keeping = true; + + // go into a slow orbit if airborne: + if (ship->IsAirborne() && ship->Class() < Ship::LCA) { + accumulator.brake = 0.2; + accumulator.stop = 0; + + double compass_pitch = ship->CompassPitch(); + double desired_bank = -PI/4; + double current_bank = asin(deflection); + double theta = desired_bank - current_bank; + ship->ApplyRoll(theta); + + double coord_pitch = compass_pitch - 0.2 * fabs(current_bank); + ship->ApplyPitch(coord_pitch); + } + else { + accumulator.brake = 1; + accumulator.stop = 1; + } + } + + // if not turning, roll to orient with world coords: + if (ship->Design()->auto_roll > 0) { + if (fabs(accumulator.pitch) < 0.1 && fabs(accumulator.yaw) < 0.25) { + // zolon spiral behavior: + if (ship->Design()->auto_roll > 1) { + if ((element_index + (ship->MissionClock()>>10)) & 0x4) + ship->ApplyRoll( 0.60); + else + ship->ApplyRoll(-0.35); + } + + // normal behavior - roll to upright: + else if (fabs(deflection) > 0.1 || inverted) { + double theta = asin(deflection/vrt.length()) * 0.5; + ship->ApplyRoll(-theta); + } + } + } + + // if not otherwise occupied, pitch to orient with world coords: + if (station_keeping && (!ship->IsAirborne() || ship->Class() < Ship::LCA)) { + Point heading = ship->Heading(); + double pitch_deflection = heading.y; + + if (fabs(pitch_deflection) > 0.05) { + double rho = asin(pitch_deflection) * 3; + ship->ApplyPitch(rho); + } + } + + ship->SetTransX(0); + ship->SetTransY(0); + ship->SetTransZ(z_shift * ship->Design()->trans_z); + ship->SetFLCSMode(go_manual ? Ship::FLCS_MANUAL : Ship::FLCS_AUTO); +} + +void +FighterAI::ThrottleControl() +{ + Element* elem = ship->GetElement(); + double ship_speed = ship->Velocity() * ship->Heading(); + double desired = 1000; + bool formation = element_index > 1; + bool station_keeping = distance < 0; + bool augmenter = false; + Ship* ward = ship->GetWard(); + + if (inbound || station_keeping) + formation = false; + + // LAUNCH / TAKEOFF + if (ship->MissionClock() < 10000) { + formation = false; + throttle = 100; + brakes = 0; + } + + // STATION KEEPING + else if (station_keeping) { + // go into a slow orbit if airborne: + if (ship->IsAirborne() && ship->Class() < Ship::LCA) { + throttle = 30; + brakes = 0; + } + else { + throttle = 0; + brakes = 1; + } + } + + // TRY TO STAY AIRBORNE, YES? + else if (ship->IsAirborne() && ship_speed < 250 && ship->Class() < Ship::LCA) { + throttle = 100; + brakes = 0; + + if (ship_speed < 200) + augmenter = true; + } + + // INBOUND + else if (inbound) { + double carrier_speed = inbound->GetDeck()->GetCarrier()->Velocity().length(); + desired = 250 + carrier_speed; + + if (distance > 25.0e3) + desired = 750 + carrier_speed; + + else if (ship->IsAirborne()) + desired = 300; + + else if (inbound->Final()) + desired = 75 + carrier_speed; + + throttle = 0; + + // holding short? + if (inbound->Approach() == 0 && !inbound->Cleared() && + distance < 2000 && !ship->IsAirborne()) + desired = 0; + + if (ship_speed > desired+5) + brakes = 0.25; + + else if (ship->IsAirborne() || Ship::GetFlightModel() > 0) { + throttle = old_throttle + 1; + } + + else if (ship_speed < 0.85 * desired) { + throttle = 100; + + if (ship_speed < 0 && ship->GetFuelLevel() > 10) + augmenter = true; + } + + else if (ship_speed < desired-5) { + throttle = 30; + } + } + + else if (rtb_code || farcaster) { + desired = 750; + + if (threat || threat_missile) { + throttle = 100; + + if (!threat_missile && ship->GetFuelLevel() > 15) + augmenter = true; + } + + else { + throttle = 0; + + if (ship_speed > desired+5) + brakes = 0.25; + + else if (Ship::GetFlightModel() > 0) { + throttle = old_throttle + 1; + } + + else if (ship_speed < 0.85 * desired) { + throttle = 100; + + if (ship_speed < 0 && ship->GetFuelLevel() > 10) + augmenter = true; + } + + else if (ship_speed < desired-5) { + throttle = 30; + } + } + } + + // RUN AWAY!!! + else if (evading) { + throttle = 100; + + if (!threat_missile && ship->GetFuelLevel() > 15) + augmenter = true; + } + + // PATROL AND FORMATION + else if (!navpt && !target && !ward) { + if (!elem || !formation) { // element lead + if (patrol) { + desired = 250; + + if (distance > 10e3) + desired = 750; + + if (ship_speed > desired+5) { + brakes = 0.25; + throttle = old_throttle - 5; + } + + else if (ship_speed < 0.85 * desired) { + throttle = 100; + + if (ship_speed < 0 && ship->GetFuelLevel() > 10) + augmenter = true; + } + + else if (ship_speed < desired-5) + throttle = old_throttle + 5; + } + + else { + throttle = 35; + + if (threat) + throttle = 100; + + brakes = accumulator.brake; + + if (brakes > 0.1) + throttle = 0; + } + } + + else { // wingman + Ship* lead = elem->GetShip(1); + double zone = ship->Radius() * 3; + + if (lead) + desired = lead->Velocity() * lead->Heading(); + + if (fabs(slot_dist) < distance/4) // try to prevent porpoising + throttle = old_throttle; + + else if (slot_dist > zone*2) { + throttle = 100; + + if (objective.z > 10e3 && ship_speed < desired && ship->GetFuelLevel() > 25) + augmenter = true; + } + + else if (slot_dist > zone) + throttle = lead->Throttle() + 10; + + else if (slot_dist < -zone*2) { + throttle = old_throttle - 10; + brakes = 1; + } + + else if (slot_dist < -zone) { + throttle = old_throttle; + brakes = 0.5; + } + + else if (lead) { + double lv = lead->Velocity().length(); + double sv = ship_speed; + double dv = lv-sv; + double dt = 0; + + if (dv > 0) dt = dv * 1e-5 * frame_time; + else if (dv < 0) dt = dv * 1e-2 * frame_time; + + throttle = old_throttle + dt; + } + + else { + throttle = old_throttle; + } + } + } + + // TARGET/WARD/NAVPOINT SEEKING + else { + throttle = old_throttle; + + if (target) { + desired = 1250; + + if (ai_level < 1) { + throttle = 70; + } + + else if (ship->IsAirborne()) { + throttle = 100; + + if (!threat_missile && fabs(objective.z) > 6e3 && ship->GetFuelLevel() > 25) + augmenter = true; + } + + else { + throttle = 100; + + if (objective.z > 20e3 && ship_speed < desired && ship->GetFuelLevel() > 35) + augmenter = true; + + else if (objective.z > 0 && objective.z < 10e3) + throttle = 50; + } + } + + else if (ward) { + double d = (ship->Location() - ward->Location()).length(); + + if (d > 5000) { + if (ai_level < 1) + throttle = 50; + else + throttle = 80; + } + else { + double speed = ward->Velocity().length(); + + if (speed > 0) { + if (ship_speed > speed) { + throttle = old_throttle - 5; + brakes = 0.25; + } + else if (ship_speed < speed - 10) { + throttle = old_throttle + 1; + } + } + } + } + + else if (navpt) { + desired = navpt->Speed(); + + if (hold) { + // go into a slow orbit if airborne: + if (ship->IsAirborne() && ship->Class() < Ship::LCA) { + throttle = 25; + brakes = 0; + } + else { + throttle = 0; + brakes = 1; + } + } + + else if (desired > 0) { + if (ship_speed > desired) { + throttle = old_throttle - 5; + brakes = 0.25; + } + + else if (ship_speed < 0.85 * desired) { + throttle = 100; + + if ((ship->IsAirborne() || ship_speed < 0.35 * desired) && ship->GetFuelLevel() > 30) + augmenter = true; + } + + else if (ship_speed < desired - 10) { + throttle = old_throttle + 1; + } + + else if (Ship::GetFlightModel() > 0) { + throttle = old_throttle; + } + } + } + + else { + throttle = 0; + brakes = 1; + } + } + + if (ship->IsAirborne() && throttle < 20 && ship->Class() < Ship::LCA) + throttle = 20; + else if (ship->Design()->auto_roll > 1 && throttle < 5) + throttle = 5; + else if (throttle < 0) + throttle = 0; + + old_throttle = throttle; + ship->SetThrottle((int) throttle); + ship->SetAugmenter(augmenter); + + if (accumulator.stop && ship->GetFLCS() != 0) + ship->GetFLCS()->FullStop(); + + else if (ship_speed > 1 && brakes > 0) + ship->SetTransY(-brakes * ship->Design()->trans_y); + + else if (throttle > 10 && (ship->GetEMCON() < 2 || ship->GetFuelLevel() < 10)) + ship->SetTransY(ship->Design()->trans_y); +} + +// +--------------------------------------------------------------------+ + +Steer +FighterAI::AvoidTerrain() +{ + Steer avoid; + + terrain_warning = false; + + if (!ship || !ship->GetRegion() || !ship->GetRegion()->IsActive() || + (navpt && navpt->Action() == Instruction::LAUNCH)) + return avoid; + + if (ship->IsAirborne() && ship->GetFlightPhase() == Ship::ACTIVE) { + // too high? + if (ship->AltitudeMSL() > 25e3) { + if (!navpt || (navpt->Region() == ship->GetRegion() && navpt->Location().z < 27e3)) { + terrain_warning = true; + ship->SetDirectorInfo(Game::GetText("ai.too-high")); + + // where will we be? + Point selfpt = ship->Location() + ship->Velocity() + Point(0, -15e3, 0); + + // transform into camera coords: + Point obj = Transform(selfpt); + + // head down! + avoid = Seek(obj); + } + } + + // too low? + else if (ship->AltitudeAGL() < 2500) { + terrain_warning = true; + ship->SetDirectorInfo(Game::GetText("ai.too-low")); + + // way too low? + if (ship->AltitudeAGL() < 1500) { + ship->SetDirectorInfo(Game::GetText("ai.way-too-low")); + target = 0; + drop_time = 5; + } + + // where will we be? + Point selfpt = ship->Location() + ship->Velocity() + Point(0, 10e3, 0); + + // transform into camera coords: + Point obj = Transform(selfpt); + + // pull up! + avoid = Seek(obj); + } + } + + return avoid; +} + +// +--------------------------------------------------------------------+ + +Steer +FighterAI::SeekTarget() +{ + if (ship->GetFlightPhase() < Ship::ACTIVE) + return Seek(objective); + + Ship* ward = ship->GetWard(); + + if ((!target && !ward && !navpt && !farcaster && !patrol && !inbound && !rtb_code) || ship->MissionClock() < 10000) { + if (element_index > 1) { + // break formation if threatened: + if (threat_missile) + return Steer(); + + else if (threat && !form_up) + return Steer(); + + // otherwise, keep in formation: + return SeekFormationSlot(); + } + else { + return Steer(); + } + } + + if (patrol) { + Steer result = Seek(objective); + ship->SetDirectorInfo(Game::GetText("ai.seek-patrol-point")); + + if (distance < 10 * self->Radius()) { + patrol = 0; + result.brake = 1; + result.stop = 1; + } + + return result; + } + + if (inbound) { + Steer result = Seek(objective); + + if (over_threshold && objective.z < 0) { + result = Steer(); + result.brake = 1; + result.stop = 1; + } + else { + ship->SetDirectorInfo(Game::GetText("ai.seek-inbound")); + + // approach legs: + if (inbound->Approach() > 0) { + if (distance < 20 * self->Radius()) + inbound->SetApproach(inbound->Approach() - 1); + } + + // marshall point and finals: + else { + if (inbound->Cleared() && distance < 10 * self->Radius()) { + if (!inbound->Final()) { + time_to_dock = TIME_TO_DOCK; + + FlightDeck* deck = inbound->GetDeck(); + if (deck) { + double total_dist = Point(deck->EndPoint() - deck->StartPoint()).length(); + double current_dist = Point(deck->EndPoint() - ship->Location()).length(); + + time_to_dock *= (current_dist / total_dist); + } + + RadioTraffic::SendQuickMessage(ship, RadioMessage::CALL_FINALS); + } + + inbound->SetFinal(true); + ship->LowerGear(); + result.brake = 1; + result.stop = 1; + } + + else if (!inbound->Cleared() && distance < 2000) { + ship->SetDirectorInfo(Game::GetText("ai.hold-final")); + result = Steer(); + result.brake = 1; + result.stop = 1; + } + } + } + + return result; + } + + else if (rtb_code) { + return Seek(objective); + } + + SimObject* tgt = target; + + if (ward && !tgt) + tgt = ward; + + if (tgt && too_close == tgt->Identity()) { + drop_time = 4; + return Steer(); + } + + else if (navpt && navpt->Action() == Instruction::LAUNCH) { + ship->SetDirectorInfo(Game::GetText("ai.launch")); + return Seek(objective); + } + + else if (farcaster) { + // wingmen should + if (element_index > 1) + return SeekFormationSlot(); + + ship->SetDirectorInfo(Game::GetText("ai.seek-farcaster")); + return Seek(objective); + } + + else if (drop_time > 0) { + return Steer(); + } + + if (tgt) { + double basis = self->Radius() + tgt->Radius(); + double gap = distance - basis; + + // target behind: + if (objective.z < 0) { + // leave some room for an attack run: + if (gap < 8000) { + Steer s; + + s.pitch = -0.1; + if (objective.x > 0) s.yaw = 0.1; + else s.yaw = -0.1; + + return s; + } + + // start the attack run: + else { + return Seek(objective); + } + } + + // target in front: + else { + if (tgt->Type() == SimObject::SIM_SHIP) { + Ship* tgt_ship = (Ship*) tgt; + + // capital target strike: + if (tgt_ship->IsStatic()) { + if (gap < 2500) + return Flee(objective); + } + + else if (tgt_ship->IsStarship()) { + if (gap < 1000) + return Flee(objective); + + else if (ship->GetFlightModel() == Ship::FM_STANDARD && gap < 20e3) + go_manual = true; + } + } + + // fighter melee: + if (tgt->Velocity() * ship->Velocity() < 0) { + // head-to-head pass: + if (gap < 1250) + return Flee(objective); + } + + else if (gap < 250) { + return Steer(); + } + + ship->SetDirectorInfo(Game::GetText("ai.seek-target")); + return Seek(objective); + } + } + + if (navpt) { + ship->SetDirectorInfo(Game::GetText("ai.seek-navpt")); + } + + return Seek(objective); +} + +// +--------------------------------------------------------------------+ + +Steer +FighterAI::SeekFormationSlot() +{ + Steer s; + + // advance memory pipeline: + az[2] = az[1]; az[1] = az[0]; + el[2] = el[1]; el[1] = el[0]; + + Element* elem = ship->GetElement(); + Ship* lead = elem->GetShip(1); + + if (lead) { + SimRegion* self_rgn = ship->GetRegion(); + SimRegion* lead_rgn = lead->GetRegion(); + + if (self_rgn != lead_rgn) { + QuantumDrive* qdrive = ship->GetQuantumDrive(); + bool use_farcaster = !qdrive || + !qdrive->IsPowerOn() || + qdrive->Status() < System::DEGRADED; + + if (use_farcaster) { + FindObjectiveFarcaster(self_rgn, lead_rgn); + } + + else if (qdrive) { + if (qdrive->ActiveState() == QuantumDrive::ACTIVE_READY) { + qdrive->SetDestination(lead_rgn, lead->Location()); + qdrive->Engage(); + } + } + } + } + + // do station keeping? + if (distance < ship->Radius() * 10 && lead->Velocity().length() < 50) { + distance = -1; + return s; + } + + // approach + if (objective.z > ship->Radius() * -4) { + az[0] = atan2(fabs(objective.x), objective.z) * 50; + el[0] = atan2(fabs(objective.y), objective.z) * 50; + + if (objective.x < 0) az[0] = -az[0]; + if (objective.y > 0) el[0] = -el[0]; + + s.yaw = az[0] - seek_damp * (az[1] + az[2] * 0.5); + s.pitch = el[0] - seek_damp * (el[1] + el[2] * 0.5); + } + + // reverse + else { + if (objective.x > 0) s.yaw = 1.0f; + else s.yaw = -1.0f; + + s.pitch = -objective.y * 0.5f; + } + + seeking = 1; + ship->SetDirectorInfo(Game::GetText("ai.seek-formation")); + + return s; +} + +// +--------------------------------------------------------------------+ + +Steer +FighterAI::Seek(const Point& point) +{ + Steer s; + + // advance memory pipeline: + az[2] = az[1]; az[1] = az[0]; + el[2] = el[1]; el[1] = el[0]; + + // approach + if (point.z > 0.0f) { + az[0] = atan2(fabs(point.x), point.z) * seek_gain; + el[0] = atan2(fabs(point.y), point.z) * seek_gain; + + if (point.x < 0) az[0] = -az[0]; + if (point.y > 0) el[0] = -el[0]; + + s.yaw = az[0] - seek_damp * (az[1] + az[2] * 0.5); + s.pitch = el[0] - seek_damp * (el[1] + el[2] * 0.5); + + // pull up: + if (ship->IsAirborne() && point.y > 5e3) + s.pitch = -1.0f; + } + + // reverse + else { + if (ship->IsAirborne()) { + // pull up: + if (point.y > 5e3) { + s.pitch = -1.0f; + } + + // head down: + else if (point.y < -5e3) { + s.pitch = 1.0f; + } + + // level turn: + else { + if (point.x > 0) s.yaw = 1.0f; + else s.yaw = -1.0f; + + s.brake = 0.5f; + } + } + + else { + if (point.x > 0) s.yaw = 1.0f; + else s.yaw = -1.0f; + } + } + + seeking = 1; + + return s; +} + +// +--------------------------------------------------------------------+ + +Steer +FighterAI::EvadeThreat() +{ + // MISSILE THREAT REACTION: + if (threat_missile) { + evading = true; + SetTarget(0); + drop_time = 3 * (3-ai_level); + + // dropped a decoy for this missile yet? + if (decoy_missile != threat_missile) { + ship->FireDecoy(); + decoy_missile = threat_missile; + } + + // beam the missile + ship->SetDirectorInfo(Game::GetText("ai.evade-missile")); + + Point beam_line = threat_missile->Velocity().cross(Point(0,1,0)); + beam_line.Normalize(); + beam_line *= 1e6; + + Point evade_p; + Point evade_w1 = threat_missile->Location() + beam_line; + Point evade_w2 = threat_missile->Location() - beam_line; + + double d1 = Point(evade_w1 - ship->Location()).length(); + double d2 = Point(evade_w2 - ship->Location()).length(); + + if (d1 > d2) + evade_p = Transform(evade_w1); + else + evade_p = Transform(evade_w2); + + return Seek(evade_p); + } + + // GENERAL THREAT EVASION: + if (threat && !form_up) { + double threat_range = 20e3; + + Ship* threat_ship = (Ship*) threat; + double threat_dist = Point(threat->Location() - ship->Location()).length(); + + if (threat_ship->IsStarship()) { + threat_range = CalcDefensePerimeter(threat_ship); + } + + if (threat_dist <= threat_range) { + ship->SetDirectorInfo(Game::GetText("ai.evade-threat")); + + if (ship->IsAirborne()) { + evading = true; + Point beam_line = threat->Velocity().cross(Point(0,1,0)); + beam_line.Normalize(); + beam_line *= threat_range; + + Point evade_w = threat->Location() + beam_line; + Point evade_p = Transform(evade_w); + + return Seek(evade_p); + } + + else if (threat_ship->IsStarship()) { + evading = true; + + if (target == threat_ship && threat_dist < threat_range / 4) { + SetTarget(0); + drop_time = 5; + } + + if (!target) { + ship->SetDirectorInfo(Game::GetText("ai.evade-starship")); + + // flee for three seconds: + if ((ship->MissionClock() & 3) != 3) { + return Flee(Transform(threat->Location())); + } + + // jink for one second: + else { + if (Game::GameTime() - jink_time > 1500) { + jink_time = Game::GameTime(); + jink = Point(rand() - 16384, + rand() - 16384, + rand() - 16384) * 15e3; + } + + Point evade_w = ship->Location() + jink; + Point evade_p = Transform(evade_w); + + return Seek(evade_p); + } + } + + else { + ship->SetDirectorInfo(Game::GetText("ai.evade-and-seek")); + + // seek for three seconds: + if ((ship->MissionClock() & 3) < 3) { + return Steer(); // no evasion + } + + // jink for one second: + else { + if (Game::GameTime() - jink_time > 1000) { + jink_time = Game::GameTime(); + jink = Point(rand() - 16384, + rand() - 16384, + rand() - 16384); + } + + Point evade_w = target->Location() + jink; + Point evade_p = Transform(evade_w); + + return Seek(evade_p); + } + } + } + + else { + evading = true; + + if (target == threat) { + if (target->Type() == SimObject::SIM_SHIP) { + Ship* tgt_ship = (Ship*) target; + if (tgt_ship->GetTrigger(0)) { + SetTarget(0); + drop_time = 3; + } + } + } + + else if (target && threat_dist < threat_range / 2) { + SetTarget(0); + drop_time = 3; + } + + if (target) + ship->SetDirectorInfo(Game::GetText("ai.evade-and-seek")); + else + ship->SetDirectorInfo(Game::GetText("ai.random-evade")); + + // beam the threat + Point beam_line = threat->Velocity().cross(Point(0,1,0)); + beam_line.Normalize(); + beam_line *= 1e6; + + Point evade_p; + Point evade_w1 = threat->Location() + beam_line; + Point evade_w2 = threat->Location() - beam_line; + + double d1 = Point(evade_w1 - ship->Location()).length(); + double d2 = Point(evade_w2 - ship->Location()).length(); + + if (d1 > d2) + evade_p = Transform(evade_w1); + else + evade_p = Transform(evade_w2); + + if (!target) { + DWORD jink_rate = 400 + 200 * (3-ai_level); + + if (Game::GameTime() - jink_time > jink_rate) { + jink_time = Game::GameTime(); + jink = Point(rand() - 16384, + rand() - 16384, + rand() - 16384) * 2000; + } + + evade_p += jink; + } + + Steer steer = Seek(evade_p); + + if (target) + return steer / 4; + + return steer; + } + } + } + + return Steer(); +} + +// +--------------------------------------------------------------------+ + +void +FighterAI::FireControl() +{ + // if nothing to shoot at, forget it: + if (!target || target->Integrity() < 1) + return; + + // if the objective is a navpt or landing bay (not a target), then don't shoot! + if (inbound || farcaster || navpt && navpt->Action() < Instruction::DEFEND) + return; + + // object behind us, or too close: + if (objective.z < 0 || distance < 4 * self->Radius()) + return; + + // compute the firing cone: + double cross_section = 2 * target->Radius() / distance; + double gun_basket = cross_section * 2; + + Weapon* primary = ship->GetPrimary(); + Weapon* secondary = ship->GetSecondary(); + const WeaponDesign* dsgn_primary = 0; + const WeaponDesign* dsgn_secondary = 0; + bool use_primary = true; + Ship* tgt_ship = 0; + + if (target->Type() == SimObject::SIM_SHIP) { + tgt_ship = (Ship*) target; + + if (tgt_ship->InTransition()) + return; + } + + if (primary) { + dsgn_primary = primary->Design(); + + if (dsgn_primary->aim_az_max > 5*DEGREES && distance > dsgn_primary->max_range/2) + gun_basket = cross_section * 4; + + gun_basket *= (3-ai_level); + + if (tgt_ship) { + if (!primary->CanTarget(tgt_ship->Class())) + use_primary = false; + + /*** XXX NEED TO SUBTARGET SYSTEMS IF TARGET IS STARSHIP... + else if (tgt_ship->ShieldStrength() > 10) + use_primary = false; + ***/ + } + + if (use_primary) { + // is target in the basket? + double dx = fabs(objective.x / distance); + double dy = fabs(objective.y / distance); + + if (primary->GetFiringOrders() == Weapon::MANUAL && + dx < gun_basket && dy < gun_basket && + distance > dsgn_primary->min_range && + distance < dsgn_primary->max_range && + !primary->IsBlockedFriendly()) + { + ship->FirePrimary(); + } + } + } + + if (secondary && secondary->GetFiringOrders() == Weapon::MANUAL) { + dsgn_secondary = secondary->Design(); + + if (missile_time <= 0 && secondary->Ammo() && !secondary->IsBlockedFriendly()) { + if (secondary->Locked() || !dsgn_secondary->self_aiming) { + // is target in basket? + Point tgt = AimTransform(target->Location()); + double tgt_range = tgt.Normalize(); + + int factor = 2-ai_level; + double s_range = 0.5 + 0.2 * factor; + double s_basket = 0.3 + 0.2 * factor; + double extra_time = 10 * factor * factor + 5; + + if (!dsgn_secondary->self_aiming) + s_basket *= 0.33; + + if (tgt_ship) { + if (tgt_ship->Class() == Ship::MINE) { + extra_time = 10; + s_range = 0.75; + } + + else if (!tgt_ship->IsDropship()) { + extra_time = 0.5 * factor + 0.5; + s_range = 0.9; + } + } + + // is target in decent range? + if (tgt_range < secondary->Design()->max_range * s_range) { + double dx = fabs(tgt.x); + double dy = fabs(tgt.y); + + if (dx < s_basket && dy < s_basket && tgt.z > 0) { + if (ship->FireSecondary()) { + missile_time = secondary->Design()->salvo_delay + extra_time; + + if (Game::GameTime() - last_call_time > 6000) { + // call fox: + int call = RadioMessage::FOX_3; // A2A + + if (secondary->CanTarget(Ship::GROUND_UNITS)) // AGM + call = RadioMessage::FOX_1; + + else if (secondary->CanTarget(Ship::DESTROYER)) // ASM + call = RadioMessage::FOX_2; + + RadioTraffic::SendQuickMessage(ship, call); + last_call_time = Game::GameTime(); + } + } + } + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +double +FighterAI::CalcDefensePerimeter(Ship* starship) +{ + double perimeter = 15e3; + + if (starship) { + ListIter g_iter = starship->Weapons(); + while (++g_iter) { + WeaponGroup* group = g_iter.value(); + + ListIter w_iter = group->GetWeapons(); + while (++w_iter) { + Weapon* weapon = w_iter.value(); + + if (weapon->Ammo() && + weapon->GetTarget() == ship && + !weapon->IsBlockedFriendly()) { + + double range = weapon->Design()->max_range * 1.2; + if (range > perimeter) + perimeter = range; + } + } + } + } + + return perimeter; +} + + + diff --git a/Stars45/FighterAI.h b/Stars45/FighterAI.h new file mode 100644 index 0000000..99a3121 --- /dev/null +++ b/Stars45/FighterAI.h @@ -0,0 +1,85 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: FighterAI.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Fighter (low-level) Artifical Intelligence class +*/ + +#ifndef FighterAI_h +#define FighterAI_h + +#include "Types.h" +#include "ShipAI.h" + +// +--------------------------------------------------------------------+ + +class Ship; +class Shot; +class InboundSlot; + +// +--------------------------------------------------------------------+ + +class FighterAI : public ShipAI +{ +public: + FighterAI(SimObject* s); + virtual ~FighterAI(); + + virtual void ExecFrame(double seconds); + virtual int Subframe() const { return true; } + + // convert the goal point from world to local coords: + virtual void FindObjective(); + virtual void FindObjectiveNavPoint(); + +protected: + // behaviors: + virtual Steer AvoidTerrain(); + virtual Steer SeekTarget(); + virtual Steer EvadeThreat(); + virtual Point ClosingVelocity(); + + // accumulate behaviors: + virtual void Navigator(); + + // steering functions: + virtual Steer Seek(const Point& point); + virtual Steer SeekFormationSlot(); + + // fire on target if appropriate: + virtual void FireControl(); + virtual void HelmControl(); + virtual void ThrottleControl(); + + virtual double CalcDefensePerimeter(Ship* starship); + virtual void ReturnToBase(Ship* controller); + + Shot* decoy_missile; + double missile_time; + int terrain_warning; + int drop_state; + char dir_info[32]; + double brakes; + double z_shift; + double time_to_dock; + InboundSlot* inbound; + int rtb_code; + bool evading; + DWORD jink_time; + Point jink; + bool over_threshold; + bool form_up; + bool go_manual; +}; + +// +--------------------------------------------------------------------+ + +#endif FighterAI_h + diff --git a/Stars45/FighterTacticalAI.cpp b/Stars45/FighterTacticalAI.cpp new file mode 100644 index 0000000..dc0c417 --- /dev/null +++ b/Stars45/FighterTacticalAI.cpp @@ -0,0 +1,562 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: FighterTacticalAI.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Fighter-specific mid-level (tactical) AI class +*/ + +#include "MemDebug.h" +#include "FighterTacticalAI.h" +#include "ShipAI.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "Shot.h" +#include "Element.h" +#include "Instruction.h" +#include "RadioMessage.h" +#include "Sensor.h" +#include "Contact.h" +#include "WeaponGroup.h" +#include "Drive.h" +#include "Sim.h" +#include "StarSystem.h" + +#include "Game.h" + +// +----------------------------------------------------------------------+ + +const int WINCHESTER_FIGHTER = 0; +const int WINCHESTER_ASSAULT = 1; +const int WINCHESTER_STRIKE = 2; +const int WINCHESTER_STATIC = 3; + +// +----------------------------------------------------------------------+ + +FighterTacticalAI::FighterTacticalAI(ShipAI* ai) + : TacticalAI(ai), secondary_selection_time(0) +{ + for (int i = 0; i < 4; i++) + winchester[i] = false; + + ai_level = ai->GetAILevel(); + + switch (ai_level) { + default: + case 2: THREAT_REACTION_TIME = 1000; break; + case 1: THREAT_REACTION_TIME = 3000; break; + case 0: THREAT_REACTION_TIME = 6000; break; + } +} + + +// +--------------------------------------------------------------------+ + +FighterTacticalAI::~FighterTacticalAI() +{ } + +// +--------------------------------------------------------------------+ + +bool +FighterTacticalAI::CheckFlightPlan() +{ + navpt = ship->GetNextNavPoint(); + + int order = Instruction::PATROL; + roe = FLEXIBLE; + + if (navpt) { + order = navpt->Action(); + + switch (order) { + case Instruction::LAUNCH: + case Instruction::DOCK: + case Instruction::RTB: roe = NONE; + break; + + case Instruction::VECTOR: + roe = SELF_DEFENSIVE; + if (element_index > 1) + roe = DEFENSIVE; + break; + + case Instruction::DEFEND: + case Instruction::ESCORT: roe = DEFENSIVE; + break; + + case Instruction::INTERCEPT: + if (element_index > 1) + roe = DEFENSIVE; + else + roe = DIRECTED; + break; + + case Instruction::RECON: + case Instruction::STRIKE: + case Instruction::ASSAULT: roe = DIRECTED; + break; + + case Instruction::PATROL: + case Instruction::SWEEP: roe = FLEXIBLE; + break; + + default: break; + } + + if (order == Instruction::STRIKE) { + ship->SetSensorMode(Sensor::GM); + + if (IsStrikeComplete(navpt)) { + ship->SetNavptStatus(navpt, Instruction::COMPLETE); + } + } + + else if (order == Instruction::ASSAULT) { + if (ship->GetSensorMode() == Sensor::GM) + ship->SetSensorMode(Sensor::STD); + + if (IsStrikeComplete(navpt)) { + ship->SetNavptStatus(navpt, Instruction::COMPLETE); + } + } + + else { + if (ship->GetSensorMode() == Sensor::GM) + ship->SetSensorMode(Sensor::STD); + } + } + + switch (roe) { + case NONE: ship->SetDirectorInfo(Game::GetText("ai.none")); break; + case SELF_DEFENSIVE: ship->SetDirectorInfo(Game::GetText("ai.self-defensive")); break; + case DEFENSIVE: ship->SetDirectorInfo(Game::GetText("ai.defensive")); break; + case DIRECTED: ship->SetDirectorInfo(Game::GetText("ai.directed")); break; + case FLEXIBLE: ship->SetDirectorInfo(Game::GetText("ai.flexible")); break; + default: ship->SetDirectorInfo(Game::GetText("ai.default")); break; + } + + return (navpt != 0); +} + +// +--------------------------------------------------------------------+ + +void +FighterTacticalAI::SelectTarget() +{ + TacticalAI::SelectTarget(); + + SimObject* target = ship_ai->GetTarget(); + + if (target && (target->Type() == SimObject::SIM_SHIP) && + (Game::GameTime() - secondary_selection_time) > THREAT_REACTION_TIME) { + SelectSecondaryForTarget((Ship*) target); + secondary_selection_time = Game::GameTime(); + } +} + +// +--------------------------------------------------------------------+ + +void +FighterTacticalAI::SelectTargetDirected(Ship* tgt) +{ + Ship* potential_target = tgt; + + if (!tgt) { + // try to target one of the element's objectives + // (if it shows up in the contact list) + + Element* elem = ship->GetElement(); + + if (elem) { + Instruction* objective = elem->GetTargetObjective(); + + if (objective) { + SimObject* obj_sim_obj = objective->GetTarget(); + Ship* obj_tgt = 0; + + if (obj_sim_obj && obj_sim_obj->Type() == SimObject::SIM_SHIP) + obj_tgt = (Ship*) obj_sim_obj; + + if (obj_tgt && ship->FindContact(obj_tgt)) + potential_target = obj_tgt; + } + } + } + + if (!CanTarget(potential_target)) + potential_target = 0; + + ship_ai->SetTarget(potential_target); + SelectSecondaryForTarget(potential_target); +} + +// +--------------------------------------------------------------------+ + +void +FighterTacticalAI::SelectTargetOpportunity() +{ + // NON-COMBATANTS do not pick targets of opportunity: + if (ship->GetIFF() == 0) + return; + + Ship* potential_target = 0; + Shot* current_shot_target = 0; + + // pick the closest combatant ship with a different IFF code: + double target_dist = 1.0e15; + double min_dist = 5.0e3; + + // FIGHTERS are primarily anti-air platforms, but may + // also attack smaller starships: + + Ship* ward = 0; + if (element_index > 1) + ward = ship->GetLeader(); + + // commit range for patrol/sweep is 80 Km + // (about 2 minutes for a fighter at max speed) + if (roe == FLEXIBLE || roe == AGRESSIVE) + target_dist = ship->Design()->commit_range; + + if (roe < FLEXIBLE) + target_dist = 0.5 * ship->Design()->commit_range; + + int class_limit = Ship::LCA; + + if (ship->Class() == Ship::ATTACK) + class_limit = Ship::DESTROYER; + + ListIter c_iter = ship->ContactList(); + while (++c_iter) { + Contact* contact = c_iter.value(); + Ship* c_ship = contact->GetShip(); + Shot* c_shot = contact->GetShot(); + int c_iff = contact->GetIFF(ship); + bool rogue = false; + + if (c_ship) + rogue = c_ship->IsRogue(); + + if (!rogue && (c_iff <= 0 || c_iff == ship->GetIFF() || c_iff == 1000)) + continue; + + // reasonable target? + if (c_ship && c_ship->Class() <= class_limit && !c_ship->InTransition()) { + if (!rogue) { + SimObject* ttgt = c_ship->GetTarget(); + + // if we are self-defensive, is this contact engaging us? + if (roe == SELF_DEFENSIVE && ttgt != ship) + continue; + + // if we are defending, is this contact engaging us or our ward? + if (roe == DEFENSIVE && ttgt != ship && ttgt != ward) + continue; + } + + // found an enemy, check distance: + double dist = (ship->Location() - c_ship->Location()).length(); + + if (dist < 0.75 * target_dist) { + + // if on patrol, check target distance from navpoint: + if (roe == FLEXIBLE && navpt) { + double ndist = (navpt->Location().OtherHand() - c_ship->Location()).length(); + if (ndist > 80e3) + continue; + } + + potential_target = c_ship; + target_dist = dist; + } + } + + else if (c_shot && c_shot->IsDrone()) { + // found an enemy shot, do we have enough time to engage? + if (c_shot->GetEta() < 10) + continue; + + // found an enemy shot, check distance: + double dist = (ship->Location() - c_shot->Location()).length(); + + if (!current_shot_target) { + current_shot_target = c_shot; + target_dist = dist; + } + + // is this shot a better target than the one we've found? + else { + Ship* ward = ship_ai->GetWard(); + + if ((c_shot->IsTracking(ward) && !current_shot_target->IsTracking(ward)) || + (dist < target_dist)) { + current_shot_target = c_shot; + target_dist = dist; + } + } + } + } + + if (current_shot_target) { + ship_ai->SetTarget(current_shot_target); + } + else { + ship_ai->SetTarget(potential_target); + SelectSecondaryForTarget(potential_target); + } +} + +// +--------------------------------------------------------------------+ + +int +FighterTacticalAI::ListSecondariesForTarget(Ship* tgt, List& weps) +{ + weps.clear(); + + if (tgt) { + ListIter iter = ship->Weapons(); + while (++iter) { + WeaponGroup* w = iter.value(); + + if (w->Ammo() && w->CanTarget(tgt->Class())) + weps.append(w); + } + } + + return weps.size(); +} + +void +FighterTacticalAI::SelectSecondaryForTarget(Ship* tgt) +{ + if (tgt) { + int wix = WINCHESTER_FIGHTER; + + if (tgt->IsGroundUnit()) wix = WINCHESTER_STRIKE; + else if (tgt->IsStatic()) wix = WINCHESTER_STATIC; + else if (tgt->IsStarship()) wix = WINCHESTER_ASSAULT; + + WeaponGroup* best = 0; + List weps; + + if (ListSecondariesForTarget(tgt, weps)) { + winchester[wix] = false; + + // select best weapon for the job: + double range = (ship->Location() - tgt->Location()).length(); + double best_range = 0; + double best_damage = 0; + + ListIter iter = weps; + while (++iter) { + WeaponGroup* w = iter.value(); + + if (!best) { + best = w; + + WeaponDesign* d = best->GetDesign(); + best_range = d->max_range; + best_damage = d->damage * d->ripple_count; + + if (best_range < range) + best = 0; + } + + else { + WeaponDesign* d = w->GetDesign(); + double w_range = d->max_range; + double w_damage = d->damage * d->ripple_count; + + if (w_range > range) { + if (w_range < best_range || w_damage > best_damage) + best = w; + } + } + } + + // now cycle weapons until you pick the best one: + WeaponGroup* current_missile = ship->GetSecondaryGroup(); + + if (current_missile && best && current_missile != best) { + ship->CycleSecondary(); + WeaponGroup* m = ship->GetSecondaryGroup(); + + while (m != current_missile && m != best) { + ship->CycleSecondary(); + m = ship->GetSecondaryGroup(); + } + } + } + + else { + winchester[wix] = true; + + // if we have NO weapons that can hit this target, + // just drop it: + + Weapon* primary = ship->GetPrimary(); + if (!primary || !primary->CanTarget(tgt->Class())) { + ship_ai->DropTarget(3); + ship->DropTarget(); + } + } + + if (tgt->IsGroundUnit()) + ship->SetSensorMode(Sensor::GM); + + else if (ship->GetSensorMode() == Sensor::GM) + ship->SetSensorMode(Sensor::STD); + } +} + +// +--------------------------------------------------------------------+ + +void +FighterTacticalAI::FindFormationSlot(int formation) +{ + // find the formation delta: + int s = element_index - 1; + Point delta(5*s, 0, -5*s); + + // diamond: + if (formation == Instruction::DIAMOND) { + switch (element_index) { + case 2: delta = Point( 12, -1, -10); break; + case 3: delta = Point(-12, -1, -10); break; + case 4: delta = Point( 0, -2, -20); break; + } + } + + // spread: + if (formation == Instruction::SPREAD) { + switch (element_index) { + case 2: delta = Point( 15, 0, 0); break; + case 3: delta = Point(-15, 0, 0); break; + case 4: delta = Point(-30, 0, 0); break; + } + } + + // box: + if (formation == Instruction::BOX) { + switch (element_index) { + case 2: delta = Point(15, 0, 0); break; + case 3: delta = Point( 0, -2, -20); break; + case 4: delta = Point(15, -2, -20); break; + } + } + + // trail: + if (formation == Instruction::TRAIL) { + delta = Point(0, s, -20*s); + } + + ship_ai->SetFormationDelta(delta * ship->Radius() * 2); +} + +// +--------------------------------------------------------------------+ + +void +FighterTacticalAI::FindThreat() +{ + // pick the closest contact on Threat Warning System: + Ship* threat_ship = 0; + Shot* threat_missile = 0; + double threat_dist = 1e9; + + ListIter c_iter = ship->ContactList(); + + while (++c_iter) { + Contact* contact = c_iter.value(); + + if (contact->Threat(ship) && + (Game::GameTime() - contact->AcquisitionTime()) > THREAT_REACTION_TIME) { + + double rng = contact->Range(ship); + + if (contact->GetShot()) { + threat_missile = contact->GetShot(); + } + + else if (rng < threat_dist && contact->GetShip()) { + Ship* candidate = contact->GetShip(); + + if (candidate->InTransition()) + continue; + + if (candidate->IsStarship() && rng < 50e3) { + threat_ship = candidate; + threat_dist = rng; + } + + else if (candidate->IsDropship() && rng < 25e3) { + threat_ship = candidate; + threat_dist = rng; + } + + // static and ground units: + else if (rng < 30e3) { + threat_ship = candidate; + threat_dist = rng; + } + } + } + } + + ship_ai->SetThreat(threat_ship); + ship_ai->SetThreatMissile(threat_missile); +} + +// +--------------------------------------------------------------------+ + +bool +FighterTacticalAI::IsStrikeComplete(Instruction* instr) +{ + // wingmen can not call a halt to a strike: + if (!ship || element_index > 1) + return false; + + // if there's nothing to shoot at, we must be done: + if (!instr || !instr->GetTarget() || instr->GetTarget()->Life() == 0 || + instr->GetTarget()->Type() != SimObject::SIM_SHIP) + return true; + + // break off strike only when ALL weapons are expended: + // (remember to check all relevant wingmen) + Element* element = ship->GetElement(); + Ship* target = (Ship*) instr->GetTarget(); + + if (!element) + return true; + + for (int i = 0; i < element->NumShips(); i++) { + Ship* s = element->GetShip(i+1); + + if (!s || s->Integrity() < 25) // || (s->Location() - target->Location()).length() > 250e3) + continue; + + ListIter g_iter = s->Weapons(); + while (++g_iter) { + WeaponGroup* w = g_iter.value(); + + if (w->Ammo() && w->CanTarget(target->Class())) { + ListIter w_iter = w->GetWeapons(); + + while (++w_iter) { + Weapon* weapon = w_iter.value(); + + if (weapon->Status() > System::CRITICAL) + return false; + } + } + } + } + + // still here? we must be done! + return true; +} \ No newline at end of file diff --git a/Stars45/FighterTacticalAI.h b/Stars45/FighterTacticalAI.h new file mode 100644 index 0000000..d1f434f --- /dev/null +++ b/Stars45/FighterTacticalAI.h @@ -0,0 +1,56 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: FighterTacticalAI.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Fighter-specific mid-level (tactical) AI +*/ + +#ifndef FighterTacticalAI_h +#define FighterTacticalAI_h + +#include "Types.h" +#include "TacticalAI.h" +#include "List.h" + +// +--------------------------------------------------------------------+ + +class WeaponGroup; + +// +--------------------------------------------------------------------+ + +class FighterTacticalAI : public TacticalAI +{ +public: + FighterTacticalAI(ShipAI* ai); + virtual ~FighterTacticalAI(); + +protected: + virtual bool CheckFlightPlan(); + virtual bool IsStrikeComplete(Instruction* instr=0); + + virtual void SelectTarget(); + virtual void SelectTargetDirected(Ship* tgt=0); + virtual void SelectTargetOpportunity(); + virtual void FindFormationSlot(int formation); + virtual void FindThreat(); + + virtual void SelectSecondaryForTarget(Ship* tgt); + virtual int ListSecondariesForTarget(Ship* tgt, List& weps); + + bool winchester[4]; + DWORD THREAT_REACTION_TIME; + DWORD secondary_selection_time; + int ai_level; +}; + +// +--------------------------------------------------------------------+ + +#endif FighterTacticalAI_h + diff --git a/Stars45/FirstTimeDlg.cpp b/Stars45/FirstTimeDlg.cpp new file mode 100644 index 0000000..7fb5d10 --- /dev/null +++ b/Stars45/FirstTimeDlg.cpp @@ -0,0 +1,147 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: FirstTimeDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Main Menu Dialog Active Window class +*/ + +#include "MemDebug.h" +#include "FirstTimeDlg.h" +#include "Player.h" +#include "MenuScreen.h" +#include "Ship.h" +#include "Starshatter.h" +#include "KeyMap.h" +#include "Random.h" + +#include "DataLoader.h" +#include "Button.h" +#include "EditBox.h" +#include "ComboBox.h" +#include "Video.h" +#include "Keyboard.h" +#include "MachineInfo.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +DEF_MAP_CLIENT(FirstTimeDlg, OnApply); + +// +--------------------------------------------------------------------+ + +FirstTimeDlg::FirstTimeDlg(Screen* s, FormDef& def, MenuScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), manager(mgr) +{ + Init(def); +} + +FirstTimeDlg::~FirstTimeDlg() +{ +} + +// +--------------------------------------------------------------------+ + +void +FirstTimeDlg::RegisterControls() +{ + edt_name = (EditBox*) FindControl(200); + cmb_playstyle = (ComboBox*) FindControl(201); + cmb_experience = (ComboBox*) FindControl(202); + + btn_apply = (Button*) FindControl(1); + REGISTER_CLIENT(EID_CLICK, btn_apply, FirstTimeDlg, OnApply); +} + +// +--------------------------------------------------------------------+ + +void +FirstTimeDlg::Show() +{ + if (!IsShown()) + FormWindow::Show(); + + if (edt_name) + edt_name->SetText("Noobie"); +} + +// +--------------------------------------------------------------------+ + +void +FirstTimeDlg::ExecFrame() +{ +} + +// +--------------------------------------------------------------------+ + +void +FirstTimeDlg::OnApply(AWEvent* event) +{ + Starshatter* stars = Starshatter::GetInstance(); + Player* player = Player::GetCurrentPlayer(); + + if (player) { + if (edt_name) { + char password[16]; + sprintf(password, "%08x", (DWORD) Random(0, 2e9)); + + player->SetName(edt_name->GetText()); + player->SetPassword(password); + } + + if (cmb_playstyle) { + // ARCADE: + if (cmb_playstyle->GetSelectedIndex() == 0) { + player->SetFlightModel(2); + player->SetLandingModel(1); + player->SetHUDMode(0); + player->SetGunsight(1); + + if (stars) { + KeyMap& keymap = stars->GetKeyMap(); + + keymap.Bind(KEY_CONTROL_MODEL, 1, 0); + keymap.SaveKeyMap("key.cfg", 256); + + stars->MapKeys(); + } + + Ship::SetControlModel(1); + } + + // HARDCORE: + else { + player->SetFlightModel(0); + player->SetLandingModel(0); + player->SetHUDMode(0); + player->SetGunsight(0); + + if (stars) { + KeyMap& keymap = stars->GetKeyMap(); + + keymap.Bind(KEY_CONTROL_MODEL, 0, 0); + keymap.SaveKeyMap("key.cfg", 256); + + stars->MapKeys(); + } + + Ship::SetControlModel(0); + } + } + + if (cmb_experience && cmb_experience->GetSelectedIndex() > 0) { + player->SetRank(2); // Lieutenant + player->SetTrained(255); // Fully Trained + } + + Player::Save(); + } + + manager->ShowMenuDlg(); +} diff --git a/Stars45/FirstTimeDlg.h b/Stars45/FirstTimeDlg.h new file mode 100644 index 0000000..6ec95f6 --- /dev/null +++ b/Stars45/FirstTimeDlg.h @@ -0,0 +1,56 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: FirstTimeDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Main Menu Dialog Active Window class +*/ + +#ifndef FirstTimeDlg_h +#define FirstTimeDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "Bitmap.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Font.h" + +// +--------------------------------------------------------------------+ + +class MenuScreen; + +// +--------------------------------------------------------------------+ + +class FirstTimeDlg : public FormWindow +{ +public: + FirstTimeDlg(Screen* s, FormDef& def, MenuScreen* mgr); + virtual ~FirstTimeDlg(); + + virtual void RegisterControls(); + virtual void Show(); + virtual void ExecFrame(); + + // Operations: + virtual void OnApply(AWEvent* event); + +protected: + MenuScreen* manager; + + EditBox* edt_name; + ComboBox* cmb_playstyle; + ComboBox* cmb_experience; + + Button* btn_apply; +}; + +#endif FirstTimeDlg_h + diff --git a/Stars45/FlightComp.cpp b/Stars45/FlightComp.cpp new file mode 100644 index 0000000..02b7b39 --- /dev/null +++ b/Stars45/FlightComp.cpp @@ -0,0 +1,305 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: FlightComp.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Flight Computer System class +*/ + +#include "MemDebug.h" +#include "FlightComp.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "Thruster.h" + +// +----------------------------------------------------------------------+ + +FlightComp::FlightComp(int comp_type, const char* comp_name) + : Computer(comp_type, comp_name), mode(0), vlimit(0.0f), + trans_x_limit(0.0f), trans_y_limit(0.0f), trans_z_limit(0.0f), + throttle(0.0f), halt(0) +{ } + +// +----------------------------------------------------------------------+ + +FlightComp::FlightComp(const Computer& c) + : Computer(c), mode(0), vlimit(0.0f), + trans_x_limit(0.0f), trans_y_limit(0.0f), trans_z_limit(0.0f), + throttle(0.0f), halt(0) +{ } + +// +--------------------------------------------------------------------+ + +FlightComp::~FlightComp() +{ } + +// +--------------------------------------------------------------------+ + +void +FlightComp::SetTransLimit(double x, double y, double z) +{ + trans_x_limit = 0.0f; + trans_y_limit = 0.0f; + trans_z_limit = 0.0f; + + if (x >= 0) trans_x_limit = (float) x; + if (y >= 0) trans_y_limit = (float) y; + if (z >= 0) trans_z_limit = (float) z; +} + +// +--------------------------------------------------------------------+ + +void +FlightComp::ExecSubFrame() +{ + if (ship) { + ExecThrottle(); + ExecTrans(); + } +} + +// +--------------------------------------------------------------------+ + +void +FlightComp::ExecThrottle() +{ + throttle = (float) ship->Throttle(); + + if (throttle > 5) + halt = false; +} + +// +--------------------------------------------------------------------+ + +void +FlightComp::ExecTrans() +{ + double tx = ship->TransX(); + double ty = ship->TransY(); + double tz = ship->TransZ(); + + double trans_x = tx; + double trans_y = ty; + double trans_z = tz; + + bool flcs_operative = false; + + if (IsPowerOn()) + flcs_operative = Status() == System::NOMINAL || + Status() == System::DEGRADED; + + // ---------------------------------------------------------- + // FIGHTER FLCS AUTO MODE + // ---------------------------------------------------------- + + if (mode == Ship::FLCS_AUTO) { + // auto thrust to align flight path with orientation: + if (tx == 0) { + if (flcs_operative) + trans_x = (ship->Velocity() * ship->BeamLine() * -200); + + else + trans_x = 0; + } + + // manual thrust up to vlimit/2: + else { + double vfwd = ship->BeamLine() * ship->Velocity(); + + if (fabs(vfwd)>= vlimit) { + if (trans_x > 0 && vfwd > 0) + trans_x = 0; + + else if (trans_x < 0 && vfwd < 0) + trans_x = 0; + } + } + + if (halt && flcs_operative) { + if (ty == 0) { + double vfwd = ship->Heading() * ship->Velocity(); + double vmag = fabs(vfwd); + + if (vmag > 0) { + if (vfwd > 0) + trans_y = -trans_y_limit; + else + trans_y = trans_y_limit; + + if (vfwd < vlimit/2) + trans_y *= (vmag/(vlimit/2)); + } + } + } + + + // auto thrust to align flight path with orientation: + if (tz == 0) { + if (flcs_operative) + trans_z = (ship->Velocity() * ship->LiftLine() * -200); + + else + trans_z = 0; + } + + // manual thrust up to vlimit/2: + else { + double vfwd = ship->LiftLine() * ship->Velocity(); + + if (fabs(vfwd) >= vlimit) { + if (trans_z > 0 && vfwd > 0) + trans_z = 0; + + else if (trans_z < 0 && vfwd < 0) + trans_z = 0; + } + } + } + + // ---------------------------------------------------------- + // STARSHIP HELM MODE + // ---------------------------------------------------------- + + else if (mode == Ship::FLCS_HELM) { + if (flcs_operative) { + double compass_heading = ship->CompassHeading(); + double compass_pitch = ship->CompassPitch(); + // rotate helm into compass orientation: + double helm = ship->GetHelmHeading() - compass_heading; + + if (helm > PI) + helm -= 2*PI; + else if (helm < -PI) + helm += 2*PI; + + // turn to align with helm heading: + if (helm != 0) + ship->ApplyYaw(helm); + + // pitch to align with helm pitch: + if (compass_pitch != ship->GetHelmPitch()) + ship->ApplyPitch(compass_pitch - ship->GetHelmPitch()); + + // roll to align with world coordinates: + if (ship->Design()->auto_roll > 0) { + Point vrt = ship->Cam().vrt(); + double deflection = vrt.y; + + if (fabs(helm) < PI/16 || ship->Design()->turn_bank < 0.01) { + if (ship->Design()->auto_roll > 1) { + ship->ApplyRoll(0.5); + } + else if (deflection != 0) { + double theta = asin(deflection); + ship->ApplyRoll(-theta); + } + } + + // else roll through turn maneuvers: + else { + double desired_bank = ship->Design()->turn_bank; + + if (helm >= 0) + desired_bank = -desired_bank; + + double current_bank = asin(deflection); + double theta = desired_bank - current_bank; + ship->ApplyRoll(theta); + + // coordinate the turn: + if (current_bank < 0 && desired_bank < 0 || + current_bank > 0 && desired_bank > 0) { + + double coord_pitch = compass_pitch + - ship->GetHelmPitch() + - fabs(helm) * fabs(current_bank); + ship->ApplyPitch(coord_pitch); + } + } + } + } + + // flcs inoperative, set helm heading based on actual compass heading: + else { + ship->SetHelmHeading(ship->CompassHeading()); + ship->SetHelmPitch(ship->CompassPitch()); + } + + // auto thrust to align flight path with helm order: + if (tx == 0) { + if (flcs_operative) + trans_x = (ship->Velocity() * ship->BeamLine() * ship->Mass() * -1); + + else + trans_x = 0; + } + + // manual thrust up to vlimit/2: + else { + double vfwd = ship->BeamLine() * ship->Velocity(); + + if (fabs(vfwd) >= vlimit/2) { + if (trans_x > 0 && vfwd > 0) + trans_x = 0; + + else if (trans_x < 0 && vfwd < 0) + trans_x = 0; + } + } + + if (trans_y == 0 && halt) { + double vfwd = ship->Heading() * ship->Velocity(); + double vdesired = 0; + + if (vfwd > vdesired) { + trans_y = -trans_y_limit; + + if (!flcs_operative) + trans_y = 0; + + double vdelta = vfwd-vdesired; + if (vdelta < vlimit/2) + trans_y *= (vdelta/(vlimit/2)); + } + } + + + + // auto thrust to align flight path with helm order: + if (tz == 0) { + if (flcs_operative) + trans_z = (ship->Velocity() * ship->LiftLine() * ship->Mass() * -1); + + else + trans_z = 0; + } + + // manual thrust up to vlimit/2: + else { + double vfwd = ship->LiftLine() * ship->Velocity(); + + if (fabs(vfwd) > vlimit/2) { + if (trans_z > 0 && vfwd > 0) + trans_z = 0; + + else if (trans_z < 0 && vfwd < 0) + trans_z = 0; + } + } + } + + if (ship->GetThruster()) { + ship->GetThruster()->ExecTrans(trans_x, trans_y, trans_z); + } + else { + ship->SetTransX(trans_x); + ship->SetTransY(trans_y); + ship->SetTransZ(trans_z); + } +} diff --git a/Stars45/FlightComp.h b/Stars45/FlightComp.h new file mode 100644 index 0000000..8696cd0 --- /dev/null +++ b/Stars45/FlightComp.h @@ -0,0 +1,63 @@ +/* Project STARSHATTER + John DiCamillo + Copyright © 1997-2002. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: FlightComp.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Flight Computer systems class +*/ + +#ifndef FLIGHT_COMP_H +#define FLIGHT_COMP_H + +#include "Types.h" +#include "Computer.h" +#include "Geometry.h" + +// +--------------------------------------------------------------------+ + +class Ship; + +// +--------------------------------------------------------------------+ + +class FlightComp : public Computer +{ +public: + enum CompType { AVIONICS=1, FLIGHT, TACTICAL }; + + FlightComp(int comp_type, const char* comp_name); + FlightComp(const Computer& rhs); + virtual ~FlightComp(); + + virtual void ExecSubFrame(); + + int Mode() const { return mode; } + double Throttle() const { return throttle; } + + void SetMode(int m) { mode = m; } + void SetVelocityLimit(double v) { vlimit = (float) v; } + void SetTransLimit(double x, double y, double z); + + void FullStop() { halt = true; } + +protected: + virtual void ExecTrans(); + virtual void ExecThrottle(); + + int mode; + int halt; + float throttle; + + float vlimit; + float trans_x_limit; + float trans_y_limit; + float trans_z_limit; +}; + +#endif FLIGHT_COMP_H + diff --git a/Stars45/FlightDeck.cpp b/Stars45/FlightDeck.cpp new file mode 100644 index 0000000..fe1f5db --- /dev/null +++ b/Stars45/FlightDeck.cpp @@ -0,0 +1,1186 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: FlightDeck.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Everything needed to launch and recover space craft... +*/ + +#include "MemDebug.h" +#include "FlightDeck.h" +#include "Ship.h" +#include "ShipCtrl.h" +#include "ShipDesign.h" +#include "Element.h" +#include "Mission.h" +#include "MissionEvent.h" +#include "Drive.h" +#include "Sim.h" +#include "Instruction.h" +#include "Hoop.h" +#include "LandingGear.h" +#include "RadioMessage.h" +#include "RadioTraffic.h" +#include "SimEvent.h" +#include "AudioConfig.h" +#include "CameraDirector.h" +#include "Combatant.h" +#include "CombatGroup.h" +#include "CombatUnit.h" + +#include "NetData.h" +#include "NetUtil.h" + +#include "Game.h" +#include "Solid.h" +#include "Light.h" +#include "Sound.h" +#include "DataLoader.h" + +static Sound* tire_sound = 0; +static Sound* catapult_sound = 0; + +// +======================================================================+ + +class FlightDeckSlot +{ +public: + FlightDeckSlot() : ship(0), state(0), sequence(0), time(0), filter(0xf) { } + int operator == (const FlightDeckSlot& that) const { return this == &that; } + + Ship* ship; + Point spot_rel; + Point spot_loc; + + int state; + int sequence; + double time; + double clearance; + DWORD filter; +}; + +// +======================================================================+ + +InboundSlot::InboundSlot(Ship* s, FlightDeck* d, int squad, int index) + : ship(s), deck(d), squadron(squad), slot(index), cleared(0), final(0), approach(0) +{ + if (ship) + Observe(ship); + + if (deck && deck->GetCarrier()) + Observe((SimObject*) deck->GetCarrier()); +} + +int InboundSlot::operator < (const InboundSlot& that) const +{ + double dthis = 0; + double dthat = 0; + + if (ship == that.ship) { + return false; + } + + if (cleared && !that.cleared) { + return true; + } + + if (!cleared && that.cleared) { + return false; + } + + if (ship && deck && that.ship) { + dthis = Point(ship->Location() - deck->MountLocation()).length(); + dthat = Point(that.ship->Location() - deck->MountLocation()).length(); + } + + if (dthis == dthat) { + return (DWORD) this < (DWORD) &that; + } + + return dthis < dthat; +} + +int InboundSlot::operator <= (const InboundSlot& that) const +{ + double dthis = 0; + double dthat = 0; + + if (ship == that.ship) + return true; + + if (cleared && !that.cleared) + return true; + + if (!cleared && that.cleared) + return false; + + if (ship && deck && that.ship) { + dthis = Point(ship->Location() - deck->MountLocation()).length(); + dthat = Point(that.ship->Location() - deck->MountLocation()).length(); + } + + return dthis <= dthat; +} + +int InboundSlot::operator == (const InboundSlot& that) const +{ + return this == &that; +} + +void InboundSlot::Clear(bool c) +{ + if (ship) + cleared = c; +} + +double InboundSlot::Distance() +{ + if (ship && deck) { + return Point(ship->Location() - deck->MountLocation()).length(); + } + + return 1e9; +} + +// +----------------------------------------------------------------------+ + +bool +InboundSlot::Update(SimObject* obj) +{ + if (obj->Type() == SimObject::SIM_SHIP) { + Ship* s = (Ship*) obj; + + if (s == ship) { + ship = 0; + } + + // Actually, this can't happen. The flight deck + // owns the slot. When the carrier is destroyed, + // the flight decks and slots are destroyed before + // the carrier updates the observers. + + // I'm leaving this block in, just in case. + + else if (deck && s == deck->GetCarrier()) { + ship = 0; + deck = 0; + } + } + + return SimObserver::Update(obj); +} + +const char* +InboundSlot::GetObserverName() const +{ + return "InboundSlot"; +} + +// +======================================================================+ + +FlightDeck::FlightDeck() + : System(FLIGHT_DECK, FLIGHT_DECK_LAUNCH, "Flight Deck", 1, 1), + carrier(0), index(0), num_slots(0), slots(0), cycle_time(5), num_hoops(0), hoops(0), + azimuth(0), light(0), num_catsounds(0), num_approach_pts(0) +{ + name = Game::GetText("sys.flight-deck"); + abrv = Game::GetText("sys.flight-deck.abrv"); +} + +// +----------------------------------------------------------------------+ + +FlightDeck::FlightDeck(const FlightDeck& s) + : System(s), + carrier(0), index(0), start_rel(s.start_rel), + end_rel(s.end_rel), cam_rel(s.cam_rel), + cycle_time(s.cycle_time), + num_slots(s.num_slots), slots(0), + num_hoops(0), hoops(0), azimuth(s.azimuth), light(0), + num_catsounds(0), num_approach_pts(s.num_approach_pts) +{ + Mount(s); + + slots = new(__FILE__,__LINE__) FlightDeckSlot[num_slots]; + for (int i = 0; i < num_slots; i++) { + slots[i].spot_rel = s.slots[i].spot_rel; + slots[i].filter = s.slots[i].filter; + } + + for (i = 0; i < NUM_APPROACH_PTS; i++) + approach_rel[i] = s.approach_rel[i]; + + for (i = 0; i < 2; i++) + runway_rel[i] = s.runway_rel[i]; + + if (s.light) { + light = new(__FILE__,__LINE__) Light(*s.light); + } +} + +// +--------------------------------------------------------------------+ + +FlightDeck::~FlightDeck() +{ + if (hoops && num_hoops) { + for (int i = 0; i < num_hoops; i++) { + Hoop* h = &hoops[i]; + Scene* scene = h->GetScene(); + if (scene) + scene->DelGraphic(h); + } + } + + delete [] slots; + delete [] hoops; + + LIGHT_DESTROY(light); + + recovery_queue.destroy(); +} + +// +--------------------------------------------------------------------+ + +void +FlightDeck::Initialize() +{ + static int initialized = 0; + if (initialized) return; + + DataLoader* loader = DataLoader::GetLoader(); + loader->SetDataPath("Sounds/"); + + const int SOUND_FLAGS = Sound::LOCALIZED | + Sound::LOC_3D; + + loader->LoadSound("Tires.wav", tire_sound, SOUND_FLAGS); + loader->LoadSound("Catapult.wav", catapult_sound, SOUND_FLAGS); + loader->SetDataPath(0); + + if (tire_sound) + tire_sound->SetMaxDistance(2.5e3f); + + if (catapult_sound) + catapult_sound->SetMaxDistance(0.5e3f); + + initialized = 1; +} + +// +--------------------------------------------------------------------+ + +void +FlightDeck::Close() +{ + delete tire_sound; + delete catapult_sound; + + tire_sound = 0; + catapult_sound = 0; +} + +// +--------------------------------------------------------------------+ + +void +FlightDeck::ExecFrame(double seconds) +{ + System::ExecFrame(seconds); + + bool advance_queue = false; + long max_vol = AudioConfig::EfxVolume(); + long volume = -1000; + Sim* sim = Sim::GetSim(); + + if (volume > max_vol) + volume = max_vol; + + // update ship status: + for (int i = 0; i < num_slots; i++) { + FlightDeckSlot* slot = &slots[i]; + Ship* slot_ship = 0; + + if (slot->ship == 0) { + slot->state = CLEAR; + } + else { + slot_ship = slot->ship; + slot_ship->SetThrottle(0); + } + + switch (slot->state) { + case CLEAR: + if (slot->time > 0) { + slot->time -= seconds; + } + else if (IsRecoveryDeck()) { + GrantClearance(); + } + break; + + case READY: { + Camera c; + c.Clone(carrier->Cam()); + c.Yaw(azimuth); + + if (slot_ship) { + slot_ship->CloneCam(c); + slot_ship->MoveTo(slot->spot_loc); + slot_ship->TranslateBy(carrier->Cam().vup() * slot->clearance); + slot_ship->SetVelocity(carrier->Velocity()); + } + slot->time = 0; + } + break; + + case QUEUED: + if (slot->time > 0) { + Camera c; + c.Clone(carrier->Cam()); + c.Yaw(azimuth); + + slot->time -= seconds; + if (slot_ship) { + slot_ship->CloneCam(c); + slot_ship->MoveTo(slot->spot_loc); + slot_ship->TranslateBy(carrier->Cam().vup() * slot->clearance); + slot_ship->SetFlightPhase(Ship::ALERT); + } + } + + if (slot->sequence == 1 && slot->time <= 0) { + bool clear_for_launch = true; + for (int i = 0; i < num_slots; i++) + if (slots[i].state == LOCKED) + clear_for_launch = false; + + if (clear_for_launch) { + slot->sequence = 0; + slot->state = LOCKED; + slot->time = cycle_time; + if (slot_ship) + slot_ship->SetFlightPhase(Ship::LOCKED); + num_catsounds = 0; + + advance_queue = true; + } + } + break; + + case LOCKED: + if (slot->time > 0) { + slot->time -= seconds; + + if (slot_ship) { + double ct4 = cycle_time/4; + + double dx = start_rel.x - slot->spot_rel.x; + double dy = start_rel.z - slot->spot_rel.z; + double dyaw = atan2(dx,dy) - azimuth; + + Camera c; + c.Clone(carrier->Cam()); + c.Yaw(azimuth); + + // rotate: + if (slot->time > 3*ct4) { + double step = 1 - (slot->time - 3*ct4) / ct4; + c.Yaw(dyaw * step); + slot_ship->CloneCam(c); + slot_ship->MoveTo(slot->spot_loc); + slot_ship->TranslateBy(carrier->Cam().vup() * slot->clearance); + + if (carrier->IsGroundUnit()) { + slot_ship->SetThrottle(25); + } + + else if (num_catsounds < 1) { + if (catapult_sound) { + Sound* sound = catapult_sound->Duplicate(); + sound->SetLocation(slot_ship->Location()); + sound->SetVolume(volume); + sound->Play(); + } + num_catsounds = 1; + } + } + + // translate: + else if (slot->time > 2*ct4) { + double step = (slot->time - 2*ct4) / ct4; + + Point loc = start_point + + (slot->spot_loc - start_point) * step; + + c.Yaw(dyaw); + slot_ship->CloneCam(c); + slot_ship->MoveTo(loc); + slot_ship->TranslateBy(carrier->Cam().vup() * slot->clearance); + + if (carrier->IsGroundUnit()) { + slot_ship->SetThrottle(25); + } + + else if (num_catsounds < 2) { + if (catapult_sound) { + Sound* sound = catapult_sound->Duplicate(); + sound->SetLocation(slot_ship->Location()); + sound->SetVolume(volume); + sound->Play(); + } + num_catsounds = 2; + } + } + + // rotate: + else if (slot->time > ct4) { + double step = (slot->time - ct4) / ct4; + c.Yaw(dyaw*step); + slot_ship->CloneCam(c); + slot_ship->MoveTo(start_point); + slot_ship->TranslateBy(carrier->Cam().vup() * slot->clearance); + + if (carrier->IsGroundUnit()) { + slot_ship->SetThrottle(25); + } + + else if (num_catsounds < 3) { + if (catapult_sound) { + Sound* sound = catapult_sound->Duplicate(); + sound->SetLocation(slot_ship->Location()); + sound->SetVolume(volume); + sound->Play(); + } + num_catsounds = 3; + } + } + + // wait: + else { + slot_ship->SetThrottle(100); + slot_ship->CloneCam(c); + slot_ship->MoveTo(start_point); + slot_ship->TranslateBy(carrier->Cam().vup() * slot->clearance); + } + + slot_ship->SetFlightPhase(Ship::LOCKED); + } + } + else { + slot->state = LAUNCH; + slot->time = 0; + + } + break; + + case LAUNCH: + LaunchShip(slot_ship); + break; + + case DOCKING: + if (slot_ship && !slot_ship->IsAirborne()) { + // do arresting gear stuff: + if (slot_ship->GetFlightModel() == Ship::FM_ARCADE) + slot_ship->ArcadeStop(); + + slot_ship->SetVelocity(carrier->Velocity()); + } + + if (slot->time > 0) { + slot->time -= seconds; + } + else { + if (slot_ship) { + slot_ship->SetFlightPhase(Ship::DOCKED); + slot_ship->Stow(); + + NetUtil::SendObjKill(slot_ship, carrier, NetObjKill::KILL_DOCK, index); + } + + Clear(i); + } + break; + + default: + break; + } + } + + if (advance_queue) { + for (i = 0; i < num_slots; i++) { + FlightDeckSlot* slot = &slots[i]; + if (slot->state == QUEUED && slot->sequence > 1) + slot->sequence--; + } + } +} + +bool +FlightDeck::LaunchShip(Ship* slot_ship) +{ + FlightDeckSlot* slot = 0; + Sim* sim = Sim::GetSim(); + + if (slot_ship) { + for (int i = 0; i < num_slots; i++) { + if (slots[i].ship == slot_ship) + slot = &(slots[i]); + } + + // only suggest a launch point if no flight plan has been filed... + if (slot_ship->GetElement()->FlightPlanLength() == 0) { + Point departure = end_point; + departure = departure.OtherHand(); + + Instruction* launch_point = new(__FILE__,__LINE__) + Instruction(carrier->GetRegion(), departure, Instruction::LAUNCH); + launch_point->SetSpeed(350); + + slot_ship->SetLaunchPoint(launch_point); + } + + if (!slot_ship->IsAirborne()) { + Point cat; + if (fabs(azimuth) < 5*DEGREES) { + cat = carrier->Heading() * 300; + } + else { + Camera c; + c.Clone(carrier->Cam()); + c.Yaw(azimuth); + cat = c.vpn() * 300; + } + + slot_ship->SetVelocity(carrier->Velocity() + cat); + slot_ship->SetFlightPhase(Ship::LAUNCH); + } + else { + slot_ship->SetFlightPhase(Ship::TAKEOFF); + } + + Director* dir = slot_ship->GetDirector(); + if (dir && dir->Type() == ShipCtrl::DIR_TYPE) { + ShipCtrl* ctrl = (ShipCtrl*) dir; + ctrl->Launch(); + } + + ShipStats* c = ShipStats::Find(carrier->Name()); + if (c) c->AddEvent(SimEvent::LAUNCH_SHIP, slot_ship->Name()); + + ShipStats* stats = ShipStats::Find(slot_ship->Name()); + if (stats) { + stats->SetRegion(carrier->GetRegion()->Name()); + stats->SetType(slot_ship->Design()->name); + + if (slot_ship->GetElement()) { + Element* elem = slot_ship->GetElement(); + stats->SetRole(Mission::RoleName(elem->Type())); + stats->SetCombatGroup(elem->GetCombatGroup()); + stats->SetCombatUnit(elem->GetCombatUnit()); + stats->SetElementIndex(slot_ship->GetElementIndex()); + } + + stats->SetIFF(slot_ship->GetIFF()); + stats->AddEvent(SimEvent::LAUNCH, carrier->Name()); + + if (slot_ship == sim->GetPlayerShip()) + stats->SetPlayer(true); + } + + sim->ProcessEventTrigger(MissionEvent::TRIGGER_LAUNCH, 0, slot_ship->Name()); + + if (slot) { + slot->ship = 0; + slot->state = CLEAR; + slot->sequence = 0; + slot->time = 0; + } + + return true; + } + + return false; +} + +// +----------------------------------------------------------------------+ + +void +FlightDeck::SetLight(double l) +{ + LIGHT_DESTROY(light); + light = new(__FILE__,__LINE__) Light((float) l); +} + +void +FlightDeck::SetApproachPoint(int i, Point loc) +{ + if (i >= 0 && i < NUM_APPROACH_PTS) { + approach_rel[i] = loc; + + if (i >= num_approach_pts) + num_approach_pts = i+1; + } +} + +void +FlightDeck::SetRunwayPoint(int i, Point loc) +{ + if (i >= 0 && i < 2) + runway_rel[i] = loc; +} + +void +FlightDeck::SetStartPoint(Point loc) +{ + start_rel = loc; +} + +void +FlightDeck::SetEndPoint(Point loc) +{ + end_rel = loc; +} + +void +FlightDeck::SetCamLoc(Point loc) +{ + cam_rel = loc; +} + +// +----------------------------------------------------------------------+ + +void +FlightDeck::AddSlot(const Point& spot, DWORD filter) +{ + if (!slots) + slots = new(__FILE__,__LINE__) FlightDeckSlot[10]; + + slots[num_slots].spot_rel = spot; + slots[num_slots].filter = filter; + num_slots++; +} + +// +----------------------------------------------------------------------+ + +void +FlightDeck::SetCycleTime(double t) +{ + cycle_time = t; +} + +// +----------------------------------------------------------------------+ + +void +FlightDeck::Orient(const Physical* rep) +{ + System::Orient(rep); + + Matrix orientation = rep->Cam().Orientation(); + Point loc = rep->Location(); + + start_point = (start_rel * orientation) + loc; + end_point = (end_rel * orientation) + loc; + cam_loc = (cam_rel * orientation) + loc; + + for (int i = 0; i < num_approach_pts; i++) + approach_point[i] = (approach_rel[i] * orientation) + loc; + + for (i = 0; i < num_slots; i++) + slots[i].spot_loc = (slots[i].spot_rel * orientation) + loc; + + if (IsRecoveryDeck()) { + if (carrier->IsAirborne()) { + runway_point[0] = (runway_rel[0] * orientation) + loc; + runway_point[1] = (runway_rel[1] * orientation) + loc; + } + + if (num_hoops < 1) { + num_hoops = 4; + hoops = new(__FILE__,__LINE__) Hoop[num_hoops]; + } + + Point hoop_vec = start_point - end_point; + double hoop_d = hoop_vec.Normalize() / num_hoops; + + orientation.Yaw(azimuth); + + for (int i = 0; i < num_hoops; i++) { + hoops[i].MoveTo(end_point + hoop_vec * (i+1) * hoop_d); + hoops[i].SetOrientation(orientation); + } + } + + if (light) + light->MoveTo(mount_loc); +} + +// +----------------------------------------------------------------------+ + +int +FlightDeck::SpaceLeft(int type) const +{ + int space_left = 0; + + for (int i = 0; i < num_slots; i++) + if (slots[i].ship == 0 && (slots[i].filter & type)) + space_left++; + + return space_left; +} + +// +----------------------------------------------------------------------+ + +bool +FlightDeck::Spot(Ship* s, int& index) +{ + if (!s) + return false; + + if (index < 0) { + for (int i = 0; i < num_slots; i++) { + if (slots[i].ship == 0 && (slots[i].filter & s->Class())) { + index = i; + break; + } + } + } + + if (index >= 0 && index < num_slots && slots[index].ship == 0) { + slots[index].state = READY; + slots[index].ship = s; + slots[index].clearance = 0; + + if (s->GetGear()) + slots[index].clearance = s->GetGear()->GetClearance(); + + if (IsRecoveryDeck() && !s->IsAirborne()) { + s->SetVelocity(s->Velocity() * 0.01); // hit the brakes! + } + + if (!IsRecoveryDeck()) { + Camera work; + work.Clone(carrier->Cam()); + work.Yaw(azimuth); + + s->CloneCam(work); + s->MoveTo(slots[index].spot_loc); + + LandingGear* g = s->GetGear(); + if (g) { + g->SetState(LandingGear::GEAR_DOWN); + g->ExecFrame(0); + s->TranslateBy(carrier->Cam().vup() * slots[index].clearance); + } + + s->SetFlightPhase(Ship::ALERT); + } + + s->SetCarrier(carrier, this); + Observe(s); + + return true; + } + + return false; +} + +bool +FlightDeck::Clear(int index) +{ + if (index >= 0 && index < num_slots && slots[index].ship != 0) { + Ship* s = slots[index].ship; + + slots[index].ship = 0; + slots[index].time = cycle_time; + slots[index].state = CLEAR; + + ListIter iter = recovery_queue; + while (++iter) { + if (iter->GetShip() == s) { + delete iter.removeItem(); // ??? SHOULD DELETE HERE ??? + break; + } + } + + return true; + } + + return false; +} + +// +----------------------------------------------------------------------+ + +bool +FlightDeck::Launch(int index) +{ + if (subtype == FLIGHT_DECK_LAUNCH && index >= 0 && index < num_slots) { + FlightDeckSlot* slot = &slots[index]; + + if (slot->ship && slot->state == READY) { + int last = 0; + FlightDeckSlot* last_slot = 0; + FlightDeckSlot* lock_slot = 0; + + for (int i = 0; i < num_slots; i++) { + FlightDeckSlot* s = &slots[i]; + + if (s->state == QUEUED && s->sequence > last) { + last = s->sequence; + last_slot = s; + } + + else if (s->state == LOCKED) { + lock_slot = s; + } + } + + // queue the slot for launch: + slot->state = QUEUED; + slot->sequence = last + 1; + slot->time = 0; + + if (last_slot) + slot->time = last_slot->time + cycle_time; + + else if (lock_slot) + slot->time = lock_slot->time; + + return true; + } + } + + return false; +} + +// +----------------------------------------------------------------------+ + +bool +FlightDeck::Recover(Ship* s) +{ + if (s && subtype == FLIGHT_DECK_RECOVERY) { + if (OverThreshold(s)) { + if (s->GetFlightPhase() < Ship::RECOVERY) { + s->SetFlightPhase(Ship::RECOVERY); + s->SetCarrier(carrier, this); // let ship know which flight deck it's in (needed for dock cam) + } + + // are we there yet? + if (s->GetFlightPhase() >= Ship::ACTIVE && s->GetFlightPhase() < Ship::DOCKING) { + if (slots[0].ship == 0) { // only need to ask this on approach + double dock_distance = (s->Location() - MountLocation()).length(); + + if (s->IsAirborne()) { + double altitude = s->Location().y - MountLocation().y; + if (dock_distance < Radius()*3 && altitude < s->Radius()) + Dock(s); + } + else { + if (dock_distance < s->Radius()) + Dock(s); + } + } + } + + return true; + } + else { + if (s->GetFlightPhase() == Ship::RECOVERY) + s->SetFlightPhase(Ship::ACTIVE); + } + } + + return false; +} + +bool +FlightDeck::Dock(Ship* s) +{ + if (s && subtype == FLIGHT_DECK_RECOVERY && slots[0].time <= 0) { + int index = 0; + if (Spot(s, index)) { + s->SetFlightPhase(Ship::DOCKING); + s->SetCarrier(carrier, this); + + // hard landings? + if (Ship::GetLandingModel() == 0) { + double base_damage = s->Design()->integrity; + + // did player do something stupid? + if (s->GetGear() && !s->IsGearDown()) { + Print("FlightDeck::Dock(%s) Belly landing!\n", s->Name()); + s->InflictDamage(0.5 * base_damage); + } + + double docking_deflection = fabs(carrier->Cam().vup().y - s->Cam().vup().y); + + if (docking_deflection > 0.35) { + Print("Landing upside down? y = %.3f\n", docking_deflection); + s->InflictDamage(0.8 * base_damage); + } + + // did incoming ship exceed safe landing parameters? + if (s->IsAirborne()) { + if (s->Velocity().y < -20) { + Print("FlightDeck::Dock(%s) Slammed it!\n", s->Name()); + s->InflictDamage(0.1 * base_damage); + } + } + + // did incoming ship exceed safe docking speed? + else { + Point delta_v = s->Velocity() - carrier->Velocity(); + double excess = (delta_v.length() - 100); + + if (excess > 0) + s->InflictDamage(excess); + } + } + + if (s->IsAirborne()) { + if (s == Sim::GetSim()->GetPlayerShip() && tire_sound) { + Sound* sound = tire_sound->Duplicate(); + sound->Play(); + } + } + + if (s->GetDrive()) + s->GetDrive()->PowerOff(); + + slots[index].state = DOCKING; + slots[index].time = 5; + + if (s->IsAirborne()) + slots[index].time = 7.5; + return true; + } + } + + return false; +} + +int +FlightDeck::Inbound(InboundSlot*& s) +{ + if (s && s->GetShip()) { + Ship* inbound = s->GetShip(); + + if (recovery_queue.contains(s)) { + InboundSlot* orig = s; + s = recovery_queue.find(s); + delete orig; + } + else { + recovery_queue.append(s); + Observe(s->GetShip()); + } + + inbound->SetInbound(s); + + // find the best initial approach point for this ship: + double current_distance = 1e9; + for (int i = 0; i < num_approach_pts; i++) { + double distance = Point(inbound->Location() - approach_point[i]).length(); + if (distance < current_distance) { + current_distance = distance; + s->SetApproach(i); + } + } + + Point offset(rand()-16000, rand()-16000, rand()-16000); + offset.Normalize(); + offset *= 200; + + s->SetOffset(offset); + + // *** DEBUG *** + // PrintQueue(); + + // if the new guy is first in line, and the deck is almost ready, + // go ahead and clear him for approach now + recovery_queue.sort(); + if (recovery_queue[0] == s && !s->Cleared() && slots[0].time <= 3) + s->Clear(); + + return recovery_queue.index(s) + 1; + } + + return 0; +} + +void +FlightDeck::GrantClearance() +{ + if (recovery_queue.size() > 0) { + if (recovery_queue[0]->Cleared() && recovery_queue[0]->Distance() > 45e3) { + recovery_queue[0]->Clear(false); + } + + if (!recovery_queue[0]->Cleared()) { + recovery_queue.sort(); + + if (recovery_queue[0]->Distance() < 35e3) { + recovery_queue[0]->Clear(); + + Ship* dst = recovery_queue[0]->GetShip(); + + RadioMessage* clearance = new(__FILE__,__LINE__) RadioMessage(dst, carrier, RadioMessage::CALL_CLEARANCE); + clearance->SetInfo(Text("for final approach to ") + Name()); + RadioTraffic::Transmit(clearance); + } + } + } +} + +// +----------------------------------------------------------------------+ + +void +FlightDeck::PrintQueue() +{ + Print("\nRecovery Queue for %s\n", Name()); + if (recovery_queue.size() < 1) { + Print(" (empty)\n\n"); + return; + } + + for (int i = 0; i < recovery_queue.size(); i++) { + InboundSlot* s = recovery_queue.at(i); + + if (!s) { + Print(" %2d. null\n", i); + } + else if (!s->GetShip()) { + Print(" %2d. ship is null\n", i); + } + else { + double d = Point(s->GetShip()->Location() - MountLocation()).length(); + Print(" %2d. %c %-20s %8d km\n", i, s->Cleared()?'*':' ', s->GetShip()->Name(), (int) (d/1000)); + } + } + + Print("\n"); +} + +// +----------------------------------------------------------------------+ + +Ship* +FlightDeck::GetShip(int index) const +{ + if (index >= 0 && index < num_slots) + return slots[index].ship; + + return 0; +} + +double +FlightDeck::TimeRemaining(int index) const +{ + if (index >= 0 && index < num_slots) + return slots[index].time; + + return 0; +} + + +int +FlightDeck::State(int index) const +{ + if (index >= 0 && index < num_slots) + return slots[index].state; + + return 0; +} + +int +FlightDeck::Sequence(int index) const +{ + if (index >= 0 && index < num_slots) + return slots[index].sequence; + + return 0; +} + +// +----------------------------------------------------------------------+ + +bool +FlightDeck::Update(SimObject* obj) +{ + if (obj->Type() == SimObject::SIM_SHIP) { + Ship* s = (Ship*) obj; + + ListIter iter = recovery_queue; + while (++iter) { + InboundSlot* recovery_slot = iter.value(); + + if (recovery_slot->GetShip() == s || recovery_slot->GetShip() == 0) { + delete iter.removeItem(); + } + } + + for (int i = 0; i < num_slots; i++) { + FlightDeckSlot* slot = &slots[i]; + + if (slot->ship == s) { + slot->ship = 0; + slot->state = 0; + slot->sequence = 0; + slot->time = 0; + break; + } + } + } + + return SimObserver::Update(obj); +} + +const char* +FlightDeck::GetObserverName() const +{ + return Name(); +} + +// +----------------------------------------------------------------------+ + +bool +FlightDeck::OverThreshold(Ship* s) const +{ + if (carrier->IsAirborne()) { + if (s->AltitudeAGL() > s->Radius() * 4) + return false; + + const Point& sloc = s->Location(); + + // is ship between the markers? + double distance = 1e9; + + Point d0 = runway_point[0] - sloc; + Point d1 = runway_point[1] - sloc; + double d = d0 * d1; + bool between = (d0 * d1) < 0; + + if (between) { + // distance from point to line: + Point src = runway_point[0]; + Point dir = runway_point[1] - src; + Point w = (sloc - src).cross(dir); + + distance = w.length() / dir.length(); + } + + return distance < Radius(); + } + + else { + return (s->Location() - MountLocation()).length() < (s->Radius() + Radius()); + } +} + +// +----------------------------------------------------------------------+ + +bool +FlightDeck::ContainsPoint(const Point& p) const +{ + return (p - MountLocation()).length() < Radius(); +} diff --git a/Stars45/FlightDeck.h b/Stars45/FlightDeck.h new file mode 100644 index 0000000..cc8eefc --- /dev/null +++ b/Stars45/FlightDeck.h @@ -0,0 +1,195 @@ +/* Project STARSHATTER + John DiCamillo + Copyright © 1997-2002. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: FlightDeck.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Everything needed to launch and recover space craft + + See Also: Hangar +*/ + +#ifndef FlightDeck_h +#define FlightDeck_h + +#include "Types.h" +#include "Geometry.h" +#include "System.h" +#include "SimObject.h" +#include "Text.h" + +// +----------------------------------------------------------------------+ + +class Hoop; +class Light; +class Ship; +class ShipDesign; +class FlightDeck; +class FlightDeckSlot; +class InboundSlot; + +// +======================================================================+ + +class InboundSlot : public SimObserver +{ +public: + static const char* TYPENAME() { return "InboundSlot"; } + + InboundSlot() : ship(0), deck(0), squadron(0), slot(0), cleared(0), final(0), approach(0) { } + InboundSlot(Ship* s, FlightDeck* d, int squad, int index); + + int operator < (const InboundSlot& that) const; + int operator <= (const InboundSlot& that) const; + int operator == (const InboundSlot& that) const; + + // SimObserver: + virtual bool Update(SimObject* obj); + virtual const char* GetObserverName() const; + + Ship* GetShip() { return ship; } + FlightDeck* GetDeck() { return deck; } + int Squadron() { return squadron; } + int Index() { return slot; } + int Cleared() { return cleared; } + int Final() { return final; } + int Approach() { return approach; } + Point Offset() { return offset; } + double Distance(); + + void SetApproach(int a) { approach = a; } + void SetOffset(const Point& p) { offset = p; } + void SetFinal(int f) { final = f; } + void Clear(bool clear=true); + +private: + Ship* ship; + FlightDeck* deck; + int squadron; + int slot; + int cleared; + int final; + int approach; + Point offset; +}; + +// +----------------------------------------------------------------------+ + +class FlightDeck : public System, public SimObserver +{ +public: + static const char* TYPENAME() { return "FlightDeck"; } + + FlightDeck(); + FlightDeck(const FlightDeck& rhs); + virtual ~FlightDeck(); + + enum FLIGHT_DECK_MODE { FLIGHT_DECK_LAUNCH, FLIGHT_DECK_RECOVERY }; + enum FLIGHT_SLOT_STATE { CLEAR, READY, QUEUED, LOCKED, LAUNCH, DOCKING }; + enum CONSTANTS { NUM_APPROACH_PTS = 8 }; + + static void Initialize(); + static void Close(); + + virtual void ExecFrame(double seconds); + void SetCarrier(Ship* s) { ship = carrier = s; } + void SetIndex(int n) { index = n; } + + virtual int SpaceLeft(int type) const; + + virtual bool Spot(Ship* s, int& index); + virtual bool Clear(int index); + virtual bool Launch(int index); + virtual bool LaunchShip(Ship* s); + virtual bool Recover(Ship* s); + virtual bool Dock(Ship* s); + virtual int Inbound(InboundSlot*& s); + virtual void GrantClearance(); + + virtual void AddSlot(const Point& loc, DWORD filter=0xf); + + virtual bool IsLaunchDeck() const { return subtype == FLIGHT_DECK_LAUNCH; } + virtual void SetLaunchDeck() { subtype = FLIGHT_DECK_LAUNCH; } + virtual bool IsRecoveryDeck() const { return subtype == FLIGHT_DECK_RECOVERY; } + virtual void SetRecoveryDeck() { subtype = FLIGHT_DECK_RECOVERY; } + + Point BoundingBox() const { return box; } + Point ApproachPoint(int i) const { return approach_point[i]; } + Point RunwayPoint(int i) const { return runway_point[i]; } + Point StartPoint() const { return start_point; } + Point EndPoint() const { return end_point; } + Point CamLoc() const { return cam_loc; } + double Azimuth() const { return azimuth; } + + virtual void SetBoundingBox(Point dimensions) { box = dimensions; } + virtual void SetApproachPoint(int i, Point loc); + virtual void SetRunwayPoint(int i, Point loc); + virtual void SetStartPoint(Point loc); + virtual void SetEndPoint(Point loc); + virtual void SetCamLoc(Point loc); + virtual void SetCycleTime(double time); + virtual void SetAzimuth(double az) { azimuth = az; } + virtual void SetLight(double l); + + virtual void Orient(const Physical* rep); + + // SimObserver: + virtual bool Update(SimObject* obj); + virtual const char* GetObserverName() const; + + // accessors: + int NumSlots() const { return num_slots; } + double TimeRemaining(int index) const; + int State(int index) const; + int Sequence(int index) const; + const Ship* GetCarrier() const { return carrier; } + int GetIndex() const { return index; } + Ship* GetShip(int index) const; + int NumHoops() const { return num_hoops; } + Hoop* GetHoops() const { return hoops; } + Light* GetLight() { return light; } + + List& GetRecoveryQueue() { return recovery_queue; } + void PrintQueue(); + + bool OverThreshold(Ship* s) const; + bool ContainsPoint(const Point& p) const; + +protected: + Ship* carrier; + int index; + int num_slots; + FlightDeckSlot* slots; + + Point box; + Point start_rel; + Point end_rel; + Point cam_rel; + Point approach_rel[NUM_APPROACH_PTS]; + Point runway_rel[2]; + + Point start_point; + Point end_point; + Point cam_loc; + Point approach_point[NUM_APPROACH_PTS]; + Point runway_point[2]; + + double azimuth; + double cycle_time; + + int num_approach_pts; + int num_catsounds; + int num_hoops; + Hoop* hoops; + Light* light; + List recovery_queue; +}; + +// +----------------------------------------------------------------------+ + +#endif FlightDeck_h + diff --git a/Stars45/FlightPlanner.cpp b/Stars45/FlightPlanner.cpp new file mode 100644 index 0000000..7d2cf7c --- /dev/null +++ b/Stars45/FlightPlanner.cpp @@ -0,0 +1,372 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: FlightPlanner.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Flight Planning class for creating navpoint routes for fighter elements. + Used both by the CarrierAI class and the Flight Dialog. +*/ + +#include "MemDebug.h" +#include "FlightPlanner.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "Element.h" +#include "Instruction.h" +#include "RadioMessage.h" +#include "RadioTraffic.h" +#include "Hangar.h" +#include "FlightDeck.h" +#include "Mission.h" +#include "Contact.h" +#include "Sim.h" +#include "StarSystem.h" +#include "Callsign.h" + +#include "Game.h" +#include "Random.h" + +// +----------------------------------------------------------------------+ + +FlightPlanner::FlightPlanner(Ship* s) + : sim(0), ship(s) +{ + sim = Sim::GetSim(); + + patrol_range = (float) (250e3 + 10e3 * (int) Random(0, 8.9)); +} + +FlightPlanner::~FlightPlanner() +{ } + +// +--------------------------------------------------------------------+ + +void +FlightPlanner::CreatePatrolRoute(Element* elem, int index) +{ + RLoc rloc; + Vec3 dummy(0,0,0); + Point loc = ship->Location(); + double zone = ship->CompassHeading(); + Instruction* instr = 0; + + if (ship->IsAirborne()) + loc.y += 8e3; + else + loc.y += 1e3; + + loc = loc.OtherHand(); + + if (index > 2) + zone += 170*DEGREES; + else if (index > 1) + zone += -90*DEGREES; + else if (index > 0) + zone += 90*DEGREES; + + rloc.SetReferenceLoc(0); + rloc.SetBaseLocation(loc); + rloc.SetDistance(30e3); + rloc.SetDistanceVar(0); + rloc.SetAzimuth(-10*DEGREES + zone); + rloc.SetAzimuthVar(0); + + instr = new(__FILE__,__LINE__) Instruction(ship->GetRegion(), dummy, Instruction::VECTOR); + instr->SetSpeed(750); + instr->GetRLoc() = rloc; + + elem->AddNavPoint(instr); + + rloc.SetReferenceLoc(0); + rloc.SetBaseLocation(loc); + if (ship->IsAirborne()) + rloc.SetDistance(140e3); + else + rloc.SetDistance(220e3); + rloc.SetDistanceVar(50e3); + rloc.SetAzimuth(-20*DEGREES + zone); + rloc.SetAzimuthVar(15*DEGREES); + + instr = new(__FILE__,__LINE__) Instruction(ship->GetRegion(), dummy, Instruction::PATROL); + instr->SetSpeed(500); + instr->GetRLoc() = rloc; + + elem->AddNavPoint(instr); + + rloc.SetReferenceLoc(&instr->GetRLoc()); + rloc.SetDistance(120e3); + rloc.SetDistanceVar(30e3); + rloc.SetAzimuth(60*DEGREES + zone); + rloc.SetAzimuthVar(20*DEGREES); + + instr = new(__FILE__,__LINE__) Instruction(ship->GetRegion(), dummy, Instruction::PATROL); + instr->SetSpeed(350); + instr->GetRLoc() = rloc; + + elem->AddNavPoint(instr); + + rloc.SetReferenceLoc(&instr->GetRLoc()); + rloc.SetDistance(120e3); + rloc.SetDistanceVar(30e3); + rloc.SetAzimuth(120*DEGREES + zone); + rloc.SetAzimuthVar(20*DEGREES); + + instr = new(__FILE__,__LINE__) Instruction(ship->GetRegion(), dummy, Instruction::PATROL); + instr->SetSpeed(350); + instr->GetRLoc() = rloc; + + elem->AddNavPoint(instr); + + rloc.SetReferenceLoc(0); + rloc.SetBaseLocation(loc); + rloc.SetDistance(40e3); + rloc.SetDistanceVar(0); + rloc.SetAzimuth(180*DEGREES + ship->CompassHeading()); + rloc.SetAzimuthVar(0*DEGREES); + + instr = new(__FILE__,__LINE__) Instruction(ship->GetRegion(), dummy, Instruction::RTB); + instr->SetSpeed(500); + instr->GetRLoc() = rloc; + + elem->AddNavPoint(instr); +} + +// +--------------------------------------------------------------------+ + +void +FlightPlanner::CreateStrikeRoute(Element* elem, Element* target) +{ + if (!elem) return; + + RLoc rloc; + Vec3 dummy(0,0,0); + Point loc = ship->Location(); + double head = ship->CompassHeading() + 15*DEGREES; + double dist = 30e3; + Instruction* instr = 0; + Ship* tgt_ship = 0; + + if (ship->IsAirborne()) + loc += ship->Cam().vup() * 8e3; + else + loc += ship->Cam().vup() * 1e3; + + loc = loc.OtherHand(); + + if (target) + tgt_ship = target->GetShip(1); + + if (tgt_ship) { + double range = Point(tgt_ship->Location() - ship->Location()).length(); + + if (range < 100e3) + dist = 20e3; + } + + rloc.SetReferenceLoc(0); + rloc.SetBaseLocation(loc); + rloc.SetDistance(dist); + rloc.SetDistanceVar(0); + rloc.SetAzimuth(head); + rloc.SetAzimuthVar(2*DEGREES); + + instr = new(__FILE__,__LINE__) Instruction(ship->GetRegion(), dummy, Instruction::VECTOR); + instr->SetSpeed(750); + instr->GetRLoc() = rloc; + + elem->AddNavPoint(instr); + + if (tgt_ship) { + Ship* tgt_ship = target->GetShip(1); + Point tgt = tgt_ship->Location() + tgt_ship->Velocity() * 10; + Point mid = ship->Location() + (tgt - ship->Location()) * 0.5; + double beam = tgt_ship->CompassHeading() + 90*DEGREES; + + if (tgt_ship->IsAirborne()) + tgt += tgt_ship->Cam().vup() * 8e3; + else + tgt += tgt_ship->Cam().vup() * 1e3; + + tgt = tgt.OtherHand(); + mid = mid.OtherHand(); + + if (tgt_ship && tgt_ship->IsStarship()) { + rloc.SetReferenceLoc(0); + rloc.SetBaseLocation(tgt); + rloc.SetDistance(60e3); + rloc.SetDistanceVar(5e3); + rloc.SetAzimuth(beam); + rloc.SetAzimuthVar(5*DEGREES); + + instr = new(__FILE__,__LINE__) Instruction(tgt_ship->GetRegion(), dummy, Instruction::ASSAULT); + instr->SetSpeed(750); + instr->GetRLoc() = rloc; + instr->SetTarget(target->Name()); + instr->SetFormation(Instruction::TRAIL); + + elem->AddNavPoint(instr); + } + + if (tgt_ship && tgt_ship->IsStatic()) { + rloc.SetReferenceLoc(0); + rloc.SetBaseLocation(mid); + rloc.SetDistance(60e3); + rloc.SetDistanceVar(5e3); + rloc.SetAzimuth(beam); + rloc.SetAzimuthVar(15*DEGREES); + + instr = new(__FILE__,__LINE__) Instruction(tgt_ship->GetRegion(), dummy, Instruction::VECTOR); + instr->SetSpeed(750); + instr->GetRLoc() = rloc; + + elem->AddNavPoint(instr); + + rloc.SetReferenceLoc(0); + rloc.SetBaseLocation(tgt); + rloc.SetDistance(40e3); + rloc.SetDistanceVar(5e3); + rloc.SetAzimuth(beam); + rloc.SetAzimuthVar(5*DEGREES); + + int action = Instruction::ASSAULT; + + if (tgt_ship->IsGroundUnit()) + action = Instruction::STRIKE; + + instr = new(__FILE__,__LINE__) Instruction(tgt_ship->GetRegion(), dummy, action); + instr->SetSpeed(750); + instr->GetRLoc() = rloc; + instr->SetTarget(target->Name()); + instr->SetFormation(Instruction::TRAIL); + + elem->AddNavPoint(instr); + } + + else if (tgt_ship && tgt_ship->IsDropship()) { + rloc.SetReferenceLoc(0); + rloc.SetBaseLocation(tgt); + rloc.SetDistance(60e3); + rloc.SetDistanceVar(5e3); + rloc.SetAzimuth(tgt_ship->CompassHeading()); + rloc.SetAzimuthVar(20*DEGREES); + + instr = new(__FILE__,__LINE__) Instruction(tgt_ship->GetRegion(), dummy, Instruction::INTERCEPT); + instr->SetSpeed(750); + instr->GetRLoc() = rloc; + instr->SetTarget(target->Name()); + instr->SetFormation(Instruction::SPREAD); + + elem->AddNavPoint(instr); + } + } + + rloc.SetReferenceLoc(0); + rloc.SetBaseLocation(loc); + rloc.SetDistance(40e3); + rloc.SetDistanceVar(0); + rloc.SetAzimuth(180*DEGREES + ship->CompassHeading()); + rloc.SetAzimuthVar(0*DEGREES); + + instr = new(__FILE__,__LINE__) Instruction(ship->GetRegion(), dummy, Instruction::RTB); + instr->SetSpeed(500); + instr->GetRLoc() = rloc; + + elem->AddNavPoint(instr); +} + +// +--------------------------------------------------------------------+ + +void +FlightPlanner::CreateEscortRoute(Element* elem, Element* ward) +{ + if (!elem) return; + + RLoc rloc; + Vec3 dummy(0,0,0); + Point loc = ship->Location(); + double head = ship->CompassHeading(); + Instruction* instr = 0; + + if (ship->IsAirborne()) + loc += ship->Cam().vup() * 8e3; + else + loc += ship->Cam().vup() * 1e3; + + loc = loc.OtherHand(); + + rloc.SetReferenceLoc(0); + rloc.SetBaseLocation(loc); + rloc.SetDistance(30e3); + rloc.SetDistanceVar(0); + rloc.SetAzimuth(head); + rloc.SetAzimuthVar(0); + + instr = new(__FILE__,__LINE__) Instruction(ship->GetRegion(), dummy, Instruction::VECTOR); + instr->SetSpeed(750); + instr->GetRLoc() = rloc; + + elem->AddNavPoint(instr); + + if (ward && ward->GetShip(1)) { + // follow ward's flight plan: + if (ward->GetFlightPlan().size()) { + ListIter iter = ward->GetFlightPlan(); + + while (++iter) { + Instruction* ward_instr = iter.value(); + + if (ward_instr->Action() != Instruction::RTB) { + rloc.SetReferenceLoc(&ward_instr->GetRLoc()); + rloc.SetDistance(25e3); + rloc.SetDistanceVar(5e3); + rloc.SetAzimuth(0); + rloc.SetAzimuthVar(90*DEGREES); + + instr = new(__FILE__,__LINE__) Instruction(ship->GetRegion(), dummy, Instruction::ESCORT); + instr->SetSpeed(350); + instr->GetRLoc() = rloc; + instr->SetTarget(ward->Name()); + + elem->AddNavPoint(instr); + } + } + } + + // if ward has no flight plan, just go to a point nearby: + else { + rloc.SetReferenceLoc(0); + rloc.SetBaseLocation(ward->GetShip(1)->Location()); + rloc.SetDistance(25e3); + rloc.SetDistanceVar(5e3); + rloc.SetAzimuth(0); + rloc.SetAzimuthVar(90*DEGREES); + + instr = new(__FILE__,__LINE__) Instruction(ship->GetRegion(), dummy, Instruction::DEFEND); + instr->SetSpeed(500); + instr->GetRLoc() = rloc; + instr->SetTarget(ward->Name()); + instr->SetHoldTime(15 * 60); // fifteen minutes + + elem->AddNavPoint(instr); + } + } + + rloc.SetReferenceLoc(0); + rloc.SetBaseLocation(loc); + rloc.SetDistance(40e3); + rloc.SetDistanceVar(0); + rloc.SetAzimuth(180*DEGREES + ship->CompassHeading()); + rloc.SetAzimuthVar(0*DEGREES); + + instr = new(__FILE__,__LINE__) Instruction(ship->GetRegion(), dummy, Instruction::RTB); + instr->SetSpeed(500); + instr->GetRLoc() = rloc; + + elem->AddNavPoint(instr); +} diff --git a/Stars45/FlightPlanner.h b/Stars45/FlightPlanner.h new file mode 100644 index 0000000..1e691cd --- /dev/null +++ b/Stars45/FlightPlanner.h @@ -0,0 +1,51 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: FlightPlanner.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Flight Planning class for creating navpoint routes for fighter elements. + Used both by the CarrierAI class and the Flight Dialog. +*/ + +#ifndef FlightPlanner_h +#define FlightPlanner_h + +#include "Types.h" +#include "Director.h" + +// +--------------------------------------------------------------------+ + +class Sim; +class Ship; +class ShipAI; +class Instruction; +class Hangar; +class Element; + +// +--------------------------------------------------------------------+ + +class FlightPlanner +{ +public: + FlightPlanner(Ship* s); + virtual ~FlightPlanner(); + + virtual void CreatePatrolRoute(Element* elem, int index); + virtual void CreateStrikeRoute(Element* strike, Element* target); + virtual void CreateEscortRoute(Element* escort, Element* ward); + + Sim* sim; + Ship* ship; + float patrol_range; +}; + +// +--------------------------------------------------------------------+ + +#endif FlightPlanner_h + diff --git a/Stars45/FltDlg.cpp b/Stars45/FltDlg.cpp new file mode 100644 index 0000000..6a89b86 --- /dev/null +++ b/Stars45/FltDlg.cpp @@ -0,0 +1,1084 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: FltDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== +*/ + +#include "MemDebug.h" +#include "FltDlg.h" +#include "GameScreen.h" +#include "Sim.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "Hangar.h" +#include "FlightDeck.h" +#include "Element.h" +#include "CombatGroup.h" +#include "Mission.h" +#include "RadioMessage.h" +#include "RadioTraffic.h" +#include "Instruction.h" +#include "FlightPlanner.h" +#include "NetUtil.h" + +#include "Game.h" +#include "Button.h" +#include "ListBox.h" +#include "ComboBox.h" +#include "FormatUtil.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: +DEF_MAP_CLIENT(FltDlg, OnFilter); +DEF_MAP_CLIENT(FltDlg, OnPackage); +DEF_MAP_CLIENT(FltDlg, OnAlert); +DEF_MAP_CLIENT(FltDlg, OnLaunch); +DEF_MAP_CLIENT(FltDlg, OnStandDown); +DEF_MAP_CLIENT(FltDlg, OnRecall); +DEF_MAP_CLIENT(FltDlg, OnClose); +DEF_MAP_CLIENT(FltDlg, OnMissionType); + +// +--------------------------------------------------------------------+ + +static const ShipDesign* design = 0; + +// +--------------------------------------------------------------------+ + +FltDlg::FltDlg(Screen* s, FormDef& def, GameScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), manager(mgr), + ship(0), filter_list(0), hangar_list(0), + package_btn(0), alert_btn(0), launch_btn(0), stand_btn(0), recall_btn(0), + mission_type(-1), flight_planner(0), patrol_pattern(0) +{ + Init(def); +} + +FltDlg::~FltDlg() +{ + delete flight_planner; +} + +// +--------------------------------------------------------------------+ + +void +FltDlg::RegisterControls() +{ + filter_list = (ComboBox*) FindControl(101); + hangar_list = (ListBox*) FindControl(102); + package_btn = (Button*) FindControl(110); + alert_btn = (Button*) FindControl(111); + launch_btn = (Button*) FindControl(112); + stand_btn = (Button*) FindControl(113); + recall_btn = (Button*) FindControl(114); + close_btn = (Button*) FindControl(1); + + if (filter_list) + REGISTER_CLIENT(EID_SELECT, filter_list, FltDlg, OnFilter); + + if (package_btn) + REGISTER_CLIENT(EID_CLICK, package_btn, FltDlg, OnPackage); + + if (alert_btn) + REGISTER_CLIENT(EID_CLICK, alert_btn, FltDlg, OnAlert); + + if (launch_btn) + REGISTER_CLIENT(EID_CLICK, launch_btn, FltDlg, OnLaunch); + + if (stand_btn) + REGISTER_CLIENT(EID_CLICK, stand_btn, FltDlg, OnStandDown); + + if (recall_btn) + REGISTER_CLIENT(EID_CLICK, recall_btn, FltDlg, OnRecall); + + if (close_btn) + REGISTER_CLIENT(EID_CLICK, close_btn, FltDlg, OnClose); + + for (int i = 0; i < 6; i++) { + mission_btn[i] = (Button*) FindControl(210 + i); + if (mission_btn[i]) + REGISTER_CLIENT(EID_CLICK, mission_btn[i], FltDlg, OnMissionType); + } + + objective_list = (ListBox*) FindControl(221); + loadout_list = (ListBox*) FindControl(222); +} + +// +--------------------------------------------------------------------+ + +void +FltDlg::SetShip(Ship* s) +{ + if (ship != s) { + ship = s; + + delete flight_planner; + flight_planner = 0; + + if (filter_list) { + filter_list->ClearItems(); + + if (ship) { + int nsquadrons = 0; + int nslots = 0; + Hangar* hangar = ship->GetHangar(); + + if (hangar) { + nsquadrons = hangar->NumSquadrons(); + + for (int i = 0; i < nsquadrons; i++) { + char filter[64]; + sprintf(filter, "%s %s", + hangar->SquadronDesign(i)->abrv, + hangar->SquadronName(i)); + + filter_list->AddItem(filter); + } + + filter_list->AddItem(Game::GetText("FltDlg.PENDING")); + filter_list->AddItem(Game::GetText("FltDlg.ACTIVE")); + } + + flight_planner = new(__FILE__,__LINE__) FlightPlanner(ship); + } + + OnFilter(0); + } + } +} + +// +--------------------------------------------------------------------+ + +void +FltDlg::ExecFrame() +{ + if (!ship || !ship->GetHangar()) { + manager->HideFltDlg(); + } + else { + UpdateSelection(); + UpdateObjective(); + } +} + +// +--------------------------------------------------------------------+ + +void +FltDlg::UpdateSelection() +{ + if (!filter_list || !hangar_list || !ship) return; + + design = 0; + + bool package = false; + bool alert = false; + bool launch = false; + bool stand = false; + bool recall = false; + + Hangar* hangar = ship->GetHangar(); + int seln = filter_list->GetSelectedIndex(); + char txt[32]; + + // selected squadron: + if (seln < hangar->NumSquadrons()) { + int nslots = hangar->SquadronSize(seln); + + for (int item = 0; item < hangar_list->NumItems(); item++) { + int i = hangar_list->GetItemData(item); + const HangarSlot* s = hangar->GetSlot(seln, i); + + if (hangar->GetState(s) == Hangar::UNAVAIL) + hangar_list->SetItemColor(item, Color::DarkGray); + else if (hangar->GetState(s) == Hangar::MAINT) + hangar_list->SetItemColor(item, Color::Gray); + else + hangar_list->SetItemColor(item, Color::White); + + if (hangar->GetState(s) > Hangar::STORAGE) { + if (hangar->GetShip(s)) + hangar_list->SetItemText(item, 1, hangar->GetShip(s)->Name()); + else if (hangar->GetPackageElement(s)) + hangar_list->SetItemText(item, 1, hangar->GetPackageElement(s)->Name()); + else + hangar_list->SetItemText(item, 1, hangar->SquadronName(seln)); + + if (hangar->GetPackageElement(s)) + hangar_list->SetItemText(item, 3, Mission::RoleName(hangar->GetPackageElement(s)->Type())); + else + hangar_list->SetItemText(item, 3, "--"); + + } + else { + hangar_list->SetItemText(item, 1, "--"); + hangar_list->SetItemText(item, 3, "--"); + } + + hangar_list->SetItemText(item, 2, hangar->StatusName(s)); + + if (hangar->GetState(s) >= Hangar::ACTIVE) { + FormatTime(txt, hangar->GetShip(s)->MissionClock() / 1000); + hangar_list->SetItemText(item, 4, txt); + } + + else if (hangar->GetState(s) || Hangar::MAINT || + hangar->GetState(s) > Hangar::STORAGE) { + FormatTime(txt, hangar->TimeRemaining(s)); + hangar_list->SetItemText(item, 4, txt); + } + + else { + hangar_list->SetItemText(item, 4, ""); + } + + if (hangar_list->IsSelected(item)) { + if (!design) design = hangar->GetDesign(s); + + switch (hangar->GetState(s)) { + case Hangar::STORAGE: alert = true; break; + case Hangar::ALERT: launch = true; + stand = true; break; + case Hangar::QUEUED: stand = true; break; + case Hangar::ACTIVE: recall = true; break; + } + } + } + } + + // selected pending filter: + else if (seln == hangar->NumSquadrons()) { + for (int item = 0; item < hangar_list->NumItems(); item++) { + int f = hangar_list->GetItemData(item, 1); + int i = hangar_list->GetItemData(item, 2); + + int squadron = -1; + int slot = -1; + const HangarSlot* s = 0; + + FlightDeck* deck = ship->GetFlightDeck(f); + + if (deck->IsLaunchDeck()) { + int state = deck->State(i); + int seq = deck->Sequence(i); + double time = deck->TimeRemaining(i); + Ship* deckship = deck->GetShip(i); + + if (deckship) { + hangar_list->SetItemText(item, 1, deckship->Name()); + + for (int a = 0; !s && a < hangar->NumSquadrons(); a++) { + for (int b = 0; !s && b < hangar->SquadronSize(a); b++) { + const HangarSlot* test = hangar->GetSlot(a,b); + if (hangar->GetShip(test) == deckship) { + s = test; + squadron = a; + slot = b; + } + } + } + + if (s) { + hangar_list->SetItemText(item, 2, hangar->StatusName(s)); + if (hangar->GetPackageElement(s)) + hangar_list->SetItemText(item, 3, Mission::RoleName(hangar->GetPackageElement(s)->Type())); + } + } + else { + hangar_list->SetItemText(item, 1, "--"); + hangar_list->SetItemText(item, 2, Game::GetText("FltDlg.Open")); + } + + FormatTime(txt, time); + hangar_list->SetItemText(item, 4, txt); + hangar_list->SetItemData(item, 3, squadron); + hangar_list->SetItemData(item, 4, slot); + + if (hangar_list->IsSelected(item) && s) { + if (!design) design = hangar->GetDesign(s); + + switch (hangar->GetState(s)) { + case Hangar::ALERT: launch = true; + stand = true; break; + case Hangar::QUEUED: stand = true; break; + } + } + } + } + } + + // selected active filter: + else if (seln == hangar->NumSquadrons()+1) { + int last_index = -1; + + for (int item = 0; item < hangar_list->NumItems(); item++) { + int squadron = hangar_list->GetItemData(item, 1); + int slot = hangar_list->GetItemData(item, 2); + + int nslots = hangar->SquadronSize(squadron); + + if (slot >= 0 && slot < nslots) { + const HangarSlot* s = hangar->GetSlot(squadron, slot); + + if (hangar->GetState(s) > Hangar::STORAGE) { + if (hangar->GetShip(s)) + hangar_list->SetItemText(item, 1, hangar->GetShip(s)->Name()); + else if (hangar->GetPackageElement(s)) + hangar_list->SetItemText(item, 1, hangar->GetPackageElement(s)->Name()); + else + hangar_list->SetItemText(item, 1, hangar->SquadronName(squadron)); + + if (hangar->GetPackageElement(s)) + hangar_list->SetItemText(item, 3, Mission::RoleName(hangar->GetPackageElement(s)->Type())); + else + hangar_list->SetItemText(item, 3, "--"); + + hangar_list->SetItemText(item, 2, hangar->StatusName(s)); + + FormatTime(txt, hangar->GetShip(s)->MissionClock() / 1000); + hangar_list->SetItemText(item, 4, txt); + + if (last_index < (int) hangar_list->GetItemData(item)) + last_index = (int) hangar_list->GetItemData(item); + } + else { + hangar_list->RemoveItem(item); + item--; + } + } + else { + hangar_list->RemoveItem(item); + item--; + } + } + + for (int i = 0; i < hangar->NumSquadrons(); i++) { + int nslots = hangar->SquadronSize(i); + + for (int j = 0; j < nslots; j++) { + const HangarSlot* s = hangar->GetSlot(i, j); + + if (hangar->GetState(s) >= Hangar::ACTIVE) { + bool found = false; + + for (int n = 0; !found && n < hangar_list->NumItems(); n++) { + if (hangar_list->GetItemData(n, 1) == (DWORD) i && + hangar_list->GetItemData(n, 2) == (DWORD) j) + found = true; + } + + if (!found) { + last_index++; + + char txt[32]; + sprintf(txt, "%2d ", last_index+1); + hangar_list->AddItemWithData(txt, last_index); // use data for sort + + if (hangar->GetShip(s)) + hangar_list->SetItemText(item, 1, hangar->GetShip(s)->Name()); + + hangar_list->SetItemText(item, 2, hangar->StatusName(s)); + + if (hangar->GetPackageElement(s)) + hangar_list->SetItemText(item, 3, Mission::RoleName(hangar->GetPackageElement(s)->Type())); + + FormatTime(txt, hangar->GetShip(s)->MissionClock() / 1000); + hangar_list->SetItemText(item, 4, txt); + + hangar_list->SetItemData(item, 1, i); + hangar_list->SetItemData(item, 2, j); + + item++; + } + } + } + } + + if (hangar_list->GetSelCount() > 0) + recall = true; + } + + if (package_btn) { + bool pkg_ok = alert && mission_type > -1; + + if (pkg_ok && mission_type > 0) { + pkg_ok = objective_list && + objective_list->GetSelCount() > 0; + + if (pkg_ok) { + int obj_index = objective_list->GetSelection(); + DWORD obj_data = objective_list->GetItemData(obj_index, 2); + + if (obj_data > 1e9) + pkg_ok = false; + } + } + + package_btn->SetEnabled(pkg_ok); + } + + if (alert_btn) { + alert_btn->SetEnabled(alert); + + for (int i = 0; i < 6; i++) + if (mission_btn[i]) + mission_btn[i]->SetEnabled(alert); + } + + if (launch_btn) + launch_btn->SetEnabled(launch); + + if (stand_btn) + stand_btn->SetEnabled(stand); + + if (recall_btn) + recall_btn->SetEnabled(recall); +} + +// +--------------------------------------------------------------------+ + +void +FltDlg::UpdateObjective() +{ + if (!objective_list || mission_type < 0 || !ship) return; + + Sim* sim = Sim::GetSim(); + char txt[32]; + + for (int item = 0; item < objective_list->NumItems(); item++) { + const char* obj_name = objective_list->GetItemText(item); + + Element* elem = sim->FindElement(obj_name); + + // if element has expired, remove it from the objective list + if (!elem || !elem->IsActive() || elem->IsFinished()) { + objective_list->RemoveItem(item); + item--; + } + + // otherwise, the element is still active, so update range/region + else { + Ship* s = elem->GetShip(1); + double r = 0; + bool con = false; + + if (s) { + Point s_loc = s->Location() + s->GetRegion()->Location(); + Point h_loc = ship->Location() + ship->GetRegion()->Location(); + + r = (s_loc - h_loc).length(); + + con = ship->FindContact(s) != 0; + + if (con) { + FormatNumber(txt, r); + } + else { + strcpy(txt, Game::GetText("FltDlg.Unknown").data()); + r = 2e9; + } + } + + objective_list->SetItemText(item, 1, s->GetRegion()->Name()); + + objective_list->SetItemText(item, 2, txt); + objective_list->SetItemData(item, 2, (DWORD) r); + } + } +} + +// +--------------------------------------------------------------------+ + +void +FltDlg::Show() +{ + if (shown) return; + + FormWindow::Show(); + UpdateSelection(); + UpdateObjective(); +} + +// +--------------------------------------------------------------------+ + +void +FltDlg::Hide() +{ + FormWindow::Hide(); +} + +// +--------------------------------------------------------------------+ + +void +FltDlg::OnFilter(AWEvent* event) +{ + if (!filter_list || !hangar_list) return; + + int seln = filter_list->GetSelectedIndex(); + + hangar_list->ClearItems(); + + if (!ship) return; + + Hangar* hangar = ship->GetHangar(); + + // selected squadron: + if (seln < hangar->NumSquadrons()) { + int nslots = hangar->SquadronSize(seln); + + for (int i = 0; i < nslots; i++) { + char txt[32]; + sprintf(txt, " %2d ", i+1); + + const HangarSlot* s = hangar->GetSlot(seln, i); + hangar_list->AddItemWithData(txt, i); + + hangar_list->SetItemText(i, 1, "--"); + hangar_list->SetItemText(i, 2, hangar->StatusName(s)); + + FormatTime(txt, hangar->TimeRemaining(s)); + hangar_list->SetItemText(i, 4, txt); + } + } + + // selected pending filter: + else if (seln == hangar->NumSquadrons()) { + int item = 0; + + for (int f = 0; f < ship->NumFlightDecks(); f++) { + FlightDeck* deck = ship->GetFlightDeck(f); + + if (deck->IsLaunchDeck()) { + for (int i = 0; i < deck->NumSlots(); i++) { + int state = deck->State(i); + int seq = deck->Sequence(i); + double time = deck->TimeRemaining(i); + Ship* deckship = deck->GetShip(i); + + int squadron = -1; + int slot = -1; + + char txt[32]; + sprintf(txt, "%d-%d ", f+1, i+1); + + hangar_list->AddItemWithData(txt, item); // use data for sort + + if (deckship) { + hangar_list->SetItemText(item, 1, deckship->Name()); + + const HangarSlot* s = 0; + + for (int a = 0; !s && a < hangar->NumSquadrons(); a++) { + for (int b = 0; !s && b < hangar->SquadronSize(a); b++) { + const HangarSlot* test = hangar->GetSlot(a, b); + if (hangar->GetShip(test) == deckship) { + s = test; + squadron = a; + slot = b; + } + } + } + + if (s) { + hangar_list->SetItemText(item, 2, hangar->StatusName(s)); + if (hangar->GetPackageElement(s)) + hangar_list->SetItemText(item, 3, Mission::RoleName(hangar->GetPackageElement(s)->Type())); + } + } + else { + hangar_list->SetItemText(item, 1, "--"); + hangar_list->SetItemText(item, 2, Game::GetText("FltDlg.Open")); + } + + + FormatTime(txt, time); + hangar_list->SetItemText(item, 4, txt); + + hangar_list->SetItemData(item, 1, f); + hangar_list->SetItemData(item, 2, i); + hangar_list->SetItemData(item, 3, squadron); + hangar_list->SetItemData(item, 4, slot); + + item++; + } + } + } + } + + // selected active filter: + else if (seln == hangar->NumSquadrons()+1) { + int item = 0; + + for (int i = 0; i < hangar->NumSquadrons(); i++) { + int nslots = hangar->SquadronSize(i); + + for (int j = 0; j < nslots; j++) { + const HangarSlot* s = hangar->GetSlot(i,j); + + if (hangar->GetState(s) >= Hangar::ACTIVE) { + char txt[32]; + sprintf(txt, " %2d ", item+1); + + hangar_list->AddItemWithData(txt, item); // use data for sort + + if (hangar->GetShip(s)) + hangar_list->SetItemText(item, 1, hangar->GetShip(s)->Name()); + + hangar_list->SetItemText(item, 2, hangar->StatusName(s)); + + if (hangar->GetPackageElement(s)) + hangar_list->SetItemText(item, 3, Mission::RoleName(hangar->GetPackageElement(s)->Type())); + + FormatTime(txt, hangar->TimeRemaining(s)); + hangar_list->SetItemText(item, 4, txt); + + hangar_list->SetItemData(item, 1, i); + hangar_list->SetItemData(item, 2, j); + + item++; + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +FltDlg::OnPackage(AWEvent* event) +{ + if (!filter_list || !hangar_list || !ship) return; + + int code = Mission::PATROL; + + switch (mission_type) { + case 0: code = Mission::PATROL; break; + case 1: code = Mission::INTERCEPT; break; + case 2: code = Mission::ASSAULT; break; + case 3: code = Mission::STRIKE; break; + case 4: code = Mission::ESCORT; break; + case 5: code = Mission::INTEL; break; + } + + int squad = filter_list->GetSelectedIndex(); + Hangar* hangar = ship->GetHangar(); + Sim* sim = Sim::GetSim(); + const char* call = sim->FindAvailCallsign(ship->GetIFF()); + Element* elem = sim->CreateElement(call, ship->GetIFF(), code); + Element* tgt = 0; + FlightDeck* deck = 0; + int queue = 1000; + int* load = 0; + + elem->SetSquadron(hangar->SquadronName(squad)); + elem->SetCarrier(ship); + + if (objective_list) { + int index = objective_list->GetListIndex(); + Text target = objective_list->GetItemText(index); + + Instruction* objective = new(__FILE__,__LINE__) Instruction(code, target.data()); + elem->AddObjective(objective); + + tgt = sim->FindElement(target.data()); + } + + if (loadout_list && design) { + int index = loadout_list->GetListIndex(); + Text loadname = loadout_list->GetItemText(index); + + ListIter sl = (List&) design->loadouts; + while (++sl && !load) { + if (sl->name == loadname) { + load = sl->load; + elem->SetLoadout(load); + } + } + } + + for (int i = 0; i < ship->NumFlightDecks(); i++) { + FlightDeck* d = ship->GetFlightDeck(i); + + if (d && d->IsLaunchDeck()) { + int dq = hangar->PreflightQueue(d); + + if (dq < queue) { + queue = dq; + deck = d; + } + } + } + + int npackage = 0; + int slots[4]; + + for (i = 0; i < 4; i++) + slots[i] = -1; + + for (i = 0; i < hangar_list->NumItems(); i++) { + if (hangar_list->IsSelected(i)) { + int nslot = hangar_list->GetItemData(i); + hangar->GotoAlert(squad, nslot, deck, elem, load, true); + slots[npackage] = nslot; + hangar_list->SetSelected(i, false); + npackage++; + + if (npackage >= 4) + break; + } + } + + NetUtil::SendElemCreate(elem, squad, slots, false); + + if (flight_planner) { + switch (mission_type) { + case 0: + default: + flight_planner->CreatePatrolRoute(elem, patrol_pattern++); + break; + + case 1: + case 2: + case 3: + if (tgt) + flight_planner->CreateStrikeRoute(elem, tgt); + else + flight_planner->CreatePatrolRoute(elem, patrol_pattern++); + break; + + case 4: + if (tgt) + flight_planner->CreateEscortRoute(elem, tgt); + else + flight_planner->CreatePatrolRoute(elem, patrol_pattern++); + break; + } + + if (patrol_pattern < 0 || patrol_pattern > 3) + patrol_pattern = 0; + } +} + +// +--------------------------------------------------------------------+ + +void +FltDlg::OnAlert(AWEvent* event) +{ + if (!filter_list || !hangar_list || !ship) return; + + int squad = filter_list->GetSelectedIndex(); + Hangar* hangar = ship->GetHangar(); + Sim* sim = Sim::GetSim(); + const char* call = sim->FindAvailCallsign(ship->GetIFF()); + Element* elem = sim->CreateElement(call, ship->GetIFF()); + FlightDeck* deck = 0; + int queue = 1000; + const int* load = 0; + + elem->SetSquadron(hangar->SquadronName(squad)); + elem->SetCarrier(ship); + + for (int i = 0; i < ship->NumFlightDecks(); i++) { + FlightDeck* d = ship->GetFlightDeck(i); + + if (d && d->IsLaunchDeck()) { + int dq = hangar->PreflightQueue(d); + + if (dq < queue) { + queue = dq; + deck = d; + } + } + } + + int nalert = 0; + int slots[4]; + + for (i = 0; i < 4; i++) + slots[i] = -1; + + for (i = 0; i < hangar_list->NumItems(); i++) { + if (hangar_list->IsSelected(i)) { + int nslot = hangar_list->GetItemData(i); + slots[nalert] = nslot; + + if (!load) { + const HangarSlot* hangar_slot = hangar->GetSlot(squad, nslot); + if (hangar_slot) { + load = hangar->GetLoadout(hangar_slot); + elem->SetLoadout((int*) load); + } + } + + hangar->GotoAlert(squad, nslot, deck, elem); + hangar_list->SetSelected(i, false); + nalert++; + + if (nalert >= 4) + break; + } + } + + NetUtil::SendElemCreate(elem, squad, slots, true); +} + +// +--------------------------------------------------------------------+ + +void +FltDlg::OnLaunch(AWEvent* event) +{ + if (!filter_list || !hangar_list || !ship) return; + + int squad = filter_list->GetSelectedIndex(); + + Hangar* hangar = ship->GetHangar(); + + // selected squadron: + if (squad < hangar->NumSquadrons()) { + for (int i = 0; i < hangar_list->NumItems(); i++) { + if (hangar_list->IsSelected(i)) { + int nslot = hangar_list->GetItemData(i); + hangar->Launch(squad, nslot); + NetUtil::SendShipLaunch(ship, squad, nslot); + } + } + } + + // selected pending filter: + else if (squad == hangar->NumSquadrons()) { + for (int item = 0; item < hangar_list->NumItems(); item++) { + if (hangar_list->IsSelected(item)) { + int squadron = hangar_list->GetItemData(item, 3); + int slot = hangar_list->GetItemData(item, 4); + + if (squadron >= 0 && slot >= 0) { + hangar->Launch(squadron, slot); + NetUtil::SendShipLaunch(ship, squadron, slot); + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +FltDlg::OnStandDown(AWEvent* event) +{ + if (!filter_list || !hangar_list || !ship) return; + + int seln = filter_list->GetSelectedIndex(); + + Hangar* hangar = ship->GetHangar(); + + // selected squadron: + if (seln < hangar->NumSquadrons()) { + for (int i = 0; i < hangar_list->NumItems(); i++) { + if (hangar_list->IsSelected(i)) { + int nslot = hangar_list->GetItemData(i); + hangar->StandDown(seln, nslot); + } + } + } + + // selected pending filter: + else if (seln == hangar->NumSquadrons()) { + for (int item = 0; item < hangar_list->NumItems(); item++) { + if (hangar_list->IsSelected(item)) { + int squadron = hangar_list->GetItemData(item, 3); + int slot = hangar_list->GetItemData(item, 4); + + if (squadron >= 0 && slot >= 0) + hangar->StandDown(squadron, slot); + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +FltDlg::OnRecall(AWEvent* event) +{ + if (!filter_list || !hangar_list || !ship) return; + + int seln = filter_list->GetSelectedIndex(); + + Hangar* hangar = ship->GetHangar(); + + // selected squadron: or selected active filter: + if (seln < hangar->NumSquadrons() || seln == hangar->NumSquadrons()+1) { + for (int i = 0; i < hangar_list->NumItems(); i++) { + if (hangar_list->IsSelected(i)) { + int nsquad = seln; + int nslot = hangar_list->GetItemData(i); + + if (seln > hangar->NumSquadrons()) { + nsquad = hangar_list->GetItemData(i, 1); + nslot = hangar_list->GetItemData(i, 2); + } + + const HangarSlot* slot = hangar->GetSlot(nsquad, nslot); + Ship* recall = hangar->GetShip(slot); + + if (recall) { + RadioMessage* msg = new(__FILE__,__LINE__) RadioMessage(recall, ship, RadioMessage::RTB); + RadioTraffic::Transmit(msg); + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +FltDlg::OnMissionType(AWEvent* event) +{ + mission_type = -1; + + for (int i = 0; i < 6; i++) { + if (mission_btn[i]) { + if (mission_btn[i] == event->window) { + mission_btn[i]->SetButtonState(1); + mission_type = i; + } + else { + mission_btn[i]->SetButtonState(0); + } + } + } + + if (objective_list && mission_type > -1) { + objective_list->ClearItems(); + + char txt[32]; + Sim* sim = Sim::GetSim(); + ListIter iter = sim->GetElements(); + + while (++iter) { + Element* elem = iter.value(); + + if (!elem->IsActive() || elem->IsFinished() || elem->IsSquadron()) + continue; + + CombatGroup* group = elem->GetCombatGroup(); + int iff = elem->GetIFF(); + Ship* s = elem->GetShip(1); + double r = 0; + bool con = false; + + if (iff != ship->GetIFF()) { + if (elem->IntelLevel() < Intel::LOCATED) + continue; + + if (group && group->IntelLevel() < Intel::LOCATED) + continue; + } + + if (s) { + Point s_loc = s->Location() + s->GetRegion()->Location(); + Point h_loc = ship->Location() + ship->GetRegion()->Location(); + + r = (s_loc - h_loc).length(); + + con = ship->FindContact(s) != 0; + + if (con) { + FormatNumber(txt, r); + } + else { + strcpy(txt, Game::GetText("FltDlg.Unknown").data()); + r = 2e9; + } + } + + switch (mission_type) { + case 1: // INTERCEPT + if (iff && iff != ship->GetIFF() && s && s->IsDropship()) { + int item = objective_list->AddItem(elem->Name()) - 1; + objective_list->SetItemText(item, 1, s->GetRegion()->Name()); + + objective_list->SetItemText(item, 2, txt); + objective_list->SetItemData(item, 2, (DWORD) r); + } + break; + + case 2: // ASSAULT + if (iff && iff != ship->GetIFF() && s && (s->IsStarship() || s->IsStatic())) { + int item = objective_list->AddItem(elem->Name()) - 1; + objective_list->SetItemText(item, 1, s->GetRegion()->Name()); + + objective_list->SetItemText(item, 2, txt); + objective_list->SetItemData(item, 2, (DWORD) r); + } + break; + + case 3: // STRIKE + if (iff && iff != ship->GetIFF() && s && s->IsGroundUnit()) { + int item = objective_list->AddItem(elem->Name()) - 1; + objective_list->SetItemText(item, 1, s->GetRegion()->Name()); + + objective_list->SetItemText(item, 2, txt); + objective_list->SetItemData(item, 2, (DWORD) r); + } + break; + + case 4: // ESCORT + if ((iff == 0 || iff == ship->GetIFF()) && (!s || !s->IsStatic())) { + int item = objective_list->AddItem(elem->Name()) - 1; + + if (s) { + objective_list->SetItemText(item, 1, s->GetRegion()->Name()); + objective_list->SetItemText(item, 2, txt); + objective_list->SetItemData(item, 2, (DWORD) r); + } + + else { + objective_list->SetItemText(item, 1, "0"); + objective_list->SetItemData(item, 1, 0); + } + } + break; + + case 5: // SCOUT? + break; + + default: break; + } + } + } + + if (loadout_list && mission_type > -1) { + loadout_list->ClearItems(); + + if (design) { + ListIter sl = (List&) design->loadouts; + while (++sl) { + int item = loadout_list->AddItem(sl->name) - 1; + + char weight[32]; + sprintf(weight, "%d kg", (int) ((design->mass + sl->mass) * 1000)); + loadout_list->SetItemText(item, 1, weight); + loadout_list->SetItemData(item, 1, (DWORD) (sl->mass * 1000)); + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +FltDlg::OnClose(AWEvent* event) +{ + if (manager) + manager->CloseTopmost(); +} + + + diff --git a/Stars45/FltDlg.h b/Stars45/FltDlg.h new file mode 100644 index 0000000..13deb42 --- /dev/null +++ b/Stars45/FltDlg.h @@ -0,0 +1,81 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: FltDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Flight Operations Active Window class +*/ + +#ifndef FltDlg_h +#define FltDlg_h + +#include "Types.h" +#include "FormWindow.h" + +// +--------------------------------------------------------------------+ + +class FlightPlanner; +class GameScreen; +class Ship; + +// +--------------------------------------------------------------------+ + +class FltDlg : public FormWindow +{ +public: + FltDlg(Screen* s, FormDef& def, GameScreen* mgr); + virtual ~FltDlg(); + + virtual void RegisterControls(); + + // Operations: + virtual void Show(); + virtual void Hide(); + + virtual void OnFilter(AWEvent* event); + virtual void OnPackage(AWEvent* event); + virtual void OnAlert(AWEvent* event); + virtual void OnLaunch(AWEvent* event); + virtual void OnStandDown(AWEvent* event); + virtual void OnRecall(AWEvent* event); + virtual void OnClose(AWEvent* event); + virtual void OnMissionType(AWEvent* event); + + virtual void ExecFrame(); + void SetShip(Ship* s); + void UpdateSelection(); + void UpdateObjective(); + +protected: + GameScreen* manager; + + ComboBox* filter_list; + ListBox* hangar_list; + + Button* package_btn; + Button* alert_btn; + Button* launch_btn; + Button* stand_btn; + Button* recall_btn; + Button* close_btn; + + int mission_type; + Button* mission_btn[6]; + + ListBox* objective_list; + ListBox* loadout_list; + + Ship* ship; + FlightPlanner* flight_planner; + + int patrol_pattern; +}; + +#endif FltDlg_h + diff --git a/Stars45/Galaxy.cpp b/Stars45/Galaxy.cpp new file mode 100644 index 0000000..1862459 --- /dev/null +++ b/Stars45/Galaxy.cpp @@ -0,0 +1,283 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Galaxy.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Galaxy (list of star systems) for a single campaign. +*/ + +#include "MemDebug.h" +#include "Galaxy.h" +#include "StarSystem.h" +#include "Starshatter.h" + +#include "Game.h" +#include "Solid.h" +#include "Sprite.h" +#include "Light.h" +#include "Bitmap.h" +#include "DataLoader.h" +#include "ParseUtil.h" + +static Galaxy* galaxy = 0; + +// +--------------------------------------------------------------------+ + +Galaxy::Galaxy(const char* n) + : name(n), radius(10) +{ } + +// +--------------------------------------------------------------------+ + +Galaxy::~Galaxy() +{ + Print(" Destroying Galaxy %s\n", (const char*) name); + systems.destroy(); + stars.destroy(); +} + +// +--------------------------------------------------------------------+ + +void +Galaxy::Initialize() +{ + if (galaxy) delete galaxy; + galaxy = new(__FILE__,__LINE__) Galaxy("Galaxy"); + galaxy->Load(); +} + +void +Galaxy::Close() +{ + delete galaxy; + galaxy = 0; +} + +Galaxy* +Galaxy::GetInstance() +{ + return galaxy; +} + +// +--------------------------------------------------------------------+ + +void +Galaxy::Load() +{ + DataLoader* loader = DataLoader::GetLoader(); + loader->SetDataPath("Galaxy/"); + sprintf(filename, "%s.def", (const char*) name); + Load(filename); + + // load mod galaxies: + List mod_galaxies; + loader->SetDataPath("Mods/Galaxy/"); + loader->ListFiles("*.def", mod_galaxies); + + ListIter iter = mod_galaxies; + while (++iter) { + Text* name = iter.value(); + + if (!name->contains("/")) { + loader->SetDataPath("Mods/Galaxy/"); + Load(name->data()); + } + } +} + +void +Galaxy::Load(const char* filename) +{ + Print("\nLoading Galaxy: %s\n", filename); + + BYTE* block = 0; + DataLoader* loader = DataLoader::GetLoader(); + loader->LoadBuffer(filename, block, true); + + Parser parser(new(__FILE__,__LINE__) BlockReader((const char*) block)); + + Term* term = parser.ParseTerm(); + + if (!term) { + Print("WARNING: could not parse '%s'\n", filename); + return; + } + else { + TermText* file_type = term->isText(); + if (!file_type || file_type->value() != "GALAXY") { + Print("WARNING: invalid galaxy file '%s'\n", filename); + return; + } + } + + // parse the galaxy: + do { + delete term; + term = parser.ParseTerm(); + + if (term) { + TermDef* def = term->isDef(); + if (def) { + if (def->name()->value() == "radius") { + GetDefNumber(radius, def, filename); + } + + else if (def->name()->value() == "system") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: system struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + + char sys_name[32]; + char classname[32]; + Vec3 sys_loc; + int sys_iff = 0; + int star_class = Star::G; + + sys_name[0] = 0; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + if (pdef->name()->value() == "name") + GetDefText(sys_name, pdef, filename); + + else if (pdef->name()->value() == "loc") + GetDefVec(sys_loc, pdef, filename); + + else if (pdef->name()->value() == "iff") + GetDefNumber(sys_iff, pdef, filename); + + else if (pdef->name()->value() == "class") { + GetDefText(classname, pdef, filename); + + switch (classname[0]) { + case 'O': star_class = Star::O; break; + case 'B': star_class = Star::B; break; + case 'A': star_class = Star::A; break; + case 'F': star_class = Star::F; break; + case 'G': star_class = Star::G; break; + case 'K': star_class = Star::K; break; + case 'M': star_class = Star::M; break; + case 'R': star_class = Star::RED_GIANT; break; + case 'W': star_class = Star::WHITE_DWARF; break; + case 'Z': star_class = Star::BLACK_HOLE; break; + } + } + } + } + + if (sys_name[0]) { + StarSystem* star_system = new(__FILE__,__LINE__) StarSystem(sys_name, sys_loc, sys_iff, star_class); + star_system->Load(); + systems.append(star_system); + + Star* star = new(__FILE__,__LINE__) Star(sys_name, sys_loc, star_class); + stars.append(star); + } + } + } + + else if (def->name()->value() == "star") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: star struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + + char star_name[32]; + char classname[32]; + Vec3 star_loc; + int star_class = Star::G; + + star_name[0] = 0; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + if (pdef->name()->value() == "name") + GetDefText(star_name, pdef, filename); + + else if (pdef->name()->value() == "loc") + GetDefVec(star_loc, pdef, filename); + + else if (pdef->name()->value() == "class") { + GetDefText(classname, pdef, filename); + + switch (classname[0]) { + case 'O': star_class = Star::O; break; + case 'B': star_class = Star::B; break; + case 'A': star_class = Star::A; break; + case 'F': star_class = Star::F; break; + case 'G': star_class = Star::G; break; + case 'K': star_class = Star::K; break; + case 'M': star_class = Star::M; break; + case 'R': star_class = Star::RED_GIANT; break; + case 'W': star_class = Star::WHITE_DWARF; break; + case 'Z': star_class = Star::BLACK_HOLE; break; + } + } + } + } + + if (star_name[0]) { + Star* star = new(__FILE__,__LINE__) Star(star_name, star_loc, star_class); + stars.append(star); + } + } + } + } + } + } + while (term); + + loader->ReleaseBuffer(block); + loader->SetDataPath(0); +} + +// +--------------------------------------------------------------------+ + +void +Galaxy::ExecFrame() +{ + ListIter sys = systems; + while (++sys) { + sys->ExecFrame(); + } +} + +// +--------------------------------------------------------------------+ + +StarSystem* +Galaxy::GetSystem(const char* name) +{ + ListIter sys = systems; + while (++sys) { + if (!strcmp(sys->Name(), name)) + return sys.value(); + } + + return 0; +} + +// +--------------------------------------------------------------------+ + +StarSystem* +Galaxy::FindSystemByRegion(const char* rgn_name) +{ + ListIter iter = systems; + while (++iter) { + StarSystem* sys = iter.value(); + if (sys->FindRegion(rgn_name)) + return sys; + } + + return 0; +} diff --git a/Stars45/Galaxy.h b/Stars45/Galaxy.h new file mode 100644 index 0000000..deacefb --- /dev/null +++ b/Stars45/Galaxy.h @@ -0,0 +1,74 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Galaxy.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Galaxy (list of star systems) for a single campaign. +*/ + +#ifndef Galaxy_h +#define Galaxy_h + +#include "Types.h" +#include "Solid.h" +#include "Bitmap.h" +#include "Geometry.h" +#include "Text.h" +#include "Term.h" +#include "List.h" + +// +--------------------------------------------------------------------+ + +class Star; +class StarSystem; +class Graphic; +class Light; +class Scene; + +// +--------------------------------------------------------------------+ + +class Galaxy +{ +public: + Galaxy(const char* name); + virtual ~Galaxy(); + + int operator == (const Galaxy& s) const { return name == s.name; } + + // operations: + virtual void Load(); + virtual void Load(const char* filename); + virtual void ExecFrame(); + + // accessors: + const char* Name() const { return name; } + const char* Description() const { return description; } + List& GetSystemList() { return systems; } + List& Stars() { return stars; } + double Radius() const { return radius; } + + StarSystem* GetSystem(const char* name); + StarSystem* FindSystemByRegion(const char* rgn_name); + + static void Initialize(); + static void Close(); + static Galaxy* GetInstance(); + +protected: + char filename[64]; + Text name; + Text description; + double radius; // radius in parsecs + + List systems; + List stars; +}; + +#endif Galaxy_h + diff --git a/Stars45/GameScreen.cpp b/Stars45/GameScreen.cpp new file mode 100644 index 0000000..3d6471a --- /dev/null +++ b/Stars45/GameScreen.cpp @@ -0,0 +1,1254 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars + FILE: GameScreen.cpp + AUTHOR: John DiCamillo + +*/ + +#include "MemDebug.h" +#include "GameScreen.h" + +#include "Sim.h" +#include "Ship.h" +#include "DisplayView.h" +#include "HUDView.h" +#include "HUDSounds.h" +#include "WepView.h" +#include "QuantumView.h" +#include "QuitView.h" +#include "RadioView.h" +#include "TacticalView.h" +#include "NavDlg.h" +#include "EngDlg.h" +#include "FltDlg.h" +#include "AudDlg.h" +#include "VidDlg.h" +#include "OptDlg.h" +#include "CtlDlg.h" +#include "KeyDlg.h" +#include "JoyDlg.h" +#include "ModDlg.h" +#include "ModInfoDlg.h" +#include "FormDef.h" +#include "Starshatter.h" +#include "CameraDirector.h" + +#include "Game.h" +#include "Video.h" +#include "Screen.h" +#include "Window.h" +#include "ActiveWindow.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "MouseController.h" +#include "CameraView.h" +#include "FadeView.h" +#include "Color.h" +#include "Bitmap.h" +#include "Font.h" +#include "FontMgr.h" +#include "EventDispatch.h" +#include "DataLoader.h" +#include "Resource.h" + +// +--------------------------------------------------------------------+ + +GameScreen* GameScreen::game_screen = 0; + +static MouseController* mouse_con = 0; +static bool mouse_active = false; + +// +--------------------------------------------------------------------+ + +GameScreen::GameScreen() + : screen(0), gamewin(0), + navdlg(0), wep_view(0), engdlg(0), fltdlg(0), + HUDfont(0), GUIfont(0), GUI_small_font(0), title_font(0), cam_view(0), + isShown(false), disp_view(0), hud_view(0), tac_view(0), radio_view(0), + quantum_view(0), quit_view(0), ctldlg(0), joydlg(0), keydlg(0), auddlg(0), + viddlg(0), moddlg(0), modInfoDlg(0), + flare1(0), flare2(0), flare3(0), flare4(0), + optdlg(0), cam_dir(0), sim(0) +{ + cam_dir = new(__FILE__,__LINE__) CameraDirector; + sim = Sim::GetSim(); + loader = DataLoader::GetLoader(); + + // find the fonts: + HUDfont = FontMgr::Find("HUD"); + GUIfont = FontMgr::Find("GUI"); + GUI_small_font = FontMgr::Find("GUIsmall"); + title_font = FontMgr::Find("Title"); + + loader->LoadTexture("flare0+.pcx", flare1, Bitmap::BMP_TRANSLUCENT); + loader->LoadTexture("flare2.pcx", flare2, Bitmap::BMP_TRANSLUCENT); + loader->LoadTexture("flare3.pcx", flare3, Bitmap::BMP_TRANSLUCENT); + loader->LoadTexture("flare4.pcx", flare4, Bitmap::BMP_TRANSLUCENT); + + mouse_con = MouseController::GetInstance(); + game_screen = this; +} + +GameScreen::~GameScreen() +{ + TearDown(); + game_screen = 0; +} + +// +--------------------------------------------------------------------+ + +void +GameScreen::Setup(Screen* s) +{ + if (!s) + return; + + screen = s; + + loader->UseFileSystem(true); + + // create windows + gamewin = new(__FILE__,__LINE__) Window(screen, 0, 0, screen->Width(), screen->Height()); + + // attach views to windows (back to front) + // fade in: + gamewin->AddView(new(__FILE__,__LINE__) FadeView(gamewin, 1, 0, 0)); + + // camera: + cam_dir = CameraDirector::GetInstance(); + cam_view = new(__FILE__,__LINE__) CameraView(gamewin, cam_dir->GetCamera(), 0); + + if (cam_view) + gamewin->AddView(cam_view); + + // HUD: + hud_view = new(__FILE__,__LINE__) HUDView(gamewin); + gamewin->AddView(hud_view); + + wep_view = new(__FILE__,__LINE__) WepView(gamewin); + gamewin->AddView(wep_view); + + quantum_view = new(__FILE__,__LINE__) QuantumView(gamewin); + gamewin->AddView(quantum_view); + + radio_view = new(__FILE__,__LINE__) RadioView(gamewin); + gamewin->AddView(radio_view); + + tac_view = new(__FILE__,__LINE__) TacticalView(gamewin, this); + gamewin->AddView(tac_view); + + disp_view = DisplayView::GetInstance(); + + // note: quit view must be last in chain + // so it can release window surface + quit_view = new(__FILE__,__LINE__) QuitView(gamewin); + gamewin->AddView(quit_view); + + Starshatter* stars = Starshatter::GetInstance(); + + // initialize lens flare bitmaps: + if (stars->LensFlare()) { + cam_view->LensFlareElements(flare1, flare4, flare2, flare3); + cam_view->LensFlare(true); + } + + // if lens flare disabled, just create the corona: + else if (stars->Corona()) { + cam_view->LensFlareElements(flare1, 0, 0, 0); + cam_view->LensFlare(true); + } + + screen->AddWindow(gamewin); + + FormDef aud_def("AudDlg", 0); + aud_def.Load("AudDlg"); + auddlg = new(__FILE__,__LINE__) AudDlg(screen, aud_def, this); + + FormDef ctl_def("CtlDlg", 0); + ctl_def.Load("CtlDlg"); + ctldlg = new(__FILE__,__LINE__) CtlDlg(screen, ctl_def, this); + + FormDef opt_def("OptDlg", 0); + opt_def.Load("OptDlg"); + optdlg = new(__FILE__,__LINE__) OptDlg(screen, opt_def, this); + + FormDef vid_def("VidDlg", 0); + vid_def.Load("VidDlg"); + viddlg = new(__FILE__,__LINE__) VidDlg(screen, vid_def, this); + + FormDef mod_def("ModDlg", 0); + mod_def.Load("ModDlg"); + moddlg = new(__FILE__,__LINE__) ModDlg(screen, mod_def, this); + + FormDef joy_def("JoyDlg", 0); + joy_def.Load("JoyDlg"); + joydlg = new(__FILE__,__LINE__) JoyDlg(screen, joy_def, this); + + FormDef key_def("KeyDlg", 0); + key_def.Load("KeyDlg"); + keydlg = new(__FILE__,__LINE__) KeyDlg(screen, key_def, this); + + FormDef mod_info_def("ModInfoDlg", 0); + mod_info_def.Load("ModInfoDlg"); + modInfoDlg = new(__FILE__,__LINE__) ModInfoDlg(screen, mod_info_def, this); + + FormDef nav_def("NavDlg", 0); + nav_def.Load("NavDlg"); + navdlg = new(__FILE__,__LINE__) NavDlg(screen, nav_def, this); + + FormDef eng_def("EngDlg", 0); + eng_def.Load("EngDlg"); + engdlg = new(__FILE__,__LINE__) EngDlg(screen, eng_def, this); + + FormDef flt_def("FltDlg", 0); + flt_def.Load("FltDlg"); + fltdlg = new(__FILE__,__LINE__) FltDlg(screen, flt_def, this); + + if (engdlg) engdlg->Hide(); + if (fltdlg) fltdlg->Hide(); + if (navdlg) navdlg->Hide(); + if (auddlg) auddlg->Hide(); + if (viddlg) viddlg->Hide(); + if (optdlg) optdlg->Hide(); + if (ctldlg) ctldlg->Hide(); + if (keydlg) keydlg->Hide(); + if (joydlg) joydlg->Hide(); + if (moddlg) moddlg->Hide(); + if (modInfoDlg) modInfoDlg->Hide(); + + screen->SetBackgroundColor(Color::Black); + + frame_rate = 0; + + loader->UseFileSystem(Starshatter::UseFileSystem()); +} + +// +--------------------------------------------------------------------+ + +void +GameScreen::TearDown() +{ + if (gamewin && disp_view) + gamewin->DelView(disp_view); + + if (screen) { + screen->DelWindow(engdlg); + screen->DelWindow(fltdlg); + screen->DelWindow(navdlg); + screen->DelWindow(modInfoDlg); + screen->DelWindow(auddlg); + screen->DelWindow(viddlg); + screen->DelWindow(optdlg); + screen->DelWindow(moddlg); + screen->DelWindow(ctldlg); + screen->DelWindow(keydlg); + screen->DelWindow(joydlg); + screen->DelWindow(gamewin); + } + + delete engdlg; + delete fltdlg; + delete navdlg; + delete modInfoDlg; + delete auddlg; + delete viddlg; + delete optdlg; + delete moddlg; + delete ctldlg; + delete keydlg; + delete joydlg; + delete gamewin; + delete cam_dir; + + engdlg = 0; + fltdlg = 0; + navdlg = 0; + modInfoDlg = 0; + auddlg = 0; + viddlg = 0; + optdlg = 0; + moddlg = 0; + ctldlg = 0; + keydlg = 0; + joydlg = 0; + gamewin = 0; + screen = 0; + cam_dir = 0; + cam_view = 0; + disp_view = 0; +} + +// +--------------------------------------------------------------------+ + +void +GameScreen::FrameRate(double f) +{ + frame_rate = f; +} + +// +--------------------------------------------------------------------+ + +void +GameScreen::SetFieldOfView(double fov) +{ + cam_view->SetFieldOfView(fov); +} + +// +--------------------------------------------------------------------+ + +double +GameScreen::GetFieldOfView() const +{ + return cam_view->GetFieldOfView(); +} + +// +--------------------------------------------------------------------+ + +Bitmap* +GameScreen::GetLensFlare(int index) +{ + switch (index) { + case 0: return flare1; + case 1: return flare2; + case 2: return flare3; + case 3: return flare4; + } + + return 0; +} + +// +--------------------------------------------------------------------+ + +void +GameScreen::ExecFrame() +{ + sim = Sim::GetSim(); + + if (sim) { + cam_view->UseCamera(CameraDirector::GetInstance()->GetCamera()); + cam_view->UseScene(sim->GetScene()); + + Ship* player = sim->GetPlayerShip(); + + if (player) { + bool dialog_showing = false; + + if (hud_view) { + hud_view->UseCameraView(cam_view); + hud_view->ExecFrame(); + } + + if (quit_view && quit_view->IsMenuShown()) { + quit_view->ExecFrame(); + dialog_showing = true; + } + + if (navdlg && navdlg->IsShown()) { + navdlg->SetShip(player); + navdlg->ExecFrame(); + dialog_showing = true; + } + + if (engdlg && engdlg->IsShown()) { + engdlg->SetShip(player); + engdlg->ExecFrame(); + dialog_showing = true; + } + + if (fltdlg && fltdlg->IsShown()) { + fltdlg->SetShip(player); + fltdlg->ExecFrame(); + dialog_showing = true; + } + + if (auddlg && auddlg->IsShown()) { + auddlg->ExecFrame(); + dialog_showing = true; + } + + if (viddlg && viddlg->IsShown()) { + viddlg->ExecFrame(); + dialog_showing = true; + } + + if (optdlg && optdlg->IsShown()) { + optdlg->ExecFrame(); + dialog_showing = true; + } + + if (ctldlg && ctldlg->IsShown()) { + ctldlg->ExecFrame(); + dialog_showing = true; + } + + if (keydlg && keydlg->IsShown()) { + keydlg->ExecFrame(); + dialog_showing = true; + } + + if (joydlg && joydlg->IsShown()) { + joydlg->ExecFrame(); + dialog_showing = true; + } + + if (moddlg && moddlg->IsShown()) { + moddlg->ExecFrame(); + dialog_showing = true; + } + + if (quantum_view && !dialog_showing) { + quantum_view->ExecFrame(); + } + + if (radio_view && !dialog_showing) { + radio_view->ExecFrame(); + } + + if (wep_view && !dialog_showing) { + wep_view->ExecFrame(); + } + + if (tac_view && !dialog_showing) { + if (cam_view) + tac_view->UseProjector(cam_view->GetProjector()); + tac_view->ExecFrame(); + } + } + + if (disp_view) { + disp_view->ExecFrame(); + } + } + + Starshatter* stars = Starshatter::GetInstance(); + + if (stars) { + if (stars->LensFlare()) { + cam_view->LensFlareElements(flare1, flare4, flare2, flare3); + cam_view->LensFlare(true); + } + + else if (stars->Corona()) { + cam_view->LensFlareElements(flare1, 0, 0, 0); + cam_view->LensFlare(true); + } + + else { + cam_view->LensFlare(false); + } + } +} + +// +--------------------------------------------------------------------+ + +void +GameScreen::CycleMFDMode(int mfd) +{ + if (hud_view) + hud_view->CycleMFDMode(mfd); +} + +void +GameScreen::CycleHUDMode() +{ + if (hud_view) + hud_view->CycleHUDMode(); +} + +void +GameScreen::CycleHUDColor() +{ + if (hud_view) + hud_view->CycleHUDColor(); +} + +void +GameScreen::CycleHUDWarn() +{ + if (hud_view) + hud_view->CycleHUDWarn(); +} + +// +--------------------------------------------------------------------+ + +bool +GameScreen::CloseTopmost() +{ + bool processed = false; + + if (!gamewin) return processed; + + if (navdlg && navdlg->IsShown()) { + HideNavDlg(); + processed = true; + } + + else if (engdlg && engdlg->IsShown()) { + HideEngDlg(); + processed = true; + } + + else if (fltdlg && fltdlg->IsShown()) { + HideFltDlg(); + processed = true; + } + + else if (modInfoDlg && modInfoDlg->IsShown()) { + HideModInfoDlg(); + processed = true; + } + + else if (keydlg && keydlg->IsShown()) { + ShowCtlDlg(); + processed = true; + } + + else if (joydlg && joydlg->IsShown()) { + ShowCtlDlg(); + processed = true; + } + + else if (auddlg && auddlg->IsShown()) { + CancelOptions(); + processed = true; + } + + else if (viddlg && viddlg->IsShown()) { + CancelOptions(); + processed = true; + } + + else if (optdlg && optdlg->IsShown()) { + CancelOptions(); + processed = true; + } + + else if (moddlg && moddlg->IsShown()) { + CancelOptions(); + processed = true; + } + + else if (ctldlg && ctldlg->IsShown()) { + CancelOptions(); + processed = true; + } + + else if (quantum_view && quantum_view->IsMenuShown()) { + quantum_view->CloseMenu(); + processed = true; + } + + else if (quit_view && quit_view->IsMenuShown()) { + quit_view->CloseMenu(); + processed = true; + } + + else if (radio_view && radio_view->IsMenuShown()) { + radio_view->CloseMenu(); + processed = true; + } + + return processed; +} + +static Window* old_disp_win = 0; + +void +GameScreen::Show() +{ + if (!isShown) { + screen->AddWindow(gamewin); + isShown = true; + + if (disp_view) { + old_disp_win = disp_view->GetWindow(); + + disp_view->SetWindow(gamewin); + gamewin->AddView(disp_view); + } + } +} + +void +GameScreen::Hide() +{ + if (isShown) { + HideAll(); + + if (disp_view && gamewin) { + gamewin->DelView(disp_view); + disp_view->SetWindow(old_disp_win); + } + + if (engdlg) engdlg->SetShip(0); + if (fltdlg) fltdlg->SetShip(0); + if (navdlg) navdlg->SetShip(0); + + HUDSounds::StopSound(HUDSounds::SND_RED_ALERT); + + screen->DelWindow(gamewin); + isShown = false; + } +} + +// +--------------------------------------------------------------------+ + +bool +GameScreen::IsFormShown() const +{ + bool form_shown = false; + + if (navdlg && navdlg->IsShown()) + form_shown = true; + + else if (engdlg && engdlg->IsShown()) + form_shown = true; + + else if (fltdlg && fltdlg->IsShown()) + form_shown = true; + + else if (auddlg && auddlg->IsShown()) + form_shown = true; + + else if (viddlg && viddlg->IsShown()) + form_shown = true; + + else if (optdlg && optdlg->IsShown()) + form_shown = true; + + else if (moddlg && moddlg->IsShown()) + form_shown = true; + + else if (ctldlg && ctldlg->IsShown()) + form_shown = true; + + else if (keydlg && keydlg->IsShown()) + form_shown = true; + + else if (joydlg && joydlg->IsShown()) + form_shown = true; + + return form_shown; +} + +// +--------------------------------------------------------------------+ + +void +GameScreen::ShowExternal() +{ + if (!gamewin) return; + + if ((navdlg && navdlg->IsShown()) || + (engdlg && engdlg->IsShown()) || + (fltdlg && fltdlg->IsShown()) || + (auddlg && auddlg->IsShown()) || + (viddlg && viddlg->IsShown()) || + (optdlg && optdlg->IsShown()) || + (moddlg && moddlg->IsShown()) || + (ctldlg && ctldlg->IsShown()) || + (keydlg && keydlg->IsShown()) || + (joydlg && joydlg->IsShown())) + return; + + gamewin->MoveTo(Rect(0, 0, screen->Width(), screen->Height())); + screen->AddWindow(gamewin); +} + +// +--------------------------------------------------------------------+ + +void +GameScreen::ShowNavDlg() +{ + if (!gamewin) return; + + if (navdlg && !navdlg->IsShown()) { + HideAll(); + + navdlg->SetSystem(sim->GetStarSystem()); + navdlg->SetShip(sim->GetPlayerShip()); + navdlg->Show(); + + if (mouse_con) { + mouse_active = mouse_con->Active(); + mouse_con->SetActive(false); + } + + Mouse::Show(true); + } + else { + HideNavDlg(); + } +} + +// +--------------------------------------------------------------------+ + +void +GameScreen::HideNavDlg() +{ + if (!gamewin) return; + + if (navdlg && navdlg->IsShown()) { + navdlg->Hide(); + + if (mouse_con) + mouse_con->SetActive(mouse_active); + + Mouse::Show(false); + screen->AddWindow(gamewin); + } +} + +// +--------------------------------------------------------------------+ + +bool +GameScreen::IsNavShown() +{ + return gamewin && navdlg && navdlg->IsShown(); +} + +// +--------------------------------------------------------------------+ + +void +GameScreen::ShowEngDlg() +{ + if (!gamewin) return; + + if (engdlg && !engdlg->IsShown()) { + HideAll(); + + engdlg->SetShip(sim->GetPlayerShip()); + engdlg->Show(); + + if (mouse_con) { + mouse_active = mouse_con->Active(); + mouse_con->SetActive(false); + } + + Mouse::Show(true); + } + else { + HideEngDlg(); + } +} + +// +--------------------------------------------------------------------+ + +void +GameScreen::HideEngDlg() +{ + if (!gamewin) return; + + if (engdlg && engdlg->IsShown()) { + engdlg->Hide(); + + if (mouse_con) + mouse_con->SetActive(mouse_active); + + Mouse::Show(false); + screen->AddWindow(gamewin); + } +} + +// +--------------------------------------------------------------------+ + +bool +GameScreen::IsEngShown() +{ + return gamewin && engdlg && engdlg->IsShown(); +} + +// +--------------------------------------------------------------------+ + +void +GameScreen::ShowFltDlg() +{ + if (!gamewin) return; + + if (fltdlg && !fltdlg->IsShown()) { + HideAll(); + + fltdlg->SetShip(sim->GetPlayerShip()); + fltdlg->Show(); + + if (mouse_con) { + mouse_active = mouse_con->Active(); + mouse_con->SetActive(false); + } + + Mouse::Show(true); + } + else { + HideFltDlg(); + } +} + +// +--------------------------------------------------------------------+ + +void +GameScreen::HideFltDlg() +{ + if (!gamewin) return; + + if (fltdlg && fltdlg->IsShown()) { + fltdlg->Hide(); + + if (mouse_con) + mouse_con->SetActive(mouse_active); + + Mouse::Show(false); + screen->AddWindow(gamewin); + } +} + +// +--------------------------------------------------------------------+ + +bool +GameScreen::IsFltShown() +{ + return gamewin && fltdlg && fltdlg->IsShown(); +} + +// +--------------------------------------------------------------------+ + +void +GameScreen::ShowAudDlg() +{ + if (auddlg) { + if (quit_view) { + quit_view->CloseMenu(); + Starshatter::GetInstance()->Pause(true); + } + + HideAll(); + + auddlg->Show(); + auddlg->SetTopMost(true); + + if (mouse_con) { + mouse_active = mouse_con->Active(); + mouse_con->SetActive(false); + } + + Mouse::Show(true); + } +} + +// +--------------------------------------------------------------------+ + +void +GameScreen::HideAudDlg() +{ + if (auddlg && auddlg->IsShown()) { + auddlg->Hide(); + Mouse::Show(false); + screen->AddWindow(gamewin); + + if (quit_view) + quit_view->ShowMenu(); + } +} + +// +--------------------------------------------------------------------+ + +bool +GameScreen::IsAudShown() +{ + return auddlg && auddlg->IsShown(); +} + +// +--------------------------------------------------------------------+ + +void +GameScreen::ShowVidDlg() +{ + if (viddlg) { + if (quit_view) { + quit_view->CloseMenu(); + Starshatter::GetInstance()->Pause(true); + } + + HideAll(); + + viddlg->Show(); + viddlg->SetTopMost(true); + + if (mouse_con) { + mouse_active = mouse_con->Active(); + mouse_con->SetActive(false); + } + + Mouse::Show(true); + } +} + +// +--------------------------------------------------------------------+ + +void +GameScreen::HideVidDlg() +{ + if (viddlg && viddlg->IsShown()) { + viddlg->Hide(); + Mouse::Show(false); + screen->AddWindow(gamewin); + + if (quit_view) + quit_view->ShowMenu(); + } +} + +// +--------------------------------------------------------------------+ + +bool +GameScreen::IsVidShown() +{ + return viddlg && viddlg->IsShown(); +} + +// +--------------------------------------------------------------------+ + +void +GameScreen::ShowOptDlg() +{ + if (optdlg) { + if (quit_view) { + quit_view->CloseMenu(); + Starshatter::GetInstance()->Pause(true); + } + + HideAll(); + + optdlg->Show(); + optdlg->SetTopMost(true); + + if (mouse_con) { + mouse_active = mouse_con->Active(); + mouse_con->SetActive(false); + } + + Mouse::Show(true); + } +} + +// +--------------------------------------------------------------------+ + +void +GameScreen::HideOptDlg() +{ + if (optdlg && optdlg->IsShown()) { + optdlg->Hide(); + Mouse::Show(false); + screen->AddWindow(gamewin); + + if (quit_view) + quit_view->ShowMenu(); + } +} + +// +--------------------------------------------------------------------+ + +bool +GameScreen::IsOptShown() +{ + return optdlg && optdlg->IsShown(); +} + +// +--------------------------------------------------------------------+ + +void +GameScreen::ShowModDlg() +{ + if (moddlg) { + if (quit_view) { + quit_view->CloseMenu(); + Starshatter::GetInstance()->Pause(true); + } + + HideAll(); + + moddlg->Show(); + moddlg->SetTopMost(true); + + if (mouse_con) { + mouse_active = mouse_con->Active(); + mouse_con->SetActive(false); + } + + Mouse::Show(true); + } +} + +// +--------------------------------------------------------------------+ + +void +GameScreen::HideModDlg() +{ + if (moddlg && moddlg->IsShown()) { + moddlg->Hide(); + Mouse::Show(false); + screen->AddWindow(gamewin); + + if (quit_view) + quit_view->ShowMenu(); + } +} + +// +--------------------------------------------------------------------+ + +bool +GameScreen::IsModShown() +{ + return moddlg && moddlg->IsShown(); +} + +// +--------------------------------------------------------------------+ + +void +GameScreen::ShowModInfoDlg() +{ + if (moddlg && modInfoDlg) { + if (quit_view) { + quit_view->CloseMenu(); + Starshatter::GetInstance()->Pause(true); + } + + HideAll(); + + moddlg->Show(); + moddlg->SetTopMost(false); + + modInfoDlg->Show(); + modInfoDlg->SetTopMost(true); + + if (mouse_con) { + mouse_active = mouse_con->Active(); + mouse_con->SetActive(false); + } + + Mouse::Show(true); + } +} + +// +--------------------------------------------------------------------+ + +void +GameScreen::HideModInfoDlg() +{ + ShowModDlg(); +} + +// +--------------------------------------------------------------------+ + +bool +GameScreen::IsModInfoShown() +{ + return modInfoDlg && modInfoDlg->IsShown(); +} + +// +--------------------------------------------------------------------+ + +void +GameScreen::ShowCtlDlg() +{ + if (ctldlg) { + if (quit_view) { + quit_view->CloseMenu(); + Starshatter::GetInstance()->Pause(true); + } + + HideAll(); + + ctldlg->Show(); + ctldlg->SetTopMost(true); + + if (mouse_con) { + mouse_active = mouse_con->Active(); + mouse_con->SetActive(false); + } + + Mouse::Show(true); + } +} + +// +--------------------------------------------------------------------+ + +void +GameScreen::HideCtlDlg() +{ + if (ctldlg && ctldlg->IsShown()) { + ctldlg->Hide(); + Mouse::Show(false); + screen->AddWindow(gamewin); + + if (quit_view) + quit_view->ShowMenu(); + } +} + +// +--------------------------------------------------------------------+ + +bool +GameScreen::IsCtlShown() +{ + return ctldlg && ctldlg->IsShown(); +} + +// +--------------------------------------------------------------------+ + +void +GameScreen::ShowKeyDlg() +{ + if (keydlg) { + if (quit_view) { + quit_view->CloseMenu(); + Starshatter::GetInstance()->Pause(true); + } + + HideAll(); + + if (ctldlg) { + ctldlg->Show(); + ctldlg->SetTopMost(false); + } + + if (keydlg) keydlg->Show(); + + if (mouse_con) { + mouse_con->SetActive(false); + } + + Mouse::Show(true); + } +} + +// +--------------------------------------------------------------------+ + +bool +GameScreen::IsKeyShown() +{ + return keydlg && keydlg->IsShown(); +} + +// +--------------------------------------------------------------------+ + +void +GameScreen::ShowJoyDlg() +{ + if (joydlg) { + if (quit_view) { + quit_view->CloseMenu(); + Starshatter::GetInstance()->Pause(true); + } + + HideAll(); + + if (ctldlg) { + ctldlg->Show(); + ctldlg->SetTopMost(false); + } + + if (joydlg) joydlg->Show(); + + if (mouse_con) { + mouse_con->SetActive(false); + } + + Mouse::Show(true); + } +} + +// +--------------------------------------------------------------------+ + +bool +GameScreen::IsJoyShown() +{ + return joydlg && joydlg->IsShown(); +} + +// +--------------------------------------------------------------------+ + +void +GameScreen::ShowWeaponsOverlay() +{ + if (wep_view) + wep_view->CycleOverlayMode(); +} + +void +GameScreen::HideWeaponsOverlay() +{ + if (wep_view) + wep_view->SetOverlayMode(0); +} + +// +--------------------------------------------------------------------+ + +void +GameScreen::HideAll() +{ + screen->DelWindow(gamewin); + + if (engdlg) engdlg->Hide(); + if (fltdlg) fltdlg->Hide(); + if (navdlg) navdlg->Hide(); + if (auddlg) auddlg->Hide(); + if (viddlg) viddlg->Hide(); + if (optdlg) optdlg->Hide(); + if (moddlg) moddlg->Hide(); + if (modInfoDlg) modInfoDlg->Hide(); + if (ctldlg) ctldlg->Hide(); + if (keydlg) keydlg->Hide(); + if (joydlg) joydlg->Hide(); +} + +// +--------------------------------------------------------------------+ + +void +GameScreen::ApplyOptions() +{ + if (ctldlg) ctldlg->Apply(); + if (optdlg) optdlg->Apply(); + if (auddlg) auddlg->Apply(); + if (viddlg) viddlg->Apply(); + + if (engdlg) engdlg->Hide(); + if (fltdlg) fltdlg->Hide(); + if (navdlg) navdlg->Hide(); + if (ctldlg) ctldlg->Hide(); + if (auddlg) auddlg->Hide(); + if (viddlg) viddlg->Hide(); + if (optdlg) optdlg->Hide(); + if (moddlg) moddlg->Hide(); + if (keydlg) keydlg->Hide(); + if (joydlg) joydlg->Hide(); + + Mouse::Show(false); + screen->AddWindow(gamewin); + Starshatter::GetInstance()->Pause(false); +} + +void +GameScreen::CancelOptions() +{ + if (ctldlg) ctldlg->Cancel(); + if (optdlg) optdlg->Cancel(); + if (auddlg) auddlg->Cancel(); + if (viddlg) viddlg->Cancel(); + + if (engdlg) engdlg->Hide(); + if (fltdlg) fltdlg->Hide(); + if (navdlg) navdlg->Hide(); + if (ctldlg) ctldlg->Hide(); + if (auddlg) auddlg->Hide(); + if (viddlg) viddlg->Hide(); + if (optdlg) optdlg->Hide(); + if (moddlg) moddlg->Hide(); + if (keydlg) keydlg->Hide(); + if (joydlg) joydlg->Hide(); + + Mouse::Show(false); + screen->AddWindow(gamewin); + Starshatter::GetInstance()->Pause(false); +} diff --git a/Stars45/GameScreen.h b/Stars45/GameScreen.h new file mode 100644 index 0000000..5f1c762 --- /dev/null +++ b/Stars45/GameScreen.h @@ -0,0 +1,192 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: GameScreen.h + AUTHOR: John DiCamillo + +*/ + +#ifndef GameScreen_h +#define GameScreen_h + +#include "Types.h" +#include "Bitmap.h" +#include "Screen.h" +#include "BaseScreen.h" + +// +--------------------------------------------------------------------+ + +class Screen; +class Sim; +class Window; +class Font; + +class NavDlg; +class EngDlg; +class FltDlg; +class CtlDlg; +class JoyDlg; +class KeyDlg; +class ModDlg; +class ModInfoDlg; + +class CameraDirector; +class DisplayView; +class HUDView; +class WepView; +class QuantumView; +class QuitView; +class RadioView; +class TacticalView; +class CameraView; +class PolyRender; +class Bitmap; +class DataLoader; +class Video; +class VideoFactory; + +// +--------------------------------------------------------------------+ + +class GameScreen : public BaseScreen +{ +public: + GameScreen(); + virtual ~GameScreen(); + + virtual void Setup(Screen* screen); + virtual void TearDown(); + virtual bool CloseTopmost(); + + virtual bool IsShown() const { return isShown; } + virtual void Show(); + virtual void Hide(); + + virtual bool IsFormShown() const; + virtual void ShowExternal(); + + virtual void ShowNavDlg(); + virtual void HideNavDlg(); + virtual bool IsNavShown(); + virtual NavDlg* GetNavDlg() { return navdlg; } + + virtual void ShowEngDlg(); + virtual void HideEngDlg(); + virtual bool IsEngShown(); + virtual EngDlg* GetEngDlg() { return engdlg; } + + virtual void ShowFltDlg(); + virtual void HideFltDlg(); + virtual bool IsFltShown(); + virtual FltDlg* GetFltDlg() { return fltdlg; } + + virtual void ShowCtlDlg(); + virtual void HideCtlDlg(); + virtual bool IsCtlShown(); + + virtual void ShowJoyDlg(); + virtual bool IsJoyShown(); + + virtual void ShowKeyDlg(); + virtual bool IsKeyShown(); + + + virtual AudDlg* GetAudDlg() const { return auddlg; } + virtual VidDlg* GetVidDlg() const { return viddlg; } + virtual OptDlg* GetOptDlg() const { return optdlg; } + virtual CtlDlg* GetCtlDlg() const { return ctldlg; } + virtual JoyDlg* GetJoyDlg() const { return joydlg; } + virtual KeyDlg* GetKeyDlg() const { return keydlg; } + virtual ModDlg* GetModDlg() const { return moddlg; } + virtual ModInfoDlg* GetModInfoDlg() const { return modInfoDlg; } + + virtual void ShowAudDlg(); + virtual void HideAudDlg(); + virtual bool IsAudShown(); + + virtual void ShowVidDlg(); + virtual void HideVidDlg(); + virtual bool IsVidShown(); + + virtual void ShowOptDlg(); + virtual void HideOptDlg(); + virtual bool IsOptShown(); + + virtual void ShowModDlg(); + virtual void HideModDlg(); + virtual bool IsModShown(); + + virtual void ShowModInfoDlg(); + virtual void HideModInfoDlg(); + virtual bool IsModInfoShown(); + + virtual void ApplyOptions(); + virtual void CancelOptions(); + + virtual void ShowWeaponsOverlay(); + virtual void HideWeaponsOverlay(); + + void SetFieldOfView(double fov); + double GetFieldOfView() const; + void CycleMFDMode(int mfd); + void CycleHUDMode(); + void CycleHUDColor(); + void CycleHUDWarn(); + void FrameRate(double f); + void ExecFrame(); + + static GameScreen* GetInstance() { return game_screen; } + CameraView* GetCameraView() const { return cam_view; } + Bitmap* GetLensFlare(int index); + +private: + void HideAll(); + + Sim* sim; + Screen* screen; + + Window* gamewin; + NavDlg* navdlg; + EngDlg* engdlg; + FltDlg* fltdlg; + CtlDlg* ctldlg; + KeyDlg* keydlg; + JoyDlg* joydlg; + AudDlg* auddlg; + VidDlg* viddlg; + OptDlg* optdlg; + ModDlg* moddlg; + ModInfoDlg* modInfoDlg; + + Font* HUDfont; + Font* GUIfont; + Font* GUI_small_font; + Font* title_font; + + Bitmap* flare1; + Bitmap* flare2; + Bitmap* flare3; + Bitmap* flare4; + + CameraDirector* cam_dir; + DisplayView* disp_view; + HUDView* hud_view; + WepView* wep_view; + QuantumView* quantum_view; + QuitView* quit_view; + TacticalView* tac_view; + RadioView* radio_view; + CameraView* cam_view; + DataLoader* loader; + + double frame_rate; + bool isShown; + + static GameScreen* game_screen; +}; + +// +--------------------------------------------------------------------+ + +#endif GameScreen_h + diff --git a/Stars45/Grid.cpp b/Stars45/Grid.cpp new file mode 100644 index 0000000..b4b75c9 --- /dev/null +++ b/Stars45/Grid.cpp @@ -0,0 +1,92 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Grid.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Tactical Grid class +*/ + +#include "MemDebug.h" +#include "Grid.h" + +#include "Game.h" +#include "Video.h" +#include "Window.h" + +static const Color DARK_LINE( 8, 8, 8); +static const Color LITE_LINE(16, 16, 16); + +// +--------------------------------------------------------------------+ + +Grid::Grid(int asize, int astep) + : size(asize), step(astep), drawn(0) +{ + radius = (float) (size * 1.414); +} + +Grid::~Grid() +{ } + +// +--------------------------------------------------------------------+ + +int Grid::CollidesWith(Graphic& o) { return 0; } + +// +--------------------------------------------------------------------+ + +void Grid::Render(Video* video, DWORD flags) +{ + if (!video || hidden) return; + + int c = 0; + Color line; + + for (int i = 0; i <= size; i += step) { + Point p1( i, 0, -size); p1 += Location(); + Point p2( i, 0, size); p2 += Location(); + Point p3(-i, 0, -size); p3 += Location(); + Point p4(-i, 0, size); p4 += Location(); + + if (c) line = DARK_LINE; + else line = LITE_LINE; + + DrawLine(video, p1,p2,line); + DrawLine(video, p3,p4,line); + + c++; + if (c > 3) c = 0; + } + + c = 0; + + for (i = 0; i <= size; i += step) { + Point p1(-size, 0, i); p1 += Location(); + Point p2( size, 0, i); p2 += Location(); + Point p3(-size, 0, -i); p3 += Location(); + Point p4( size, 0, -i); p4 += Location(); + + if (c) line = DARK_LINE; + else line = LITE_LINE; + + DrawLine(video, p1,p2,line); + DrawLine(video, p3,p4,line); + + c++; + if (c > 3) c = 0; + } +} + +void Grid::DrawLine(Video* video, Point& p1, Point& p2, Color grid_color) +{ + Vec3 v[2]; + + v[0] = p1; + v[1] = p2; + + video->DrawLines(1, v, grid_color, Video::BLEND_ADDITIVE); +} diff --git a/Stars45/Grid.h b/Stars45/Grid.h new file mode 100644 index 0000000..6cbea7f --- /dev/null +++ b/Stars45/Grid.h @@ -0,0 +1,50 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Grid.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Tactical Grid +*/ + +#ifndef Grid_h +#define Grid_h + +#include "Types.h" +#include "Graphic.h" +#include "Geometry.h" + +// +--------------------------------------------------------------------+ + +class Window; +class Projector; +class PolyRender; + +// +--------------------------------------------------------------------+ + +class Grid : public Graphic +{ +public: + Grid(int size, int step); + virtual ~Grid(); + + virtual void Render(Video* video, DWORD flags); + virtual int CollidesWith(Graphic& o); + +protected: + virtual void DrawLine(Video* video, Point& p1, Point& p2, Color c); + + int size; + int step; + int drawn; +}; + +// +--------------------------------------------------------------------+ + +#endif Grid_h + diff --git a/Stars45/GroundAI.cpp b/Stars45/GroundAI.cpp new file mode 100644 index 0000000..c7b7c6b --- /dev/null +++ b/Stars45/GroundAI.cpp @@ -0,0 +1,191 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: GroundAI.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Low-Level Artificial Intelligence class for Ground Units +*/ + +#include "MemDebug.h" +#include "GroundAI.h" +#include "SteerAI.h" +#include "System.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "Shield.h" +#include "Sim.h" +#include "Player.h" +#include "CarrierAI.h" +#include "Contact.h" +#include "Weapon.h" +#include "WeaponGroup.h" + +#include "Game.h" +#include "Physical.h" + +// +----------------------------------------------------------------------+ + +GroundAI::GroundAI(SimObject* s) + : ship((Ship*) s), target(0), subtarget(0), exec_time(0), carrier_ai(0) +{ + Sim* sim = Sim::GetSim(); + Ship* pship = sim->GetPlayerShip(); + int player_team = 1; + int ai_level = 1; + + if (pship) + player_team = pship->GetIFF(); + + Player* player = Player::GetCurrentPlayer(); + if (player) { + if (ship && ship->GetIFF() && ship->GetIFF() != player_team) { + ai_level = player->AILevel(); + } + else if (player->AILevel() == 0) { + ai_level = 1; + } + } + + // evil alien ships are *always* smart: + if (ship && ship->GetIFF() > 1 && ship->Design()->auto_roll > 1) { + ai_level = 2; + } + + if (ship && ship->GetHangar() && ship->GetCommandAILevel() > 0) + carrier_ai = new(__FILE__,__LINE__) CarrierAI(ship, ai_level); +} + + +// +--------------------------------------------------------------------+ + +GroundAI::~GroundAI() +{ + delete carrier_ai; +} + +// +--------------------------------------------------------------------+ + +void +GroundAI::SetTarget(SimObject* targ, System* sub) +{ + if (target != targ) { + target = targ; + + if (target) + Observe(target); + } + + subtarget = sub; +} + +// +--------------------------------------------------------------------+ + +bool +GroundAI::Update(SimObject* obj) +{ + if (obj == target) { + target = 0; + subtarget = 0; + } + + return SimObserver::Update(obj); +} + +const char* +GroundAI::GetObserverName() const +{ + static char name[64]; + sprintf(name, "GroundAI(%s)", ship->Name()); + return name; +} + +// +--------------------------------------------------------------------+ + +void +GroundAI::SelectTarget() +{ + SimObject* potential_target = 0; + + // pick the closest combatant ship with a different IFF code: + double target_dist = 1.0e15; + + Ship* current_ship_target = 0; + + ListIter c_iter = ship->ContactList(); + while (++c_iter) { + Contact* contact = c_iter.value(); + int c_iff = contact->GetIFF(ship); + Ship* c_ship = contact->GetShip(); + Shot* c_shot = contact->GetShot(); + bool rogue = false; + + if (c_ship) + rogue = c_ship->IsRogue(); + + if (rogue || c_iff > 0 && c_iff != ship->GetIFF() && c_iff < 1000) { + if (c_ship && !c_ship->InTransition()) { + // found an enemy, check distance: + double dist = (ship->Location() - c_ship->Location()).length(); + + if (!current_ship_target || (c_ship->Class() <= current_ship_target->Class() && + dist < target_dist)) { + current_ship_target = c_ship; + target_dist = dist; + } + } + } + + potential_target = current_ship_target; + } + + SetTarget(potential_target); +} + +// +--------------------------------------------------------------------+ + +int +GroundAI::Type() const +{ + return SteerAI::GROUND; +} + +// +--------------------------------------------------------------------+ + +void +GroundAI::ExecFrame(double secs) +{ + const int exec_period = 1000; + + if ((int) Game::GameTime() - exec_time > exec_period) { + exec_time = (int) Game::GameTime(); + SelectTarget(); + } + + if (ship) { + Shield* shield = ship->GetShield(); + + if (shield) + shield->SetPowerLevel(100); + + ListIter iter = ship->Weapons(); + while (++iter) { + WeaponGroup* group = (WeaponGroup*) iter.value(); + + if (group->NumWeapons() > 1 && group->CanTarget(Ship::DROPSHIPS)) + group->SetFiringOrders(Weapon::POINT_DEFENSE); + else + group->SetFiringOrders(Weapon::AUTO); + + group->SetTarget((Ship*) target, 0); + } + + if (carrier_ai) + carrier_ai->ExecFrame(secs); + } +} diff --git a/Stars45/GroundAI.h b/Stars45/GroundAI.h new file mode 100644 index 0000000..9b8f5a5 --- /dev/null +++ b/Stars45/GroundAI.h @@ -0,0 +1,61 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: GroundAI.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Ground Unit (low-level) Artifical Intelligence class +*/ + +#ifndef GroundAI_h +#define GroundAI_h + +#include "Types.h" +#include "SimObject.h" +#include "Director.h" +#include "Geometry.h" + +// +--------------------------------------------------------------------+ + +class Ship; +class System; +class CarrierAI; + +// +--------------------------------------------------------------------+ + +class GroundAI : public Director, + public SimObserver +{ +public: + GroundAI(SimObject* self); + virtual ~GroundAI(); + + virtual void ExecFrame(double seconds); + virtual void SetTarget(SimObject* targ, System* sub=0); + virtual SimObject* GetTarget() const { return target; } + virtual System* GetSubTarget() const { return subtarget; } + virtual int Type() const; + + virtual bool Update(SimObject* obj); + virtual const char* GetObserverName() const; + +protected: + virtual void SelectTarget(); + + Ship* ship; + SimObject* target; + System* subtarget; + double exec_time; + CarrierAI* carrier_ai; +}; + + +// +--------------------------------------------------------------------+ + +#endif GroundAI_h + diff --git a/Stars45/HUDSounds.cpp b/Stars45/HUDSounds.cpp new file mode 100644 index 0000000..86d1ec8 --- /dev/null +++ b/Stars45/HUDSounds.cpp @@ -0,0 +1,118 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: HUDSounds.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + HUDSounds singleton class utility implementation +*/ + +#include "MemDebug.h" +#include "HUDSounds.h" +#include "AudioConfig.h" + +#include "Sound.h" +#include "DataLoader.h" + +// +--------------------------------------------------------------------+ + +static Sound* mfd_mode = 0; +static Sound* nav_mode = 0; +static Sound* wep_mode = 0; +static Sound* wep_disp = 0; +static Sound* hud_mode = 0; +static Sound* hud_widget = 0; +static Sound* shield_level = 0; +static Sound* red_alert = 0; +static Sound* tac_accept = 0; +static Sound* tac_reject = 0; + +// +--------------------------------------------------------------------+ + +static void LoadInterfaceSound(DataLoader* loader, const char* wave, Sound*& s) +{ + loader->LoadSound(wave, s, 0, true); // optional sound effect +} + +void +HUDSounds::Initialize() +{ + DataLoader* loader = DataLoader::GetLoader(); + loader->SetDataPath("Sounds/"); + + LoadInterfaceSound(loader, "mfd_mode.wav", mfd_mode); + LoadInterfaceSound(loader, "nav_mode.wav", nav_mode); + LoadInterfaceSound(loader, "wep_mode.wav", wep_mode); + LoadInterfaceSound(loader, "wep_disp.wav", wep_disp); + LoadInterfaceSound(loader, "hud_mode.wav", hud_mode); + LoadInterfaceSound(loader, "hud_widget.wav", hud_widget); + LoadInterfaceSound(loader, "shield_level.wav", shield_level); + LoadInterfaceSound(loader, "alarm.wav", red_alert); + LoadInterfaceSound(loader, "tac_accept.wav", tac_accept); + LoadInterfaceSound(loader, "tac_reject.wav", tac_reject); + + if (red_alert) + red_alert->SetFlags(Sound::AMBIENT | Sound::LOOP | Sound::LOCKED); + + loader->SetDataPath(0); +} + +// +--------------------------------------------------------------------+ + +void +HUDSounds::Close() +{ + delete mfd_mode; + delete nav_mode; + delete wep_mode; + delete wep_disp; + delete hud_mode; + delete hud_widget; + delete shield_level; + delete red_alert; + delete tac_accept; + delete tac_reject; +} + +void HUDSounds::PlaySound(int n) +{ + Sound* sound = 0; + + switch (n) { + default: + case SND_MFD_MODE: if (mfd_mode) sound = mfd_mode->Duplicate(); break; + case SND_NAV_MODE: if (nav_mode) sound = nav_mode->Duplicate(); break; + case SND_WEP_MODE: if (wep_mode) sound = wep_mode->Duplicate(); break; + case SND_WEP_DISP: if (wep_disp) sound = wep_disp->Duplicate(); break; + case SND_HUD_MODE: if (hud_mode) sound = hud_mode->Duplicate(); break; + case SND_HUD_WIDGET: if (hud_widget) sound = hud_widget->Duplicate(); break; + case SND_SHIELD_LEVEL: if (shield_level) sound = shield_level->Duplicate(); break; + case SND_TAC_ACCEPT: if (tac_accept) sound = tac_accept->Duplicate(); break; + case SND_TAC_REJECT: if (tac_reject) sound = tac_reject->Duplicate(); break; + + // RED ALERT IS A SPECIAL CASE! + case SND_RED_ALERT: + if (red_alert) { + sound = red_alert; + } + break; + } + + if (sound && !sound->IsPlaying()) { + int gui_volume = AudioConfig::GuiVolume(); + sound->SetVolume(gui_volume); + sound->Play(); + } +} + +void HUDSounds::StopSound(int n) +{ + if (n == SND_RED_ALERT && red_alert) { + red_alert->Stop(); + } +} diff --git a/Stars45/HUDSounds.h b/Stars45/HUDSounds.h new file mode 100644 index 0000000..99a40b9 --- /dev/null +++ b/Stars45/HUDSounds.h @@ -0,0 +1,46 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: HUDSounds.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + HUDSounds singleton class utility +*/ + +#ifndef HUDSounds_h +#define HUDSounds_h + +#include "Types.h" + +// +--------------------------------------------------------------------+ + +class HUDSounds +{ +public: + enum SOUNDS { + SND_MFD_MODE, + SND_NAV_MODE, + SND_WEP_MODE, + SND_WEP_DISP, + SND_HUD_MODE, + SND_HUD_WIDGET, + SND_SHIELD_LEVEL, + SND_RED_ALERT, + SND_TAC_ACCEPT, + SND_TAC_REJECT + }; + + static void Initialize(); + static void Close(); + + static void PlaySound(int n); + static void StopSound(int n); +}; + +#endif HUDSounds_h + diff --git a/Stars45/HUDView.cpp b/Stars45/HUDView.cpp new file mode 100644 index 0000000..486ec19 --- /dev/null +++ b/Stars45/HUDView.cpp @@ -0,0 +1,4364 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: HUDView.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + View class for Heads Up Display +*/ + +#include "MemDebug.h" +#include "HUDView.h" +#include "HUDSounds.h" +#include "Ship.h" +#include "Element.h" +#include "Computer.h" +#include "Drive.h" +#include "Instruction.h" +#include "NavSystem.h" +#include "Power.h" +#include "Shield.h" +#include "Sensor.h" +#include "Contact.h" +#include "ShipDesign.h" +#include "Shot.h" +#include "Drone.h" +#include "Thruster.h" +#include "Weapon.h" +#include "WeaponGroup.h" +#include "FlightDeck.h" +#include "SteerAI.h" +#include "Sim.h" +#include "StarSystem.h" +#include "Starshatter.h" +#include "CameraDirector.h" +#include "MFD.h" +#include "RadioView.h" +#include "FormatUtil.h" +#include "Hoop.h" +#include "QuantumDrive.h" +#include "KeyMap.h" +#include "AudioConfig.h" +#include "Player.h" + +#include "NetGame.h" +#include "NetPlayer.h" + +#include "Color.h" +#include "CameraView.h" +#include "Screen.h" +#include "DataLoader.h" +#include "Scene.h" +#include "FontMgr.h" +#include "Graphic.h" +#include "Sprite.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "MouseController.h" +#include "Polygon.h" +#include "Sound.h" +#include "Game.h" +#include "Window.h" + +static Bitmap hud_left_air; +static Bitmap hud_right_air; +static Bitmap hud_left_fighter; +static Bitmap hud_right_fighter; +static Bitmap hud_left_starship; +static Bitmap hud_right_starship; +static Bitmap instr_left; +static Bitmap instr_right; +static Bitmap warn_left; +static Bitmap warn_right; +static Bitmap lead; +static Bitmap cross; +static Bitmap cross1; +static Bitmap cross2; +static Bitmap cross3; +static Bitmap cross4; +static Bitmap fpm; +static Bitmap hpm; +static Bitmap pitch_ladder_pos; +static Bitmap pitch_ladder_neg; +static Bitmap chase_left; +static Bitmap chase_right; +static Bitmap chase_top; +static Bitmap chase_bottom; +static Bitmap icon_ship; +static Bitmap icon_target; + +static BYTE* hud_left_shade_air = 0; +static BYTE* hud_right_shade_air = 0; +static BYTE* hud_left_shade_fighter = 0; +static BYTE* hud_right_shade_fighter = 0; +static BYTE* hud_left_shade_starship = 0; +static BYTE* hud_right_shade_starship = 0; +static BYTE* instr_left_shade = 0; +static BYTE* instr_right_shade = 0; +static BYTE* warn_left_shade = 0; +static BYTE* warn_right_shade = 0; +static BYTE* lead_shade = 0; +static BYTE* cross_shade = 0; +static BYTE* cross1_shade = 0; +static BYTE* cross2_shade = 0; +static BYTE* cross3_shade = 0; +static BYTE* cross4_shade = 0; +static BYTE* fpm_shade = 0; +static BYTE* hpm_shade = 0; +static BYTE* pitch_ladder_pos_shade = 0; +static BYTE* pitch_ladder_neg_shade = 0; +static BYTE* chase_left_shade = 0; +static BYTE* chase_right_shade = 0; +static BYTE* chase_top_shade = 0; +static BYTE* chase_bottom_shade = 0; +static BYTE* icon_ship_shade = 0; +static BYTE* icon_target_shade = 0; + +static Sprite* hud_left_sprite = 0; +static Sprite* hud_right_sprite = 0; +static Sprite* fpm_sprite = 0; +static Sprite* hpm_sprite = 0; +static Sprite* lead_sprite = 0; +static Sprite* aim_sprite = 0; +static Sprite* tgt1_sprite = 0; +static Sprite* tgt2_sprite = 0; +static Sprite* tgt3_sprite = 0; +static Sprite* tgt4_sprite = 0; +static Sprite* chase_sprite = 0; +static Sprite* instr_left_sprite = 0; +static Sprite* instr_right_sprite = 0; +static Sprite* warn_left_sprite = 0; +static Sprite* warn_right_sprite = 0; +static Sprite* icon_ship_sprite = 0; +static Sprite* icon_target_sprite = 0; + +static Sound* missile_lock_sound; + +const int NUM_HUD_COLORS = 4; + +Color standard_hud_colors[NUM_HUD_COLORS] = { + Color(130,190,140), // green + Color(130,200,220), // cyan + Color(250,170, 80), // orange +// Color(220,220,100), // yellow + Color( 16, 16, 16) // dark gray + }; + +Color standard_txt_colors[NUM_HUD_COLORS] = { + Color(150,200,170), // green w/ green gray + Color(220,220,180), // cyan w/ light yellow + Color(220,220, 80), // orange w/ yellow +// Color(180,200,220), // yellow w/ white + Color( 32, 32, 32) // dark gray + }; + +Color night_vision_colors[NUM_HUD_COLORS] = { + Color( 20, 80, 20), // green + Color( 30, 80, 80), // cyan + Color( 80, 80, 20), // yellow +// Color(180,200,220), // not used + Color( 0, 0, 0) // no night vision + }; + +static Font* hud_font = 0; +static Font* big_font = 0; + +static bool mouse_in = false; +static int mouse_latch = 0; +static int mouse_index = -1; + +static int ship_status = System::NOMINAL; +static int tgt_status = System::NOMINAL; + +// +--------------------------------------------------------------------+ + +static enum TXT { + MAX_CONTACT = 50, + + TXT_CAUTION_TXT = 0, + TXT_LAST_CAUTION = 23, + TXT_CAM_ANGLE, + TXT_CAM_MODE, + TXT_PAUSED, + TXT_GEAR_DOWN, + + TXT_HUD_MODE, + TXT_PRIMARY_WEP, + TXT_SECONDARY_WEP, + TXT_DECOY, + TXT_SHIELD, + TXT_AUTO, + TXT_SHOOT, + TXT_NAV_INDEX, + TXT_NAV_ACTION, + TXT_NAV_FORMATION, + TXT_NAV_SPEED, + TXT_NAV_ETR, + TXT_NAV_HOLD, + + TXT_SPEED, + TXT_RANGE, + TXT_CLOSING_SPEED, + TXT_THREAT_WARN, + TXT_COMPASS, + TXT_HEADING, + TXT_PITCH, + TXT_ALTITUDE, + TXT_GFORCE, + TXT_MISSILE_T1, + TXT_MISSILE_T2, + TXT_ICON_SHIP_TYPE, + TXT_ICON_TARGET_TYPE, + TXT_TARGET_NAME, + TXT_TARGET_DESIGN, + TXT_TARGET_SHIELD, + TXT_TARGET_HULL, + TXT_TARGET_SUB, + TXT_TARGET_ETA, + + TXT_MSG_1, + TXT_MSG_2, + TXT_MSG_3, + TXT_MSG_4, + TXT_MSG_5, + TXT_MSG_6, + + TXT_NAV_PT, + TXT_SELF, + TXT_SELF_NAME, + TXT_CONTACT_NAME, + TXT_CONTACT_INFO = TXT_CONTACT_NAME + MAX_CONTACT, + TXT_LAST = TXT_CONTACT_INFO + MAX_CONTACT, + + TXT_LAST_ACTIVE = TXT_NAV_HOLD, + TXT_INSTR_PAGE = TXT_CAUTION_TXT + 6, +}; + +static HUDText hud_text[TXT_LAST]; + +void +HUDView::DrawHUDText(int index, const char* txt, Rect& rect, int align, int upcase, bool box) +{ + if (index < 0 || index >= TXT_LAST) + return; + + HUDText& ht = hud_text[index]; + Color hc = ht.color; + + char txt_buf[256]; + int n = strlen(txt); + + if (n > 250) n = 250; + + for (int i = 0; i < n; i++) { + if (upcase && islower(txt[i])) + txt_buf[i] = toupper(txt[i]); + else + txt_buf[i] = txt[i]; + } + + txt_buf[i] = 0; + + if (box) { + ht.font->DrawText(txt_buf, n, rect, DT_LEFT | DT_SINGLELINE | DT_CALCRECT); + + if ((align & DT_CENTER) != 0) { + int cx = width/2; + rect.x = cx - rect.w/2; + } + } + + if (!cockpit_hud_texture && rect.Contains(Mouse::X(), Mouse::Y())) { + mouse_in = true; + + if (index <= TXT_LAST_ACTIVE) + hc = Color::White; + + if (Mouse::LButton() && !mouse_latch) { + mouse_latch = 2; + mouse_index = index; + } + } + + if (cockpit_hud_texture && + index >= TXT_HUD_MODE && + index <= TXT_TARGET_ETA && + ht.font != big_font) { + + Sprite* s = hud_sprite[0]; + + int cx = (int) s->Location().x; + int cy = (int) s->Location().y; + int w2 = s->Width() / 2; + int h2 = s->Height() / 2; + + Rect txt_rect(rect); + txt_rect.x -= (cx-w2); + txt_rect.y -= (cy-h2); + + if (index == TXT_ICON_SHIP_TYPE) + txt_rect = Rect(0, 500, 128, 12); + + else if (index == TXT_ICON_TARGET_TYPE) + txt_rect = Rect(128, 500, 128, 12); + + ht.font->SetColor(hc); + ht.font->DrawText(txt_buf, n, txt_rect, align | DT_SINGLELINE, cockpit_hud_texture); + ht.hidden = false; + } + else { + ht.font->SetColor(hc); + ht.font->DrawText(txt_buf, n, rect, align | DT_SINGLELINE); + ht.rect = rect; + ht.hidden = false; + + if (box) { + rect.Inflate(3,2); + rect.h--; + window->DrawRect(rect, hud_color); + } + } +} + +void +HUDView::HideHUDText(int index) +{ + if (index >= TXT_LAST) + return; + + hud_text[index].hidden = true; +} + +// +--------------------------------------------------------------------+ + +HUDView* HUDView::hud_view = 0; +bool HUDView::arcade = false; +bool HUDView::show_fps = false; +int HUDView::def_color_set = 1; +int HUDView::gunsight = 1; + +// +--------------------------------------------------------------------+ + +HUDView::HUDView(Window* c) + : View(c), projector(0), camview(0), + sim(0), ship(0), target(0), mode(HUD_MODE_TAC), + tactical(0), overlay(0), cockpit_hud_texture(0), + threat(0), active_region(0), transition(false), docking(false), + az_ring(0), az_pointer(0), el_ring(0), el_pointer(0), compass_scale(1), + show_warn(false), show_inst(false), inst_page(0) +{ + hud_view = this; + + sim = Sim::GetSim(); + + if (sim) + sim->ShowGrid(false); + + int i; + + width = window->Width(); + height = window->Height(); + xcenter = (width / 2.0) - 0.5; + ycenter = (height / 2.0) + 0.5; + + PrepareBitmap("HUDleftA.pcx", hud_left_air, hud_left_shade_air); + PrepareBitmap("HUDrightA.pcx", hud_right_air, hud_right_shade_air); + PrepareBitmap("HUDleft.pcx", hud_left_fighter, hud_left_shade_fighter); + PrepareBitmap("HUDright.pcx", hud_right_fighter, hud_right_shade_fighter); + PrepareBitmap("HUDleft1.pcx", hud_left_starship, hud_left_shade_starship); + PrepareBitmap("HUDright1.pcx", hud_right_starship, hud_right_shade_starship); + PrepareBitmap("INSTR_left.pcx", instr_left, instr_left_shade); + PrepareBitmap("INSTR_right.pcx", instr_right, instr_right_shade); + PrepareBitmap("CAUTION_left.pcx", warn_left, warn_left_shade); + PrepareBitmap("CAUTION_right.pcx", warn_right, warn_right_shade); + PrepareBitmap("hud_icon.pcx", icon_ship, icon_ship_shade); + PrepareBitmap("hud_icon.pcx", icon_target, icon_target_shade); + + PrepareBitmap("lead.pcx", lead, lead_shade); + PrepareBitmap("cross.pcx", cross, cross_shade); + PrepareBitmap("cross1.pcx", cross1, cross1_shade); + PrepareBitmap("cross2.pcx", cross2, cross2_shade); + PrepareBitmap("cross3.pcx", cross3, cross3_shade); + PrepareBitmap("cross4.pcx", cross4, cross4_shade); + PrepareBitmap("fpm.pcx", fpm, fpm_shade); + PrepareBitmap("hpm.pcx", hpm, hpm_shade); + PrepareBitmap("chase_l.pcx", chase_left, chase_left_shade); + PrepareBitmap("chase_r.pcx", chase_right, chase_right_shade); + PrepareBitmap("chase_t.pcx", chase_top, chase_top_shade); + PrepareBitmap("chase_b.pcx", chase_bottom, chase_bottom_shade); + PrepareBitmap("ladder1.pcx", pitch_ladder_pos, + pitch_ladder_pos_shade); + PrepareBitmap("ladder2.pcx", pitch_ladder_neg, + pitch_ladder_neg_shade); + + hud_left_air.SetType(Bitmap::BMP_TRANSLUCENT); + hud_right_air.SetType(Bitmap::BMP_TRANSLUCENT); + hud_left_fighter.SetType(Bitmap::BMP_TRANSLUCENT); + hud_right_fighter.SetType(Bitmap::BMP_TRANSLUCENT); + hud_left_starship.SetType(Bitmap::BMP_TRANSLUCENT); + hud_right_starship.SetType(Bitmap::BMP_TRANSLUCENT); + instr_left.SetType(Bitmap::BMP_TRANSLUCENT); + instr_right.SetType(Bitmap::BMP_TRANSLUCENT); + warn_left.SetType(Bitmap::BMP_TRANSLUCENT); + warn_right.SetType(Bitmap::BMP_TRANSLUCENT); + icon_ship.SetType(Bitmap::BMP_TRANSLUCENT); + icon_target.SetType(Bitmap::BMP_TRANSLUCENT); + fpm.SetType(Bitmap::BMP_TRANSLUCENT); + hpm.SetType(Bitmap::BMP_TRANSLUCENT); + lead.SetType(Bitmap::BMP_TRANSLUCENT); + cross.SetType(Bitmap::BMP_TRANSLUCENT); + cross1.SetType(Bitmap::BMP_TRANSLUCENT); + cross2.SetType(Bitmap::BMP_TRANSLUCENT); + cross3.SetType(Bitmap::BMP_TRANSLUCENT); + cross4.SetType(Bitmap::BMP_TRANSLUCENT); + chase_left.SetType(Bitmap::BMP_TRANSLUCENT); + chase_right.SetType(Bitmap::BMP_TRANSLUCENT); + chase_top.SetType(Bitmap::BMP_TRANSLUCENT); + chase_bottom.SetType(Bitmap::BMP_TRANSLUCENT); + pitch_ladder_pos.SetType(Bitmap::BMP_TRANSLUCENT); + pitch_ladder_neg.SetType(Bitmap::BMP_TRANSLUCENT); + + hud_left_sprite = new(__FILE__,__LINE__) Sprite(&hud_left_fighter); + hud_right_sprite = new(__FILE__,__LINE__) Sprite(&hud_right_fighter); + instr_left_sprite = new(__FILE__,__LINE__) Sprite(&instr_left); + instr_right_sprite = new(__FILE__,__LINE__) Sprite(&instr_right); + warn_left_sprite = new(__FILE__,__LINE__) Sprite(&warn_left); + warn_right_sprite = new(__FILE__,__LINE__) Sprite(&warn_right); + icon_ship_sprite = new(__FILE__,__LINE__) Sprite(&icon_ship); + icon_target_sprite = new(__FILE__,__LINE__) Sprite(&icon_target); + fpm_sprite = new(__FILE__,__LINE__) Sprite(&fpm); + hpm_sprite = new(__FILE__,__LINE__) Sprite(&hpm); + lead_sprite = new(__FILE__,__LINE__) Sprite(&lead); + aim_sprite = new(__FILE__,__LINE__) Sprite(&cross); + tgt1_sprite = new(__FILE__,__LINE__) Sprite(&cross1); + tgt2_sprite = new(__FILE__,__LINE__) Sprite(&cross2); + tgt3_sprite = new(__FILE__,__LINE__) Sprite(&cross3); + tgt4_sprite = new(__FILE__,__LINE__) Sprite(&cross4); + chase_sprite = new(__FILE__,__LINE__) Sprite(&chase_left); + + ZeroMemory(hud_sprite, sizeof(hud_sprite)); + + hud_sprite[ 0] = hud_left_sprite; + hud_sprite[ 1] = hud_right_sprite; + hud_sprite[ 2] = instr_left_sprite; + hud_sprite[ 3] = instr_right_sprite; + hud_sprite[ 4] = warn_left_sprite; + hud_sprite[ 5] = warn_right_sprite; + hud_sprite[ 6] = icon_ship_sprite; + hud_sprite[ 7] = icon_target_sprite; + hud_sprite[ 8] = fpm_sprite; + hud_sprite[ 9] = hpm_sprite; + hud_sprite[10] = lead_sprite; + hud_sprite[11] = aim_sprite; + hud_sprite[12] = tgt1_sprite; + hud_sprite[13] = tgt2_sprite; + hud_sprite[14] = tgt3_sprite; + hud_sprite[15] = tgt4_sprite; + hud_sprite[16] = chase_sprite; + + double pitch_ladder_UV[8] = { 0.125,0.0625, 0.875,0.0625, 0.875,0, 0.125,0 }; + double UV[8]; + + for (i = 0; i < 15; i++) { + pitch_ladder[i] = new(__FILE__,__LINE__) Sprite(&pitch_ladder_pos); + + CopyMemory(UV, pitch_ladder_UV, sizeof(UV)); + UV[1] = UV[3] = (pitch_ladder_UV[1] * (i )); + UV[5] = UV[7] = (pitch_ladder_UV[1] * (i+1)); + + pitch_ladder[i]->Reshape(192, 16); + pitch_ladder[i]->SetTexCoords(UV); + pitch_ladder[i]->SetBlendMode(2); + pitch_ladder[i]->Hide(); + } + + // zero mark at i=15 + { + pitch_ladder[i] = new(__FILE__,__LINE__) Sprite(&pitch_ladder_pos); + + UV[0] = UV[6] = 0; + UV[2] = UV[4] = 1; + UV[1] = UV[3] = (pitch_ladder_UV[1] * (i+1)); + UV[5] = UV[7] = (pitch_ladder_UV[1] * (i )); + + pitch_ladder[i]->Reshape(256, 16); + pitch_ladder[i]->SetTexCoords(UV); + pitch_ladder[i]->SetBlendMode(2); + pitch_ladder[i]->Hide(); + } + + for (i = 16; i < 31; i++) { + pitch_ladder[i] = new(__FILE__,__LINE__) Sprite(&pitch_ladder_neg); + + CopyMemory(UV, pitch_ladder_UV, sizeof(UV)); + UV[1] = UV[3] = (pitch_ladder_UV[1] * (30 - i )); + UV[5] = UV[7] = (pitch_ladder_UV[1] * (30 - i+1)); + + pitch_ladder[i]->Reshape(192, 16); + pitch_ladder[i]->SetTexCoords(UV); + pitch_ladder[i]->SetBlendMode(2); + pitch_ladder[i]->Hide(); + } + + for (i = 0; i < 3; i++) + mfd[i] = new(__FILE__,__LINE__) MFD(window, i); + + mfd[0]->SetRect(Rect( 8, height - 136, 128, 128)); + mfd[1]->SetRect(Rect(width - 136, height - 136, 128, 128)); + mfd[2]->SetRect(Rect( 8, 8, 128, 128)); + + hud_left_sprite->MoveTo( Point(width/2-128, height/2, 1)); + hud_right_sprite->MoveTo(Point(width/2+128, height/2, 1)); + hud_left_sprite->SetBlendMode(2); + hud_left_sprite->SetFilter(0); + hud_right_sprite->SetBlendMode(2); + hud_right_sprite->SetFilter(0); + + instr_left_sprite->MoveTo( Point(width/2-128, height-128, 1)); + instr_right_sprite->MoveTo(Point(width/2+128, height-128, 1)); + instr_left_sprite->SetBlendMode(2); + instr_left_sprite->SetFilter(0); + instr_right_sprite->SetBlendMode(2); + instr_right_sprite->SetFilter(0); + + warn_left_sprite->MoveTo( Point(width/2-128, height-128, 1)); + warn_right_sprite->MoveTo(Point(width/2+128, height-128, 1)); + warn_left_sprite->SetBlendMode(2); + warn_left_sprite->SetFilter(0); + warn_right_sprite->SetBlendMode(2); + warn_right_sprite->SetFilter(0); + + icon_ship_sprite->MoveTo( Point( 184, height-72, 1)); + icon_target_sprite->MoveTo(Point(width - 184, height-72, 1)); + icon_ship_sprite->SetBlendMode(2); + icon_ship_sprite->SetFilter(0); + icon_target_sprite->SetBlendMode(2); + icon_target_sprite->SetFilter(0); + + fpm_sprite->MoveTo(Point(width/2, height/2, 1)); + hpm_sprite->MoveTo(Point(width/2, height/2, 1)); + lead_sprite->MoveTo(Point(width/2, height/2, 1)); + aim_sprite->MoveTo(Point(width/2, height/2, 1)); + tgt1_sprite->MoveTo(Point(width/2, height/2, 1)); + tgt2_sprite->MoveTo(Point(width/2, height/2, 1)); + tgt3_sprite->MoveTo(Point(width/2, height/2, 1)); + tgt4_sprite->MoveTo(Point(width/2, height/2, 1)); + + fpm_sprite->SetBlendMode(2); + hpm_sprite->SetBlendMode(2); + lead_sprite->SetBlendMode(2); + aim_sprite->SetBlendMode(2); + tgt1_sprite->SetBlendMode(2); + tgt2_sprite->SetBlendMode(2); + tgt3_sprite->SetBlendMode(2); + tgt4_sprite->SetBlendMode(2); + chase_sprite->SetBlendMode(2); + + fpm_sprite->SetFilter(0); + hpm_sprite->SetFilter(0); + lead_sprite->SetFilter(0); + aim_sprite->SetFilter(0); + tgt1_sprite->SetFilter(0); + tgt2_sprite->SetFilter(0); + tgt3_sprite->SetFilter(0); + tgt4_sprite->SetFilter(0); + chase_sprite->SetFilter(0); + + lead_sprite->Hide(); + aim_sprite->Hide(); + tgt1_sprite->Hide(); + tgt2_sprite->Hide(); + tgt3_sprite->Hide(); + tgt4_sprite->Hide(); + chase_sprite->Hide(); + + aw = chase_left.Width() / 2; + ah = chase_left.Height() / 2; + + mfd[0]->SetMode(MFD::MFD_MODE_SHIP); + mfd[1]->SetMode(MFD::MFD_MODE_FOV); + mfd[2]->SetMode(MFD::MFD_MODE_GAME); + + hud_font = FontMgr::Find("HUD"); + big_font = FontMgr::Find("GUI"); + + for (i = 0; i < TXT_LAST; i++) { + hud_text[i].font = hud_font; + } + + hud_text[TXT_THREAT_WARN].font = big_font; + hud_text[TXT_SHOOT].font = big_font; + hud_text[TXT_AUTO].font = big_font; + + SetHUDColorSet(def_color_set); + MFD::SetColor(standard_hud_colors[color]); + + DataLoader* loader = DataLoader::GetLoader(); + loader->SetDataPath("HUD/"); + + az_ring = new(__FILE__,__LINE__) Solid; + az_pointer = new(__FILE__,__LINE__) Solid; + el_ring = new(__FILE__,__LINE__) Solid; + el_pointer = new(__FILE__,__LINE__) Solid; + + az_ring->Load("CompassRing.mag", compass_scale); + az_pointer->Load("CompassPointer.mag", compass_scale); + el_ring->Load("PitchRing.mag", compass_scale); + el_pointer->Load("CompassPointer.mag", compass_scale); + + loader->SetDataPath("Sounds/"); + loader->LoadSound("MissileLock.wav", missile_lock_sound, Sound::LOOP | Sound::LOCKED); + + loader->SetDataPath(0); + + for (i = 0; i < MAX_MSG; i++) + msg_time[i] = 0; +} + +HUDView::~HUDView() +{ + HideCompass(); + + if (missile_lock_sound) { + missile_lock_sound->Stop(); + missile_lock_sound->Release(); + missile_lock_sound = 0; + } + + for (int i = 0; i < 3; i++) { + delete mfd[i]; + mfd[i] = 0; + } + + for (i = 0; i < 32; i++) { + GRAPHIC_DESTROY(hud_sprite[i]); + } + + fpm.ClearImage(); + hpm.ClearImage(); + lead.ClearImage(); + cross.ClearImage(); + cross1.ClearImage(); + cross2.ClearImage(); + cross3.ClearImage(); + cross4.ClearImage(); + hud_left_air.ClearImage(); + hud_right_air.ClearImage(); + hud_left_fighter.ClearImage(); + hud_right_fighter.ClearImage(); + hud_left_starship.ClearImage(); + hud_right_starship.ClearImage(); + instr_left.ClearImage(); + instr_right.ClearImage(); + warn_left.ClearImage(); + warn_right.ClearImage(); + icon_ship.ClearImage(); + icon_target.ClearImage(); + chase_left.ClearImage(); + chase_right.ClearImage(); + chase_top.ClearImage(); + chase_bottom.ClearImage(); + pitch_ladder_pos.ClearImage(); + pitch_ladder_neg.ClearImage(); + + delete [] fpm_shade; + delete [] hpm_shade; + delete [] lead_shade; + delete [] cross_shade; + delete [] cross1_shade; + delete [] cross2_shade; + delete [] cross3_shade; + delete [] cross4_shade; + delete [] hud_left_shade_air; + delete [] hud_right_shade_air; + delete [] hud_left_shade_fighter; + delete [] hud_right_shade_fighter; + delete [] hud_left_shade_starship; + delete [] hud_right_shade_starship; + delete [] instr_left_shade; + delete [] instr_right_shade; + delete [] warn_left_shade; + delete [] warn_right_shade; + delete [] icon_ship_shade; + delete [] icon_target_shade; + delete [] chase_left_shade; + delete [] chase_right_shade; + delete [] chase_top_shade; + delete [] chase_bottom_shade; + delete [] pitch_ladder_pos_shade; + delete [] pitch_ladder_neg_shade; + + delete az_ring; + delete az_pointer; + delete el_ring; + delete el_pointer; + + fpm_shade = 0; + hpm_shade = 0; + cross_shade = 0; + cross1_shade = 0; + cross2_shade = 0; + cross3_shade = 0; + cross4_shade = 0; + hud_left_shade_air = 0; + hud_right_shade_air = 0; + hud_left_shade_fighter = 0; + hud_right_shade_fighter = 0; + hud_left_shade_starship = 0; + hud_right_shade_starship = 0; + instr_left_shade = 0; + instr_right_shade = 0; + warn_left_shade = 0; + warn_right_shade = 0; + icon_ship_shade = 0; + icon_target_shade = 0; + chase_left_shade = 0; + chase_right_shade = 0; + chase_top_shade = 0; + chase_bottom_shade = 0; + pitch_ladder_pos_shade = 0; + pitch_ladder_neg_shade = 0; + + az_ring = 0; + az_pointer = 0; + el_ring = 0; + el_pointer = 0; + + hud_view = 0; +} + +void +HUDView::OnWindowMove() +{ + width = window->Width(); + height = window->Height(); + xcenter = (width / 2.0) - 0.5; + ycenter = (height / 2.0) + 0.5; + + mfd[0]->SetRect(Rect( 8, height - 136, 128, 128)); + mfd[1]->SetRect(Rect(width - 136, height - 136, 128, 128)); + mfd[2]->SetRect(Rect( 8, 8, 128, 128)); + + hud_left_sprite->MoveTo( Point(width/2-128, height/2, 1)); + hud_right_sprite->MoveTo(Point(width/2+128, height/2, 1)); + + instr_left_sprite->MoveTo( Point(width/2-128, height-128, 1)); + instr_right_sprite->MoveTo(Point(width/2+128, height-128, 1)); + warn_left_sprite->MoveTo( Point(width/2-128, height-128, 1)); + warn_right_sprite->MoveTo(Point(width/2+128, height-128, 1)); + icon_ship_sprite->MoveTo( Point( 184, height-72, 1)); + icon_target_sprite->MoveTo(Point(width - 184, height-72, 1)); + + for (int i = 0; i < TXT_LAST; i++) { + hud_text[i].font = hud_font; + hud_text[i].color = standard_txt_colors[color]; + } + + if (big_font) { + hud_text[TXT_THREAT_WARN].font = big_font; + hud_text[TXT_SHOOT].font = big_font; + hud_text[TXT_AUTO].font = big_font; + } + + MFD::SetColor(standard_hud_colors[color]); + + int cx = width/2; + int cy = height/2; +} + +// +--------------------------------------------------------------------+ + +void +HUDView::SetTacticalMode(int mode) +{ + if (tactical != mode) { + tactical = mode; + + if (tactical) { + hud_left_sprite->Hide(); + hud_right_sprite->Hide(); + + for (int i = 0; i < 31; i++) + pitch_ladder[i]->Hide(); + } + else if (Game::MaxTexSize() > 128) { + hud_left_sprite->Show(); + hud_right_sprite->Show(); + } + } +} + +void +HUDView::SetOverlayMode(int mode) +{ + if (overlay != mode) { + overlay = mode; + } +} + +// +--------------------------------------------------------------------+ + +bool +HUDView::Update(SimObject* obj) +{ + if (obj == ship) { + if (target) + SetTarget(0); + + ship = 0; + + for (int i = 0; i < 3; i++) + mfd[i]->SetShip(ship); + } + + if (obj == target) { + target = 0; + PrepareBitmap("hud_icon.pcx", icon_target, icon_target_shade); + ColorizeBitmap(icon_target, icon_target_shade, txt_color); + } + + return SimObserver::Update(obj); +} + +const char* +HUDView::GetObserverName() const +{ + return "HUDView"; +} + +// +--------------------------------------------------------------------+ + +void +HUDView::UseCameraView(CameraView* v) +{ + if (v && camview != v) { + camview = v; + + for (int i = 0; i < 3; i++) + mfd[i]->UseCameraView(camview); + + projector = camview->GetProjector(); + } +} + +// +--------------------------------------------------------------------+ + +Color +HUDView::MarkerColor(Contact* contact) +{ + Color c(80,80,80); + + if (contact) { + Sim* sim = Sim::GetSim(); + Ship* ship = sim->GetPlayerShip(); + + int c_iff = contact->GetIFF(ship); + + c = Ship::IFFColor(c_iff) * contact->Age(); + + if (contact->GetShot() && contact->Threat(ship)) { + if ((Game::RealTime()/500) & 1) + c = c * 2; + else + c = c * 0.5; + } + } + + return c; +} + +// +--------------------------------------------------------------------+ + +void +HUDView::DrawContactMarkers() +{ + threat = 0; + + for (int i = 0; i < MAX_CONTACT; i++) { + HideHUDText(TXT_CONTACT_NAME+i); + HideHUDText(TXT_CONTACT_INFO+i); + } + + + if (!ship) + return; + + int index = 0; + ListIter contact = ship->ContactList(); + + // draw own sensor contacts: + while (++contact) { + Contact* c = contact.value(); + + // draw track ladder: + if (c->TrackLength() > 0 && c->GetShip() != ship) { + DrawTrack(c); + } + + DrawContact(c, index++); + } + + Color c = ship->MarkerColor(); + + // draw own ship track ladder: + if (CameraDirector::GetCameraMode() == CameraDirector::MODE_ORBIT && ship->TrackLength() > 0) { + int ctl = ship->TrackLength(); + + Point t1 = ship->Location(); + Point t2 = ship->TrackPoint(0); + + if (t1 != t2) + DrawTrackSegment(t1, t2, c); + + for (int i = 0; i < ctl-1; i++) { + t1 = ship->TrackPoint(i); + t2 = ship->TrackPoint(i+1); + + if (t1 != t2) + DrawTrackSegment(t1, t2, c * ((double) (ctl-i)/ (double) ctl)); + } + } + + // draw own ship marker: + Point mark_pt = ship->Location(); + projector->Transform(mark_pt); + + // clip: + if (CameraDirector::GetCameraMode() == CameraDirector::MODE_ORBIT && mark_pt.z > 1.0) { + projector->Project(mark_pt); + + int x = (int) mark_pt.x; + int y = (int) mark_pt.y; + + if (x > 4 && x < width-4 && + y > 4 && y < height-4) { + + DrawDiamond(x,y,5,c); + + if (tactical) { + Rect self_rect(x+8, y-4, 200, 12); + DrawHUDText(TXT_SELF, ship->Name(), self_rect, DT_LEFT, HUD_MIXED_CASE); + + if (NetGame::GetInstance()) { + Player* p = Player::GetCurrentPlayer(); + if (p) { + Rect net_name_rect(x+8, y+6, 120, 12); + DrawHUDText(TXT_SELF_NAME, p->Name(), net_name_rect, DT_LEFT, HUD_MIXED_CASE); + } + } + } + } + } + + // draw life bars on targeted ship: + if (target && target->Type() == SimObject::SIM_SHIP && target->Rep()) { + Ship* tgt_ship = (Ship*) target; + Graphic* g = tgt_ship->Rep(); + Rect r = g->ScreenRect(); + + Point mark_pt; + + if (tgt_ship) + mark_pt = tgt_ship->Location(); + + projector->Transform(mark_pt); + + // clip: + if (mark_pt.z > 1.0) { + projector->Project(mark_pt); + + int x = (int) mark_pt.x; + int y = r.y; + + if (y >= 2000) + y = (int) mark_pt.y; + + if (x > 4 && x < width-4 && + y > 4 && y < height-4) { + + const int BAR_LENGTH = 40; + + // life bars: + int sx = x - BAR_LENGTH/2; + int sy = y - 8; + + double hull_strength = tgt_ship->Integrity() / tgt_ship->Design()->integrity; + + int hw = (int) (BAR_LENGTH * hull_strength); + int sw = (int) (BAR_LENGTH * (tgt_ship->ShieldStrength() / 100.0)); + + System::STATUS s = System::NOMINAL; + + if (hull_strength < 0.30) s = System::CRITICAL; + else if (hull_strength < 0.60) s = System::DEGRADED; + + Color hc = GetStatusColor(s); + Color sc = hud_color; + + window->FillRect(sx, sy, sx+hw, sy+1, hc); + window->FillRect(sx, sy+3, sx+sw, sy+4, sc); + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +HUDView::DrawContact(Contact* contact, int index) +{ + if (index >= MAX_CONTACT) return; + + Color c = MarkerColor(contact); + int c_iff = contact->GetIFF(ship); + Ship* c_ship = contact->GetShip(); + Shot* c_shot = contact->GetShot(); + Point mark_pt = contact->Location(); + double distance = 0; + + if (!c_ship && !c_shot || c_ship == ship) + return; + + if (c_ship && c_ship->GetFlightPhase() < Ship::ACTIVE) + return; + + if (c_ship) { + mark_pt = c_ship->Location(); + + if (c_ship->IsGroundUnit()) + mark_pt += Point(0,150,0); + } + else { + mark_pt = c_shot->Location(); + } + + projector->Transform(mark_pt); + + // clip: + if (mark_pt.z > 1.0) { + distance = mark_pt.length(); + + projector->Project(mark_pt); + + int x = (int) mark_pt.x; + int y = (int) mark_pt.y; + + if (x > 4 && x < width-4 && + y > 4 && y < height-4) { + + DrawDiamond(x,y,3,c); + + if (contact->Threat(ship)) { + if (c_ship) { + window->DrawEllipse(x-6, y-6, x+6, y+6, c); + } + else { + DrawDiamond(x,y,7,c); + } + } + + bool name_crowded = false; + + if (x < width-8) { + char code = *(Game::GetText("HUDView.symbol.fighter").data()); + + if (c_ship) { + if (c_ship->Class() > Ship::LCA) + code = *(Game::GetText("HUDView.symbol.starship").data()); + } + + else if (c_shot) { + code = *(Game::GetText("HUDView.symbol.torpedo").data()); + } + + Sensor* sensor = ship->GetSensor(); + double limit = 75e3; + + if (sensor) + limit = sensor->GetBeamRange(); + + double range = contact->Range(ship, limit); + + char contact_buf[256]; + Rect contact_rect(x+8, y-4, 120, 12); + + if (range == 0) { + sprintf(contact_buf, "%c *", code); + } + else { + bool mega = false; + + if (range > 999e3) { + range /= 1e6; + mega = true; + } + else if (range < 1e3) + range = 1; + else + range /= 1000; + + if (arcade) { + if (c_ship) + strcpy(contact_buf, c_ship->Name()); + else if (!mega) + sprintf(contact_buf, "%c %d", code, (int) range); + else + sprintf(contact_buf, "%c %.1f M", code, range); + } + else { + char closing = '+'; + Point delta_v; + + if (c_ship) + delta_v = ship->Velocity() - c_ship->Velocity(); + else + delta_v = ship->Velocity() - c_shot->Velocity(); + + if (delta_v * ship->Velocity() < 0) // losing ground + closing = '-'; + + if (!mega) + sprintf(contact_buf, "%c %d%c", code, (int) range, closing); + else + sprintf(contact_buf, "%c %.1f M", code, range); + } + } + + if (!IsNameCrowded(x, y)) { + DrawHUDText(TXT_CONTACT_INFO+index, contact_buf, contact_rect, DT_LEFT, HUD_MIXED_CASE); + + if (c_shot || (c_ship && (c_ship->IsDropship() || c_ship->IsStatic()))) + name_crowded = distance > 50e3; + } + else { + name_crowded = true; + } + } + + bool name_drawn = false; + if (NetGame::GetInstance() && c_ship) { + NetPlayer* netp = NetGame::GetInstance()->FindPlayerByObjID(c_ship->GetObjID()); + if (netp && strcmp(netp->Name(), "Server A.I. Ship")) { + Rect contact_rect(x+8, y+6, 120, 12); + DrawHUDText(TXT_CONTACT_NAME+index, netp->Name(), contact_rect, DT_LEFT, HUD_MIXED_CASE); + name_drawn = true; + } + } + + if (!name_drawn && !name_crowded && c_ship && c_iff < 10 && !arcade) { + Rect contact_rect(x+8, y+6, 120, 12); + DrawHUDText(TXT_CONTACT_NAME+index, c_ship->Name(), contact_rect, DT_LEFT, HUD_MIXED_CASE); + } + } + } + + if (contact->Threat(ship) && !ship->IsStarship()) { + if (threat < 1 && c_ship && !c_ship->IsStarship()) + threat = 1; + + if (c_shot) + threat = 2; + } +} + +// +--------------------------------------------------------------------+ + +void +HUDView::DrawTrackSegment(Point& t1, Point& t2, Color c) +{ + int x1, y1, x2, y2; + + projector->Transform(t1); + projector->Transform(t2); + + const double CLIP_Z = 0.1; + + if (t1.z < CLIP_Z && t2.z < CLIP_Z) + return; + + if (t1.z < CLIP_Z && t2.z >= CLIP_Z) { + double dx = t2.x - t1.x; + double dy = t2.y - t1.y; + double s = (CLIP_Z - t1.z) / (t2.z - t1.z); + + t1.x += dx * s; + t1.y += dy * s; + t1.z = CLIP_Z; + } + + else if (t2.z < CLIP_Z && t1.z >= CLIP_Z) { + double dx = t1.x - t2.x; + double dy = t1.y - t2.y; + double s = (CLIP_Z - t2.z) / (t1.z - t2.z); + + t2.x += dx * s; + t2.y += dy * s; + t2.z = CLIP_Z; + } + + if (t1.z >= CLIP_Z && t2.z >= CLIP_Z) { + projector->Project(t1, false); + projector->Project(t2, false); + + x1 = (int) t1.x; + y1 = (int) t1.y; + x2 = (int) t2.x; + y2 = (int) t2.y; + + if (window->ClipLine(x1,y1,x2,y2)) + window->DrawLine(x1,y1,x2,y2,c); + } +} + +void +HUDView::DrawTrack(Contact* contact) +{ + Ship* c_ship = contact->GetShip(); + + if (c_ship && c_ship->GetFlightPhase() < Ship::ACTIVE) + return; + + int ctl = contact->TrackLength(); + Color c = MarkerColor(contact); + + Point t1 = contact->Location(); + Point t2 = contact->TrackPoint(0); + + if (t1 != t2) + DrawTrackSegment(t1, t2, c); + + for (int i = 0; i < ctl-1; i++) { + t1 = contact->TrackPoint(i); + t2 = contact->TrackPoint(i+1); + + if (t1 != t2) + DrawTrackSegment(t1, t2, c * ((double) (ctl-i)/ (double) ctl)); + } +} + +// +--------------------------------------------------------------------+ + +void +HUDView::DrawRect(SimObject* targ) +{ + Graphic* g = targ->Rep(); + Rect r = g->ScreenRect(); + Color c; + + if (targ->Type() == SimObject::SIM_SHIP) + c = ((Ship*) targ)->MarkerColor(); + else + c = ((Shot*) targ)->MarkerColor(); + + if (r.w > 0 && r.h > 0) { + if (r.w < 8) { + r.x -= (8-r.w)/2; + r.w = 8; + } + + if (r.h < 8) { + r.y -= (8-r.h)/2; + r.h = 8; + } + } + + else { + Point mark_pt = targ->Location(); + projector->Transform(mark_pt); + + // clip: + if (mark_pt.z < 1.0) + return; + + projector->Project(mark_pt); + + int x = (int) mark_pt.x; + int y = (int) mark_pt.y; + + if (x < 4 || x > width-4 || y < 4 || y > height-4) + return; + + r.x = x-4; + r.y = y-4; + r.w = 8; + r.h = 8; + } + + // horizontal + window->DrawLine(r.x, r.y, r.x+8, r.y, c); + window->DrawLine(r.x+r.w-8, r.y, r.x+r.w, r.y, c); + window->DrawLine(r.x, r.y+r.h, r.x+8, r.y+r.h, c); + window->DrawLine(r.x+r.w-8, r.y+r.h, r.x+r.w, r.y+r.h, c); + // vertical + window->DrawLine(r.x, r.y, r.x, r.y+8, c); + window->DrawLine(r.x, r.y+r.h-8, r.x, r.y+r.h, c); + window->DrawLine(r.x+r.w, r.y, r.x+r.w, r.y+8, c); + window->DrawLine(r.x+r.w, r.y+r.h-8, r.x+r.w, r.y+r.h, c); +} + +// +--------------------------------------------------------------------+ + +void +HUDView::DrawBars() +{ + fpm_sprite->Hide(); + hpm_sprite->Hide(); + lead_sprite->Hide(); + aim_sprite->Hide(); + tgt1_sprite->Hide(); + tgt2_sprite->Hide(); + tgt3_sprite->Hide(); + tgt4_sprite->Hide(); + chase_sprite->Hide(); + + for (int i = 0; i < 31; i++) + pitch_ladder[i]->Hide(); + + const int bar_width = 256; + const int bar_height = 192; + const int box_width = 120; + + int cx = width/2; + int cy = height/2; + int l = cx - bar_width/2; + int r = cx + bar_width/2; + int t = cy - bar_height/2; + int b = cy + bar_height/2; + int align = DT_LEFT; + + if (Game::Paused()) + DrawHUDText(TXT_PAUSED, Game::GetText("HUDView.PAUSED"), Rect(cx-128, cy-60, 256, 12), DT_CENTER); + + if (ship) { + DrawContactMarkers(); + + char txt[256]; + double speed = ship->Velocity().length(); + + if (ship->Velocity() * ship->Heading() < 0) + speed = -speed; + + FormatNumber(txt, speed); + + if (tactical) { + l = box_width + 16; + r = width - box_width - 16; + } + + Rect speed_rect(l-box_width-8, cy-5, box_width, 12); + + align = (tactical) ? DT_LEFT : DT_RIGHT; + DrawHUDText(TXT_SPEED, txt, speed_rect, align); + + // upper left hud quadrant (airborne fighters) + if (ship->IsAirborne()) { + double alt_msl = ship->AltitudeMSL(); + double alt_agl = ship->AltitudeAGL(); + + if (alt_agl <= 1000) + sprintf(txt, "R %4d", (int) alt_agl); + else + FormatNumber(txt, alt_msl); + + speed_rect.y -= 20; + + if (arcade) { + char arcade_txt[32]; + sprintf(arcade_txt, "%s %s", Game::GetText("HUDView.altitude").data(), txt); + align = (tactical) ? DT_LEFT : DT_RIGHT; + DrawHUDText(TXT_ALTITUDE, arcade_txt, speed_rect, align); + } + else { + align = (tactical) ? DT_LEFT : DT_RIGHT; + DrawHUDText(TXT_ALTITUDE, txt, speed_rect, align); + } + + if (!arcade) { + sprintf(txt, "%.1f G", ship->GForce()); + speed_rect.y -= 20; + + align = (tactical) ? DT_LEFT : DT_RIGHT; + DrawHUDText(TXT_GFORCE, txt, speed_rect, align); + + speed_rect.y += 40; + } + } + + // upper left hud quadrant (starships) + else if (ship->IsStarship() && ship->GetFLCSMode() == Ship::FLCS_HELM && !arcade) { + sprintf(txt, "%s: %.1f", Game::GetText("HUDView.Pitch").data(), ship->GetHelmPitch()/DEGREES); + speed_rect.y -= 50; + + align = (tactical) ? DT_LEFT : DT_RIGHT; + DrawHUDText(TXT_PITCH, txt, speed_rect, align); + + speed_rect.y -= 10; + int heading_degrees = (int) (ship->GetHelmHeading()/DEGREES); + if (heading_degrees < 0) heading_degrees += 360; + sprintf(txt, "%s: %03d", Game::GetText("HUDView.Heading").data(), heading_degrees); + DrawHUDText(TXT_HEADING, txt, speed_rect, align); + + speed_rect.y += 60; + } + + // per user request, all ships should show compass heading + if (!tactical && !arcade) { + Rect heading_rect(l, t+5, bar_width, 12); + int heading_degrees = (int) (ship->CompassHeading()/DEGREES); + if (heading_degrees < 0) heading_degrees += 360; + sprintf(txt, "%d", heading_degrees); + DrawHUDText(TXT_COMPASS, txt, heading_rect, DT_CENTER); + } + + switch (mode) { + case HUD_MODE_TAC: strcpy(txt, Game::GetText("HUDView.mode.tactical").data()); break; + case HUD_MODE_NAV: strcpy(txt, Game::GetText("HUDView.mode.navigation").data()); break; + case HUD_MODE_ILS: strcpy(txt, Game::GetText("HUDView.mode.landing").data()); break; + } + + if (tactical) { + speed_rect.y += 76; + align = DT_LEFT; + } + else { + speed_rect.y = cy+76; + align = DT_RIGHT; + } + + DrawHUDText(TXT_HUD_MODE, txt, speed_rect, align); + + // landing gear: + if (ship->IsGearDown()) { + const char* gear_down = Game::GetText("HUDView.gear-down"); + + Rect gear_rect(l, b+20, box_width, 12); + DrawHUDText(TXT_GEAR_DOWN, gear_down, gear_rect, DT_CENTER, HUD_UPPER_CASE, true); + } + + // sensor/missile lock warnings and quantum drive countdown: + QuantumDrive* quantum = ship->GetQuantumDrive(); + + if (threat || (quantum && quantum->JumpTime() > 0)) { + const char* threat_warn = Game::GetText("HUDView.threat-warn"); + bool show_msg = true; + + if (quantum && quantum->JumpTime() > 0) { + static char buf[64]; + sprintf(buf, "%s: %d", Game::GetText("HUDView.quantum-jump").data(), (int) quantum->JumpTime()); + threat_warn = buf; + } + + else if (threat > 1) { + threat_warn = Game::GetText("HUDView.missile-warn"); + show_msg = ((Game::RealTime()/500) & 1) != 0; + } + + if (show_msg) { + Rect lock_rect(l, t-25, box_width, 12); + DrawHUDText(TXT_THREAT_WARN, threat_warn, lock_rect, DT_CENTER, HUD_MIXED_CASE, true); + } + } + + if (ship->CanTimeSkip()) { + Rect auto_rect(l, t-40, box_width, 12); + DrawHUDText(TXT_AUTO, Game::GetText("HUDView.AUTO"), auto_rect, DT_CENTER, HUD_MIXED_CASE, true); + } + + if (mode == HUD_MODE_NAV) { + Instruction* next = ship->GetNextNavPoint(); + + if (next) { + double distance = ship->RangeToNavPoint(next); + FormatNumber(txt, distance); + + Rect range_rect(r-20, cy-5, box_width, 12); + DrawHUDText(TXT_RANGE, txt, range_rect, DT_RIGHT); + range_rect.Inflate(2,2); + } + } + + // lower left hud quadrant + else if (mode == HUD_MODE_TAC) { + speed_rect.x = l-box_width-8; + speed_rect.y = cy-5 +20; + speed_rect.w = box_width; + align = (tactical) ? DT_LEFT : DT_RIGHT; + + if (!arcade && ship->GetPrimary() && !ship->IsNetObserver()) + DrawHUDText(TXT_PRIMARY_WEP, ship->GetPrimary()->Abbreviation(), speed_rect, align); + + WeaponGroup* missile = ship->GetSecondaryGroup(); + + if (missile && missile->Ammo() > 0 && !ship->IsNetObserver()) { + if (!arcade) { + speed_rect.y = cy-5 +30; + sprintf(txt, "%s %d", missile->Name(), missile->Ammo()); + DrawHUDText(TXT_SECONDARY_WEP, txt, speed_rect, align); + } + + // missile range indicator + if (missile->GetSelected()->Locked()) { + Rect shoot_rect(l, b+5, box_width, 12); + DrawHUDText(TXT_SHOOT, Game::GetText("HUDView.SHOOT"), shoot_rect, DT_CENTER, HUD_MIXED_CASE, true); + } + } + + if (!arcade && !ship->IsNetObserver()) { + if (ship->GetShield()) { + speed_rect.y = cy-5+40; + sprintf(txt, "%s - %03d +", Game::GetText("HUDView.SHIELD").data(), ship->ShieldStrength()); + DrawHUDText(TXT_SHIELD, txt, speed_rect, align); + } + else if (ship->GetDecoy()) { + speed_rect.y = cy-5+40; + sprintf(txt, "%s %d", Game::GetText("HUDView.DECOY").data(), ship->GetDecoy()->Ammo()); + DrawHUDText(TXT_DECOY, txt, speed_rect, align); + } + + + Rect eta_rect = speed_rect; + eta_rect.y += 10; + + align = DT_RIGHT; + + if (tactical) { + eta_rect.x = 8; + align = DT_LEFT; + } + + for (int i = 0; i < 2; i++) { + int eta = ship->GetMissileEta(i); + if (eta > 0) { + int minutes = (eta/60) % 60; + int seconds = (eta ) % 60; + + char eta_buf[16]; + sprintf(eta_buf, "T %d:%02d", minutes, seconds); + DrawHUDText(TXT_MISSILE_T1+i, eta_buf, eta_rect, align); + eta_rect.y += 10; + } + } + } + + NetGame* netgame = NetGame::GetInstance(); + if (netgame && !netgame->IsActive()) { + Rect shoot_rect(l, b+5, box_width, 12); + DrawHUDText(TXT_SHOOT, Game::GetText("HUDView.NET-GAME-OVER"), shoot_rect, DT_CENTER, HUD_MIXED_CASE, true); + } + + else if (ship->IsNetObserver()) { + Rect shoot_rect(l, b+5, box_width, 12); + DrawHUDText(TXT_SHOOT, Game::GetText("HUDView.OBSERVER"), shoot_rect, DT_CENTER, HUD_MIXED_CASE, true); + } + + DrawTarget(); + } + + else if (mode == HUD_MODE_ILS) { + DrawTarget(); + } + + DrawNavInfo(); + } +} + +// +--------------------------------------------------------------------+ + +void +HUDView::DrawFPM() +{ + fpm_sprite->Hide(); + + if (ship->Velocity().length() > 50) { + double xtarg = xcenter; + double ytarg = ycenter; + + Point svel = ship->Velocity(); + svel.Normalize(); + + Point tloc = ship->Location() + svel * 1e8; + // Translate into camera relative: + projector->Transform(tloc); + + int behind = tloc.z < 0; + + if (behind) + return; + + // Project into screen coordinates: + projector->Project(tloc); + + xtarg = tloc.x; + ytarg = tloc.y; + + fpm_sprite->Show(); + fpm_sprite->MoveTo(Point(xtarg, ytarg, 1)); + } +} + +// +--------------------------------------------------------------------+ + +void +HUDView::DrawPitchLadder() +{ + for (int i = 0; i < 31; i++) + pitch_ladder[i]->Hide(); + + if (ship->IsAirborne() && Game::MaxTexSize() > 128) { + double xtarg = xcenter; + double ytarg = ycenter; + + Point uvec = Point(0,1,0); + Point svel = ship->Velocity(); + + if (svel.length() == 0) + svel = ship->Heading(); + + if (svel.x == 0 && svel.z == 0) + return; + + svel.y = 0; + svel.Normalize(); + + Point gloc = ship->Location(); + gloc.y = 0; + + const double baseline = 1e9; + const double clip_angle = 20*DEGREES; + + Point tloc = gloc + svel * baseline; + + // Translate into camera relative: + projector->Transform(tloc); + + // Project into screen coordinates: + projector->Project(tloc); + + xtarg = tloc.x; + ytarg = tloc.y; + + // compute roll angle: + double roll_angle = 0; + double pitch_angle = 0; + + Point heading = ship->Heading(); + heading.Normalize(); + + if (heading.x != 0 || heading.z != 0) { + Point gheading = heading; + gheading.y = 0; + gheading.Normalize(); + + double dot = gheading * heading; + + if (heading.y < 0) dot = -dot; + + pitch_angle = acos(dot); + + if (pitch_angle > PI/2) + pitch_angle -= PI; + + double s0 = sin(pitch_angle); + double c0 = cos(pitch_angle); + double s1 = sin(pitch_angle + 10*DEGREES); + double c1 = cos(pitch_angle + 10*DEGREES); + + tloc = gloc + (svel * baseline * c0) + (uvec * baseline * s0); + projector->Transform(tloc); + + double x0 = tloc.x; + double y0 = tloc.y; + + tloc = gloc + (svel * baseline * c1) + (uvec * baseline * s1); + projector->Transform(tloc); + + double x1 = tloc.x; + double y1 = tloc.y; + + double dx = x1-x0; + double dy = y1-y0; + + roll_angle = atan2(-dy,dx) + PI/2; + } + + const double alias_limit = 0.1*DEGREES; + + if (fabs(roll_angle) <= alias_limit) { + if (roll_angle > 0) + roll_angle = alias_limit; + else + roll_angle = -alias_limit; + } + + else if (fabs(roll_angle-PI) <= alias_limit) { + roll_angle = PI - alias_limit; + } + + if (fabs(pitch_angle) <= clip_angle) { + pitch_ladder[15]->Show(); + pitch_ladder[15]->MoveTo(Point(xtarg, ytarg, 1)); + pitch_ladder[15]->SetAngle(roll_angle); + } + + for (i = 1; i <= 15; i++) { + double angle = i * 5 * DEGREES; + + if (i > 12) + angle = (60 + (i-12)*10) * DEGREES; + + double s = sin(angle); + double c = cos(angle); + + if (fabs(pitch_angle - angle) <= clip_angle) { + // positive angle: + tloc = gloc + (svel * baseline * c) + (uvec * baseline * s); + projector->Transform(tloc); + + if (tloc.z > 0) { + projector->Project(tloc); + pitch_ladder[15-i]->Show(); + pitch_ladder[15-i]->MoveTo(Point(tloc.x, tloc.y, 1)); + pitch_ladder[15-i]->SetAngle(roll_angle); + } + } + + if (fabs(pitch_angle + angle) <= clip_angle) { + // negative angle: + tloc = gloc + (svel * baseline * c) + (uvec * -baseline * s); + projector->Transform(tloc); + + if (tloc.z > 0) { + projector->Project(tloc); + pitch_ladder[15+i]->Show(); + pitch_ladder[15+i]->MoveTo(Point(tloc.x, tloc.y, 1)); + pitch_ladder[15+i]->SetAngle(roll_angle); + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +HUDView::DrawHPM() +{ + hpm_sprite->Hide(); + + if (!ship) + return; + + double xtarg = xcenter; + double ytarg = ycenter; + + double az = ship->GetHelmHeading() - PI; + double el = ship->GetHelmPitch(); + + Point hvec = Point(sin(az), sin(el), cos(az)); + hvec.Normalize(); + + Point tloc = ship->Location() + hvec * 1e8; + // Translate into camera relative: + projector->Transform(tloc); + + int behind = tloc.z < 0; + + if (behind) + return; + + // Project into screen coordinates: + projector->Project(tloc); + + xtarg = tloc.x; + ytarg = tloc.y; + + hpm_sprite->Show(); + hpm_sprite->MoveTo(Point(xtarg, ytarg, 1)); +} + +// +--------------------------------------------------------------------+ + +void +HUDView::HideCompass() +{ + az_ring->Hide(); + az_pointer->Hide(); + el_ring->Hide(); + el_pointer->Hide(); + + Scene* scene = az_ring->GetScene(); + if (scene) { + scene->DelGraphic(az_ring); + scene->DelGraphic(az_pointer); + scene->DelGraphic(el_ring); + scene->DelGraphic(el_pointer); + } +} + +// +--------------------------------------------------------------------+ + +void +HUDView::DrawCompass() +{ + if (!ship || !ship->Rep()) + return; + + Solid* solid = (Solid*) ship->Rep(); + Point loc = solid->Location(); + + az_ring->MoveTo(loc); + az_pointer->MoveTo(loc); + el_ring->MoveTo(loc); + el_pointer->MoveTo(loc); + + double helm_heading = ship->GetHelmHeading(); + double helm_pitch = ship->GetHelmPitch(); + double curr_heading = ship->CompassHeading(); + double curr_pitch = ship->CompassPitch(); + + bool show_az = fabs(helm_heading - curr_heading) > 5*DEGREES; + bool show_el = fabs(helm_pitch - curr_pitch) > 5*DEGREES; + + Scene* scene = camview->GetScene(); + + if (show_az || show_el) { + scene->AddGraphic(az_ring); + az_ring->Show(); + + if (show_el || fabs(helm_pitch) > 5 * DEGREES) { + scene->AddGraphic(el_ring); + Matrix ring_orient; + ring_orient.Yaw(helm_heading + PI); + el_ring->SetOrientation(ring_orient); + el_ring->Show(); + + scene->AddGraphic(el_pointer); + Matrix pointer_orient; + pointer_orient.Yaw(helm_heading + PI); + pointer_orient.Pitch(-helm_pitch); + pointer_orient.Roll(PI/2); + el_pointer->SetOrientation(pointer_orient); + el_pointer->Show(); + } + else { + scene->AddGraphic(az_pointer); + Matrix pointer_orient; + pointer_orient.Yaw(helm_heading + PI); + + az_pointer->SetOrientation(pointer_orient); + az_pointer->Show(); + } + } +} + +// +--------------------------------------------------------------------+ + +void +HUDView::DrawLCOS(SimObject* targ, double dist) +{ + lead_sprite->Hide(); + aim_sprite->Hide(); + chase_sprite->Hide(); + + double xtarg = xcenter; + double ytarg = ycenter; + + Weapon* prim = ship->GetPrimary(); + if (!prim) return; + + Point tloc = targ->Location(); + // Translate into camera relative: + projector->Transform(tloc); + + int behind = tloc.z < 0; + + if (behind) + tloc.z = -tloc.z; + + // Project into screen coordinates: + projector->Project(tloc); + + // DRAW THE OFFSCREEN CHASE INDICATOR: + if (behind || + tloc.x <= 0 || tloc.x >= width-1 || + tloc.y <= 0 || tloc.y >= height-1) { + + // Left side: + if (tloc.x <= 0 || (behind && tloc.x < width/2)) { + if (tloc.y < ah) tloc.y = ah; + else if (tloc.y >= height-ah) tloc.y = height-1-ah; + + chase_sprite->Show(); + chase_sprite->SetAnimation(&chase_left); + chase_sprite->MoveTo(Point(aw, tloc.y, 1)); + } + + // Right side: + else if (tloc.x >= width-1 || behind) { + if (tloc.y < ah) tloc.y = ah; + else if (tloc.y >= height-ah) tloc.y = height-1-ah; + + chase_sprite->Show(); + chase_sprite->SetAnimation(&chase_right); + chase_sprite->MoveTo(Point(width-1-aw, tloc.y, 1)); + } + else { + if (tloc.x < aw) tloc.x = aw; + else if (tloc.x >= width-aw) tloc.x = width-1-aw; + + // Top edge: + if (tloc.y <= 0) { + chase_sprite->Show(); + chase_sprite->SetAnimation(&chase_top); + chase_sprite->MoveTo(Point(tloc.x, ah, 1)); + } + + // Bottom edge: + else if (tloc.y >= height-1) { + chase_sprite->Show(); + chase_sprite->SetAnimation(&chase_bottom); + chase_sprite->MoveTo(Point(tloc.x, height-1-ah, 1)); + } + } + } + + // DRAW THE LCOS: + else { + if (!ship->IsStarship()) { + Point aim_vec = ship->Heading(); + aim_vec.Normalize(); + + // shot speed is relative to ship speed: + Point shot_vel = ship->Velocity() + aim_vec * prim->Design()->speed; + double shot_speed = shot_vel.length(); + + // time for shot to reach target + double time = dist / shot_speed; + + // LCOS (Lead Computing Optical Sight) + if (gunsight == 0) { + // where the shot will be when it is the same distance + // away from the ship as the target: + Point impact = ship->Location() + (shot_vel * time); + + // where the target will be when the shot reaches it: + Point targ_vel = targ->Velocity(); + Point dest = targ->Location() + (targ_vel * time); + Point delta = impact - dest; + + // draw the gun sight here in 3d world coordinates: + Point sight = targ->Location() + delta; + + // Project into screen coordinates: + projector->Transform(sight); + projector->Project(sight); + + xtarg = sight.x; + ytarg = sight.y; + + aim_sprite->Show(); + aim_sprite->MoveTo(Point(xtarg, ytarg, 1)); + } + + // Wing Commander style lead indicator + else { + // where the target will be when the shot reaches it: + Point targ_vel = targ->Velocity() - ship->Velocity(); + Point dest = targ->Location() + (targ_vel * time); + + // Translate into camera relative: + projector->Transform(dest); + projector->Project(dest); + + xtarg = dest.x; + ytarg = dest.y; + + lead_sprite->Show(); + lead_sprite->MoveTo(Point(xtarg, ytarg, 1)); + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +HUDView::DrawTarget() +{ + const int bar_width = 256; + const int bar_height = 192; + const int box_width = 120; + + SimObject* old_target = target; + + if (mode == HUD_MODE_ILS) { + Ship* controller = ship->GetController(); + if (controller && !target) + target = controller; + } + + if (target && target->Rep()) { + Sensor* sensor = ship->GetSensor(); + Contact* contact = 0; + + if (sensor && target->Type() == SimObject::SIM_SHIP) { + contact = sensor->FindContact((Ship*) target); + } + + int cx = width/2; + int cy = height/2; + int l = cx - bar_width/2; + int r = cx + bar_width/2; + int t = cy - bar_height/2; + int b = cy + bar_height/2; + Point delta = target->Location() - ship->Location(); + double distance = delta.length(); + Point delta_v = ship->Velocity() - target->Velocity(); + double speed = delta_v.length(); + char txt[256]; + + if (mode == HUD_MODE_ILS && ship->GetInbound() && ship->GetInbound()->GetDeck()) { + delta = ship->GetInbound()->GetDeck()->EndPoint() - ship->Location(); + distance = delta.length(); + } + + if (delta * ship->Velocity() > 0) { // in front + if (delta_v * ship->Velocity() < 0) // losing ground + speed = -speed; + } + else { // behind + if (delta_v * ship->Velocity() > 0) // passing + speed = -speed; + } + + Rect range_rect(r-20, cy-5, box_width, 12); + + if (tactical) + range_rect.x = width - range_rect.w - 8; + + if (contact) { + Sensor* sensor = ship->GetSensor(); + double limit = 75e3; + + if (sensor) + limit = sensor->GetBeamRange(); + + distance = contact->Range(ship, limit); + + if (!contact->ActLock() && !contact->PasLock()) { + strcpy(txt, Game::GetText("HUDView.No-Range").data()); + speed = 0; + } + else { + FormatNumber(txt, distance); + } + } + + else { + FormatNumber(txt, distance); + } + + DrawHUDText(TXT_RANGE, txt, range_rect, DT_RIGHT); + + if (arcade) { + target = old_target; + return; + } + + range_rect.y += 18; + FormatNumber(txt, speed); + DrawHUDText(TXT_CLOSING_SPEED, txt, range_rect, DT_RIGHT); + + // target info: + if (!tactical) { + range_rect.y = cy-76; + } + + else { + range_rect.x = width - 2*box_width - 8; + range_rect.y = cy-76; + range_rect.w = 2*box_width; + } + + DrawHUDText(TXT_TARGET_NAME, target->Name(), range_rect, DT_RIGHT); + + if (target->Type() == SimObject::SIM_SHIP) { + Ship* tgt_ship = (Ship*) target; + + range_rect.y += 10; + DrawHUDText(TXT_TARGET_DESIGN, tgt_ship->Design()->display_name, range_rect, DT_RIGHT); + + if (mode != HUD_MODE_ILS) { + if (tgt_ship->IsStarship()) { + range_rect.y += 10; + sprintf(txt, "%s %03d", Game::GetText("HUDView.symbol.shield").data(), (int) tgt_ship->ShieldStrength()); + DrawHUDText(TXT_TARGET_SHIELD, txt, range_rect, DT_RIGHT); + } + + range_rect.y += 10; + sprintf(txt, "%s %03d", Game::GetText("HUDView.symbol.hull").data(), (int) (tgt_ship->Integrity() / tgt_ship->Design()->integrity * 100)); + DrawHUDText(TXT_TARGET_HULL, txt, range_rect, DT_RIGHT); + + System* sys = ship->GetSubTarget(); + if (sys) { + Color stat = hud_color; + static DWORD blink = Game::RealTime(); + + int blink_delta = Game::RealTime() - blink; + sprintf(txt, "%s %03d", sys->Abbreviation(), (int) sys->Availability()); + + switch (sys->Status()) { + case System::DEGRADED: stat = Color(255,255, 0); break; + case System::CRITICAL: + case System::DESTROYED: stat = Color(255, 0, 0); break; + case System::MAINT: + if (blink_delta < 250) + stat = Color(8,8,8); + break; + } + + if (blink_delta > 500) + blink = Game::RealTime(); + + range_rect.y += 10; + DrawHUDText(TXT_TARGET_SUB, txt, range_rect, DT_RIGHT); + } + } + } + + else if (target->Type() == SimObject::SIM_DRONE) { + Drone* tgt_drone = (Drone*) target; + + range_rect.y += 10; + DrawHUDText(TXT_TARGET_DESIGN, tgt_drone->DesignName(), range_rect, DT_RIGHT); + + range_rect.y += 10; + int eta = tgt_drone->GetEta(); + + if (eta > 0) { + int minutes = (eta/60) % 60; + int seconds = (eta ) % 60; + + char eta_buf[16]; + sprintf(eta_buf, "T %d:%02d", minutes, seconds); + DrawHUDText(TXT_TARGET_ETA, eta_buf, range_rect, DT_RIGHT); + } + } + } + + target = old_target; +} + +// +--------------------------------------------------------------------+ + +void +HUDView::DrawNavInfo() +{ + const int bar_width = 256; + const int bar_height = 192; + const int box_width = 120; + + if (arcade) { + if (ship->IsAutoNavEngaged()) { + Rect info_rect(width/2-box_width, height/2+bar_height, box_width*2, 12); + + if (big_font) + hud_text[TXT_NAV_INDEX].font = big_font; + + DrawHUDText(TXT_NAV_INDEX, Game::GetText("HUDView.Auto-Nav"), info_rect, DT_CENTER); + } + + return; + } + + hud_text[TXT_NAV_INDEX].font = hud_font; + + Instruction* navpt = ship->GetNextNavPoint(); + + if (navpt) { + int cx = width/2; + int cy = height/2; + int l = cx - bar_width/2; + int r = cx + bar_width/2; + int t = cy - bar_height/2; + int b = cy + bar_height/2; + + int index = ship->GetNavIndex(navpt); + double distance = ship->RangeToNavPoint(navpt); + double speed = ship->Velocity().length(); + int etr = 0; + char txt[256]; + + if (speed > 10) + etr = (int) (distance/speed); + + Rect info_rect(r-20, cy+32, box_width, 12); + + if (tactical) + info_rect.x = width - info_rect.w - 8; + + if (ship->IsAutoNavEngaged()) + sprintf(txt, "%s %d", Game::GetText("HUDView.Auto-Nav").data(), index); + else + sprintf(txt, "%s %d", Game::GetText("HUDView.Nav").data(), index); + DrawHUDText(TXT_NAV_INDEX, txt, info_rect, DT_RIGHT); + + info_rect.y += 10; + if (navpt->Action()) + DrawHUDText(TXT_NAV_ACTION, Instruction::ActionName(navpt->Action()), info_rect, DT_RIGHT); + + info_rect.y += 10; + FormatNumber(txt, navpt->Speed()); + DrawHUDText(TXT_NAV_SPEED, txt, info_rect, DT_RIGHT); + + if (etr > 3600) { + info_rect.y += 10; + sprintf(txt, "%s XX:XX", Game::GetText("HUDView.time-enroute").data()); + DrawHUDText(TXT_NAV_ETR, txt, info_rect, DT_RIGHT); + } + else if (etr > 0) { + info_rect.y += 10; + + int minutes = (etr/60) % 60; + int seconds = (etr ) % 60; + sprintf(txt, "%s %2d:%02d", Game::GetText("HUDView.time-enroute").data(), minutes, seconds); + DrawHUDText(TXT_NAV_ETR, txt, info_rect, DT_RIGHT); + } + + if (navpt->HoldTime() > 0) { + info_rect.y += 10; + + int hold = (int) navpt->HoldTime(); + int minutes = (hold/60) % 60; + int seconds = (hold ) % 60; + sprintf(txt, "%s %2d:%02d", Game::GetText("HUDView.HOLD").data(), minutes, seconds); + DrawHUDText(TXT_NAV_HOLD, txt, info_rect, DT_RIGHT); + } + } +} + +// +--------------------------------------------------------------------+ + +void +HUDView::DrawSight() +{ + if (target && target->Rep()) { + Point delta = target->Location() - ship->Location(); + double distance = delta.length(); + + // draw LCOS on target: + if (!tactical) + DrawLCOS(target, distance); + } +} + +// +--------------------------------------------------------------------+ + +void +HUDView::DrawDesignators() +{ + double xtarg = xcenter; + double ytarg = ycenter; + SimObject* t1 = 0; + SimObject* t2 = 0; + SimObject* t3 = 0; + Sprite* sprite = 0; + + tgt1_sprite->Hide(); + tgt2_sprite->Hide(); + tgt3_sprite->Hide(); + tgt4_sprite->Hide(); + + // fighters just show primary target: + if (ship->IsDropship()) { + SimObject* t = ship->GetTarget(); + System* s = ship->GetSubTarget(); + + if (t) { + Point tloc = t->Location(); + if (s) { + tloc = s->MountLocation(); + } + else if (t->Type() == SimObject::SIM_SHIP) { + Ship* tgt_ship = (Ship*) t; + + if (tgt_ship->IsGroundUnit()) + tloc += Point(0,150,0); + } + + projector->Transform(tloc); + + if (tloc.z > 0) { + projector->Project(tloc); + + xtarg = tloc.x; + ytarg = tloc.y; + + if (xtarg>0 && xtarg0 && ytargLocation() - ship->Location()).length(); + + // use out-of-range crosshair if out of range: + if (!ship->GetPrimaryDesign() || ship->GetPrimaryDesign()->max_range < range) { + tgt4_sprite->Show(); + tgt4_sprite->MoveTo(Point(xtarg, ytarg, 1)); + } + + // else, use in-range primary crosshair: + else { + tgt1_sprite->Show(); + tgt1_sprite->MoveTo(Point(xtarg, ytarg, 1)); + } + } + } + } + } + + // starships show up to three targets: + else { + ListIter w = ship->Weapons(); + while (!t3 && ++w) { + SimObject* t = w->GetTarget(); + System* s = w->GetSubTarget(); + + if (w->Contains(ship->GetPrimary())) { + if (t == 0) t = ship->GetTarget(); + t1 = t; + sprite = tgt1_sprite; + } + + else if (t && w->Contains(ship->GetSecondary())) { + t2 = t; + sprite = tgt2_sprite; + + if (t2 == t1) + continue; // don't overlap target designators + } + + else if (t) { + t3 = t; + sprite = tgt3_sprite; + + if (t3 == t1 || t3 == t2) + continue; // don't overlap target designators + } + + if (t) { + Point tloc = t->Location(); + + if (s) + tloc = s->MountLocation(); + + projector->Transform(tloc); + + if (tloc.z > 0) { + projector->Project(tloc); + + xtarg = tloc.x; + ytarg = tloc.y; + + if (xtarg>0 && xtarg0 && ytargLocation() - ship->Location()).length(); + + // flip to out-of-range crosshair + if (sprite == tgt1_sprite) { + if (!ship->GetPrimaryDesign() || ship->GetPrimaryDesign()->max_range < range) { + sprite = tgt4_sprite; + } + } + + sprite->Show(); + sprite->MoveTo(Point(xtarg, ytarg, 1)); + } + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +Color +HUDView::GetStatusColor(System::STATUS status) +{ + Color sc; + + switch (status) { + default: + case System::NOMINAL: sc = Color( 32,192, 32); break; + case System::DEGRADED: sc = Color(255,255, 0); break; + case System::CRITICAL: sc = Color(255, 0, 0); break; + case System::DESTROYED: sc = Color( 0, 0, 0); break; + } + + return sc; +} + +void +HUDView::SetStatusColor(System::STATUS status) +{ + switch (status) { + default: + case System::NOMINAL: status_color = txt_color; break; + case System::DEGRADED: status_color = Color(255,255, 0); break; + case System::CRITICAL: status_color = Color(255, 0, 0); break; + case System::DESTROYED: status_color = Color( 0, 0, 0); break; + } +} + +// +--------------------------------------------------------------------+ + +static int GetReactorStatus(Ship* ship) +{ + if (!ship || ship->Reactors().size() < 1) + return -1; + + int status = System::NOMINAL; + bool maint = false; + + ListIter iter = ship->Reactors(); + while (++iter) { + PowerSource* s = iter.value(); + + if (s->Status() < status) + status = s->Status(); + } + + if (maint && status == System::NOMINAL) + status = System::MAINT; + + return status; +} + +static int GetDriveStatus(Ship* ship) +{ + if (!ship || ship->Drives().size() < 1) + return -1; + + int status = System::NOMINAL; + bool maint = false; + + ListIter iter = ship->Drives(); + while (++iter) { + Drive* s = iter.value(); + + if (s->Status() < status) + status = s->Status(); + + else if (s->Status() == System::MAINT) + maint = true; + } + + if (maint && status == System::NOMINAL) + status = System::MAINT; + + return status; +} + +static int GetQuantumStatus(Ship* ship) +{ + if (!ship || ship->GetQuantumDrive() == 0) + return -1; + + QuantumDrive* s = ship->GetQuantumDrive(); + return s->Status(); +} + +static int GetThrusterStatus(Ship* ship) +{ + if (!ship || ship->GetThruster() == 0) + return -1; + + Thruster* s = ship->GetThruster(); + return s->Status(); +} + +static int GetShieldStatus(Ship* ship) +{ + if (!ship) + return -1; + + Shield* s = ship->GetShield(); + Weapon* d = ship->GetDecoy(); + + if (!s && !d) + return -1; + + int status = System::NOMINAL; + bool maint = false; + + if (s) { + if (s->Status() < status) + status = s->Status(); + + else if (s->Status() == System::MAINT) + maint = true; + } + + if (d) { + if (d->Status() < status) + status = d->Status(); + + else if (d->Status() == System::MAINT) + maint = true; + } + + if (maint && status == System::NOMINAL) + status = System::MAINT; + + return status; +} + +static int GetWeaponStatus(Ship* ship, int index) +{ + if (!ship || ship->Weapons().size() <= index) + return -1; + + WeaponGroup* group = ship->Weapons().at(index); + + int status = System::NOMINAL; + bool maint = false; + + ListIter iter = group->GetWeapons(); + while (++iter) { + Weapon* s = iter.value(); + + if (s->Status() < status) + status = s->Status(); + + else if (s->Status() == System::MAINT) + maint = true; + } + + if (maint && status == System::NOMINAL) + status = System::MAINT; + + return status; +} + +static int GetSensorStatus(Ship* ship) +{ + if (!ship || ship->GetSensor() == 0) + return -1; + + Sensor* s = ship->GetSensor(); + Weapon* p = ship->GetProbeLauncher(); + + int status = s->Status(); + bool maint = s->Status() == System::MAINT; + + if (p) { + if (p->Status() < status) + status = p->Status(); + + else if (p->Status() == System::MAINT) + maint = true; + } + + if (maint && status == System::NOMINAL) + status = System::MAINT; + + return status; +} + +static int GetComputerStatus(Ship* ship) +{ + if (!ship || ship->Computers().size() < 1) + return -1; + + int status = System::NOMINAL; + bool maint = false; + + ListIter iter = ship->Computers(); + while (++iter) { + Computer* s = iter.value(); + + if (s->Status() < status) + status = s->Status(); + + else if (s->Status() == System::MAINT) + maint = true; + } + + if (ship->GetNavSystem()) { + NavSystem* s = ship->GetNavSystem(); + + if (s->Status() < status) + status = s->Status(); + + else if (s->Status() == System::MAINT) + maint = true; + } + + if (maint && status == System::NOMINAL) + status = System::MAINT; + + return status; +} + +static int GetFlightDeckStatus(Ship* ship) +{ + if (!ship || ship->FlightDecks().size() < 1) + return -1; + + int status = System::NOMINAL; + bool maint = false; + + ListIter iter = ship->FlightDecks(); + while (++iter) { + FlightDeck* s = iter.value(); + + if (s->Status() < status) + status = s->Status(); + + else if (s->Status() == System::MAINT) + maint = true; + } + + if (maint && status == System::NOMINAL) + status = System::MAINT; + + return status; +} + +void +HUDView::DrawWarningPanel() +{ + int box_width = 75; + int box_height = 17; + int row_height = 28; + int box_left = width/2 - box_width*2; + + if (cockpit_hud_texture) { + box_left = 275; + box_height = 18; + row_height = 18; + } + + if (ship) { + if (Game::MaxTexSize() > 128) { + warn_left_sprite->Show(); + warn_right_sprite->Show(); + } + + int x = box_left; + int y = cockpit_hud_texture ? 410 : height-97; + int c = cockpit_hud_texture ? 3 : 4; + + static DWORD blink = Game::RealTime(); + + for (int index = 0; index < 12; index++) { + int stat = -1; + Text abrv = Game::GetText("HUDView.UNKNOWN"); + + switch (index) { + case 0: stat = GetReactorStatus(ship); abrv = Game::GetText("HUDView.REACTOR"); break; + case 1: stat = GetDriveStatus(ship); abrv = Game::GetText("HUDView.DRIVE"); break; + case 2: stat = GetQuantumStatus(ship); abrv = Game::GetText("HUDView.QUANTUM"); break; + case 3: stat = GetShieldStatus(ship); abrv = Game::GetText("HUDView.SHIELD"); + if (ship->GetShield() == 0 && ship->GetDecoy()) + abrv = Game::GetText("HUDView.DECOY"); + break; + + case 4: + case 5: + case 6: + case 7: stat = GetWeaponStatus(ship, index-4); + if (stat >= 0) { + WeaponGroup* g = ship->Weapons().at(index-4); + abrv = g->Name(); + } + break; + + case 8: stat = GetSensorStatus(ship); abrv = Game::GetText("HUDView.SENSOR"); break; + case 9: stat = GetComputerStatus(ship); abrv = Game::GetText("HUDView.COMPUTER"); break; + case 10: stat = GetThrusterStatus(ship); abrv = Game::GetText("HUDView.THRUSTER"); break; + case 11: stat = GetFlightDeckStatus(ship);abrv = Game::GetText("HUDView.FLTDECK"); break; + } + + Rect warn_rect(x, y, box_width, box_height); + + if (cockpit_hud_texture) + cockpit_hud_texture->DrawRect(warn_rect, Color::DarkGray); + + if (stat >= 0) { + SetStatusColor((System::STATUS) stat); + Color tc = status_color; + + if (stat != System::NOMINAL) { + if (Game::RealTime() - blink < 250) { + tc = cockpit_hud_texture ? txt_color : Color(8,8,8); + } + } + + if (cockpit_hud_texture) { + if (tc != txt_color) { + Rect r2 = warn_rect; + r2.Inset(1,1,1,1); + cockpit_hud_texture->FillRect(r2, tc); + tc = Color::Black; + } + + warn_rect.y += 4; + + hud_font->SetColor(tc); + hud_font->DrawText(abrv, -1, + warn_rect, + DT_CENTER | DT_SINGLELINE, + cockpit_hud_texture); + + warn_rect.y -= 4; + } + else { + DrawHUDText(TXT_CAUTION_TXT + index, + abrv, + warn_rect, + DT_CENTER); + + hud_text[TXT_CAUTION_TXT + index].color = tc; + } + } + + x += box_width; + + if (--c <= 0) { + c = cockpit_hud_texture ? 3 : 4; + x = box_left; + y += row_height; + } + } + + if (Game::RealTime() - blink > 500) + blink = Game::RealTime(); + + // reset for next time + SetStatusColor(System::NOMINAL); + } +} + +// +--------------------------------------------------------------------+ + +void +HUDView::DrawInstructions() +{ + if (!ship) return; + + if (Game::MaxTexSize() > 128) { + instr_left_sprite->Show(); + instr_right_sprite->Show(); + } + + int ninst = 0; + int nobj = 0; + Element* elem = ship->GetElement(); + + if (elem) { + ninst = elem->NumInstructions(); + nobj = elem->NumObjectives(); + } + + Rect r = Rect(width/2 - 143, height - 105, 290, 17); + + if (ninst) { + int npages = ninst/6 + (ninst%6 ? 1 : 0); + + if (inst_page >= npages) + inst_page = npages-1; + else if (inst_page < 0) + inst_page = 0; + + int first = inst_page * 6; + int last = first + 6; + if (last > ninst) last = ninst; + + int n = TXT_CAUTION_TXT; + + for (int i = first; i < last; i++) { + hud_text[n].color = standard_txt_colors[color]; + DrawHUDText(n++, FormatInstruction(elem->GetInstruction(i)), r, DT_LEFT, HUD_MIXED_CASE); + r.y += 14; + } + + char page[32]; + sprintf(page, "%d / %d", inst_page+1, npages); + r = Rect(width/2 + 40, height-16, 110, 16); + DrawHUDText(TXT_INSTR_PAGE, page, r, DT_CENTER, HUD_MIXED_CASE); + } + + else if (nobj) { + int n = TXT_CAUTION_TXT; + + for (int i = 0; i < nobj; i++) { + char desc[256]; + sprintf(desc, "* %s", elem->GetObjective(i)->GetShortDescription()); + hud_text[n].color = standard_txt_colors[color]; + DrawHUDText(n++, desc, r, DT_LEFT, HUD_MIXED_CASE); + r.y += 14; + } + } + + else { + hud_text[TXT_CAUTION_TXT].color = standard_txt_colors[color]; + DrawHUDText(TXT_CAUTION_TXT, Game::GetText("HUDView.No-Instructions"), r, DT_LEFT, HUD_MIXED_CASE); + } +} + +// +--------------------------------------------------------------------+ + +const char* +HUDView::FormatInstruction(Text instr) +{ + if (!instr.contains('$')) + return (const char*) instr; + + static char result[256]; + + const char* s = (const char*) instr; + char* d = result; + + KeyMap& keymap = Starshatter::GetInstance()->GetKeyMap(); + + while (*s) { + if (*s == '$') { + s++; + char action[32]; + char* a = action; + while (*s && (isupper(*s) || isdigit(*s) || *s == '_')) *a++ = *s++; + *a = 0; + + int act = KeyMap::GetKeyAction(action); + int key = keymap.FindMapIndex(act); + const char* s2 = keymap.DescribeKey(key); + + if (!s2) s2 = action; + while (*s2) *d++ = *s2++; + } + else { + *d++ = *s++; + } + } + + *d = 0; + + return result; +} + +// +--------------------------------------------------------------------+ + +void +HUDView::CycleInstructions(int direction) +{ + if (direction > 0) + inst_page++; + else + inst_page--; +} + +// +--------------------------------------------------------------------+ + +void +HUDView::DrawMessages() +{ + int message_queue_empty = true; + + // age messages: + for (int i = 0; i < MAX_MSG; i++) { + if (msg_time[i] > 0) { + msg_time[i] -= Game::GUITime(); + + if (msg_time[i] <= 0) { + msg_time[i] = 0; + msg_text[i] = ""; + } + + message_queue_empty = false; + } + } + + if (!message_queue_empty) { + // advance message pipeline: + for (i = 0; i < MAX_MSG; i++) { + if (msg_time[0] == 0) { + for (int j = 0; j < MAX_MSG-1; j++) { + msg_time[j] = msg_time[j+1]; + msg_text[j] = msg_text[j+1]; + } + + msg_time[MAX_MSG-1] = 0; + msg_text[MAX_MSG-1] = ""; + } + } + + // draw messages: + for (i = 0; i < MAX_MSG; i++) { + int index = TXT_MSG_1 + i; + + if (msg_time[i] > 0) { + Rect msg_rect(10, 95 + i*10, width-20, 12); + DrawHUDText(index, msg_text[i], msg_rect, DT_LEFT, HUD_MIXED_CASE); + if (msg_time[i] > 1) + hud_text[index].color = txt_color; + else + hud_text[index].color = txt_color.dim(0.5 + 0.5*msg_time[i]); + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +HUDView::DrawNav() +{ + if (!sim) + return; + + active_region = sim->GetActiveRegion(); + + if (ship) { + int nav_index = 1; + Instruction* next = ship->GetNextNavPoint(); + + if (mode == HUD_MODE_NAV) { + if (next && next->Action() == Instruction::LAUNCH) + DrawNavPoint(*next, 0, true); + + ListIter navpt = ship->GetFlightPlan(); + while (++navpt) { + DrawNavPoint(*navpt.value(), nav_index++, (navpt.value() == next)); + } + } + else if (next) { + DrawNavPoint(*next, 0, true); + } + } +} + +void +HUDView::DrawILS() +{ + if (ship) { + bool hoops_drawn = false; + bool same_sector = false; + + InboundSlot* inbound = ship->GetInbound(); + if (inbound) { + FlightDeck* fd = inbound->GetDeck(); + + if (fd && fd->IsRecoveryDeck() && fd->GetCarrier()) { + if (fd->GetCarrier()->GetRegion() == ship->GetRegion()) + same_sector = true; + + if (same_sector && mode == HUD_MODE_ILS && !transition && !docking) { + Point dst = fd->MountLocation(); + projector->Transform(dst); + + if (dst.z > 1.0) { + projector->Project(dst); + + int x = (int) dst.x; + int y = (int) dst.y; + + if (x > 4 && x < width-4 && + y > 4 && y < height-4) { + + window->DrawLine(x-6, y-6, x+6, y+6, hud_color); + window->DrawLine(x+6, y-6, x-6, y+6, hud_color); + } + } + } + + // draw the hoops for this flight deck: + Scene* scene = camview->GetScene(); + for (int h = 0; h < fd->NumHoops(); h++) { + Hoop* hoop = fd->GetHoops() + h; + if (hoop && scene) { + if (same_sector && mode == HUD_MODE_ILS && !transition && !docking) { + scene->AddGraphic(hoop); + hoop->Show(); + + hoops_drawn = true; + } + else { + hoop->Hide(); + scene->DelGraphic(hoop); + } + } + } + } + } + + if (!hoops_drawn) { + ListIter iter = ship->GetRegion()->Carriers(); + while (++iter) { + Ship* carrier = iter.value(); + + bool ours = (carrier->GetIFF() == ship->GetIFF()) || + (carrier->GetIFF() == 0); + + for (int i = 0; i < carrier->NumFlightDecks(); i++) { + FlightDeck* fd = carrier->GetFlightDeck(i); + + if (fd && fd->IsRecoveryDeck()) { + if (mode == HUD_MODE_ILS && ours && !transition && !docking) { + Point dst = fd->MountLocation(); + projector->Transform(dst); + + if (dst.z > 1.0) { + projector->Project(dst); + + int x = (int) dst.x; + int y = (int) dst.y; + + if (x > 4 && x < width-4 && + y > 4 && y < height-4) { + + window->DrawLine(x-6, y-6, x+6, y+6, hud_color); + window->DrawLine(x+6, y-6, x-6, y+6, hud_color); + } + } + } + + // draw the hoops for this flight deck: + Scene* scene = camview->GetScene(); + for (int h = 0; h < fd->NumHoops(); h++) { + Hoop* hoop = fd->GetHoops() + h; + if (hoop && scene) { + if (mode == HUD_MODE_ILS && ours && !transition && !docking) { + hoop->Show(); + if (!hoop->GetScene()) + scene->AddGraphic(hoop); + } + else { + hoop->Hide(); + if (hoop->GetScene()) + scene->DelGraphic(hoop); + } + } + } + } + } + } + } + } +} + +void +HUDView::DrawObjective() +{ + if (ship && ship->GetDirector() && ship->GetDirector()->Type() >= SteerAI::SEEKER) { + SteerAI* steer = (SteerAI*) ship->GetDirector(); + Point obj = steer->GetObjective(); + projector->Transform(obj); + + if (obj.z > 1.0) { + projector->Project(obj); + + int x = (int) obj.x; + int y = (int) obj.y; + + if (x > 4 && x < width-4 && + y > 4 && y < height-4) { + + Color c = Color::Cyan; + window->DrawRect(x-6, y-6, x+6, y+6, c); + window->DrawLine(x-6, y-6, x+6, y+6, c); + window->DrawLine(x+6, y-6, x-6, y+6, c); + } + } + + if (steer->GetOther()) { + obj = steer->GetOther()->Location(); + projector->Transform(obj); + + if (obj.z > 1.0) { + projector->Project(obj); + + int x = (int) obj.x; + int y = (int) obj.y; + + if (x > 4 && x < width-4 && + y > 4 && y < height-4) { + + Color c = Color::Orange; + window->DrawRect(x-6, y-6, x+6, y+6, c); + window->DrawLine(x-6, y-6, x+6, y+6, c); + window->DrawLine(x+6, y-6, x-6, y+6, c); + } + } + } + } + /***/ +} + +void +HUDView::DrawNavPoint(Instruction& navpt, int index, int next) +{ + if (index >= 15 || !navpt.Region()) return; + + // transform from starsystem to world coordinates: + Point npt = navpt.Region()->Location() + navpt.Location(); + + if (active_region) + npt -= active_region->Location(); + + npt = npt.OtherHand(); + + // transform from world to camera: + projector->Transform(npt); + + // clip: + if (npt.z > 1.0) { + + // project: + projector->Project(npt); + + int x = (int) npt.x; + int y = (int) npt.y; + + // clip: + if (x > 4 && x < width-4 && + y > 4 && y < height-4) { + + Color c = Color::White; + if (navpt.Status() > Instruction::ACTIVE && navpt.HoldTime() <= 0) + c = Color::DarkGray; + + // draw: + if (next) + window->DrawEllipse(x-6, y-6, x+5, y+5, c); + + window->DrawLine(x-6, y-6, x+6, y+6, c); + window->DrawLine(x+6, y-6, x-6, y+6, c); + + if (index > 0) { + char npt_buf[32]; + Rect npt_rect(x+10, y-4, 200, 12); + + if (navpt.Status() == Instruction::COMPLETE && navpt.HoldTime() > 0) { + char hold_time[32]; + FormatTime(hold_time, navpt.HoldTime()); + sprintf(npt_buf, "%d %s", index, hold_time); + } + else { + sprintf(npt_buf, "%d", index); + } + + DrawHUDText(TXT_NAV_PT + index, npt_buf, npt_rect, DT_LEFT); + } + } + } + + if (next && mode == HUD_MODE_NAV && navpt.Region() == ship->GetRegion()) { + + // Translate into camera relative: + Point tloc = navpt.Location().OtherHand(); + projector->Transform(tloc); + + int behind = tloc.z < 0; + + if (behind) + tloc.z = -tloc.z; + + // Project into screen coordinates: + projector->Project(tloc); + + // DRAW THE OFFSCREEN CHASE INDICATOR: + if (behind || + tloc.x <= 0 || tloc.x >= width-1 || + tloc.y <= 0 || tloc.y >= height-1) { + + // Left side: + if (tloc.x <= 0 || (behind && tloc.x < width/2)) { + if (tloc.y < ah) tloc.y = ah; + else if (tloc.y >= height-ah) tloc.y = height-1-ah; + + chase_sprite->Show(); + chase_sprite->SetAnimation(&chase_left); + chase_sprite->MoveTo(Point(aw, tloc.y, 1)); + } + + // Right side: + else if (tloc.x >= width-1 || behind) { + if (tloc.y < ah) tloc.y = ah; + else if (tloc.y >= height-ah) tloc.y = height-1-ah; + + chase_sprite->Show(); + chase_sprite->SetAnimation(&chase_right); + chase_sprite->MoveTo(Point(width-1-aw, tloc.y, 1)); + } + else { + if (tloc.x < aw) tloc.x = aw; + else if (tloc.x >= width-aw) tloc.x = width-1-aw; + + // Top edge: + if (tloc.y <= 0) { + chase_sprite->Show(); + chase_sprite->SetAnimation(&chase_top); + chase_sprite->MoveTo(Point(tloc.x, ah, 1)); + } + + // Bottom edge: + else if (tloc.y >= height-1) { + chase_sprite->Show(); + chase_sprite->SetAnimation(&chase_bottom); + chase_sprite->MoveTo(Point(tloc.x, height-1-ah, 1)); + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +HUDView::SetShip(Ship* s) +{ + if (ship != s) { + double new_scale = 1; + + ship_status = -1; + ship = s; + + if (ship) { + if (ship->Life() == 0 || ship->IsDying() || ship->IsDead()) { + ship = 0; + } + else { + Observe(ship); + new_scale = 1.1 * ship->Radius() / 64; + + if (ship->Design()->hud_icon.Width()) { + TransferBitmap(ship->Design()->hud_icon, icon_ship, icon_ship_shade); + ColorizeBitmap(icon_ship, icon_ship_shade, txt_color); + } + } + } + + if (az_ring) { + az_ring->Rescale(1/compass_scale); + az_ring->Rescale(new_scale); + } + + if (az_pointer) { + az_pointer->Rescale(1/compass_scale); + az_pointer->Rescale(new_scale); + } + + if (el_ring) { + el_ring->Rescale(1/compass_scale); + el_ring->Rescale(new_scale); + } + + if (el_pointer) { + el_pointer->Rescale(1/compass_scale); + el_pointer->Rescale(new_scale); + } + + compass_scale = new_scale; + inst_page = 0; + + if (ship && ship->GetElement() && ship->GetElement()->NumInstructions() > 0) + if (!show_inst) + CycleHUDInst(); + } + + else if (ship && ship->Design()->hud_icon.Width()) { + bool update = false; + System::STATUS s = System::NOMINAL; + int integrity = (int) (ship->Integrity() / ship->Design()->integrity * 100); + + if (integrity < 30) s = System::CRITICAL; + else if (integrity < 60) s = System::DEGRADED; + + if (s != ship_status) { + ship_status = s; + update = true; + } + + if (update) { + SetStatusColor((System::STATUS) ship_status); + ColorizeBitmap(icon_ship, icon_ship_shade, status_color); + } + } + + if (ship && ship->Cockpit()) { + Solid* cockpit = (Solid*) ship->Cockpit(); + + bool change = false; + + if (cockpit->Hidden()) { + if (cockpit_hud_texture) + change = true; + + cockpit_hud_texture = 0; + } + else { + if (!cockpit_hud_texture) + change = true; + + Model* cockpit_model = cockpit->GetModel(); + Material* hud_material = 0; + + if (cockpit_model) { + hud_material = (Material*) cockpit_model->FindMaterial("HUD"); + if (hud_material) { + cockpit_hud_texture = hud_material->tex_emissive; + } + } + } + + if (change) { + SetHUDColorSet(color); + } + } +} + +void +HUDView::SetTarget(SimObject* t) +{ + bool update = false; + + if (target != t) { + tgt_status = -1; + target = t; + if (target) Observe(target); + update = true; + } + + if (target && target->Type() == SimObject::SIM_SHIP) { + System::STATUS s = System::NOMINAL; + Ship* tship = (Ship*) target; + int integrity = (int) (tship->Integrity() / tship->Design()->integrity * 100); + + if (integrity < 30) s = System::CRITICAL; + else if (integrity < 60) s = System::DEGRADED; + + if (s != tgt_status) { + tgt_status = s; + update = true; + } + } + + if (update) { + if (target && target->Type() == SimObject::SIM_SHIP) { + Ship* tship = (Ship*) target; + TransferBitmap(tship->Design()->hud_icon, icon_target, icon_target_shade); + } + else { + PrepareBitmap("hud_icon.pcx", icon_target, icon_target_shade); + } + + SetStatusColor((System::STATUS) tgt_status); + ColorizeBitmap(icon_target, icon_target_shade, status_color); + } +} + +// +--------------------------------------------------------------------+ + +MFD* +HUDView::GetMFD(int n) const +{ + if (n >= 0 && n < 3) + return mfd[n]; + + return 0; +} + +// +--------------------------------------------------------------------+ + +void +HUDView::Refresh() +{ + sim = Sim::GetSim(); + mouse_in = false; + + if (!sim || !camview || !projector) { + return; + } + + if (Mouse::LButton() == 0) { + mouse_latch = 0; + mouse_index = -1; + } + + int mouse_index_old = mouse_index; + + SetShip(sim->GetPlayerShip()); + + if (mode == HUD_MODE_OFF) { + if (cockpit_hud_texture) { + cockpit_hud_texture->FillRect(0,0,512,256,Color::Black); + } + + sim->ShowGrid(false); + return; + } + + if (cockpit_hud_texture && cockpit_hud_texture->Width() == 512) { + Bitmap* hud_bmp = 0; + + if (hud_sprite[0]) { + hud_bmp = hud_sprite[0]->Frame(); + int bmp_w = hud_bmp->Width(); + int bmp_h = hud_bmp->Height(); + + cockpit_hud_texture->BitBlt( 0, 0, *hud_bmp, 0, 0, bmp_w, bmp_h); + } + + if (hud_sprite[1]) { + hud_bmp = hud_sprite[1]->Frame(); + int bmp_w = hud_bmp->Width(); + int bmp_h = hud_bmp->Height(); + + cockpit_hud_texture->BitBlt(256, 0, *hud_bmp, 0, 0, bmp_w, bmp_h); + } + + if (hud_sprite[6]) { + if (hud_sprite[6]->Hidden()) { + cockpit_hud_texture->FillRect(0,384,128,512,Color::Black); + } + else { + hud_bmp = hud_sprite[6]->Frame(); + int bmp_w = hud_bmp->Width(); + int bmp_h = hud_bmp->Height(); + + cockpit_hud_texture->BitBlt(0,384, *hud_bmp, 0, 0, bmp_w, bmp_h); + } + } + + if (hud_sprite[7]) { + if (hud_sprite[7]->Hidden()) { + cockpit_hud_texture->FillRect(128,384,256,512,Color::Black); + } + else { + hud_bmp = hud_sprite[7]->Frame(); + int bmp_w = hud_bmp->Width(); + int bmp_h = hud_bmp->Height(); + + cockpit_hud_texture->BitBlt(128,384, *hud_bmp, 0, 0, bmp_w, bmp_h); + } + } + + for (int i = 8; i < 32; i++) { + if (hud_sprite[i] && !hud_sprite[i]->Hidden()) { + Sprite* s = hud_sprite[i]; + + int cx = (int) s->Location().x; + int cy = (int) s->Location().y; + int w2 = s->Width() / 2; + int h2 = s->Height() / 2; + + window->DrawBitmap(cx-w2, cy-h2, cx+w2, cy+h2, s->Frame(), Video::BLEND_ALPHA); + } + } + } + else { + for (int i = 0; i < 32; i++) { + if (hud_sprite[i] && !hud_sprite[i]->Hidden()) { + Sprite* s = hud_sprite[i]; + + int cx = (int) s->Location().x; + int cy = (int) s->Location().y; + int w2 = s->Width() / 2; + int h2 = s->Height() / 2; + + window->DrawBitmap(cx-w2, cy-h2, cx+w2, cy+h2, s->Frame(), Video::BLEND_ALPHA); + } + } + + Video* video = Video::GetInstance(); + + for (i = 0; i < 31; i++) { + Sprite* s = pitch_ladder[i]; + + if (s && !s->Hidden()) { + s->Render2D(video); + } + } + } + + //DrawStarSystem(); + DrawMessages(); + + if (ship) { + // no hud in transition: + if (ship->InTransition()) { + transition = true; + HideAll(); + return; + } + + else if (transition) { + transition = false; + RestoreHUD(); + } + + CameraDirector* cam_dir = CameraDirector::GetInstance(); + + // everything is off during docking, except the final message: + if (cam_dir && cam_dir->GetMode() == CameraDirector::MODE_DOCKING) { + docking = true; + HideAll(); + + if (ship->GetFlightPhase() == Ship::DOCKING) { + Rect dock_rect(width/2-100, height/6, 200, 20); + + if (ship->IsAirborne()) + DrawHUDText(TXT_AUTO, Game::GetText("HUDView.SUCCESSFUL-LANDING"), dock_rect, DT_CENTER); + else + DrawHUDText(TXT_AUTO, Game::GetText("HUDView.DOCKING-COMPLETE"), dock_rect, DT_CENTER); + } + return; + } + else if (docking) { + docking = false; + RestoreHUD(); + } + + // go to NAV mode during autopilot: + if (ship->GetNavSystem() && ship->GetNavSystem()->AutoNavEngaged() && !arcade) + mode = HUD_MODE_NAV; + + SetTarget(ship->GetTarget()); + + // internal view of HUD reticule + if (CameraDirector::GetCameraMode() <= CameraDirector::MODE_CHASE) + SetTacticalMode(0); + + // external view + else + SetTacticalMode(!cockpit_hud_texture); + + sim->ShowGrid(tactical && + !ship->IsAirborne() && + CameraDirector::GetCameraMode() != CameraDirector::MODE_VIRTUAL); + + // draw HUD bars: + DrawBars(); + + if (missile_lock_sound) { + if (threat > 1) { + long max_vol = AudioConfig::WrnVolume(); + long volume = -1500; + + if (volume > max_vol) + volume = max_vol; + + missile_lock_sound->SetVolume(volume); + missile_lock_sound->Play(); + } + else { + missile_lock_sound->Stop(); + } + } + + DrawNav(); + DrawILS(); + + // FOR DEBUG PURPOSES ONLY: + // DrawObjective(); + + if (!overlay) { + Rect fov_rect(0, 10, width, 10); + int fov_degrees = 180 - 2 * (int)(projector->XAngle()*180/PI); + + if (fov_degrees > 90) + DrawHUDText(TXT_CAM_ANGLE, Game::GetText("HUDView.Wide-Angle"), fov_rect, DT_CENTER); + + fov_rect.y = 20; + DrawHUDText(TXT_CAM_MODE, CameraDirector::GetModeName(), fov_rect, DT_CENTER); + } + + DrawMFDs(); + + instr_left_sprite->Hide(); + instr_right_sprite->Hide(); + warn_left_sprite->Hide(); + warn_right_sprite->Hide(); + + if (cockpit_hud_texture) + cockpit_hud_texture->FillRect(256,384,512,512,Color::Black); + + if (show_inst) { + DrawInstructions(); + } + + else if (!arcade) { + if (ship->MasterCaution() && !show_warn) + ShowHUDWarn(); + + if (show_warn) + DrawWarningPanel(); + } + + if (width > 640 || (!show_inst && !show_warn)) { + Rect icon_rect(120, height-24, 128, 16); + + if (ship) + DrawHUDText(TXT_ICON_SHIP_TYPE, ship->DesignName(), icon_rect, DT_CENTER); + + icon_rect.x = width - 248; + + if (target && target->Type() == SimObject::SIM_SHIP) { + Ship* tship = (Ship*) target; + DrawHUDText(TXT_ICON_TARGET_TYPE, tship->DesignName(), icon_rect, DT_CENTER); + } + } + } + else { + if (target) { + SetTarget(0); + } + } + + // latch mouse down to prevent dragging into a control: + if (Mouse::LButton() == 1) + mouse_latch = 1; + + if (mouse_index > -1 && mouse_index_old != mouse_index) + MouseFrame(); +} + +void +HUDView::DrawMFDs() +{ + for (int i = 0; i < 3; i++) { + mfd[i]->Show(); + mfd[i]->SetShip(ship); + mfd[i]->SetCockpitHUDTexture(cockpit_hud_texture); + mfd[i]->Draw(); + } +} + +// +--------------------------------------------------------------------+ + +void +HUDView::DrawStarSystem() +{ + if (sim && sim->GetStarSystem()) { + StarSystem* sys = sim->GetStarSystem(); + + ListIter iter = sys->Bodies(); + while (++iter) { + OrbitalBody* body = iter.value(); + DrawOrbitalBody(body); + } + } +} + +void +HUDView::DrawOrbitalBody(OrbitalBody* body) +{ + if (body) { + Point p = body->Rep()->Location(); + + projector->Transform(p); + + if (p.z > 100) { + float r = (float) body->Radius(); + r = projector->ProjectRadius(p, r); + projector->Project(p, false); + + window->DrawEllipse((int) (p.x-r), + (int) (p.y-r), + (int) (p.x+r), + (int) (p.y+r), + Color::Cyan); + } + + ListIter iter = body->Satellites(); + while (++iter) { + OrbitalBody* body = iter.value(); + DrawOrbitalBody(body); + } + } +} + +// +--------------------------------------------------------------------+ + +void +HUDView::ExecFrame() +{ + // update the position of HUD elements that are + // part of the 3D scene (like fpm and lcos sprites) + HideCompass(); + + if (ship && !transition && !docking && mode != HUD_MODE_OFF) { + Player* p = Player::GetCurrentPlayer(); + gunsight = p->Gunsight(); + + if (ship->IsStarship()) { + if (tactical) { + hud_left_sprite->Hide(); + hud_right_sprite->Hide(); + } + + else if (hud_left_sprite->Frame() != &hud_left_starship) { + hud_left_sprite->SetAnimation(&hud_left_starship); + hud_right_sprite->SetAnimation(&hud_right_starship); + + hud_left_sprite->MoveTo( Point(width/2-128, height/2, 1)); + hud_right_sprite->MoveTo(Point(width/2+128, height/2, 1)); + } + } + + else if (!ship->IsStarship()) { + if (ship->IsAirborne() && hud_left_sprite->Frame() != &hud_left_air) { + hud_left_sprite->SetAnimation(&hud_left_air); + hud_right_sprite->SetAnimation(&hud_right_air); + } + + else if (!ship->IsAirborne() && hud_left_sprite->Frame() != &hud_left_fighter) { + hud_left_sprite->SetAnimation(&hud_left_fighter); + hud_right_sprite->SetAnimation(&hud_right_fighter); + } + } + + if (!tactical) { + if (Game::MaxTexSize() > 128) { + hud_left_sprite->Show(); + hud_right_sprite->Show(); + } + + if (!arcade) + DrawFPM(); + + if (ship->IsStarship() && ship->GetFLCSMode() == Ship::FLCS_HELM) + DrawHPM(); + else if (!arcade) + DrawPitchLadder(); + } + + else { + if (ship->IsStarship() && ship->GetFLCSMode() == Ship::FLCS_HELM) + DrawCompass(); + } + + if (mode == HUD_MODE_TAC) { + DrawSight(); + DrawDesignators(); + } + + if (width > 640 || (!show_inst && !show_warn)) { + icon_ship_sprite->Show(); + icon_target_sprite->Show(); + } + else { + icon_ship_sprite->Hide(); + icon_target_sprite->Hide(); + } + } + + // if the hud is off or prohibited, + // hide all of the sprites: + + else { + hud_left_sprite->Hide(); + hud_right_sprite->Hide(); + instr_left_sprite->Hide(); + instr_right_sprite->Hide(); + warn_left_sprite->Hide(); + warn_right_sprite->Hide(); + icon_ship_sprite->Hide(); + icon_target_sprite->Hide(); + fpm_sprite->Hide(); + hpm_sprite->Hide(); + lead_sprite->Hide(); + aim_sprite->Hide(); + tgt1_sprite->Hide(); + tgt2_sprite->Hide(); + tgt3_sprite->Hide(); + tgt4_sprite->Hide(); + chase_sprite->Hide(); + + for (int i = 0; i < 3; i++) + mfd[i]->Hide(); + + for (i = 0; i < 31; i++) + pitch_ladder[i]->Hide(); + + DrawILS(); + } +} + +// +--------------------------------------------------------------------+ + +void +HUDView::CycleMFDMode(int mfd_index) +{ + if (mfd_index < 0 || mfd_index > 2) return; + + int m = mfd[mfd_index]->GetMode(); + m++; + + if (mfd_index == 2) { + if (m > MFD::MFD_MODE_SHIP) + m = MFD::MFD_MODE_OFF; + } + else { + if (m > MFD::MFD_MODE_3D) + m = MFD::MFD_MODE_OFF; + + if (m == MFD::MFD_MODE_GAME) + m++; + + if (mfd_index != 0 && m == MFD::MFD_MODE_SHIP) + m++; + } + + mfd[mfd_index]->SetMode(m); + HUDSounds::PlaySound(HUDSounds::SND_MFD_MODE); +} + +// +--------------------------------------------------------------------+ + +void +HUDView::ShowHUDWarn() +{ + if (!show_warn) { + show_warn = true; + + if (ship && ship->HullStrength() <= 40) { + // TOO OBNOXIOUS!! + HUDSounds::PlaySound(HUDSounds::SND_RED_ALERT); + } + } +} + +void +HUDView::ShowHUDInst() +{ + show_inst = true; +} + +// +--------------------------------------------------------------------+ + +void +HUDView::HideHUDWarn() +{ + show_warn = false; + + if (ship) { + ship->ClearCaution(); + HUDSounds::StopSound(HUDSounds::SND_RED_ALERT); + } +} + +void +HUDView::HideHUDInst() +{ + show_inst = false; +} + +// +--------------------------------------------------------------------+ + +void +HUDView::CycleHUDWarn() +{ + HUDSounds::PlaySound(HUDSounds::SND_HUD_WIDGET); + show_warn = !show_warn; + + if (ship && !show_warn) { + ship->ClearCaution(); + HUDSounds::StopSound(HUDSounds::SND_RED_ALERT); + } +} + +void +HUDView::CycleHUDInst() +{ + show_inst = !show_inst; + HUDSounds::PlaySound(HUDSounds::SND_HUD_WIDGET); +} + +// +--------------------------------------------------------------------+ + +void +HUDView::SetHUDMode(int m) +{ + if (mode != m) { + mode = m; + + if (mode > HUD_MODE_ILS || mode < HUD_MODE_OFF) + mode = HUD_MODE_OFF; + + if (ship && !ship->IsDropship() && mode == HUD_MODE_ILS) + mode = HUD_MODE_OFF; + + RestoreHUD(); + } +} + +void +HUDView::CycleHUDMode() +{ + mode++; + + if (arcade && mode != HUD_MODE_TAC) + mode = HUD_MODE_OFF; + + else if (mode > HUD_MODE_ILS || mode < HUD_MODE_OFF) + mode = HUD_MODE_OFF; + + else if (!ship->IsDropship() && mode == HUD_MODE_ILS) + mode = HUD_MODE_OFF; + + RestoreHUD(); + HUDSounds::PlaySound(HUDSounds::SND_HUD_MODE); +} + +void +HUDView::RestoreHUD() +{ + if (mode == HUD_MODE_OFF) { + HideAll(); + } + else { + for (int i = 0; i < 3; i++) + mfd[i]->Show(); + + if (width > 640 || (!show_inst && !show_warn)) { + icon_ship_sprite->Show(); + icon_target_sprite->Show(); + } + else { + icon_ship_sprite->Hide(); + icon_target_sprite->Hide(); + } + + if (!tactical && Game::MaxTexSize() > 128) { + hud_left_sprite->Show(); + hud_right_sprite->Show(); + } + + fpm_sprite->Show(); + + if (ship && ship->IsStarship()) + hpm_sprite->Show(); + + if (gunsight == 0) + aim_sprite->Show(); + else + lead_sprite->Show(); + } +} + +void +HUDView::HideAll() +{ + for (int i = 0; i < 3; i++) + mfd[i]->Hide(); + + hud_left_sprite->Hide(); + hud_right_sprite->Hide(); + instr_left_sprite->Hide(); + instr_right_sprite->Hide(); + warn_left_sprite->Hide(); + warn_right_sprite->Hide(); + icon_ship_sprite->Hide(); + icon_target_sprite->Hide(); + fpm_sprite->Hide(); + hpm_sprite->Hide(); + lead_sprite->Hide(); + aim_sprite->Hide(); + tgt1_sprite->Hide(); + tgt2_sprite->Hide(); + tgt3_sprite->Hide(); + tgt4_sprite->Hide(); + chase_sprite->Hide(); + + sim->ShowGrid(false); + + for (i = 0; i < 31; i++) + pitch_ladder[i]->Hide(); + + if (missile_lock_sound) + missile_lock_sound->Stop(); + + HideCompass(); + DrawILS(); + Mouse::Show(false); +} + +// +--------------------------------------------------------------------+ + +Color +HUDView::Ambient() const +{ + if (!sim || !ship || mode == HUD_MODE_OFF) + return Color::Black; + + SimRegion* rgn = sim->GetActiveRegion(); + + if (!rgn || !rgn->IsAirSpace()) + return Color::Black; + + Color c = sim->GetStarSystem()->Ambient(); + + if (c.Red() > 32 || c.Green() > 32 || c.Blue() > 32) + return Color::Black; + + // if we get this far, the night-vision aid is on + return night_vision_colors[color]; +} + +Color +HUDView::CycleHUDColor() +{ + HUDSounds::PlaySound(HUDSounds::SND_HUD_MODE); + SetHUDColorSet(color+1); + return hud_color; +} + +void +HUDView::SetHUDColorSet(int c) +{ + color = c; + if (color > NUM_HUD_COLORS-1) color = 0; + hud_color = standard_hud_colors[color]; + txt_color = standard_txt_colors[color]; + + ColorizeBitmap(fpm, fpm_shade, hud_color, true); + ColorizeBitmap(hpm, hpm_shade, hud_color, true); + ColorizeBitmap(lead, lead_shade, txt_color * 1.25, true); + ColorizeBitmap(cross, cross_shade, hud_color, true); + ColorizeBitmap(cross1, cross1_shade, hud_color, true); + ColorizeBitmap(cross2, cross2_shade, hud_color, true); + ColorizeBitmap(cross3, cross3_shade, hud_color, true); + ColorizeBitmap(cross4, cross4_shade, hud_color, true); + + if (Game::MaxTexSize() > 128) { + ColorizeBitmap(hud_left_air, hud_left_shade_air, hud_color); + ColorizeBitmap(hud_right_air, hud_right_shade_air, hud_color); + ColorizeBitmap(hud_left_fighter, hud_left_shade_fighter, hud_color); + ColorizeBitmap(hud_right_fighter, hud_right_shade_fighter, hud_color); + ColorizeBitmap(hud_left_starship, hud_left_shade_starship, hud_color); + ColorizeBitmap(hud_right_starship, hud_right_shade_starship, hud_color); + + ColorizeBitmap(instr_left, instr_left_shade, hud_color); + ColorizeBitmap(instr_right, instr_right_shade, hud_color); + ColorizeBitmap(warn_left, warn_left_shade, hud_color); + ColorizeBitmap(warn_right, warn_right_shade, hud_color); + + ColorizeBitmap(pitch_ladder_pos, pitch_ladder_pos_shade, hud_color); + ColorizeBitmap(pitch_ladder_neg, pitch_ladder_neg_shade, hud_color); + } + + ColorizeBitmap(icon_ship, icon_ship_shade, txt_color); + ColorizeBitmap(icon_target, icon_target_shade, txt_color); + + ColorizeBitmap(chase_left, chase_left_shade, hud_color, true); + ColorizeBitmap(chase_right, chase_right_shade, hud_color, true); + ColorizeBitmap(chase_top, chase_top_shade, hud_color, true); + ColorizeBitmap(chase_bottom, chase_bottom_shade, hud_color, true); + + MFD::SetColor(hud_color); + Hoop::SetColor(hud_color); + + for (int i = 0; i < 3; i++) + mfd[i]->SetText3DColor(txt_color); + + Font* font = FontMgr::Find("HUD"); + if (font) + font->SetColor(txt_color); + + for (i = 0; i < TXT_LAST; i++) + hud_text[i].color = txt_color; +} + +// +--------------------------------------------------------------------+ + +void +HUDView::Message(const char* fmt, ...) +{ + if (fmt) { + char msg[512]; + vsprintf(msg, fmt, (char *)(&fmt+1)); + + char* newline = strchr(msg, '\n'); + if (newline) + *newline = 0; + + Print("%s\n", msg); + + if (hud_view) { + int index = -1; + + for (int i = 0; i < MAX_MSG; i++) { + if (hud_view->msg_time[i] <= 0) { + index = i; + break; + } + } + + // no space; advance pipeline: + if (index < 0) { + for (i = 0; i < MAX_MSG-1; i++) { + hud_view->msg_text[i] = hud_view->msg_text[i+1]; + hud_view->msg_time[i] = hud_view->msg_time[i+1]; + } + + index = MAX_MSG-1; + } + + hud_view->msg_text[index] = msg; + hud_view->msg_time[index] = 10; + } + } +} + +// +--------------------------------------------------------------------+ + +void +HUDView::ClearMessages() +{ + if (hud_view) { + for (int i = 0; i < MAX_MSG-1; i++) { + hud_view->msg_text[i] = Text(); + hud_view->msg_time[i] = 0; + } + } +} + +// +--------------------------------------------------------------------+ + +void +HUDView::PrepareBitmap(const char* name, Bitmap& img, BYTE*& shades) +{ + delete [] shades; + shades = 0; + + DataLoader* loader = DataLoader::GetLoader(); + + loader->SetDataPath("HUD/"); + int loaded = loader->LoadBitmap(name, img, Bitmap::BMP_TRANSPARENT); + loader->SetDataPath(0); + + if (!loaded) + return; + + int w = img.Width(); + int h = img.Height(); + + shades = new(__FILE__,__LINE__) BYTE[w*h]; + + for (int y = 0; y < h; y++) + for (int x = 0; x < w; x++) + shades[y*w+x] = (BYTE) (img.GetColor(x,y).Red() * 0.66); +} + +void +HUDView::TransferBitmap(const Bitmap& src, Bitmap& img, BYTE*& shades) +{ + delete [] shades; + shades = 0; + + if (src.Width() != img.Width() || src.Height() != img.Height()) + return; + + img.CopyBitmap(src); + img.SetType(Bitmap::BMP_TRANSLUCENT); + + int w = img.Width(); + int h = img.Height(); + + shades = new(__FILE__,__LINE__) BYTE[w*h]; + + for (int y = 0; y < h; y++) + for (int x = 0; x < w; x++) + shades[y*w+x] = (BYTE) (img.GetColor(x,y).Red() * 0.5); +} + +void +HUDView::ColorizeBitmap(Bitmap& img, BYTE* shades, Color color, bool force_alpha) +{ + if (!shades) return; + + int max_tex_size = Game::MaxTexSize(); + + if (max_tex_size < 128) + Game::SetMaxTexSize(128); + + if (hud_view && hud_view->cockpit_hud_texture && !force_alpha) { + img.FillColor(Color::Black); + Color* dst = img.HiPixels(); + BYTE* src = shades; + + for (int y = 0; y < img.Height(); y++) { + for (int x = 0; x < img.Width(); x++) { + if (*src) + *dst = color.dim(*src/200.0); + else + *dst = Color::Black; + + dst++; + src++; + } + } + img.MakeTexture(); + } + else { + img.FillColor(color); + img.CopyAlphaImage(img.Width(), img.Height(), shades); + img.MakeTexture(); + } + + if (max_tex_size < 128) + Game::SetMaxTexSize(max_tex_size); +} + +// +--------------------------------------------------------------------+ + +void +HUDView::MouseFrame() +{ + MouseController* ctrl = MouseController::GetInstance(); + if (ctrl && ctrl->Active()) + return; + + if (mouse_index >= TXT_CAUTION_TXT && mouse_index <= TXT_LAST_CAUTION) { + if (show_inst) { + if (mouse_index == TXT_INSTR_PAGE) { + if (Mouse::X() > width/2 + 125) + CycleInstructions(1); + else if (Mouse::X() < width/2 + 65) + CycleInstructions(-1); + } + else + show_inst = false; + } + else { + CycleHUDWarn(); + } + return; + } + + Starshatter* stars = Starshatter::GetInstance(); + if (mouse_index == TXT_PAUSED) + stars->Pause(!Game::Paused()); + + if (mouse_index == TXT_GEAR_DOWN) + ship->ToggleGear(); + + if (mouse_index == TXT_HUD_MODE) { + CycleHUDMode(); + + if (mode == HUD_MODE_OFF) + CycleHUDMode(); + } + + if (mouse_index == TXT_PRIMARY_WEP) { + HUDSounds::PlaySound(HUDSounds::SND_WEP_MODE); + ship->CyclePrimary(); + } + + if (mouse_index == TXT_SECONDARY_WEP) { + HUDSounds::PlaySound(HUDSounds::SND_WEP_MODE); + ship->CycleSecondary(); + } + + if (mouse_index == TXT_DECOY) + ship->FireDecoy(); + + if (mouse_index == TXT_SHIELD) { + Shield* shield = ship->GetShield(); + + if (shield) { + double level = shield->GetPowerLevel(); + + const Rect& r = hud_text[TXT_SHIELD].rect; + if (Mouse::X() < r.x + r.w * 0.75) + shield->SetPowerLevel(level - 10); + else + shield->SetPowerLevel(level + 10); + + HUDSounds::PlaySound(HUDSounds::SND_SHIELD_LEVEL); + } + } + + if (mouse_index == TXT_AUTO) + ship->TimeSkip(); + + if (mouse_index >= TXT_NAV_INDEX && mouse_index <= TXT_NAV_ETR) { + ship->SetAutoNav(!ship->IsAutoNavEngaged()); + SetHUDMode(HUD_MODE_TAC); + } +} + +// +--------------------------------------------------------------------+ + +bool +HUDView::IsMouseLatched() +{ + bool result = mouse_in; + + if (!result) { + HUDView* hud = HUDView::GetInstance(); + + for (int i = 0; i < 3; i++) + result = result || hud->mfd[i]->IsMouseLatched(); + } + + return result; +} + +// +--------------------------------------------------------------------+ + +bool +HUDView::IsNameCrowded(int x, int y) +{ + for (int i = 0; i < MAX_CONTACT; i++) { + HUDText& test = hud_text[TXT_CONTACT_NAME + i]; + + if (!test.hidden) { + Rect r = test.rect; + + int dx = r.x - x; + int dy = r.y - y; + int d = dx*dx + dy*dy; + + if (d <= 400) + return true; + } + + test = hud_text[TXT_CONTACT_INFO + i]; + + if (!test.hidden) { + Rect r = test.rect; + + int dx = r.x - x; + int dy = r.y - y; + int d = dx*dx + dy*dy; + + if (d <= 400) + return true; + } + } + + return false; +} + +void +HUDView::DrawDiamond(int x, int y, int r, Color c) +{ + POINT diamond[4]; + + diamond[0].x = x; + diamond[0].y = y-r; + + diamond[1].x = x+r; + diamond[1].y = y; + + diamond[2].x = x; + diamond[2].y = y+r; + + diamond[3].x = x-r; + diamond[3].y = y; + + window->DrawPoly(4, diamond, c); +} \ No newline at end of file diff --git a/Stars45/HUDView.h b/Stars45/HUDView.h new file mode 100644 index 0000000..ef973bf --- /dev/null +++ b/Stars45/HUDView.h @@ -0,0 +1,218 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: HUDView.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + View class for Heads Up Display +*/ + +#ifndef HUDView_h +#define HUDView_h + +#include "Types.h" +#include "View.h" +#include "Bitmap.h" +#include "Font.h" +#include "System.h" +#include "SimObject.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class Graphic; +class Sprite; +class Solid; +class Ship; +class Contact; +class Physical; +class OrbitalBody; +class OrbitalRegion; +class Instruction; +class CameraView; +class Projector; +class MFD; + +// +--------------------------------------------------------------------+ + +class HUDView : public View, + public SimObserver +{ +public: + HUDView(Window* c); + virtual ~HUDView(); + + enum HUDModes { HUD_MODE_OFF, HUD_MODE_TAC, HUD_MODE_NAV, HUD_MODE_ILS }; + + // Operations: + virtual void Refresh(); + virtual void OnWindowMove(); + virtual void ExecFrame(); + virtual void UseCameraView(CameraView* v); + + virtual Ship* GetShip() const { return ship; } + virtual SimObject* GetTarget() const { return target; } + virtual void SetShip(Ship* s); + virtual void SetTarget(SimObject* t); + virtual MFD* GetMFD(int n) const; + + virtual void HideAll(); + virtual void DrawBars(); + virtual void DrawNav(); + virtual void DrawILS(); + virtual void DrawObjective(); + virtual void DrawNavInfo(); + virtual void DrawNavPoint(Instruction& navpt, int index, int next); + virtual void DrawContactMarkers(); + virtual void DrawContact(Contact* c, int index); + virtual void DrawTrack(Contact* c); + virtual void DrawTrackSegment(Point& t1, Point& t2, Color c); + virtual void DrawRect(SimObject* targ); + virtual void DrawTarget(); + virtual void DrawSight(); + virtual void DrawLCOS(SimObject* targ, double dist); + virtual void DrawDesignators(); + virtual void DrawFPM(); + virtual void DrawHPM(); + virtual void DrawCompass(); + virtual void HideCompass(); + virtual void DrawPitchLadder(); + virtual void DrawStarSystem(); + + virtual void DrawMFDs(); + virtual void DrawWarningPanel(); + virtual void DrawInstructions(); + virtual void DrawMessages(); + + virtual void MouseFrame(); + + virtual int GetHUDMode() const { return mode; } + virtual int GetTacticalMode() const { return tactical; } + virtual void SetTacticalMode(int mode=1); + virtual int GetOverlayMode() const { return overlay; } + virtual void SetOverlayMode(int mode=1); + + virtual void SetHUDMode(int mode); + virtual void CycleHUDMode(); + virtual Color CycleHUDColor(); + virtual void SetHUDColorSet(int c); + virtual int GetHUDColorSet() const { return color; } + virtual Color GetHUDColor() const { return hud_color; } + virtual Color GetTextColor() const { return txt_color; } + virtual Color Ambient() const; + virtual void ShowHUDWarn(); + virtual void ShowHUDInst(); + virtual void HideHUDWarn(); + virtual void HideHUDInst(); + virtual void CycleHUDWarn(); + virtual void CycleHUDInst(); + virtual void CycleMFDMode(int mfd); + virtual void CycleInstructions(int direction); + virtual void RestoreHUD(); + + virtual void TargetOff() { target = 0; } + static Color MarkerColor(Contact* targ); + + static bool IsNameCrowded(int x, int y); + static bool IsMouseLatched(); + static HUDView* GetInstance() { return hud_view; } + static void Message(const char* fmt, ...); + static void ClearMessages(); + static void PrepareBitmap(const char* name, Bitmap& img, BYTE*& shades); + static void TransferBitmap(const Bitmap& src, Bitmap& img, BYTE*& shades); + static void ColorizeBitmap(Bitmap& img, BYTE* shades, Color color, bool force_alpha=false); + + static int GetGunsight() { return gunsight; } + static void SetGunsight(int s) { gunsight = s; } + static bool IsArcade() { return arcade; } + static void SetArcade(bool a) { arcade = a; } + static int DefaultColorSet() { return def_color_set; } + static void SetDefaultColorSet(int c) { def_color_set = c; } + static Color GetStatusColor(System::STATUS status); + static bool ShowFPS() { return show_fps; } + static void ShowFPS(bool f) { show_fps = f; } + + virtual bool Update(SimObject* obj); + virtual const char* GetObserverName() const; + +protected: + const char* FormatInstruction(Text instr); + void SetStatusColor(System::STATUS status); + + enum HUD_CASE { HUD_MIXED_CASE, HUD_UPPER_CASE }; + + void DrawDiamond(int x, int y, int r, Color c); + void DrawHUDText(int index, const char* txt, Rect& rect, int align, int upcase=HUD_UPPER_CASE, bool box=false); + void HideHUDText(int index); + + void DrawOrbitalBody(OrbitalBody* body); + + Projector* projector; + CameraView* camview; + + int width, height, aw, ah; + double xcenter, ycenter; + + Sim* sim; + Ship* ship; + SimObject* target; + + SimRegion* active_region; + + Bitmap* cockpit_hud_texture; + + Color hud_color; + Color txt_color; + Color status_color; + + bool show_warn; + bool show_inst; + int inst_page; + int threat; + + int mode; + int color; + int tactical; + int overlay; + int transition; + int docking; + + MFD* mfd[3]; + + Sprite* pitch_ladder[31]; + Sprite* hud_sprite[32]; + + Solid* az_ring; + Solid* az_pointer; + Solid* el_ring; + Solid* el_pointer; + double compass_scale; + + enum { MAX_MSG = 6 }; + Text msg_text[MAX_MSG]; + double msg_time[MAX_MSG]; + + static HUDView* hud_view; + static bool arcade; + static bool show_fps; + static int gunsight; + static int def_color_set; +}; + + +// +--------------------------------------------------------------------+ + +struct HUDText { + Font* font; + Color color; + Rect rect; + bool hidden; +}; + +#endif HUDView_h + diff --git a/Stars45/Hangar.cpp b/Stars45/Hangar.cpp new file mode 100644 index 0000000..f1e8019 --- /dev/null +++ b/Stars45/Hangar.cpp @@ -0,0 +1,933 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Hangar.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Everything needed to store and maintain space craft + + See Also: FlightDeck +*/ + +#include "MemDebug.h" +#include "Hangar.h" +#include "FlightDeck.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "Sim.h" +#include "Instruction.h" +#include "Element.h" +#include "Mission.h" +#include "RadioMessage.h" +#include "Campaign.h" +#include "Combatant.h" +#include "CombatGroup.h" + +#include "Game.h" +#include "Random.h" + +// +======================================================================+ + +class HangarSlot +{ + friend class Hangar; + +public: + static const char* TYPENAME() { return "HangarSlot"; } + + HangarSlot(); + ~HangarSlot(); + int operator == (const HangarSlot& that) const { return this == &that; } + +private: + Text squadron; + CombatGroup* group; + Ship* ship; + int iff; + const ShipDesign* design; + FlightDeck* deck; + int slot; + int state; + double time; + Element* package; + bool alert_hold; + int loadout[16]; +}; + +// +--------------------------------------------------------------------+ + +HangarSlot::HangarSlot() + : ship(0), group(0), design(0), deck(0), slot(0), + state(0), time(0), package(0), alert_hold(false) +{ + for (int i = 0; i < 16; i++) + loadout[i] = -1; +} + +HangarSlot::~HangarSlot() +{ +} + +// +======================================================================+ + +Hangar::Hangar() + : ship(0), nsquadrons(0), last_patrol_launch(0) +{ + ZeroMemory(nslots, sizeof(nslots)); + ZeroMemory(squadrons, sizeof(squadrons)); +} + +// +----------------------------------------------------------------------+ + +Hangar::Hangar(const Hangar& s) + : ship(0), nsquadrons(s.nsquadrons), last_patrol_launch(s.last_patrol_launch) +{ + ZeroMemory(nslots, sizeof(nslots)); + ZeroMemory(squadrons, sizeof(squadrons)); +} + +// +--------------------------------------------------------------------+ + +Hangar::~Hangar() +{ + for (int i = 0; i < MAX_SQUADRONS; i++) + delete [] squadrons[i]; +} + +// +--------------------------------------------------------------------+ + +void +Hangar::ExecFrame(double seconds) +{ + for (int n = 0; n < nsquadrons; n++) { + if (squadrons[n] && nslots[n] > 0) { + for (int i = 0; i < nslots[n]; i++) { + HangarSlot* slot = &squadrons[n][i]; + + switch (slot->state) { + case UNAVAIL: + case STORAGE: + case APPROACH: + break; + + case ACTIVE: + if (slot->ship && slot->ship->GetFlightPhase() == Ship::APPROACH) + slot->state = APPROACH; + break; + + case MAINT: + if (slot->time > 0) { + slot->time -= seconds; + } + else { + slot->time = 0; + slot->state = STORAGE; + } + break; + + + case PREP: + if (slot->time > 0) { + slot->time -= seconds; + } + else { + slot->time = 0; + FinishPrep(slot); + } + break; + + case ALERT: + if (slot->time > 0) { + slot->time -= seconds; + } + else if (slot->deck) { + slot->time = 0; + + // if package has specific objective, launch as soon as possible: + if (!slot->alert_hold) + slot->deck->Launch(slot->slot); + + switch (slot->deck->State(slot->slot)) { + case FlightDeck::READY: slot->state = ALERT; break; + case FlightDeck::QUEUED: slot->state = QUEUED; break; + case FlightDeck::LOCKED: slot->state = LOCKED; break; + case FlightDeck::LAUNCH: slot->state = LAUNCH; break; + default: slot->state = STORAGE; break; + } + } + break; + + case QUEUED: + case LOCKED: + if (slot->deck) { + switch (slot->deck->State(slot->slot)) { + case FlightDeck::READY: slot->state = ALERT; break; + case FlightDeck::QUEUED: slot->state = QUEUED; break; + case FlightDeck::LOCKED: slot->state = LOCKED; break; + case FlightDeck::LAUNCH: slot->state = LAUNCH; break; + default: slot->state = STORAGE; break; + } + + slot->time = slot->deck->TimeRemaining(slot->slot); + } + break; + + case LAUNCH: + if (slot->deck) { + slot->time = slot->deck->TimeRemaining(slot->slot); + + if (slot->ship && slot->ship->GetFlightPhase() > Ship::LAUNCH) { + slot->state = ACTIVE; + slot->time = 0; + } + } + break; + + case RECOVERY: + break; + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +bool +Hangar::FinishPrep(HangarSlot* slot) +{ + if (slot->deck->SpaceLeft(slot->design->type)) { + Sim* sim = Sim::GetSim(); + + Text ship_name = slot->squadron; + + slot->ship = sim->CreateShip(ship_name, "", + (ShipDesign*) slot->design, + ship->GetRegion()->Name(), + Point(0, 0, 0), + slot->iff, + ship->GetCommandAILevel(), + slot->loadout); + + Observe(slot->ship); + + if (slot->package) { + slot->package->SetCommander(ship->GetElement()); + slot->package->AddShip(slot->ship); + + if (slot->group) { + slot->package->SetCombatGroup(slot->group); + slot->package->SetCombatUnit(slot->group->GetNextUnit()); + } + + char name[64]; + sprintf(name, "%s %d", + (const char*) slot->package->Name(), + slot->ship->GetElementIndex()); + slot->ship->SetName(name); + } + + slot->slot = -1; // take first available slot + if (slot->deck->Spot(slot->ship, slot->slot)) { + slot->state = ALERT; + return true; + } + + Print("WARNING: Could not spot alert ship - carrier: '%s' ship '%s'\n", + ship->Name(), slot->ship->Name()); + } + + return false; +} + +// +--------------------------------------------------------------------+ + +bool +Hangar::Update(SimObject* obj) +{ + bool found = false; + + for (int n = 0; !found && n < nsquadrons; n++) { + if (squadrons[n] && nslots[n] > 0) { + for (int i = 0; !found && i < nslots[n]; i++) { + HangarSlot* slot = &squadrons[n][i]; + + if (slot->ship == obj) { + // was ship destroyed in combat, + // or did it just dock here? + if (slot->state != MAINT) { + slot->state = UNAVAIL; + slot->ship = 0; + slot->deck = 0; + slot->time = 0; + slot->package = 0; + } + + found = true; + } + } + } + } + + return SimObserver::Update(obj); +} + +const char* +Hangar::GetObserverName() const +{ + static char name[64]; + if (ship) + sprintf(name, "Hangar(%s)", ship->Name()); + else + sprintf(name, "Hangar"); + return name; +} + +// +--------------------------------------------------------------------+ + +bool +Hangar::CreateSquadron(Text squadron, CombatGroup* group, + const ShipDesign* design, int count, int iff, + int* def_load, int maint_count, int dead_count) +{ + if (nsquadrons < MAX_SQUADRONS && count > 0) { + HangarSlot* s = new(__FILE__,__LINE__) HangarSlot[count]; + + for (int i = 0; i < count; i++) { + s[i].squadron = squadron; + s[i].group = group; + s[i].design = design; + s[i].iff = iff; + + if (def_load) + CopyMemory(s[i].loadout, def_load, sizeof(s[i].loadout)); + } + + squadrons[nsquadrons] = s; + nslots[nsquadrons] = count; + names[nsquadrons] = squadron; + + i = count-1; + while (dead_count-- > 0) + s[i--].state = UNAVAIL; + + while (maint_count-- > 0) { + s[i].state = MAINT; + s[i--].time = 600 + rand() / 15; + } + + nsquadrons++; + return true; + } + + return false; +} + +bool +Hangar::GotoActiveFlight(int squadron, int slot_index, Element* elem, int* loadout) +{ + if (elem && squadron < nsquadrons && slot_index < nslots[squadron]) { + HangarSlot* slot = &(squadrons[squadron][slot_index]); + + if (slot->state == STORAGE) { + slot->deck = 0; + slot->state = ACTIVE; + slot->time = 0; + slot->package = elem; + slot->alert_hold = false; + + if (loadout) + CopyMemory(slot->loadout, loadout, sizeof(slot->loadout)); + + Sim* sim = Sim::GetSim(); + + Text ship_name = slot->squadron; + + slot->ship = sim->CreateShip(ship_name, "", + (ShipDesign*) slot->design, + ship->GetRegion()->Name(), + ship->Location() + RandomPoint(), + slot->iff, + ship->GetCommandAILevel(), + slot->loadout); + + if (slot->ship) { + Observe(slot->ship); + + elem->SetCommander(ship->GetElement()); + elem->AddShip(slot->ship); + + if (slot->group) { + elem->SetCombatGroup(slot->group); + elem->SetCombatUnit(slot->group->GetNextUnit()); + } + + char name[64]; + sprintf(name, "%s %d", + (const char*) elem->Name(), + slot->ship->GetElementIndex()); + + slot->ship->SetName(name); + } + + return true; + } + } + + return false; +} + +bool +Hangar::GotoAlert(int squadron, int slot, FlightDeck* d, Element* elem, int* loadout, bool pkg, bool expedite) +{ + if (squadron < nsquadrons && slot < nslots[squadron]) { + HangarSlot* s = &(squadrons[squadron][slot]); + + if (s->state == STORAGE) { + s->deck = d; + s->state = PREP; + s->time = expedite ? 3 : s->design->prep_time; + s->package = elem; + s->alert_hold = !pkg; + + if (loadout) + CopyMemory(s->loadout, loadout, sizeof(s->loadout)); + + if (expedite) + FinishPrep(s); + + return true; + } + } + + return false; +} + +bool +Hangar::Launch(int squadron, int slot) +{ + if (squadron < nsquadrons && slot < nslots[squadron]) { + HangarSlot* s = &(squadrons[squadron][slot]); + + if (s->state == ALERT && s->deck) + return s->deck->Launch(s->slot); + } + + return false; +} + +bool +Hangar::StandDown(int squadron, int slot) +{ + if (squadron < nsquadrons && slot < nslots[squadron]) { + HangarSlot* s = &(squadrons[squadron][slot]); + + Element* package = 0; + bool clear_slot = false; + + if (s->state == ALERT && s->deck) { + if (s->deck->Clear(s->slot)) { + if (s->ship) { + Sim* sim = Sim::GetSim(); + + if (s->package) { + package = s->package; + package->DelShip(s->ship); + } + + sim->DestroyShip(s->ship); + } + + clear_slot = true; + } + } + + else if (s->state == PREP) { + clear_slot = true; + package = s->package; + } + + if (clear_slot) { + s->state = STORAGE; + s->deck = 0; + s->slot = 0; + s->ship = 0; + s->package = 0; + + if (package) { + int npkg = 0; + for (int i = 0; i < nslots[squadron]; i++) { + if (squadrons[squadron][i].package == package) + npkg++; + } + + if (npkg == 0) { + Sim::GetSim()->DestroyElement(package); + } + } + + return true; + } + } + + return false; +} + +// +--------------------------------------------------------------------+ + +bool +Hangar::CanStow(Ship* incoming) +{ + int squadron = -1; + int slot = -1; + + if (FindSlot(incoming, squadron, slot)) + return true; + + return false; +} + +// +--------------------------------------------------------------------+ + +bool +Hangar::Stow(Ship* incoming) +{ + int squadron = -1; + int slot = -1; + + if (FindSlot(incoming, squadron, slot)) { + HangarSlot* s = &(squadrons[squadron][slot]); + s->state = MAINT; + s->design = incoming->Design(); + s->time = 2400; + s->package = 0; // XXX MEMORY LEAK? + + // extra maintenance time? + if (incoming->Integrity() < incoming->Design()->integrity) { + double damage = 100 * ((double) incoming->Design()->integrity - (double) incoming->Integrity()) / (double) incoming->Design()->integrity; + + if (damage < 10) s->time *= 1.2; + else if (damage < 25) s->time *= 2; + else if (damage < 50) s->time *= 4; + else s->time *= 10; + } + + // quicker turnaround during network play: + Sim* sim = Sim::GetSim(); + if (sim && sim->IsNetGame()) + s->time /= 40; + + return true; + } + + return false; +} + +bool +Hangar::FindSlot(Ship* test, int& squadron, int& slot, int desired_state) +{ + if (test) { + // if test is already inbound to this carrier, + // keep the inbound squadron and slot selections: + if (desired_state == UNAVAIL && test->GetInbound()) { + InboundSlot* inbound = test->GetInbound(); + FlightDeck* deck = inbound->GetDeck(); + + if (deck && deck->GetCarrier() == ship && deck->IsPowerOn()) { + squadron = inbound->Squadron(); + slot = inbound->Index(); + return true; + } + } + + int avail_squadron = -1; + int avail_slot = -1; + + for (int i = 0; i < nsquadrons; i++) { + if (squadron < 0 || squadron == i) { + for (int j = 0; j < nslots[i]; j++) { + HangarSlot* s = &(squadrons[i][j]); + if (s->ship == test) { + squadron = i; + slot = j; + return true; + } + + else if (avail_slot < 0 && s->ship == 0) { + if ((desired_state > STORAGE && s->state == STORAGE) || + (desired_state < STORAGE && s->state == UNAVAIL)) { + avail_squadron = i; + avail_slot = j; + } + } + } + } + } + + if (avail_squadron >= 0 && avail_slot >= 0) { + squadron = avail_squadron; + slot = avail_slot; + + if (desired_state > STORAGE) { + HangarSlot* s = &(squadrons[squadron][slot]); + + s->ship = test; + s->design = test->Design(); + s->state = desired_state; + s->deck = 0; + s->slot = 0; + s->package = test->GetElement(); + s->time = 0; + + Observe(s->ship); + } + + return true; + } + } + + return false; +} + +bool +Hangar::FindSquadronAndSlot(Ship* test, int& squadron, int& slot) +{ + if (test) { + for (int i = 0; i < nsquadrons; i++) { + if (squadron < 0 || squadron == i) { + for (int j = 0; j < nslots[i]; j++) { + HangarSlot* s = &(squadrons[i][j]); + if (s->ship == test) { + squadron = i; + slot = j; + return true; + } + } + } + } + } + + return false; +} + + +bool +Hangar::FindAvailSlot(const ShipDesign* design, int& squadron, int& slot) +{ + if (design) { + for (int i = 0; i < nsquadrons; i++) { + if (nslots[i] > 0 && squadrons[i]->design == design) { + for (int j = 0; j < nslots[i]; j++) { + HangarSlot* s = &(squadrons[i][j]); + + if (s->state == STORAGE) { + squadron = i; + slot = j; + return true; + } + } + } + } + } + + return false; +} + +bool +Hangar::Ready(int squadron, int slot, FlightDeck* d) +{ + if (squadron < 0 || squadron >= nsquadrons || slot < 0 || slot >= nslots[squadron] || !d) + return false; + + HangarSlot* s = &(squadrons[squadron][slot]); + + s->time = 3; // 5; + s->deck = d; + s->slot = -1; // take first available slot + + if (d->Spot(s->ship, s->slot)) { + s->state = ALERT; + s->alert_hold = false; + return true; + } + + return false; +} + +// +--------------------------------------------------------------------+ + +Text +Hangar::SquadronName(int n) const +{ + if (n >= 0 && n < nsquadrons) + return names[n]; + + return Game::GetText("Unknown"); +} + +int +Hangar::SquadronSize(int n) const +{ + if (n >= 0 && n < nsquadrons) + return nslots[n]; + + return 0; +} + +int +Hangar::SquadronIFF(int n) const +{ + if (n >= 0 && n < nsquadrons) + return squadrons[n]->iff; + + return 0; +} + +const ShipDesign* +Hangar::SquadronDesign(int n) const +{ + if (n >= 0 && n < nsquadrons && nslots[n]) + return squadrons[n]->design; + + return 0; +} + +const HangarSlot* +Hangar::GetSlot(int i, int j) const +{ + if (i >= 0 && i < nsquadrons) + if (j >= 0 && j < nslots[i]) + return squadrons[i] + j; + + return 0; +} + +// +--------------------------------------------------------------------+ + +Ship* +Hangar::GetShip(const HangarSlot* s) const +{ + if (s) return s->ship; + return 0; +} + +const ShipDesign* +Hangar::GetDesign(const HangarSlot* s) const +{ + if (s) return s->design; + return 0; +} + +FlightDeck* +Hangar::GetFlightDeck(const HangarSlot* s) const +{ + if (s) return s->deck; + return 0; +} + +int +Hangar::GetFlightDeckSlot(const HangarSlot* s) const +{ + if (s) return s->slot; + return 0; +} + +int +Hangar::GetState(const HangarSlot* s) const +{ + if (s) return s->state; + return 0; +} + +double +Hangar::TimeRemaining(const HangarSlot* s) const +{ + if (s) return s->time; + return 0; +} + +Element* +Hangar::GetPackageElement(const HangarSlot* s) const +{ + if (s) return s->package; + return 0; +} + +const int* +Hangar::GetLoadout(const HangarSlot* s) const +{ + if (s) return s->loadout; + return 0; +} + +Text +Hangar::StatusName(const HangarSlot* s) const +{ + switch (s->state) { + default: + case UNAVAIL: return Game::GetText("hangar.UNAVAIL"); + case MAINT: return Game::GetText("hangar.MAINT"); + case STORAGE: return Game::GetText("hangar.STORAGE"); + case PREP: return Game::GetText("hangar.PREP"); + case ALERT: return Game::GetText("hangar.ALERT"); + case QUEUED: { + Text state = Game::GetText("hangar.QUEUED"); + char seq[8]; + sprintf(seq, " %d", s->deck->Sequence(s->slot)); + return state + seq; + } + case LOCKED: return Game::GetText("hangar.LOCKED"); + case LAUNCH: return Game::GetText("hangar.LAUNCH"); + case ACTIVE: return Game::GetText("hangar.ACTIVE"); + case APPROACH: return Game::GetText("hangar.APPROACH"); + case RECOVERY: return Game::GetText("hangar.RECOVERY"); + } +} + +// +--------------------------------------------------------------------+ + +int +Hangar::PreflightQueue(FlightDeck* d) const +{ + int result = 0; + + for (int n = 0; n < nsquadrons; n++) { + if (squadrons[n] && nslots[n] > 0) { + for (int i = 0; i < nslots[n]; i++) { + HangarSlot* slot = &squadrons[n][i]; + + if (slot->deck == d) + result++; + } + } + } + + return result; +} + +// +--------------------------------------------------------------------+ + +int +Hangar::NumShipsReady(int n) const +{ + int result = 0; + + if (n >= 0 && n < nsquadrons && squadrons[n] && nslots[n] > 0) { + for (int i = 0; i < nslots[n]; i++) { + HangarSlot* slot = &squadrons[n][i]; + + if (slot->state == STORAGE) + result++; + } + } + + return result; +} + +int +Hangar::NumShipsMaint(int n) const +{ + int result = 0; + + if (n >= 0 && n < nsquadrons && squadrons[n] && nslots[n] > 0) { + for (int i = 0; i < nslots[n]; i++) { + HangarSlot* slot = &squadrons[n][i]; + + if (slot->state == MAINT) + result++; + } + } + + return result; +} + +int +Hangar::NumShipsDead(int n) const +{ + int result = 0; + + if (n >= 0 && n < nsquadrons && squadrons[n] && nslots[n] > 0) { + for (int i = 0; i < nslots[n]; i++) { + HangarSlot* slot = &squadrons[n][i]; + + if (slot->state == UNAVAIL) + result++; + } + } + + return result; +} + +int +Hangar::NumSlotsEmpty() const +{ + int result = 0; + + for (int n = 0; n < nsquadrons; n++) { + if (squadrons[n] && nslots[n] > 0) { + for (int i = 0; i < nslots[n]; i++) { + HangarSlot* slot = &squadrons[n][i]; + + if (slot->state == UNAVAIL) + result++; + } + } + } + + return result; +} + +int +Hangar::GetActiveElements(List& active_list) +{ + active_list.clear(); + + for (int n = 0; n < nsquadrons; n++) { + if (squadrons[n] && nslots[n] > 0) { + for (int i = 0; i < nslots[n]; i++) { + HangarSlot* slot = &squadrons[n][i]; + + if (slot->package != 0 && !active_list.contains(slot->package)) + active_list.append(slot->package); + } + } + } + + return active_list.size(); +} + +// +--------------------------------------------------------------------+ + +DWORD +Hangar::GetLastPatrolLaunch() const +{ + return last_patrol_launch; +} + +void +Hangar::SetLastPatrolLaunch(DWORD t) +{ + last_patrol_launch = t; +} + +// +--------------------------------------------------------------------+ + +void +Hangar::SetAllIFF(int iff) +{ + for (int n = 0; n < nsquadrons; n++) { + if (squadrons[n] && nslots[n] > 0) { + for (int i = 0; i < nslots[n]; i++) { + HangarSlot* slot = &squadrons[n][i]; + + if (slot->ship) + slot->ship->SetIFF(iff); + } + } + } +} diff --git a/Stars45/Hangar.h b/Stars45/Hangar.h new file mode 100644 index 0000000..aa52829 --- /dev/null +++ b/Stars45/Hangar.h @@ -0,0 +1,127 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Hangar.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Everything needed to store and maintain space craft + + See Also: FlightDeck +*/ + +#ifndef Hangar_h +#define Hangar_h + +#include "Types.h" +#include "Geometry.h" +#include "SimObject.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class Ship; +class ShipDesign; +class FlightDeck; +class FlightDeckSlot; +class Element; +class HangarSlot; +class InboundSlot; +class CombatGroup; + +// +--------------------------------------------------------------------+ + +class Hangar : public SimObserver +{ +public: + Hangar(); + Hangar(const Hangar& rhs); + virtual ~Hangar(); + + enum HANGAR_STATE { UNAVAIL = -2, + MAINT = -1, + STORAGE = 0, + PREP, + ALERT, + QUEUED, + LOCKED, + LAUNCH, + ACTIVE, + APPROACH, + RECOVERY + }; + + enum CONSTANTS { MAX_SQUADRONS=10 }; + + virtual void ExecFrame(double seconds); + void SetShip(Ship* s) { ship = s; } + + virtual bool CreateSquadron(Text squadron, CombatGroup* g, + const ShipDesign* design, int count, int iff=-1, + int* def_load=0, int maint_count=0, int dead_count=0); + + // used only by net client to expedite creation of already active ships: + virtual bool GotoActiveFlight(int squadron, int slot, Element* elem, int* loadout); + + virtual bool GotoAlert(int squadron, int slot, FlightDeck* d, Element* elem=0, int* loadout=0, bool pkg=false, bool expedite=false); + virtual bool Ready(int squadron, int slot, FlightDeck* d); + virtual bool Launch(int squadron, int slot); + virtual bool StandDown(int squadron, int slot); + virtual bool CanStow(Ship* s); + virtual bool Stow(Ship* s); + virtual bool FindSlot(Ship* s, int& squadron, int& slot, int state=UNAVAIL); + virtual bool FindSquadronAndSlot(Ship* s, int& squadron, int& slot); + virtual bool FindAvailSlot(const ShipDesign* s, int& squadron, int& slot); + virtual bool FinishPrep(HangarSlot* slot); + virtual void SetAllIFF(int iff); + + virtual bool Update(SimObject* obj); + virtual const char* GetObserverName() const; + + // accessors: + int NumSquadrons() const { return nsquadrons; } + Text SquadronName(int n) const; + int SquadronSize(int n) const; + int SquadronIFF(int n) const; + const ShipDesign* SquadronDesign(int n) const; + + int NumShipsReady(int squadron) const; + int NumShipsMaint(int squadron) const; + int NumShipsDead(int squadron) const; + int NumSlotsEmpty() const; + + int GetActiveElements(List& active_list); + + const HangarSlot* GetSlot(int squadron, int index) const; + Ship* GetShip(const HangarSlot* s) const; + const ShipDesign* GetDesign(const HangarSlot* s) const; + FlightDeck* GetFlightDeck(const HangarSlot* s) const; + int GetFlightDeckSlot(const HangarSlot* s) const; + int GetState(const HangarSlot* s) const; + double TimeRemaining(const HangarSlot* s) const; + Element* GetPackageElement(const HangarSlot* s) const; + const int* GetLoadout(const HangarSlot* s) const; + Text StatusName(const HangarSlot* s) const; + + int PreflightQueue(FlightDeck* d) const; + DWORD GetLastPatrolLaunch() const; + void SetLastPatrolLaunch(DWORD t); + +protected: + Ship* ship; + int nsquadrons; + int nslots[MAX_SQUADRONS]; + Text names[MAX_SQUADRONS]; + HangarSlot* squadrons[MAX_SQUADRONS]; + DWORD last_patrol_launch; +}; + +// +--------------------------------------------------------------------+ + + +#endif Hangar_h + diff --git a/Stars45/HardPoint.cpp b/Stars45/HardPoint.cpp new file mode 100644 index 0000000..f9b83a3 --- /dev/null +++ b/Stars45/HardPoint.cpp @@ -0,0 +1,104 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: HardPoint.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + HardPoint class +*/ + +#include "MemDebug.h" +#include "HardPoint.h" +#include "Weapon.h" +#include "WeaponDesign.h" +#include "Shot.h" +#include "Ship.h" +#include "Sim.h" + +// +----------------------------------------------------------------------+ + +HardPoint::HardPoint(Vec3 muzzle_loc, double az, double el) + : aim_azimuth((float) az), aim_elevation((float) el), muzzle(muzzle_loc) +{ + ZeroMemory(designs, sizeof(designs)); +} + +// +----------------------------------------------------------------------+ + +HardPoint::HardPoint(const HardPoint& h) + : aim_azimuth(h.aim_azimuth), aim_elevation(h.aim_elevation), muzzle(h.muzzle), + mount_rel(h.mount_rel), radius(h.radius), hull_factor(h.hull_factor) +{ + CopyMemory(designs, h.designs, sizeof(designs)); +} + +// +--------------------------------------------------------------------+ + +HardPoint::~HardPoint() +{ +} + +// +--------------------------------------------------------------------+ + +void +HardPoint::Mount(Point loc, float rad, float hull) +{ + mount_rel = loc; + radius = rad; + hull_factor = hull; +} + +// +--------------------------------------------------------------------+ + +void +HardPoint::AddDesign(WeaponDesign* d) +{ + for (int i = 0; i < MAX_DESIGNS; i++) { + if (!designs[i]) { + designs[i] = d; + return; + } + } +} + +// +--------------------------------------------------------------------+ + +Weapon* +HardPoint::CreateWeapon(int type_index) +{ + if (type_index >= 0 && type_index < MAX_DESIGNS && designs[type_index]) { + Vec3 zero_pt = Vec3(0.0f, 0.0f, 0.0f); + Vec3* muzzle_pt = &zero_pt; + + if (designs[type_index]->turret.length() == 0) + muzzle_pt = &muzzle; + + Weapon* missile = new(__FILE__,__LINE__) Weapon(designs[type_index], + 1, + muzzle_pt, + aim_azimuth, + aim_elevation); + missile->SetAbbreviation(GetAbbreviation()); + missile->Mount(mount_rel, radius, hull_factor); + return missile; + } + + return 0; +} + +// +--------------------------------------------------------------------+ + +double +HardPoint::GetCarryMass(int type_index) +{ + if (type_index >= 0 && type_index < MAX_DESIGNS && designs[type_index]) + return designs[type_index]->carry_mass; + + return 0; +} + diff --git a/Stars45/HardPoint.h b/Stars45/HardPoint.h new file mode 100644 index 0000000..ae6d74d --- /dev/null +++ b/Stars45/HardPoint.h @@ -0,0 +1,82 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: HardPoint.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Hard Point (gun or missile launcher) class +*/ + +#ifndef HardPoint_h +#define HardPoint_h + +#include "Types.h" +#include "Geometry.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class Weapon; +class WeaponDesign; + +// +--------------------------------------------------------------------+ + +class HardPoint +{ +public: + static const char* TYPENAME() { return "HardPoint"; } + + enum CONSTANTS { MAX_DESIGNS=8 }; + + HardPoint(Vec3 muzzle, double az=0, double el=0); + HardPoint(const HardPoint& rhs); + virtual ~HardPoint(); + + int operator==(const HardPoint& w) const { return this == &w; } + + virtual void AddDesign(WeaponDesign* dsn); + virtual Weapon* CreateWeapon(int type_index=0); + virtual double GetCarryMass(int type_index=0); + WeaponDesign* GetWeaponDesign(int n) { return designs[n]; } + + virtual void Mount(Point loc, float rad, float hull=0.5f); + Point MountLocation() const { return mount_rel; } + double Radius() const { return radius; } + double HullProtection() const { return hull_factor; } + + virtual const char* GetName() const { return name; } + virtual void SetName(const char* s) { name = s; } + virtual const char* GetAbbreviation() const { return abrv; } + virtual void SetAbbreviation(const char* s) { abrv = s; } + virtual const char* GetDesign() const { return sys_dsn; } + virtual void SetDesign(const char* s) { sys_dsn = s; } + + virtual double GetAzimuth() const { return aim_azimuth; } + virtual void SetAzimuth(double a) { aim_azimuth = (float) a; } + virtual double GetElevation() const { return aim_elevation; } + virtual void SetElevation(double e) { aim_elevation = (float) e; } + +protected: + // Displayable name: + Text name; + Text abrv; + Text sys_dsn; + + WeaponDesign* designs[MAX_DESIGNS]; + Vec3 muzzle; + float aim_azimuth; + float aim_elevation; + + // Mounting: + Point mount_rel; // object space + float radius; + float hull_factor; +}; + +#endif HardPoint_h + diff --git a/Stars45/Hoop.cpp b/Stars45/Hoop.cpp new file mode 100644 index 0000000..176ba46 --- /dev/null +++ b/Stars45/Hoop.cpp @@ -0,0 +1,156 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2005. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Hoop.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + ILS Hoop (HUD display) class +*/ + +#include "MemDebug.h" +#include "Hoop.h" + +#include "Game.h" +#include "Bitmap.h" +#include "DataLoader.h" +#include "Window.h" + +static Color ils_color; + +// +--------------------------------------------------------------------+ + +Hoop::Hoop() + : width(360), height(180), mtl(0) +{ + foreground = 1; + radius = (float) width; + + DataLoader* loader = DataLoader::GetLoader(); + + loader->SetDataPath("HUD/"); + loader->LoadTexture("ILS.pcx", hoop_texture, Bitmap::BMP_TRANSLUCENT); + loader->SetDataPath(0); + + CreatePolys(); +} + +Hoop::~Hoop() +{ } + +// +--------------------------------------------------------------------+ + +void Hoop::SetColor(Color c) +{ + ils_color = c; +} + +// +--------------------------------------------------------------------+ + +void +Hoop::CreatePolys() +{ + Material* mtl = new(__FILE__,__LINE__) Material; + + mtl->tex_diffuse = hoop_texture; + mtl->blend = Material::MTL_ADDITIVE; + + int w = width /2; + int h = height/2; + + model = new(__FILE__,__LINE__) Model; + own_model = 1; + + Surface* surface = new(__FILE__,__LINE__) Surface; + + surface->SetName("hoop"); + surface->CreateVerts(4); + surface->CreatePolys(2); + + VertexSet* vset = surface->GetVertexSet(); + Poly* polys = surface->GetPolys(); + + ZeroMemory(polys, sizeof(Poly) * 2); + + for (int i = 0; i < 4; i++) { + int x = w; + int y = h; + float u = 0; + float v = 0; + + if (i == 0 || i == 3) + x = -x; + else + u = 1; + + if (i < 2) + y = -y; + else + v = 1; + + vset->loc[i] = Vec3(x, y, 0); + vset->nrm[i] = Vec3(0, 0, 0); + + vset->tu[i] = u; + vset->tv[i] = v; + } + + for (i = 0; i < 2; i++) { + Poly& poly = polys[i]; + + poly.nverts = 4; + poly.vertex_set = vset; + poly.material = mtl; + + poly.verts[0] = i ? 3 : 0; + poly.verts[1] = i ? 2 : 1; + poly.verts[2] = i ? 1 : 2; + poly.verts[3] = i ? 0 : 3; + + poly.plane = Plane(vset->loc[poly.verts[0]], + vset->loc[poly.verts[2]], + vset->loc[poly.verts[1]]); + + surface->AddIndices(6); + } + + // then assign them to cohesive segments: + Segment* segment = new(__FILE__,__LINE__) Segment; + segment->npolys = 2; + segment->polys = &polys[0]; + segment->material = segment->polys->material; + + surface->GetSegments().append(segment); + + model->AddSurface(surface); + + + SetLuminous(true); +} + +// +--------------------------------------------------------------------+ + +void +Hoop::Update() +{ + if (mtl) + mtl->Ke = ils_color; + + if (model && luminous) { + ListIter s_iter = model->GetSurfaces(); + while (++s_iter) { + Surface* surface = s_iter.value(); + VertexSet* vset = surface->GetVertexSet(); + + for (int i = 0; i < vset->nverts; i++) { + vset->diffuse[i] = ils_color.Value(); + } + } + + InvalidateSurfaceData(); + } +} diff --git a/Stars45/Hoop.h b/Stars45/Hoop.h new file mode 100644 index 0000000..f71dec4 --- /dev/null +++ b/Stars45/Hoop.h @@ -0,0 +1,45 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Hoop.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + ILS Hoop (HUD display) class +*/ + +#ifndef Hoop_h +#define Hoop_h + +#include "Types.h" +#include "Solid.h" +#include "Geometry.h" + +// +--------------------------------------------------------------------+ + +class Hoop : public Solid +{ +public: + Hoop(); + virtual ~Hoop(); + + virtual void Update(); + static void SetColor(Color c); + +protected: + virtual void CreatePolys(); + + Bitmap* hoop_texture; + Material* mtl; + int width; + int height; +}; + +// +--------------------------------------------------------------------+ + +#endif Hoop_h + diff --git a/Stars45/Instruction.cpp b/Stars45/Instruction.cpp new file mode 100644 index 0000000..2cad9b3 --- /dev/null +++ b/Stars45/Instruction.cpp @@ -0,0 +1,569 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Instruction.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Navigation Point class implementation +*/ + +#include "MemDebug.h" +#include "Instruction.h" +#include "Element.h" +#include "RadioMessage.h" +#include "Ship.h" +#include "Sim.h" + +#include "Game.h" +#include "Text.h" + +// +----------------------------------------------------------------------+ + +Instruction::Instruction(int act, const char* tgt) + : region(0), action(act), formation(0), tgt_name(tgt), + status(PENDING), speed(0), target(0), emcon(0), wep_free(0), + priority(PRIMARY), farcast(0), hold_time(0) +{ } + +Instruction::Instruction(const char* rgn, Point loc, int act) + : region(0), action(act), formation(0), + status(PENDING), speed(0), target(0), emcon(0), wep_free(0), + priority(PRIMARY), farcast(0), hold_time(0) +{ + rgn_name = rgn; + rloc.SetBaseLocation(loc); + rloc.SetDistance(0); +} + +Instruction::Instruction(SimRegion* rgn, Point loc, int act) + : region(rgn), action(act), formation(0), + status(PENDING), speed(0), target(0), emcon(0), wep_free(0), + priority(PRIMARY), farcast(0), hold_time(0) +{ + rgn_name = region->Name(); + rloc.SetBaseLocation(loc); + rloc.SetDistance(0); +} + +Instruction::Instruction(const Instruction& instr) + : region(instr.region), rgn_name(instr.rgn_name), + rloc(instr.rloc), action(instr.action), + formation(instr.formation), status(instr.status), speed(instr.speed), + target(0), tgt_name(instr.tgt_name), tgt_desc(instr.tgt_desc), + emcon(instr.emcon), wep_free(instr.wep_free), + priority(instr.priority), farcast(instr.farcast), + hold_time(instr.hold_time) +{ + SetTarget(instr.target); +} + +Instruction::~Instruction() +{ } + +// +--------------------------------------------------------------------+ + +Instruction& +Instruction::operator = (const Instruction& n) +{ + rgn_name = n.rgn_name; + region = n.region; + rloc = n.rloc; + action = n.action; + formation = n.formation; + status = n.status; + speed = n.speed; + + tgt_name = n.tgt_name; + tgt_desc = n.tgt_desc; + target = 0; + emcon = n.emcon; + wep_free = n.wep_free; + priority = n.priority; + farcast = n.farcast; + hold_time = n.hold_time; + + SetTarget(n.target); + return *this; +} + +// +--------------------------------------------------------------------+ + +Point +Instruction::Location() const +{ + Instruction* pThis = (Instruction*) this; + return pThis->rloc.Location(); +} + +void +Instruction::SetLocation(const Point& l) +{ + rloc.SetBaseLocation(l); + rloc.SetReferenceLoc(0); + rloc.SetDistance(0); +} + +// +----------------------------------------------------------------------+ + +SimObject* +Instruction::GetTarget() +{ + if (!target && tgt_name.length() > 0) { + Sim* sim = Sim::GetSim(); + + if (sim) { + Ship* s = sim->FindShip(tgt_name, rgn_name); + + if (s) { + target = s; + Observe(target); + } + } + } + + return target; +} + +void +Instruction::SetTarget(const char* n) +{ + if (n && *n && tgt_name != n) { + tgt_name = n; + tgt_desc = n; + + if (target) + target = 0; + } +} + +void +Instruction::SetTarget(SimObject* s) +{ + if (s && target != s) { + tgt_name = s->Name(); + target = s; + Observe(target); + } +} + +void +Instruction::SetTargetDesc(const char* d) +{ + if (d && *d) + tgt_desc = d; +} + +void +Instruction::ClearTarget() +{ + if (target) { + target = 0; + tgt_name = ""; + tgt_desc = ""; + } +} + +// +----------------------------------------------------------------------+ + +bool +Instruction::Update(SimObject* obj) +{ + if (target == obj) + target = 0; + + return SimObserver::Update(obj); +} + +const char* +Instruction::GetObserverName() const +{ + return "Instruction"; +} + +// +----------------------------------------------------------------------+ + +void +Instruction::Evaluate(Ship* ship) +{ + Sim* sim = Sim::GetSim(); + + switch (action) { + case VECTOR: + break; + + case LAUNCH: + if (ship->GetFlightPhase() == Ship::ACTIVE) + SetStatus(COMPLETE); + break; + + case DOCK: + case RTB: + if (sim->GetPlayerShip() == ship && + (ship->GetFlightPhase() == Ship::DOCKING || + ship->GetFlightPhase() == Ship::DOCKED)) + SetStatus(COMPLETE); + else if (ship->Integrity() < 1) + SetStatus(FAILED); + break; + + case DEFEND: + case ESCORT: + { + bool found = false; + bool safe = true; + + ListIter iter = sim->GetElements(); + while (++iter && !found) { + Element* e = iter.value(); + + if (e->IsFinished() || e->IsSquadron()) + continue; + + if (e->Name() == tgt_name || + (e->GetCommander() && e->GetCommander()->Name() == tgt_name)) { + + found = true; + + for (int i = 0; i < e->NumShips(); i++) { + Ship* s = e->GetShip(i+1); + + if (s && s->Integrity() < 1) + SetStatus(FAILED); + } + + if (status == PENDING) { + // if the element had a flight plan, and all nav points + // have been addressed, then the element is safe + if (e->FlightPlanLength() > 0) { + if (e->GetNextNavPoint() == 0) + SetStatus(COMPLETE); + else + safe = false; + } + } + } + } + + if (status == PENDING && safe && + sim->GetPlayerShip() == ship && + (ship->GetFlightPhase() == Ship::DOCKING || + ship->GetFlightPhase() == Ship::DOCKED)) { + SetStatus(COMPLETE); + } + } + break; + + case PATROL: + case SWEEP: + { + Sim* sim = Sim::GetSim(); + bool alive = false; + + ListIter iter = sim->GetElements(); + while (++iter) { + Element* e = iter.value(); + + if (e->IsFinished() || e->IsSquadron()) + continue; + + if (e->GetIFF() && e->GetIFF() != ship->GetIFF()) { + for (int i = 0; i < e->NumShips(); i++) { + Ship* s = e->GetShip(i+1); + + if (s && s->Integrity() >= 1) + alive = true; + } + } + } + + if (status == PENDING && !alive) { + SetStatus(COMPLETE); + } + } + break; + + case INTERCEPT: + case STRIKE: + case ASSAULT: + { + Sim* sim = Sim::GetSim(); + bool alive = false; + + ListIter iter = sim->GetElements(); + while (++iter) { + Element* e = iter.value(); + + if (e->IsFinished() || e->IsSquadron()) + continue; + + if (e->Name() == tgt_name) { + for (int i = 0; i < e->NumShips(); i++) { + Ship* s = e->GetShip(i+1); + + if (s && s->Integrity() >= 1) + alive = true; + } + } + } + + if (status == PENDING && !alive) { + SetStatus(COMPLETE); + } + } + break; + + case RECON: + break; + + default: + break; + } +} + +void +Instruction::SetStatus(int s) +{ + status = s; +} + +// +----------------------------------------------------------------------+ + +const char* +Instruction::GetShortDescription() const +{ + static char desc[256]; + + switch (action) { + case VECTOR: + if (farcast) + sprintf(desc, Game::GetText("instr.short.farcast").data(), rgn_name.data()); + else + sprintf(desc, Game::GetText("instr.short.vector").data(), rgn_name.data()); + break; + + case LAUNCH: + sprintf(desc, Game::GetText("instr.short.launch").data(), tgt_name.data()); + break; + + case DOCK: + sprintf(desc, Game::GetText("instr.short.dock").data(), tgt_name.data()); + break; + + case RTB: + sprintf(desc, Game::GetText("instr.short.return-to-base").data()); + break; + + case DEFEND: + if (priority == PRIMARY) { + sprintf(desc, Game::GetText("instr.short.defend").data(), ActionName(action), tgt_desc.data()); + } + else { + sprintf(desc, Game::GetText("instr.short.protect").data(), tgt_desc.data()); + } + break; + + case ESCORT: + if (priority == PRIMARY) { + sprintf(desc, Game::GetText("instr.short.escort").data(), ActionName(action), tgt_desc.data()); + } + else { + sprintf(desc, Game::GetText("instr.short.protect").data(), tgt_desc.data()); + } + break; + + case PATROL: + sprintf(desc, Game::GetText("instr.short.patrol").data(), + tgt_desc.data(), + rgn_name.data()); + break; + + case SWEEP: + sprintf(desc, Game::GetText("instr.short.sweep").data(), + tgt_desc.data(), + rgn_name.data()); + break; + + case INTERCEPT: + sprintf(desc, Game::GetText("instr.short.intercept").data(), tgt_desc.data()); + break; + + case STRIKE: + sprintf(desc, Game::GetText("instr.short.strike").data(), tgt_desc.data()); + break; + + case ASSAULT: + sprintf(desc, Game::GetText("instr.short.assault").data(), tgt_desc.data()); + break; + + case RECON: + sprintf(desc, Game::GetText("instr.short.recon").data(), tgt_desc.data()); + break; + + default: + sprintf(desc, "%s", ActionName(action)); + break; + } + + if (status != PENDING) { + strcat(desc, " - "); + strcat(desc, Game::GetText(StatusName(status))); + } + + return desc; +} + +// +----------------------------------------------------------------------+ + +const char* +Instruction::GetDescription() const +{ + static char desc[1024]; + + switch (action) { + case VECTOR: + if (farcast) + sprintf(desc, Game::GetText("instr.long.farcast").data(), rgn_name.data()); + else + sprintf(desc, Game::GetText("instr.long.vector").data(), rgn_name.data()); + break; + + case LAUNCH: + sprintf(desc, Game::GetText("instr.long.launch").data(), tgt_name.data()); + break; + + case DOCK: + sprintf(desc, Game::GetText("instr.long.dock").data(), tgt_name.data()); + break; + + case RTB: + sprintf(desc, Game::GetText("instr.long.return-to-base").data()); + break; + + case DEFEND: + if (priority == PRIMARY) { + sprintf(desc, Game::GetText("instr.long.defend").data(), ActionName(action), tgt_desc.data()); + } + else { + sprintf(desc, Game::GetText("instr.long.protect").data(), tgt_desc.data()); + } + break; + + case ESCORT: + if (priority == PRIMARY) { + sprintf(desc, Game::GetText("instr.long.escort").data(), ActionName(action), tgt_desc.data()); + } + else { + sprintf(desc, Game::GetText("instr.long.protect").data(), tgt_desc.data()); + } + break; + + case PATROL: + sprintf(desc, Game::GetText("instr.long.patrol").data(), + tgt_desc.data(), + rgn_name.data()); + break; + + case SWEEP: + sprintf(desc, Game::GetText("instr.long.sweep").data(), + tgt_desc.data(), + rgn_name.data()); + break; + + case INTERCEPT: + sprintf(desc, Game::GetText("instr.long.intercept").data(), tgt_desc.data()); + break; + + case STRIKE: + sprintf(desc, Game::GetText("instr.long.strike").data(), tgt_desc.data()); + break; + + case ASSAULT: + sprintf(desc, Game::GetText("instr.long.assault").data(), tgt_desc.data()); + break; + + case RECON: + sprintf(desc, Game::GetText("instr.long.recon").data(), tgt_desc.data()); + break; + + default: + sprintf(desc, "%s", ActionName(action)); + break; + } + + if (status != PENDING) { + strcat(desc, " - "); + strcat(desc, Game::GetText(StatusName(status))); + } + + return desc; +} + +// +----------------------------------------------------------------------+ + +const char* +Instruction::ActionName(int a) +{ + switch (a) { + case VECTOR: return "Vector"; + case LAUNCH: return "Launch"; + case DOCK: return "Dock"; + case RTB: return "RTB"; + + case DEFEND: return "Defend"; + case ESCORT: return "Escort"; + case PATROL: return "Patrol"; + case SWEEP: return "Sweep"; + case INTERCEPT: return "Intercept"; + case STRIKE: return "Strike"; + case ASSAULT: return "Assault"; + case RECON: return "Recon"; + + default: return "Unknown"; + } +} + +const char* +Instruction::StatusName(int s) +{ + switch (s) { + case PENDING: return "Pending"; + case ACTIVE: return "Active"; + case SKIPPED: return "Skipped"; + case ABORTED: return "Aborted"; + case FAILED: return "Failed"; + case COMPLETE: return "Complete"; + + default: return "Unknown"; + } +} + +const char* +Instruction::FormationName(int f) +{ + switch (f) { + case DIAMOND: return "Diamond"; + case SPREAD: return "Spread"; + case BOX: return "Box"; + case TRAIL: return "Trail"; + + default: return "Unknown"; + } +} + +const char* +Instruction::PriorityName(int p) +{ + switch (p) { + case PRIMARY: return "Primary"; + case SECONDARY: return "Secondary"; + case BONUS: return "Bonus"; + + default: return "Unknown"; + } +} + diff --git a/Stars45/Instruction.h b/Stars45/Instruction.h new file mode 100644 index 0000000..57f83a9 --- /dev/null +++ b/Stars45/Instruction.h @@ -0,0 +1,166 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Instruction.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Instruction (NavPoint / Order / Objective) class declaration +*/ + +#ifndef Instruction_h +#define Instruction_h + +#include "Types.h" +#include "Geometry.h" +#include "SimObject.h" +#include "Text.h" +#include "RLoc.h" + +// +--------------------------------------------------------------------+ + +class Ship; + +// +--------------------------------------------------------------------+ + +class Instruction : public SimObserver +{ +public: + static const char* TYPENAME() { return "Instruction"; } + + enum ACTION + { + VECTOR, + LAUNCH, + DOCK, + RTB, + + DEFEND, + ESCORT, + PATROL, + SWEEP, + INTERCEPT, + STRIKE, // ground attack + ASSAULT, // starship attack + RECON, + + //RECALL, + //DEPLOY, + + NUM_ACTIONS + }; + + enum STATUS + { + PENDING, + ACTIVE, + SKIPPED, + ABORTED, + FAILED, + COMPLETE, + + NUM_STATUS + }; + + enum FORMATION + { + DIAMOND, + SPREAD, + BOX, + TRAIL, + + NUM_FORMATIONS + }; + + enum PRIORITY + { + PRIMARY = 1, + SECONDARY, + BONUS + }; + + Instruction(int action, const char* tgt); + Instruction(const char* rgn, Point loc, int act=VECTOR); + Instruction(SimRegion* rgn, Point loc, int act=VECTOR); + Instruction(const Instruction& instr); + virtual ~Instruction(); + + Instruction& operator = (const Instruction& n); + + // accessors: + static const char* ActionName(int a); + static const char* StatusName(int s); + static const char* FormationName(int f); + static const char* PriorityName(int p); + + const char* RegionName() const { return rgn_name; } + SimRegion* Region() const { return region; } + Point Location() const; + RLoc& GetRLoc() { return rloc; } + int Action() const { return action; } + int Status() const { return status; } + int Formation() const { return formation; } + int Speed() const { return speed; } + int EMCON() const { return emcon; } + int WeaponsFree() const { return wep_free; } + int Priority() const { return priority; } + int Farcast() const { return farcast; } + double HoldTime() const { return hold_time; } + + const char* TargetName() const { return tgt_name; } + const char* TargetDesc() const { return tgt_desc; } + SimObject* GetTarget(); + + void Evaluate(Ship* s); + const char* GetShortDescription() const; + const char* GetDescription() const; + + // mutators: + void SetRegion(SimRegion* r) { region = r; } + void SetLocation(const Point& l); + void SetAction(int s) { action = s; } + void SetStatus(int s); + void SetFormation(int s) { formation = s; } + void SetSpeed(int s) { speed = s; } + void SetEMCON(int e) { emcon = e; } + void SetWeaponsFree(int f) { wep_free = f; } + void SetPriority(int p) { priority = p; } + void SetFarcast(int f) { farcast = f; } + void SetHoldTime(double t) { hold_time = t; } + + void SetTarget(const char* n); + void SetTarget(SimObject* s); + void SetTargetDesc(const char* d); + void ClearTarget(); + + virtual bool Update(SimObject* s); + virtual const char* GetObserverName() const; + +protected: + Text rgn_name; + SimRegion* region; + RLoc rloc; + int action; + int formation; + int status; + int speed; + + Text tgt_name; + Text tgt_desc; + SimObject* target; + int emcon; + int wep_free; + int priority; + int farcast; + + double hold_time; +}; + +// +--------------------------------------------------------------------+ + +#endif Instruction_h + diff --git a/Stars45/Intel.cpp b/Stars45/Intel.cpp new file mode 100644 index 0000000..2ce0e63 --- /dev/null +++ b/Stars45/Intel.cpp @@ -0,0 +1,46 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Intel.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + An element in the dynamic campaign +*/ + +#include "MemDebug.h" +#include "Intel.h" + +#include "Game.h" +#include "DataLoader.h" + +// +--------------------------------------------------------------------+ + +static const char* intel_name[] = { + "", + "Reserve", + "Secret", + "Known", + "Located", + "Tracked", +}; + +const char* +Intel::NameFromIntel(int intel) +{ + return intel_name[intel]; +} + +int +Intel::IntelFromName(const char* type_name) +{ + for (int i = RESERVE; i <= TRACKED; i++) + if (!stricmp(type_name, intel_name[i])) + return i; + + return 0; +} diff --git a/Stars45/Intel.h b/Stars45/Intel.h new file mode 100644 index 0000000..9c1e0fb --- /dev/null +++ b/Stars45/Intel.h @@ -0,0 +1,37 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Intel.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== +*/ + +#ifndef Intel_h +#define Intel_h + +#include "Types.h" + +// +--------------------------------------------------------------------+ + +class Intel +{ +public: + enum INTEL_TYPE { + RESERVE = 1, // out-system reserve: this group is not even here + SECRET, // enemy is completely unaware of this group + KNOWN, // enemy knows this group is in the system + LOCATED, // enemy has located at least the lead ship + TRACKED // enemy is tracking all elements + }; + + static int IntelFromName(const char* name); + static const char* NameFromIntel(int intel); +}; + +#endif Intel_h + diff --git a/Stars45/JoyDlg.cpp b/Stars45/JoyDlg.cpp new file mode 100644 index 0000000..6581c94 --- /dev/null +++ b/Stars45/JoyDlg.cpp @@ -0,0 +1,230 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: JoyDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== +*/ + +#include "MemDebug.h" +#include "JoyDlg.h" +#include "KeyMap.h" +#include "MenuScreen.h" +#include "Starshatter.h" +#include "FormatUtil.h" + +#include "Game.h" +#include "ListBox.h" +#include "ComboBox.h" +#include "Button.h" +#include "Joystick.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +DEF_MAP_CLIENT(JoyDlg, OnApply); +DEF_MAP_CLIENT(JoyDlg, OnCancel); +DEF_MAP_CLIENT(JoyDlg, OnAxis); + +static const char* joy_axis_names[] = { + "JoyDlg.axis.0", + "JoyDlg.axis.1", + "JoyDlg.axis.2", + "JoyDlg.axis.3", + "JoyDlg.axis.4", + "JoyDlg.axis.5", + "JoyDlg.axis.6", + "JoyDlg.axis.7" +}; + +static int selected_axis = -1; +static int sample_axis = -1; +static int samples[8]; + +static int map_axis[4]; + +// +--------------------------------------------------------------------+ + +JoyDlg::JoyDlg(Screen* s, FormDef& def, BaseScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), manager(mgr), + apply(0), cancel(0), message(0) +{ + Init(def); +} + +JoyDlg::~JoyDlg() +{ +} + +void +JoyDlg::RegisterControls() +{ + if (apply) + return; + + for (int i = 0; i < 4; i++) { + axis_button[i] = (Button*) FindControl(201 + i); + invert_checkbox[i] = (Button*) FindControl(301 + i); + + if (axis_button[i]) + REGISTER_CLIENT(EID_CLICK, axis_button[i], JoyDlg, OnAxis); + } + + message = FindControl(11); + + apply = (Button*) FindControl(1); + REGISTER_CLIENT(EID_CLICK, apply, JoyDlg, OnApply); + + cancel = (Button*) FindControl(2); + REGISTER_CLIENT(EID_CLICK, cancel, JoyDlg, OnCancel); +} + +// +--------------------------------------------------------------------+ + +void +JoyDlg::ExecFrame() +{ + if (selected_axis >= 0 && selected_axis < 4) { + Joystick* joystick = Joystick::GetInstance(); + if (joystick) { + joystick->Acquire(); + + int delta = 1000; + + for (int i = 0; i < 8; i++) { + int a = Joystick::ReadRawAxis(i + KEY_JOY_AXIS_X); + + int d = a - samples[i]; + if (d < 0) d = -d; + + if (d > delta && samples[i] < 1e6) { + delta = d; + sample_axis = i; + } + + samples[i] = a; + } + + Button* b = axis_button[selected_axis]; + + if (sample_axis >= 0) { + b->SetText(Game::GetText(joy_axis_names[sample_axis])); + map_axis[selected_axis] = sample_axis; + } + + else + b->SetText(Game::GetText("JoyDlg.select")); + } + } +} + +// +--------------------------------------------------------------------+ + +void +JoyDlg::Show() +{ + FormWindow::Show(); + + for (int i = 0; i < 4; i++) { + Button* b = axis_button[i]; + if (b) { + int map = Joystick::GetAxisMap(i) - KEY_JOY_AXIS_X; + int inv = Joystick::GetAxisInv(i); + + if (map >= 0 && map < 8) { + b->SetText(Game::GetText(joy_axis_names[map])); + map_axis[i] = map; + } + else { + b->SetText(Game::GetText("JoyDlg.unmapped")); + } + + b->SetButtonState(0); + + invert_checkbox[i]->SetButtonState(inv ? 1 : 0); + } + } + + SetFocus(); +} + +// +--------------------------------------------------------------------+ + +void +JoyDlg::OnAxis(AWEvent* event) +{ + for (int i = 0; i < 4; i++) { + int map = map_axis[i]; + Text name = Game::GetText("JoyDlg.unmapped"); + Button* b = axis_button[i]; + + if (map >= 0 && map < 8) + name = Game::GetText(joy_axis_names[map]); + + if (b) { + if (b == event->window) { + if (selected_axis == i) { + b->SetText(name); + b->SetButtonState(0); + selected_axis = -1; + } + else { + b->SetText(Game::GetText("JoyDlg.select")); + b->SetButtonState(1); + selected_axis = i; + } + } + else { + b->SetText(name); + b->SetButtonState(0); + } + } + } + + for (i = 0; i < 8; i++) { + samples[i] = 10000000; + } +} + +// +--------------------------------------------------------------------+ + +void +JoyDlg::OnApply(AWEvent* event) +{ + Starshatter* stars = Starshatter::GetInstance(); + + if (stars) { + KeyMap& keymap = stars->GetKeyMap(); + + keymap.Bind(KEY_AXIS_YAW, map_axis[0]+KEY_JOY_AXIS_X, 0); + keymap.Bind(KEY_AXIS_PITCH, map_axis[1]+KEY_JOY_AXIS_X, 0); + keymap.Bind(KEY_AXIS_ROLL, map_axis[2]+KEY_JOY_AXIS_X, 0); + keymap.Bind(KEY_AXIS_THROTTLE, map_axis[3]+KEY_JOY_AXIS_X, 0); + + keymap.Bind(KEY_AXIS_YAW_INVERT, invert_checkbox[0]->GetButtonState(), 0); + keymap.Bind(KEY_AXIS_PITCH_INVERT, invert_checkbox[1]->GetButtonState(), 0); + keymap.Bind(KEY_AXIS_ROLL_INVERT, invert_checkbox[2]->GetButtonState(), 0); + keymap.Bind(KEY_AXIS_THROTTLE_INVERT, invert_checkbox[3]->GetButtonState(), 0); + + keymap.SaveKeyMap("key.cfg", 256); + + stars->MapKeys(); + } + + if (manager) + manager->ShowCtlDlg(); +} + +void +JoyDlg::OnCancel(AWEvent* event) +{ + if (manager) + manager->ShowCtlDlg(); +} + +// +--------------------------------------------------------------------+ diff --git a/Stars45/JoyDlg.h b/Stars45/JoyDlg.h new file mode 100644 index 0000000..658e87a --- /dev/null +++ b/Stars45/JoyDlg.h @@ -0,0 +1,56 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: JoyDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Navigation Active Window class +*/ + +#ifndef JoyDlg_h +#define JoyDlg_h + +#include "Types.h" +#include "FormWindow.h" + +// +--------------------------------------------------------------------+ + +class BaseScreen; + +// +--------------------------------------------------------------------+ + +class JoyDlg : public FormWindow +{ +public: + JoyDlg(Screen* s, FormDef& def, BaseScreen* mgr); + virtual ~JoyDlg(); + + virtual void RegisterControls(); + virtual void Show(); + + // Operations: + virtual void ExecFrame(); + + virtual void OnApply(AWEvent* event); + virtual void OnCancel(AWEvent* event); + + virtual void OnAxis(AWEvent* event); + +protected: + BaseScreen* manager; + + ActiveWindow* message; + Button* axis_button[4]; + Button* invert_checkbox[4]; + + Button* apply; + Button* cancel; +}; + +#endif JoyDlg_h + diff --git a/Stars45/KeyDlg.cpp b/Stars45/KeyDlg.cpp new file mode 100644 index 0000000..ce2c12c --- /dev/null +++ b/Stars45/KeyDlg.cpp @@ -0,0 +1,199 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: KeyDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== +*/ + +#include "MemDebug.h" +#include "KeyDlg.h" +#include "KeyMap.h" +#include "MenuScreen.h" +#include "Starshatter.h" +#include "FormatUtil.h" + +#include "Game.h" +#include "ListBox.h" +#include "ComboBox.h" +#include "Button.h" +#include "Joystick.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +DEF_MAP_CLIENT(KeyDlg, OnApply); +DEF_MAP_CLIENT(KeyDlg, OnCancel); +DEF_MAP_CLIENT(KeyDlg, OnClear); + +// +--------------------------------------------------------------------+ + +KeyDlg::KeyDlg(Screen* s, FormDef& def, BaseScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), manager(mgr), + key_key(0), key_shift(0), key_joy(0), key_clear(0), + command(0), current_key(0), new_key(0), clear(0), + apply(0), cancel(0) +{ + Init(def); +} + +KeyDlg::~KeyDlg() +{ +} + +void +KeyDlg::RegisterControls() +{ + if (apply) + return; + + command = FindControl(201); + current_key = FindControl(202); + new_key = FindControl(203); + + clear = (Button*) FindControl(300); + REGISTER_CLIENT(EID_CLICK, clear, KeyDlg, OnClear); + + apply = (Button*) FindControl(1); + REGISTER_CLIENT(EID_CLICK, apply, KeyDlg, OnApply); + + cancel = (Button*) FindControl(2); + REGISTER_CLIENT(EID_CLICK, cancel, KeyDlg, OnCancel); +} + +// +--------------------------------------------------------------------+ + +void +KeyDlg::ExecFrame() +{ + int key = 0; + int shift = 0; + int joy = 0; + Joystick* joystick = Joystick::GetInstance(); + + if (joystick) joystick->Acquire(); + + for (int i = 0; i < 256; i++) { + int vk = KeyMap::GetMappableVKey(i); + + if (vk >= KEY_JOY_1 && vk <= KEY_JOY_16) { + if (joystick && joystick->KeyDown(vk)) + joy = vk; + } + + else if (vk >= KEY_POV_0_UP && vk <= KEY_POV_3_RIGHT) { + if (joystick && joystick->KeyDown(vk)) + joy = vk; + } + + else if (GetAsyncKeyState(vk)) { + if (vk == VK_SHIFT || vk == VK_MENU) + shift = vk; + else + key = vk; + } + } + + if (key) { + key_key = key; + key_shift = shift; + + new_key->SetText(KeyMap::DescribeKey(key, shift, joy)); + } + + else if (joy) { + key_joy = joy; + new_key->SetText(KeyMap::DescribeKey(key, shift, joy)); + } +} + +// +--------------------------------------------------------------------+ + +void +KeyDlg::Show() +{ + FormWindow::Show(); + + Starshatter* stars = Starshatter::GetInstance(); + + if (stars) { + KeyMap& keymap = stars->GetKeyMap(); + + if (command) + command->SetText(keymap.DescribeAction(key_index)); + + if (current_key) + current_key->SetText(keymap.DescribeKey(key_index)); + } + + key_clear = false; + new_key->SetText(""); + SetFocus(); +} + +// +--------------------------------------------------------------------+ + +void +KeyDlg::SetKeyMapIndex(int i) +{ + key_index = i; + key_key = 0; + key_shift = 0; +} + +// +--------------------------------------------------------------------+ + +void +KeyDlg::OnClear(AWEvent* event) +{ + key_clear = true; + + key_key = 0; + key_shift = 0; + key_joy = 0; +} + +// +--------------------------------------------------------------------+ + +void +KeyDlg::OnApply(AWEvent* event) +{ + Starshatter* stars = Starshatter::GetInstance(); + + if (stars) { + KeyMap& keymap = stars->GetKeyMap(); + KeyMapEntry* map = keymap.GetKeyMap(key_index); + + if (key_clear) { + map->key = 0; + map->alt = 0; + map->joy = 0; + } + + if (key_key) { + map->key = key_key; + map->alt = key_shift; + } + + if (key_joy) { + map->joy = key_joy; + } + } + + if (manager) + manager->ShowCtlDlg(); +} + +void +KeyDlg::OnCancel(AWEvent* event) +{ + if (manager) + manager->ShowCtlDlg(); +} + +// +--------------------------------------------------------------------+ diff --git a/Stars45/KeyDlg.h b/Stars45/KeyDlg.h new file mode 100644 index 0000000..79fb16b --- /dev/null +++ b/Stars45/KeyDlg.h @@ -0,0 +1,65 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: KeyDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Navigation Active Window class +*/ + +#ifndef KeyDlg_h +#define KeyDlg_h + +#include "Types.h" +#include "FormWindow.h" + +// +--------------------------------------------------------------------+ + +class BaseScreen; + +// +--------------------------------------------------------------------+ + +class KeyDlg : public FormWindow +{ +public: + KeyDlg(Screen* s, FormDef& def, BaseScreen* mgr); + virtual ~KeyDlg(); + + virtual void RegisterControls(); + virtual void Show(); + + // Operations: + virtual void ExecFrame(); + + virtual void OnApply(AWEvent* event); + virtual void OnCancel(AWEvent* event); + virtual void OnClear(AWEvent* event); + + int GetKeyMapIndex() const { return key_index; } + void SetKeyMapIndex(int i); + +protected: + BaseScreen* manager; + + int key_index; + int key_key; + int key_shift; + int key_joy; + int key_clear; + + Button* clear; + Button* apply; + Button* cancel; + + ActiveWindow* command; + ActiveWindow* current_key; + ActiveWindow* new_key; +}; + +#endif KeyDlg_h + diff --git a/Stars45/KeyMap.cpp b/Stars45/KeyMap.cpp new file mode 100644 index 0000000..a70ad4a --- /dev/null +++ b/Stars45/KeyMap.cpp @@ -0,0 +1,824 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: KeyMap.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Weapon class +*/ + +#include "MemDebug.h" +#include "Keyboard.h" +#include "KeyMap.h" +#include "Game.h" +#include "Ship.h" + +// +----------------------------------------------------------------------+ + +struct KeyName +{ + int category; + int key; + const char* name; + const char* desc; +}; + +static KeyName key_action_table[] = { + { KeyMap::KEY_FLIGHT, KEY_PLUS_X, "KEY_PLUS_X", "Translate Right" }, + { KeyMap::KEY_FLIGHT, KEY_MINUS_X, "KEY_MINUS_X", "Translate Left" }, + { KeyMap::KEY_FLIGHT, KEY_PLUS_Y, "KEY_PLUS_Y", "Translate Forward" }, + { KeyMap::KEY_FLIGHT, KEY_MINUS_Y, "KEY_MINUS_Y", "Translate Aft" }, + { KeyMap::KEY_FLIGHT, KEY_PLUS_Z, "KEY_PLUS_Z", "Translate Up" }, + { KeyMap::KEY_FLIGHT, KEY_MINUS_Z, "KEY_MINUS_Z", "Translate Down" }, + + { KeyMap::KEY_FLIGHT, KEY_PITCH_UP, "KEY_PITCH_UP", "Pitch Up" }, + { KeyMap::KEY_FLIGHT, KEY_PITCH_DOWN, "KEY_PITCH_DOWN", "Pitch Down" }, + { KeyMap::KEY_FLIGHT, KEY_YAW_LEFT, "KEY_YAW_LEFT", "Yaw Left" }, + { KeyMap::KEY_FLIGHT, KEY_YAW_RIGHT, "KEY_YAW_RIGHT", "Yaw Right" }, + { KeyMap::KEY_FLIGHT, KEY_ROLL_LEFT, "KEY_ROLL_LEFT", "Roll Left" }, + { KeyMap::KEY_FLIGHT, KEY_ROLL_RIGHT, "KEY_ROLL_RIGHT", "Roll Right" }, + { KeyMap::KEY_FLIGHT, KEY_CENTER, "KEY_CENTER", "Center" }, + { KeyMap::KEY_FLIGHT, KEY_ROLL_ENABLE, "KEY_ROLL_ENABLE", "Roll Enable" }, + { KeyMap::KEY_FLIGHT, KEY_SWAP_ROLL_YAW, "KEY_SWAP_ROLL_YAW", "Swap Ctrl Axes" }, + + { KeyMap::KEY_WEAPONS, KEY_ACTION_0, "KEY_ACTION_0", "Fire Primary" }, + { KeyMap::KEY_WEAPONS, KEY_ACTION_1, "KEY_ACTION_1", "Fire Secondary" }, + { KeyMap::KEY_WEAPONS, KEY_ACTION_2, "KEY_ACTION_2", "Action 2" }, + { KeyMap::KEY_WEAPONS, KEY_ACTION_3, "KEY_ACTION_3", "Action 3" }, + + { KeyMap::KEY_FLIGHT, KEY_CONTROL_MODEL, "KEY_CONTROL_MODEL" }, + + { KeyMap::KEY_FLIGHT, KEY_MOUSE_SELECT, "KEY_MOUSE_SELECT" }, + { KeyMap::KEY_FLIGHT, KEY_MOUSE_SENSE, "KEY_MOUSE_SENSE" }, + { KeyMap::KEY_FLIGHT, KEY_MOUSE_SWAP, "KEY_MOUSE_SWAP" }, + { KeyMap::KEY_FLIGHT, KEY_MOUSE_INVERT, "KEY_MOUSE_INVERT" }, + { KeyMap::KEY_FLIGHT, KEY_MOUSE_ACTIVE, "KEY_MOUSE_ACTIVE", "Toggle Mouse Ctrl" }, + + { KeyMap::KEY_FLIGHT, KEY_JOY_SELECT, "KEY_JOY_SELECT" }, + { KeyMap::KEY_FLIGHT, KEY_JOY_RUDDER, "KEY_JOY_RUDDER" }, + { KeyMap::KEY_FLIGHT, KEY_JOY_THROTTLE, "KEY_JOY_THROTTLE" }, + { KeyMap::KEY_FLIGHT, KEY_JOY_SENSE, "KEY_JOY_SENSE" }, + { KeyMap::KEY_FLIGHT, KEY_JOY_DEAD_ZONE, "KEY_JOY_DEAD_ZONE" }, + { KeyMap::KEY_FLIGHT, KEY_JOY_SWAP, "KEY_JOY_SWAP" }, + + { KeyMap::KEY_FLIGHT, KEY_AXIS_YAW, "KEY_AXIS_YAW" }, + { KeyMap::KEY_FLIGHT, KEY_AXIS_PITCH, "KEY_AXIS_PITCH" }, + { KeyMap::KEY_FLIGHT, KEY_AXIS_ROLL, "KEY_AXIS_ROLL" }, + { KeyMap::KEY_FLIGHT, KEY_AXIS_THROTTLE, "KEY_AXIS_THROTTLE" }, + { KeyMap::KEY_FLIGHT, KEY_AXIS_YAW_INVERT, "KEY_AXIS_YAW_INVERT" }, + { KeyMap::KEY_FLIGHT, KEY_AXIS_PITCH_INVERT, "KEY_AXIS_PITCH_INVERT" }, + { KeyMap::KEY_FLIGHT, KEY_AXIS_ROLL_INVERT, "KEY_AXIS_ROLL_INVERT" }, + { KeyMap::KEY_FLIGHT, KEY_AXIS_THROTTLE_INVERT, "KEY_AXIS_THROTTLE_INVERT" }, + + { KeyMap::KEY_MISC, KEY_EXIT, "KEY_EXIT", "Exit" }, + { KeyMap::KEY_MISC, KEY_PAUSE, "KEY_PAUSE", "Pause" }, +// { KeyMap::KEY_VIEW, KEY_NEXT_VIEW, "KEY_NEXT_VIEW", "Next View" }, + { KeyMap::KEY_VIEW, KEY_TARGET_PADLOCK, "KEY_TARGET_PADLOCK","Padlock Target" }, + { KeyMap::KEY_VIEW, KEY_THREAT_PADLOCK, "KEY_THREAT_PADLOCK","Padlock Threat" }, + { KeyMap::KEY_WEAPONS, KEY_LOCK_TARGET, "KEY_LOCK_TARGET", "Lock Target" }, + { KeyMap::KEY_WEAPONS, KEY_LOCK_THREAT, "KEY_LOCK_THREAT", "Lock Threat" }, + { KeyMap::KEY_WEAPONS, KEY_LOCK_CLOSEST_SHIP, "KEY_LOCK_CLOSEST_SHIP", "Lock Closest Ship" }, + { KeyMap::KEY_WEAPONS, KEY_LOCK_CLOSEST_THREAT, "KEY_LOCK_CLOSEST_THREAT", "Lock Closest Threat" }, + { KeyMap::KEY_WEAPONS, KEY_LOCK_HOSTILE_SHIP, "KEY_LOCK_HOSTILE_SHIP", "Lock Hostile Ship" }, + { KeyMap::KEY_WEAPONS, KEY_LOCK_HOSTILE_THREAT, "KEY_LOCK_HOSTILE_THREAT", "Lock Hostile Threat" }, + { KeyMap::KEY_WEAPONS, KEY_CYCLE_SUBTARGET, "KEY_CYCLE_SUBTARGET", "Target Subsystem" }, + { KeyMap::KEY_WEAPONS, KEY_PREV_SUBTARGET, "KEY_PREV_SUBTARGET", "Previous Subsystem" }, + { KeyMap::KEY_FLIGHT, KEY_AUTO_NAV, "KEY_AUTO_NAV", "Autonav" }, + { KeyMap::KEY_MISC, KEY_TIME_COMPRESS, "KEY_TIME_COMPRESS", "Compress Time" }, + { KeyMap::KEY_MISC, KEY_TIME_EXPAND, "KEY_TIME_EXPAND", "Expand Time" }, + { KeyMap::KEY_MISC, KEY_TIME_SKIP, "KEY_TIME_SKIP", "Skip to Next Event" }, + + { KeyMap::KEY_FLIGHT, KEY_THROTTLE_UP, "KEY_THROTTLE_UP", "Throttle Up" }, + { KeyMap::KEY_FLIGHT, KEY_THROTTLE_DOWN, "KEY_THROTTLE_DOWN", "Throttle Down" }, + { KeyMap::KEY_FLIGHT, KEY_THROTTLE_ZERO, "KEY_THROTTLE_ZERO", "All Stop" }, + { KeyMap::KEY_FLIGHT, KEY_THROTTLE_FULL, "KEY_THROTTLE_FULL", "Full Throttle" }, + { KeyMap::KEY_FLIGHT, KEY_AUGMENTER, "KEY_AUGMENTER", "Augmenter" }, + { KeyMap::KEY_FLIGHT, KEY_FLCS_MODE_AUTO, "KEY_FLCS_MODE_AUTO","FLCS Mode Toggle" }, + { KeyMap::KEY_FLIGHT, KEY_COMMAND_MODE, "KEY_COMMAND_MODE", "CMD Mode Toggle" }, + { KeyMap::KEY_FLIGHT, KEY_DROP_ORBIT, "KEY_DROP_ORBIT", "Break Orbit" }, + { KeyMap::KEY_FLIGHT, KEY_GEAR_TOGGLE, "KEY_GEAR_TOGGLE", "Landing Gear" }, + { KeyMap::KEY_FLIGHT, KEY_NAVLIGHT_TOGGLE, "KEY_NAVLIGHT_TOGGLE","Nav Lights" }, + + { KeyMap::KEY_WEAPONS, KEY_CYCLE_PRIMARY, "KEY_CYCLE_PRIMARY", "Cycle Primary" }, + { KeyMap::KEY_WEAPONS, KEY_CYCLE_SECONDARY, "KEY_CYCLE_SECONDARY", "Cycle Secondary" }, + + { KeyMap::KEY_VIEW, KEY_HUD_INST, "KEY_HUD_INST", "Instr. Display" }, + { KeyMap::KEY_VIEW, KEY_CAM_BRIDGE, "KEY_CAM_BRIDGE", "Bridge Cam" }, + { KeyMap::KEY_VIEW, KEY_CAM_VIRT, "KEY_CAM_VIRT", "Virtual Cockpit" }, + { KeyMap::KEY_VIEW, KEY_CAM_CHASE, "KEY_CAM_CHASE", "Chase Cam" }, + { KeyMap::KEY_VIEW, KEY_CAM_DROP, "KEY_CAM_DROP", "Drop Cam" }, + { KeyMap::KEY_VIEW, KEY_CAM_EXTERN, "KEY_CAM_EXTERN", "Orbit Cam" }, + { KeyMap::KEY_MISC, KEY_HUD_MODE, "KEY_HUD_MODE", "HUD Mode" }, + { KeyMap::KEY_MISC, KEY_HUD_COLOR, "KEY_HUD_COLOR", "HUD Color" }, + { KeyMap::KEY_MISC, KEY_HUD_WARN, "KEY_HUD_WARN", "Master Caution" }, + { KeyMap::KEY_MISC, KEY_NAV_DLG, "KEY_NAV_DLG", "NAV Window" }, + { KeyMap::KEY_MISC, KEY_WEP_DLG, "KEY_WEP_DLG", "TAC Overlay" }, + { KeyMap::KEY_MISC, KEY_FLT_DLG, "KEY_FLT_DLG", "FLT Window" }, + { KeyMap::KEY_MISC, KEY_ENG_DLG, "KEY_ENG_DLG", "ENG Window" }, + + { KeyMap::KEY_VIEW, KEY_ZOOM_WIDE, "KEY_ZOOM_WIDE", "Toggle Wide Angle" }, + { KeyMap::KEY_VIEW, KEY_ZOOM_IN, "KEY_ZOOM_IN", "Zoom In" }, + { KeyMap::KEY_VIEW, KEY_ZOOM_OUT, "KEY_ZOOM_OUT", "Zoom Out" }, + + { KeyMap::KEY_VIEW, KEY_CAM_VIRT_PLUS_AZ, "KEY_CAM_VIRT_PLUS_AZ", "Look Left" }, + { KeyMap::KEY_VIEW, KEY_CAM_VIRT_MINUS_AZ, "KEY_CAM_VIRT_MINUS_AZ", "Look Right" }, + { KeyMap::KEY_VIEW, KEY_CAM_VIRT_PLUS_EL, "KEY_CAM_VIRT_PLUS_EL", "Look Up" }, + { KeyMap::KEY_VIEW, KEY_CAM_VIRT_MINUS_EL, "KEY_CAM_VIRT_MINUS_EL", "Look Down" }, + { KeyMap::KEY_VIEW, KEY_CAM_CYCLE_OBJECT, "KEY_CAM_CYCLE_OBJECT", "View Next Object" }, + { KeyMap::KEY_VIEW, KEY_CAM_EXT_PLUS_AZ, "KEY_CAM_EXT_PLUS_AZ", "View Spin Left" }, + { KeyMap::KEY_VIEW, KEY_CAM_EXT_MINUS_AZ, "KEY_CAM_EXT_MINUS_AZ", "View Spin Right" }, + { KeyMap::KEY_VIEW, KEY_CAM_EXT_PLUS_EL, "KEY_CAM_EXT_PLUS_EL", "View Raise" }, + { KeyMap::KEY_VIEW, KEY_CAM_EXT_MINUS_EL, "KEY_CAM_EXT_MINUS_EL", "View Lower" }, + { KeyMap::KEY_VIEW, KEY_CAM_EXT_PLUS_RANGE, "KEY_CAM_EXT_PLUS_RANGE", "View Farther" }, + { KeyMap::KEY_VIEW, KEY_CAM_EXT_MINUS_RANGE,"KEY_CAM_EXT_MINUS_RANGE", "View Closer" }, + { KeyMap::KEY_VIEW, KEY_CAM_VIEW_SELECTION, "KEY_CAM_VIEW_SELECTION", "View Selection" }, + + { KeyMap::KEY_WEAPONS, KEY_TARGET_SELECTION, "KEY_TARGET_SELECTION", "Target Selection" }, + { KeyMap::KEY_MISC, KEY_RADIO_MENU, "KEY_RADIO_MENU", "Radio Call" }, + { KeyMap::KEY_MISC, KEY_QUANTUM_MENU, "KEY_QUANTUM_MENU", "Quantum Drive" }, + + { KeyMap::KEY_MISC, KEY_MFD1, "KEY_MFD1", "MFD 1" }, + { KeyMap::KEY_MISC, KEY_MFD2, "KEY_MFD2", "MFD 2" }, + { KeyMap::KEY_MISC, KEY_MFD3, "KEY_MFD3", "MFD 3" }, + { KeyMap::KEY_MISC, KEY_MFD4, "KEY_MFD4", "MFD 4" }, + { KeyMap::KEY_MISC, KEY_SELF_DESTRUCT, "KEY_SELF_DESTRUCT", "Self Destruct" }, + + { KeyMap::KEY_MISC, KEY_COMM_ATTACK_TGT, "KEY_COMM_ATTACK_TGT", "'Attack Tgt'" }, + { KeyMap::KEY_MISC, KEY_COMM_ESCORT_TGT, "KEY_COMM_ESCORT_TGT", "'Escort Tgt'" }, + { KeyMap::KEY_MISC, KEY_COMM_WEP_FREE, "KEY_COMM_WEP_FREE", "'Break & Attack'" }, + { KeyMap::KEY_MISC, KEY_COMM_WEP_HOLD, "KEY_COMM_WEP_HOLD", "'Form Up'" }, + { KeyMap::KEY_MISC, KEY_COMM_COVER_ME, "KEY_COMM_COVER_ME", "'Help Me Out!'" }, + { KeyMap::KEY_MISC, KEY_COMM_SKIP_NAV, "KEY_COMM_SKIP_NAV", "'Skip Navpoint'" }, + { KeyMap::KEY_MISC, KEY_COMM_RETURN_TO_BASE,"KEY_COMM_RETURN_TO_BASE", "'Return to Base'" }, + { KeyMap::KEY_MISC, KEY_COMM_CALL_INBOUND, "KEY_COMM_CALL_INBOUND", "'Call Inbound'" }, + { KeyMap::KEY_MISC, KEY_COMM_REQUEST_PICTURE, "KEY_COMM_REQUEST_PICTURE", "'Request Picture'" }, + { KeyMap::KEY_MISC, KEY_COMM_REQUEST_SUPPORT, "KEY_COMM_REQUEST_SUPPORT", "'Request Support'" }, + { KeyMap::KEY_MISC, KEY_CHAT_BROADCAST, "KEY_CHAT_BROADCAST", "Chat Broadcast" }, + { KeyMap::KEY_MISC, KEY_CHAT_TEAM, "KEY_CHAT_TEAM", "Chat Team" }, + { KeyMap::KEY_MISC, KEY_CHAT_WING, "KEY_CHAT_WING", "Chat Wingman" }, + { KeyMap::KEY_MISC, KEY_CHAT_UNIT, "KEY_CHAT_UNIT", "Chat Unit" }, + + { KeyMap::KEY_WEAPONS, KEY_SHIELDS_UP, "KEY_SHIELDS_UP", "Raise Shields" }, + { KeyMap::KEY_WEAPONS, KEY_SHIELDS_DOWN, "KEY_SHIELDS_DOWN", "Lower Shields" }, + { KeyMap::KEY_WEAPONS, KEY_SHIELDS_FULL, "KEY_SHIELDS_FULL", "Full Shields" }, + { KeyMap::KEY_WEAPONS, KEY_SHIELDS_ZERO, "KEY_SHIELDS_ZERO", "Zero Shields" }, + + { KeyMap::KEY_WEAPONS, KEY_SENSOR_MODE, "KEY_SENSOR_MODE", "Sensor Mode" }, + { KeyMap::KEY_WEAPONS, KEY_SENSOR_GROUND_MODE, "KEY_SENSOR_GROUND_MODE", "Sensor AGM" }, + { KeyMap::KEY_WEAPONS, KEY_SENSOR_BEAM, "KEY_SENSOR_BEAM", "Sensor Sweep Angle" }, + { KeyMap::KEY_WEAPONS, KEY_SENSOR_RANGE_PLUS, "KEY_SENSOR_RANGE_PLUS", "Inc Sensor Range" }, + { KeyMap::KEY_WEAPONS, KEY_SENSOR_RANGE_MINUS, "KEY_SENSOR_RANGE_MINUS", "Dec Sensor Range" }, + { KeyMap::KEY_WEAPONS, KEY_EMCON_PLUS, "KEY_EMCON_PLUS", "Inc EMCON Level" }, + { KeyMap::KEY_WEAPONS, KEY_EMCON_MINUS, "KEY_EMCON_MINUS", "Dec EMCON Level" }, + + { KeyMap::KEY_WEAPONS, KEY_DECOY, "KEY_DECOY", "Launch Decoy" }, + { KeyMap::KEY_WEAPONS, KEY_LAUNCH_PROBE, "KEY_LAUNCH_PROBE", "Launch Probe" }, + +}; + +static KeyName key_key_table[] = { + { 0, VK_ESCAPE, "VK_ESCAPE", "Escape" }, + { 0, VK_UP, "VK_UP", "Up" }, + { 0, VK_DOWN, "VK_DOWN", "Down" }, + { 0, VK_LEFT, "VK_LEFT", "Left" }, + { 0, VK_RIGHT, "VK_RIGHT", "Right" }, + { 0, VK_NEXT, "VK_NEXT", "Pg Down" }, + { 0, VK_PRIOR, "VK_PRIOR", "Pg Up" }, + { 0, VK_ADD, "VK_ADD", "+" }, + { 0, VK_SUBTRACT, "VK_SUBTRACT", "-" }, + { 0, VK_MULTIPLY, "VK_MULTIPLY", "*" }, + { 0, VK_DIVIDE, "VK_DIVIDE", "/" }, + { 0, VK_SPACE, "VK_SPACE", "Space" }, + { 0, VK_TAB, "VK_TAB", "Tab" }, + { 0, VK_RETURN, "VK_RETURN", "Enter" }, + { 0, VK_HOME, "VK_HOME", "Home" }, + { 0, VK_END, "VK_END", "End" }, + { 0, VK_SHIFT, "VK_SHIFT", "Shift" }, + { 0, VK_CONTROL, "VK_CONTROL", "Ctrl" }, + { 0, VK_MENU, "VK_MENU", "Alt" }, + + { 0, VK_DECIMAL, "VK_DECIMAL", "." }, + { 0, VK_SEPARATOR, "VK_SEPARATOR", "Separator" }, + { 0, VK_PAUSE, "VK_PAUSE", "Pause" }, + { 0, VK_BACK, "VK_BACK", "Backspace" }, + { 0, VK_INSERT, "VK_INSERT", "Insert" }, + { 0, VK_DELETE, "VK_DELETE", "Delete" }, + { 0, 20, "CAP", "CapsLock" }, + { 0, 144, "NUM", "NumLock" }, + { 0, 145, "SCROLL", "Scroll" }, + { 0, 44, "PRINT", "PrintScr" }, + + { 0, VK_NUMPAD0, "VK_NUMPAD0", "Num 0" }, + { 0, VK_NUMPAD1, "VK_NUMPAD1", "Num 1" }, + { 0, VK_NUMPAD2, "VK_NUMPAD2", "Num 2" }, + { 0, VK_NUMPAD3, "VK_NUMPAD3", "Num 3" }, + { 0, VK_NUMPAD4, "VK_NUMPAD4", "Num 4" }, + { 0, VK_NUMPAD5, "VK_NUMPAD5", "Num 5" }, + { 0, VK_NUMPAD6, "VK_NUMPAD6", "Num 6" }, + { 0, VK_NUMPAD7, "VK_NUMPAD7", "Num 7" }, + { 0, VK_NUMPAD8, "VK_NUMPAD8", "Num 8" }, + { 0, VK_NUMPAD9, "VK_NUMPAD9", "Num 9" }, + + { 0, VK_F1, "VK_F1", "F1" }, + { 0, VK_F2, "VK_F2", "F2" }, + { 0, VK_F3, "VK_F3", "F3" }, + { 0, VK_F4, "VK_F4", "F4" }, + { 0, VK_F5, "VK_F5", "F5" }, + { 0, VK_F6, "VK_F6", "F6" }, + { 0, VK_F7, "VK_F7", "F7" }, + { 0, VK_F8, "VK_F8", "F8" }, + { 0, VK_F9, "VK_F9", "F9" }, + { 0, VK_F10, "VK_F10", "F10" }, + { 0, VK_F11, "VK_F11", "F11" }, + { 0, VK_F12, "VK_F12", "F12" }, + + { 0, KEY_JOY_1, "KEY_JOY_1", "Joy 1" }, + { 0, KEY_JOY_2, "KEY_JOY_2", "Joy 2" }, + { 0, KEY_JOY_3, "KEY_JOY_3", "Joy 3" }, + { 0, KEY_JOY_4, "KEY_JOY_4", "Joy 4" }, + { 0, KEY_JOY_5, "KEY_JOY_5", "Joy 5" }, + { 0, KEY_JOY_6, "KEY_JOY_6", "Joy 6" }, + { 0, KEY_JOY_7, "KEY_JOY_7", "Joy 7" }, + { 0, KEY_JOY_8, "KEY_JOY_8", "Joy 8" }, + { 0, KEY_JOY_9, "KEY_JOY_9", "Joy 9" }, + { 0, KEY_JOY_10, "KEY_JOY_10", "Joy 10" }, + { 0, KEY_JOY_11, "KEY_JOY_11", "Joy 11" }, + { 0, KEY_JOY_12, "KEY_JOY_12", "Joy 12" }, + { 0, KEY_JOY_13, "KEY_JOY_13", "Joy 13" }, + { 0, KEY_JOY_14, "KEY_JOY_14", "Joy 14" }, + { 0, KEY_JOY_15, "KEY_JOY_15", "Joy 15" }, + { 0, KEY_JOY_16, "KEY_JOY_16", "Joy 16" }, + + { 0, KEY_POV_0_UP, "KEY_POV_0_UP", "Hat 1 Up" }, + { 0, KEY_POV_0_DOWN, "KEY_POV_0_DOWN", "Hat 1 Down" }, + { 0, KEY_POV_0_LEFT, "KEY_POV_0_LEFT", "Hat 1 Left" }, + { 0, KEY_POV_0_RIGHT, "KEY_POV_0_RIGHT", "Hat 1 Right" }, + { 0, KEY_POV_1_UP, "KEY_POV_1_UP", "Hat 2 Up" }, + { 0, KEY_POV_1_DOWN, "KEY_POV_1_DOWN", "Hat 2 Down" }, + { 0, KEY_POV_1_LEFT, "KEY_POV_1_LEFT", "Hat 2 Left" }, + { 0, KEY_POV_1_RIGHT, "KEY_POV_1_RIGHT", "Hat 2 Right" }, + { 0, KEY_POV_2_UP, "KEY_POV_2_UP", "Hat 3 Up" }, + { 0, KEY_POV_2_DOWN, "KEY_POV_2_DOWN", "Hat 3 Down" }, + { 0, KEY_POV_2_LEFT, "KEY_POV_2_LEFT", "Hat 3 Left" }, + { 0, KEY_POV_2_RIGHT, "KEY_POV_2_RIGHT", "Hat 3 Right" }, + { 0, KEY_POV_3_UP, "KEY_POV_3_UP", "Hat 4 Up" }, + { 0, KEY_POV_3_DOWN, "KEY_POV_3_DOWN", "Hat 4 Down" }, + { 0, KEY_POV_3_LEFT, "KEY_POV_3_LEFT", "Hat 4 Left" }, + { 0, KEY_POV_3_RIGHT, "KEY_POV_3_RIGHT", "Hat 4 Right" }, + + { 0, 186, ";", ";" }, + { 0, 188, ",", "<" }, + { 0, 190, ".", ">" }, + { 0, 191, "/", "/" }, + { 0, 192, "~", "~" }, + { 0, 219, "[", "[" }, + { 0, 220, "\\", "\\" }, + { 0, 221, "]", "]" }, + { 0, 222, "'", "'" }, + + { 0, '0', "0", "0" }, + { 0, '1', "1", "1" }, + { 0, '2', "2", "2" }, + { 0, '3', "3", "3" }, + { 0, '4', "4", "4" }, + { 0, '5', "5", "5" }, + { 0, '6', "6", "6" }, + { 0, '7', "7", "7" }, + { 0, '8', "8", "8" }, + { 0, '9', "9", "9" }, + + { 0, 'A', "A", "A" }, + { 0, 'B', "B", "B" }, + { 0, 'C', "C", "C" }, + { 0, 'D', "D", "D" }, + { 0, 'E', "E", "E" }, + { 0, 'F', "F", "F" }, + { 0, 'G', "G", "G" }, + { 0, 'H', "H", "H" }, + { 0, 'I', "I", "I" }, + { 0, 'J', "J", "J" }, + { 0, 'K', "K", "K" }, + { 0, 'L', "L", "L" }, + { 0, 'M', "M", "M" }, + { 0, 'N', "N", "N" }, + { 0, 'O', "O", "O" }, + { 0, 'P', "P", "P" }, + { 0, 'Q', "Q", "Q" }, + { 0, 'R', "R", "R" }, + { 0, 'S', "S", "S" }, + { 0, 'T', "T", "T" }, + { 0, 'U', "U", "U" }, + { 0, 'V', "V", "V" }, + { 0, 'W', "W", "W" }, + { 0, 'X', "X", "X" }, + { 0, 'Y', "Y", "Y" }, + { 0, 'Z', "Z", "Z" }, + +}; + +// +----------------------------------------------------------------------+ + +int KeyMap::GetKeyAction(const char* act_str) +{ + if (!act_str) return -1; + + int nactions = sizeof(key_action_table) / sizeof(KeyName); + + for (int i = 0; i < nactions; i++) + if (!stricmp(act_str, key_action_table[i].name)) + return key_action_table[i].key; + + return -1; +} + +// +----------------------------------------------------------------------+ + +int KeyMap::GetKeyActionIndex(int act) +{ + int nactions = sizeof(key_action_table) / sizeof(KeyName); + + for (int i = 0; i < nactions; i++) + if (key_action_table[i].key == act) + return i; + + return -1; +} + +// +----------------------------------------------------------------------+ + +int KeyMap::GetKeyKey(const char* key_str) +{ + if (!key_str) return 0; + + if (*key_str == '=') { + int value = 0; + sscanf(key_str, "=%d", &value); + return value; + } + + int nkeys = sizeof(key_key_table) / sizeof(KeyName); + + for (int i = 0; i < nkeys; i++) + if (!stricmp(key_str, key_key_table[i].name)) + return key_key_table[i].key; + + return 0; +} + +// +----------------------------------------------------------------------+ + +int KeyMap::GetKeyKeyIndex(int key) +{ + int nkeys = sizeof(key_key_table) / sizeof(KeyName); + + for (int i = 0; i < nkeys; i++) + if (key_key_table[i].key == key) + return i; + + return -1; +} + +// +----------------------------------------------------------------------+ + + +KeyMap::KeyMap() + : nkeys(0) +{ + int n = BuildDefaultKeyMap(); + DefaultKeyMap(n); +} + +KeyMap::~KeyMap() +{ } + +// +----------------------------------------------------------------------+ + +int +KeyMap::BuildDefaultKeyMap() +{ + int i = 0; + + defmap[i++] = KeyMapEntry(KEY_PITCH_UP, VK_DOWN); + defmap[i++] = KeyMapEntry(KEY_PITCH_DOWN, VK_UP); + defmap[i++] = KeyMapEntry(KEY_YAW_LEFT, VK_LEFT); + defmap[i++] = KeyMapEntry(KEY_YAW_RIGHT, VK_RIGHT); + defmap[i++] = KeyMapEntry(KEY_ROLL_LEFT, VK_NUMPAD7); + defmap[i++] = KeyMapEntry(KEY_ROLL_RIGHT, VK_NUMPAD9); + + defmap[i++] = KeyMapEntry(KEY_PLUS_X, 190, 0, KEY_POV_0_RIGHT); // . + defmap[i++] = KeyMapEntry(KEY_MINUS_X, 188, 0, KEY_POV_0_LEFT); // , + defmap[i++] = KeyMapEntry(KEY_PLUS_Y, VK_HOME); + defmap[i++] = KeyMapEntry(KEY_MINUS_Y, VK_END); + defmap[i++] = KeyMapEntry(KEY_PLUS_Z, VK_PRIOR, 0, KEY_POV_0_UP); + defmap[i++] = KeyMapEntry(KEY_MINUS_Z, VK_NEXT, 0, KEY_POV_0_DOWN); + + defmap[i++] = KeyMapEntry(KEY_ACTION_0, VK_CONTROL, 0, KEY_JOY_1); + defmap[i++] = KeyMapEntry(KEY_ACTION_1, VK_SPACE, 0, KEY_JOY_2); + + + defmap[i++] = KeyMapEntry(KEY_THROTTLE_UP, 'A'); + defmap[i++] = KeyMapEntry(KEY_THROTTLE_DOWN, 'Z'); + defmap[i++] = KeyMapEntry(KEY_THROTTLE_FULL, 'A', VK_SHIFT); + defmap[i++] = KeyMapEntry(KEY_THROTTLE_ZERO, 'Z', VK_SHIFT); + defmap[i++] = KeyMapEntry(KEY_AUGMENTER, VK_TAB); + defmap[i++] = KeyMapEntry(KEY_FLCS_MODE_AUTO, 'M'); + defmap[i++] = KeyMapEntry(KEY_COMMAND_MODE, 'M', VK_SHIFT); + + defmap[i++] = KeyMapEntry(KEY_CYCLE_PRIMARY, VK_BACK, VK_SHIFT); + defmap[i++] = KeyMapEntry(KEY_CYCLE_SECONDARY, VK_BACK); + defmap[i++] = KeyMapEntry(KEY_LOCK_TARGET, 'T', 0, KEY_JOY_3); + defmap[i++] = KeyMapEntry(KEY_LOCK_THREAT, 'T', VK_SHIFT); + defmap[i++] = KeyMapEntry(KEY_LOCK_CLOSEST_SHIP, 'U'); + defmap[i++] = KeyMapEntry(KEY_LOCK_CLOSEST_THREAT, 'U', VK_SHIFT); + defmap[i++] = KeyMapEntry(KEY_LOCK_HOSTILE_SHIP, 'Y'); + defmap[i++] = KeyMapEntry(KEY_LOCK_HOSTILE_THREAT, 'Y', VK_SHIFT); + defmap[i++] = KeyMapEntry(KEY_CYCLE_SUBTARGET, 186); + defmap[i++] = KeyMapEntry(KEY_PREV_SUBTARGET, 186, VK_SHIFT); + + defmap[i++] = KeyMapEntry(KEY_DECOY, 'D', 0, KEY_JOY_4); + defmap[i++] = KeyMapEntry(KEY_GEAR_TOGGLE, 'G'); + defmap[i++] = KeyMapEntry(KEY_NAVLIGHT_TOGGLE, 'L'); + + defmap[i++] = KeyMapEntry(KEY_AUTO_NAV, 'N', VK_SHIFT); + defmap[i++] = KeyMapEntry(KEY_DROP_ORBIT, 'O'); + + defmap[i++] = KeyMapEntry(KEY_SHIELDS_UP, 'S'); + defmap[i++] = KeyMapEntry(KEY_SHIELDS_DOWN, 'X'); + defmap[i++] = KeyMapEntry(KEY_SHIELDS_FULL, 'S', VK_SHIFT); + defmap[i++] = KeyMapEntry(KEY_SHIELDS_ZERO, 'X', VK_SHIFT); + + defmap[i++] = KeyMapEntry(KEY_SENSOR_MODE, VK_F5); + defmap[i++] = KeyMapEntry(KEY_SENSOR_GROUND_MODE, VK_F5, VK_SHIFT); + defmap[i++] = KeyMapEntry(KEY_LAUNCH_PROBE, VK_F6); + defmap[i++] = KeyMapEntry(KEY_SENSOR_RANGE_MINUS, VK_F7); + defmap[i++] = KeyMapEntry(KEY_SENSOR_RANGE_PLUS, VK_F8); + defmap[i++] = KeyMapEntry(KEY_EMCON_MINUS, VK_F9); + defmap[i++] = KeyMapEntry(KEY_EMCON_PLUS, VK_F10); + + defmap[i++] = KeyMapEntry(KEY_EXIT, VK_ESCAPE); + defmap[i++] = KeyMapEntry(KEY_PAUSE, VK_PAUSE); +// defmap[i++] = KeyMapEntry(KEY_NEXT_VIEW, VK_TAB); + defmap[i++] = KeyMapEntry(KEY_TIME_EXPAND, VK_DELETE); + defmap[i++] = KeyMapEntry(KEY_TIME_COMPRESS, VK_INSERT); + defmap[i++] = KeyMapEntry(KEY_TIME_SKIP, VK_INSERT, VK_SHIFT); + + defmap[i++] = KeyMapEntry(KEY_CAM_BRIDGE, VK_F1); + defmap[i++] = KeyMapEntry(KEY_CAM_VIRT, VK_F1, VK_SHIFT); + defmap[i++] = KeyMapEntry(KEY_CAM_CHASE, VK_F2); + defmap[i++] = KeyMapEntry(KEY_CAM_DROP, VK_F2, VK_SHIFT); + defmap[i++] = KeyMapEntry(KEY_CAM_EXTERN, VK_F3); + defmap[i++] = KeyMapEntry(KEY_TARGET_PADLOCK, VK_F4); + + defmap[i++] = KeyMapEntry(KEY_ZOOM_WIDE, 'K'); + defmap[i++] = KeyMapEntry(KEY_HUD_MODE, 'H'); + defmap[i++] = KeyMapEntry(KEY_HUD_COLOR, 'H', VK_SHIFT); + defmap[i++] = KeyMapEntry(KEY_HUD_WARN, 'C'); + defmap[i++] = KeyMapEntry(KEY_HUD_INST, 'I'); + defmap[i++] = KeyMapEntry(KEY_NAV_DLG, 'N'); + defmap[i++] = KeyMapEntry(KEY_WEP_DLG, 'W'); + defmap[i++] = KeyMapEntry(KEY_ENG_DLG, 'E'); + defmap[i++] = KeyMapEntry(KEY_FLT_DLG, 'F'); + defmap[i++] = KeyMapEntry(KEY_RADIO_MENU, 'R'); + defmap[i++] = KeyMapEntry(KEY_QUANTUM_MENU, 'Q'); + + defmap[i++] = KeyMapEntry(KEY_MFD1, 219); // [ + defmap[i++] = KeyMapEntry(KEY_MFD2, 221); // ] + defmap[i++] = KeyMapEntry(KEY_SELF_DESTRUCT, VK_ESCAPE, VK_SHIFT); + + defmap[i++] = KeyMapEntry(KEY_CAM_CYCLE_OBJECT, VK_TAB, VK_SHIFT); + defmap[i++] = KeyMapEntry(KEY_CAM_EXT_PLUS_AZ, VK_LEFT, VK_SHIFT); + defmap[i++] = KeyMapEntry(KEY_CAM_EXT_MINUS_AZ, VK_RIGHT, VK_SHIFT); + defmap[i++] = KeyMapEntry(KEY_CAM_EXT_PLUS_EL, VK_UP, VK_SHIFT); + defmap[i++] = KeyMapEntry(KEY_CAM_EXT_MINUS_EL, VK_DOWN, VK_SHIFT); + defmap[i++] = KeyMapEntry(KEY_CAM_EXT_PLUS_RANGE, VK_SUBTRACT); + defmap[i++] = KeyMapEntry(KEY_CAM_EXT_MINUS_RANGE, VK_ADD); + defmap[i++] = KeyMapEntry(KEY_CAM_VIEW_SELECTION, 'V'); + defmap[i++] = KeyMapEntry(KEY_CAM_VIRT_PLUS_AZ, VK_LEFT, VK_SHIFT); + defmap[i++] = KeyMapEntry(KEY_CAM_VIRT_MINUS_AZ, VK_RIGHT, VK_SHIFT); + defmap[i++] = KeyMapEntry(KEY_CAM_VIRT_PLUS_EL, VK_DOWN, VK_SHIFT); + defmap[i++] = KeyMapEntry(KEY_CAM_VIRT_MINUS_EL, VK_UP, VK_SHIFT); + + defmap[i++] = KeyMapEntry(KEY_SWAP_ROLL_YAW, 'J'); + + defmap[i++] = KeyMapEntry(KEY_COMM_ATTACK_TGT, 'A', VK_MENU); + defmap[i++] = KeyMapEntry(KEY_COMM_ESCORT_TGT, 'E', VK_MENU); + defmap[i++] = KeyMapEntry(KEY_COMM_WEP_FREE, 'B', VK_MENU); + defmap[i++] = KeyMapEntry(KEY_COMM_WEP_HOLD, 'F', VK_MENU); + defmap[i++] = KeyMapEntry(KEY_COMM_COVER_ME, 'H', VK_MENU); + defmap[i++] = KeyMapEntry(KEY_COMM_SKIP_NAV, 'N', VK_MENU); + defmap[i++] = KeyMapEntry(KEY_COMM_RETURN_TO_BASE, 'R', VK_MENU); + defmap[i++] = KeyMapEntry(KEY_COMM_CALL_INBOUND, 'I', VK_MENU); + defmap[i++] = KeyMapEntry(KEY_COMM_REQUEST_PICTURE,'P', VK_MENU); + defmap[i++] = KeyMapEntry(KEY_COMM_REQUEST_SUPPORT,'S', VK_MENU); + + defmap[i++] = KeyMapEntry(KEY_CHAT_BROADCAST, '1', VK_MENU); + defmap[i++] = KeyMapEntry(KEY_CHAT_TEAM, '2', VK_MENU); + defmap[i++] = KeyMapEntry(KEY_CHAT_WING, '3', VK_MENU); + defmap[i++] = KeyMapEntry(KEY_CHAT_UNIT, '4', VK_MENU); + + defmap[i++] = KeyMapEntry(KEY_CONTROL_MODEL, 0); + + defmap[i++] = KeyMapEntry(KEY_JOY_SELECT, 1); + defmap[i++] = KeyMapEntry(KEY_JOY_RUDDER, 0); + defmap[i++] = KeyMapEntry(KEY_JOY_THROTTLE, 1); + defmap[i++] = KeyMapEntry(KEY_JOY_SENSE, 1); + defmap[i++] = KeyMapEntry(KEY_JOY_DEAD_ZONE, 500); + + defmap[i++] = KeyMapEntry(KEY_MOUSE_SELECT, 1); + defmap[i++] = KeyMapEntry(KEY_MOUSE_SENSE, 10); + defmap[i++] = KeyMapEntry(KEY_MOUSE_ACTIVE, 192); // ~ key + +/*** For Debug Convenience Only: ***/ + defmap[i++] = KeyMapEntry(KEY_INC_STARDATE, VK_F11); + defmap[i++] = KeyMapEntry(KEY_DEC_STARDATE, VK_F11, VK_SHIFT); +/***/ + + return i; +} + +// +----------------------------------------------------------------------+ + +int +KeyMap::DefaultKeyMap(int max_keys) +{ + for (int i = 0; i < max_keys; i++) + map[i] = defmap[i]; + + nkeys = max_keys; + return nkeys; +} + +void +KeyMap::Bind(int a, int k, int s) +{ + if (!a) return; + + for (int i = 0; i < nkeys; i++) { + if (map[i].act == a) { + map[i].key = k; + map[i].alt = s; + + return; + } + } + + map[nkeys++] = KeyMapEntry(a, k, s); +} + +// +----------------------------------------------------------------------+ + +int +KeyMap::LoadKeyMap(const char* filename, int max_keys) +{ + FILE* f = fopen(filename, "r"); + if (!f) return nkeys; + + char line[256]; + + while (fgets(line, sizeof(line), f)) { + int act = -1, key = -1, alt = 0, joy = -1; + char act_str[128], key_str[128], alt_str[128], joy_str[128]; + + ZeroMemory(act_str, sizeof(act_str)); + ZeroMemory(key_str, sizeof(key_str)); + ZeroMemory(alt_str, sizeof(alt_str)); + ZeroMemory(joy_str, sizeof(joy_str)); + + sscanf(line, "%s %s %s %s", act_str, key_str, alt_str, joy_str); + + act = GetKeyAction(act_str); + key = GetKeyKey(key_str); + alt = GetKeyKey(alt_str); + joy = GetKeyKey(joy_str); + + if (act != -1 && key != -1) { + if (act == KEY_CONTROL_MODEL) + Ship::SetControlModel(key); + + int mapped = false; + + for (int i = 0; i < max_keys && !mapped; i++) { + if (map[i].act == act) { + Print(" Remapping: '%s' => %s(%d) %s(%d) %s(%d)\n", + act_str, key_str, key, alt_str, alt, joy_str, joy); + + map[i] = KeyMapEntry(act, key, alt, joy); + mapped = true; + } + } + + if (!mapped) { + Print(" Mapping: '%s' => %s(%d) %s(%d) %s(%d)\n", + act_str, key_str, key, alt_str, alt, joy_str, joy); + map[nkeys++] = KeyMapEntry(act, key, alt, joy); + } + } + + if (nkeys >= max_keys-1) { + Print(" Too many keys in configuration...\n"); + break; + } + } + + fclose(f); + return nkeys; +} + +// +----------------------------------------------------------------------+ + +int +KeyMap::SaveKeyMap(const char* filename, int max_keys) +{ + FILE* f = fopen(filename, "w"); + if (!f) return 0; + + for (int i = 0; i < nkeys; i++) { + if (map[i].act >= KEY_CONTROL_MODEL && + map[i].act <= KEY_AXIS_THROTTLE_INVERT) { + + int a = GetKeyActionIndex(map[i].act); + + fprintf(f, "%-24s =%d\n", key_action_table[a].name, map[i].key); + } + + else if (map[i] != defmap[i]) { + int a = GetKeyActionIndex(map[i].act); + int k = GetKeyKeyIndex(map[i].key); + int s = GetKeyKeyIndex(map[i].alt); + int j = GetKeyKeyIndex(map[i].joy); + + if (a > -1) { + if (j > -1) { + if (s > -1) { + fprintf(f, "%-24s %-16s %-16s %-16s\n", + key_action_table[a].name, + key_key_table[ k].name, + key_key_table[ s].name, + key_key_table[ j].name); + } + else if (k > -1) { + fprintf(f, "%-24s %-16s %-16s %-16s\n", + key_action_table[a].name, + key_key_table[ k].name, + "null", + key_key_table[ j].name); + } + else { + fprintf(f, "%-24s %-16s %-16s %-16s\n", + key_action_table[a].name, + "null", + "null", + key_key_table[ j].name); + } + } + else if (s > -1) { + fprintf(f, "%-24s %-16s %-16s\n", + key_action_table[a].name, + key_key_table[ k].name, + key_key_table[ s].name); + } + else if (k > -1) { + fprintf(f, "%-24s %-16s\n", + key_action_table[a].name, + key_key_table[ k].name); + } + } + } + } + + fclose(f); + return nkeys; +} + +// +----------------------------------------------------------------------+ + +int +KeyMap::GetCategory(int n) +{ + int nactions = sizeof(key_action_table) / sizeof(KeyName); + + for (int i = 0; i < nactions; i++) { + if (map[n].act == key_action_table[i].key) { + return key_action_table[i].category; + } + } + + return KEY_MISC; +} + +// +----------------------------------------------------------------------+ + +int +KeyMap::FindMapIndex(int act) +{ + for (int n = 0; n < nkeys; n++) + if (map[n].act == act) + return n; + return -1; +} + +// +----------------------------------------------------------------------+ + +const char* +KeyMap::DescribeAction(int n) +{ + int nactions = sizeof(key_action_table) / sizeof(KeyName); + + for (int i = 0; i < nactions; i++) { + if (map[n].act == key_action_table[i].key) { + if (key_action_table[i].desc) + return key_action_table[i].desc; + + return key_action_table[i].name; + } + } + + return 0; +} + +// +----------------------------------------------------------------------+ + +static char key_desc[32]; + +const char* +KeyMap::DescribeKey(int n) +{ + if (n >= 0 && n < 256) + return DescribeKey(map[n].key, map[n].alt, map[n].joy); + + return 0; +} + +const char* +KeyMap::DescribeKey(int vk, int shift, int j) +{ + const char* key = 0; + const char* alt = 0; + const char* joy = 0; + + int nkeys = sizeof(key_key_table) / sizeof(KeyName); + + for (int i = 0; i < nkeys; i++) { + if (vk > 0 && vk == key_key_table[i].key) { + if (key_key_table[i].desc) + key = key_key_table[i].desc; + else + key = key_key_table[i].name; + } + + if (shift > 0 && shift == key_key_table[i].key) { + if (key_key_table[i].desc) + alt = key_key_table[i].desc; + else + alt = key_key_table[i].name; + } + + if (j > 0 && j == key_key_table[i].key) { + if (key_key_table[i].desc) + joy = key_key_table[i].desc; + else + joy = key_key_table[i].name; + } + } + + if (key) { + if (alt) { + sprintf(key_desc, "%s+%s", alt, key); + } + else { + strcpy(key_desc, key); + } + + if (joy) { + strcat(key_desc, ", "); + strcat(key_desc, joy); + } + } + + else if (joy) { + strcpy(key_desc, joy); + } + + else { + sprintf(key_desc, "%d", vk); + } + + return key_desc; +} + +int +KeyMap::GetMappableVKey(int n) +{ + int nkeys = sizeof(key_key_table) / sizeof(KeyName); + + if (n >= 0 && n < nkeys) { + return key_key_table[n].key; + } + + return 0; +} \ No newline at end of file diff --git a/Stars45/KeyMap.h b/Stars45/KeyMap.h new file mode 100644 index 0000000..46821d6 --- /dev/null +++ b/Stars45/KeyMap.h @@ -0,0 +1,181 @@ +/* Project Starshatter 4.6 + Destroyer Studios LLC + Copyright © 1997-2006. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: KeyMap.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Keyboard Mapping +*/ + +#ifndef KeyMap_h +#define KeyMap_h + +#include "Types.h" +#include "Geometry.h" +#include "MotionController.h" + +// +--------------------------------------------------------------------+ + +class KeyMap +{ +public: + KeyMap(); + virtual ~KeyMap(); + + int DefaultKeyMap(int max_keys = 256); + int LoadKeyMap(const char* filename, int max_keys = 256); + int SaveKeyMap(const char* filename, int max_keys = 256); + + enum KEY_CATEGORY { KEY_FLIGHT, KEY_WEAPONS, KEY_VIEW, KEY_MISC }; + + int GetCategory(int i); + const char* DescribeAction(int i); + const char* DescribeKey(int i); + int FindMapIndex(int act); + + static const char* DescribeKey(int vk, int shift, int joy); + static int GetMappableVKey(int n); + + int GetNumKeys() { return nkeys; } + KeyMapEntry* GetMapping() { return map; } + KeyMapEntry* GetKeyMap(int i) { return &map[i]; } + KeyMapEntry* GetDefault(int i) { return &defmap[i]; } + + void Bind(int a, int k, int s); + + static int GetKeyAction(const char* act_str); + static int GetKeyActionIndex(int act); + static int GetKeyKey(const char* key_str); + static int GetKeyKeyIndex(int key); + +protected: + int BuildDefaultKeyMap(); + + KeyMapEntry map[256]; + KeyMapEntry defmap[256]; + int nkeys; +}; + +// +--------------------------------------------------------------------+ + + +const int KEY_EXIT = 0 + KEY_USER_BASE; +const int KEY_PAUSE = 1 + KEY_USER_BASE; +const int KEY_NEXT_VIEW = 2 + KEY_USER_BASE; +const int KEY_TARGET_PADLOCK = 3 + KEY_USER_BASE; +const int KEY_THREAT_PADLOCK = 4 + KEY_USER_BASE; +const int KEY_LOCK_TARGET = 5 + KEY_USER_BASE; +const int KEY_LOCK_THREAT = 6 + KEY_USER_BASE; +const int KEY_AUTO_NAV = 7 + KEY_USER_BASE; +const int KEY_TIME_COMPRESS = 8 + KEY_USER_BASE; +const int KEY_TIME_EXPAND = 9 + KEY_USER_BASE; +const int KEY_TIME_SKIP = 10 + KEY_USER_BASE; + +const int KEY_SWAP_ROLL_YAW = 11 + KEY_USER_BASE; + +const int KEY_THROTTLE_UP = 15 + KEY_USER_BASE; +const int KEY_THROTTLE_DOWN = 16 + KEY_USER_BASE; +const int KEY_THROTTLE_ZERO = 17 + KEY_USER_BASE; +const int KEY_THROTTLE_FULL = 18 + KEY_USER_BASE; +const int KEY_CYCLE_PRIMARY = 19 + KEY_USER_BASE; +const int KEY_CYCLE_SECONDARY = 20 + KEY_USER_BASE; +const int KEY_FLCS_MODE_AUTO = 21 + KEY_USER_BASE; +const int KEY_DROP_ORBIT = 22 + KEY_USER_BASE; + +const int KEY_HUD_INST = 23 + KEY_USER_BASE; +const int KEY_CAM_BRIDGE = 24 + KEY_USER_BASE; +const int KEY_CAM_CHASE = 25 + KEY_USER_BASE; +const int KEY_CAM_EXTERN = 26 + KEY_USER_BASE; +const int KEY_HUD_MODE = 27 + KEY_USER_BASE; +const int KEY_HUD_COLOR = 28 + KEY_USER_BASE; +const int KEY_HUD_WARN = 29 + KEY_USER_BASE; +const int KEY_NAV_DLG = 30 + KEY_USER_BASE; +const int KEY_WEP_DLG = 31 + KEY_USER_BASE; +const int KEY_FLT_DLG = 32 + KEY_USER_BASE; +const int KEY_ENG_DLG = 33 + KEY_USER_BASE; + +const int KEY_ZOOM_WIDE = 34 + KEY_USER_BASE; +const int KEY_ZOOM_IN = 35 + KEY_USER_BASE; +const int KEY_ZOOM_OUT = 36 + KEY_USER_BASE; +const int KEY_CAM_CYCLE_OBJECT = 37 + KEY_USER_BASE; +const int KEY_CAM_EXT_PLUS_AZ = 38 + KEY_USER_BASE; +const int KEY_CAM_EXT_MINUS_AZ = 39 + KEY_USER_BASE; +const int KEY_CAM_EXT_PLUS_EL = 40 + KEY_USER_BASE; +const int KEY_CAM_EXT_MINUS_EL = 41 + KEY_USER_BASE; +const int KEY_CAM_EXT_PLUS_RANGE = 42 + KEY_USER_BASE; +const int KEY_CAM_EXT_MINUS_RANGE = 43 + KEY_USER_BASE; +const int KEY_CAM_VIEW_SELECTION = 44 + KEY_USER_BASE; +const int KEY_CAM_DROP = 45 + KEY_USER_BASE; + +const int KEY_TARGET_SELECTION = 50 + KEY_USER_BASE; +const int KEY_RADIO_MENU = 51 + KEY_USER_BASE; +const int KEY_QUANTUM_MENU = 52 + KEY_USER_BASE; +const int KEY_MFD1 = 53 + KEY_USER_BASE; +const int KEY_MFD2 = 54 + KEY_USER_BASE; +const int KEY_MFD3 = 55 + KEY_USER_BASE; +const int KEY_MFD4 = 56 + KEY_USER_BASE; + +const int KEY_SENSOR_MODE = 60 + KEY_USER_BASE; +const int KEY_SENSOR_GROUND_MODE = 61 + KEY_USER_BASE; +const int KEY_SENSOR_BEAM = 62 + KEY_USER_BASE; +const int KEY_SENSOR_RANGE_PLUS = 63 + KEY_USER_BASE; +const int KEY_SENSOR_RANGE_MINUS = 64 + KEY_USER_BASE; +const int KEY_EMCON_PLUS = 65 + KEY_USER_BASE; +const int KEY_EMCON_MINUS = 66 + KEY_USER_BASE; + +const int KEY_SHIELDS_UP = 67 + KEY_USER_BASE; +const int KEY_SHIELDS_DOWN = 68 + KEY_USER_BASE; +const int KEY_SHIELDS_FULL = 69 + KEY_USER_BASE; +const int KEY_SHIELDS_ZERO = 70 + KEY_USER_BASE; +const int KEY_DECOY = 71 + KEY_USER_BASE; +const int KEY_ECM_TOGGLE = 72 + KEY_USER_BASE; +const int KEY_LAUNCH_PROBE = 73 + KEY_USER_BASE; +const int KEY_GEAR_TOGGLE = 74 + KEY_USER_BASE; + +const int KEY_LOCK_CLOSEST_SHIP = 75 + KEY_USER_BASE; +const int KEY_LOCK_CLOSEST_THREAT = 76 + KEY_USER_BASE; +const int KEY_LOCK_HOSTILE_SHIP = 77 + KEY_USER_BASE; +const int KEY_LOCK_HOSTILE_THREAT = 78 + KEY_USER_BASE; +const int KEY_CYCLE_SUBTARGET = 79 + KEY_USER_BASE; +const int KEY_PREV_SUBTARGET = 80 + KEY_USER_BASE; + +const int KEY_AUGMENTER = 81 + KEY_USER_BASE; +const int KEY_NAVLIGHT_TOGGLE = 82 + KEY_USER_BASE; + +const int KEY_CAM_VIRT = 85 + KEY_USER_BASE; +const int KEY_CAM_VIRT_PLUS_AZ = 86 + KEY_USER_BASE; +const int KEY_CAM_VIRT_MINUS_AZ = 87 + KEY_USER_BASE; +const int KEY_CAM_VIRT_PLUS_EL = 88 + KEY_USER_BASE; +const int KEY_CAM_VIRT_MINUS_EL = 89 + KEY_USER_BASE; + +const int KEY_COMM_ATTACK_TGT = 90 + KEY_USER_BASE; +const int KEY_COMM_ESCORT_TGT = 91 + KEY_USER_BASE; +const int KEY_COMM_WEP_FREE = 92 + KEY_USER_BASE; +const int KEY_COMM_WEP_HOLD = 93 + KEY_USER_BASE; +const int KEY_COMM_COVER_ME = 94 + KEY_USER_BASE; +const int KEY_COMM_SKIP_NAV = 95 + KEY_USER_BASE; +const int KEY_COMM_RETURN_TO_BASE = 96 + KEY_USER_BASE; +const int KEY_COMM_CALL_INBOUND = 97 + KEY_USER_BASE; +const int KEY_COMM_REQUEST_PICTURE = 98 + KEY_USER_BASE; +const int KEY_COMM_REQUEST_SUPPORT = 99 + KEY_USER_BASE; + +const int KEY_CHAT_BROADCAST = 100 + KEY_USER_BASE; +const int KEY_CHAT_TEAM = 101 + KEY_USER_BASE; +const int KEY_CHAT_WING = 102 + KEY_USER_BASE; +const int KEY_CHAT_UNIT = 103 + KEY_USER_BASE; + +const int KEY_COMMAND_MODE = 104 + KEY_USER_BASE; +const int KEY_SELF_DESTRUCT = 105 + KEY_USER_BASE; + +/*** For Debug Convenience Only: ***/ +const int KEY_INC_STARDATE = 120 + KEY_USER_BASE; +const int KEY_DEC_STARDATE = 121 + KEY_USER_BASE; +/***/ + +#endif KeyMap_h + diff --git a/Stars45/LandingGear.cpp b/Stars45/LandingGear.cpp new file mode 100644 index 0000000..61135ed --- /dev/null +++ b/Stars45/LandingGear.cpp @@ -0,0 +1,278 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: LandingGear.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + LandingGear System class +*/ + +#include "MemDebug.h" +#include "LandingGear.h" +#include "Ship.h" +#include "Sim.h" +#include "AudioConfig.h" + +#include "DataLoader.h" +#include "Physical.h" +#include "Scene.h" +#include "Sound.h" +#include "Game.h" + +static Sound* gear_transit_sound = 0; + +// +----------------------------------------------------------------------+ + +LandingGear::LandingGear() + : System(MISC_SYSTEM, 0, "Landing Gear", 1, 1, 1, 1), + state(GEAR_UP), transit(0), ngear(0), clearance(0) +{ + name = Game::GetText("sys.landing-gear"); + abrv = Game::GetText("sys.landing-gear.abrv"); + + for (int i = 0; i < MAX_GEAR; i++) { + models[i] = 0; + gear[i] = 0; + } +} + +// +----------------------------------------------------------------------+ + +LandingGear::LandingGear(const LandingGear& g) + : System(g), state(GEAR_UP), transit(0), ngear(g.ngear), clearance(0) +{ + Mount(g); + SetAbbreviation(g.Abbreviation()); + + for (int i = 0; i < ngear; i++) { + models[i] = 0; + gear[i] = new(__FILE__,__LINE__) Solid; + start[i] = g.start[i]; + end[i] = g.end[i]; + + gear[i]->UseModel(g.models[i]); + + if (clearance > end[i].y) + clearance = end[i].y; + } + + while (i < MAX_GEAR) { + models[i] = 0; + gear[i] = 0; + i++; + } + + clearance += mount_rel.y; +} + +// +--------------------------------------------------------------------+ + +LandingGear::~LandingGear() +{ + for (int i = 0; i < MAX_GEAR; i++) { + delete models[i]; + + if (gear[i]) { + Solid* g = gear[i]; + + if (g->GetScene()) + g->GetScene()->DelGraphic(g); + delete g; + } + } +} + +// +--------------------------------------------------------------------+ + +void +LandingGear::Initialize() +{ + if (!gear_transit_sound) { + DataLoader* loader = DataLoader::GetLoader(); + loader->SetDataPath("Sounds/"); + loader->LoadSound("GearTransit.wav", gear_transit_sound); + } +} + +// +--------------------------------------------------------------------+ + +void +LandingGear::Close() +{ + delete gear_transit_sound; + gear_transit_sound = 0; +} + +// +--------------------------------------------------------------------+ + +int +LandingGear::AddGear(Model* m, const Point& s, const Point& e) +{ + if (ngear < MAX_GEAR) { + models[ngear] = m; + start[ngear] = s; + end[ngear] = e; + + ngear++; + } + + return ngear; +} + +// +--------------------------------------------------------------------+ + +void +LandingGear::SetState(GEAR_STATE s) +{ + if (state != s) { + state = s; + + if (ship && ship == Sim::GetSim()->GetPlayerShip()) { + if (state == GEAR_LOWER || state == GEAR_RAISE) { + if (gear_transit_sound) { + Sound* sound = gear_transit_sound->Duplicate(); + sound->SetVolume(AudioConfig::EfxVolume()); + sound->Play(); + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +LandingGear::ExecFrame(double seconds) +{ + System::ExecFrame(seconds); + + switch (state) { + case GEAR_UP: + transit = 0; + break; + + case GEAR_DOWN: + transit = 1; + break; + + case GEAR_LOWER: + if (transit < 1) { + transit += seconds; + + Scene* s = 0; + if (ship && ship->Rep()) + s = ship->Rep()->GetScene(); + + if (s) { + for (int i = 0; i < ngear; i++) { + if (gear[i] && !gear[i]->GetScene()) { + s->AddGraphic(gear[i]); + } + } + } + } + else { + transit = 1; + state = GEAR_DOWN; + } + break; + + case GEAR_RAISE: + if (transit > 0) { + transit -= seconds; + } + else { + transit = 0; + state = GEAR_UP; + + for (int i = 0; i < ngear; i++) { + if (gear[i]) { + Scene* s = gear[i]->GetScene(); + if (s) s->DelGraphic(gear[i]); + } + } + } + break; + } +} + +// +--------------------------------------------------------------------+ + +void +LandingGear::Orient(const Physical* rep) +{ + System::Orient(rep); + + const Matrix& orientation = rep->Cam().Orientation(); + Point ship_loc = rep->Location(); + + for (int i = 0; i < ngear; i++) { + Point gloc; + if (transit < 1) gloc = start[i] + (end[i]-start[i])*transit; + else gloc = end[i]; + + Point projector = (gloc * orientation) + ship_loc; + if (gear[i]) { + gear[i]->MoveTo(projector); + gear[i]->SetOrientation(orientation); + } + } +} + +// +--------------------------------------------------------------------+ + +Solid* +LandingGear::GetGear(int index) +{ + if (index >= 0 && index < ngear) { + Solid* g = gear[index]; + return g; + } + + return 0; +} + +// +--------------------------------------------------------------------+ + +Point +LandingGear::GetGearStop(int index) +{ + if (index >= 0 && index < ngear) { + Solid* g = gear[index]; + + if (g) + return g->Location(); + } + + return Point(); +} + +// +--------------------------------------------------------------------+ + +double +LandingGear::GetTouchDown() +{ + double down = 0; + + if (ship) { + down = ship->Location().y; + + if (state != GEAR_UP) { + for (int i = 0; i < ngear; i++) { + if (gear[i]) { + Point stop = gear[i]->Location(); + + if (stop.y < down) + down = stop.y; + } + } + } + } + + return down; +} \ No newline at end of file diff --git a/Stars45/LandingGear.h b/Stars45/LandingGear.h new file mode 100644 index 0000000..30b18ac --- /dev/null +++ b/Stars45/LandingGear.h @@ -0,0 +1,66 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: LandingGear.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Fighter undercarriage (landing gear) system class +*/ + +#ifndef LandingGear_h +#define LandingGear_h + +#include "Types.h" +#include "System.h" +#include "Solid.h" + +// +--------------------------------------------------------------------+ + +class Ship; + +// +--------------------------------------------------------------------+ + +class LandingGear : public System +{ +public: + enum CONSTANTS { MAX_GEAR = 4 }; + enum GEAR_STATE { GEAR_UP, GEAR_LOWER, GEAR_DOWN, GEAR_RAISE }; + + LandingGear(); + LandingGear(const LandingGear& rhs); + virtual ~LandingGear(); + + virtual int AddGear(Model* m, const Point& s, const Point& e); + virtual void ExecFrame(double seconds); + virtual void Orient(const Physical* rep); + + GEAR_STATE GetState() const { return state; } + void SetState(GEAR_STATE s); + int NumGear() const { return ngear; } + Solid* GetGear(int i); + Point GetGearStop(int i); + double GetTouchDown(); + double GetClearance() const { return clearance; } + + static void Initialize(); + static void Close(); + +protected: + GEAR_STATE state; + double transit; + double clearance; + + int ngear; + Model* models[MAX_GEAR]; + Solid* gear[MAX_GEAR]; + Point start[MAX_GEAR]; + Point end[MAX_GEAR]; +}; + +#endif LandingGear_h + diff --git a/Stars45/LoadDlg.cpp b/Stars45/LoadDlg.cpp new file mode 100644 index 0000000..313aa22 --- /dev/null +++ b/Stars45/LoadDlg.cpp @@ -0,0 +1,77 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: LoadDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Loading progress dialog box +*/ + + +#include "MemDebug.h" +#include "LoadDlg.h" +#include "Starshatter.h" + +#include "Game.h" +#include "DataLoader.h" +#include "Video.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "ListBox.h" +#include "ComboBox.h" +#include "Slider.h" +#include "FormatUtil.h" + +// +--------------------------------------------------------------------+ + +LoadDlg::LoadDlg(Screen* s, FormDef& def) + : FormWindow(s, 0, 0, s->Width(), s->Height()), + progress(0), activity(0) +{ + Init(def); +} + +LoadDlg::~LoadDlg() +{ +} + +void +LoadDlg::RegisterControls() +{ + title = FindControl(100); + activity = FindControl(101); + progress = (Slider*) FindControl(102); +} + +// +--------------------------------------------------------------------+ + +void +LoadDlg::ExecFrame() +{ + Starshatter* stars = Starshatter::GetInstance(); + + if (stars) { + if (title) { + if (stars->GetGameMode() == Starshatter::CLOD_MODE || + stars->GetGameMode() == Starshatter::CMPN_MODE) + title->SetText(Game::GetText("LoadDlg.campaign")); + + else if (stars->GetGameMode() == Starshatter::MENU_MODE) + title->SetText(Game::GetText("LoadDlg.tac-ref")); + + else + title->SetText(Game::GetText("LoadDlg.mission")); + } + + activity->SetText(stars->GetLoadActivity()); + progress->SetValue(stars->GetLoadProgress()); + } +} + +// +--------------------------------------------------------------------+ + diff --git a/Stars45/LoadDlg.h b/Stars45/LoadDlg.h new file mode 100644 index 0000000..c33bb8d --- /dev/null +++ b/Stars45/LoadDlg.h @@ -0,0 +1,41 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: LoadDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Loading progress dialog box +*/ + +#ifndef LoadDlg_h +#define LoadDlg_h + +#include "Types.h" +#include "FormWindow.h" + +// +--------------------------------------------------------------------+ + +class LoadDlg : public FormWindow +{ +public: + LoadDlg(Screen* s, FormDef& def); + virtual ~LoadDlg(); + + virtual void RegisterControls(); + + // Operations: + virtual void ExecFrame(); + +protected: + ActiveWindow* title; + ActiveWindow* activity; + Slider* progress; +}; + +#endif LoadDlg_h + diff --git a/Stars45/LoadScreen.cpp b/Stars45/LoadScreen.cpp new file mode 100644 index 0000000..337a19d --- /dev/null +++ b/Stars45/LoadScreen.cpp @@ -0,0 +1,163 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars + FILE: LoadScreen.cpp + AUTHOR: John DiCamillo + +*/ + +#include "MemDebug.h" +#include "LoadScreen.h" +#include "LoadDlg.h" +#include "CmpLoadDlg.h" +#include "Starshatter.h" + +#include "Game.h" +#include "Video.h" +#include "Screen.h" +#include "FormDef.h" +#include "Window.h" +#include "ActiveWindow.h" +#include "Mouse.h" +#include "Color.h" +#include "Bitmap.h" +#include "Font.h" +#include "FontMgr.h" +#include "DataLoader.h" +#include "Resource.h" + +// +--------------------------------------------------------------------+ + +LoadScreen::LoadScreen() + : screen(0), load_dlg(0), cmp_load_dlg(0), isShown(false) +{ } + +LoadScreen::~LoadScreen() +{ + TearDown(); +} + +// +--------------------------------------------------------------------+ + +void +LoadScreen::Setup(Screen* s) +{ + if (!s) + return; + + screen = s; + + DataLoader* loader = DataLoader::GetLoader(); + loader->UseFileSystem(true); + + // create windows + FormDef load_def("LoadDlg", 0); + load_def.Load("LoadDlg"); + load_dlg = new(__FILE__,__LINE__) LoadDlg(screen, load_def); + + FormDef cmp_load_def("CmpLoadDlg", 0); + cmp_load_def.Load("CmpLoadDlg"); + cmp_load_dlg = new(__FILE__,__LINE__) CmpLoadDlg(screen, cmp_load_def); + + loader->UseFileSystem(Starshatter::UseFileSystem()); + ShowLoadDlg(); +} + +// +--------------------------------------------------------------------+ + +void +LoadScreen::TearDown() +{ + if (screen) { + if (load_dlg) screen->DelWindow(load_dlg); + if (cmp_load_dlg) screen->DelWindow(cmp_load_dlg); + } + + delete load_dlg; + delete cmp_load_dlg; + + load_dlg = 0; + cmp_load_dlg = 0; + screen = 0; +} + +// +--------------------------------------------------------------------+ + +void +LoadScreen::ExecFrame() +{ + Game::SetScreenColor(Color::Black); + + if (load_dlg && load_dlg->IsShown()) + load_dlg->ExecFrame(); + + if (cmp_load_dlg && cmp_load_dlg->IsShown()) + cmp_load_dlg->ExecFrame(); +} + +// +--------------------------------------------------------------------+ + +bool +LoadScreen::CloseTopmost() +{ + return false; +} + +void +LoadScreen::Show() +{ + if (!isShown) { + ShowLoadDlg(); + isShown = true; + } +} + +void +LoadScreen::Hide() +{ + if (isShown) { + HideLoadDlg(); + isShown = false; + } +} + +// +--------------------------------------------------------------------+ + +void +LoadScreen::ShowLoadDlg() +{ + if (load_dlg) load_dlg->Hide(); + if (cmp_load_dlg) cmp_load_dlg->Hide(); + + Starshatter* stars = Starshatter::GetInstance(); + + // show campaign load dialog if available and loading campaign + if (stars && cmp_load_dlg) { + if (stars->GetGameMode() == Starshatter::CLOD_MODE || + stars->GetGameMode() == Starshatter::CMPN_MODE) { + cmp_load_dlg->Show(); + Mouse::Show(false); + return; + } + } + + // otherwise, show regular load dialog + if (load_dlg) { + load_dlg->Show(); + Mouse::Show(false); + } +} + +// +--------------------------------------------------------------------+ + +void +LoadScreen::HideLoadDlg() +{ + if (load_dlg && load_dlg->IsShown()) + load_dlg->Hide(); + + if (cmp_load_dlg && cmp_load_dlg->IsShown()) + cmp_load_dlg->Hide(); +} diff --git a/Stars45/LoadScreen.h b/Stars45/LoadScreen.h new file mode 100644 index 0000000..39756e5 --- /dev/null +++ b/Stars45/LoadScreen.h @@ -0,0 +1,64 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: LoadScreen.h + AUTHOR: John DiCamillo + +*/ + +#ifndef LoadScreen_h +#define LoadScreen_h + +#include "Types.h" +#include "Bitmap.h" +#include "Screen.h" + +// +--------------------------------------------------------------------+ + +class LoadDlg; +class CmpLoadDlg; + +class Bitmap; +class DataLoader; +class Font; +class Screen; +class Video; +class VideoFactory; + +// +--------------------------------------------------------------------+ + +class LoadScreen +{ +public: + LoadScreen(); + virtual ~LoadScreen(); + + virtual void Setup(Screen* screen); + virtual void TearDown(); + virtual bool CloseTopmost(); + + virtual bool IsShown() const { return isShown; } + virtual void Show(); + virtual void Hide(); + + virtual void ShowLoadDlg(); + virtual void HideLoadDlg(); + virtual LoadDlg* GetLoadDlg() { return load_dlg; } + virtual CmpLoadDlg* GetCmpLoadDlg() { return cmp_load_dlg; } + + virtual void ExecFrame(); + +private: + Screen* screen; + LoadDlg* load_dlg; + CmpLoadDlg* cmp_load_dlg; + + bool isShown; +}; + +// +--------------------------------------------------------------------+ + +#endif LoadScreen_h + diff --git a/Stars45/Main.cpp b/Stars45/Main.cpp new file mode 100644 index 0000000..c71bd4d --- /dev/null +++ b/Stars45/Main.cpp @@ -0,0 +1,186 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: main.cpp + AUTHOR: John DiCamillo +*/ + + +#include "MemDebug.h" +#include "Starshatter.h" +#include "StarServer.h" +#include "Authorization.h" +#include "HUDView.h" + +#include "NetHost.h" +#include "NetAddr.h" +#include "NetLayer.h" +#include "NetBrokerClient.h" +#include "NetClient.h" +#include "HttpClient.h" + +#include "Color.h" +#include "DataLoader.h" +#include "Pcx.h" +#include "MachineInfo.h" +#include "Encrypt.h" +#include "FormatUtil.h" +#include "ParseUtil.h" +#include "Random.h" + +// +--------------------------------------------------------------------+ +// WinMain +// +--------------------------------------------------------------------+ + +extern FILE* ErrLog; +extern int VD3D_describe_things; +int dump_missions = 0; + +#ifdef STARSHATTER_DEMO_RELEASE +const char* versionInfo = "5.0 DEMO"; +#else +const char* versionInfo = "5.0.4"; +#endif + +static void PrintLogHeader() +{ + Text sTime = FormatTimeString(); + + Print("+====================================================================+\n"); + Print("| STARSHATTER %-25s%29s |\n", versionInfo, sTime.data()); + + Memory::SetLevel(Memory::MAXIMAL); + Memory::OpenLog(); + MachineInfo::DescribeMachine(); +} + +int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, + LPSTR lpCmdLine, int nCmdShow) +{ + int result = 0; + int test_mode = 0; + int do_server = 0; + + if (strstr(lpCmdLine, "-server")) + ErrLog = fopen("serverlog.txt", "w"); + else + ErrLog = fopen("errlog.txt", "w"); + + PrintLogHeader(); + + if (strstr(lpCmdLine, "-test")) { + Print(" Request TEST mode\n"); + test_mode = 1; + } + + if (strstr(lpCmdLine, "-fps")) { + HUDView::ShowFPS(true); + } + + if (strstr(lpCmdLine, "-dump")) { + Print(" Request dump dynamic missions\n"); + dump_missions = 1; + } + + if (strstr(lpCmdLine, "-lan")) { + Print(" Request LAN ONLY mode\n"); + NetBrokerClient::Disable(); + } + + if (strstr(lpCmdLine, "-server")) { + do_server = 1; + Print(" Request Standalone Server Mode\n"); + } + + char* d3dinfo = strstr(lpCmdLine, "-d3d"); + if (d3dinfo) { + int n = d3dinfo[4] - '0'; + + if (n >= 0 && n <= 5) + VD3D_describe_things = n; + + Print(" D3D Info Level: %d\n", VD3D_describe_things); + } + else { + VD3D_describe_things = 0; + } + + + // FREE VERSION - AUTHORIZATION DISABLED + /* + ::Print(" Checking authorization codes...\n"); + if (!Authorization::IsUserAuthorized()) { + if (!DataLoader::GetLoader()) { + DataLoader::Initialize(); + DataLoader::GetLoader()->EnableDatafile("content.dat"); + } + + Game* game = new Game(); + game->InitContent(); + + MessageBox(0, FormatTextEscape(Game::GetText("main.auth-invalid")).data(), + Game::GetText("main.title.error").data(), MB_OK); + ::Print(" Not authorized.\n"); + + delete game; + DataLoader::Close(); + } + else { + ::Print(" Authorized\n"); + */ + try { + NetLayer net; + + if (do_server) { + StarServer* server = new(__FILE__,__LINE__) StarServer(); + + if (server->Init(hInstance, hPrevInstance, lpCmdLine, nCmdShow)) + result = server->Run(); + + Print("\n+====================================================================+\n"); + Print(" Begin Shutdown...\n"); + + delete server; + } + + else { + Starshatter* stars = 0; + + stars = new(__FILE__,__LINE__) Starshatter; + stars->SetTestMode(test_mode); + + if (stars->Init(hInstance, hPrevInstance, lpCmdLine, nCmdShow)) + result = stars->Run(); + + Print("\n+====================================================================+\n"); + Print(" Begin Shutdown...\n"); + + delete stars; + } + + Token::close(); + + if (*Game::GetPanicMessage()) + MessageBox(0, Game::GetPanicMessage(), "Starshatter - Error", MB_OK); + } + + catch (const char* msg) { + Print(" FATAL EXCEPTION: '%s'\n", msg); + } + /* } */ + + Memory::Stats(); + Memory::DumpLeaks(); + Memory::CloseLog(); + + Print("+====================================================================+\n"); + Print(" END OF LINE.\n"); + + fclose(ErrLog); + + return result; +} + + diff --git a/Stars45/MapView.cpp b/Stars45/MapView.cpp new file mode 100644 index 0000000..1c65d6e --- /dev/null +++ b/Stars45/MapView.cpp @@ -0,0 +1,3478 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: MapView.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Star Map class +*/ + +#include "MemDebug.h" +#include "MapView.h" + +#include "Galaxy.h" +#include "StarSystem.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "Instruction.h" +#include "Element.h" +#include "NavAI.h" +#include "Weapon.h" +#include "Sim.h" +#include "Mission.h" +#include "Campaign.h" +#include "Combatant.h" +#include "CombatGroup.h" +#include "CombatUnit.h" +#include "Contact.h" +#include "MenuView.h" + +#include "NetLobby.h" +#include "NetUtil.h" + +#include "Game.h" +#include "DataLoader.h" +#include "EventDispatch.h" +#include "Video.h" +#include "Button.h" +#include "Bitmap.h" +#include "Font.h" +#include "FontMgr.h" +#include "Mouse.h" +#include "FormatUtil.h" +#include "Menu.h" + +// +--------------------------------------------------------------------+ + +// Supported Selection Modes: + +const int SELECT_NONE = -1; +const int SELECT_SYSTEM = 0; +const int SELECT_PLANET = 1; +const int SELECT_REGION = 2; +const int SELECT_STATION = 3; +const int SELECT_STARSHIP = 4; +const int SELECT_FIGHTER = 5; +const int SELECT_NAVPT = 6; + +const int VIEW_GALAXY = 0; +const int VIEW_SYSTEM = 1; +const int VIEW_REGION = 2; + +// +--------------------------------------------------------------------+ + +MapView::MapView(Window* win) + : View(win) + , system(0), zoom(1.1), offset_x(0), offset_y(0), ship(0), campaign(0) + , captured(false), dragging(false), adding_navpt(false) + , moving_navpt(false), moving_elem(false) + , view_mode(VIEW_SYSTEM), seln_mode(SELECT_REGION), ship_filter(0xffffffff) + , current_star(0), current_planet(0), current_region(0) + , current_ship(0), current_elem(0), current_navpt(0), mission(0) + , scrolling(0), scroll_x(0), scroll_y(0), click_x(0), click_y(0) + , active_menu(0), map_menu(0), map_system_menu(0), map_sector_menu(0) + , ship_menu(0), editor(false) + , nav_menu(0), action_menu(0), objective_menu(0), formation_menu(0), speed_menu(0) + , hold_menu(0), farcast_menu(0), menu_view(0) +{ + for (int i = 0; i < 3; i++) { + view_zoom[i] = zoom; + view_offset_x[i] = offset_x; + view_offset_y[i] = offset_y; + } + + menu_view = new(__FILE__,__LINE__) MenuView(window); + + title_font = FontMgr::Find("Limerick12"); + font = FontMgr::Find("Verdana"); + + active_window = (ActiveWindow*) window; + + if (active_window) + active_window->AddView(this); +} + +// +--------------------------------------------------------------------+ + +MapView::~MapView() +{ + ClearMenu(); + galaxy_image.ClearImage(); + + delete menu_view; +} + +// +--------------------------------------------------------------------+ + +void +MapView::OnWindowMove() +{ + if (menu_view) + menu_view->OnWindowMove(); +} + +// +--------------------------------------------------------------------+ + +void +MapView::SetGalaxy(List& g) +{ + system_list.clear(); + system_list.append(g); + + if (system_list.size() > 0) { + SetSystem(system_list[0]); + } +} + +void +MapView::SetSystem(StarSystem* s) +{ + if (system != s) { + system = s; + + // forget invalid selection: + current_star = 0; + current_planet = 0; + current_region = 0; + current_ship = 0; + current_elem = 0; + current_navpt = 0; + + // flush old object pointers: + stars.clear(); + planets.clear(); + regions.clear(); + + // insert objects from star system: + if (system) { + ListIter star = system->Bodies(); + while (++star) { + switch (star->Type()) { + case Orbital::STAR: stars.append(star.value()); + break; + case Orbital::PLANET: + case Orbital::MOON: planets.append(star.value()); + break; + } + } + + ListIter planet = star->Satellites(); + while (++planet) { + planets.append(planet.value()); + + ListIter moon = planet->Satellites(); + while (++moon) { + planets.append(moon.value()); + } + } + + ListIter rgn = system->AllRegions(); + while (++rgn) + regions.append(rgn.value()); + + // sort region list by distance from the star: + regions.sort(); + } + + BuildMenu(); + } +} + +// +--------------------------------------------------------------------+ + +void +MapView::SetShip(Ship* s) +{ + if (ship != s) { + ship = s; + + // forget invalid selection: + current_star = 0; + current_planet = 0; + current_region = 0; + current_ship = 0; + current_elem = 0; + current_navpt = 0; + + if (ship && system_list.size() > 0) { + SimRegion* rgn = ship->GetRegion(); + + if (rgn && rgn->System()) { + system = 0; + SetSystem(rgn->System()); + } + } + + BuildMenu(); + } +} + +// +--------------------------------------------------------------------+ + +void +MapView::SetMission(Mission* m) +{ + if (mission != m) { + mission = m; + + // forget invalid selection: + current_star = 0; + current_planet = 0; + current_region = 0; + current_ship = 0; + current_elem = 0; + current_navpt = 0; + + if (mission && system_list.size() > 0) { + system = 0; + SetSystem(mission->GetStarSystem()); + } + + BuildMenu(); + } +} + +// +--------------------------------------------------------------------+ + +void +MapView::SetCampaign(Campaign* c) +{ + if (campaign != c) { + campaign = c; + + // forget invalid selection: + current_star = 0; + current_planet = 0; + current_region = 0; + current_ship = 0; + current_elem = 0; + current_navpt = 0; + + if (campaign) + SetGalaxy(campaign->GetSystemList()); + } +} + +// +--------------------------------------------------------------------+ + +enum MapView_MENU { + MAP_SYSTEM = 1000, + MAP_SECTOR = 2000, + MAP_SHIP = 3000, + MAP_NAV = 4000, + MAP_ADDNAV = 4001, + MAP_DELETE = 4002, + MAP_CLEAR = 4003, + MAP_ACTION = 5000, + MAP_FORMATION = 6000, + MAP_SPEED = 7000, + MAP_HOLD = 8000, + MAP_FARCAST = 8500, + MAP_OBJECTIVE = 9000 +}; + +void +MapView::BuildMenu() +{ + ClearMenu(); + + map_system_menu = new(__FILE__,__LINE__) Menu(Game::GetText("MapView.menu.STARSYSTEM")); + + if (system_list.size() > 0) { + int i = 0; + ListIter iter = system_list; + while (++iter) { + StarSystem* s = iter.value(); + map_system_menu->AddItem(s->Name(), MAP_SYSTEM + i); + i++; + } + } + + else if (system) { + map_system_menu->AddItem(system->Name(), MAP_SYSTEM); + } + + map_sector_menu = new(__FILE__,__LINE__) Menu(Game::GetText("MapView.menu.SECTOR")); + for (int i = 0; i < regions.size(); i++) { + Orbital* rgn = regions[i]; + map_sector_menu->AddItem(rgn->Name(), MAP_SECTOR + i); + } + + map_menu = new(__FILE__,__LINE__) Menu(Game::GetText("MapView.menu.MAP")); + map_menu->AddMenu("System", map_system_menu); + map_menu->AddMenu("Sector", map_sector_menu); + + if (ship || mission) { + ship_menu = new(__FILE__,__LINE__) Menu(Game::GetText("MapView.menu.SHIP")); + ship_menu->AddMenu(Game::GetText("MapView.item.Starsystem"), map_system_menu); + ship_menu->AddMenu(Game::GetText("MapView.item.Sector"), map_sector_menu); + + ship_menu->AddItem("", 0); + ship_menu->AddItem(Game::GetText("MapView.item.Add-Nav"), MAP_ADDNAV); + ship_menu->AddItem(Game::GetText("MapView.item.Clear-All"), MAP_CLEAR); + + action_menu = new(__FILE__,__LINE__) Menu(Game::GetText("MapView.menu.ACTION")); + for (i = 0; i < Instruction::NUM_ACTIONS; i++) { + action_menu->AddItem(Game::GetText(Text("MapView.item.") + Instruction::ActionName(i)), MAP_ACTION + i); + } + + formation_menu = new(__FILE__,__LINE__) Menu(Game::GetText("MapView.menu.FORMATION")); + for (i = 0; i < Instruction::NUM_FORMATIONS; i++) { + formation_menu->AddItem(Game::GetText(Text("MapView.item.") + Instruction::FormationName(i)), MAP_FORMATION + i); + } + + speed_menu = new(__FILE__,__LINE__) Menu(Game::GetText("MapView.menu.SPEED")); + speed_menu->AddItem("250", MAP_SPEED + 0); + speed_menu->AddItem("500", MAP_SPEED + 1); + speed_menu->AddItem("750", MAP_SPEED + 2); + speed_menu->AddItem("1000", MAP_SPEED + 3); + + hold_menu = new(__FILE__,__LINE__) Menu(Game::GetText("MapView.menu.HOLD")); + hold_menu->AddItem(Game::GetText("MapView.item.None"), MAP_HOLD + 0); + hold_menu->AddItem(Game::GetText("MapView.item.1-Minute"), MAP_HOLD + 1); + hold_menu->AddItem(Game::GetText("MapView.item.5-Minutes"), MAP_HOLD + 2); + hold_menu->AddItem(Game::GetText("MapView.item.10-Minutes"), MAP_HOLD + 3); + hold_menu->AddItem(Game::GetText("MapView.item.15-Minutes"), MAP_HOLD + 4); + + farcast_menu = new(__FILE__,__LINE__) Menu(Game::GetText("MapView.menu.FARCAST")); + farcast_menu->AddItem(Game::GetText("MapView.item.Use-Quantum"), MAP_FARCAST + 0); + farcast_menu->AddItem(Game::GetText("MapView.item.Use-Farcast"), MAP_FARCAST + 1); + + objective_menu = new(__FILE__,__LINE__) Menu(Game::GetText("MapView.menu.OBJECTIVE")); + + nav_menu = new(__FILE__,__LINE__) Menu(Game::GetText("MapView.menu.NAVPT")); + nav_menu->AddMenu(Game::GetText("MapView.item.Action"), action_menu); + nav_menu->AddMenu(Game::GetText("MapView.item.Objective"), objective_menu); + nav_menu->AddMenu(Game::GetText("MapView.item.Formation"), formation_menu); + nav_menu->AddMenu(Game::GetText("MapView.item.Speed"), speed_menu); + nav_menu->AddMenu(Game::GetText("MapView.item.Hold"), hold_menu); + nav_menu->AddMenu(Game::GetText("MapView.item.Farcast"), farcast_menu); + nav_menu->AddItem("", 0); + nav_menu->AddItem(Game::GetText("MapView.item.Add-Nav"), MAP_ADDNAV); + nav_menu->AddItem(Game::GetText("MapView.item.Del-Nav"), MAP_DELETE); + } + + else if (campaign) { + ship_menu = 0; + speed_menu = 0; + hold_menu = 0; + farcast_menu = 0; + objective_menu = 0; + formation_menu = 0; + nav_menu = 0; + } + + active_menu = map_menu; +} + +// +--------------------------------------------------------------------+ + +void +MapView::ClearMenu() +{ + delete map_menu; + delete map_system_menu; + delete map_sector_menu; + delete ship_menu; + delete nav_menu; + delete action_menu; + delete objective_menu; + delete formation_menu; + delete speed_menu; + delete hold_menu; + delete farcast_menu; + + map_menu = 0; + map_system_menu = 0; + map_sector_menu = 0; + ship_menu = 0; + nav_menu = 0; + action_menu = 0; + objective_menu = 0; + formation_menu = 0; + speed_menu = 0; + hold_menu = 0; + farcast_menu = 0; +} + +// +--------------------------------------------------------------------+ + +void +MapView::ProcessMenuItem(int action) +{ + bool send_nav_data = false; + bool can_command = true; + + if (ship && current_ship && ship != current_ship) { + if (ship->GetElement() && current_ship->GetElement()) { + if (!ship->GetElement()->CanCommand(current_ship->GetElement())) { + can_command = false; + } + } + } + + else if (current_elem && NetLobby::GetInstance()) { + can_command = false; + } + + if (action >= MAP_OBJECTIVE) { + int index = action - MAP_OBJECTIVE; + + if (current_navpt && can_command) { + current_navpt->SetTarget(objective_menu->GetItem(index)->GetText()); + send_nav_data = true; + } + } + + else if (action >= MAP_FARCAST) { + if (current_navpt && can_command) { + current_navpt->SetFarcast(action - MAP_FARCAST); + send_nav_data = true; + } + } + + else if (action >= MAP_HOLD) { + int hold_time = 0; + switch (action) { + default: + case MAP_HOLD + 0: hold_time = 0; break; + case MAP_HOLD + 1: hold_time = 60; break; + case MAP_HOLD + 2: hold_time = 300; break; + case MAP_HOLD + 3: hold_time = 600; break; + case MAP_HOLD + 4: hold_time = 900; break; + } + + if (current_navpt && can_command) { + current_navpt->SetHoldTime(hold_time); + send_nav_data = true; + } + } + + else if (action >= MAP_SPEED) { + if (current_navpt && can_command) { + current_navpt->SetSpeed((action - MAP_SPEED + 1) * 250); + send_nav_data = true; + } + } + + else if (action >= MAP_FORMATION) { + if (current_navpt && can_command) { + current_navpt->SetFormation(action - MAP_FORMATION); + send_nav_data = true; + } + } + + else if (action >= MAP_ACTION) { + if (current_navpt && can_command) { + current_navpt->SetAction(action - MAP_ACTION); + SelectNavpt(current_navpt); + send_nav_data = true; + } + } + + else if (action == MAP_ADDNAV) { + Text rgn_name = regions[current_region]->Name(); + Instruction* prior = current_navpt; + Instruction* n = 0; + + if (current_ship && can_command) { + Sim* sim = Sim::GetSim(); + SimRegion* rgn = sim->FindRegion(rgn_name); + Point init_pt; + + if (rgn) { + if (rgn->IsAirSpace()) + init_pt.z = 10e3; + + n = new(__FILE__,__LINE__) Instruction(rgn, init_pt); + } + else { + n = new(__FILE__,__LINE__) Instruction(rgn_name, init_pt); + } + + n->SetSpeed(500); + + if (prior) { + n->SetAction(prior->Action()); + n->SetFormation(prior->Formation()); + n->SetSpeed(prior->Speed()); + n->SetTarget(prior->GetTarget()); + } + + current_ship->AddNavPoint(n, prior); + } + + else if (current_elem && can_command) { + Point init_pt; + + if (regions[current_region]->Type() == Orbital::TERRAIN) + init_pt.z = 10e3; + + n = new(__FILE__,__LINE__) Instruction(rgn_name, init_pt); + n->SetSpeed(500); + + if (prior) { + n->SetAction(prior->Action()); + n->SetFormation(prior->Formation()); + n->SetSpeed(prior->Speed()); + n->SetTarget(prior->GetTarget()); + } + + current_elem->AddNavPoint(n, prior); + } + + if (can_command) { + current_navpt = n; + current_status = Instruction::PENDING; + adding_navpt = true; + captured = SetCapture(); + } + } + + else if (action == MAP_DELETE) { + if (current_navpt && can_command) { + if (current_ship) + current_ship->DelNavPoint(current_navpt); + else if (current_elem && can_command) + current_elem->DelNavPoint(current_navpt); + + SelectNavpt(0); + } + } + + else if (action == MAP_CLEAR) { + if (current_ship && can_command) + current_ship->ClearFlightPlan(); + else if (current_elem && can_command) + current_elem->ClearFlightPlan(); + + SelectNavpt(0); + } + + else if (action >= MAP_NAV) { + } + + else if (action >= MAP_SHIP) { + } + + else if (action >= MAP_SECTOR) { + int index = action - MAP_SECTOR; + + if (index < regions.size()) + current_region = index; + + if (view_mode == VIEW_SYSTEM) { + Orbital* s = regions[current_region]; + SetupScroll(s); + } + } + + else if (system_list.size() > 0 && action >= MAP_SYSTEM) { + int index = action - MAP_SYSTEM; + + if (index < system_list.size()) + SetSystem(system_list[index]); + } + + else { + } + + Sim* sim = Sim::GetSim(); + if (send_nav_data && sim) { + Ship* s = current_ship; + + if (s && s->GetElement()) { + Element* elem = s->GetElement(); + int index = elem->GetNavIndex(current_navpt); + + if (index >= 0) + NetUtil::SendNavData(false, elem, index-1, current_navpt); + } + } +} + +// +--------------------------------------------------------------------+ + +bool +MapView::SetCapture() +{ + EventDispatch* dispatch = EventDispatch::GetInstance(); + if (dispatch) + return dispatch->CaptureMouse(this) ? true : false; + + return 0; +} + +// +--------------------------------------------------------------------+ + +bool +MapView::ReleaseCapture() +{ + EventDispatch* dispatch = EventDispatch::GetInstance(); + if (dispatch) + return dispatch->ReleaseMouse(this) ? true : false; + + return 0; +} + +// +--------------------------------------------------------------------+ + +void +MapView::SetViewMode(int mode) +{ + if (mode >= 0 && mode < 3) { + // save state: + view_zoom[view_mode] = zoom; + view_offset_x[view_mode] = offset_x; + view_offset_y[view_mode] = offset_y; + + // switch mode: + view_mode = mode; + + // restore state: + + if (view_mode == VIEW_GALAXY) { + zoom = 1; + offset_x = 0; + offset_y = 0; + } + else { + zoom = view_zoom[view_mode]; + offset_x = view_offset_x[view_mode]; + offset_y = view_offset_y[view_mode]; + } + + scrolling = 0; + scroll_x = 0; + scroll_y = 0; + } +} + +// +--------------------------------------------------------------------+ + +bool +MapView::Update(SimObject* obj) +{ + if (obj == current_ship) { + current_ship = 0; + active_menu = map_menu; + } + + return SimObserver::Update(obj); +} + +// +--------------------------------------------------------------------+ + +void +MapView::SelectShip(Ship* selship) +{ + if (selship != current_ship) { + current_ship = selship; + + if (current_ship) { + if (current_ship->Life() == 0 || current_ship->IsDying() || current_ship->IsDead()) { + current_ship = 0; + } + else { + Observe(current_ship); + } + } + } + + SelectNavpt(0); +} + +// +--------------------------------------------------------------------+ + +void +MapView::SelectElem(MissionElement* elem) +{ + if (elem != current_elem) { + current_elem = elem; + + if (current_elem) { + if (current_elem->IsStarship()) { + ship_menu->GetItem(3)->SetEnabled(true); + } + + else if (current_elem->IsDropship()) { + ship_menu->GetItem(3)->SetEnabled(true); + } + + else { + ship_menu->GetItem(3)->SetEnabled(false); + } + } + } + + SelectNavpt(0); +} + +// +--------------------------------------------------------------------+ + +void +MapView::SelectNavpt(Instruction* navpt) +{ + current_navpt = navpt; + + if (current_navpt) { + current_status = current_navpt->Status(); + + List ships; + objective_menu->ClearItems(); + + switch (current_navpt->Action()) { + case Instruction::VECTOR: + case Instruction::LAUNCH: + case Instruction::PATROL: + case Instruction::SWEEP: + case Instruction::RECON: + objective_menu->AddItem(Game::GetText("MapView.item.not-available"), 0); + objective_menu->GetItem(0)->SetEnabled(false); + break; + + case Instruction::DOCK: + FindShips(true, true, true, false, ships); + break; + + case Instruction::DEFEND: + FindShips(true, true, true, false, ships); + break; + + case Instruction::ESCORT: + FindShips(true, false, true, true, ships); + break; + + case Instruction::INTERCEPT: + FindShips(false, false, false, true, ships); + break; + + case Instruction::ASSAULT: + FindShips(false, false, true, false, ships); + break; + + case Instruction::STRIKE: + FindShips(false, true, false, false, ships); + break; + } + + for (int i = 0; i < ships.size(); i++) + objective_menu->AddItem(ships[i]->data(), MAP_OBJECTIVE + i); + + ships.destroy(); + } + else { + objective_menu->ClearItems(); + objective_menu->AddItem(Game::GetText("MapView.item.not-available"), 0); + objective_menu->GetItem(0)->SetEnabled(false); + } +} + +// +--------------------------------------------------------------------+ + +void +MapView::FindShips(bool friendly, bool station, bool starship, bool dropship, + List& result) +{ + if (mission) { + for (int i = 0; i < mission->GetElements().size(); i++) { + MissionElement* elem = mission->GetElements().at(i); + + if (elem->IsSquadron()) continue; + if (!station && elem->IsStatic()) continue; + if (!starship && elem->IsStarship()) continue; + if (!dropship && elem->IsDropship()) continue; + + if (!editor && friendly && elem->GetIFF() > 0 && elem->GetIFF() != mission->Team()) + continue; + + if (!editor && !friendly && (elem->GetIFF() == 0 || elem->GetIFF() == mission->Team())) + continue; + + result.append(new(__FILE__,__LINE__) Text(elem->Name())); + } + } + + else if (ship) { + Sim* sim = Sim::GetSim(); + + if (sim) { + for (int r = 0; r < sim->GetRegions().size(); r++) { + SimRegion* rgn = sim->GetRegions().at(r); + + for (int i = 0; i < rgn->Ships().size(); i++) { + Ship* s = rgn->Ships().at(i); + + if (!station && s->IsStatic()) continue; + if (!starship && s->IsStarship()) continue; + if (!dropship && s->IsDropship()) continue; + + if (friendly && s->GetIFF() > 0 && s->GetIFF() != ship->GetIFF()) + continue; + + if (!friendly && (s->GetIFF() == 0 || s->GetIFF() == ship->GetIFF())) + continue; + + result.append(new(__FILE__,__LINE__) Text(s->Name())); + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +MapView::SetSelectionMode(int mode) +{ + if (mode <= SELECT_NONE) { + seln_mode = SELECT_NONE; + return; + } + + if (mode != seln_mode && mode <= SELECT_FIGHTER) { + seln_mode = mode; + + // when changing mode, + // select the item closest to the current center: + if (system && view_mode == VIEW_SYSTEM) + SelectAt(rect.x + rect.w/2, + rect.y + rect.h/2); + } +} + +void +MapView::SetSelection(int index) +{ + if (scrolling) return; + Orbital* s = 0; + + switch (seln_mode) { + case SELECT_SYSTEM: + if (index < system_list.size()) + SetSystem(system_list[index]); + s = stars[current_star]; + break; + + default: + case SELECT_PLANET: + if (index < planets.size()) + current_planet = index; + s = planets[current_planet]; + break; + + case SELECT_REGION: + if (index < regions.size()) + current_region = index; + s = regions[current_region]; + break; + + case SELECT_STATION: + { + if (mission) { + MissionElement* selected_elem = 0; + + ListIter elem = mission->GetElements(); + while (++elem) { + if (elem->IsStatic()) { + if (elem->Identity() == index) { + selected_elem = elem.value(); + break; + } + } + } + + SelectElem(selected_elem); + + if (selected_elem && regions.size()) { + ListIter rgn = regions; + while (++rgn) { + if (!stricmp(selected_elem->Region(), rgn->Name())) { + Orbital* elem_region = rgn.value(); + current_region = regions.index(elem_region); + } + } + } + } + + else { + Ship* selship = 0; + + if (ship) { + SimRegion* simrgn = ship->GetRegion(); + if (simrgn) { + ListIter s = simrgn->Ships(); + while (++s) { + if (s->IsStatic()) { + if (s->Identity() == index) { + selship = s.value(); + break; + } + } + } + } + } + + SelectShip(selship); + + if (selship) { + s = selship->GetRegion()->GetOrbitalRegion(); + current_region = regions.index(s); + } + } + } + break; + + case SELECT_STARSHIP: + { + if (mission) { + MissionElement* selected_elem = 0; + + ListIter elem = mission->GetElements(); + while (++elem) { + if (elem->IsStarship()) { + if (elem->Identity() == index) { + selected_elem = elem.value(); + break; + } + } + } + + SelectElem(selected_elem); + + if (selected_elem && regions.size()) { + ListIter rgn = regions; + while (++rgn) { + if (!stricmp(selected_elem->Region(), rgn->Name())) { + Orbital* elem_region = rgn.value(); + current_region = regions.index(elem_region); + } + } + } + } + + else { + Ship* selship = 0; + + if (ship) { + SimRegion* simrgn = ship->GetRegion(); + if (simrgn) { + ListIter s = simrgn->Ships(); + while (++s) { + if (s->IsStarship()) { + if (s->Identity() == index) { + selship = s.value(); + break; + } + } + } + } + } + + SelectShip(selship); + + if (selship) { + s = selship->GetRegion()->GetOrbitalRegion(); + current_region = regions.index(s); + } + } + } + break; + + case SELECT_FIGHTER: + { + if (mission) { + MissionElement* selected_elem = 0; + + ListIter elem = mission->GetElements(); + while (++elem) { + if (elem->IsDropship() && !elem->IsSquadron()) { + if (elem->Identity() == index) { + selected_elem = elem.value(); + break; + } + } + } + + SelectElem(selected_elem); + + if (selected_elem && regions.size()) { + ListIter rgn = regions; + while (++rgn) { + if (!stricmp(selected_elem->Region(), rgn->Name())) { + Orbital* elem_region = rgn.value(); + current_region = regions.index(elem_region); + } + } + } + } + + else { + Ship* selship = 0; + + if (ship) { + SimRegion* simrgn = ship->GetRegion(); + if (simrgn) { + ListIter s = simrgn->Ships(); + while (++s) { + if (s->IsDropship()) { + if (s->Identity() == index) { + selship = s.value(); + break; + } + } + } + } + } + + SelectShip(selship); + + if (selship) { + s = selship->GetRegion()->GetOrbitalRegion(); + current_region = regions.index(s); + } + } + } + break; + } + + SetupScroll(s); +} + +void +MapView::SetSelectedShip(Ship* ship) +{ + if (scrolling) return; + Orbital* s = 0; + Ship* selship = 0; + + + switch (seln_mode) { + case SELECT_SYSTEM: + case SELECT_PLANET: + case SELECT_REGION: + default: + break; + + case SELECT_STATION: + case SELECT_STARSHIP: + case SELECT_FIGHTER: + { + if (ship) { + SimRegion* simrgn = ship->GetRegion(); + + if (simrgn && simrgn->NumShips()) { + selship = simrgn->Ships().find(ship); + } + } + + SelectShip(selship); + } + break; + } + + if (selship) + SetupScroll(s); +} + +void +MapView::SetSelectedElem(MissionElement* elem) +{ + if (scrolling) return; + Orbital* s = 0; + + switch (seln_mode) { + case SELECT_SYSTEM: + case SELECT_PLANET: + case SELECT_REGION: + default: + break; + + case SELECT_STATION: + case SELECT_STARSHIP: + case SELECT_FIGHTER: + { + SelectElem(elem); + } + break; + } + + if (current_elem) + SetupScroll(s); +} + +void +MapView::SetupScroll(Orbital* s) +{ + switch (view_mode) { + case VIEW_GALAXY: + zoom = 1; + offset_x = 0; + offset_y = 0; + scrolling = 0; + break; + + case VIEW_SYSTEM: + if (s == 0) { + offset_x = 0; + offset_y = 0; + scrolling = 0; + } + else { + scroll_x = (offset_x + s->Location().x) / 5.0; + scroll_y = (offset_y + s->Location().y) / 5.0; + scrolling = 5; + } + break; + + case VIEW_REGION: + if (current_navpt) { + // don't move the map + scrolling = 0; + } + else if (current_ship) { + Point sloc = current_ship->Location().OtherHand(); + + if (!IsVisible(sloc)) { + scroll_x = (offset_x + sloc.x) / 5.0; + scroll_y = (offset_y + sloc.y) / 5.0; + scrolling = 5; + } + else { + scroll_x = 0; + scroll_y = 0; + scrolling = 0; + } + } + else if (current_elem) { + Point sloc = current_elem->Location(); + + if (!IsVisible(sloc)) { + scroll_x = (offset_x + sloc.x) / 5.0; + scroll_y = (offset_y + sloc.y) / 5.0; + scrolling = 5; + } + else { + scroll_x = 0; + scroll_y = 0; + scrolling = 0; + } + } + else { + offset_x = 0; + offset_y = 0; + scrolling = 0; + } + break; + } +} + +bool +MapView::IsVisible(const Point& loc) +{ + if (view_mode == VIEW_REGION) { + double scale = c/r; + double ox = offset_x * scale; + double oy = offset_y * scale; + double sx = loc.x * scale; + double sy = loc.y * scale; + double cx = rect.w/2; + double cy = rect.h/2; + + int test_x = (int) (cx + sx + ox); + int test_y = (int) (cy + sy + oy); + + bool visible = test_x >= 0 && test_x < rect.w && + test_y >= 0 && test_y < rect.h; + + return visible; + } + + return false; +} + +// +--------------------------------------------------------------------+ + +void +MapView::SetRegion(OrbitalRegion* rgn) +{ + if (scrolling || rgn == 0) return; + + int index = regions.index(rgn); + + if (index < 0 || index == current_region || index >= regions.size()) + return; + + current_region = index; + Orbital* s = regions[current_region]; + + if (!s) + return; + + switch (view_mode) { + case VIEW_GALAXY: + case VIEW_SYSTEM: + scroll_x = (offset_x + s->Location().x) / 5.0; + scroll_y = (offset_y + s->Location().y) / 5.0; + scrolling = 5; + break; + + case VIEW_REGION: + offset_x = 0; + offset_y = 0; + scrolling = 0; + break; + } +} + +// +--------------------------------------------------------------------+ + +void +MapView::SetRegionByName(const char* rgn_name) +{ + OrbitalRegion* rgn = 0; + + for (int i = 0; i < regions.size(); i++) { + Orbital* r = regions[i]; + if (!strcmp(rgn_name, r->Name())) { + rgn = (OrbitalRegion*) r; + break; + } + } + + SetRegion(rgn); +} + +// +--------------------------------------------------------------------+ + +void +MapView::SelectAt(int x, int y) +{ + if (scrolling) return; + if (c == 0) return; + if (seln_mode < 0) return; + + Orbital* s = 0; + + double scale = r/c; + double cx = rect.w/2; + double cy = rect.h/2; + double test_x = (x - rect.x - cx) * scale - offset_x; + double test_y = (y - rect.y - cy) * scale - offset_y; + double dist = 1.0e20; + int closest = 0; + + if (view_mode == VIEW_GALAXY) { + c = (cx>cy) ? cx : cy; + r = 10; + + Galaxy* g = Galaxy::GetInstance(); + if (g) + r = g->Radius(); + + StarSystem* closest_system = 0; + + // draw the list of systems, and their connections: + ListIter iter = system_list; + while (++iter) { + StarSystem* s = iter.value(); + + double dx = (s->Location().x - test_x); + double dy = (s->Location().y - test_y); + double d = sqrt(dx*dx + dy*dy); + + if (d < dist) { + dist = d; + closest_system = s; + } + } + + if (closest_system) + SetSystem(closest_system); + } + + else if (view_mode == VIEW_SYSTEM) { + switch (seln_mode) { + case SELECT_SYSTEM: { + if (stars.isEmpty()) return; + int index = 0; + ListIter star = stars; + while (++star) { + double dx = (star->Location().x - test_x); + double dy = (star->Location().y - test_y); + double d = sqrt(dx*dx + dy*dy); + + if (d < dist) { + dist = d; + closest = index; + } + + index++; + } + + current_star = closest; + } + s = stars[current_star]; + break; + + case SELECT_PLANET: { + if (planets.isEmpty()) return; + int index = 0; + ListIter planet = planets; + while (++planet) { + double dx = (planet->Location().x - test_x); + double dy = (planet->Location().y - test_y); + double d = sqrt(dx*dx + dy*dy); + + if (d < dist) { + dist = d; + closest = index; + } + + index++; + } + + current_planet = closest; + } + s = planets[current_planet]; + break; + + default: + case SELECT_REGION: { + if (regions.isEmpty()) return; + int index = 0; + ListIter region = regions; + while (++region) { + double dx = (region->Location().x - test_x); + double dy = (region->Location().y - test_y); + double d = sqrt(dx*dx + dy*dy); + + if (d < dist) { + dist = d; + closest = index; + } + + index++; + } + + current_region = closest; + } + s = regions[current_region]; + break; + } + } + + else if (view_mode == VIEW_REGION) { + dist = 5.0e3; + + if (mission) { + Orbital* rgn = regions[current_region]; + MissionElement* sel_elem = 0; + Instruction* sel_nav = 0; + + if (!rgn) return; + + // check nav points: + ListIter elem = mission->GetElements(); + while (++elem) { + MissionElement* e = elem.value(); + + if (!e->IsSquadron() && (editor || e->GetIFF() == mission->Team())) { + ListIter navpt = e->NavList(); + while (++navpt) { + Instruction* n = navpt.value(); + + if (!stricmp(n->RegionName(), rgn->Name())) { + Point nloc = n->Location(); + double dx = nloc.x - test_x; + double dy = nloc.y - test_y; + double d = sqrt(dx*dx + dy*dy); + + if (d < dist) { + dist = d; + sel_nav = n; + sel_elem = e; + } + } + } + } + } + + if (sel_nav) { + SelectElem(sel_elem); + SelectNavpt(sel_nav); + } + + // check elements: + else { + elem.reset(); + while (++elem) { + MissionElement* e = elem.value(); + + if (e->Region() == rgn->Name() && !e->IsSquadron()) { + Point sloc = e->Location(); + double dx = sloc.x - test_x; + double dy = sloc.y - test_y; + double d = sqrt(dx*dx + dy*dy); + + if (d < dist) { + dist = d; + sel_elem = e; + } + } + } + + SelectElem(sel_elem); + + if (sel_elem) + s = rgn; + } + } + else if (ship) { + Sim* sim = Sim::GetSim(); + Orbital* rgn = regions[current_region]; + SimRegion* simrgn = 0; + Ship* sel_ship = 0; + Instruction* sel_nav = 0; + + if (sim && rgn) + simrgn = sim->FindRegion(rgn->Name()); + + // check nav points: + if (simrgn) { + for (int r = 0; r < sim->GetRegions().size(); r++) { + SimRegion* simrgn = sim->GetRegions().at(r); + + for (int i = 0; i < simrgn->Ships().size(); i++) { + Ship* s = simrgn->Ships().at(i); + + if (s->GetIFF() == ship->GetIFF() && s->GetElementIndex() == 1) { + ListIter navpt = s->GetFlightPlan(); + while (++navpt) { + Instruction* n = navpt.value(); + + if (!stricmp(n->RegionName(), rgn->Name())) { + Point nloc = n->Location(); + double dx = nloc.x - test_x; + double dy = nloc.y - test_y; + double d = sqrt(dx*dx + dy*dy); + + if (d < dist) { + dist = d; + sel_nav = n; + sel_ship = s; + } + } + } + } + } + } + } + + if (sel_nav) { + SelectShip(sel_ship); + SelectNavpt(sel_nav); + } + + // check ships: + else if (simrgn->NumShips()) { + ListIter ship = simrgn->Ships(); + while (++ship) { + Ship* s = ship.value(); + + if (!IsClutter(*s)) { + Point sloc = s->Location().OtherHand(); + double dx = sloc.x - test_x; + double dy = sloc.y - test_y; + double d = sqrt(dx*dx + dy*dy); + + if (d < dist) { + dist = d; + sel_ship = s; + } + } + } + + SelectShip(sel_ship); + } + else { + SelectShip(0); + SelectNavpt(0); + } + } + } + + if (s) + SetupScroll(s); +} + +// +--------------------------------------------------------------------+ + +Orbital* +MapView::GetSelection() +{ + Orbital* s = 0; + + switch (seln_mode) { + case SELECT_SYSTEM: + if (current_star < stars.size()) + s = stars[current_star]; + break; + + default: + case SELECT_PLANET: + if (current_planet < planets.size()) + s = planets[current_planet]; + break; + + case SELECT_REGION: + if (current_region < regions.size()) + s = regions[current_region]; + break; + + case SELECT_STATION: + case SELECT_STARSHIP: + case SELECT_FIGHTER: + break; + } + + return s; +} + +Ship* +MapView::GetSelectedShip() +{ + return current_ship; +} + +MissionElement* +MapView::GetSelectedElem() +{ + return current_elem; +} + +// +--------------------------------------------------------------------+ + +int +MapView::GetSelectionIndex() +{ + int s = 0; + + switch (seln_mode) { + case SELECT_SYSTEM: s = current_star; break; + default: + case SELECT_PLANET: s = current_planet; break; + case SELECT_REGION: s = current_region; break; + + case SELECT_STATION: + case SELECT_STARSHIP: + case SELECT_FIGHTER: + { + s = -1; + Sim* sim = Sim::GetSim(); + Orbital* rgn = regions[current_region]; + SimRegion* simrgn = 0; + + if (sim && rgn) + simrgn = sim->FindRegion(rgn->Name()); + + if (simrgn) { + if (current_ship && simrgn->NumShips()) { + s = simrgn->Ships().index(current_ship); + } + } + } + break; + } + + return s; +} + +void +MapView::DrawTabbedText(Font* font, const char* text) +{ + if (font && text && *text) { + Rect label_rect; + + label_rect.w = rect.w; + label_rect.h = rect.h; + + label_rect.Inset(8,8,8,8); + + DWORD text_flags = DT_WORDBREAK | DT_LEFT; + + active_window->SetFont(font); + active_window->DrawText(text, 0, label_rect, text_flags); + } +} + +// +--------------------------------------------------------------------+ + +void +MapView::Refresh() +{ + rect = window->GetRect(); + + if (!system) { + DrawGrid(); + DrawTabbedText(title_font, Game::GetText("MapView.item.no-system")); + return; + } + + if (font) + font->SetColor(Color::White); + + if (scrolling) { + offset_x -= scroll_x; + offset_y -= scroll_y; + scrolling--; + } + + rect.w -= 2; + rect.h -= 2; + + switch (view_mode) { + case VIEW_GALAXY: DrawGalaxy(); break; + case VIEW_SYSTEM: DrawSystem(); break; + case VIEW_REGION: DrawRegion(); break; + default: DrawGrid(); break; + } + + rect.w += 2; + rect.h += 2; + + if (menu_view) { + if (current_navpt) { + active_menu = nav_menu; + } + else if (current_ship) { + if (current_ship->GetIFF() == ship->GetIFF()) + active_menu = ship_menu; + else + active_menu = map_menu; + } + else if (current_elem) { + if (editor || current_elem->GetIFF() == mission->Team()) + active_menu = ship_menu; + else + active_menu = map_menu; + } + else { + active_menu = map_menu; + } + + menu_view->SetBackColor(Color::Gray); + menu_view->SetTextColor(Color::White); + menu_view->SetMenu(active_menu); + menu_view->DoMouseFrame(); + + if (menu_view->GetAction()) { + ProcessMenuItem(menu_view->GetAction()); + } + + menu_view->Refresh(); + } + + DrawTitle(); +} + +// +--------------------------------------------------------------------+ + +void +MapView::DrawTitle() +{ + title_font->SetColor(active_window->GetForeColor()); + DrawTabbedText(title_font, title); +} + +// +--------------------------------------------------------------------+ + +void +MapView::DrawGalaxy() +{ + title = Game::GetText("MapView.title.Galaxy"); + DrawGrid(); + + double cx = rect.w/2; + double cy = rect.h/2; + + c = (cx>cy) ? cx : cy; + r = 10; // * zoom; + + Galaxy* g = Galaxy::GetInstance(); + if (g) + r = g->Radius(); // * zoom; + + double scale = c/r; + double ox = 0; + double oy = 0; + + // compute offset: + ListIter iter = system_list; + while (++iter) { + StarSystem* s = iter.value(); + + if (system == s) { + if (fabs(s->Location().x) > 10 || fabs(s->Location().y) > 10) { + int sx = (int) s->Location().x; + int sy = (int) s->Location().y; + + sx -= sx % 10; + sy -= sy % 10; + + ox = sx * -scale; + oy = sy * -scale; + } + } + } + + // draw the list of systems, and their connections: + iter.reset(); + while (++iter) { + StarSystem* s = iter.value(); + + int sx = (int) (cx + ox + s->Location().x * scale); + int sy = (int) (cy + oy + s->Location().y * scale); + + if (sx < 4 || sx > rect.w-4 || sy < 4 || sy > rect.h-4) + continue; + + window->DrawEllipse(sx-7, sy-7, sx+7, sy+7, Ship::IFFColor(s->Affiliation())); + + if (s == system) { + window->DrawLine(0, sy, rect.w, sy, Color::Gray, Video::BLEND_ADDITIVE); + window->DrawLine(sx, 0, sx, rect.h, Color::Gray, Video::BLEND_ADDITIVE); + } + + ListIter iter2 = system_list; + while (++iter2) { + StarSystem* s2 = iter2.value(); + + if (s != s2 && s->HasLinkTo(s2)) { + int ax = sx; + int ay = sy; + + int bx = (int) (cx + ox + s2->Location().x * scale); + int by = (int) (cy + oy + s2->Location().y * scale); + + if (ax == bx) { + if (ay < by) { + ay += 8; by -= 8; + } + else { + ay -= 8; by += 8; + } + } + + else if (ay == by) { + if (ax < bx) { + ax += 8; bx -= 8; + } + else { + ax -= 8; bx += 8; + } + } + + else { + Point d = Point(bx, by, 0) - Point(ax, ay, 0); + d.Normalize(); + + ax += (int) (8 * d.x); + ay += (int) (8 * d.y); + + bx -= (int) (8 * d.x); + by -= (int) (8 * d.y); + } + + window->DrawLine(ax, ay, bx, by, Color(120,120,120), Video::BLEND_ADDITIVE); + } + } + } + + // finally draw all the stars in the galaxy: + if (g) { + ListIter iter = g->Stars(); + while (++iter) { + Star* s = iter.value(); + + int sx = (int) (cx + ox + s->Location().x * scale); + int sy = (int) (cy + oy + s->Location().y * scale); + int sr = s->GetSize(); + + if (sx < 4 || sx > rect.w-4 || sy < 4 || sy > rect.h-4) + continue; + + window->FillEllipse(sx-sr, sy-sr, sx+sr, sy+sr, s->GetColor()); + + if (!strncmp(s->Name(), "GSC", 3)) + font->SetColor(Color(100,100,100)); + else + font->SetColor(Color::White); + + Rect name_rect(sx-60, sy+8, 120, 20); + active_window->SetFont(font); + active_window->DrawText(s->Name(), 0, name_rect, DT_SINGLELINE | DT_CENTER); + } + } + + font->SetColor(Color::White); +} + +// +--------------------------------------------------------------------+ + +void +MapView::DrawSystem() +{ + Text caption = Game::GetText("MapView.title.Starsystem"); + caption += " "; + caption += system->Name(); + + if (current_ship) { + caption += "\n"; + caption += Game::GetText("MapView.title.Ship"); + caption += " "; + caption += current_ship->Name(); + } + else if (current_elem) { + caption += "\n"; + caption += Game::GetText("MapView.title.Ship"); + caption += " "; + caption += current_elem->Name(); + } + + title = caption; + + ListIter star = system->Bodies(); + while (++star) { + int p_orb = 1; + + ListIter planet = star->Satellites(); + while (++planet) { + DrawOrbital(*planet, p_orb++); + + int m_orb = 1; + + ListIter moon = planet->Satellites(); + while (++moon) { + DrawOrbital(*moon, m_orb++); + + ListIter region = moon->Regions(); + while (++region) { + DrawOrbital(*region, 1); + } + } + + ListIter region = planet->Regions(); + while (++region) { + DrawOrbital(*region, 1); + } + } + + ListIter region = star->Regions(); + while (++region) { + DrawOrbital(*region, 1); + } + + DrawOrbital(*star, 0); + } + + char r_txt[32]; + FormatNumber(r_txt, system->Radius() * zoom); + char resolution[64]; + sprintf(resolution, "%s: %s", Game::GetText("MapView.info.Resolution").data(), r_txt); + + active_window->SetFont(font); + active_window->DrawText(resolution, -1, Rect(4, 4, rect.w-8, 24), DT_SINGLELINE|DT_RIGHT); +} + +// +--------------------------------------------------------------------+ + +void +MapView::DrawRegion() +{ + OrbitalRegion* rgn = (OrbitalRegion*) regions[current_region]; + + Text caption = Game::GetText("MapView.title.Sector"); + caption += " "; + caption += rgn->Name(); + + if (current_ship) { + caption += "\n"; + caption += Game::GetText("MapView.title.Ship"); + caption += " "; + caption += current_ship->Name(); + } + else if (current_elem) { + caption += "\n"; + caption += Game::GetText("MapView.title.Ship"); + caption += " "; + caption += current_elem->Name(); + } + + title = caption; + + double cx = rect.w/2; + double cy = rect.h/2; + + int size = (int) rgn->Radius(); + int step = (int) rgn->GridSpace(); + + c = (cxRadius() * zoom; + double scale = c/r; + + double ox = offset_x * scale; + double oy = offset_y * scale; + + int left = (int) (-size * scale + ox + cx); + int right = (int) ( size * scale + ox + cx); + int top = (int) (-size * scale + oy + cy); + int bottom = (int) ( size * scale + oy + cy); + + Color major(48,48,48); + Color minor(24,24,24); + + int x,y; + int tick = 0; + + for (x = 0; x <= size; x += step) { + int lx = (int) (x * scale + ox + cx); + if (!tick) + window->DrawLine(lx, top, lx, bottom, major, Video::BLEND_ADDITIVE); + else + window->DrawLine(lx, top, lx, bottom, minor, Video::BLEND_ADDITIVE); + + lx = (int) (-x * scale + ox + cx); + if (!tick) + window->DrawLine(lx, top, lx, bottom, major, Video::BLEND_ADDITIVE); + else + window->DrawLine(lx, top, lx, bottom, minor, Video::BLEND_ADDITIVE); + + if (++tick > 3) tick = 0; + } + + tick = 0; + + for (y = 0; y <= size; y += step) { + int ly = (int) (y * scale + oy + cy); + if (!tick) + window->DrawLine(left, ly, right, ly, major, Video::BLEND_ADDITIVE); + else + window->DrawLine(left, ly, right, ly, minor, Video::BLEND_ADDITIVE); + + ly = (int) (-y * scale + oy + cy); + if (!tick) + window->DrawLine(left, ly, right, ly, major, Video::BLEND_ADDITIVE); + else + window->DrawLine(left, ly, right, ly, minor, Video::BLEND_ADDITIVE); + + if (++tick > 3) tick = 0; + } + + int rep = 3; + if (r > 70e3) rep = 2; + if (r > 250e3) rep = 1; + + if (campaign && rgn) { + // draw the combatants in this region: + ListIter iter = campaign->GetCombatants(); + while (++iter) { + Combatant* combatant = iter.value(); + DrawCombatGroup(combatant->GetForce(), rep); + } + } + + else if (mission && rgn) { + // draw the elements in this region: + ListIter elem = mission->GetElements(); + while (++elem) + if (!elem->IsSquadron()) + DrawElem(*elem, (elem.value() == current_elem), rep); + } + + else if (ship) { + // draw the ships in this region: + Sim* sim = Sim::GetSim(); + SimRegion* simrgn = 0; + + if (sim && rgn) + simrgn = sim->FindRegion(rgn->Name()); + + // this algorithm uses the allied track list for the region, + // even if this ship is in a different region. a previous + // version used the ship's own contact list, which only shows + // ships in the player's region. this way is less "realistic" + // but more fun for managing large battles. + + if (simrgn) { + ListIter c = simrgn->TrackList(ship->GetIFF()); + while (++c) { + Contact* contact = c.value(); + Ship* s = contact->GetShip(); + + if (s && (s->Class() & ship_filter) && !IsClutter(*s) && s != ship) + DrawShip(*s, (s == current_ship), rep); + } + + ListIter s_iter = simrgn->Ships(); + while (++s_iter) { + Ship* s = s_iter.value(); + + if (s && (s->IsStatic()) && !IsClutter(*s) && + (s->GetIFF() == ship->GetIFF() || s->GetIFF() == 0)) + DrawShip(*s, (s == current_ship), rep); + } + + // draw nav routes for allied ships not in the region: + ListIter r_iter = sim->GetRegions(); + while (++r_iter) { + SimRegion* r = r_iter.value(); + + if (r != simrgn) { + ListIter s_iter = r->Ships(); + while (++s_iter) { + Ship* s = s_iter.value(); + + if (s && !s->IsStatic() && !IsClutter(*s) && + (s->GetIFF() == ship->GetIFF() || s->GetIFF() == 0)) { + DrawNavRoute(simrgn->GetOrbitalRegion(), + s->GetFlightPlan(), + s->MarkerColor(), + s, 0); + } + } + } + } + } + + // draw our own ship: + DrawShip(*ship, (ship == current_ship), rep); + } + + char r_txt[32]; + FormatNumber(r_txt, r*2); + char resolution[64]; + sprintf(resolution, "%s: %s", Game::GetText("MapView.info.Resolution").data(), r_txt); + + active_window->SetFont(font); + active_window->DrawText(resolution, -1, Rect(4, 4, rect.w-8, 24), DT_SINGLELINE|DT_RIGHT); +} + +// +--------------------------------------------------------------------+ + +void +MapView::DrawGrid() +{ + int grid_step = rect.w/8; + int cx = rect.w/2; + int cy = rect.h/2; + + Color c(32,32,32); + + window->DrawLine(0, cy, rect.w, cy, c, Video::BLEND_ADDITIVE); + window->DrawLine(cx, 0, cx, rect.h, c, Video::BLEND_ADDITIVE); + + for (int i = 1; i < 4; i++) { + window->DrawLine(0, cy + (i*grid_step), rect.w, cy + (i*grid_step), c, Video::BLEND_ADDITIVE); + window->DrawLine(0, cy - (i*grid_step), rect.w, cy - (i*grid_step), c, Video::BLEND_ADDITIVE); + window->DrawLine(cx + (i*grid_step), 0, cx + (i*grid_step), rect.h, c, Video::BLEND_ADDITIVE); + window->DrawLine(cx - (i*grid_step), 0, cx - (i*grid_step), rect.h, c, Video::BLEND_ADDITIVE); + } +} + +// +--------------------------------------------------------------------+ + +void +MapView::DrawOrbital(Orbital& body, int index) +{ + int type = body.Type(); + + if (type == Orbital::NOTHING) + return; + + int x1, y1, x2, y2; + Rect label_rect; + int label_w = 64; + int label_h = 18; + + double cx = rect.w/2; + double cy = rect.h/2; + + c = (cxRadius() * zoom; + + if ((r > 300e9) && (type > Orbital::PLANET)) + return; + + double xscale = cx / r; + double yscale = cy / r * 0.75; + + double ox = offset_x * xscale; + double oy = offset_y * yscale; + + double bo_x = body.Orbit() * xscale; + double bo_y = body.Orbit() * yscale; + double br = body.Radius() * yscale; + double bx = body.Location().x * xscale; + double by = body.Location().y * yscale; + + double px = 0; + double py = 0; + + if (body.Primary()) { + double min_pr = GetMinRadius(body.Primary()->Type()); + + if (index) { + if (min_pr < 4) + min_pr = 4; + + min_pr *= (index+1); + } + + double min_x = min_pr * xscale / yscale; + double min_y = min_pr; + + if (bo_x < min_x) + bo_x = min_x; + + if (bo_y < min_y) + bo_y = min_y; + + px = body.Primary()->Location().x * xscale; + py = body.Primary()->Location().y * yscale; + } + + if (type == Orbital::TERRAIN) + bo_x = bo_y; + + int ipx = (int) (cx + px + ox); + int ipy = (int) (cy + py + oy); + int ibo_x = (int) bo_x; + int ibo_y = (int) bo_y; + + x1 = ipx - ibo_x; + y1 = ipy - ibo_y; + x2 = ipx + ibo_x; + y2 = ipy + ibo_y; + + if (type != Orbital::TERRAIN) { + double a = x2-x1; + double b = rect.w*32; + + if (a < b) + window->DrawEllipse(x1, y1, x2, y2, Color(64,64,64), Video::BLEND_ADDITIVE); + } + + // show body's location on possibly magnified orbit + bx = px + bo_x * cos(body.Phase()); + by = py + bo_y * sin(body.Phase()); + + double min_br = GetMinRadius(type); + if (br < min_br) br = min_br; + + Color color; + + switch (type) { + case Orbital::STAR: color = Color(248, 248, 128); break; + case Orbital::PLANET: color = Color( 64, 64, 192); break; + case Orbital::MOON: color = Color( 32, 192, 96); break; + case Orbital::REGION: color = Color(255, 255, 255); break; + case Orbital::TERRAIN: color = Color( 16, 128, 48); break; + } + + int icx = (int) (cx + bx + ox); + int icy = (int) (cy + by + oy); + int ibr = (int) br; + + x1 = icx - ibr; + y1 = icy - ibr; + x2 = icx + ibr; + y2 = icy + ibr; + + if (type < Orbital::REGION) { + if (body.GetMapIcon().Width() > 64) { + Bitmap* map_icon = (Bitmap*) &body.GetMapIcon(); + + if (type == Orbital::STAR) + window->DrawBitmap(x1,y1,x2,y2, map_icon, Video::BLEND_ADDITIVE); + else + window->DrawBitmap(x1,y1,x2,y2, map_icon, Video::BLEND_ALPHA); + } + else { + window->FillEllipse(x1, y1, x2, y2, color); + } + } + else { + window->DrawRect(x1, y1, x2, y2, color); + + if (campaign) { + ListIter iter = campaign->GetCombatants(); + while (++iter) { + Combatant* combatant = iter.value(); + + if (ibr >= 4 || combatant->GetIFF() == 1) + DrawCombatantSystem(combatant, &body, icx, icy, ibr); + } + } + } + + if (type == Orbital::STAR || bo_y > label_h) { + label_rect.x = x1 - label_w + (int) br; + label_rect.y = y1 - label_h; + label_rect.w = label_w * 2; + label_rect.h = label_h; + + active_window->SetFont(font); + active_window->DrawText(body.Name(), -1, label_rect, DT_SINGLELINE|DT_CENTER); + } +} + +// +--------------------------------------------------------------------+ + +double +MapView::GetMinRadius(int type) +{ + switch (type) { + case Orbital::STAR: return 8; + case Orbital::PLANET: return 4; + case Orbital::MOON: return 2; + case Orbital::REGION: return 2; + } + + return 0; +} + +// +--------------------------------------------------------------------+ + +static void +ColorizeBitmap(Bitmap& img, Color color) +{ + int w = img.Width(); + int h = img.Height(); + + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + Color c = img.GetColor(x, y); + + if (c != Color::Black && c != Color::White) { + img.SetColor(x,y,color.ShadeColor(c.Blue()/2)); + } + } + } + + img.AutoMask(); +} + +static POINT shipshape1[] = { {6,0}, {-6,4}, {-6,-4} }; +static POINT shipshape2[] = { {8,0}, { 4,4}, {-8,4}, {-8,-4}, {4,-4} }; +static POINT shipshape3[] = { {8,8}, {-8,8}, {-8,-8}, {8,-8} }; + +void +MapView::DrawShip(Ship& s, bool current, int rep) +{ + OrbitalRegion* rgn = (OrbitalRegion*) regions[current_region]; + if (!rgn) return; + + int x1, y1, x2, y2; + POINT shiploc; + Point sloc = s.Location().OtherHand(); + + double cx = rect.w/2; + double cy = rect.h/2; + + c = (cxRadius() * zoom; + + double scale = c/r; + + double ox = offset_x * scale; + double oy = offset_y * scale; + + double rlx = 0; + double rly = 0; + + int sprite_width = 10; + + window->SetFont(font); + + // draw ship icon: + if (rep && view_mode == VIEW_REGION && rgn == s.GetRegion()->GetOrbitalRegion()) { + double sx = (sloc.x + rlx) * scale; + double sy = (sloc.y + rly) * scale; + + shiploc.x = (int) (cx + sx + ox); + shiploc.y = (int) (cy + sy + oy); + + bool ship_visible = shiploc.x >= 0 && shiploc.x < rect.w && + shiploc.y >= 0 && shiploc.y < rect.h; + + if (ship_visible) { + if (rep < 3) { + window->FillRect(shiploc.x-2, shiploc.y-2, shiploc.x+2, shiploc.y+2, s.MarkerColor()); + sprite_width = 2; + + if (&s == ship || !IsCrowded(s)) + window->Print(shiploc.x-sprite_width, shiploc.y+sprite_width+2, s.Name()); + } + else { + Point heading = s.Heading().OtherHand(); + heading.z = 0; + heading.Normalize(); + + double theta = 0; + + if (heading.y > 0) + theta = acos(heading.x); + else + theta = -acos(heading.x); + + const double THETA_SLICE = 4 / PI; + const double THETA_OFFSET = PI + THETA_SLICE/2; + + int sprite_index = (int) ((theta + THETA_OFFSET) * THETA_SLICE); + int nsprites = s.Design()->map_sprites.size(); + + if (nsprites) { + if (sprite_index < 0 || sprite_index >= nsprites) + sprite_index = sprite_index % nsprites; + + Bitmap* map_sprite = s.Design()->map_sprites[sprite_index]; + + Bitmap bmp; + bmp.CopyBitmap(*map_sprite); + ColorizeBitmap(bmp, s.MarkerColor()); + sprite_width = bmp.Width()/2; + int h = bmp.Height()/2; + + window->DrawBitmap(shiploc.x-sprite_width, + shiploc.y-h, + shiploc.x+sprite_width, + shiploc.y+h, + &bmp, + Video::BLEND_ALPHA); + } + + else { + theta -= PI/2; + + if (s.IsStatic()) { + window->FillRect(shiploc.x-6, shiploc.y-6, shiploc.x+6, shiploc.y+6, s.MarkerColor()); + window->DrawRect(shiploc.x-6, shiploc.y-6, shiploc.x+6, shiploc.y+6, Color::White); + } + else if (s.IsStarship()) { + window->FillRect(shiploc.x-4, shiploc.y-4, shiploc.x+4, shiploc.y+4, s.MarkerColor()); + window->DrawRect(shiploc.x-4, shiploc.y-4, shiploc.x+4, shiploc.y+4, Color::White); + } + else { + window->FillRect(shiploc.x-3, shiploc.y-3, shiploc.x+3, shiploc.y+3, s.MarkerColor()); + window->DrawRect(shiploc.x-3, shiploc.y-3, shiploc.x+3, shiploc.y+3, Color::White); + } + } + + window->Print(shiploc.x-sprite_width, shiploc.y+sprite_width+2, s.Name()); + } + } + } + + // draw nav route: + // draw current ship marker: + if (current && Text(s.GetRegion()->Name()) == regions[current_region]->Name()) { + x1 = (int) (shiploc.x - sprite_width - 1); + x2 = (int) (shiploc.x + sprite_width + 1); + y1 = (int) (shiploc.y - sprite_width - 1); + y2 = (int) (shiploc.y + sprite_width + 1); + + window->DrawRect(x1, y1, x2, y2, Color::White); + } + + // only see routes for your own team: + if (s.GetIFF() == 0 || ship && s.GetIFF() == ship->GetIFF()) { + DrawNavRoute(rgn, s.GetFlightPlan(), s.MarkerColor(), &s, 0); + } +} + +void +MapView::DrawElem(MissionElement& s, bool current, int rep) +{ + if (!mission) return; + + bool visible = editor || + s.GetIFF() == 0 || + s.GetIFF() == mission->Team() || + s.IntelLevel() > Intel::KNOWN; + + if (!visible) return; + + OrbitalRegion* rgn = (OrbitalRegion*) regions[current_region]; + + if (!rgn) return; + + int x1, y1, x2, y2; + POINT shiploc; + + double cx = rect.w/2; + double cy = rect.h/2; + + c = (cxRadius() * zoom; + + double scale = c/r; + + double ox = offset_x * scale; + double oy = offset_y * scale; + + double rlx = 0; + double rly = 0; + + int sprite_width = 10; + + window->SetFont(font); + + // draw ship icon: + if (!stricmp(s.Region(), rgn->Name())) { + double sx = (s.Location().x + rlx) * scale; + double sy = (s.Location().y + rly) * scale; + + shiploc.x = (int) (cx + sx + ox); + shiploc.y = (int) (cy + sy + oy); + + bool ship_visible = shiploc.x >= 0 && shiploc.x < rect.w && + shiploc.y >= 0 && shiploc.y < rect.h; + + if (ship_visible) { + if (rep < 3) { + window->FillRect(shiploc.x-2, shiploc.y-2, shiploc.x+2, shiploc.y+2, s.MarkerColor()); + sprite_width = 2; + + if (!IsCrowded(s)) + window->Print(shiploc.x-sprite_width, shiploc.y+sprite_width+2, s.Name()); + } + else { + double theta = s.Heading(); + + const double THETA_SLICE = 4 / PI; + const double THETA_OFFSET = PI / 2; + + int sprite_index = (int) ((theta + THETA_OFFSET) * THETA_SLICE); + int nsprites = 0; + + if (s.GetDesign()) + nsprites = s.GetDesign()->map_sprites.size(); + + if (nsprites > 0) { + if (sprite_index < 0 || sprite_index >= nsprites) + sprite_index = sprite_index % nsprites; + + Bitmap* map_sprite = s.GetDesign()->map_sprites[sprite_index]; + + Bitmap bmp; + bmp.CopyBitmap(*map_sprite); + ColorizeBitmap(bmp, s.MarkerColor()); + sprite_width = bmp.Width()/2; + int h = bmp.Height()/2; + + window->DrawBitmap(shiploc.x-sprite_width, + shiploc.y-h, + shiploc.x+sprite_width, + shiploc.y+h, + &bmp, + Video::BLEND_ALPHA); + } + + else { + theta -= PI/2; + + if (s.IsStatic()) { + window->FillRect(shiploc.x-6, shiploc.y-6, shiploc.x+6, shiploc.y+6, s.MarkerColor()); + window->DrawRect(shiploc.x-6, shiploc.y-6, shiploc.x+6, shiploc.y+6, Color::White); + } + else if (s.IsStarship()) { + window->FillRect(shiploc.x-4, shiploc.y-4, shiploc.x+4, shiploc.y+4, s.MarkerColor()); + window->DrawRect(shiploc.x-4, shiploc.y-4, shiploc.x+4, shiploc.y+4, Color::White); + } + else { + window->FillRect(shiploc.x-3, shiploc.y-3, shiploc.x+3, shiploc.y+3, s.MarkerColor()); + window->DrawRect(shiploc.x-3, shiploc.y-3, shiploc.x+3, shiploc.y+3, Color::White); + } + } + + char label[64]; + + if (s.Count() > 1) + sprintf(label, "%s x %d", (const char*) s.Name(), s.Count()); + else + strcpy(label, (const char*) s.Name()); + + window->Print(shiploc.x-sprite_width, shiploc.y+sprite_width+2, label); + } + } + } + + // draw nav route: + // draw current ship marker: + if (current && s.Region() == regions[current_region]->Name()) { + x1 = (int) (shiploc.x - sprite_width - 1); + x2 = (int) (shiploc.x + sprite_width + 1); + y1 = (int) (shiploc.y - sprite_width - 1); + y2 = (int) (shiploc.y + sprite_width + 1); + + window->DrawRect(x1, y1, x2, y2, Color::White); + } + + // only see routes for your own team: + if (editor || s.GetIFF() == 0 || mission && s.GetIFF() == mission->Team()) { + DrawNavRoute(rgn, s.NavList(), s.MarkerColor(), 0, &s); + } +} + +void +MapView::DrawNavRoute(OrbitalRegion* rgn, + List& s_route, + Color s_marker, + Ship* ship, + MissionElement* elem) +{ + int x1, y1, x2, y2; + double cx = rect.w/2; + double cy = rect.h/2; + + c = (cxRadius() * zoom; + + if (view_mode == VIEW_REGION) { + if (!rgn) return; + r = rgn->Radius() * zoom; + } + + double scale = c/r; + double ox = offset_x * scale; + double oy = offset_y * scale; + + Point old_loc; + double old_x = 0; + double old_y = 0; + bool old_in = false; + + Point first_loc; + int first_x = 0; + int first_y = 0; + bool first_in = false; + + bool draw_route = true; + bool draw_bold = false; + + if (ship && ship->GetElementIndex() > 1) + draw_route = false; + + if (ship && ship == current_ship) { + s_marker = s_marker * 1.5; + draw_bold = true; + } + + else if (elem && elem == current_elem) { + s_marker = s_marker * 1.5; + draw_bold = true; + } + + for (int i = 0; i < s_route.size(); i++) { + Instruction* navpt = s_route[i]; + + if (!stricmp(navpt->RegionName(), rgn->Name())) { + double nav_x = navpt->Location().x * scale; + double nav_y = navpt->Location().y * scale; + + int isx = (int) (cx + nav_x + ox); + int isy = (int) (cy + nav_y + oy); + + if (old_in && draw_route) { + int iox = (int) (cx + old_x + ox); + int ioy = (int) (cy + old_y + oy); + window->DrawLine(iox, ioy, isx, isy, s_marker); + + int x1 = (iox-isx); + int y1 = (ioy-isy); + + if (draw_bold) { + if (fabs(x1) > fabs(y1)) { + window->DrawLine(iox, ioy+1, isx, isy+1, s_marker); + } + else { + window->DrawLine(iox+1, ioy, isx+1, isy, s_marker); + } + } + + if ((x1*x1 + y1*y1) > 2000) { + double dist = Point(navpt->Location() - old_loc).length(); + + int imx = (int) (cx + (old_x+nav_x)/2 + ox); + int imy = (int) (cy + (old_y+nav_y)/2 + oy); + + char dist_txt[32]; + FormatNumber(dist_txt, dist); + font->SetColor(Color::Gray); + window->SetFont(font); + window->Print(imx-20, imy-6, dist_txt); + font->SetColor(Color::White); + } + } + + x1 = isx - 3; + y1 = isy - 3; + x2 = isx + 3; + y2 = isy + 3; + + Color c = Color::White; + if (navpt->Status() > Instruction::ACTIVE) { + c = Color::Gray; + } + else if (!first_in) { + first_in = true; + first_loc = navpt->Location(); + first_x = isx; + first_y = isy; + } + + if (draw_route) { + window->DrawLine(x1, y1, x2, y2, c); + window->DrawLine(x1, y2, x2, y1, c); + + if (navpt == current_navpt) + window->DrawRect(x1-2, y1-2, x2+2, y2+2, c); + + char buf[256]; + sprintf(buf, "%d", i+1); + window->SetFont(font); + window->Print(x2+3, y1, buf); + + if (navpt == current_navpt) { + if (navpt->TargetName() && strlen(navpt->TargetName())) { + sprintf(buf, "%s %s", Game::GetText(Text("MapView.item.") + Instruction::ActionName(navpt->Action())).data(), navpt->TargetName()); + window->Print(x2+3, y1+10, buf); + } + else { + sprintf(buf, "%s", Game::GetText(Text("MapView.item.") + Instruction::ActionName(navpt->Action())).data()); + window->Print(x2+3, y1+10, buf); + } + + sprintf(buf, "%s", Game::GetText(Text("MapView.item.") + Instruction::FormationName(navpt->Formation())).data()); + window->Print(x2+3, y1+20, buf); + + sprintf(buf, "%d", navpt->Speed()); + window->Print(x2+3, y1+30, buf); + + if (navpt->HoldTime()) { + char hold_time[32]; + FormatTime(hold_time, navpt->HoldTime()); + + sprintf(buf, "%s %s", Game::GetText("MapView.item.Hold").data(), hold_time); + window->Print(x2+3, y1+40, buf); + } + } + } + + old_loc = navpt->Location(); + old_x = nav_x; + old_y = nav_y; + old_in = true; + } + + else { + old_loc = navpt->Location(); + old_x = 0; + old_y = 0; + old_in = false; + } + } + + // if the ship and the first active navpoint are both in the region, + // draw a line from the ship to the first active navpoint: + + if (first_in) { + old_in = false; + + if (ship && ship->GetRegion()) { + old_in = (ship->GetRegion()->GetOrbitalRegion() == rgn); + + if (old_in) { + old_loc = ship->Location().OtherHand(); + old_x = old_loc.x * scale; + old_y = old_loc.y * scale; + } + } + + else if (elem) { + old_in = (elem->Region() == rgn->Name()) ? true : false; + + if (old_in) { + old_loc = elem->Location(); + old_x = old_loc.x * scale; + old_y = old_loc.y * scale; + } + } + + if (old_in) { + int iox = (int) (cx + old_x + ox); + int ioy = (int) (cy + old_y + oy); + window->DrawLine(iox, ioy, first_x, first_y, s_marker); + + int x1 = (iox-first_x); + int y1 = (ioy-first_y); + + if (draw_bold) { + if (fabs(x1) > fabs(y1)) { + window->DrawLine(iox, ioy+1, first_x, first_y+1, s_marker); + } + else { + window->DrawLine(iox+1, ioy, first_x+1, first_y, s_marker); + } + } + + if ((x1*x1 + y1*y1) > 2000) { + double dist = Point(first_loc - old_loc).length(); + double nav_x = first_loc.x * scale; + double nav_y = first_loc.y * scale; + + int imx = (int) (cx + (old_x+nav_x)/2 + ox); + int imy = (int) (cy + (old_y+nav_y)/2 + oy); + + char dist_txt[32]; + FormatNumber(dist_txt, dist); + font->SetColor(Color::Gray); + window->SetFont(font); + window->Print(imx-20, imy-6, dist_txt); + font->SetColor(Color::White); + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +MapView::DrawCombatantSystem(Combatant* c, Orbital* rgn, int x, int y, int r) +{ + int team = c->GetIFF(); + int x1 = 0; + int x2 = 0; + int y1 = y - r; + int a = 0; + + switch (team) { + case 0: x1 = x - 64; + x2 = x + 64; + y1 = y + r + 4; + a = DT_CENTER; + break; + + case 1: x1 = x - 200; + x2 = x - r - 4; + a = DT_RIGHT; + break; + + default: x1 = x + r + 4; + x2 = x + 200; + a = DT_LEFT; + break; + } + + DrawCombatGroupSystem(c->GetForce(), rgn, x1, x2, y1, a); +} + +// +--------------------------------------------------------------------+ + +void +MapView::DrawCombatGroupSystem(CombatGroup* group, Orbital* rgn, int x1, int x2, int& y, int a) +{ + if (!group || group->IsReserve() || group->CalcValue() < 1) + return; + + char txt[80]; + + if (group->GetRegion() == rgn->Name()) { + switch (group->Type()) { + case CombatGroup::CARRIER_GROUP: + case CombatGroup::BATTLE_GROUP: + case CombatGroup::DESTROYER_SQUADRON: + sprintf(txt, "%s '%s'", group->GetShortDescription(), group->Name().data()); + active_window->SetFont(font); + active_window->DrawText(txt, 0, Rect(x1, y, x2-x1, 12), a); + y += 10; + break; + + case CombatGroup::BATTALION: + case CombatGroup::STATION: + case CombatGroup::STARBASE: + + case CombatGroup::MINEFIELD: + case CombatGroup::BATTERY: + case CombatGroup::MISSILE: + + active_window->SetFont(font); + active_window->DrawText(group->GetShortDescription(), 0, Rect(x1, y, x2-x1, 12), a); + y += 10; + break; + + default: + break; + } + } + + ListIter iter = group->GetComponents(); + while (++iter) { + CombatGroup* g = iter.value(); + DrawCombatGroupSystem(g, rgn, x1, x2, y, a); + } +} + +// +--------------------------------------------------------------------+ + +void +MapView::DrawCombatGroup(CombatGroup* group, int rep) +{ + // does group even exist yet? + if (!group || group->IsReserve() || group->CalcValue() < 1) + return; + + // is group a squadron? don't draw squadrons on map: + if (group->Type() >= CombatGroup::WING && group->Type() < CombatGroup::FLEET) + return; + + // has group been discovered yet? + CombatGroup* player_group = campaign->GetPlayerGroup(); + if (group->GetIFF() && player_group && player_group->GetIFF() != group->GetIFF()) + if (group->IntelLevel() <= Intel::KNOWN) + return; + + // has group been destroyed already? + if (group->CalcValue() < 1) + return; + + OrbitalRegion* rgn = (OrbitalRegion*) regions[current_region]; + if (!rgn) return; + + POINT shiploc; + + double cx = rect.w/2; + double cy = rect.h/2; + + c = (cxRadius() * zoom; + + double scale = c/r; + + double ox = offset_x * scale; + double oy = offset_y * scale; + + double rlx = 0; + double rly = 0; + + int sprite_width = 10; + + if (group->GetUnits().size() > 0) { + CombatUnit* unit = 0; + + for (int i = 0; i < group->GetUnits().size(); i++) { + unit = group->GetUnits().at(i); + + if (unit->Count() - unit->DeadCount() > 0) + break; + } + + // draw unit icon: + if (unit->GetRegion() == rgn->Name() && unit->Type() > Ship::LCA && unit->Count() > 0) { + double sx = (unit->Location().x + rlx) * scale; + double sy = (unit->Location().y + rly) * scale; + + shiploc.x = (int) (cx + sx + ox); + shiploc.y = (int) (cy + sy + oy); + + bool ship_visible = shiploc.x >= 0 && shiploc.x < rect.w && + shiploc.y >= 0 && shiploc.y < rect.h; + + if (ship_visible) { + if (rep < 3) { + window->FillRect(shiploc.x-2, shiploc.y-2, shiploc.x+2, shiploc.y+2, unit->MarkerColor()); + sprite_width = 2; + + char buf[256]; + sprintf(buf, "%s", unit->Name().data()); + window->SetFont(font); + window->Print(shiploc.x-sprite_width, shiploc.y+sprite_width+2, buf); + } + else { + int sprite_index = 2; + int nsprites = 0; + + if (unit->GetDesign()) + nsprites = unit->GetDesign()->map_sprites.size(); + + if (nsprites) { + if (sprite_index < 0 || sprite_index >= nsprites) + sprite_index = sprite_index % nsprites; + + Bitmap* map_sprite = unit->GetDesign()->map_sprites[sprite_index]; + + Bitmap bmp; + bmp.CopyBitmap(*map_sprite); + ColorizeBitmap(bmp, unit->MarkerColor()); + sprite_width = bmp.Width()/2; + int h = bmp.Height()/2; + + window->DrawBitmap(shiploc.x-sprite_width, + shiploc.y-h, + shiploc.x+sprite_width, + shiploc.y+h, + &bmp, + Video::BLEND_ALPHA); + } + + else { + if (unit->IsStatic()) { + window->FillRect(shiploc.x-6, shiploc.y-6, shiploc.x+6, shiploc.y+6, unit->MarkerColor()); + window->DrawRect(shiploc.x-6, shiploc.y-6, shiploc.x+6, shiploc.y+6, Color::White); + } + else if (unit->IsStarship()) { + window->FillRect(shiploc.x-4, shiploc.y-4, shiploc.x+4, shiploc.y+4, unit->MarkerColor()); + window->DrawRect(shiploc.x-4, shiploc.y-4, shiploc.x+4, shiploc.y+4, Color::White); + } + else { + window->FillRect(shiploc.x-3, shiploc.y-3, shiploc.x+3, shiploc.y+3, unit->MarkerColor()); + window->DrawRect(shiploc.x-3, shiploc.y-3, shiploc.x+3, shiploc.y+3, Color::White); + } + } + + char label[128]; + strcpy(label, unit->GetDescription()); + window->SetFont(font); + window->Print(shiploc.x-sprite_width, shiploc.y+sprite_width+2, label); + } + } + } + } + + // recurse + ListIter iter = group->GetComponents(); + while (++iter) { + CombatGroup* g = iter.value(); + DrawCombatGroup(g, rep); + } +} + +// +--------------------------------------------------------------------+ + +bool +MapView::IsClutter(Ship& test) +{ + // get leader: + Ship* lead = test.GetLeader(); + + if (lead == &test) // this is the leader: + return false; + + // too close? + if (lead) { + POINT testloc, leadloc; + + GetShipLoc(test, testloc); + GetShipLoc(*lead, leadloc); + + double dx = testloc.x - leadloc.x; + double dy = testloc.y - leadloc.y; + double d = dx*dx + dy*dy; + + if (d <= 64) + return true; + } + + return false; +} + +// +--------------------------------------------------------------------+ + +bool +MapView::IsCrowded(Ship& test) +{ + POINT testloc, refloc; + Sim* sim = Sim::GetSim(); + Orbital* rgn = regions[current_region]; + SimRegion* simrgn = sim->FindRegion(rgn->Name()); + + if (simrgn) { + GetShipLoc(test, testloc); + + ListIter s = simrgn->Ships(); + while (++s) { + Ship* ref = s.value(); + + // too close? + if (ref && ref != &test) { + GetShipLoc(*ref, refloc); + + double dx = testloc.x - refloc.x; + double dy = testloc.y - refloc.y; + double d = dx*dx + dy*dy; + + if (d <= 64) + return true; + } + } + } + + return false; +} + +void +MapView::GetShipLoc(Ship& s, POINT& shiploc) +{ + double cx = rect.w/2; + double cy = rect.h/2; + + c = (cxRadius() * zoom; + + OrbitalRegion* rgn = (OrbitalRegion*) regions[current_region]; + + if (view_mode == VIEW_REGION) { + if (!rgn) return; + r = rgn->Radius() * zoom; + } + + double scale = c/r; + + double ox = offset_x * scale; + double oy = offset_y * scale; + + double rlx = 0; + double rly = 0; + + if (view_mode == VIEW_SYSTEM) { + rgn = system->ActiveRegion(); + + if (rgn) { + rlx = rgn->Location().x; + rly = rgn->Location().y; + } + } + + if (view_mode == VIEW_SYSTEM || + (view_mode == VIEW_REGION && rgn == s.GetRegion()->GetOrbitalRegion())) { + double sx = (s.Location().x + rlx) * scale; + double sy = (s.Location().y + rly) * scale; + + shiploc.x = (int) (cx + sx + ox); + shiploc.y = (int) (cy + sy + oy); + } + else { + shiploc.x = -1; + shiploc.y = -1; + } +} + +// +--------------------------------------------------------------------+ + +bool +MapView::IsCrowded(MissionElement& test) +{ + POINT testloc, refloc; + Sim* sim = Sim::GetSim(); + Orbital* rgn = regions[current_region]; + + if (mission) { + GetElemLoc(test, testloc); + + ListIter s = mission->GetElements(); + while (++s) { + MissionElement* ref = s.value(); + + if (ref && ref != &test && !stricmp(ref->Region(), rgn->Name())) { + GetElemLoc(*ref, refloc); + + double dx = testloc.x - refloc.x; + double dy = testloc.y - refloc.y; + double d = dx*dx + dy*dy; + + if (d <= 64) + return true; + } + } + } + + return false; +} + +void +MapView::GetElemLoc(MissionElement& s, POINT& shiploc) +{ + double cx = rect.w/2; + double cy = rect.h/2; + + c = (cxRadius() * zoom; + + OrbitalRegion* rgn = (OrbitalRegion*) regions[current_region]; + + if (view_mode == VIEW_REGION) { + if (!rgn) return; + r = rgn->Radius() * zoom; + } + + double scale = c/r; + + double ox = offset_x * scale; + double oy = offset_y * scale; + + double rlx = 0; + double rly = 0; + + if (view_mode == VIEW_SYSTEM) { + rgn = system->ActiveRegion(); + + if (rgn) { + rlx = rgn->Location().x; + rly = rgn->Location().y; + } + } + + if (view_mode == VIEW_SYSTEM || + (view_mode == VIEW_REGION && !stricmp(s.Region(), rgn->Name()))) { + double sx = (s.Location().x + rlx) * scale; + double sy = (s.Location().y + rly) * scale; + + shiploc.x = (int) (cx + sx + ox); + shiploc.y = (int) (cy + sy + oy); + } + else { + shiploc.x = -1; + shiploc.y = -1; + } +} + +// +--------------------------------------------------------------------+ + +OrbitalRegion* +MapView::GetRegion() const +{ + OrbitalRegion* result = 0; + + if (current_region < regions.size()) + result = (OrbitalRegion*) regions[current_region]; + + return result; +} + +// +--------------------------------------------------------------------+ + +void +MapView::ZoomIn() +{ + zoom *= 0.9; + + if (view_mode == VIEW_SYSTEM) { + if (system && zoom * system->Radius() < 2e6) { + zoom = 2e6 / system->Radius(); + } + } + else if (view_mode == VIEW_REGION) { + OrbitalRegion* rgn = GetRegion(); + if (rgn && zoom * rgn->Radius() < 1e3) { + zoom = 1e3 / rgn->Radius(); + } + } +} + +void +MapView::ZoomOut() +{ + zoom *= 1.1; + + if (view_mode == VIEW_SYSTEM) { + if (system && zoom * system->Radius() > 500e9) { + zoom = 500e9 / system->Radius(); + } + } + else if (view_mode == VIEW_REGION) { + OrbitalRegion* rgn = GetRegion(); + if (rgn && zoom * rgn->Radius() > 1e6) { + zoom = 1e6 / rgn->Radius(); + } + } +} + +// +--------------------------------------------------------------------+ + +void +MapView::OnShow() +{ + EventDispatch* dispatch = EventDispatch::GetInstance(); + if (dispatch) + dispatch->Register(this); +} + +void +MapView::OnHide() +{ + EventDispatch* dispatch = EventDispatch::GetInstance(); + if (dispatch) + dispatch->Unregister(this); + + if (captured) { + ReleaseCapture(); + captured = false; + Mouse::Show(true); + } + + dragging = false; +} + +// +--------------------------------------------------------------------+ + +bool +MapView::IsEnabled() const +{ + if (active_window) + return active_window->IsEnabled(); + + return false; +} + +bool +MapView::IsVisible() const +{ + if (active_window) + return active_window->IsVisible(); + + return false; +} + +bool +MapView::IsFormActive() const +{ + if (active_window) + return active_window->IsFormActive(); + + return false; +} + +Rect +MapView::TargetRect() const +{ + if (active_window) + return active_window->TargetRect(); + + return Rect(); +} + +// +--------------------------------------------------------------------+ + +int +MapView::OnMouseMove(int x, int y) +{ + if (captured) { + EventTarget* test = 0; + EventDispatch* dispatch = EventDispatch::GetInstance(); + if (dispatch) + test = dispatch->GetCapture(); + + if (test != this) { + captured = false; + Mouse::Show(true); + } + + else { + if (dragging) { + int delta_x = x - mouse_x; + int delta_y = y - mouse_y; + + offset_x += delta_x * r / c; + offset_y += delta_y * r / c; + + Mouse::SetCursorPos(mouse_x, mouse_y); + } + + else if (view_mode == VIEW_REGION) { + double scale = r/c; + click_x = (x - rect.x - rect.w/2) * scale - offset_x; + click_y = (y - rect.y - rect.h/2) * scale - offset_y; + + if ((adding_navpt || moving_navpt) && current_navpt) { + Point loc = current_navpt->Location(); + loc.x = click_x; + loc.y = click_y; + current_navpt->SetLocation(loc); + current_navpt->SetStatus(current_status); + } + + else if (editor && moving_elem && current_elem) { + Point loc = current_elem->Location(); + loc.x = click_x; + loc.y = click_y; + current_elem->SetLocation(loc); + } + } + } + } + + return active_window->OnMouseMove(x,y); +} + +// +--------------------------------------------------------------------+ + +int +MapView::OnRButtonDown(int x, int y) +{ + if (!captured) + captured = SetCapture(); + + if (captured) { + dragging = true; + mouse_x = x; + mouse_y = y; + Mouse::Show(false); + } + + return active_window->OnRButtonDown(x, y); +} + +// +--------------------------------------------------------------------+ + +int +MapView::OnRButtonUp(int x, int y) +{ + if (captured) { + ReleaseCapture(); + captured = false; + Mouse::Show(true); + } + + dragging = false; + + return active_window->OnRButtonUp(x, y); +} + +// +--------------------------------------------------------------------+ + +int MapView::OnClick() +{ + return active_window->OnClick(); +} + +int MapView::OnLButtonDown(int x, int y) +{ + if (menu_view && menu_view->IsShown()) { + // ignore this event... + } + else { + if (!captured) + captured = SetCapture(); + + if (view_mode == VIEW_REGION) { + double scale = r/c; + click_x = (x - rect.x - rect.w/2) * scale - offset_x; + click_y = (y - rect.y - rect.h/2) * scale - offset_y; + + if (current_navpt) { + Point nloc = current_navpt->Location(); + double dx = nloc.x - click_x; + double dy = nloc.y - click_y; + double d = sqrt(dx*dx + dy*dy); + + if (d < 5e3) { + moving_navpt = true; + + if (ship && current_ship && ship != current_ship) { + if (ship->GetElement() && current_ship->GetElement()) { + if (!ship->GetElement()->CanCommand(current_ship->GetElement())) { + moving_navpt = false; + } + } + } + else if (current_elem && NetLobby::GetInstance()) { + moving_navpt = false; + } + } + } + + else if (editor && current_elem) { + Point nloc = current_elem->Location(); + double dx = nloc.x - click_x; + double dy = nloc.y - click_y; + double d = sqrt(dx*dx + dy*dy); + + if (d < 5e3) { + moving_elem = true; + + if (current_elem && NetLobby::GetInstance()) { + moving_elem = false; + } + } + } + } + } + + return active_window->OnLButtonDown(x,y); +} + +int MapView::OnLButtonUp(int x, int y) +{ + bool process_event = false; + + if (captured) { + process_event = true; + ReleaseCapture(); + captured = false; + Mouse::Show(true); + } + + if (process_event && !adding_navpt) { + if (!moving_navpt && !moving_elem) { + Button::PlaySound(); + } + + if (view_mode == VIEW_REGION) { + double scale = r/c; + click_x = (x - rect.x - rect.w/2) * scale - offset_x; + click_y = (y - rect.y - rect.h/2) * scale - offset_y; + + if (!scrolling) + SelectAt(x,y); + + active_window->ClientEvent(EID_MAP_CLICK, x, y); + } + + else if (!scrolling) { + SelectAt(x,y); + + active_window->ClientEvent(EID_MAP_CLICK, x, y); + } + } + + if ((adding_navpt || moving_navpt) && current_navpt) { + current_navpt->SetStatus(current_status); + + Sim* sim = Sim::GetSim(); + if (sim) { + Ship* s = current_ship; + + if (s && s->GetElement()) { + Element* elem = s->GetElement(); + int index = elem->GetNavIndex(current_navpt); + + if (index >= 0) + NetUtil::SendNavData(false, elem, index-1, current_navpt); + } + } + } + + adding_navpt = false; + moving_navpt = false; + moving_elem = false; + + return active_window->OnLButtonUp(x,y); +} + + diff --git a/Stars45/MapView.h b/Stars45/MapView.h new file mode 100644 index 0000000..d48fd89 --- /dev/null +++ b/Stars45/MapView.h @@ -0,0 +1,223 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: MapView.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Star Map class +*/ + +#ifndef MapView_h +#define MapView_h + +#include "Types.h" +#include "SimObject.h" +#include "View.h" +#include "EventTarget.h" +#include "Bitmap.h" +#include "List.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class ActiveWindow; +class StarSystem; +class Orbital; +class OrbitalRegion; +class Ship; +class Instruction; +class Mission; +class MissionElement; +class Campaign; +class Combatant; +class CombatGroup; +class Menu; +class MenuItem; +class MenuView; +class Font; + +const int EID_MAP_CLICK = 1000; + +// +--------------------------------------------------------------------+ + +class MapView : public View, + public EventTarget, + public SimObserver +{ +public: + MapView(Window* win); + virtual ~MapView(); + + // Operations: + virtual void Refresh(); + virtual void OnWindowMove(); + virtual void OnShow(); + virtual void OnHide(); + + virtual void DrawTitle(); + virtual void DrawGalaxy(); + virtual void DrawSystem(); + virtual void DrawRegion(); + + virtual void DrawGrid(); + virtual void DrawOrbital(Orbital& orbital, int index); + virtual void DrawShip(Ship& ship, bool current=false, int rep=3); + virtual void DrawElem(MissionElement& elem, bool current=false, int rep=3); + virtual void DrawNavRoute(OrbitalRegion* rgn, + List& route, + Color smarker, + Ship* ship=0, + MissionElement* elem=0); + + virtual void DrawCombatantSystem(Combatant* c, Orbital* rgn, int x, int y, int r); + virtual void DrawCombatGroupSystem(CombatGroup* g, Orbital* rgn, int x1, int x2, int& y, int a); + virtual void DrawCombatGroup(CombatGroup* g, int rep=3); + + virtual int GetViewMode() const { return view_mode; } + virtual void SetViewMode(int mode); + virtual void SetSelectionMode(int mode); + virtual int GetSelectionMode() const { return seln_mode; } + virtual void SetSelection(int index); + virtual void SetSelectedShip(Ship* ship); + virtual void SetSelectedElem(MissionElement* elem); + virtual void SetRegion(OrbitalRegion* rgn); + virtual void SetRegionByName(const char* rgn_name); + virtual void SelectAt(int x, int y); + virtual Orbital* GetSelection(); + virtual Ship* GetSelectedShip(); + virtual MissionElement* GetSelectedElem(); + virtual int GetSelectionIndex(); + virtual void SetShipFilter(DWORD f) { ship_filter = f; } + + // Event Target Interface: + virtual int OnMouseMove(int x, int y); + virtual int OnLButtonDown(int x, int y); + virtual int OnLButtonUp(int x, int y); + virtual int OnClick(); + virtual int OnRButtonDown(int x, int y); + virtual int OnRButtonUp(int x, int y); + + virtual bool IsEnabled() const; + virtual bool IsVisible() const; + virtual bool IsFormActive() const; + virtual Rect TargetRect() const; + + void ZoomIn(); + void ZoomOut(); + + void SetGalaxy(List& systems); + void SetSystem(StarSystem* s); + void SetMission(Mission* m); + void SetShip(Ship* s); + void SetCampaign(Campaign* c); + + bool IsVisible(const Point& loc); + + // accessors: + virtual void GetClickLoc(double& x, double& y) { x = click_x; y = click_y; } + List& GetGalaxy() { return system_list; } + StarSystem* GetSystem() const { return system; } + OrbitalRegion* GetRegion() const; + + virtual bool Update(SimObject* obj); + virtual const char* GetObserverName() const { return "MapWin"; } + + bool GetEditorMode() const { return editor; } + void SetEditorMode(bool b) { editor = b; } + +protected: + virtual void BuildMenu(); + virtual void ClearMenu(); + virtual void ProcessMenuItem(int action); + virtual bool SetCapture(); + virtual bool ReleaseCapture(); + + virtual void DrawTabbedText(Font* font, const char* text); + + bool IsClutter(Ship& s); + bool IsCrowded(Ship& s); + bool IsCrowded(MissionElement& elem); + void GetShipLoc(Ship& s, POINT& loc); + void GetElemLoc(MissionElement& s, POINT& loc); + void SelectShip(Ship* selship); + void SelectElem(MissionElement* selelem); + void SelectNavpt(Instruction* navpt); + void FindShips(bool friendly, bool station, bool starship, bool dropship, + List& result); + void SetupScroll(Orbital* s); + + double GetMinRadius(int type); + + Text title; + Rect rect; + Campaign* campaign; + Mission* mission; + List system_list; + StarSystem* system; + List stars; + List planets; + List regions; + Ship* ship; + Bitmap galaxy_image; + bool editor; + + int current_star; + int current_planet; + int current_region; + Ship* current_ship; + MissionElement* current_elem; + Instruction* current_navpt; + int current_status; + + int view_mode; + int seln_mode; + bool captured; + bool dragging; + bool adding_navpt; + bool moving_navpt; + bool moving_elem; + int scrolling; + int mouse_x; + int mouse_y; + DWORD ship_filter; + + double zoom; + double view_zoom[3]; + double offset_x; + double offset_y; + double view_offset_x[3]; + double view_offset_y[3]; + double c, r; + double scroll_x; + double scroll_y; + double click_x; + double click_y; + + Font* font; + Font* title_font; + + ActiveWindow* active_window; + Menu* active_menu; + + Menu* map_menu; + Menu* map_system_menu; + Menu* map_sector_menu; + Menu* ship_menu; + Menu* nav_menu; + Menu* action_menu; + Menu* objective_menu; + Menu* formation_menu; + Menu* speed_menu; + Menu* hold_menu; + Menu* farcast_menu; + + MenuView* menu_view; +}; + +#endif MapView_h + diff --git a/Stars45/MenuDlg.cpp b/Stars45/MenuDlg.cpp new file mode 100644 index 0000000..436526d --- /dev/null +++ b/Stars45/MenuDlg.cpp @@ -0,0 +1,276 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: MenuDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Main Menu Dialog Active Window class +*/ + +#include "MemDebug.h" +#include "MenuDlg.h" +#include "MenuScreen.h" +#include "Starshatter.h" +#include "Campaign.h" + +#include "Game.h" +#include "DataLoader.h" +#include "Button.h" +#include "ListBox.h" +#include "Slider.h" +#include "Video.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "ParseUtil.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +DEF_MAP_CLIENT(MenuDlg, OnStart); +DEF_MAP_CLIENT(MenuDlg, OnCampaign); +DEF_MAP_CLIENT(MenuDlg, OnMission); +DEF_MAP_CLIENT(MenuDlg, OnPlayer); +DEF_MAP_CLIENT(MenuDlg, OnMultiplayer); +DEF_MAP_CLIENT(MenuDlg, OnMod); +DEF_MAP_CLIENT(MenuDlg, OnTacReference); +DEF_MAP_CLIENT(MenuDlg, OnVideo); +DEF_MAP_CLIENT(MenuDlg, OnOptions); +DEF_MAP_CLIENT(MenuDlg, OnControls); +DEF_MAP_CLIENT(MenuDlg, OnQuit); +DEF_MAP_CLIENT(MenuDlg, OnButtonEnter); +DEF_MAP_CLIENT(MenuDlg, OnButtonExit); + +extern const char* versionInfo; + +// +--------------------------------------------------------------------+ + +MenuDlg::MenuDlg(Screen* s, FormDef& def, MenuScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), + manager(mgr), btn_start(0), + btn_campaign(0), btn_mission(0), btn_player(0), btn_multi(0), + btn_mod(0), btn_tac(0), + btn_options(0), btn_controls(0), btn_quit(0), + version(0), description(0), stars(0), campaign(0) +{ + stars = Starshatter::GetInstance(); + campaign = Campaign::GetCampaign(); + + Init(def); +} + +MenuDlg::~MenuDlg() +{ +} + +// +--------------------------------------------------------------------+ + +void +MenuDlg::RegisterControls() +{ + if (btn_start) + return; + + btn_start = (Button*) FindControl(120); + btn_campaign = (Button*) FindControl(101); + btn_mission = (Button*) FindControl(102); + btn_player = (Button*) FindControl(103); + btn_multi = (Button*) FindControl(104); + btn_video = (Button*) FindControl(111); + btn_options = (Button*) FindControl(112); + btn_controls = (Button*) FindControl(113); + btn_quit = (Button*) FindControl(114); + btn_mod = (Button*) FindControl(115); + btn_tac = (Button*) FindControl(116); + + if (btn_start) { + REGISTER_CLIENT(EID_CLICK, btn_start, MenuDlg, OnStart); + REGISTER_CLIENT(EID_MOUSE_ENTER, btn_start, MenuDlg, OnButtonEnter); + REGISTER_CLIENT(EID_MOUSE_EXIT, btn_start, MenuDlg, OnButtonExit); + } + + if (btn_campaign) { + REGISTER_CLIENT(EID_CLICK, btn_campaign, MenuDlg, OnCampaign); + REGISTER_CLIENT(EID_MOUSE_ENTER, btn_campaign, MenuDlg, OnButtonEnter); + REGISTER_CLIENT(EID_MOUSE_EXIT, btn_campaign, MenuDlg, OnButtonExit); + } + + if (btn_mission) { + REGISTER_CLIENT(EID_CLICK, btn_mission, MenuDlg, OnMission); + REGISTER_CLIENT(EID_MOUSE_ENTER, btn_mission, MenuDlg, OnButtonEnter); + REGISTER_CLIENT(EID_MOUSE_EXIT, btn_mission, MenuDlg, OnButtonExit); + } + + if (btn_player) { + REGISTER_CLIENT(EID_CLICK, btn_player, MenuDlg, OnPlayer); + REGISTER_CLIENT(EID_MOUSE_ENTER, btn_player, MenuDlg, OnButtonEnter); + REGISTER_CLIENT(EID_MOUSE_EXIT, btn_player, MenuDlg, OnButtonExit); + } + + if (btn_multi) { + REGISTER_CLIENT(EID_CLICK, btn_multi, MenuDlg, OnMultiplayer); + REGISTER_CLIENT(EID_MOUSE_ENTER, btn_multi, MenuDlg, OnButtonEnter); + REGISTER_CLIENT(EID_MOUSE_EXIT, btn_multi, MenuDlg, OnButtonExit); + } + + if (btn_video) { + REGISTER_CLIENT(EID_CLICK, btn_video, MenuDlg, OnVideo); + REGISTER_CLIENT(EID_MOUSE_ENTER, btn_video, MenuDlg, OnButtonEnter); + REGISTER_CLIENT(EID_MOUSE_EXIT, btn_video, MenuDlg, OnButtonExit); + } + + if (btn_options) { + REGISTER_CLIENT(EID_CLICK, btn_options, MenuDlg, OnOptions); + REGISTER_CLIENT(EID_MOUSE_ENTER, btn_options, MenuDlg, OnButtonEnter); + REGISTER_CLIENT(EID_MOUSE_EXIT, btn_options, MenuDlg, OnButtonExit); + } + + if (btn_controls) { + REGISTER_CLIENT(EID_CLICK, btn_controls, MenuDlg, OnControls); + REGISTER_CLIENT(EID_MOUSE_ENTER, btn_controls, MenuDlg, OnButtonEnter); + REGISTER_CLIENT(EID_MOUSE_EXIT, btn_controls, MenuDlg, OnButtonExit); + } + + if (btn_mod) { + REGISTER_CLIENT(EID_CLICK, btn_mod, MenuDlg, OnMod); + REGISTER_CLIENT(EID_MOUSE_ENTER, btn_mod, MenuDlg, OnButtonEnter); + REGISTER_CLIENT(EID_MOUSE_EXIT, btn_mod, MenuDlg, OnButtonExit); + } + + if (btn_tac) { + REGISTER_CLIENT(EID_CLICK, btn_tac, MenuDlg, OnTacReference); + REGISTER_CLIENT(EID_MOUSE_ENTER, btn_tac, MenuDlg, OnButtonEnter); + REGISTER_CLIENT(EID_MOUSE_EXIT, btn_tac, MenuDlg, OnButtonExit); + } + + if (btn_quit) { + REGISTER_CLIENT(EID_CLICK, btn_quit, MenuDlg, OnQuit); + REGISTER_CLIENT(EID_MOUSE_ENTER, btn_quit, MenuDlg, OnButtonEnter); + REGISTER_CLIENT(EID_MOUSE_EXIT, btn_quit, MenuDlg, OnButtonExit); + } + + version = FindControl(100); + + if (version) { + version->SetText(versionInfo); + } + + description = FindControl(202); +} + +// +--------------------------------------------------------------------+ + +void +MenuDlg::Show() +{ + FormWindow::Show(); + + if (btn_multi && Starshatter::UseFileSystem()) + btn_multi->SetEnabled(false); +} + +void +MenuDlg::ExecFrame() +{ +} + +// +--------------------------------------------------------------------+ + +void +MenuDlg::OnStart(AWEvent* event) +{ + if (description) description->SetText(""); + stars->StartOrResumeGame(); +} + +void +MenuDlg::OnCampaign(AWEvent* event) +{ + if (description) description->SetText(""); + manager->ShowCmpSelectDlg(); +} + +void +MenuDlg::OnMission(AWEvent* event) +{ + if (description) description->SetText(""); + manager->ShowMsnSelectDlg(); +} + +void +MenuDlg::OnPlayer(AWEvent* event) +{ + if (description) description->SetText(""); + manager->ShowPlayerDlg(); +} + +void +MenuDlg::OnMultiplayer(AWEvent* event) +{ + if (description) description->SetText(""); + manager->ShowNetClientDlg(); +} + +void +MenuDlg::OnVideo(AWEvent* event) +{ + if (description) description->SetText(""); + manager->ShowVidDlg(); +} + +void +MenuDlg::OnOptions(AWEvent* event) +{ + if (description) description->SetText(""); + manager->ShowOptDlg(); +} + +void +MenuDlg::OnControls(AWEvent* event) +{ + if (description) description->SetText(""); + manager->ShowCtlDlg(); +} + +void +MenuDlg::OnMod(AWEvent* event) +{ + if (description) description->SetText(""); + manager->ShowModDlg(); +} + +void +MenuDlg::OnTacReference(AWEvent* event) +{ + if (description) description->SetText(""); + stars->OpenTacticalReference(); +} + +void +MenuDlg::OnQuit(AWEvent* event) +{ + if (description) description->SetText(""); + manager->ShowExitDlg(); +} + +// +--------------------------------------------------------------------+ + +void +MenuDlg::OnButtonEnter(AWEvent* event) +{ + ActiveWindow* src = event->window; + + if (src && description) + description->SetText(src->GetAltText()); +} + +void +MenuDlg::OnButtonExit(AWEvent* event) +{ + if (description) + description->SetText(""); +} diff --git a/Stars45/MenuDlg.h b/Stars45/MenuDlg.h new file mode 100644 index 0000000..be21feb --- /dev/null +++ b/Stars45/MenuDlg.h @@ -0,0 +1,86 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: MenuDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Main Menu Dialog Active Window class +*/ + +#ifndef MenuDlg_h +#define MenuDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "Bitmap.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Font.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class MenuScreen; +class Campaign; +class Starshatter; + +// +--------------------------------------------------------------------+ + +class MenuDlg : public FormWindow +{ +public: + MenuDlg(Screen* s, FormDef& def, MenuScreen* mgr); + virtual ~MenuDlg(); + + virtual void RegisterControls(); + virtual void Show(); + virtual void ExecFrame(); + + // Operations: + virtual void OnStart(AWEvent* event); + virtual void OnCampaign(AWEvent* event); + virtual void OnMission(AWEvent* event); + virtual void OnPlayer(AWEvent* event); + virtual void OnMultiplayer(AWEvent* event); + virtual void OnMod(AWEvent* event); + virtual void OnTacReference(AWEvent* event); + + virtual void OnVideo(AWEvent* event); + virtual void OnOptions(AWEvent* event); + virtual void OnControls(AWEvent* event); + virtual void OnQuit(AWEvent* event); + + virtual void OnButtonEnter(AWEvent* event); + virtual void OnButtonExit(AWEvent* event); + +protected: + MenuScreen* manager; + + Button* btn_start; + Button* btn_campaign; + Button* btn_mission; + Button* btn_player; + Button* btn_multi; + Button* btn_mod; + Button* btn_tac; + + Button* btn_video; + Button* btn_options; + Button* btn_controls; + Button* btn_quit; + + ActiveWindow* version; + ActiveWindow* description; + + Starshatter* stars; + Campaign* campaign; +}; + +#endif MenuDlg_h + diff --git a/Stars45/MenuScreen.cpp b/Stars45/MenuScreen.cpp new file mode 100644 index 0000000..b84ac6a --- /dev/null +++ b/Stars45/MenuScreen.cpp @@ -0,0 +1,1076 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: main.cpp + AUTHOR: John DiCamillo + +*/ + +#include "MemDebug.h" +#include "MenuScreen.h" + +#include "FormDef.h" +#include "MenuDlg.h" + +#include "ExitDlg.h" +#include "AudDlg.h" +#include "VidDlg.h" +#include "OptDlg.h" +#include "CtlDlg.h" +#include "JoyDlg.h" +#include "KeyDlg.h" +#include "ConfirmDlg.h" +#include "PlayerDlg.h" +#include "ModDlg.h" +#include "ModInfoDlg.h" +#include "MsnSelectDlg.h" +#include "MsnEditDlg.h" +#include "MsnElemDlg.h" +#include "MsnEventDlg.h" +#include "MsnEditNavDlg.h" +#include "FirstTimeDlg.h" +#include "AwardShowDlg.h" +#include "LoadDlg.h" +#include "TacRefDlg.h" + +#include "CmpSelectDlg.h" +#include "NetClientDlg.h" +#include "NetLobbyDlg.h" +#include "NetServerDlg.h" +#include "NetUnitDlg.h" +#include "NetAddrDlg.h" +#include "NetPassDlg.h" + +#include "Starshatter.h" +#include "Player.h" +#include "NetLobby.h" +#include "NetClientConfig.h" +#include "NetServerConfig.h" + +#include "Game.h" +#include "Video.h" +#include "Screen.h" +#include "ActiveWindow.h" +#include "Mouse.h" +#include "Keyboard.h" +#include "FadeView.h" +#include "Color.h" +#include "Bitmap.h" +#include "Font.h" +#include "FontMgr.h" +#include "EventDispatch.h" +#include "DataLoader.h" +#include "Resource.h" + +// +--------------------------------------------------------------------+ + +MenuScreen::MenuScreen() + : screen(0), menudlg(0), + fadewin(0), fadeview(0), exitdlg(0), + auddlg(0), viddlg(0), optdlg(0), ctldlg(0), joydlg(0), keydlg(0), + playdlg(0), confirmdlg(0), firstdlg(0), awarddlg(0), cmpSelectDlg(0), + msnSelectDlg(0), modDlg(0), modInfoDlg(0), + msnEditDlg(0), msnElemDlg(0), msnEventDlg(0), msnEditNavDlg(0), + netClientDlg(0), netAddrDlg(0), netPassDlg(0), netLobbyDlg(0), netServerDlg(0), + netUnitDlg(0), loadDlg(0), tacRefDlg(0), current_dlg(0), isShown(false) +{ + loader = DataLoader::GetLoader(); +} + +MenuScreen::~MenuScreen() +{ + TearDown(); +} + +// +--------------------------------------------------------------------+ + +void +MenuScreen::Setup(Screen* s) +{ + if (!s) + return; + + screen = s; + + Color::SetFade(0); + + // create windows + + loader->UseFileSystem(true); + + FormDef menu_def("MenuDlg", 0); + menu_def.Load("MenuDlg"); + menudlg = new(__FILE__,__LINE__) MenuDlg(screen, menu_def, this); + + FormDef exit_def("ExitDlg", 0); + exit_def.Load("ExitDlg"); + exitdlg = new(__FILE__,__LINE__) ExitDlg(screen, exit_def, this); + + FormDef aud_def("AudDlg", 0); + aud_def.Load("AudDlg"); + auddlg = new(__FILE__,__LINE__) AudDlg(screen, aud_def, this); + + FormDef ctl_def("CtlDlg", 0); + ctl_def.Load("CtlDlg"); + ctldlg = new(__FILE__,__LINE__) CtlDlg(screen, ctl_def, this); + + FormDef opt_def("OptDlg", 0); + opt_def.Load("OptDlg"); + optdlg = new(__FILE__,__LINE__) OptDlg(screen, opt_def, this); + + FormDef vid_def("VidDlg", 0); + vid_def.Load("VidDlg"); + viddlg = new(__FILE__,__LINE__) VidDlg(screen, vid_def, this); + + FormDef mod_def("ModDlg", 0); + mod_def.Load("ModDlg"); + modDlg = new(__FILE__,__LINE__) ModDlg(screen, mod_def, this); + + FormDef tac_ref_def("TacRefDlg", 0); + tac_ref_def.Load("TacRefDlg"); + tacRefDlg = new(__FILE__,__LINE__) TacRefDlg(screen, tac_ref_def, this); + + FormDef cmp_sel_def("CmpSelectDlg", 0); + cmp_sel_def.Load("CmpSelectDlg"); + cmpSelectDlg = new(__FILE__,__LINE__) CmpSelectDlg(screen, cmp_sel_def, this); + + FormDef net_lobby_def("NetLobbyDlg", 0); + net_lobby_def.Load("NetLobbyDlg"); + netLobbyDlg = new(__FILE__,__LINE__) NetLobbyDlg(screen, net_lobby_def, this); + + FormDef net_client_def("NetClientDlg", 0); + net_client_def.Load("NetClientDlg"); + netClientDlg = new(__FILE__,__LINE__) NetClientDlg(screen, net_client_def, this); + + FormDef net_server_def("NetServerDlg", 0); + net_server_def.Load("NetServerDlg"); + netServerDlg = new(__FILE__,__LINE__) NetServerDlg(screen, net_server_def, this); + + FormDef net_unit_def("NetUnitDlg", 0); + net_unit_def.Load("NetUnitDlg"); + netUnitDlg = new(__FILE__,__LINE__) NetUnitDlg(screen, net_unit_def, this); + + FormDef net_addr_def("NetAddrDlg", 0); + net_addr_def.Load("NetAddrDlg"); + netAddrDlg = new(__FILE__,__LINE__) NetAddrDlg(screen, net_addr_def, this); + + FormDef net_pass_def("NetPassDlg", 0); + net_pass_def.Load("NetPassDlg"); + netPassDlg = new(__FILE__,__LINE__) NetPassDlg(screen, net_pass_def, this); + + FormDef msn_edit_def("MsnEditDlg", 0); + msn_edit_def.Load("MsnEditDlg"); + msnEditDlg = new(__FILE__,__LINE__) MsnEditDlg(screen, msn_edit_def, this); + + FormDef msn_nav_def("MsnEditNavDlg", 0); + msn_nav_def.Load("MsnEditNavDlg"); + msnEditNavDlg = new(__FILE__,__LINE__) MsnEditNavDlg(screen, msn_nav_def, this); + + FormDef msn_elem_def("MsnElemDlg", 0); + msn_elem_def.Load("MsnElemDlg"); + msnElemDlg = new(__FILE__,__LINE__) MsnElemDlg(screen, msn_elem_def, this); + + FormDef msn_event_def("MsnEventDlg", 0); + msn_event_def.Load("MsnEventDlg"); + msnEventDlg = new(__FILE__,__LINE__) MsnEventDlg(screen, msn_event_def, this); + + FormDef msn_def("MsnSelectDlg", 0); + msn_def.Load("MsnSelectDlg"); + msnSelectDlg = new(__FILE__,__LINE__) MsnSelectDlg(screen, msn_def, this); + + FormDef player_def("PlayerDlg", 0); + player_def.Load("PlayerDlg"); + playdlg = new(__FILE__,__LINE__) PlayerDlg(screen, player_def, this); + + FormDef award_def("AwardDlg", 0); + award_def.Load("AwardDlg"); + awarddlg = new(__FILE__,__LINE__) AwardShowDlg(screen, award_def, this); + + FormDef joy_def("JoyDlg", 0); + joy_def.Load("JoyDlg"); + joydlg = new(__FILE__,__LINE__) JoyDlg(screen, joy_def, this); + + FormDef key_def("KeyDlg", 0); + key_def.Load("KeyDlg"); + keydlg = new(__FILE__,__LINE__) KeyDlg(screen, key_def, this); + + FormDef first_def("FirstTimeDlg", 0); + first_def.Load("FirstTimeDlg"); + firstdlg = new(__FILE__,__LINE__) FirstTimeDlg(screen, first_def, this); + + FormDef mod_info_def("ModInfoDlg", 0); + mod_info_def.Load("ModInfoDlg"); + modInfoDlg = new(__FILE__,__LINE__) ModInfoDlg(screen, mod_info_def, this); + + FormDef confirm_def("ConfirmDlg", 0); + confirm_def.Load("ConfirmDlg"); + confirmdlg = new(__FILE__,__LINE__) ConfirmDlg(screen, confirm_def, this); + + FormDef load_def("LoadDlg", 0); + load_def.Load("LoadDlg"); + loadDlg = new(__FILE__,__LINE__) LoadDlg(screen, load_def); + + fadewin = new(__FILE__,__LINE__) Window(screen, 0, 0, 1, 1); + fadeview = new(__FILE__,__LINE__) FadeView(fadewin, 2, 0, 0); + fadewin->AddView(fadeview); + screen->AddWindow(fadewin); + + loader->UseFileSystem(Starshatter::UseFileSystem()); + + ShowMenuDlg(); +} + +// +--------------------------------------------------------------------+ + +void +MenuScreen::TearDown() +{ + if (screen) { + if (menudlg) screen->DelWindow(menudlg); + if (netClientDlg) screen->DelWindow(netClientDlg); + if (netAddrDlg) screen->DelWindow(netAddrDlg); + if (netPassDlg) screen->DelWindow(netPassDlg); + if (netLobbyDlg) screen->DelWindow(netLobbyDlg); + if (netServerDlg) screen->DelWindow(netServerDlg); + if (netUnitDlg) screen->DelWindow(netUnitDlg); + + if (cmpSelectDlg) screen->DelWindow(cmpSelectDlg); + if (awarddlg) screen->DelWindow(awarddlg); + if (firstdlg) screen->DelWindow(firstdlg); + if (msnSelectDlg) screen->DelWindow(msnSelectDlg); + if (msnEditDlg) screen->DelWindow(msnEditDlg); + if (msnElemDlg) screen->DelWindow(msnElemDlg); + if (msnEventDlg) screen->DelWindow(msnEventDlg); + if (msnEditNavDlg) screen->DelWindow(msnEditNavDlg); + if (tacRefDlg) screen->DelWindow(tacRefDlg); + if (loadDlg) screen->DelWindow(loadDlg); + + if (auddlg) screen->DelWindow(auddlg); + if (viddlg) screen->DelWindow(viddlg); + if (optdlg) screen->DelWindow(optdlg); + if (ctldlg) screen->DelWindow(ctldlg); + if (modDlg) screen->DelWindow(modDlg); + if (modInfoDlg) screen->DelWindow(modInfoDlg); + if (joydlg) screen->DelWindow(joydlg); + if (keydlg) screen->DelWindow(keydlg); + if (exitdlg) screen->DelWindow(exitdlg); + if (playdlg) screen->DelWindow(playdlg); + if (confirmdlg) screen->DelWindow(confirmdlg); + if (fadewin) screen->DelWindow(fadewin); + } + + delete menudlg; + delete fadewin; + delete exitdlg; + delete auddlg; + delete viddlg; + delete optdlg; + delete ctldlg; + delete modDlg; + delete modInfoDlg; + delete joydlg; + delete keydlg; + delete playdlg; + delete confirmdlg; + + delete netClientDlg; + delete netAddrDlg; + delete netPassDlg; + delete netLobbyDlg; + delete netServerDlg; + delete netUnitDlg; + delete msnSelectDlg; + delete msnEditDlg; + delete msnElemDlg; + delete msnEventDlg; + delete msnEditNavDlg; + delete tacRefDlg; + delete loadDlg; + delete firstdlg; + delete awarddlg; + delete cmpSelectDlg; + + screen = 0; + fadewin = 0; + fadeview = 0; + menudlg = 0; + exitdlg = 0; + playdlg = 0; + confirmdlg = 0; + msnSelectDlg = 0; + msnEditDlg = 0; + msnElemDlg = 0; + msnEventDlg = 0; + msnEditNavDlg = 0; + cmpSelectDlg = 0; + awarddlg = 0; + firstdlg = 0; + netClientDlg = 0; + netAddrDlg = 0; + netPassDlg = 0; + netLobbyDlg = 0; + netServerDlg = 0; + netUnitDlg = 0; + loadDlg = 0; + tacRefDlg = 0; + + auddlg = 0; + viddlg = 0; + optdlg = 0; + ctldlg = 0; + modDlg = 0; + modInfoDlg = 0; + joydlg = 0; + keydlg = 0; + + screen = 0; +} + +// +--------------------------------------------------------------------+ + +void +MenuScreen::ExecFrame() +{ + Game::SetScreenColor(Color::Black); + + if (menudlg && menudlg->IsShown()) + menudlg->ExecFrame(); + + if (exitdlg && exitdlg->IsShown()) + exitdlg->ExecFrame(); + + if (joydlg && joydlg->IsShown()) + joydlg->ExecFrame(); + + if (keydlg && keydlg->IsShown()) + keydlg->ExecFrame(); + + if (ctldlg && ctldlg->IsShown()) + ctldlg->ExecFrame(); + + if (optdlg && optdlg->IsShown()) + optdlg->ExecFrame(); + + if (auddlg && auddlg->IsShown()) + auddlg->ExecFrame(); + + if (viddlg && viddlg->IsShown()) + viddlg->ExecFrame(); + + if (confirmdlg && confirmdlg->IsShown()) + confirmdlg->ExecFrame(); + + if (playdlg && playdlg->IsShown()) + playdlg->ExecFrame(); + + if (msnSelectDlg && msnSelectDlg->IsShown()) + msnSelectDlg->ExecFrame(); + + if (msnEditNavDlg && msnEditNavDlg->IsShown()) + msnEditNavDlg->ExecFrame(); + + if (firstdlg && firstdlg->IsShown()) + firstdlg->ExecFrame(); + + if (awarddlg && awarddlg->IsShown()) + awarddlg->ExecFrame(); + + if (cmpSelectDlg && cmpSelectDlg->IsShown()) + cmpSelectDlg->ExecFrame(); + + if (netClientDlg && netClientDlg->IsShown()) + netClientDlg->ExecFrame(); + + if (netAddrDlg && netAddrDlg->IsShown()) + netAddrDlg->ExecFrame(); + + if (netPassDlg && netPassDlg->IsShown()) + netPassDlg->ExecFrame(); + + if (netLobbyDlg && netLobbyDlg->IsShown()) + netLobbyDlg->ExecFrame(); + + if (netServerDlg && netServerDlg->IsShown()) + netServerDlg->ExecFrame(); + + if (netUnitDlg && netUnitDlg->IsShown()) + netUnitDlg->ExecFrame(); + + if (loadDlg && loadDlg->IsShown()) + loadDlg->ExecFrame(); + + if (tacRefDlg && tacRefDlg->IsShown()) + tacRefDlg->ExecFrame(); +} + +// +--------------------------------------------------------------------+ + +bool +MenuScreen::CloseTopmost() +{ + bool processed = false; + if (joydlg && joydlg->IsShown()) { + ShowCtlDlg(); + processed = true; + } + + else if (keydlg && keydlg->IsShown()) { + ShowCtlDlg(); + processed = true; + } + + else if (msnElemDlg && msnElemDlg->IsShown()) { + HideMsnElemDlg(); + processed = true; + } + + else if (msnEventDlg && msnEventDlg->IsShown()) { + HideMsnEventDlg(); + processed = true; + } + + else if (netAddrDlg && netAddrDlg->IsShown()) { + ShowNetClientDlg(); + processed = true; + } + + else if (netPassDlg && netPassDlg->IsShown()) { + ShowNetClientDlg(); + processed = true; + } + + else if (netServerDlg && netServerDlg->IsShown()) { + ShowNetClientDlg(); + processed = true; + } + + else if (netUnitDlg && netUnitDlg->IsShown()) { + netUnitDlg->OnCancel(0); + processed = true; + } + + else if (netLobbyDlg && netLobbyDlg->IsShown()) { + netLobbyDlg->OnCancel(0); + processed = true; + } + + else if (netClientDlg && netClientDlg->IsShown()) { + netClientDlg->OnCancel(0); + processed = true; + } + + else if (exitdlg && exitdlg->IsShown()) { + // key_exit is handled in the exit dlg... + } + + else if (cmpSelectDlg && cmpSelectDlg->IsShown()) { + if (cmpSelectDlg->CanClose()) + ShowMenuDlg(); + + processed = true; + } + + else if (menudlg && !menudlg->IsShown()) { + ShowMenuDlg(); + processed = true; + } + + return processed; +} + +void +MenuScreen::Show() +{ + if (!isShown) { + Starshatter* stars = Starshatter::GetInstance(); + NetLobby* lobby = NetLobby::GetInstance(); + + if (lobby) { + ShowNetLobbyDlg(); + } + else if (current_dlg == msnSelectDlg) { + ShowMsnSelectDlg(); + } + else { + if (Player::ConfigExists()) { + ShowMenuDlg(); + } + + else { + ShowFirstTimeDlg(); + } + } + + isShown = true; + } +} + +void +MenuScreen::Hide() +{ + if (isShown) { + HideAll(); + isShown = false; + } +} + +// +--------------------------------------------------------------------+ + +void +MenuScreen::ShowMenuDlg() +{ + HideAll(); + if (menudlg) { + menudlg->Show(); + menudlg->SetTopMost(true); + } + Mouse::Show(true); +} + +// +--------------------------------------------------------------------+ + +void +MenuScreen::ShowCmpSelectDlg() +{ + HideAll(); + current_dlg = cmpSelectDlg; + if (cmpSelectDlg) + cmpSelectDlg->Show(); + Mouse::Show(true); +} + +// +--------------------------------------------------------------------+ + +void +MenuScreen::ShowMsnSelectDlg() +{ + HideAll(); + current_dlg = msnSelectDlg; + + if (msnSelectDlg) + msnSelectDlg->Show(); + + if (msnEditNavDlg) + msnEditNavDlg->SetMission(0); + + Mouse::Show(true); +} + +// +--------------------------------------------------------------------+ + +void +MenuScreen::ShowModDlg() +{ + if (modDlg) { + HideAll(); + modDlg->Show(); + modDlg->SetTopMost(true); + Mouse::Show(true); + } + else { + ShowMsnSelectDlg(); + } +} + +// +--------------------------------------------------------------------+ + +void +MenuScreen::ShowModInfoDlg() +{ + if (modDlg && modInfoDlg) { + HideAll(); + modDlg->Show(); + modDlg->SetTopMost(false); + modInfoDlg->Show(); + Mouse::Show(true); + } + else { + ShowMsnSelectDlg(); + } +} + +// +--------------------------------------------------------------------+ + +void +MenuScreen::ShowMsnEditDlg() +{ + if (msnEditDlg) { + bool nav_shown = false; + if (msnEditNavDlg && msnEditNavDlg->IsShown()) + nav_shown = true; + + HideAll(); + + if (nav_shown) { + msnEditNavDlg->Show(); + msnEditNavDlg->SetTopMost(true); + } + else { + msnEditDlg->Show(); + msnEditDlg->SetTopMost(true); + } + + Mouse::Show(true); + } + else { + ShowMsnSelectDlg(); + } +} + +// +--------------------------------------------------------------------+ + +void +MenuScreen::ShowMsnElemDlg() +{ + if (msnElemDlg) { + if (msnEditDlg && msnEditDlg->IsShown()) + msnEditDlg->SetTopMost(false); + + if (msnEditNavDlg && msnEditNavDlg->IsShown()) + msnEditNavDlg->SetTopMost(false); + + msnElemDlg->Show(); + msnElemDlg->SetTopMost(true); + Mouse::Show(true); + } +} + +// +--------------------------------------------------------------------+ + +void +MenuScreen::ShowMsnEventDlg() +{ + if (msnEventDlg) { + if (msnEditDlg && msnEditDlg->IsShown()) + msnEditDlg->SetTopMost(false); + + if (msnEditNavDlg && msnEditNavDlg->IsShown()) + msnEditNavDlg->SetTopMost(false); + + msnEventDlg->Show(); + msnEventDlg->SetTopMost(true); + Mouse::Show(true); + } +} + +// +--------------------------------------------------------------------+ + +void +MenuScreen::ShowNavDlg() +{ + if (msnEditNavDlg && !msnEditNavDlg->IsShown()) { + HideAll(); + msnEditNavDlg->Show(); + Mouse::Show(true); + } +} + +// +--------------------------------------------------------------------+ + +void +MenuScreen::ShowFirstTimeDlg() +{ + HideAll(); + if (menudlg && firstdlg) { + menudlg->Show(); + menudlg->SetTopMost(false); + + firstdlg->Show(); + firstdlg->SetTopMost(true); + } + + Mouse::Show(true); +} + +// +--------------------------------------------------------------------+ + +void +MenuScreen::ShowPlayerDlg() +{ + if (playdlg) { + HideAll(); + playdlg->Show(); + } + Mouse::Show(true); +} + +// +--------------------------------------------------------------------+ + +void +MenuScreen::ShowAwardDlg() +{ + if (awarddlg) { + HideAll(); + awarddlg->Show(); + } + Mouse::Show(true); +} + +// +--------------------------------------------------------------------+ + +void +MenuScreen::ShowTacRefDlg() +{ + if (tacRefDlg) { + HideAll(); + tacRefDlg->Show(); + } + Mouse::Show(true); +} + +// +--------------------------------------------------------------------+ + +void +MenuScreen::ShowNetClientDlg() +{ + if (netClientDlg) { + HideAll(); + netClientDlg->Show(); + netClientDlg->SetTopMost(true); + } + + Mouse::Show(true); +} + +// +--------------------------------------------------------------------+ + +void +MenuScreen::ShowNetAddrDlg() +{ + if (netAddrDlg) { + if (netClientDlg) { + netClientDlg->Show(); + netClientDlg->SetTopMost(false); + } + + netAddrDlg->Show(); + } + + Mouse::Show(true); +} + +// +--------------------------------------------------------------------+ + +void +MenuScreen::ShowNetPassDlg() +{ + if (netPassDlg) { + ShowNetClientDlg(); + if (netClientDlg) + netClientDlg->SetTopMost(false); + + netPassDlg->Show(); + } + + Mouse::Show(true); +} + +// +--------------------------------------------------------------------+ + +void +MenuScreen::ShowNetLobbyDlg() +{ + if (netLobbyDlg) { + HideAll(); + netLobbyDlg->Show(); + netLobbyDlg->SetTopMost(true); + } + + Mouse::Show(true); +} + +// +--------------------------------------------------------------------+ + +void +MenuScreen::ShowNetServerDlg() +{ + if (netServerDlg) { + netServerDlg->Show(); + netServerDlg->SetTopMost(true); + } + + Mouse::Show(true); +} + +// +--------------------------------------------------------------------+ + +void +MenuScreen::ShowNetUnitDlg() +{ + if (netUnitDlg) { + HideAll(); + netUnitDlg->Show(); + netUnitDlg->SetTopMost(true); + } + + Mouse::Show(true); +} + +// +--------------------------------------------------------------------+ + +void +MenuScreen::ShowAudDlg() +{ + HideAll(); + if (auddlg) + auddlg->Show(); + Mouse::Show(true); +} + +// +--------------------------------------------------------------------+ + +void +MenuScreen::ShowVidDlg() +{ + HideAll(); + if (viddlg) + viddlg->Show(); + Mouse::Show(true); +} + +// +--------------------------------------------------------------------+ + +void +MenuScreen::ShowOptDlg() +{ + HideAll(); + if (optdlg) + optdlg->Show(); + Mouse::Show(true); +} + +// +--------------------------------------------------------------------+ + +void +MenuScreen::ShowCtlDlg() +{ + HideAll(); + if (ctldlg) { + ctldlg->Show(); + ctldlg->SetTopMost(true); + } + Mouse::Show(true); +} + +// +--------------------------------------------------------------------+ + +void +MenuScreen::ShowJoyDlg() +{ + HideAll(); + if (ctldlg) { + ctldlg->Show(); + ctldlg->SetTopMost(false); + } + + if (joydlg) + joydlg->Show(); + Mouse::Show(true); +} + +// +--------------------------------------------------------------------+ + +void +MenuScreen::ShowKeyDlg() +{ + HideAll(); + + if (ctldlg) { + ctldlg->Show(); + ctldlg->SetTopMost(false); + } + + if (keydlg) + keydlg->Show(); + Mouse::Show(true); +} + +// +--------------------------------------------------------------------+ + +void +MenuScreen::ShowConfirmDlg() +{ + if (confirmdlg) { + if (msnSelectDlg && msnSelectDlg->IsShown()) + msnSelectDlg->SetTopMost(false); + + if (playdlg && playdlg->IsShown()) + playdlg->SetTopMost(false); + + confirmdlg->Show(); + confirmdlg->SetTopMost(true); + Mouse::Show(true); + } +} + +void +MenuScreen::HideConfirmDlg() +{ + if (confirmdlg) + confirmdlg->Hide(); + + if (msnSelectDlg && msnSelectDlg->IsShown()) + msnSelectDlg->SetTopMost(true); + + if (playdlg && playdlg->IsShown()) + playdlg->SetTopMost(true); +} + +// +--------------------------------------------------------------------+ + +void +MenuScreen::ShowLoadDlg() +{ + if (loadDlg) { + if (menudlg && menudlg->IsShown()) + menudlg->SetTopMost(false); + + loadDlg->Show(); + loadDlg->SetTopMost(true); + Mouse::Show(true); + } +} + +void +MenuScreen::HideLoadDlg() +{ + if (loadDlg) + loadDlg->Hide(); + + if (menudlg && menudlg->IsShown()) + menudlg->SetTopMost(true); +} + +// +--------------------------------------------------------------------+ + +void +MenuScreen::ShowExitDlg() +{ + HideAll(); + + if (menudlg) { + menudlg->Show(); + menudlg->SetTopMost(false); + } + + if (exitdlg) + exitdlg->Show(); + else + Starshatter::GetInstance()->Exit(); + + Mouse::Show(true); +} + +// +--------------------------------------------------------------------+ + +void +MenuScreen::HideNavDlg() +{ + if (msnEditNavDlg) + msnEditNavDlg->Hide(); +} + +// +--------------------------------------------------------------------+ + +void +MenuScreen::HideMsnElemDlg() +{ + if (msnElemDlg) + msnElemDlg->Hide(); + + if (msnEditDlg && msnEditDlg->IsShown()) + msnEditDlg->SetTopMost(true); + + if (msnEditNavDlg && msnEditNavDlg->IsShown()) + msnEditNavDlg->SetTopMost(true); +} + +// +--------------------------------------------------------------------+ + +void +MenuScreen::HideMsnEventDlg() +{ + if (msnEventDlg) + msnEventDlg->Hide(); + + if (msnEditDlg && msnEditDlg->IsShown()) + msnEditDlg->SetTopMost(true); + + if (msnEditNavDlg && msnEditNavDlg->IsShown()) + msnEditNavDlg->SetTopMost(true); +} + +// +--------------------------------------------------------------------+ + +bool +MenuScreen::IsNavShown() +{ + return msnEditNavDlg && msnEditNavDlg->IsShown(); +} + +// +--------------------------------------------------------------------+ + +void +MenuScreen::HideAll() +{ + Keyboard::FlushKeys(); + + current_dlg = 0; + + if (menudlg) menudlg->Hide(); + if (exitdlg) exitdlg->Hide(); + if (auddlg) auddlg->Hide(); + if (viddlg) viddlg->Hide(); + if (ctldlg) ctldlg->Hide(); + if (optdlg) optdlg->Hide(); + if (joydlg) joydlg->Hide(); + if (keydlg) keydlg->Hide(); + if (playdlg) playdlg->Hide(); + if (confirmdlg) confirmdlg->Hide(); + if (modDlg) modDlg->Hide(); + if (modInfoDlg) modInfoDlg->Hide(); + if (msnSelectDlg) msnSelectDlg->Hide(); + if (msnEditDlg) msnEditDlg->Hide(); + if (msnElemDlg) msnElemDlg->Hide(); + if (msnEventDlg) msnEventDlg->Hide(); + if (msnEditNavDlg) msnEditNavDlg->Hide(); + if (netClientDlg) netClientDlg->Hide(); + if (netAddrDlg) netAddrDlg->Hide(); + if (netPassDlg) netPassDlg->Hide(); + if (netLobbyDlg) netLobbyDlg->Hide(); + if (netServerDlg) netServerDlg->Hide(); + if (netUnitDlg) netUnitDlg->Hide(); + if (firstdlg) firstdlg->Hide(); + if (awarddlg) awarddlg->Hide(); + if (cmpSelectDlg) cmpSelectDlg->Hide(); + if (tacRefDlg) tacRefDlg->Hide(); + if (loadDlg) loadDlg->Hide(); +} + +// +--------------------------------------------------------------------+ + +void +MenuScreen::ApplyOptions() +{ + if (ctldlg) ctldlg->Apply(); + if (optdlg) optdlg->Apply(); + if (auddlg) auddlg->Apply(); + if (viddlg) viddlg->Apply(); + if (modDlg) modDlg->Apply(); + + ShowMenuDlg(); +} + +void +MenuScreen::CancelOptions() +{ + if (ctldlg) ctldlg->Cancel(); + if (optdlg) optdlg->Cancel(); + if (auddlg) auddlg->Cancel(); + if (viddlg) viddlg->Cancel(); + if (modDlg) modDlg->Cancel(); + + ShowMenuDlg(); +} diff --git a/Stars45/MenuScreen.h b/Stars45/MenuScreen.h new file mode 100644 index 0000000..fea3e1c --- /dev/null +++ b/Stars45/MenuScreen.h @@ -0,0 +1,199 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: MenuScreen.h + AUTHOR: John DiCamillo + +*/ + +#ifndef MenuScreen_h +#define MenuScreen_h + +#include "Types.h" +#include "Bitmap.h" +#include "Screen.h" +#include "BaseScreen.h" + +// +--------------------------------------------------------------------+ + +class MenuDlg; +class AudDlg; +class VidDlg; +class OptDlg; +class CtlDlg; +class JoyDlg; +class KeyDlg; +class ExitDlg; +class ConfirmDlg; + +class FirstTimeDlg; +class PlayerDlg; +class AwardShowDlg; + +class MsnSelectDlg; +class CmpSelectDlg; +class ModDlg; +class ModInfoDlg; +class MsnEditDlg; +class MsnElemDlg; +class MsnEventDlg; + +class NetClientDlg; +class NetAddrDlg; +class NetPassDlg; +class NetLobbyDlg; +class NetServerDlg; +class NetUnitDlg; + +class LoadDlg; +class TacRefDlg; + +class ActiveWindow; +class Bitmap; +class DataLoader; +class FadeView; +class Font; +class FormWindow; +class Screen; +class Video; +class VideoFactory; +class Window; + +// +--------------------------------------------------------------------+ + +class MenuScreen : public BaseScreen +{ +public: + MenuScreen(); + virtual ~MenuScreen(); + + virtual void Setup(Screen* screen); + virtual void TearDown(); + virtual bool CloseTopmost(); + + virtual bool IsShown() const { return isShown; } + virtual void Show(); + virtual void Hide(); + + virtual void ExecFrame(); + + virtual void ShowMenuDlg(); + virtual void ShowCmpSelectDlg(); + virtual void ShowMsnSelectDlg(); + virtual void ShowModDlg(); + virtual void ShowModInfoDlg(); + virtual void ShowMsnEditDlg(); + virtual void ShowNetClientDlg(); + virtual void ShowNetAddrDlg(); + virtual void ShowNetPassDlg(); + virtual void ShowNetLobbyDlg(); + virtual void ShowNetServerDlg(); + virtual void ShowNetUnitDlg(); + virtual void ShowFirstTimeDlg(); + virtual void ShowPlayerDlg(); + virtual void ShowTacRefDlg(); + virtual void ShowAwardDlg(); + virtual void ShowAudDlg(); + virtual void ShowVidDlg(); + virtual void ShowOptDlg(); + virtual void ShowCtlDlg(); + virtual void ShowJoyDlg(); + virtual void ShowKeyDlg(); + virtual void ShowExitDlg(); + virtual void ShowConfirmDlg(); + virtual void HideConfirmDlg(); + virtual void ShowLoadDlg(); + virtual void HideLoadDlg(); + + // base screen interface: + virtual void ShowMsnElemDlg(); + virtual void HideMsnElemDlg(); + virtual MsnElemDlg* GetMsnElemDlg() { return msnElemDlg; } + + virtual void ShowMsnEventDlg(); + virtual void HideMsnEventDlg(); + virtual MsnEventDlg* GetMsnEventDlg() { return msnEventDlg; } + + virtual void ShowNavDlg(); + virtual void HideNavDlg(); + virtual bool IsNavShown(); + virtual NavDlg* GetNavDlg() { return msnEditNavDlg; } + + virtual MsnSelectDlg* GetMsnSelectDlg() const { return msnSelectDlg; } + virtual ModDlg* GetModDlg() const { return modDlg; } + virtual ModInfoDlg* GetModInfoDlg() const { return modInfoDlg; } + virtual MsnEditDlg* GetMsnEditDlg() const { return msnEditDlg; } + virtual NetClientDlg* GetNetClientDlg() const { return netClientDlg; } + virtual NetAddrDlg* GetNetAddrDlg() const { return netAddrDlg; } + virtual NetPassDlg* GetNetPassDlg() const { return netPassDlg; } + virtual NetLobbyDlg* GetNetLobbyDlg() const { return netLobbyDlg; } + virtual NetServerDlg* GetNetServerDlg() const { return netServerDlg; } + virtual NetUnitDlg* GetNetUnitDlg() const { return netUnitDlg; } + virtual LoadDlg* GetLoadDlg() const { return loadDlg; } + virtual TacRefDlg* GetTacRefDlg() const { return tacRefDlg; } + + virtual AudDlg* GetAudDlg() const { return auddlg; } + virtual VidDlg* GetVidDlg() const { return viddlg; } + virtual OptDlg* GetOptDlg() const { return optdlg; } + virtual CtlDlg* GetCtlDlg() const { return ctldlg; } + virtual JoyDlg* GetJoyDlg() const { return joydlg; } + virtual KeyDlg* GetKeyDlg() const { return keydlg; } + virtual ExitDlg* GetExitDlg() const { return exitdlg; } + virtual FirstTimeDlg* GetFirstTimeDlg() const { return firstdlg; } + virtual PlayerDlg* GetPlayerDlg() const { return playdlg; } + virtual AwardShowDlg* GetAwardDlg() const { return awarddlg; } + virtual ConfirmDlg* GetConfirmDlg() const { return confirmdlg; } + + virtual void ApplyOptions(); + virtual void CancelOptions(); + +private: + void HideAll(); + + Screen* screen; + MenuDlg* menudlg; + + Window* fadewin; + FadeView* fadeview; + ExitDlg* exitdlg; + AudDlg* auddlg; + VidDlg* viddlg; + OptDlg* optdlg; + CtlDlg* ctldlg; + JoyDlg* joydlg; + KeyDlg* keydlg; + ConfirmDlg* confirmdlg; + PlayerDlg* playdlg; + AwardShowDlg* awarddlg; + ModDlg* modDlg; + ModInfoDlg* modInfoDlg; + MsnSelectDlg* msnSelectDlg; + MsnEditDlg* msnEditDlg; + MsnElemDlg* msnElemDlg; + MsnEventDlg* msnEventDlg; + NavDlg* msnEditNavDlg; + LoadDlg* loadDlg; + TacRefDlg* tacRefDlg; + + CmpSelectDlg* cmpSelectDlg; + FirstTimeDlg* firstdlg; + NetClientDlg* netClientDlg; + NetAddrDlg* netAddrDlg; + NetPassDlg* netPassDlg; + NetLobbyDlg* netLobbyDlg; + NetServerDlg* netServerDlg; + NetUnitDlg* netUnitDlg; + + FormWindow* current_dlg; + DataLoader* loader; + + int wc, hc; + bool isShown; +}; + +// +--------------------------------------------------------------------+ + +#endif MenuScreen_h + diff --git a/Stars45/MenuView.cpp b/Stars45/MenuView.cpp new file mode 100644 index 0000000..fec343b --- /dev/null +++ b/Stars45/MenuView.cpp @@ -0,0 +1,333 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: MenuView.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + +*/ + +#include "MemDebug.h" +#include "MenuView.h" + +#include "Color.h" +#include "Window.h" +#include "Video.h" +#include "DataLoader.h" +#include "Scene.h" +#include "FontMgr.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "MouseController.h" +#include "Menu.h" +#include "Button.h" +#include "Game.h" +#include "FormatUtil.h" + +// +--------------------------------------------------------------------+ + +MenuView::MenuView(Window* c) + : View(c), + mouse_down(0), right_down(0), shift_down(0), show_menu(false), + action(0), menu(0), menu_item(0), selected(0), + text_color(Color::White), back_color(Color::Black) +{ + right_start.x = 0; + right_start.y = 0; + + OnWindowMove(); +} + +MenuView::~MenuView() +{ +} + +// +--------------------------------------------------------------------+ + +void +MenuView::OnWindowMove() +{ + offset.x = window->X(); + offset.y = window->Y(); + width = window->Width(); + height = window->Height(); +} + +// +--------------------------------------------------------------------+ + +void +MenuView::Refresh() +{ + if (show_menu) + DrawMenu(); +} + +// +--------------------------------------------------------------------+ + +void +MenuView::DoMouseFrame() +{ + static DWORD rbutton_latch = 0; + + action = 0; + + if (Mouse::RButton()) { + MouseController* mouse_con = MouseController::GetInstance(); + if (!right_down && (!mouse_con || !mouse_con->Active())) { + rbutton_latch = Game::RealTime(); + right_down = true; + show_menu = false; + } + } + else { + if (right_down && (Game::RealTime() - rbutton_latch < 250)) { + right_start.x = Mouse::X() - offset.x; + right_start.y = Mouse::Y() - offset.y; + show_menu = true; + Button::PlaySound(Button::SND_MENU_OPEN); + } + + right_down = false; + } + + MouseController* mouse_con = MouseController::GetInstance(); + + if (!mouse_con || !mouse_con->Active()) { + if (Mouse::LButton()) { + if (!mouse_down) + shift_down = Keyboard::KeyDown(VK_SHIFT); + + mouse_down = true; + } + + else if (mouse_down) { + int mouse_x = Mouse::X() - offset.x; + int mouse_y = Mouse::Y() - offset.y; + int keep_menu = false; + + if (show_menu) { + keep_menu = ProcessMenuItem(); + Mouse::Show(true); + } + + mouse_down = false; + + if (!keep_menu) { + ClearMenuSelection(menu); + show_menu = false; + } + } + } +} + +// +--------------------------------------------------------------------+ + +int +MenuView::ProcessMenuItem() +{ + if (!menu_item || !menu_item->GetEnabled()) + return false; + + if (menu_item->GetSubmenu()) { + ListIter item = menu_item->GetMenu()->GetItems(); + while (++item) + if (item.value() == menu_item) + item->SetSelected(2); + else + item->SetSelected(0); + + Button::PlaySound(Button::SND_MENU_OPEN); + return true; // keep menu showing + } + + action = menu_item->GetData(); + Button::PlaySound(Button::SND_MENU_SELECT); + return false; +} + +// +--------------------------------------------------------------------+ + +void +MenuView::DrawMenu() +{ + menu_item = 0; + + if (menu) { + int mx = right_start.x - 2; + int my = right_start.y - 2; + + DrawMenu(mx, my, menu); + } +} + +void +MenuView::DrawMenu(int mx, int my, Menu* m) +{ + if (m) { + MenuItem* locked_item = 0; + Menu* submenu = 0; + int subx = 0; + int suby = 0; + Font* font = FontMgr::Find("HUD"); + + Rect menu_rect(mx, my, 100, m->NumItems() * 10 + 6); + + int max_width = 0; + int max_height = 0; + int extra_width = 16; + + ListIter item = m->GetItems(); + while (++item) { + menu_rect.w = width/2; + + if (item->GetText().length()) { + window->SetFont(font); + window->DrawText(item->GetText(), 0, menu_rect, DT_LEFT|DT_SINGLELINE|DT_CALCRECT); + if (menu_rect.w > max_width) + max_width = menu_rect.w; + max_height += 11; + + if (item->GetSubmenu()) + extra_width = 28; + + if (item->GetSelected() > 1) + locked_item = item.value(); + } + else { + max_height += 4; + } + } + + menu_rect.h = max_height + 6; + menu_rect.w = max_width + extra_width; + + if (menu_rect.x + menu_rect.w >= width) + menu_rect.x = width - menu_rect.w - 2; + + if (menu_rect.y + menu_rect.h >= height) + menu_rect.y = height - menu_rect.h - 2; + + window->FillRect(menu_rect, back_color * 0.2); + window->DrawRect(menu_rect, back_color); + + Rect item_rect = menu_rect; + + item_rect.x += 4; + item_rect.y += 3; + item_rect.w -= 8; + item_rect.h = 12; + + item.reset(); + while (++item) { + int line_height = 0; + + if (item->GetText().length()) { + Rect fill_rect = item_rect; + fill_rect.Inflate(2,-1); + fill_rect.y -= 1; + + int mx = Mouse::X() - offset.x; + int my = Mouse::Y() - offset.y; + + // is this item picked? + if (menu_rect.Contains(mx, my)) { + if (my >= fill_rect.y && my <= fill_rect.y+fill_rect.h) { + if (Mouse::LButton()) { + menu_item = item.value(); + item->SetSelected(2); + if (locked_item && locked_item->GetMenu() == m) + locked_item->SetSelected(0); + locked_item = menu_item; + } + else if (!locked_item || locked_item->GetMenu() != m) { + item->SetSelected(true); + menu_item = item.value(); + } + + if (menu_item && menu_item != selected) { + selected = menu_item; + Button::PlaySound(Button::SND_MENU_HILITE); + } + } + else if (item.value() != locked_item) { + item->SetSelected(false); + } + } + + if (item->GetSelected()) { + window->FillRect(fill_rect, back_color * 0.35); + window->DrawRect(fill_rect, back_color * 0.75); + + if (item->GetSubmenu()) { + submenu = item->GetSubmenu(); + subx = menu_rect.x + max_width + extra_width; + suby = fill_rect.y - 3; + } + } + + if (item->GetEnabled()) + font->SetColor(text_color); + else + font->SetColor(text_color * 0.33); + + window->SetFont(font); + window->DrawText(item->GetText(), 0, item_rect, DT_LEFT|DT_SINGLELINE); + line_height = 11; + } + else { + window->DrawLine(item_rect.x, + item_rect.y + 2, + item_rect.x + max_width + extra_width - 8, + item_rect.y + 2, + back_color); + line_height = 4; + } + + if (item->GetSubmenu()) { + int left = item_rect.x + max_width + 10; + int top = item_rect.y + 1; + + // draw the arrow: + POINT arrow[3]; + + arrow[0].x = left; + arrow[0].y = top; + arrow[1].x = left + 8; + arrow[1].y = top + 4; + arrow[2].x = left; + arrow[2].y = top + 8; + + window->FillPoly(3, arrow, back_color); + } + + item_rect.y += line_height; + } + + if (submenu) { + if (subx + 60 > width) + subx = menu_rect.x - 60; + + DrawMenu(subx, suby, submenu); + } + } +} + +// +--------------------------------------------------------------------+ + +void +MenuView::ClearMenuSelection(Menu* menu) +{ + if (menu) { + ListIter item = menu->GetItems(); + while (++item) { + item->SetSelected(0); + if (item->GetSubmenu()) + ClearMenuSelection(item->GetSubmenu()); + } + } +} diff --git a/Stars45/MenuView.h b/Stars45/MenuView.h new file mode 100644 index 0000000..7303afb --- /dev/null +++ b/Stars45/MenuView.h @@ -0,0 +1,77 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: MenuView.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + View class for displaying right-click context menus +*/ + +#ifndef MenuView_h +#define MenuView_h + +#include "Types.h" +#include "View.h" +#include "Bitmap.h" +#include "Font.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class Menu; +class MenuItem; + +// +--------------------------------------------------------------------+ + +class MenuView : public View +{ +public: + MenuView(Window* c); + virtual ~MenuView(); + + // Operations: + virtual void Refresh(); + virtual void OnWindowMove(); + virtual void DoMouseFrame(); + virtual void DrawMenu(); + virtual void DrawMenu(int x, int y, Menu* menu); + virtual int ProcessMenuItem(); + virtual void ClearMenuSelection(Menu* menu); + + virtual bool IsShown() { return show_menu != 0; } + virtual int GetAction() { return action; } + virtual Menu* GetMenu() { return menu; } + virtual void SetMenu(Menu* m) { menu = m; } + virtual MenuItem* GetMenuItem() { return menu_item; } + + virtual Color GetBackColor() { return back_color; } + virtual void SetBackColor(Color c) { back_color = c; } + virtual Color GetTextColor() { return text_color; } + virtual void SetTextColor(Color c) { text_color = c; } + +protected: + int width, height; + + int shift_down; + int mouse_down; + int right_down; + int show_menu; + POINT right_start; + POINT offset; + + int action; + Menu* menu; + MenuItem* menu_item; + MenuItem* selected; + + Color back_color; + Color text_color; +}; + +#endif MenuView_h + diff --git a/Stars45/Mfd.cpp b/Stars45/Mfd.cpp new file mode 100644 index 0000000..caf8bee --- /dev/null +++ b/Stars45/Mfd.cpp @@ -0,0 +1,1403 @@ +/* Project Starshatter 5.0 + Destroyer Studios LLC + Copyright © 1997-2007. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: MFD.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Class for all Multi Function Displays +*/ + +#include "MemDebug.h" +#include "MFD.h" +#include "HUDView.h" +#include "Ship.h" +#include "NavSystem.h" +#include "Power.h" +#include "Shield.h" +#include "Sensor.h" +#include "Contact.h" +#include "ShipDesign.h" +#include "Shot.h" +#include "Weapon.h" +#include "WeaponGroup.h" +#include "Sim.h" +#include "StarSystem.h" +#include "Starshatter.h" +#include "Drive.h" +#include "QuantumDrive.h" +#include "Power.h" +#include "Instruction.h" + +#include "NetGame.h" + +#include "CameraView.h" +#include "Color.h" +#include "Font.h" +#include "FontMgr.h" +#include "Window.h" +#include "Video.h" +#include "Screen.h" +#include "DataLoader.h" +#include "Scene.h" +#include "Graphic.h" +#include "Sprite.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "Game.h" + +static Bitmap sensor_fov; +static Bitmap sensor_fwd; +static Bitmap sensor_hsd; +static Bitmap sensor_3d; + +static BYTE* sensor_fov_shade; +static BYTE* sensor_fwd_shade; +static BYTE* sensor_hsd_shade; +static BYTE* sensor_3d_shade; + +static Color hud_color = Color::Black; +static Color txt_color = Color::Black; + +// +--------------------------------------------------------------------+ + +MFD::MFD(Window* c, int n) + : window(c), rect(0,0,0,0), index(n), mode(MFD_MODE_OFF), sprite(0), + ship(0), hidden(true), camview(0), lines(0), mouse_latch(0), mouse_in(false), + cockpit_hud_texture(0) +{ + sprite = new(__FILE__,__LINE__) Sprite(&sensor_fov); + + sprite->SetBlendMode(2); + sprite->SetFilter(0); + sprite->Hide(); + + Font* font = FontMgr::Find("HUD"); + + for (int i = 0; i < TXT_LAST; i++) { + mfd_text[i].font = font; + mfd_text[i].color = Color::White; + mfd_text[i].hidden = true; + } +} + +MFD::~MFD() +{ + GRAPHIC_DESTROY(sprite); +} + +// +--------------------------------------------------------------------+ + +void +MFD::Initialize() +{ + static int initialized = 0; + if (initialized) return; + + HUDView::PrepareBitmap("sensor_fov.pcx", sensor_fov, sensor_fov_shade); + HUDView::PrepareBitmap("sensor_fwd.pcx", sensor_fwd, sensor_fwd_shade); + HUDView::PrepareBitmap("sensor_hsd.pcx", sensor_hsd, sensor_hsd_shade); + HUDView::PrepareBitmap("sensor_3d.pcx", sensor_3d, sensor_3d_shade); + + sensor_fov.SetType(Bitmap::BMP_TRANSLUCENT); + sensor_fwd.SetType(Bitmap::BMP_TRANSLUCENT); + sensor_hsd.SetType(Bitmap::BMP_TRANSLUCENT); + sensor_3d.SetType(Bitmap::BMP_TRANSLUCENT); + + initialized = 1; +} + +void +MFD::Close() +{ + sensor_fov.ClearImage(); + sensor_fwd.ClearImage(); + sensor_hsd.ClearImage(); + sensor_3d.ClearImage(); + + delete [] sensor_fov_shade; + delete [] sensor_fwd_shade; + delete [] sensor_hsd_shade; + delete [] sensor_3d_shade; +} + +// +--------------------------------------------------------------------+ + +void +MFD::UseCameraView(CameraView* v) +{ + if (v && !camview) { + camview = v; + } +} + +void +MFD::SetColor(Color c) +{ + HUDView* hud = HUDView::GetInstance(); + + if (hud) { + hud_color = hud->GetHUDColor(); + txt_color = hud->GetTextColor(); + } + else { + hud_color = c; + txt_color = c; + } + + HUDView::ColorizeBitmap(sensor_fov, sensor_fov_shade, c); + HUDView::ColorizeBitmap(sensor_fwd, sensor_fwd_shade, c); + HUDView::ColorizeBitmap(sensor_hsd, sensor_hsd_shade, c); + HUDView::ColorizeBitmap(sensor_3d, sensor_3d_shade, c); +} + +void +MFD::SetText3DColor(Color c) +{ + for (int i = 0; i < TXT_LAST; i++) + mfd_text[i].color = c; +} + +// +--------------------------------------------------------------------+ + +void +MFD::Show() +{ + switch (mode) { + case MFD_MODE_FOV: + case MFD_MODE_HSD: + case MFD_MODE_3D: + if (sprite) + sprite->Show(); + break; + } + + hidden = false; +} + +void +MFD::Hide() +{ + if (sprite) + sprite->Hide(); + + for (int i = 0; i < TXT_LAST; i++) + HideMFDText(i); + + hidden = true; +} + +// +--------------------------------------------------------------------+ + +void +MFD::SetRect(const Rect& r) +{ + rect = r; + + if (sprite) + sprite->MoveTo(Point(rect.x + sprite->Width()/2, + rect.y + sprite->Height()/2, + 1)); +} + +// +--------------------------------------------------------------------+ + +void +MFD::SetMode(int m) +{ + if (m < MFD_MODE_OFF || m > MFD_MODE_3D) + mode = MFD_MODE_OFF; + else + mode = m; + + sprite->Hide(); + + for (int i = 0; i < TXT_LAST; i++) + HideMFDText(i); + + switch (mode) { + case MFD_MODE_GAME: + case MFD_MODE_SHIP: + lines = 0; + break; + + case MFD_MODE_FOV: + sprite->SetAnimation(&sensor_fov); + sprite->Show(); + sprite->Reshape(sensor_fov.Width()-8, 16); + break; + case MFD_MODE_HSD: + sprite->SetAnimation(&sensor_hsd); + sprite->Show(); + sprite->Reshape(sensor_hsd.Width()-8, 16); + break; + case MFD_MODE_3D: + sprite->SetAnimation(&sensor_3d); + sprite->Show(); + sprite->Reshape(sensor_3d.Width()-8, 16); + break; + } +} + +// +--------------------------------------------------------------------+ + +void +MFD::Draw() +{ + mouse_in = false; + + if (Mouse::LButton() == 0) + mouse_latch = 0; + + if (rect.Contains(Mouse::X(), Mouse::Y())) + mouse_in = true; + + // click to turn on MFD when off: + if (mode < MFD_MODE_FOV && Mouse::LButton() && !mouse_latch) { + mouse_latch = 1; + if (mouse_in) { + HUDView* hud = HUDView::GetInstance(); + if (hud) + hud->CycleMFDMode(index); + } + } + + for (int i = 0; i < TXT_LAST; i++) + HideMFDText(i); + + if (hidden || mode < MFD_MODE_FOV) { + if (cockpit_hud_texture) { + int x1 = index*128; + int y1 = 256; + int x2 = x1 + 128; + int y2 = y1 + 128; + + cockpit_hud_texture->FillRect(x1, y1, x2, y2, Color::Black); + } + + if (hidden) + return; + } + + if (sprite && !sprite->Hidden()) { + if (cockpit_hud_texture) { + int x1 = index*128; + int y1 = 256; + int w = sprite->Width(); + int h = sprite->Height(); + + cockpit_hud_texture->BitBlt(x1, y1, *sprite->Frame(), 0,0,w,h); + } + else { + int cx = rect.x + rect.w/2; + int cy = rect.y + rect.h/2; + int w2 = sprite->Width()/2; + int h2 = sprite->Height()/2; + + window->DrawBitmap(cx-w2, cy-h2, cx+w2, cy+h2, sprite->Frame(), Video::BLEND_ALPHA); + } + } + + switch (mode) { + default: + case MFD_MODE_OFF: break; + case MFD_MODE_GAME: DrawGameMFD(); break; + case MFD_MODE_SHIP: DrawStatusMFD(); break; + + // sensor sub-modes: + case MFD_MODE_FOV: DrawSensorMFD(); break; + case MFD_MODE_HSD: DrawHSD(); break; + case MFD_MODE_3D: Draw3D(); break; + } +} + +// +--------------------------------------------------------------------+ + +void +MFD::DrawSensorLabels(const char* mfd_mode) +{ + Sensor* sensor = ship->GetSensor(); + char mode_buf[8] = " "; + int scan_r = rect.w; + int scan_x = rect.x; + int scan_y = rect.y; + + switch (sensor->GetMode()) { + case Sensor::PAS: strcpy(mode_buf, Game::GetText("MFD.mode.passive").data()); break; + case Sensor::STD: strcpy(mode_buf, Game::GetText("MFD.mode.standard").data()); break; + case Sensor::ACM: strcpy(mode_buf, Game::GetText("MFD.mode.auto-combat").data()); break; + case Sensor::GM: strcpy(mode_buf, Game::GetText("MFD.mode.ground").data()); break; + case Sensor::PST: strcpy(mode_buf, Game::GetText("MFD.mode.passive").data()); break; + case Sensor::CST: strcpy(mode_buf, Game::GetText("MFD.mode.combined").data()); break; + default: break; + } + + Rect mode_rect(scan_x+2, scan_y+2, 40, 12); + DrawMFDText(0, mode_buf, mode_rect, DT_LEFT); + + char range_txt[12]; + double beam_range = sensor->GetBeamRange() + 1; + if (beam_range >= 1e6) + sprintf(range_txt, "-%dM+", (int) (beam_range / 1e6)); + else + sprintf(range_txt, "-%3d+", (int) (beam_range / 1e3)); + + Rect range_rect(scan_x+2, scan_y+scan_r-12, 40, 12); + DrawMFDText(1, range_txt, range_rect, DT_LEFT); + + Rect disp_rect(scan_x+scan_r-41, scan_y+2, 40, 12); + DrawMFDText(2, mfd_mode, disp_rect, DT_RIGHT); + + Rect probe_rect(scan_x+scan_r-41, scan_y+scan_r-12, 40, 12); + + if (ship->GetProbeLauncher()) { + char probes[32]; + sprintf(probes, "%s %02d", Game::GetText("MFD.probe").data(), ship->GetProbeLauncher()->Ammo()); + DrawMFDText(3, probes, probe_rect, DT_RIGHT); + } + else { + HideMFDText(3); + } + + if (Mouse::LButton() && !mouse_latch) { + mouse_latch = 1; + + if (mode_rect.Contains(Mouse::X(), Mouse::Y())) { + if (sensor->GetMode() < Sensor::PST) { + int sensor_mode = sensor->GetMode() + 1; + if (sensor_mode > Sensor::GM) + sensor_mode = Sensor::PAS; + + sensor->SetMode((Sensor::Mode) sensor_mode); + } + } + + else if (range_rect.Contains(Mouse::X(), Mouse::Y())) { + if (Mouse::X() > range_rect.x+range_rect.w/2) + sensor->IncreaseRange(); + else + sensor->DecreaseRange(); + } + + else if (disp_rect.Contains(Mouse::X(), Mouse::Y())) { + HUDView* hud = HUDView::GetInstance(); + if (hud) + hud->CycleMFDMode(index); + } + + else if (probe_rect.Contains(Mouse::X(), Mouse::Y())) { + ship->LaunchProbe(); + } + } +} + +// +--------------------------------------------------------------------+ + +// AZIMUTH-ELEVATION ANGULAR SCANNER + +void +MFD::DrawSensorMFD() +{ + int scan_r = rect.w; + int scan_x = cockpit_hud_texture ? (index*128) : rect.x; + int scan_y = cockpit_hud_texture ? 256 : rect.y; + int r = scan_r / 2; + + double xctr = (scan_r / 2.0) - 0.5; + double yctr = (scan_r / 2.0) + 0.5; + + Sensor* sensor = ship->GetSensor(); + if (!sensor) { + DrawMFDText(0, Game::GetText("MFD.inactive").data(), rect, DT_CENTER); + return; + } + + int w = sprite->Width(); + int h = sprite->Height(); + + if (w < sprite->Frame()->Width()) + w += 2; + + if (h < sprite->Frame()->Height()) + h += 16; + + sprite->Reshape(w, h); + sprite->Show(); + + if (h < sprite->Frame()->Height()) + return; + + double sweep_scale = r / (PI/2); + + if (sensor->GetBeamLimit() > 90*DEGREES) + sweep_scale = (double) r / (90*DEGREES); + + int az = (int) (sensor->GetBeamLimit() * sweep_scale); + int el = az; + int xc = (int) (scan_x + xctr); + int yc = (int) (scan_y + yctr); + + if (mode == MFD_MODE_FOV) { + if (sensor->GetMode() < Sensor::GM) { + if (cockpit_hud_texture) + cockpit_hud_texture->DrawEllipse(xc-az, yc-el, xc+az, yc+el, hud_color); + else + window->DrawEllipse(xc-az, yc-el, xc+az, yc+el, hud_color); + } + } + else { + char az_txt[8]; + sprintf(az_txt, "%d", (int) (sensor->GetBeamLimit() / DEGREES)); + + Rect az_rect(scan_x+2, scan_y+scan_r-12, 32, 12); + DrawMFDText(1, az_txt, az_rect, DT_LEFT); + + az_rect.x = scan_x + (scan_r/2) - (az_rect.w/2); + DrawMFDText(2, "0", az_rect, DT_CENTER); + + az_rect.x = scan_x + scan_r - az_rect.w - 2; + DrawMFDText(3, az_txt, az_rect, DT_RIGHT); + } + + // draw next nav point: + Instruction* navpt = ship->GetNextNavPoint(); + if (navpt && navpt->Region() == ship->GetRegion()) { + const Camera* cam = &ship->Cam(); + + // translate: + Point pt = navpt->Location().OtherHand() - ship->Location(); + + // rotate: + double tx = pt * cam->vrt(); + double ty = pt * cam->vup(); + double tz = pt * cam->vpn(); + + if (tz > 1.0) { + // convert to spherical coords: + double rng = pt.length(); + double az = asin(fabs(tx) / rng); + double el = asin(fabs(ty) / rng); + + if (tx < 0) az = -az; + if (ty < 0) el = -el; + + if (fabs(az) < 90*DEGREES) { + az *= sweep_scale; + el *= sweep_scale; + + int x = (int) (r + az); + int y = (int) (r - el); + + // clip again: + if (x > 0 && x < scan_r && + y > 0 && y < scan_r) { + + // draw: + int xc = scan_x + x; + int yc = scan_y + y; + + if (cockpit_hud_texture) { + cockpit_hud_texture->DrawLine(xc-2, yc-2, xc+2, yc+2, Color::White); + cockpit_hud_texture->DrawLine(xc-2, yc+2, xc+2, yc-2, Color::White); + } + else { + window->DrawLine(xc-2, yc-2, xc+2, yc+2, Color::White); + window->DrawLine(xc-2, yc+2, xc+2, yc-2, Color::White); + } + } + } + } + } + + int num_contacts = ship->NumContacts(); + ListIter iter = ship->ContactList(); + + while (++iter) { + Contact* contact = iter.value(); + Ship* c_ship = contact->GetShip(); + double az, el, rng; + bool aft = false; + + if (c_ship == ship) continue; + + contact->GetBearing(ship, az, el, rng); + + // clip (is in-front): + if (fabs(az) < 90*DEGREES) { + az *= sweep_scale; + el *= sweep_scale; + } + + // rear anulus: + else { + double len = sqrt(az*az + el*el); + + if (len > 1e-6) { + az = r * az/len; + el = r * el/len; + } + else { + az = -r; + el = 0; + } + + aft = true; + } + + int x = (int) (r + az); + int y = (int) (r - el); + + // clip again: + if (x < 0 || x > scan_r) continue; + if (y < 0 || y > scan_r) continue; + + // draw: + Color mark = HUDView::MarkerColor(contact); + + if (aft) + mark = mark * 0.75; + + int xc = scan_x + x; + int yc = scan_y + y; + int size = 1; + + if (c_ship && c_ship == ship->GetTarget()) + size = 2; + + if (cockpit_hud_texture) + cockpit_hud_texture->FillRect(xc-size, yc-size, xc+size, yc+size, mark); + else + window->FillRect(xc-size, yc-size, xc+size, yc+size, mark); + + if (contact->Threat(ship)) { + if (c_ship) { + if (cockpit_hud_texture) + cockpit_hud_texture->DrawEllipse(xc-4, yc-4, xc+3, yc+3, mark); + else + window->DrawEllipse(xc-4, yc-4, xc+3, yc+3, mark); + } + else { + if (cockpit_hud_texture) { + cockpit_hud_texture->DrawLine(xc, yc-5, xc+5, yc, mark); + cockpit_hud_texture->DrawLine(xc+5, yc, xc, yc+5, mark); + cockpit_hud_texture->DrawLine(xc, yc+5, xc-5, yc, mark); + cockpit_hud_texture->DrawLine(xc-5, yc, xc, yc-5, mark); + } + else { + window->DrawLine(xc, yc-5, xc+5, yc, mark); + window->DrawLine(xc+5, yc, xc, yc+5, mark); + window->DrawLine(xc, yc+5, xc-5, yc, mark); + window->DrawLine(xc-5, yc, xc, yc-5, mark); + } + } + } + } + + DrawSensorLabels(Game::GetText("MFD.mode.field-of-view").data()); +} + +// +--------------------------------------------------------------------+ + +// HORIZONTAL SITUATION DISPLAY + +void +MFD::DrawHSD() +{ + int scan_r = rect.w; + int scan_x = cockpit_hud_texture ? (index*128) : rect.x; + int scan_y = cockpit_hud_texture ? 256 : rect.y; + int r = scan_r / 2 - 4; + + double xctr = (scan_r / 2.0) - 0.5; + double yctr = (scan_r / 2.0) + 0.5; + + int xc = (int) xctr + scan_x; + int yc = (int) yctr + scan_y; + + Sensor* sensor = ship->GetSensor(); + if (!sensor) { + DrawMFDText(0, Game::GetText("MFD.inactive").data(), rect, DT_CENTER); + return; + } + + int w = sprite->Width(); + int h = sprite->Height(); + + if (w < sprite->Frame()->Width()) + w += 2; + + if (h < sprite->Frame()->Height()) + h += 16; + + sprite->Reshape(w, h); + sprite->Show(); + + if (h < sprite->Frame()->Height()) + return; + + if (sensor->GetMode() < Sensor::PST) { + double s = sin(sensor->GetBeamLimit()); + double c = cos(sensor->GetBeamLimit()); + + int x0 = (int) (0.1*r*s); + int y0 = (int) (0.1*r*c); + int x1 = (int) (1.0*r*s); + int y1 = (int) (1.0*r*c); + + if (cockpit_hud_texture) { + cockpit_hud_texture->DrawLine(xc-x0, yc-y0, xc-x1, yc-y1, hud_color); + cockpit_hud_texture->DrawLine(xc+x0, yc-y0, xc+x1, yc-y1, hud_color); + } + else { + window->DrawLine(xc-x0, yc-y0, xc-x1, yc-y1, hud_color); + window->DrawLine(xc+x0, yc-y0, xc+x1, yc-y1, hud_color); + } + } + + double rscale = (double) r/(sensor->GetBeamRange()); + + Camera hsd_cam = ship->Cam(); + Point look = ship->Location() + ship->Heading() * 1000; + look.y = ship->Location().y; + + hsd_cam.LookAt(look); + + // draw tick marks on range rings: + for (int dir = 0; dir < 4; dir++) { + Point tick; + + switch (dir) { + case 0: tick = Point( 0, 0, 1000); break; + case 1: tick = Point( 1000, 0, 0); break; + case 2: tick = Point( 0, 0, -1000); break; + case 3: tick = Point(-1000, 0, 0); break; + } + + double tx = tick * hsd_cam.vrt(); + double tz = tick * hsd_cam.vpn(); + double az = asin(fabs(tx) / 1000); + + if (tx < 0) az = -az; + + if (tz < 0) + if (az < 0) az = -PI - az; + else az = PI - az; + + for (double range = 0.3; range < 1; range += 0.3) { + int x0 = (int) (sin(az) * r * range); + int y0 = (int) (cos(az) * r * range); + int x1 = (int) (sin(az) * r * (range + 0.1)); + int y1 = (int) (cos(az) * r * (range + 0.1)); + + if (cockpit_hud_texture) { + cockpit_hud_texture->DrawLine(xc+x0, yc-y0, xc+x1, yc-y1, hud_color); + } + else { + window->DrawLine(xc+x0, yc-y0, xc+x1, yc-y1, hud_color); + } + } + } + + // draw next nav point: + Instruction* navpt = ship->GetNextNavPoint(); + if (navpt && navpt->Region() == ship->GetRegion()) { + const Camera* cam = &hsd_cam; + + // translate: + Point pt = navpt->Location().OtherHand() - ship->Location(); + + // rotate: + double tx = pt * cam->vrt(); + double ty = pt * cam->vup(); + double tz = pt * cam->vpn(); + + // convert to spherical coords: + double rng = pt.length(); + double az = asin(fabs(tx) / rng); + + if (rng > sensor->GetBeamRange()) + rng = sensor->GetBeamRange(); + + if (tx < 0) + az = -az; + + if (tz < 0) + if (az < 0) + az = -PI - az; + else + az = PI - az; + + // draw: + int x = (int) (xc + sin(az) * rng * rscale); + int y = (int) (yc - cos(az) * rng * rscale); + + if (cockpit_hud_texture) { + cockpit_hud_texture->DrawLine(x-2, y-2, x+2, y+2, Color::White); + cockpit_hud_texture->DrawLine(x-2, y+2, x+2, y-2, Color::White); + } + else { + window->DrawLine(x-2, y-2, x+2, y+2, Color::White); + window->DrawLine(x-2, y+2, x+2, y-2, Color::White); + } + } + + // draw contact markers: + double limit = sensor->GetBeamRange(); + ListIter contact = ship->ContactList(); + + while (++contact) { + Ship* c_ship = contact->GetShip(); + if (c_ship == ship) continue; + + // translate: + Point targ_pt = contact->Location() - hsd_cam.Pos(); + + // rotate: + double tx = targ_pt * hsd_cam.vrt(); + double rg = contact->Range(ship, limit); + double true_range = targ_pt.length(); + double az = asin(fabs(tx) / true_range); + + // clip: + if (rg > limit || rg <= 0) + continue; + + if (tx < 0) + az = -az; + + if (!contact->InFront(ship)) + if (az < 0) + az = -PI - az; + else + az = PI - az; + + // draw: + int x = (int) (xc + sin(az) * rg * rscale); + int y = (int) (yc - cos(az) * rg * rscale); + int size = 2; + + // clip again: + if (x < scan_x || y < scan_y) + continue; + + if (c_ship && c_ship == ship->GetTarget()) + size = 3; + + Color mark = HUDView::MarkerColor(contact.value()); + if (cockpit_hud_texture) { + cockpit_hud_texture->FillRect(x-size, y-size, x+size, y+size, mark); + } + else { + window->FillRect(x-size, y-size, x+size, y+size, mark); + } + + if (contact->Threat(ship)) { + if (c_ship) { + if (cockpit_hud_texture) { + cockpit_hud_texture->DrawEllipse(x-4, y-4, x+3, y+3, mark); + } + else { + window->DrawEllipse(x-4, y-4, x+3, y+3, mark); + } + } + else { + if (cockpit_hud_texture) { + cockpit_hud_texture->DrawLine(x, y-5, x+5, y, mark); + cockpit_hud_texture->DrawLine(x+5, y, x, y+5, mark); + cockpit_hud_texture->DrawLine(x, y+5, x-5, y, mark); + cockpit_hud_texture->DrawLine(x-5, y, x, y-5, mark); + } + else { + window->DrawLine(x, y-5, x+5, y, mark); + window->DrawLine(x+5, y, x, y+5, mark); + window->DrawLine(x, y+5, x-5, y, mark); + window->DrawLine(x-5, y, x, y-5, mark); + } + } + } + } + + DrawSensorLabels(Game::GetText("MFD.mode.horizontal").data()); +} + +// +--------------------------------------------------------------------+ + +// ELITE-STYLE 3D RADAR + +void +MFD::Draw3D() +{ + int scan_r = rect.w; + int scan_x = cockpit_hud_texture ? (index*128) : rect.x; + int scan_y = cockpit_hud_texture ? 256 : rect.y; + int r = scan_r / 2 - 4; + + double xctr = (scan_r / 2.0) - 0.5; + double yctr = (scan_r / 2.0) + 0.5; + + int xc = (int) xctr + scan_x; + int yc = (int) yctr + scan_y; + + Sensor* sensor = ship->GetSensor(); + if (!sensor) { + DrawMFDText(0, Game::GetText("MFD.inactive").data(), rect, DT_CENTER); + return; + } + + int w = sprite->Width(); + int h = sprite->Height(); + + if (w < sprite->Frame()->Width()) + w += 2; + + if (h < sprite->Frame()->Height()) + h += 16; + + sprite->Reshape(w, h); + sprite->Show(); + + if (h < sprite->Frame()->Height()) + return; + + double rscale = (double) r/(sensor->GetBeamRange()); + + Camera hsd_cam = ship->Cam(); + + if (ship->IsStarship()) { + Point look = ship->Location() + ship->Heading() * 1000; + look.y = ship->Location().y; + + hsd_cam.LookAt(look); + } + + + // draw next nav point: + Instruction* navpt = ship->GetNextNavPoint(); + if (navpt && navpt->Region() == ship->GetRegion()) { + const Camera* cam = &hsd_cam; + + // translate: + Point pt = navpt->Location().OtherHand() - ship->Location(); + + // rotate: + double tx = pt * cam->vrt(); + double ty = pt * cam->vup(); + double tz = pt * cam->vpn(); + + // convert to cylindrical coords: + double rng = pt.length(); + double az = asin(fabs(tx) / rng); + + if (rng > sensor->GetBeamRange()) + rng = sensor->GetBeamRange(); + + if (tx < 0) + az = -az; + + if (tz < 0) { + if (az < 0) + az = -PI - az; + else + az = PI - az; + } + + // accentuate vertical: + if (ty > 10) + ty = log10(ty-9) * r/8; + + else if (ty < -10) + ty = -log10(9-ty) * r/8; + + else + ty = 0; + + // draw: + int x = (int) (sin(az) * rng * rscale); + int y = (int) (cos(az) * rng * rscale/2); + int z = (int) (ty); + + int x0 = xc+x; + int y0 = yc-y-z; + + if (cockpit_hud_texture) { + cockpit_hud_texture->DrawLine(x0-2, y0-2, x0+2, y0+2, Color::White); + cockpit_hud_texture->DrawLine(x0-2, y0+2, x0+2, y0-2, Color::White); + } + else { + window->DrawLine(x0-2, y0-2, x0+2, y0+2, Color::White); + window->DrawLine(x0-2, y0+2, x0+2, y0-2, Color::White); + } + + if (cockpit_hud_texture) { + if (z > 0) + cockpit_hud_texture->DrawLine(x0, y0+1, x0, y0+z, Color::White); + else if (z < 0) + cockpit_hud_texture->DrawLine(x0, y0+z, x0, y0-1, Color::White); + } + else { + if (z > 0) + window->DrawLine(x0, y0+1, x0, y0+z, Color::White); + else if (z < 0) + window->DrawLine(x0, y0+z, x0, y0-1, Color::White); + } + } + + + // draw contact markers: + double limit = sensor->GetBeamRange(); + ListIter contact = ship->ContactList(); + + while (++contact) { + Ship* c_ship = contact->GetShip(); + if (c_ship == ship) continue; + + // translate: + Point targ_pt = contact->Location() - hsd_cam.Pos(); + + // rotate: + double tx = targ_pt * hsd_cam.vrt(); + double ty = targ_pt * hsd_cam.vup(); + double rg = contact->Range(ship, limit); + double true_range = targ_pt.length(); + double az = asin(fabs(tx) / true_range); + + // clip: + if (rg > limit || rg <= 0) + continue; + + if (tx < 0) + az = -az; + + if (!contact->InFront(ship)) + if (az < 0) + az = -PI - az; + else + az = PI - az; + + // accentuate vertical: + ty *= 4; + + // draw: + int x = (int) (sin(az) * rg * rscale); + int y = (int) (cos(az) * rg * rscale/2); + int z = (int) (ty * rscale/2); + int size = 1; + + int x0 = xc+x; + int y0 = yc-y-z; + + if (c_ship && c_ship == ship->GetTarget()) + size = 2; + + Color mark = HUDView::MarkerColor(contact.value()); + + if (cockpit_hud_texture) { + cockpit_hud_texture->FillRect(x0-size, y0-size, x0+size, y0+size, mark); + + if (contact->Threat(ship)) { + if (c_ship) { + cockpit_hud_texture->DrawEllipse(x0-4, y0-4, x0+3, y0+3, mark); + } + else { + cockpit_hud_texture->DrawLine(x0, y0-5, x0+5, y0, mark); + cockpit_hud_texture->DrawLine(x0+5, y0, x0, y0+5, mark); + cockpit_hud_texture->DrawLine(x0, y0+5, x0-5, y0, mark); + cockpit_hud_texture->DrawLine(x0-5, y0, x0, y0-5, mark); + } + } + + if (z > 0) + cockpit_hud_texture->FillRect(x0-1, y0+size, x0, y0+z, mark); + else if (z < 0) + cockpit_hud_texture->FillRect(x0-1, y0+z, x0, y0-size, mark); + } + else { + window->FillRect(x0-size, y0-size, x0+size, y0+size, mark); + + if (contact->Threat(ship)) { + if (c_ship) { + window->DrawEllipse(x0-4, y0-4, x0+3, y0+3, mark); + } + else { + window->DrawLine(x0, y0-5, x0+5, y0, mark); + window->DrawLine(x0+5, y0, x0, y0+5, mark); + window->DrawLine(x0, y0+5, x0-5, y0, mark); + window->DrawLine(x0-5, y0, x0, y0-5, mark); + } + } + + if (z > 0) + window->FillRect(x0-1, y0+size, x0, y0+z, mark); + else if (z < 0) + window->FillRect(x0-1, y0+z, x0, y0-size, mark); + } + } + + DrawSensorLabels(Game::GetText("MFD.mode.3D").data()); +} + +// +--------------------------------------------------------------------+ + +// GROUND MAP + +void +MFD::DrawMap() +{ + DrawMFDText(0, Game::GetText("MFD.mode.ground").data(), Rect(rect.x, rect.y, rect.w, 12), DT_CENTER); +} + +// +--------------------------------------------------------------------+ + +void +MFD::DrawGauge(int x, int y, int percent) +{ + if (cockpit_hud_texture) { + x += this->index * 128 - this->rect.x; + y += 256 - this->rect.y; + cockpit_hud_texture->DrawRect(x, y, x+53, y+8, Color::DarkGray); + } + else { + window->DrawRect(x, y, x+53, y+8, Color::DarkGray); + } + + if (percent < 3) return; + if (percent > 100) percent = 100; + + percent /= 2; + + if (cockpit_hud_texture) + cockpit_hud_texture->FillRect(x+2, y+2, x+2+percent, y+7, Color::Gray); + else + window->FillRect(x+2, y+2, x+2+percent, y+7, Color::Gray); +} + +void +MFD::DrawGameMFD() +{ + if (lines < 10) lines++; + + char txt[64]; + Rect txt_rect(rect.x, rect.y, rect.w, 12); + + int t = 0; + + if (!HUDView::IsArcade() && HUDView::ShowFPS()) { + sprintf(txt, "FPS: %6.2f", Game::FrameRate()); + DrawMFDText(t++, txt, txt_rect, DT_LEFT); + txt_rect.y += 10; + + if (lines <= 1) return; + + Starshatter* game = Starshatter::GetInstance(); + sprintf(txt, "Polys: %d", game->GetPolyStats().npolys); + DrawMFDText(t++, txt, txt_rect, DT_LEFT); + txt_rect.y += 10; + } + + if (ship) { + DrawMFDText(t++, ship->Name(), txt_rect, DT_LEFT); + txt_rect.y += 10; + } + + if (lines <= 2) return; + + int hours = (Game::GameTime() / 3600000) ; + int minutes = (Game::GameTime() / 60000) % 60; + int seconds = (Game::GameTime() / 1000) % 60; + + if (ship) { + DWORD clock = ship->MissionClock(); + + hours = (clock / 3600000) ; + minutes = (clock / 60000) % 60; + seconds = (clock / 1000) % 60; + } + + if (Game::TimeCompression() > 1) + sprintf(txt, "%02d:%02d:%02d x%d", hours, minutes, seconds, Game::TimeCompression()); + else + sprintf(txt, "%02d:%02d:%02d", hours, minutes, seconds); + + DrawMFDText(t++, txt, txt_rect, DT_LEFT); + txt_rect.y += 10; + + if (HUDView::IsArcade() || lines <= 3) return; + + DrawMFDText(t++, ship->GetRegion()->Name(), txt_rect, DT_LEFT); + txt_rect.y += 10; + + if (lines <= 4) return; + + if (ship) { + switch (ship->GetFlightPhase()) { + case Ship::DOCKED: DrawMFDText(t++, Game::GetText("MFD.phase.DOCKED").data(), txt_rect, DT_LEFT); break; + case Ship::ALERT: DrawMFDText(t++, Game::GetText("MFD.phase.ALERT").data(), txt_rect, DT_LEFT); break; + case Ship::LOCKED: DrawMFDText(t++, Game::GetText("MFD.phase.LOCKED").data(), txt_rect, DT_LEFT); break; + case Ship::LAUNCH: DrawMFDText(t++, Game::GetText("MFD.phase.LAUNCH").data(), txt_rect, DT_LEFT); break; + case Ship::TAKEOFF: DrawMFDText(t++, Game::GetText("MFD.phase.TAKEOFF").data(), txt_rect, DT_LEFT); break; + case Ship::ACTIVE: DrawMFDText(t++, Game::GetText("MFD.phase.ACTIVE").data(), txt_rect, DT_LEFT); break; + case Ship::APPROACH: DrawMFDText(t++, Game::GetText("MFD.phase.APPROACH").data(), txt_rect, DT_LEFT); break; + case Ship::RECOVERY: DrawMFDText(t++, Game::GetText("MFD.phase.RECOVERY").data(), txt_rect, DT_LEFT); break; + case Ship::DOCKING: DrawMFDText(t++, Game::GetText("MFD.phase.DOCKING").data(), txt_rect, DT_LEFT); break; + } + } +} + +void +MFD::DrawStatusMFD() +{ + if (lines < 10) lines++; + + Rect status_rect(rect.x, rect.y, rect.w, 12); + int row = 0; + char txt[32]; + + if (status_rect.y > 320 && !ship->IsStarship()) + status_rect.y += 32; + + if (ship) { + Drive* drive = ship->GetDrive(); + if (drive) { + DrawMFDText(row++, Game::GetText("MFD.status.THRUST").data(), status_rect, DT_LEFT); + DrawGauge(status_rect.x+70, status_rect.y, (int) ship->Throttle()); + status_rect.y += 10; + } + + if (lines <= 1) return; + + if (ship->Reactors().size() > 0) { + PowerSource* reactor = ship->Reactors()[0]; + if (reactor) { + DrawMFDText(row++, Game::GetText("MFD.status.FUEL").data(), status_rect, DT_LEFT); + DrawGauge(status_rect.x+70, status_rect.y, reactor->Charge()); + status_rect.y += 10; + } + } + + if (lines <= 2) return; + + QuantumDrive* quantum_drive = ship->GetQuantumDrive(); + if (quantum_drive) { + DrawMFDText(row++, Game::GetText("MFD.status.QUANTUM").data(), status_rect, DT_LEFT); + DrawGauge(status_rect.x+70, status_rect.y, (int) quantum_drive->Charge()); + status_rect.y += 10; + } + + if (lines <= 3) return; + + double hull = ship->Integrity() / ship->Design()->integrity * 100; + int hull_status = System::CRITICAL; + + if (hull > 66) + hull_status = System::NOMINAL; + else if (hull > 33) + hull_status = System::DEGRADED; + + DrawMFDText(row++, Game::GetText("MFD.status.HULL").data(), status_rect, DT_LEFT); + DrawGauge(status_rect.x+70, status_rect.y, (int) hull); + status_rect.y += 10; + + if (lines <= 4) return; + + Shield* shield = ship->GetShield(); + if (shield) { + DrawMFDText(row++, Game::GetText("MFD.status.SHIELD").data(), status_rect, DT_LEFT); + DrawGauge(status_rect.x+70, status_rect.y, ship->ShieldStrength()); + status_rect.y += 10; + } + + if (lines <= 5) return; + + Weapon* primary = ship->GetPrimary(); + if (primary) { + DrawMFDText(row++, Game::GetText("MFD.status.GUNS").data(), status_rect, DT_LEFT); + DrawGauge(status_rect.x+70, status_rect.y, primary->Charge()); + status_rect.y += 10; + } + + if (lines <= 6) return; + + if (HUDView::IsArcade()) { + for (int i = 0; i < ship->Weapons().size() && i < 4; i++) { + WeaponGroup* w = ship->Weapons().at(i); + + if (w->IsMissile()) { + char ammo[8]; + + if (ship->GetSecondaryGroup() == w) + sprintf(ammo, "%d *", w->Ammo()); + else + sprintf(ammo, "%d", w->Ammo()); + + DrawMFDText(row++, (const char*) w->GetDesign()->name, status_rect, DT_LEFT); + status_rect.x += 70; + DrawMFDText(row++, ammo, status_rect, DT_LEFT); + status_rect.x -= 70; + status_rect.y += 10; + } + } + + if (ship->GetDecoy()) { + char ammo[8]; + sprintf(ammo, "%d", ship->GetDecoy()->Ammo()); + DrawMFDText(row++, Game::GetText("MFD.status.DECOY").data(), status_rect, DT_LEFT); + status_rect.x += 70; + DrawMFDText(row++, ammo, status_rect, DT_LEFT); + status_rect.x -= 70; + status_rect.y += 10; + } + + if (NetGame::GetInstance()) { + char lives[8]; + sprintf(lives, "%d", ship->RespawnCount() + 1); + DrawMFDText(row++, Game::GetText("MFD.status.LIVES").data(), status_rect, DT_LEFT); + status_rect.x += 70; + DrawMFDText(row++, lives, status_rect, DT_LEFT); + status_rect.x -= 70; + status_rect.y += 10; + } + + return; + } + + Sensor* sensor = ship->GetSensor(); + if (sensor) { + if (ship->GetFlightPhase() != Ship::ACTIVE) { + DrawMFDText(row++, Game::GetText("MFD.status.SENSOR").data(), status_rect, DT_LEFT); + status_rect.x += 70; + DrawMFDText(row++, Game::GetText("MFD.status.OFFLINE").data(), status_rect, DT_LEFT); + status_rect.x -= 70; + status_rect.y += 10; + } + + else { + DrawMFDText(row++, Game::GetText("MFD.status.EMCON").data(), status_rect, DT_LEFT); + status_rect.x += 70; + + sprintf(txt, "%s %d", Game::GetText("MFD.status.MODE").data(), ship->GetEMCON()); + + if (!sensor->IsPowerOn() || sensor->GetEnergy() == 0) { + if (!Game::Paused() && (Game::RealTime()/1000) & 2) + strcpy(txt, Game::GetText("MFD.status.SENSOR-OFF").data()); + } + + DrawMFDText(row++, txt, status_rect, DT_LEFT); + status_rect.x -= 70; + status_rect.y += 10; + } + } + + if (lines <= 7) return; + + DrawMFDText(row++, Game::GetText("MFD.status.SYSTEMS").data(), status_rect, DT_LEFT); + status_rect.x += 70; + DrawMFDText(row++, ship->GetDirectorInfo(), status_rect, DT_LEFT); + + if (NetGame::GetInstance()) { + char lives[8]; + sprintf(lives, "%d", ship->RespawnCount() + 1); + status_rect.x -= 70; + status_rect.y += 10; + DrawMFDText(row++, Game::GetText("MFD.status.LIVES").data(), status_rect, DT_LEFT); + status_rect.x += 70; + DrawMFDText(row++, lives, status_rect, DT_LEFT); + } + } +} + +// +--------------------------------------------------------------------+ + +void +MFD::SetStatusColor(int status) +{ + Color status_color; + + switch (status) { + default: + case System::NOMINAL: status_color = txt_color; break; + case System::DEGRADED: status_color = Color(255,255, 0); break; + case System::CRITICAL: status_color = Color(255, 0, 0); break; + case System::DESTROYED: status_color = Color( 0, 0, 0); break; + } +} + +// +--------------------------------------------------------------------+ + +bool MFD::IsMouseLatched() const +{ + return mouse_in; +} + +// +--------------------------------------------------------------------+ + +void MFD::DrawMFDText(int index, const char* txt, Rect& txt_rect, int align, int status) +{ + if (index >= MFD::TXT_LAST) { + Print("MFD DrawMFDText() invalid mfd_text index %d '%s'\n", index, txt); + } + else { + HUDText& mt = mfd_text[index]; + Color mc = mt.color; + + switch (status) { + default: + case System::NOMINAL: mc = txt_color; break; + case System::DEGRADED: mc = Color(255,255, 0); break; + case System::CRITICAL: mc = Color(255, 0, 0); break; + case System::DESTROYED: mc = Color( 0, 0, 0); break; + } + + char txt_buf[256]; + int n = strlen(txt); + + if (n > 250) n = 250; + + for (int i = 0; i < n; i++) { + if (islower(txt[i])) + txt_buf[i] = toupper(txt[i]); + else + txt_buf[i] = txt[i]; + } + + txt_buf[i] = 0; + + + if (cockpit_hud_texture) { + Rect hud_rect(txt_rect); + + hud_rect.x = txt_rect.x + this->index * 128 - this->rect.x; + hud_rect.y = txt_rect.y + 256 - this->rect.y; + + mt.font->SetColor(mc); + mt.font->DrawText(txt_buf, 0, hud_rect, align | DT_SINGLELINE, cockpit_hud_texture); + mt.rect = rect; + mt.hidden = false; + } + else { + if (txt_rect.Contains(Mouse::X(), Mouse::Y())) + mc = Color::White; + + mt.font->SetColor(mc); + mt.font->DrawText(txt_buf, 0, txt_rect, align | DT_SINGLELINE); + mt.rect = rect; + mt.hidden = false; + } + + } +} + +void MFD::HideMFDText(int index) +{ + if (index >= MFD::TXT_LAST) + Print("MFD HideMFDText() invalid mfd_text index %d\n", index); + else + mfd_text[index].hidden = true; +} + + + + diff --git a/Stars45/Mfd.h b/Stars45/Mfd.h new file mode 100644 index 0000000..d1985ea --- /dev/null +++ b/Stars45/Mfd.h @@ -0,0 +1,104 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: MFD.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Class for all Multi Function Displays +*/ + +#ifndef MFD_h +#define MFD_h + +#include "Types.h" +#include "Geometry.h" +#include "Color.h" +#include "HUDView.h" + +// +--------------------------------------------------------------------+ + +class Contact; +class Ship; +class Sprite; +class Window; + +// +--------------------------------------------------------------------+ + +class MFD +{ +public: + enum Modes { MFD_MODE_OFF, MFD_MODE_GAME, MFD_MODE_SHIP, + MFD_MODE_FOV, /*MFD_MODE_FWD, MFD_MODE_MAP,*/ + MFD_MODE_HSD, MFD_MODE_3D + }; + + MFD(Window* c, int index); + virtual ~MFD(); + + int operator == (const MFD& that) const { return this == &that; } + + static void Initialize(); + static void Close(); + static void SetColor(Color c); + + // Operations: + virtual void Draw(); + virtual void DrawGameMFD(); + virtual void DrawStatusMFD(); + virtual void DrawSensorMFD(); + virtual void DrawHSD(); + virtual void Draw3D(); + virtual void DrawSensorLabels(const char* mfd_mode); + virtual void DrawMap(); + virtual void DrawGauge(int x, int y, int percent); + virtual void SetStatusColor(int status); + + virtual void SetWindow(Window* w) { window = w; } + virtual Window* GetWindow() { return window; } + virtual void SetRect(const Rect& r); + virtual const Rect& GetRect() const { return rect; } + virtual void SetMode(int m); + virtual int GetMode() const { return mode; } + virtual Sprite* GetSprite() { return sprite; } + + virtual void SetShip(Ship* s) { ship = s; } + virtual Ship* GetShip() { return ship; } + + virtual void Show(); + virtual void Hide(); + + virtual void UseCameraView(CameraView* v); + void DrawMFDText(int index, const char* txt, Rect& r, int align, int status=-1); + void HideMFDText(int index); + void SetText3DColor(Color c); + void SetCockpitHUDTexture(Bitmap* bmp) { cockpit_hud_texture = bmp; } + + bool IsMouseLatched() const; + +protected: + + enum { TXT_LAST=20 }; + + Window* window; + Rect rect; + int index; + int mode; + int lines; + Sprite* sprite; + bool hidden; + Ship* ship; + HUDText mfd_text[TXT_LAST]; + CameraView* camview; + Bitmap* cockpit_hud_texture; + + int mouse_latch; + bool mouse_in; +}; + +#endif MFD_h + diff --git a/Stars45/Mission.cpp b/Stars45/Mission.cpp new file mode 100644 index 0000000..be8a711 --- /dev/null +++ b/Stars45/Mission.cpp @@ -0,0 +1,2160 @@ +/* Project Starshatter 5.0 + Destroyer Studios LLC + Copyright © 1997-2007. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Mission.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Mission classes +*/ + +#include "MemDebug.h" +#include "Mission.h" +#include "MissionEvent.h" +#include "StarSystem.h" +#include "Galaxy.h" +#include "Starshatter.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "Element.h" +#include "Instruction.h" +#include "WeaponDesign.h" +#include "Sim.h" + +#include "Game.h" +#include "DataLoader.h" +#include "ParseUtil.h" +#include "FormatUtil.h" +#include "Random.h" +#include "Skin.h" + +// +--------------------------------------------------------------------+ + +Mission::Mission(int identity, const char* fname, const char* pname) + : id(identity), type(0), team(1), ok(false), active(false), complete(false), + star_system(0), start(33 * 3600), stardate(0), target(0), ward(0), + current(0), degrees(false) +{ + objective = Game::GetText("Mission.unspecified"); + sitrep = Game::GetText("Mission.unknown"); + + if (fname) + strcpy(filename, fname); + else + ZeroMemory(filename, sizeof(filename)); + + if (pname) + strcpy(path, pname); + else + strcpy(path, "Missions/"); +} + +Mission::~Mission() +{ + ::Print("Mission::~Mission() id = %d name = '%s'\n", id, name.data()); + elements.destroy(); + events.destroy(); +} + +// +--------------------------------------------------------------------+ + +const char* +Mission::Subtitles() const +{ + return subtitles; +} + +// +--------------------------------------------------------------------+ + +void +Mission::AddElement(MissionElement* elem) +{ + if (elem) + elements.append(elem); +} + +// +--------------------------------------------------------------------+ + +MissionElement* +Mission::FindElement(const char* name) +{ + ListIter iter = elements; + while (++iter) { + MissionElement* elem = iter.value(); + + if (elem->Name() == name) + return elem; + } + + return 0; +} + +// +--------------------------------------------------------------------+ + +void +Mission::IncreaseElemPriority(int elem_index) +{ + if (elem_index > 0 && elem_index < elements.size()) { + MissionElement* elem1 = elements.at(elem_index-1); + MissionElement* elem2 = elements.at(elem_index); + + elements.at(elem_index-1) = elem2; + elements.at(elem_index) = elem1; + } +} + +void +Mission::DecreaseElemPriority(int elem_index) +{ + if (elem_index >= 0 && elem_index < elements.size()-1) { + MissionElement* elem1 = elements.at(elem_index); + MissionElement* elem2 = elements.at(elem_index+1); + + elements.at(elem_index) = elem2; + elements.at(elem_index+1) = elem1; + } +} + +// +--------------------------------------------------------------------+ + +void +Mission::IncreaseEventPriority(int event_index) +{ + if (event_index > 0 && event_index < events.size()) { + MissionEvent* event1 = events.at(event_index-1); + MissionEvent* event2 = events.at(event_index); + + events.at(event_index-1) = event2; + events.at(event_index) = event1; + } +} + +void +Mission::DecreaseEventPriority(int event_index) +{ + if (event_index >= 0 && event_index < events.size()-1) { + MissionEvent* event1 = events.at(event_index); + MissionEvent* event2 = events.at(event_index+1); + + events.at(event_index) = event2; + events.at(event_index+1) = event1; + } +} + +// +--------------------------------------------------------------------+ + +void +Mission::SetStarSystem(StarSystem* s) +{ + if (star_system != s) { + star_system = s; + + if (!system_list.contains(s)) + system_list.append(s); + } +} + +void +Mission::ClearSystemList() +{ + star_system = 0; + system_list.clear(); +} + +// +--------------------------------------------------------------------+ + +void +Mission::SetPlayer(MissionElement* player_element) +{ + ListIter elem = elements; + while (++elem) { + MissionElement* element = elem.value(); + if (element == player_element) + element->player = 1; + else + element->player = 0; + } +} + +MissionElement* +Mission::GetPlayer() +{ + MissionElement* p = 0; + + ListIter elem = elements; + while (++elem) { + if (elem->player > 0) + p = elem.value(); + } + + return p; +} + +// +--------------------------------------------------------------------+ + +MissionEvent* +Mission::FindEvent(int event_type) const +{ + Mission* pThis = (Mission*) this; + ListIter iter = pThis->events; + while (++iter) { + MissionEvent* event = iter.value(); + + if (event->Event() == event_type) + return event; + } + + return 0; +} + +void +Mission::AddEvent(MissionEvent* event) +{ + if (event) + events.append(event); +} + +// +--------------------------------------------------------------------+ + +bool +Mission::Load(const char* fname, const char* pname) +{ + ok = false; + + if (fname) + strcpy(filename, fname); + + if (pname) + strcpy(path, pname); + + if (!filename[0]) { + Print("\nCan't Load Mission, script unspecified.\n"); + return ok; + } + + // wipe existing mission before attempting to load... + elements.destroy(); + events.destroy(); + + Print("\nLoad Mission: '%s'\n", filename); + + DataLoader* loader = DataLoader::GetLoader(); + bool old_fs = loader->IsFileSystemEnabled(); + BYTE* block = 0; + + loader->UseFileSystem(true); + loader->SetDataPath(path); + loader->LoadBuffer(filename, block, true); + loader->SetDataPath(0); + loader->UseFileSystem(old_fs); + + ok = ParseMission((const char*) block); + + loader->ReleaseBuffer(block); + Print("Mission Loaded.\n\n"); + + if (ok) + Validate(); + + return ok; +} + +// +--------------------------------------------------------------------+ + +bool +Mission::ParseMission(const char* block) +{ + Parser parser(new(__FILE__,__LINE__) BlockReader(block)); + Term* term = parser.ParseTerm(); + char err[256]; + + if (!term) { + sprintf(err, "ERROR: could not parse '%s'\n", filename); + AddError(err); + return ok; + } + else { + TermText* file_type = term->isText(); + if (!file_type || file_type->value() != "MISSION") { + sprintf(err, "ERROR: invalid mission file '%s'\n", filename); + AddError(err); + term->print(10); + return ok; + } + } + + ok = true; + + char target_name[256]; + char ward_name[256]; + + target_name[0] = 0; + ward_name[0] = 0; + + do { + delete term; term = 0; + term = parser.ParseTerm(); + + if (term) { + TermDef* def = term->isDef(); + if (def) { + Text defname = def->name()->value(); + defname.setSensitive(false); + + if (defname == "name") { + GetDefText(name, def, filename); + name = Game::GetText(name); + } + + else if (defname == "desc") { + GetDefText(desc, def, filename); + if (desc.length() > 0 && desc.length() < 32) + desc = Game::GetText(desc); + } + + else if (defname == "type") { + char typestr[64]; + GetDefText(typestr, def, filename); + type = TypeFromName(typestr); + } + + else if (defname == "system") { + char sysname[64]; + GetDefText(sysname, def, filename); + + Galaxy* galaxy = Galaxy::GetInstance(); + + if (galaxy) { + SetStarSystem(galaxy->GetSystem(sysname)); + } + } + + else if (defname == "degrees") + GetDefBool(degrees, def, filename); + + else if (defname == "region") + GetDefText(region, def, filename); + + else if (defname == "objective") { + GetDefText(objective, def, filename); + if (objective.length() > 0 && objective.length() < 32) + objective = Game::GetText(objective); + } + + else if (defname == "sitrep") { + GetDefText(sitrep, def, filename); + if (sitrep.length() > 0 && sitrep.length() < 32) + sitrep = Game::GetText(sitrep); + } + + else if (defname == "subtitles") { + Text subtitles_path; + DataLoader* loader = DataLoader::GetLoader(); + BYTE* block = 0; + + GetDefText(subtitles_path, def, filename); + loader->SetDataPath(0); + loader->LoadBuffer(subtitles_path, block, true); + + subtitles = Text("\n") + (const char*) block; + + loader->ReleaseBuffer(block); + } + + else if (defname == "start") + GetDefTime(start, def, filename); + + else if (defname == "stardate") + GetDefNumber(stardate, def, filename); + + else if (defname == "team") + GetDefNumber(team, def, filename); + + else if (defname == "target") + GetDefText(target_name, def, filename); + + else if (defname == "ward") + GetDefText(ward_name, def, filename); + + else if ((defname == "element") || + (defname == "ship") || + (defname == "station")) { + + if (!def->term() || !def->term()->isStruct()) { + sprintf(err, "ERROR: element struct missing in '%s'\n", filename); + AddError(err); + } + else { + TermStruct* val = def->term()->isStruct(); + MissionElement* elem = ParseElement(val); + AddElement(elem); + } + } + + else if (defname == "event") { + if (!def->term() || !def->term()->isStruct()) { + sprintf(err, "ERROR: event struct missing in '%s'\n", filename); + AddError(err); + } + else { + TermStruct* val = def->term()->isStruct(); + MissionEvent* event = ParseEvent(val); + AddEvent(event); + } + } + } // def + } // term + } + while (term); + + if (ok) { + if (target_name[0]) + target = FindElement(target_name); + + if (ward_name[0]) + ward = FindElement(ward_name); + } + + return ok; +} + +// +--------------------------------------------------------------------+ + +bool +Mission::Save() +{ + Validate(); + + if (!filename[0] || !path[0]) { + AddError(Game::GetText("Mission.error.no-file")); + return ok; + } + + Text content = Serialize(); + + if (content.length() < 8) { + AddError(Game::GetText("Mission.error.no-serial")); + return ok; + } + + if (!stricmp(path, "mods/missions/")) { + CreateDirectory("Mods", 0); + CreateDirectory("Mods/Missions", 0); + } + + else if (!stricmp(path, "multiplayer/")) { + CreateDirectory("Multiplayer", 0); + } + + char fname[256]; + sprintf(fname, "%s%s", path, filename); + FILE* f = fopen(fname, "w"); + if (f) { + fwrite(content.data(), content.length(), 1, f); + fclose(f); + } + + return ok; +} + +// +--------------------------------------------------------------------+ + +void +Mission::Validate() +{ + char err[256]; + + ok = true; + + if (elements.isEmpty()) { + sprintf(err, Game::GetText("Mission.error.no-elem").data(), filename); + AddError(err); + } + else { + bool found_player = false; + + for (int i = 0; i < elements.size(); i++) { + MissionElement* elem = elements.at(i); + + if (elem->Name().length() < 1) { + sprintf(err, Game::GetText("Mission.error.unnamed-elem").data(), filename); + AddError(err); + } + + if (elem->Player() > 0) { + if (!found_player) { + found_player = true; + + if (elem->Region() != GetRegion()) { + sprintf(err, Game::GetText("Mission.error.wrong-sector").data(), + elem->Name().data(), + GetRegion()); + AddError(err); + } + } + else { + sprintf(err, Game::GetText("Mission.error.extra-player").data(), + elem->Name().data(), + filename); + AddError(err); + } + } + } + + if (!found_player) { + sprintf(err, Game::GetText("Mission.error.no-player").data(), filename); + AddError(err); + } + } +} + +void +Mission::AddError(Text err) +{ + ::Print(err); + errmsg += err; + + ok = false; +} + +// +--------------------------------------------------------------------+ + +#define MSN_CHECK(x) if (!stricmp(n, #x)) result = Mission::x; + +int +Mission::TypeFromName(const char* n) +{ + int result = -1; + + MSN_CHECK(PATROL) + else MSN_CHECK(SWEEP) + else MSN_CHECK(INTERCEPT) + else MSN_CHECK(AIR_PATROL) + else MSN_CHECK(AIR_SWEEP) + else MSN_CHECK(AIR_INTERCEPT) + else MSN_CHECK(STRIKE) + else MSN_CHECK(DEFEND) + else MSN_CHECK(ESCORT) + else MSN_CHECK(ESCORT_FREIGHT) + else MSN_CHECK(ESCORT_SHUTTLE) + else MSN_CHECK(ESCORT_STRIKE) + else MSN_CHECK(INTEL) + else MSN_CHECK(SCOUT) + else MSN_CHECK(RECON) + else MSN_CHECK(TRANSPORT) + else MSN_CHECK(BLOCKADE) + else MSN_CHECK(FLEET) + else MSN_CHECK(BOMBARDMENT) + else MSN_CHECK(FLIGHT_OPS) + else MSN_CHECK(TRANSPORT) + else MSN_CHECK(CARGO) + else MSN_CHECK(TRAINING) + else MSN_CHECK(OTHER) + + if (result < PATROL) { + for (int i = PATROL; i <= OTHER && result < PATROL; i++) { + if (!stricmp(n, RoleName(i))) { + result = i; + } + } + } + + return result; +} + +// +--------------------------------------------------------------------+ + +static int elem_id = 351; + +MissionElement* +Mission::ParseElement(TermStruct* val) +{ + Text design; + Text skin_name; + Text role_name; + int deck = 1; + char err[256]; + + MissionElement* element = new(__FILE__,__LINE__) MissionElement(); + element->rgn_name = region; + element->elem_id = elem_id++; + + current = element; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + Text defname = pdef->name()->value(); + defname.setSensitive(false); + + if (defname == "name") + GetDefText(element->name, pdef, filename); + + else if (defname == "carrier") + GetDefText(element->carrier, pdef, filename); + + else if (defname == "commander") + GetDefText(element->commander, pdef, filename); + + else if (defname == "squadron") + GetDefText(element->squadron, pdef, filename); + + else if (defname == "path") + GetDefText(element->path, pdef, filename); + + else if (defname == "design") { + GetDefText(design, pdef, filename); + element->design = ShipDesign::Get(design, element->path); + + if (!element->design) { + sprintf(err, Game::GetText("Mission.error.unknown-ship").data(), design.data(), filename); + AddError(err); + } + } + + else if (defname == "skin") { + if (!element->design) { + sprintf(err, Game::GetText("Mission.error.out-of-order").data(), filename); + AddError(err); + } + + else if (pdef->term()->isText()) { + GetDefText(skin_name, pdef, filename); + element->skin = element->design->FindSkin(skin_name); + } + + else if (pdef->term()->isStruct()) { + sprintf(err, Game::GetText("Mission.error.bad-skin").data(), filename); + AddError(err); + } + } + + else if (defname == "mission") { + GetDefText(role_name, pdef, filename); + element->mission_role = TypeFromName(role_name); + } + + else if (defname == "intel") { + GetDefText(role_name, pdef, filename); + element->intel = Intel::IntelFromName(role_name); + } + + else if (defname == "loc") { + Vec3 loc; + GetDefVec(loc, pdef, filename); + element->SetLocation(loc); + } + + else if (defname == "rloc") { + if (pdef->term()->isStruct()) { + RLoc* rloc = ParseRLoc(pdef->term()->isStruct()); + element->SetRLoc(*rloc); + delete rloc; + } + } + + else if (defname.indexOf("head") == 0) { + if (pdef->term()->isArray()) { + Vec3 head; + GetDefVec(head, pdef, filename); + if (degrees) head.z *= (float) DEGREES; + element->heading = head.z; + } + else if (pdef->term()->isNumber()) { + double heading = 0; + GetDefNumber(heading, pdef, filename); + if (degrees) heading *= DEGREES; + element->heading = heading; + } + } + + else if (defname == "region" || defname == "rgn") + GetDefText(element->rgn_name, pdef, filename); + + else if (defname == "iff") + GetDefNumber(element->IFF_code, pdef, filename); + + else if (defname == "count") + GetDefNumber(element->count, pdef, filename); + + else if (defname == "maint_count") + GetDefNumber(element->maint_count, pdef, filename); + + else if (defname == "dead_count") + GetDefNumber(element->dead_count, pdef, filename); + + else if (defname == "player") + GetDefNumber(element->player, pdef, filename); + + else if (defname == "alert") + GetDefBool(element->alert, pdef, filename); + + else if (defname == "playable") + GetDefBool(element->playable, pdef, filename); + + else if (defname == "rogue") + GetDefBool(element->rogue, pdef, filename); + + else if (defname == "invulnerable") + GetDefBool(element->invulnerable, pdef, filename); + + else if (defname == "command_ai") + GetDefNumber(element->command_ai, pdef, filename); + + else if (defname.indexOf("respawn") == 0) + GetDefNumber(element->respawns, pdef, filename); + + else if (defname.indexOf("hold") == 0) + GetDefNumber(element->hold_time, pdef, filename); + + else if (defname.indexOf("zone") == 0) { + if (pdef->term() && pdef->term()->isBool()) { + bool locked = false; + GetDefBool(locked, pdef, filename); + element->zone_lock = locked; + } + else { + GetDefNumber(element->zone_lock, pdef, filename); + } + } + + else if (defname == "objective") { + if (!pdef->term() || !pdef->term()->isStruct()) { + sprintf(err, Game::GetText("Mission.error.no-objective").data(), element->name.data(), filename); + AddError(err); + } + else { + TermStruct* val = pdef->term()->isStruct(); + Instruction* obj = ParseInstruction(val, element); + element->objectives.append(obj); + } + } + + else if (defname == "instr") { + Text* obj = new(__FILE__,__LINE__) Text; + if (GetDefText(*obj, pdef, filename)) + element->instructions.append(obj); + else + delete obj; + } + + else if (defname == "ship") { + if (!pdef->term() || !pdef->term()->isStruct()) { + sprintf(err, Game::GetText("Mission.error.no-ship").data(), element->name.data(), filename); + AddError(err); + } + else { + TermStruct* val = pdef->term()->isStruct(); + MissionShip* s = ParseShip(val, element); + element->ships.append(s); + + if (s->Integrity() < 0 && element->design) + s->SetIntegrity(element->design->integrity); + } + } + + else if (defname == "order" || defname == "navpt") { + if (!pdef->term() || !pdef->term()->isStruct()) { + sprintf(err, Game::GetText("Mission.error.no-navpt").data(), element->name.data(), filename); + AddError(err); + } + else { + TermStruct* val = pdef->term()->isStruct(); + Instruction* npt = ParseInstruction(val, element); + element->navlist.append(npt); + } + } + + else if (defname == "loadout") { + if (!pdef->term() || !pdef->term()->isStruct()) { + sprintf(err, Game::GetText("Mission.error.no-loadout").data(), element->name.data(), filename); + AddError(err); + } + else { + TermStruct* val = pdef->term()->isStruct(); + ParseLoadout(val, element); + } + } + } + } + + if (element->name.length() < 1) { + sprintf(err, Game::GetText("Mission.error.unnamed-elem").data(), filename); + AddError(err); + } + + else if (element->design == 0) { + sprintf(err, Game::GetText("Mission.error.unknown-ship").data(), element->name.data(), filename); + AddError(err); + } + + current = 0; + + return element; +} + +MissionEvent* +Mission::ParseEvent(TermStruct* val) +{ + MissionEvent* event = new(__FILE__,__LINE__) MissionEvent; + Text event_name; + Text trigger_name; + static int event_id = 1; + static double event_time = 0; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + Text defname = pdef->name()->value(); + defname.setSensitive(false); + + if (defname == "event") { + GetDefText(event_name, pdef, filename); + event->event = MissionEvent::EventForName(event_name); + } + + else if (defname == "trigger") { + GetDefText(trigger_name, pdef, filename); + event->trigger = MissionEvent::TriggerForName(trigger_name); + } + + else if (defname == "id") + GetDefNumber(event_id, pdef, filename); + + else if (defname == "time") + GetDefNumber(event_time, pdef, filename); + + else if (defname == "delay") + GetDefNumber(event->delay, pdef, filename); + + else if (defname == "event_param" || defname == "param" || defname == "color") { + ZeroMemory(event->event_param, sizeof(event->event_param)); + + if (pdef->term()->isNumber()) { + GetDefNumber(event->event_param[0], pdef, filename); + event->event_nparams = 1; + } + + else if (pdef->term()->isArray()) { + FloatList plist; + GetDefArray(plist, pdef, filename); + + for (int i = 0; i < 10 && i < plist.size(); i++) { + float f = plist[i]; + event->event_param[i] = (int) f; + event->event_nparams = i + 1; + } + } + } + + else if (defname == "trigger_param") { + ZeroMemory(event->trigger_param, sizeof(event->trigger_param)); + + if (pdef->term()->isNumber()) { + GetDefNumber(event->trigger_param[0], pdef, filename); + event->trigger_nparams = 1; + } + + else if (pdef->term()->isArray()) { + FloatList plist; + GetDefArray(plist, pdef, filename); + + for (int i = 0; i < 10 && i < plist.size(); i++) { + float f = plist[i]; + event->trigger_param[i] = (int) f; + event->trigger_nparams = i + 1; + } + } + } + + else if (defname == "event_ship" || defname == "ship") + GetDefText(event->event_ship, pdef, filename); + + else if (defname == "event_source" || defname == "source" || defname == "font") + GetDefText(event->event_source, pdef, filename); + + else if (defname == "event_target" || defname == "target" || defname == "image") + GetDefText(event->event_target, pdef, filename); + + else if (defname == "event_message" || defname == "message") { + Text raw_msg; + GetDefText(raw_msg, pdef, filename); + raw_msg = Game::GetText(raw_msg); + event->event_message = FormatTextEscape(raw_msg); + } + + else if (defname == "event_chance" || defname == "chance") + GetDefNumber(event->event_chance, pdef, filename); + + else if (defname == "event_sound" || defname == "sound") + GetDefText(event->event_sound, pdef, filename); + + else if (defname == "loc" || defname == "vec" || defname == "fade") + GetDefVec(event->event_point, pdef, filename); + + else if (defname == "rect") + GetDefRect(event->event_rect, pdef, filename); + + else if (defname == "trigger_ship") + GetDefText(event->trigger_ship, pdef, filename); + + else if (defname == "trigger_target") + GetDefText(event->trigger_target, pdef, filename); + } + } + + event->id = event_id++; + event->time = event_time; + return event; +} + +MissionShip* +Mission::ParseShip(TermStruct* val, MissionElement* element) +{ + MissionShip* msn_ship = new(__FILE__,__LINE__) MissionShip; + + Text name; + Text skin_name; + Text regnum; + Text region; + char err[256]; + Vec3 loc(-1.0e9f, -1.0e9f, -1.0e9f); + Vec3 vel(-1.0e9f, -1.0e9f, -1.0e9f); + int respawns = -1; + double heading = -1e9; + double integrity = -1; + int ammo[16]; + int fuel[4]; + int i; + + for (i = 0; i < 16; i++) + ammo[i] = -10; + + for (i = 0; i < 4; i++) + fuel[i] = -10; + + for (i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + Text defname = pdef->name()->value(); + defname.setSensitive(false); + + if (defname == "name") + GetDefText(name, pdef, filename); + + else if (defname == "skin") { + if (!element || !element->design) { + sprintf(err, Game::GetText("Mission.error.out-of-order").data(), filename); + AddError(err); + } + + else if (pdef->term()->isText()) { + GetDefText(skin_name, pdef, filename); + msn_ship->skin = element->design->FindSkin(skin_name); + } + + else if (pdef->term()->isStruct()) { + sprintf(err, Game::GetText("Mission.error.bad-skin").data(), filename); + AddError(err); + } + } + + else if (defname == "regnum") + GetDefText(regnum, pdef, filename); + + else if (defname == "region") + GetDefText(region, pdef, filename); + + else if (defname == "loc") + GetDefVec(loc, pdef, filename); + + else if (defname == "velocity") + GetDefVec(vel, pdef, filename); + + else if (defname == "respawns") + GetDefNumber(respawns, pdef, filename); + + else if (defname == "heading") { + if (pdef->term()->isArray()) { + Vec3 h; + GetDefVec(h, pdef, filename); + if (degrees) h.z *= (float) DEGREES; + heading = h.z; + } + else if (pdef->term()->isNumber()) { + double h = 0; + GetDefNumber(h, pdef, filename); + if (degrees) h *= DEGREES; + heading = h; + } + } + + else if (defname == "integrity") + GetDefNumber(integrity, pdef, filename); + + else if (defname == "ammo") + GetDefArray(ammo, 16, pdef, filename); + + else if (defname == "fuel") + GetDefArray(fuel, 4, pdef, filename); + } + } + + msn_ship->SetName(name); + msn_ship->SetRegNum(regnum); + msn_ship->SetRegion(region); + msn_ship->SetIntegrity(integrity); + + if (loc.x > -1e9) + msn_ship->SetLocation(loc); + + if (vel.x > -1e9) + msn_ship->SetVelocity(vel); + + if (respawns > -1) + msn_ship->SetRespawns(respawns); + + if (heading > -1e9) + msn_ship->SetHeading(heading); + + if (ammo[0] > -10) + msn_ship->SetAmmo(ammo); + + if (fuel[0] > -10) + msn_ship->SetFuel(fuel); + + return msn_ship; +} + +Instruction* +Mission::ParseInstruction(TermStruct* val, MissionElement* element) +{ + int order = Instruction::VECTOR; + int status = Instruction::PENDING; + int formation = 0; + int speed = 0; + int priority = 1; + int farcast = 0; + int hold = 0; + int emcon = 0; + Vec3 loc(0,0,0); + RLoc* rloc = 0; + Text order_name; + Text status_name; + Text order_rgn_name; + Text tgt_name; + Text tgt_desc; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + Text defname = pdef->name()->value(); + defname.setSensitive(false); + + if (defname == "cmd") { + GetDefText(order_name, pdef, filename); + + for (int cmd = 0; cmd < Instruction::NUM_ACTIONS; cmd++) + if (!stricmp(order_name, Instruction::ActionName(cmd))) + order = cmd; + } + + else if (defname == "status") { + GetDefText(status_name, pdef, filename); + + for (int n = 0; n < Instruction::NUM_STATUS; n++) + if (!stricmp(status_name, Instruction::StatusName(n))) + status = n; + } + + else if (defname == "loc") { + GetDefVec(loc, pdef, filename); + } + + else if (defname == "rloc") { + if (pdef->term()->isStruct()) + rloc = ParseRLoc(pdef->term()->isStruct()); + } + + else if (defname == "rgn") { + GetDefText(order_rgn_name, pdef, filename); + } + else if (defname == "speed") { + GetDefNumber(speed, pdef, filename); + } + else if (defname == "formation") { + GetDefNumber(formation, pdef, filename); + } + else if (defname == "emcon") { + GetDefNumber(emcon, pdef, filename); + } + else if (defname == "priority") { + GetDefNumber(priority, pdef, filename); + } + else if (defname == "farcast") { + if (pdef->term()->isBool()) { + bool f = false; + GetDefBool(f, pdef, filename); + farcast = f; + } + else { + GetDefNumber(farcast, pdef, filename); + } + } + else if (defname == "tgt") { + GetDefText(tgt_name, pdef, filename); + } + else if (defname == "tgt_desc") { + GetDefText(tgt_desc, pdef, filename); + } + else if (defname.indexOf("hold") == 0) { + GetDefNumber(hold, pdef, filename); + } + } + } + + Text rgn; + + if (order_rgn_name.length() > 0) + rgn = order_rgn_name; + + else if (element->navlist.size() > 0) + rgn = element->navlist[element->navlist.size()-1]->RegionName(); + + else + rgn = region; + + if (tgt_desc.length() && tgt_name.length()) + tgt_desc = tgt_desc + " " + tgt_name; + + Instruction* instr = new(__FILE__,__LINE__) Instruction(rgn, loc, order); + + instr->SetStatus(status); + instr->SetEMCON(emcon); + instr->SetFormation(formation); + instr->SetSpeed(speed); + instr->SetTarget(tgt_name); + instr->SetTargetDesc(tgt_desc); + instr->SetPriority(priority-1); + instr->SetFarcast(farcast); + instr->SetHoldTime(hold); + + if (rloc) { + instr->GetRLoc() = *rloc; + delete rloc; + } + + return instr; +} + +void +Mission::ParseLoadout(TermStruct* val, MissionElement* element) +{ + int ship = -1; + int stations[16]; + Text name; + + ZeroMemory(stations, sizeof(stations)); + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + Text defname = pdef->name()->value(); + defname.setSensitive(false); + + if (defname == "ship") { + GetDefNumber(ship, pdef, filename); + } + else if (defname == "name") { + GetDefText(name, pdef, filename); + } + else if (defname == "stations") { + GetDefArray(stations, 16, pdef, filename); + } + } + } + + MissionLoad* load = new(__FILE__,__LINE__) MissionLoad(ship); + + if (name.length()) + load->SetName(name); + + for (i = 0; i < 16; i++) + load->SetStation(i, stations[i]); + + element->loadouts.append(load); +} + +RLoc* +Mission::ParseRLoc(TermStruct* val) +{ + Vec3 base_loc; + RLoc* rloc = new(__FILE__,__LINE__) RLoc; + RLoc* ref = 0; + + double dex = 0; + double dex_var = 5e3; + double az = 0; + double az_var = 3.1415; + double el = 0; + double el_var = 0.1; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + Text defname = pdef->name()->value(); + defname.setSensitive(false); + + if (defname == "dex") { + GetDefNumber(dex, pdef, filename); + rloc->SetDistance(dex); + } + else if (defname == "dex_var") { + GetDefNumber(dex_var, pdef, filename); + rloc->SetDistanceVar(dex_var); + } + else if (defname == "az") { + GetDefNumber(az, pdef, filename); + if (degrees) az *= DEGREES; + rloc->SetAzimuth(az); + } + else if (defname == "az_var") { + GetDefNumber(az_var, pdef, filename); + if (degrees) az_var *= DEGREES; + rloc->SetAzimuthVar(az_var); + } + else if (defname == "el") { + GetDefNumber(el, pdef, filename); + if (degrees) el *= DEGREES; + rloc->SetElevation(el); + } + else if (defname == "el_var") { + GetDefNumber(el_var, pdef, filename); + if (degrees) el_var *= DEGREES; + rloc->SetElevationVar(el_var); + } + else if (defname == "loc") { + GetDefVec(base_loc, pdef, filename); + rloc->SetBaseLocation(base_loc); + } + + else if (defname == "ref") { + Text refstr; + GetDefText(refstr, pdef, filename); + + int sep = refstr.indexOf(':'); + + if (sep >= 0) { + Text elem_name = refstr.substring(0, sep); + Text nav_name = refstr.substring(sep+1, refstr.length()); + MissionElement* elem = 0; + + if (elem_name == "this") + elem = current; + else + elem = FindElement(elem_name); + + if (elem && elem->NavList().size() > 0) { + int index = atoi(nav_name)-1; + if (index < 0) + index = 0; + else if (index >= elem->NavList().size()) + index = elem->NavList().size()-1; + + ref = &elem->NavList()[index]->GetRLoc(); + rloc->SetReferenceLoc(ref); + } + else { + ::Print("Warning: no ref found for rloc '%s' in elem '%s'\n", refstr.data(), current->Name().data()); + rloc->SetBaseLocation(RandomPoint()); + } + } + else { + MissionElement* elem = 0; + + if (refstr == "this") + elem = current; + else + elem = FindElement(refstr); + + if (elem) { + ref = &elem->GetRLoc(); + rloc->SetReferenceLoc(ref); + } + else { + ::Print("Warning: no ref found for rloc '%s' in elem '%s'\n", refstr.data(), current->Name().data()); + rloc->SetBaseLocation(RandomPoint()); + } + } + } + } + } + + return rloc; +} + +const char* +Mission::RoleName(int role) +{ + switch (role) { + case PATROL: return "Patrol"; + case SWEEP: return "Sweep"; + case INTERCEPT: return "Intercept"; + case AIR_PATROL: return "Airborne Patrol"; + case AIR_SWEEP: return "Airborne Sweep"; + case AIR_INTERCEPT: return "Airborne Intercept"; + case STRIKE: return "Strike"; + case ASSAULT: return "Assault"; + case DEFEND: return "Defend"; + case ESCORT: return "Escort"; + case ESCORT_FREIGHT: return "Freight Escort"; + case ESCORT_SHUTTLE: return "Shuttle Escort"; + case ESCORT_STRIKE: return "Strike Escort"; + case INTEL: return "Intel"; + case SCOUT: return "Scout"; + case RECON: return "Recon"; + case BLOCKADE: return "Blockade"; + case FLEET: return "Fleet"; + case BOMBARDMENT: return "Attack"; + case FLIGHT_OPS: return "Flight Ops"; + case TRANSPORT: return "Transport"; + case CARGO: return "Cargo"; + case TRAINING: return "Training"; + default: + case OTHER: return "Misc"; + } +} + +// +--------------------------------------------------------------------+ + +Text +Mission::Serialize(const char* player_elem, int player_index) +{ + Text s = "MISSION\n\nname: \""; + s += SafeString(Name()); + + if (desc.length()) { + s += "\"\ndesc: \""; + s += SafeString(desc); + } + + s += "\"\ntype: \""; + s += SafeString(TypeName()); + + ListIter sys_iter = system_list; + while (++sys_iter) { + StarSystem* sys = sys_iter.value(); + + if (sys != star_system) { + s += "\"\nsystem: \""; + s += sys->Name(); + } + } + + s += "\"\nsystem: \""; + if (GetStarSystem()) + s += SafeString(GetStarSystem()->Name()); + else + s += "null"; + + ListIter iter = GetElements(); + + Sim* sim = Sim::GetSim(); + if (sim && sim->GetElements().size() > 0) + iter = sim->GetMissionElements(); + + s += "\"\n"; + + bool region_set = false; + if (player_elem && *player_elem) { + // set the mission region to that of the active player + while (++iter) { + MissionElement* e = iter.value(); + if (e->Name() == player_elem) { + char buf[32]; + sprintf(buf, "team: %d\n", e->GetIFF()); + s += buf; + + s += "region: \""; + s += SafeString(e->Region()); + s += "\"\n\n"; + + region_set = true; + break; + } + } + + iter.reset(); + } + + if (!region_set) { + s += "region: \""; + s += SafeString(GetRegion()); + s += "\"\n\n"; + } + + if (Objective() && *Objective()) { + s += "objective: \""; + s += SafeString(Objective()); + s += "\"\n\n"; + } + + if (Situation() && *Situation()) { + s += "sitrep: \""; + s += SafeString(Situation()); + s += "\"\n\n"; + } + + char buffer[256]; + FormatTime(buffer, Start()); + + s += "start: \""; + s += buffer; + s += "\"\n\n"; + s += "degrees: true\n\n"; + + while (++iter) { + MissionElement* elem = iter.value(); + + s += "element: {\n"; + s += " name: \""; + s += SafeString(elem->Name()); + s += "\"\n"; + + if (elem->Path().length()) { + s += " path: \""; + s += SafeString(elem->Path()); + s += "\"\n"; + } + + if (elem->GetDesign()) { + s += " design: \""; + s += SafeString(elem->GetDesign()->name); + s += "\"\n"; + } + + if (elem->GetSkin()) { + s += " skin: \""; + s += SafeString(elem->GetSkin()->Name()); + s += "\"\n"; + } + + if (elem->Squadron().length()) { + s += " squadron: \""; + s += SafeString(elem->Squadron()); + s += "\"\n"; + } + + if (elem->Carrier().length()) { + s += " carrier: \""; + s += SafeString(elem->Carrier()); + s += "\"\n"; + } + + if (elem->Commander().length()) { + s += " commander: \""; + s += SafeString(elem->Commander()); + s += "\"\n"; + } + + s += " mission: \""; + s += elem->RoleName(); + s += "\"\n\n"; + + if (elem->IntelLevel()) { + s += " intel: \""; + s += Intel::NameFromIntel(elem->IntelLevel()); + s += "\"\n"; + } + + sprintf(buffer, " count: %d\n", elem->Count()); + s += buffer; + + if (elem->MaintCount()) { + sprintf(buffer, " maint_count: %d\n", elem->MaintCount()); + s += buffer; + } + + if (elem->DeadCount()) { + sprintf(buffer, " dead_count: %d\n", elem->DeadCount()); + s += buffer; + } + + if (elem->RespawnCount()) { + sprintf(buffer, " respawn_count: %d\n", elem->RespawnCount()); + s += buffer; + } + + if (elem->HoldTime()) { + sprintf(buffer, " hold_time: %d\n", elem->HoldTime()); + s += buffer; + } + + if (elem->ZoneLock()) { + sprintf(buffer, " zone_lock: %d\n", elem->ZoneLock()); + s += buffer; + } + + if (elem->IsAlert()) { + s += " alert: true\n"; + } + + if (!elem->IsSquadron()) { + sprintf(buffer, " command_ai:%d\n", elem->CommandAI()); + s += buffer; + } + + sprintf(buffer, " iff: %d\n", elem->GetIFF()); + s += buffer; + + if (player_elem) { + if (elem->Name() == player_elem) { + if (player_index < 1) + player_index = 1; + + sprintf(buffer, " player: %d\n", player_index); + s += buffer; + } + } + + else { + if (elem->Player()) { + sprintf(buffer, " player: %d\n", elem->Player()); + s += buffer; + } + } + + if (!elem->IsSquadron()) { + if (elem->IsPlayable()) + s += " playable: true\n"; + else + s += " playable: false\n"; + } + + + s += " region: \""; + s += elem->Region(); + s += "\"\n"; + + sprintf(buffer, " loc: (%.0f, %.0f, %.0f)\n", + elem->Location().x, + elem->Location().y, + elem->Location().z); + s += buffer; + + if (elem->Heading() != 0) { + sprintf(buffer, " head: %d\n", (int) (elem->Heading()/DEGREES)); + s += buffer; + } + + if (elem->Loadouts().size()) { + s += "\n"; + + ListIter load_iter = elem->Loadouts(); + while (++load_iter) { + MissionLoad* load = load_iter.value(); + + sprintf(buffer, " loadout: { ship: %d, ", load->GetShip()); + s += buffer; + + if (load->GetName().length()) { + s += "name: \""; + s += SafeString(load->GetName()); + s += "\" }\n"; + } + else { + s += "stations: ("; + + for (int i = 0; i < 16; i++) { + sprintf(buffer, "%d", load->GetStation(i)); + s += buffer; + + if (i < 15) + s += ", "; + } + + s += ") }\n"; + } + } + } + + if (elem->Objectives().size()) { + s += "\n"; + + ListIter obj_iter = elem->Objectives(); + while (++obj_iter) { + Instruction* inst = obj_iter.value(); + + s += " objective: { cmd: "; + s += Instruction::ActionName(inst->Action()); + s += ", tgt: \""; + s += SafeString(inst->TargetName()); + s += "\" }\n"; + } + } + + if (elem->NavList().size()) { + s += "\n"; + + ListIter nav_iter = elem->NavList(); + while (++nav_iter) { + Instruction* inst = nav_iter.value(); + + s += " navpt: { cmd: "; + s += Instruction::ActionName(inst->Action()); + s += ", status: "; + s += Instruction::StatusName(inst->Status()); + + if (inst->TargetName() && *inst->TargetName()) { + s += ", tgt: \""; + s += SafeString(inst->TargetName()); + s += "\""; + } + + sprintf(buffer, ", loc: (%.0f, %.0f, %.0f), speed: %d", + inst->Location().x, + inst->Location().y, + inst->Location().z, + inst->Speed()); + s += buffer; + + if (inst->RegionName() && *inst->RegionName()) { + s += ", rgn: \""; + s += inst->RegionName(); + s += "\""; + } + + if (inst->HoldTime()) { + sprintf(buffer, ", hold: %d", (int) inst->HoldTime()); + s += buffer; + } + + if (inst->Farcast()) { + s += ", farcast: true"; + } + + if (inst->Formation() > Instruction::DIAMOND) { + sprintf(buffer, ", formation: %d", (int) inst->Formation()); + s += buffer; + } + + if (inst->Priority() > Instruction::PRIMARY) { + sprintf(buffer, ", priority: %d", (int) inst->Priority()); + s += buffer; + } + + s += " }\n"; + } + } + + if (elem->Instructions().size()) { + s += "\n"; + + ListIter i_iter = elem->Instructions(); + while (++i_iter) { + s += " instr: \""; + s += SafeString(*i_iter.value()); + s += "\"\n"; + } + } + + if (elem->Ships().size()) { + ListIter s_iter = elem->Ships(); + while (++s_iter) { + MissionShip* ship = s_iter.value(); + + s += "\n ship: {\n"; + + if (ship->Name().length()) { + s += " name: \""; + s += SafeString(ship->Name()); + s += "\"\n"; + } + + if (ship->RegNum().length()) { + s += " regnum: \""; + s += SafeString(ship->RegNum()); + s += "\"\n"; + } + + if (ship->Region().length()) { + s += " region: \""; + s += SafeString(ship->Region()); + s += "\"\n"; + } + + if (fabs(ship->Location().x) < 1e9) { + sprintf(buffer, " loc: (%.0f, %.0f, %.0f),\n", + ship->Location().x, + ship->Location().y, + ship->Location().z); + s += buffer; + } + + if (fabs(ship->Velocity().x) < 1e9) { + sprintf(buffer, " velocity: (%.1f, %.1f, %.1f),\n", + ship->Velocity().x, + ship->Velocity().y, + ship->Velocity().z); + s += buffer; + } + + if (ship->Respawns() > -1) { + sprintf(buffer, " respawns: %d,\n", ship->Respawns()); + s += buffer; + } + + if (ship->Heading() > -1e9) { + sprintf(buffer, " heading: %d,\n", (int) (ship->Heading()/DEGREES)); + s += buffer; + } + + if (ship->Integrity() > -1) { + sprintf(buffer, " integrity: %d,\n", (int) ship->Integrity()); + s += buffer; + } + + if (ship->Decoys() > -1) { + sprintf(buffer, " decoys: %d,\n", ship->Decoys()); + s += buffer; + } + + if (ship->Probes() > -1) { + sprintf(buffer, " probes: %d,\n", ship->Probes()); + s += buffer; + } + + if (ship->Ammo()[0] > -10) { + s += "\n ammo: ("; + + for (int i = 0; i < 16; i++) { + sprintf(buffer, "%d", ship->Ammo()[i]); + s += buffer; + + if (i < 15) + s += ", "; + } + + s += ")\n"; + } + + if (ship->Fuel()[0] > -10) { + s += "\n fuel: ("; + + for (int i = 0; i < 4; i++) { + sprintf(buffer, "%d", ship->Fuel()[i]); + s += buffer; + + if (i < 3) + s += ", "; + } + + s += ")\n"; + } + + s += " }\n"; + } + } + + s += "}\n\n"; + } + + ListIter iter2 = GetEvents(); + while (++iter2) { + MissionEvent* event = iter2.value(); + + s += "event: {\n"; + + s += " id: "; + sprintf(buffer, "%d", event->EventID()); + s += buffer; + s += ",\n time: "; + sprintf(buffer, "%.1f", event->Time()); + s += buffer; + s += ",\n delay: "; + sprintf(buffer, "%.1f", event->Delay()); + s += buffer; + s += ",\n event: "; + s += event->EventName(); + s += "\n"; + + if (event->EventShip().length()) { + s += " event_ship: \""; + s += SafeString(event->EventShip()); + s += "\"\n"; + } + + if (event->EventSource().length()) { + s += " event_source: \""; + s += SafeString(event->EventSource()); + s += "\"\n"; + } + + if (event->EventTarget().length()) { + s += " event_target: \""; + s += SafeString(event->EventTarget()); + s += "\"\n"; + } + + if (event->EventSound().length()) { + s += " event_sound: \""; + s += SafeString(event->EventSound()); + s += "\"\n"; + } + + if (event->EventMessage().length()) { + s += " event_message: \""; + s += SafeString(event->EventMessage()); + s += "\"\n"; + } + + if (event->EventParam()) { + sprintf(buffer, "%d", event->EventParam()); + s += " event_param: "; + s += buffer; + s += "\n"; + } + + if (event->EventChance()) { + sprintf(buffer, "%d", event->EventChance()); + s += " event_chance: "; + s += buffer; + s += "\n"; + } + + s += " trigger: \""; + s += event->TriggerName(); + s += "\"\n"; + + if (event->TriggerShip().length()) { + s += " trigger_ship: \""; + s += SafeString(event->TriggerShip()); + s += "\"\n"; + } + + if (event->TriggerTarget().length()) { + s += " trigger_target: \""; + s += SafeString(event->TriggerTarget()); + s += "\"\n"; + } + + Text param_str = event->TriggerParamStr(); + + if (param_str.length()) { + s += " trigger_param: "; + s += param_str; + s += "\n"; + } + + s += "}\n\n"; + } + + s += "// EOF\n"; + + return s; +} + +// +====================================================================+ + +static int elem_idkey = 1; + +MissionElement::MissionElement() + : id (elem_idkey++), + elem_id(0), design(0), skin(0), count(1), maint_count(0), dead_count(0), + IFF_code(0), player(0), alert(false), playable(false), rogue(false), invulnerable(false), + respawns(0), hold_time(0), zone_lock(0), heading(0), mission_role(Mission::OTHER), + intel(Intel::SECRET), command_ai(1), combat_group(0), combat_unit(0) +{ +} + +MissionElement::~MissionElement() +{ + ships.destroy(); + objectives.destroy(); + instructions.destroy(); + navlist.destroy(); + loadouts.destroy(); +} + +Text +MissionElement::Abbreviation() const +{ + if (design) + return design->abrv; + + return "UNK"; +} + +Text +MissionElement::GetShipName(int index) const +{ + if (index < 0 || index >= ships.size()) { + if (count > 1) { + char sname[256]; + sprintf(sname, "%s %d", (const char*) name, index+1); + return sname; + } + else { + return name; + } + } + + return ships.at(index)->Name(); +} + +Text +MissionElement::GetRegistry(int index) const +{ + if (index < 0 || index >= ships.size()) { + return Text(); + } + + return ships.at(index)->RegNum(); +} + +Text +MissionElement::RoleName() const +{ + return Mission::RoleName(mission_role); +} + +Color +MissionElement::MarkerColor() const +{ + return Ship::IFFColor(IFF_code); +} + +bool +MissionElement::IsStatic() const +{ + int design_type = 0; + if (GetDesign()) + design_type = GetDesign()->type; + + return design_type >= Ship::STATION; +} + +bool +MissionElement::IsGroundUnit() const +{ + int design_type = 0; + if (GetDesign()) + design_type = GetDesign()->type; + + return (design_type & Ship::GROUND_UNITS) ? true : false; +} + +bool +MissionElement::IsStarship() const +{ + int design_type = 0; + if (GetDesign()) + design_type = GetDesign()->type; + + return (design_type & Ship::STARSHIPS) ? true : false; +} + +bool +MissionElement::IsDropship() const +{ + int design_type = 0; + if (GetDesign()) + design_type = GetDesign()->type; + + return (design_type & Ship::DROPSHIPS) ? true : false; +} + +bool +MissionElement::IsCarrier() const +{ + const ShipDesign* design = GetDesign(); + if (design && design->flight_decks.size() > 0) + return true; + + return false; +} + +bool +MissionElement::IsSquadron() const +{ + if (carrier.length() > 0) + return true; + + return false; +} + +// +--------------------------------------------------------------------+ + +Point +MissionElement::Location() const +{ + MissionElement* pThis = (MissionElement*) this; + return pThis->rloc.Location(); +} + +void +MissionElement::SetLocation(const Point& l) +{ + rloc.SetBaseLocation(l); + rloc.SetReferenceLoc(0); + rloc.SetDistance(0); +} + +void +MissionElement::SetRLoc(const RLoc& r) +{ + rloc = r; +} + +// +----------------------------------------------------------------------+ + +void +MissionElement::AddNavPoint(Instruction* pt, Instruction* afterPoint) +{ + if (pt && !navlist.contains(pt)) { + if (afterPoint) { + int index = navlist.index(afterPoint); + + if (index > -1) + navlist.insert(pt, index+1); + else + navlist.append(pt); + } + + else { + navlist.append(pt); + } + } +} + +void +MissionElement::DelNavPoint(Instruction* pt) +{ + if (pt) + delete navlist.remove(pt); +} + +void +MissionElement::ClearFlightPlan() +{ + navlist.destroy(); +} + +// +----------------------------------------------------------------------+ + +int +MissionElement::GetNavIndex(const Instruction* n) +{ + int index = 0; + + if (navlist.size() > 0) { + ListIter navpt = navlist; + while (++navpt) { + index++; + if (navpt.value() == n) + return index; + } + } + + return 0; +} + +// +====================================================================+ + +MissionLoad::MissionLoad(int s, const char* n) + : ship(s) +{ + for (int i = 0; i < 16; i++) + load[i] = -1; // default: no weapon mounted + + if (n) + name = n; +} + +MissionLoad::~MissionLoad() +{ +} + +// +--------------------------------------------------------------------+ + +int +MissionLoad::GetShip() const +{ + return ship; +} + +void +MissionLoad::SetShip(int s) +{ + ship = s; +} + +Text +MissionLoad::GetName() const +{ + return name; +} + +void +MissionLoad::SetName(Text n) +{ + name = n; +} + +int* +MissionLoad::GetStations() +{ + return load; +} + +int +MissionLoad::GetStation(int index) +{ + if (index >= 0 && index < 16) + return load[index]; + + return 0; +} + +void +MissionLoad::SetStation(int index, int selection) +{ + if (index >= 0 && index < 16) + load[index] = selection; +} + + +// +====================================================================+ + +MissionShip::MissionShip() + : loc(-1e9, -1e9, -1e9), respawns(0), heading(0), integrity(100), + decoys(-10), probes(-10), skin(0) +{ + for (int i = 0; i < 16; i++) + ammo[i] = -10; + + for (i = 0; i < 4; i++) + fuel[i] = -10; +} + +void +MissionShip::SetAmmo(const int* a) +{ + if (a) { + for (int i = 0; i < 16; i++) + ammo[i] = a[i]; + } +} + +void +MissionShip::SetFuel(const int* f) +{ + if (f) { + for (int i = 0; i < 4; i++) + fuel[i] = f[i]; + } +} diff --git a/Stars45/Mission.h b/Stars45/Mission.h new file mode 100644 index 0000000..f792951 --- /dev/null +++ b/Stars45/Mission.h @@ -0,0 +1,426 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Mission.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Simulation Universe and Region classes +*/ + +#ifndef Mission_h +#define Mission_h + +#include "Types.h" +#include "Intel.h" +#include "RLoc.h" +#include "Universe.h" +#include "Scene.h" +#include "Skin.h" +#include "Physical.h" +#include "Geometry.h" +#include "List.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class Mission; +class MissionElement; +class MissionLoad; +class MissionEvent; +class MissionShip; + +class CombatGroup; +class CombatUnit; + +class Ship; +class System; +class Element; +class ShipDesign; +class WeaponDesign; +class StarSystem; +class Instruction; + +class Term; +class TermArray; +class TermStruct; + +// +--------------------------------------------------------------------+ + +class Mission +{ +public: + static const char* TYPENAME() { return "Mission"; } + + enum TYPE + { + PATROL, + SWEEP, + INTERCEPT, + AIR_PATROL, + AIR_SWEEP, + AIR_INTERCEPT, + STRIKE, // ground attack + ASSAULT, // starship attack + DEFEND, + ESCORT, + ESCORT_FREIGHT, + ESCORT_SHUTTLE, + ESCORT_STRIKE, + INTEL, + SCOUT, + RECON, + BLOCKADE, + FLEET, + BOMBARDMENT, + FLIGHT_OPS, + TRANSPORT, + CARGO, + TRAINING, + OTHER + }; + + Mission(int id, const char* filename=0, const char* path=0); + virtual ~Mission(); + + int operator == (const Mission& m) const { return id == m.id; } + + virtual void Validate(); + virtual bool Load(const char* filename=0, const char* path=0); + virtual bool Save(); + virtual bool ParseMission(const char* buffer); + virtual void SetPlayer(MissionElement* player_element); + virtual MissionElement* GetPlayer(); + + // accessors/mutators: + int Identity() const { return id; } + const char* FileName() const { return filename; } + const char* Name() const { return name; } + const char* Description() const { return desc; } + const char* Situation() const { return sitrep; } + const char* Objective() const { return objective; } + const char* Subtitles() const; + int Start() const { return start; } + double Stardate() const { return stardate; } + int Type() const { return type; } + const char* TypeName() const { return RoleName(type); } + int Team() const { return team; } + bool IsOK() const { return ok; } + bool IsActive() const { return active; } + bool IsComplete() const { return complete; } + + StarSystem* GetStarSystem() const { return star_system; } + List& GetSystemList() { return system_list; } + const char* GetRegion() const { return region; } + + List& GetElements() { return elements; } + virtual MissionElement* FindElement(const char* name); + virtual void AddElement(MissionElement* elem); + + List& GetEvents() { return events; } + MissionEvent* FindEvent(int event_type) const; + virtual void AddEvent(MissionEvent* event); + + MissionElement* GetTarget() const { return target; } + MissionElement* GetWard() const { return ward; } + + void SetName(const char* n) { name = n; } + void SetDescription(const char* d) { desc = d; } + void SetSituation(const char* sit) { sitrep = sit; } + void SetObjective(const char* obj) { objective = obj; } + void SetStart(int s) { start = s; } + void SetType(int t) { type = t; } + void SetTeam(int iff) { team = iff; } + void SetStarSystem(StarSystem* s); + void SetRegion(const char* rgn) { region = rgn; } + void SetOK(bool a) { ok = a; } + void SetActive(bool a) { active = a; } + void SetComplete(bool c) { complete = c; } + void SetTarget(MissionElement* t) { target = t; } + void SetWard(MissionElement* w) { ward = w; } + + void ClearSystemList(); + + void IncreaseElemPriority(int index); + void DecreaseElemPriority(int index); + void IncreaseEventPriority(int index); + void DecreaseEventPriority(int index); + + static const char* RoleName(int role); + static int TypeFromName(const char* n); + + Text ErrorMessage() const { return errmsg; } + void AddError(Text err); + + Text Serialize(const char* player_elem=0, int player_index=0); + +protected: + MissionElement* ParseElement(TermStruct* val); + MissionEvent* ParseEvent(TermStruct* val); + MissionShip* ParseShip(TermStruct* val, MissionElement* element); + Instruction* ParseInstruction(TermStruct* val, MissionElement* element); + void ParseLoadout(TermStruct* val, MissionElement* element); + RLoc* ParseRLoc(TermStruct* val); + + int id; + char filename[64]; + char path[64]; + Text region; + Text name; + Text desc; + int type; + int team; + int start; + double stardate; + bool ok; + bool active; + bool complete; + bool degrees; + Text objective; + Text sitrep; + Text errmsg; + Text subtitles; + StarSystem* star_system; + List system_list; + + List elements; + List events; + + MissionElement* target; + MissionElement* ward; + MissionElement* current; +}; + +// +--------------------------------------------------------------------+ + +class MissionElement +{ + friend class Mission; + +public: + static const char* TYPENAME() { return "MissionElement"; } + + MissionElement(); + ~MissionElement(); + + int operator == (const MissionElement& r) const { return id == r.id; } + + int Identity() const { return id; } + const Text& Name() const { return name; } + Text Abbreviation() const; + const Text& Carrier() const { return carrier; } + const Text& Commander() const { return commander; } + const Text& Squadron() const { return squadron; } + const Text& Path() const { return path; } + int ElementID() const { return elem_id; } + const ShipDesign* GetDesign() const { return design; } + const Skin* GetSkin() const { return skin; } + int Count() const { return count; } + int MaintCount() const { return maint_count; } + int DeadCount() const { return dead_count; } + int GetIFF() const { return IFF_code; } + int IntelLevel() const { return intel; } + int MissionRole() const { return mission_role; } + int Player() const { return player; } + Text RoleName() const; + Color MarkerColor() const; + bool IsStarship() const; + bool IsDropship() const; + bool IsStatic() const; + bool IsGroundUnit() const; + bool IsSquadron() const; + bool IsCarrier() const; + bool IsAlert() const { return alert; } + bool IsPlayable() const { return playable; } + bool IsRogue() const { return rogue; } + bool IsInvulnerable() const { return invulnerable; } + int RespawnCount() const { return respawns; } + int HoldTime() const { return hold_time; } + int CommandAI() const { return command_ai; } + int ZoneLock() const { return zone_lock; } + + const Text& Region() const { return rgn_name; } + Point Location() const; + RLoc& GetRLoc() { return rloc; } + double Heading() const { return heading; } + + Text GetShipName(int n) const; + Text GetRegistry(int n) const; + + List& Objectives() { return objectives; } + List& Instructions() { return instructions; } + List& NavList() { return navlist; } + List& Loadouts() { return loadouts; } + List& Ships() { return ships; } + + void SetName(const char* n) { name = n; } + void SetCarrier(const char* c) { carrier = c; } + void SetCommander(const char* c) { commander = c; } + void SetSquadron(const char* s) { squadron = s; } + void SetPath(const char* p) { path = p; } + void SetElementID(int id) { elem_id = id; } + void SetDesign(const ShipDesign* d){ design = d; } + void SetSkin(const Skin* s) { skin = s; } + void SetCount(int n) { count = n; } + void SetMaintCount(int n) { maint_count = n; } + void SetDeadCount(int n) { dead_count = n; } + void SetIFF(int iff) { IFF_code = iff; } + void SetIntelLevel(int i) { intel = i; } + void SetMissionRole(int r) { mission_role = r; } + void SetPlayer(int p) { player = p; } + void SetPlayable(bool p) { playable = p; } + void SetRogue(bool r) { rogue = r; } + void SetInvulnerable(bool n) { invulnerable = n; } + void SetAlert(bool a) { alert = a; } + void SetCommandAI(int a) { command_ai = a; } + void SetRegion(const char* rgn) { rgn_name = rgn; } + void SetLocation(const Point& p); + void SetRLoc(const RLoc& r); + void SetHeading(double h) { heading = h; } + void SetRespawnCount(int r) { respawns = r; } + void SetHoldTime(int t) { hold_time = t; } + void SetZoneLock(int z) { zone_lock = z; } + + void AddNavPoint(Instruction* pt, Instruction* afterPoint=0); + void DelNavPoint(Instruction* pt); + void ClearFlightPlan(); + int GetNavIndex(const Instruction* n); + + void AddObjective(Instruction* obj) { objectives.append(obj); } + void AddInstruction(const char* i) { instructions.append(new(__FILE__,__LINE__) Text(i)); } + + CombatGroup* GetCombatGroup() { return combat_group; } + void SetCombatGroup(CombatGroup* g) { combat_group = g; } + CombatUnit* GetCombatUnit() { return combat_unit; } + void SetCombatUnit(CombatUnit* u) { combat_unit = u; } + +protected: + int id; + Text name; + Text carrier; + Text commander; + Text squadron; + Text path; + int elem_id; + const ShipDesign* design; + const Skin* skin; + int count; + int maint_count; + int dead_count; + int IFF_code; + int mission_role; + int intel; + int respawns; + int hold_time; + int zone_lock; + int player; + int command_ai; + bool alert; + bool playable; + bool rogue; + bool invulnerable; + + Text rgn_name; + RLoc rloc; + double heading; + + CombatGroup* combat_group; + CombatUnit* combat_unit; + + List objectives; + List instructions; + List navlist; + List loadouts; + List ships; +}; + +// +--------------------------------------------------------------------+ + +class MissionLoad +{ + friend class Mission; + +public: + static const char* TYPENAME() { return "MissionLoad"; } + + MissionLoad(int ship=-1, const char* name=0); + ~MissionLoad(); + + int GetShip() const; + void SetShip(int ship); + + Text GetName() const; + void SetName(Text name); + + int* GetStations(); + int GetStation(int index); + void SetStation(int index, int selection); + +protected: + int ship; + Text name; + int load[16]; +}; + +// +--------------------------------------------------------------------+ + +class MissionShip +{ + friend class Mission; + +public: + static const char* TYPENAME() { return "MissionShip"; } + + MissionShip(); + ~MissionShip() { } + + const Text& Name() const { return name; } + const Text& RegNum() const { return regnum; } + const Text& Region() const { return region; } + const Skin* GetSkin() const { return skin; } + const Point& Location() const { return loc; } + const Point& Velocity() const { return velocity; } + int Respawns() const { return respawns; } + double Heading() const { return heading; } + double Integrity() const { return integrity; } + int Decoys() const { return decoys; } + int Probes() const { return probes; } + const int* Ammo() const { return ammo; } + const int* Fuel() const { return fuel; } + + void SetName(const char* n) { name = n; } + void SetRegNum(const char* n) { regnum = n; } + void SetRegion(const char* n) { region = n; } + void SetSkin(const Skin* s) { skin = s; } + void SetLocation(const Point& p) { loc = p; } + void SetVelocity(const Point& p) { velocity = p; } + void SetRespawns(int r) { respawns = r; } + void SetHeading(double h) { heading = h; } + void SetIntegrity(double n) { integrity = n; } + void SetDecoys(int d) { decoys = d; } + void SetProbes(int p) { probes = p; } + void SetAmmo(const int* a); + void SetFuel(const int* f); + +protected: + Text name; + Text regnum; + Text region; + const Skin* skin; + Point loc; + Point velocity; + int respawns; + double heading; + double integrity; + int decoys; + int probes; + int ammo[16]; + int fuel[4]; +}; + +#endif Mission_h + diff --git a/Stars45/MissionEvent.cpp b/Stars45/MissionEvent.cpp new file mode 100644 index 0000000..3eebf30 --- /dev/null +++ b/Stars45/MissionEvent.cpp @@ -0,0 +1,874 @@ +/* Project Starshatter 5.0 + Destroyer Studios LLC + Copyright © 1997-2007. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: MissionEvent.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Events for mission scripting +*/ + +#include "MemDebug.h" +#include "MissionEvent.h" +#include "Mission.h" +#include "StarSystem.h" +#include "Galaxy.h" +#include "Starshatter.h" +#include "StarServer.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "Element.h" +#include "DisplayView.h" +#include "HUDView.h" +#include "Instruction.h" +#include "QuantumDrive.h" +#include "Sim.h" +#include "AudioConfig.h" +#include "CameraDirector.h" +#include "RadioMessage.h" +#include "RadioTraffic.h" +#include "Weapon.h" +#include "WeaponGroup.h" +#include "Player.h" +#include "Campaign.h" +#include "CombatGroup.h" + +#include "NetData.h" +#include "NetUtil.h" + +#include "Game.h" +#include "DataLoader.h" +#include "Font.h" +#include "FontMgr.h" +#include "Sound.h" +#include "ParseUtil.h" +#include "FormatUtil.h" +#include "Random.h" + +const char* FormatGameTime(); + +// +--------------------------------------------------------------------+ + +MissionEvent::MissionEvent() + : id(0), status(PENDING), time(0), delay(0), event(0), event_nparams(0), + event_chance(100), trigger(0), trigger_nparams(0), sound(0) +{ + ZeroMemory(event_param, sizeof(event_param)); + ZeroMemory(trigger_param, sizeof(trigger_param)); +} + +MissionEvent::~MissionEvent() +{ + if (sound) { + sound->Stop(); + sound->Release(); + } +} + +// +--------------------------------------------------------------------+ + +void +MissionEvent::ExecFrame(double seconds) +{ + Sim* sim = Sim::GetSim(); + + if (!sim) { + status = PENDING; + return; + } + + if (status == PENDING) + CheckTrigger(); + + if (status == ACTIVE) { + if (delay > 0) + delay -= seconds; + + else + Execute(); + } +} + +// +--------------------------------------------------------------------+ + +void +MissionEvent::Activate() +{ + if (status == PENDING) { + if (event_chance > 0 && event_chance < 100) { + if (Random(0, 100) < event_chance) + status = ACTIVE; + else + status = SKIPPED; + } + + else { + status = ACTIVE; + } + + if (status == SKIPPED) { + Sim::GetSim()->ProcessEventTrigger(TRIGGER_SKIPPED, id); + } + } +} + +void +MissionEvent::Skip() +{ + if (status == PENDING) { + status = SKIPPED; + } +} + +// +--------------------------------------------------------------------+ + +bool +MissionEvent::CheckTrigger() +{ + Sim* sim = Sim::GetSim(); + + if (time > 0 && time > sim->MissionClock()) + return false; + + switch (trigger) { + case TRIGGER_TIME: { + if (time <= sim->MissionClock()) + Activate(); + } + break; + + case TRIGGER_DAMAGE: { + Ship* ship = sim->FindShip(trigger_ship); + if (ship) { + double damage = 100.0 * (ship->Design()->integrity - ship->Integrity()) / + (ship->Design()->integrity); + + if (damage >= trigger_param[0]) + Activate(); + } + } + break; + + case TRIGGER_DETECT: { + Ship* ship = sim->FindShip(trigger_ship); + Ship* tgt = sim->FindShip(trigger_target); + + if (ship && tgt) { + if (ship->FindContact(tgt)) + Activate(); + } + else { + Skip(); + } + } + break; + + case TRIGGER_RANGE: { + Ship* ship = sim->FindShip(trigger_ship); + Ship* tgt = sim->FindShip(trigger_target); + + if (ship && tgt) { + double range = (ship->Location() - tgt->Location()).length(); + double min_range = 0; + double max_range = 1e12; + + if (trigger_param[0] > 0) + min_range = trigger_param[0]; + else + max_range = -trigger_param[0]; + + if (range < min_range || range > max_range) + Activate(); + } + else { + Skip(); + } + } + break; + + case TRIGGER_SHIPS_LEFT: { + int alive = 0; + int count = 0; + int iff = -1; + int nparams = NumTriggerParams(); + + if (nparams > 0) count = TriggerParam(0); + if (nparams > 1) iff = TriggerParam(1); + + ListIter iter = sim->GetRegions(); + while (++iter) { + SimRegion* rgn = iter.value(); + + ListIter s_iter = rgn->Ships(); + while (++s_iter) { + Ship* ship = s_iter.value(); + + if (ship->Type() >= Ship::STATION) + continue; + + if (ship->Life() == 0 && ship->RespawnCount() < 1) + continue; + + if (iff < 0 || ship->GetIFF() == iff) + alive++; + } + } + + if (alive <= count) + Activate(); + } + break; + + case TRIGGER_EVENT_ALL: { + bool all = true; + int nparams = NumTriggerParams(); + for (int i = 0; all && i < nparams; i++) { + int trigger_id = TriggerParam(i); + + ListIter iter = sim->GetEvents(); + while (++iter) { + MissionEvent* e = iter.value(); + if (e->EventID() == trigger_id) { + if (e->Status() != COMPLETE) + all = false; + break; + } + + else if (e->EventID() == -trigger_id) { + if (e->Status() == COMPLETE) + all = false; + break; + } + } + } + + if (all) + Activate(); + } + break; + + case TRIGGER_EVENT_ANY: { + bool any = false; + int nparams = NumTriggerParams(); + for (int i = 0; !any && i < nparams; i++) { + int trigger_id = TriggerParam(i); + + ListIter iter = sim->GetEvents(); + while (++iter) { + MissionEvent* e = iter.value(); + if (e->EventID() == trigger_id) { + if (e->Status() == COMPLETE) + any = true; + break; + } + } + } + + if (any) + Activate(); + } + break; + } + + return status == ACTIVE; +} + +// +--------------------------------------------------------------------+ + +void +MissionEvent::Execute(bool silent) +{ + Starshatter* stars = Starshatter::GetInstance(); + HUDView* hud = HUDView::GetInstance(); + Sim* sim = Sim::GetSim(); + Ship* player = sim->GetPlayerShip(); + Ship* ship = 0; + Ship* src = 0; + Ship* tgt = 0; + Element* elem = 0; + int pan = 0; + bool end_mission = false; + + if (event_ship.length()) + ship = sim->FindShip(event_ship); + else + ship = player; + + if (event_source.length()) + src = sim->FindShip(event_source); + + if (event_target.length()) + tgt = sim->FindShip(event_target); + + if (ship) + elem = ship->GetElement(); + + else if (event_ship.length()) { + elem = sim->FindElement(event_ship); + + if (elem) + ship = elem->GetShip(1); + } + + // expire the delay, if any remains + delay = 0; + + // fire the event action + switch (event) { + case MESSAGE: + if (event_message.length() > 0) { + if (ship) { + RadioMessage* msg = new(__FILE__,__LINE__) RadioMessage(ship, src, event_param[0]); + msg->SetInfo(event_message); + msg->SetChannel(ship->GetIFF()); + if (tgt) + msg->AddTarget(tgt); + RadioTraffic::Transmit(msg); + } + + else if (elem) { + RadioMessage* msg = new(__FILE__,__LINE__) RadioMessage(elem, src, event_param[0]); + msg->SetInfo(event_message); + msg->SetChannel(elem->GetIFF()); + if (tgt) + msg->AddTarget(tgt); + RadioTraffic::Transmit(msg); + } + } + + if (event_sound.length() > 0) { + pan = event_param[0]; + } + break; + + case OBJECTIVE: + if (elem) { + if (event_param[0]) { + elem->ClearInstructions(); + elem->ClearObjectives(); + } + + Instruction* obj = new(__FILE__,__LINE__) Instruction(event_param[0], 0); + obj->SetTarget(event_target); + elem->AddObjective(obj); + + if (elem->Contains(player)) { + HUDView* hud = HUDView::GetInstance(); + + if (hud) + hud->ShowHUDInst(); + } + } + break; + + case INSTRUCTION: + if (elem) { + if (event_param[0]) + elem->ClearInstructions(); + + elem->AddInstruction(event_message); + + if (elem->Contains(player) && event_message.length() > 0) { + HUDView* hud = HUDView::GetInstance(); + + if (hud) + hud->ShowHUDInst(); + } + } + break; + + case IFF: + if (elem) { + elem->SetIFF(event_param[0]); + } + + else if (ship) { + ship->SetIFF(event_param[0]); + } + break; + + case DAMAGE: + if (ship) { + ship->InflictDamage(event_param[0]); + + if (ship->Integrity() < 1) { + NetUtil::SendObjKill(ship, 0, NetObjKill::KILL_MISC); + ship->DeathSpiral(); + Print(" %s Killed By Scripted Event %d (%s)\n", (const char*) ship->Name(), id, FormatGameTime()); + } + } + else { + Print(" EVENT %d: Could not apply damage to ship '%s' (not found).\n", id, (const char*) event_ship); + } + break; + + case JUMP: + if (ship) { + SimRegion* rgn = sim->FindRegion(event_target); + + if (rgn && ship->GetRegion() != rgn) { + if (rgn->IsOrbital()) { + QuantumDrive* quantum_drive = ship->GetQuantumDrive(); + if (quantum_drive) { + quantum_drive->SetDestination(rgn, Point(0,0,0)); + quantum_drive->Engage(true); // request immediate jump + } + + else if (ship->IsAirborne()) { + ship->MakeOrbit(); + } + } + + else { + ship->DropOrbit(); + } + } + + } + break; + + case HOLD: + if (elem) + elem->SetHoldTime(event_param[0]); + break; + + case SKIP: { + for (int i = 0; i < event_nparams; i++) { + int skip_id = event_param[i]; + + ListIter iter = sim->GetEvents(); + while (++iter) { + MissionEvent* e = iter.value(); + if (e->EventID() == skip_id) { + if (e->status != COMPLETE) + e->status = SKIPPED; + } + } + } + } + break; + + case END_MISSION: + Print(" END MISSION By Scripted Event %d (%s)\n", id, FormatGameTime()); + end_mission = true; + break; + + // + // NOTE: CUTSCENE EVENTS DO NOT APPLY IN MULTIPLAYER + // + case BEGIN_SCENE: + Print(" ------------------------------------\n"); + Print(" Begin Cutscene '%s'\n", event_message.data()); + stars->BeginCutscene(); + break; + + case END_SCENE: + Print(" End Cutscene '%s'\n", event_message.data()); + Print(" ------------------------------------\n"); + stars->EndCutscene(); + break; + + case CAMERA: + if (stars->InCutscene()) { + CameraDirector* cam_dir = CameraDirector::GetInstance(); + + if (!cam_dir->GetShip()) + cam_dir->SetShip(player); + + switch (event_param[0]) { + case 1: + if (cam_dir->GetMode() != CameraDirector::MODE_COCKPIT) + cam_dir->SetMode(CameraDirector::MODE_COCKPIT, event_rect.x); + break; + + case 2: + if (cam_dir->GetMode() != CameraDirector::MODE_CHASE) + cam_dir->SetMode(CameraDirector::MODE_CHASE, event_rect.x); + break; + + case 3: + if (cam_dir->GetMode() != CameraDirector::MODE_ORBIT) + cam_dir->SetMode(CameraDirector::MODE_ORBIT, event_rect.x); + break; + + case 4: + if (cam_dir->GetMode() != CameraDirector::MODE_TARGET) + cam_dir->SetMode(CameraDirector::MODE_TARGET, event_rect.x); + break; + } + + if (event_target.length()) { + ::Print("Mission Event %d: setting camera target to %s\n", id, (const char*) event_target); + Ship* s_tgt = 0; + + if (event_target.indexOf("body:") < 0) + s_tgt = sim->FindShip(event_target); + + if (s_tgt) { + ::Print(" found ship %s\n", s_tgt->Name()); + cam_dir->SetViewOrbital(0); + + if (cam_dir->GetViewObject() != s_tgt) { + + if (event_param[0] == 6) { + s_tgt->DropCam(event_param[1], event_param[2]); + cam_dir->SetShip(s_tgt); + cam_dir->SetMode(CameraDirector::MODE_DROP, 0); + } + else { + Ship* cam_ship = cam_dir->GetShip(); + + if (cam_ship && cam_ship->IsDropCam()) { + cam_ship->CompleteTransition(); + } + + if (cam_dir->GetShip() != sim->GetPlayerShip()) + cam_dir->SetShip(sim->GetPlayerShip()); + cam_dir->SetViewObject(s_tgt, true); // immediate, no transition + } + } + } + + else { + const char* body_name = event_target.data(); + + if (!strncmp(body_name, "body:", 5)) + body_name += 5; + + Orbital* orb = sim->FindOrbitalBody(body_name); + + if (orb) { + ::Print(" found body %s\n", orb->Name()); + cam_dir->SetViewOrbital(orb); + } + } + } + + if (event_param[0] == 3) { + cam_dir->SetOrbitPoint(event_point.x, event_point.y, event_point.z); + } + + else if (event_param[0] == 5) { + cam_dir->SetOrbitRates(event_point.x, event_point.y, event_point.z); + } + } + break; + + case VOLUME: + if (stars->InCutscene()) { + AudioConfig* audio_cfg = AudioConfig::GetInstance(); + + audio_cfg->SetEfxVolume(event_param[0]); + audio_cfg->SetWrnVolume(event_param[0]); + } + break; + + case DISPLAY: + if (stars->InCutscene()) { + DisplayView* disp_view = DisplayView::GetInstance(); + + if (disp_view) { + Color color; + color.Set(event_param[0]); + + if (event_message.length() && event_source.length()) { + + if (event_message.contains('$')) { + Campaign* campaign = Campaign::GetCampaign(); + Player* user = Player::GetCurrentPlayer(); + CombatGroup* group = campaign->GetPlayerGroup(); + + if (user) { + event_message = FormatTextReplace(event_message, "$NAME", user->Name().data()); + event_message = FormatTextReplace(event_message, "$RANK", Player::RankName(user->Rank())); + } + + if (group) { + event_message = FormatTextReplace(event_message, "$GROUP", group->GetDescription()); + } + + if (event_message.contains("$TIME")) { + char timestr[32]; + FormatDayTime(timestr, campaign->GetTime(), true); + event_message = FormatTextReplace(event_message, "$TIME", timestr); + } + } + + disp_view->AddText( event_message, + FontMgr::Find(event_source), + color, + event_rect, + event_point.y, + event_point.x, + event_point.z); + + } + + else if (event_target.length()) { + DataLoader* loader = DataLoader::GetLoader(); + + if (loader) { + loader->SetDataPath(0); + loader->LoadBitmap(event_target, image, 0, true); + } + + if (image.Width() && image.Height()) + disp_view->AddImage( &image, + color, + Video::BLEND_ALPHA, + event_rect, + event_point.y, + event_point.x, + event_point.z); + } + } + } + break; + + case FIRE_WEAPON: + if (ship) { + // fire single weapon: + if (event_param[0] >= 0) { + ship->FireWeapon(event_param[0]); + } + + // fire all weapons: + else { + ListIter g_iter = ship->Weapons(); + while (++g_iter) { + ListIter w_iter = g_iter->GetWeapons(); + while (++w_iter) { + Weapon* w = w_iter.value(); + w->Fire(); + } + } + } + } + break; + + default: + break; + } + + sim->ProcessEventTrigger(TRIGGER_EVENT, id); + + if (!silent && !sound && event_sound.length()) { + DataLoader* loader = DataLoader::GetLoader(); + bool use_fs = loader->IsFileSystemEnabled(); + DWORD flags = pan ? Sound::LOCKED|Sound::LOCALIZED : + Sound::LOCKED|Sound::AMBIENT; + + loader->UseFileSystem(true); + loader->SetDataPath("Sounds/"); + loader->LoadSound(event_sound, sound, flags); + loader->SetDataPath(0); + + if (!sound) { + loader->SetDataPath("Mods/Sounds/"); + loader->LoadSound(event_sound, sound, flags); + loader->SetDataPath(0); + } + + if (!sound) { + loader->LoadSound(event_sound, sound, flags); + } + + loader->UseFileSystem(use_fs); + + // fire and forget: + if (sound) { + if (sound->GetFlags() & Sound::STREAMED) { + sound->SetFlags(flags | sound->GetFlags()); + sound->SetVolume(AudioConfig::VoxVolume()); + sound->Play(); + } + else { + sound->SetFlags(flags); + sound->SetVolume(AudioConfig::VoxVolume()); + sound->SetPan(pan); + sound->SetFilename(event_sound); + sound->AddToSoundCard(); + sound->Play(); + } + } + } + + status = COMPLETE; + + if (end_mission) { + StarServer* server = StarServer::GetInstance(); + + if (stars) { + stars->EndMission(); + } + + else if (server) { + // end mission event uses event_target member + // to forward server to next mission in the chain: + if (event_target.length()) + server->SetNextMission(event_target); + + server->SetGameMode(StarServer::MENU_MODE); + } + } +} + +// +--------------------------------------------------------------------+ + +Text +MissionEvent::TriggerParamStr() const +{ + + Text result; + char buffer[8]; + + if (trigger_param[0] == 0) { + // nothing + } + + else if (trigger_param[1] == 0) { + sprintf(buffer, "%d", trigger_param[0]); + result = buffer; + } + + else { + result = "("; + + for (int i = 0; i < 8; i++) { + if (trigger_param[i] == 0) + break; + + if (i < 7 && trigger_param[i+1] != 0) + sprintf(buffer, "%d, ", trigger_param[i]); + else + sprintf(buffer, "%d", trigger_param[i]); + + result += buffer; + } + + result += ")"; + } + + return result; +} + +// +--------------------------------------------------------------------+ + +int +MissionEvent::EventParam(int index) const +{ + if (index >= 0 && index < NumEventParams()) + return event_param[index]; + + return 0; +} + +int +MissionEvent::NumEventParams() const +{ + return event_nparams; +} + +// +--------------------------------------------------------------------+ + +int +MissionEvent::TriggerParam(int index) const +{ + if (index >= 0 && index < NumTriggerParams()) + return trigger_param[index]; + + return 0; +} + +int +MissionEvent::NumTriggerParams() const +{ + return trigger_nparams; +} + +// +--------------------------------------------------------------------+ + +static const char* event_names[] = { + "Message", + "Objective", + "Instruction", + "IFF", + "Damage", + "Jump", + "Hold", + "Skip", + "Exit", + + "BeginScene", + "Camera", + "Volume", + "Display", + "Fire", + "EndScene" +}; + +static const char* trigger_names[] = { + "Time", + "Damage", + "Destroyed", + "Jump", + "Launch", + "Dock", + "Navpoint", + "Event", + "Skipped", + "Target", + "Ships Left", + "Detect", + "Range", + "Event (ALL)", + "Event (ANY)" +}; + +const char* +MissionEvent::EventName() const +{ + return event_names[event]; +} + +const char* +MissionEvent::EventName(int n) +{ + return event_names[n]; +} + +int +MissionEvent::EventForName(const char* n) +{ + for (int i = 0; i < NUM_EVENTS; i++) + if (!stricmp(n, event_names[i])) + return i; + + return 0; +} + +const char* +MissionEvent::TriggerName() const +{ + return trigger_names[trigger]; +} + +const char* +MissionEvent::TriggerName(int n) +{ + return trigger_names[n]; +} + +int +MissionEvent::TriggerForName(const char* n) +{ + for (int i = 0; i < NUM_TRIGGERS; i++) + if (!stricmp(n, trigger_names[i])) + return i; + + return 0; +} \ No newline at end of file diff --git a/Stars45/MissionEvent.h b/Stars45/MissionEvent.h new file mode 100644 index 0000000..c4025d1 --- /dev/null +++ b/Stars45/MissionEvent.h @@ -0,0 +1,167 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: MissionEvent.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Events for mission scripting +*/ + +#ifndef MissionEvent_h +#define MissionEvent_h + +#include "Types.h" +#include "List.h" +#include "Text.h" +#include "Geometry.h" +#include "Bitmap.h" + +// +--------------------------------------------------------------------+ + +class Mission; +class MissionElement; +class MissionLoad; +class MissionEvent; + +class Ship; +class System; +class Element; +class ShipDesign; +class WeaponDesign; +class StarSystem; +class Instruction; +class Sound; + +// +--------------------------------------------------------------------+ + +class MissionEvent +{ + friend class Mission; + friend class MissionTemplate; + friend class MsnEditDlg; + friend class MsnEventDlg; + +public: + static const char* TYPENAME() { return "MissionEvent"; } + + enum EVENT_TYPE { + MESSAGE, + OBJECTIVE, + INSTRUCTION, + IFF, + DAMAGE, + JUMP, + HOLD, + SKIP, + END_MISSION, + + BEGIN_SCENE, + CAMERA, + VOLUME, + DISPLAY, + FIRE_WEAPON, + END_SCENE, + + NUM_EVENTS + }; + + enum EVENT_STATUS { + PENDING, ACTIVE, COMPLETE, SKIPPED + }; + + enum EVENT_TRIGGER { + TRIGGER_TIME, TRIGGER_DAMAGE, TRIGGER_DESTROYED, + TRIGGER_JUMP, TRIGGER_LAUNCH, TRIGGER_DOCK, + TRIGGER_NAVPT, TRIGGER_EVENT, TRIGGER_SKIPPED, + TRIGGER_TARGET, TRIGGER_SHIPS_LEFT, TRIGGER_DETECT, + TRIGGER_RANGE, TRIGGER_EVENT_ALL, TRIGGER_EVENT_ANY, + NUM_TRIGGERS + }; + + MissionEvent(); + ~MissionEvent(); + + // operations: + void ExecFrame(double seconds); + void Activate(); + + virtual bool CheckTrigger(); + virtual void Execute(bool silent=false); + virtual void Skip(); + + // accessors: + int EventID() const { return id; } + int Status() const { return status; } + bool IsPending() const { return status == PENDING; } + bool IsActive() const { return status == ACTIVE; } + bool IsComplete() const { return status == COMPLETE; } + bool IsSkipped() const { return status == SKIPPED; } + + double Time() const { return time; } + double Delay() const { return delay; } + + int Event() const { return event; } + const char* EventName() const; + Text EventShip() const { return event_ship; } + Text EventSource() const { return event_source; } + Text EventTarget() const { return event_target; } + Text EventMessage() const { return event_message; } + Text EventSound() const { return event_sound; } + + int EventParam(int index=0) const; + int NumEventParams() const; + + int EventChance() const { return event_chance; } + Point EventPoint() const { return event_point; } + Rect EventRect() const { return event_rect; } + + int Trigger() const { return trigger; } + const char* TriggerName() const; + Text TriggerShip() const { return trigger_ship; } + Text TriggerTarget() const { return trigger_target; } + + Text TriggerParamStr() const; + int TriggerParam(int index=0) const; + int NumTriggerParams() const; + + static const char* EventName(int n); + static int EventForName(const char* n); + static const char* TriggerName(int n); + static int TriggerForName(const char* n); + +protected: + int id; + int status; + double time; + double delay; + + int event; + Text event_ship; + Text event_source; + Text event_target; + Text event_message; + Text event_sound; + int event_param[10]; + int event_nparams; + int event_chance; + Vec3 event_point; + Rect event_rect; + + int trigger; + Text trigger_ship; + Text trigger_target; + int trigger_param[10]; + int trigger_nparams; + + Bitmap image; + Sound* sound; +}; + + +#endif MissionEvent_h + diff --git a/Stars45/MissionTemplate.cpp b/Stars45/MissionTemplate.cpp new file mode 100644 index 0000000..dbb6ca9 --- /dev/null +++ b/Stars45/MissionTemplate.cpp @@ -0,0 +1,846 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Mission.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Mission Template classes +*/ + +#include "MemDebug.h" +#include "MissionTemplate.h" +#include "MissionEvent.h" +#include "StarSystem.h" +#include "Galaxy.h" +#include "Callsign.h" +#include "Campaign.h" +#include "Combatant.h" +#include "CombatGroup.h" +#include "CombatUnit.h" +#include "Starshatter.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "Element.h" +#include "Instruction.h" +#include "Random.h" + +#include "Game.h" +#include "DataLoader.h" +#include "ParseUtil.h" + +// +--------------------------------------------------------------------+ + +MissionTemplate::MissionTemplate(int identity, const char* fname, const char* pname) + : Mission(identity, fname, pname) +{ +} + +MissionTemplate::~MissionTemplate() +{ + callsigns.destroy(); + aliases.destroy(); +} + +// +--------------------------------------------------------------------+ + +void +MissionTemplate::AddElement(MissionElement* elem) +{ + if (elem) { + elements.append(elem); + aliases.append(new(__FILE__,__LINE__) MissionAlias(elem->Name(), elem)); + } +} + +bool +MissionTemplate::MapElement(MissionElement* elem) +{ + bool result = false; + + if (elem && !elem->GetCombatUnit()) { + if (elem->IsDropship()) { + Text callsign = MapCallsign(elem->Name(), elem->GetIFF()); + + if (callsign.length()) + elem->SetName(callsign); + } + + ListIter obj = elem->Objectives(); + while (++obj) { + Instruction* i = obj.value(); + if (strlen(i->TargetName())) { + // find a callsign, only if one already exists: + Text callsign = MapCallsign(i->TargetName(), -1); + if (callsign.length()) + i->SetTarget(callsign.data()); + } + } + + ListIter nav = elem->NavList(); + while (++nav) { + Instruction* i = nav.value(); + if (strlen(i->TargetName())) { + // find a callsign, only if one already exists: + Text callsign = MapCallsign(i->TargetName(), -1); + if (callsign.length()) + i->SetTarget(callsign.data()); + } + } + + CombatGroup* g = FindCombatGroup(elem->GetIFF(), elem->GetDesign()); + + if (g) { + CombatUnit* u = g->GetNextUnit(); + + if (u) { + elem->SetCombatGroup(g); + elem->SetCombatUnit(u); + } + } + + if (elem->GetCombatUnit()) { + MissionElement* cmdr = FindElement(elem->Commander()); + if (cmdr) + elem->SetCommander(cmdr->Name()); + + MissionElement* sqdr = FindElement(elem->Squadron()); + if (sqdr) + elem->SetSquadron(sqdr->Name()); + + if (!elem->IsDropship()) { + aliases.append(new(__FILE__,__LINE__) MissionAlias(elem->Name(), elem)); + elem->SetName(elem->GetCombatUnit()->Name()); + } + + result = true; + } + } + + return result; +} + +Text +MissionTemplate::MapShip(Text name) +{ + Text result = name; + int len = name.length(); + + if (len) { + MissionElement* elem = 0; + + // single ship from an element (e.g. "Alpha 1")? + if (isdigit(name[len-1]) && isspace(name[len-2])) { + Text elem_name = name.substring(0, len-2); + + elem = FindElement(elem_name); + if (elem) + result = elem->Name() + name.substring(len-2, 2); + } + + // full element name + // (also used to try again if single-ship search fails) + if (!elem) { + elem = FindElement(name); + + if (elem) + result = elem->Name(); + } + } + + return result; +} + +// +--------------------------------------------------------------------+ + +bool +MissionTemplate::MapEvent(MissionEvent* event) +{ + bool result = false; + + if (event) { + event->event_ship = MapShip(event->event_ship); + event->event_source = MapShip(event->event_source); + event->event_target = MapShip(event->event_target); + event->trigger_ship = MapShip(event->trigger_ship); + event->trigger_target = MapShip(event->trigger_target); + + result = true; + } + + return result; +} + +// +--------------------------------------------------------------------+ + +Text +MissionTemplate::MapCallsign(const char* name, int iff) +{ + for (int i = 0; i < callsigns.size(); i++) { + if (callsigns[i]->Name() == name) + return callsigns[i]->Callsign(); + } + + if (iff >= 0) { + const char* callsign = Callsign::GetCallsign(iff); + MissionCallsign* mc = new(__FILE__,__LINE__) MissionCallsign(callsign, name); + callsigns.append(mc); + + return mc->Callsign(); + } + + return name; +} + +// +--------------------------------------------------------------------+ + +static void SelectCombatGroups(CombatGroup* g, const ShipDesign* d, List& list) +{ + if (g->IntelLevel() <= Intel::RESERVE) + return; + + if (g->GetUnits().size() > 0) { + for (int i = 0; i < g->GetUnits().size(); i++) { + CombatUnit* u = g->GetUnits().at(i); + if (u->GetDesign() == d && u->Count() - u->DeadCount() > 0) { + list.append(g); + } + } + } + + ListIter subgroup = g->GetComponents(); + while (++subgroup) + SelectCombatGroups(subgroup.value(), d, list); +} + +CombatGroup* +MissionTemplate::FindCombatGroup(int iff, const ShipDesign* d) +{ + CombatGroup* result = 0; + Campaign* campaign = Campaign::GetCampaign(); + List group_list; + static int combat_group_index = 0; + + if (campaign) { + ListIter combatant = campaign->GetCombatants(); + while (++combatant && !result) { + if (combatant->GetIFF() == iff) { + ::SelectCombatGroups(combatant->GetForce(), d, group_list); + } + } + + if (group_list.size() > 0) + result = group_list[combat_group_index++ % group_list.size()]; + } + + return result; +} + +// +--------------------------------------------------------------------+ + +MissionElement* +MissionTemplate::FindElement(const char* n) +{ + Text name = n; + + ListIter c_iter = callsigns; + while (++c_iter) { + MissionCallsign* c = c_iter.value(); + if (c->Name() == name) { + name = c->Callsign(); + break; + } + } + + ListIter a_iter = aliases; + while (++a_iter) { + MissionAlias* a = a_iter.value(); + if (a->Name() == name) + return a->Element(); + } + + ListIter e_iter = elements; + while (++e_iter) { + MissionElement* elem = e_iter.value(); + if (elem->Name() == name) + return elem; + } + + return 0; +} + +// +--------------------------------------------------------------------+ + +bool +MissionTemplate::Load(const char* fname, const char* pname) +{ + ok = false; + + if (fname) + strcpy(filename, fname); + + if (pname) + strcpy(path, pname); + + if (!filename[0]) { + Print("\nCan't Load Mission Template, script unspecified.\n"); + return ok; + } + + Print("\nLoad Mission Template: '%s'\n", filename); + + int max_ships = (int) 1e6; + + DataLoader* loader = DataLoader::GetLoader(); + bool old_fs = loader->IsFileSystemEnabled(); + BYTE* block = 0; + + loader->UseFileSystem(true); + loader->SetDataPath(path); + loader->LoadBuffer(filename, block, true); + loader->SetDataPath(0); + loader->UseFileSystem(old_fs); + + Parser parser(new(__FILE__,__LINE__) BlockReader((const char*) block)); + + Term* term = parser.ParseTerm(); + + if (!term) { + Print("ERROR: could not parse '%s'\n", filename); + return ok; + } + else { + TermText* file_type = term->isText(); + if (!file_type || file_type->value() != "MISSION_TEMPLATE") { + Print("ERROR: invalid MISSION TEMPLATE file '%s'\n", filename); + term->print(10); + return ok; + } + } + + ok = true; + + char target_name[256]; + char ward_name[256]; + + target_name[0] = 0; + ward_name[0] = 0; + + do { + delete term; term = 0; + term = parser.ParseTerm(); + + if (term) { + TermDef* def = term->isDef(); + if (def) { + Text defname = def->name()->value(); + + if (defname == "name") + GetDefText(name, def, filename); + + else if (defname == "type") { + char typestr[64]; + GetDefText(typestr, def, filename); + type = TypeFromName(typestr); + } + + else if (defname == "system") { + char sysname[64]; + GetDefText(sysname, def, filename); + + Campaign* campaign = Campaign::GetCampaign(); + + if (campaign) { + Galaxy* galaxy = Galaxy::GetInstance(); + + if (galaxy) { + star_system = galaxy->GetSystem(sysname); + } + } + } + + else if (defname == "degrees") + GetDefBool(degrees, def, filename); + + else if (defname == "region") + GetDefText(region, def, filename); + + else if (defname == "objective") + GetDefText(objective, def, filename); + + else if (defname == "sitrep") + GetDefText(sitrep, def, filename); + + else if (defname == "start") + GetDefTime(start, def, filename); + + else if (defname == "team") + GetDefNumber(team, def, filename); + + else if (defname == "target") + GetDefText(target_name, def, filename); + + else if (defname == "ward") + GetDefText(ward_name, def, filename); + + else if ((defname == "alias")) { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: alias struct missing in '%s'\n", filename); + ok = false; + } + else { + TermStruct* val = def->term()->isStruct(); + ParseAlias(val); + } + } + + else if ((defname == "callsign")) { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: callsign struct missing in '%s'\n", filename); + ok = false; + } + else { + TermStruct* val = def->term()->isStruct(); + ParseCallsign(val); + } + } + + else if (defname == "optional") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: optional group struct missing in '%s'\n", filename); + ok = false; + } + else { + TermStruct* val = def->term()->isStruct(); + ParseOptional(val); + } + } + + else if (defname == "element") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: element struct missing in '%s'\n", filename); + ok = false; + } + else { + TermStruct* val = def->term()->isStruct(); + MissionElement* elem = ParseElement(val); + if (MapElement(elem)) { + AddElement(elem); + } + else { + Print("WARNING: failed to map element %s '%s' in '%s'\n", + elem->GetDesign() ? elem->GetDesign()->name : "NO DSN", + elem->Name().data(), + filename); + val->print(); + Print("\n"); + delete elem; + ok = false; + } + } + } + + else if (defname == "event") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: event struct missing in '%s'\n", filename); + ok = false; + } + else { + TermStruct* val = def->term()->isStruct(); + MissionEvent* event = ParseEvent(val); + + if (MapEvent(event)) + AddEvent(event); + } + } + } // def + } // term + } + while (term); + + loader->ReleaseBuffer(block); + + if (ok) { + CheckObjectives(); + + if (target_name[0]) + target = FindElement(target_name); + + if (ward_name[0]) + ward = FindElement(ward_name); + + Print("Mission Template Loaded.\n\n"); + } + + return ok; +} + +// +--------------------------------------------------------------------+ + +void +MissionTemplate::ParseAlias(TermStruct* val) +{ + Text name; + Text design; + Text code; + Text elem_name; + int iff = -1; + int player = 0; + RLoc* rloc = 0; + bool use_loc = false; + Vec3 loc; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + Text defname = pdef->name()->value(); + defname.setSensitive(false); + + if (defname == "name") + GetDefText(name, pdef, filename); + + else if (defname == "elem") + GetDefText(elem_name, pdef, filename); + + else if (defname == "code") + GetDefText(code, pdef, filename); + + else if (defname == "design") + GetDefText(design, pdef, filename); + + else if (defname == "iff") + GetDefNumber(iff, pdef, filename); + + else if (defname == "loc") { + loc; + GetDefVec(loc, pdef, filename); + use_loc = true; + } + + else if (defname == "rloc") { + if (pdef->term()->isStruct()) { + rloc = ParseRLoc(pdef->term()->isStruct()); + } + } + + else if (defname == "player") { + GetDefNumber(player, pdef, filename); + + if (player && !code.length()) + code = "player"; + } + } + } + + MissionElement* elem = 0; + + // now find element and create alias: + if (name.length()) { + for (int i = 0; i < aliases.size(); i++) + if (aliases[i]->Name() == name) + return; + + // by element name? + if (elem_name.length()) { + elem = FindElement(elem_name); + } + + // by special code? + else if (code.length()) { + code.toLower(); + Campaign* campaign = Campaign::GetCampaign(); + + if (code == "player") { + for (int i = 0; !elem && i < elements.size(); i++) { + MissionElement* e = elements[i]; + if (e->Player() > 0) { + elem = e; + } + } + } + + else if (campaign && code == "player_carrier") { + CombatGroup* player_group = campaign->GetPlayerGroup(); + + if (player_group) { + CombatGroup* carrier = player_group->FindCarrier(); + + if (carrier) { + elem = FindElement(carrier->Name()); + } + } + } + + else if (campaign && code == "player_squadron") { + CombatGroup* player_group = player_squadron; + + if (!player_group) + player_group = campaign->GetPlayerGroup(); + + if (player_group && + (player_group->Type() == CombatGroup::INTERCEPT_SQUADRON || + player_group->Type() == CombatGroup::FIGHTER_SQUADRON || + player_group->Type() == CombatGroup::ATTACK_SQUADRON)) { + elem = FindElement(player_group->Name()); + } + } + + else if (campaign && code == "strike_target") { + CombatGroup* player_group = campaign->GetPlayerGroup(); + + if (player_group) { + CombatGroup* strike_target = campaign->FindStrikeTarget(player_group->GetIFF(), player_group); + + if (strike_target) { + elem = FindElement(strike_target->Name()); + } + } + } + } + + // by design and team? + else { + MissionElement* first_match = 0; + + for (int i = 0; !elem && i < elements.size(); i++) { + MissionElement* e = elements[i]; + if (e->GetIFF() == iff && design == e->GetDesign()->name) { + // do we already have an alias for this element? + bool found = false; + for (int a = 0; !found && a < aliases.size(); a++) + if (aliases[a]->Element() == e) + found = true; + + if (!found) + elem = e; + + else if (!first_match) + first_match = e; + } + } + + if (first_match && !elem) + elem = first_match; + } + + if (elem) { + if (rloc) elem->SetRLoc(*rloc); + else if (use_loc) elem->SetLocation(loc); + + delete rloc; + + aliases.append(new(__FILE__,__LINE__) MissionAlias(name, elem)); + } + else { + ::Print("WARNING: Could not resolve mission alias '%s'\n", (const char*) name); + ok = false; + } + } + + if (!elem || !ok) return; + + // re-parse the struct, dealing with stuff + // that needs to be attached to the element: + for (i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + Text defname = pdef->name()->value(); + defname.setSensitive(false); + + if (defname == "objective") { + if (!pdef->term() || !pdef->term()->isStruct()) { + Print("WARNING: order struct missing for element '%s' in '%s'\n", (const char*) elem->Name(), filename); + ok = false; + } + else { + TermStruct* val = pdef->term()->isStruct(); + Instruction* obj = ParseInstruction(val, elem); + elem->Objectives().append(obj); + } + } + + else if (defname == "instr") { + Text* obj = new(__FILE__,__LINE__) Text; + if (GetDefText(*obj, pdef, filename)) + elem->Instructions().append(obj); + } + + else if (defname == "order" || defname == "navpt") { + if (!pdef->term() || !pdef->term()->isStruct()) { + Print("WARNING: order struct missing for element '%s' in '%s'\n", (const char*) elem->Name(), filename); + ok = false; + } + else { + TermStruct* val = pdef->term()->isStruct(); + Instruction* npt = ParseInstruction(val, elem); + elem->NavList().append(npt); + } + } + + else if (defname == "loadout") { + if (!pdef->term() || !pdef->term()->isStruct()) { + Print("WARNING: loadout struct missing for element '%s' in '%s'\n", (const char*) elem->Name(), filename); + ok = false; + } + else { + TermStruct* val = pdef->term()->isStruct(); + ParseLoadout(val, elem); + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +MissionTemplate::ParseCallsign(TermStruct* val) +{ + Text name; + int iff = -1; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + Text defname = pdef->name()->value(); + defname.setSensitive(false); + + if (defname == "name") + GetDefText(name, pdef, filename); + + else if (defname == "iff") + GetDefNumber(iff, pdef, filename); + } + } + + if (name.length() > 0 && iff >= 0) + MapCallsign(name, iff); +} + +// +--------------------------------------------------------------------+ + +bool +MissionTemplate::ParseOptional(TermStruct* val) +{ + int n = 0; + int min = 0; + int max = 1000; + int skip = 0; + int total = val->elements()->size(); + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + Text defname = pdef->name()->value(); + defname.setSensitive(false); + + if (defname == "min") { + GetDefNumber(min, pdef, filename); + total--; + } + + else if (defname == "max") { + GetDefNumber(max, pdef, filename); + total--; + } + + + else if ((defname == "optional")) { + bool select; + + if (n >= max) + select = false; + else if (total - n - skip <= min) + select = true; + else + select = RandomChance(); + + if (select) { + if (!pdef->term() || !pdef->term()->isStruct()) { + Print("WARNING: optional group struct missing in '%s'\n", filename); + ok = false; + skip++; + } + else { + TermStruct* val = pdef->term()->isStruct(); + if (ParseOptional(val)) + n++; + else + skip++; + } + } + else { + skip++; + } + } + + else if (defname == "element") { + bool select; + + if (n >= max) + select = false; + else if (total - n - skip <= min) + select = true; + else + select = RandomChance(); + + if (select) { + if (!pdef->term() || !pdef->term()->isStruct()) { + Print("WARNING: element struct missing in '%s'\n", filename); + ok = false; + skip++; + } + else { + TermStruct* es = pdef->term()->isStruct(); + MissionElement* elem = ParseElement(es); + if (MapElement(elem)) { + AddElement(elem); + n++; + } + else { + delete elem; + skip++; + } + } + } + else { + skip++; + } + } + } + } + + return n > 0 && n >= min; +} + +// +--------------------------------------------------------------------+ + +void +MissionTemplate::CheckObjectives() +{ + ListIter iter = elements; + while (++iter) { + MissionElement* elem = iter.value(); + + ListIter obj = elem->Objectives(); + while (++obj) { + Instruction* o = obj.value(); + Text tgt = o->TargetName(); + + MissionElement* tgt_elem = 0; + + if (tgt.length()) { + tgt_elem = FindElement(tgt); + + if (!tgt_elem) + obj.removeItem(); + else + o->SetTarget(tgt_elem->Name()); + } + } + } +} diff --git a/Stars45/MissionTemplate.h b/Stars45/MissionTemplate.h new file mode 100644 index 0000000..f0772fa --- /dev/null +++ b/Stars45/MissionTemplate.h @@ -0,0 +1,117 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: MissionTemplate.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Simulation Universe and Region classes +*/ + +#ifndef MissionTemplate_h +#define MissionTemplate_h + +#include "Types.h" +#include "Mission.h" + +// +--------------------------------------------------------------------+ + +class MissionTemplate; +class MissionAlias; +class MissionCallsign; +class MissionEvent; + +// +--------------------------------------------------------------------+ + +class MissionTemplate : public Mission +{ +public: + static const char* TYPENAME() { return "MissionTemplate"; } + + MissionTemplate(int id, const char* filename=0, const char* path=0); + virtual ~MissionTemplate(); + + virtual bool Load(const char* filename=0, const char* path=0); + + // accessors/mutators: + virtual MissionElement* FindElement(const char* name); + virtual void AddElement(MissionElement* elem); + virtual bool MapElement(MissionElement* elem); + virtual Text MapShip(Text name); + virtual CombatGroup* GetPlayerSquadron() const { return player_squadron; } + virtual void SetPlayerSquadron(CombatGroup* ps) { player_squadron = ps; } + virtual Text MapCallsign(const char* name, int iff); + virtual bool MapEvent(MissionEvent* event); + + +protected: + CombatGroup* FindCombatGroup(int iff, const ShipDesign* dsn); + void ParseAlias(TermStruct* val); + void ParseCallsign(TermStruct* val); + bool ParseOptional(TermStruct* val); + void CheckObjectives(); + + List aliases; + List callsigns; + CombatGroup* player_squadron; +}; + +// +--------------------------------------------------------------------+ + +class MissionAlias +{ + friend class MissionTemplate; + +public: + static const char* TYPENAME() { return "MissionAlias"; } + + MissionAlias() : elem(0) { } + MissionAlias(const char* n, MissionElement* e) : name(n), elem(e) { } + virtual ~MissionAlias() { } + + int operator == (const MissionAlias& a) const { return name == a.name; } + + Text Name() const { return name; } + MissionElement* Element() const { return elem; } + + void SetName(const char* n) { name = n; } + void SetElement(MissionElement* e) { elem = e; } + +protected: + Text name; + MissionElement* elem; +}; + +// +--------------------------------------------------------------------+ + +class MissionCallsign +{ + friend class MissionTemplate; + +public: + static const char* TYPENAME() { return "MissionCallsign"; } + + MissionCallsign() { } + MissionCallsign(const char* c, const char* n) : call(c), name(n) { } + virtual ~MissionCallsign() { } + + int operator == (const MissionCallsign& a)const { return call == a.call; } + + Text Callsign() const { return call; } + Text Name() const { return name; } + + void SetCallsign(const char* c) { call = c; } + void SetName(const char* n) { name = n; } + +protected: + Text call; + Text name; +}; + + +#endif MissionTemplate_h + diff --git a/Stars45/ModConfig.cpp b/Stars45/ModConfig.cpp new file mode 100644 index 0000000..dd2f96e --- /dev/null +++ b/Stars45/ModConfig.cpp @@ -0,0 +1,381 @@ +/* Project Starshatter 5.0 + Destroyer Studios LLC + Copyright © 1997-2007. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: ModConfig.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Mod file deployment configuration and manager +*/ + + +#include "MemDebug.h" +#include "ModConfig.h" +#include "ModInfo.h" +#include "Campaign.h" +#include "ShipDesign.h" +#include "WeaponDesign.h" +#include "Starshatter.h" +#include "ParseUtil.h" +#include "DataLoader.h" + +// +-------------------------------------------------------------------+ + +static ModConfig* mod_config = 0; + +// +-------------------------------------------------------------------+ + +ModConfig::ModConfig() +{ + mod_config = this; + + Load(); + FindMods(); + Deploy(); +} + +ModConfig::~ModConfig() +{ + if (mod_config == this) + mod_config = 0; + + Undeploy(); + + enabled.destroy(); + disabled.destroy(); + mods.destroy(); +} + +// +-------------------------------------------------------------------+ + +void +ModConfig::Initialize() +{ + mod_config = new(__FILE__,__LINE__) ModConfig; +} + +void +ModConfig::Close() +{ + delete mod_config; + mod_config = 0; +} + +// +-------------------------------------------------------------------+ + +ModConfig* +ModConfig::GetInstance() +{ + return mod_config; +} + +// +-------------------------------------------------------------------+ + +void +ModConfig::Load() +{ + // read the config file: + BYTE* block = 0; + int blocklen = 0; + + char filename[64]; + strcpy(filename, "mod.cfg"); + + FILE* f = ::fopen(filename, "rb"); + + if (f) { + ::fseek(f, 0, SEEK_END); + blocklen = ftell(f); + ::fseek(f, 0, SEEK_SET); + + block = new(__FILE__,__LINE__) BYTE[blocklen+1]; + block[blocklen] = 0; + + ::fread(block, blocklen, 1, f); + ::fclose(f); + } + + if (blocklen == 0) + return; + + Parser parser(new(__FILE__,__LINE__) BlockReader((const char*) block, blocklen)); + Term* term = parser.ParseTerm(); + + if (!term) { + Print("ERROR: could not parse '%s'.\n", filename); + return; + } + else { + TermText* file_type = term->isText(); + if (!file_type || file_type->value() != "MOD_CONFIG") { + Print("WARNING: invalid '%s' file. No mods deployed\n", filename); + return; + } + } + + enabled.destroy(); + + do { + delete term; + + term = parser.ParseTerm(); + + if (term) { + TermDef* def = term->isDef(); + if (def) { + Text name; + GetDefText(name, def, filename); + enabled.append(new(__FILE__,__LINE__) Text(name)); + } + } + } + while (term); + + delete [] block; +} + +void +ModConfig::Save() +{ + FILE* f = fopen("mod.cfg", "w"); + if (f) { + fprintf(f, "MOD_CONFIG\n\n"); + + ListIter iter = enabled; + while (++iter) { + Text* name = iter.value(); + fprintf(f, "mod: \"%s\"\n", name->data()); + } + + fclose(f); + } +} + +void +ModConfig::FindMods() +{ + disabled.destroy(); + + DataLoader* loader = DataLoader::GetLoader(); + + if (loader) { + loader->UseFileSystem(true); + loader->ListFiles("*.dat", disabled, true); + loader->UseFileSystem(Starshatter::UseFileSystem()); + + ListIter iter = disabled; + while (++iter) { + Text* name = iter.value(); + name->setSensitive(false); + + if (*name == "shatter.dat" || + *name == "beta.dat" || + *name == "start.dat" || + *name == "irunin.dat" || + *name == "vox.dat" || + name->contains("uninstall") || + enabled.contains(name)) + delete iter.removeItem(); + } + } +} + +// +-------------------------------------------------------------------+ + +ModInfo* +ModConfig::GetModInfo(const char* filename) +{ + for (int i = 0; i < mods.size(); i++) { + ModInfo* mod_info = mods[i]; + + if (mod_info->Filename() == filename && mod_info->IsEnabled()) + return mod_info; + } + + return false; +} + +// +-------------------------------------------------------------------+ + +bool +ModConfig::IsDeployed(const char* name) +{ + for (int i = 0; i < mods.size(); i++) { + ModInfo* mod_info = mods[i]; + + if (mod_info->Name() == name && mod_info->IsEnabled()) + return true; + } + + return false; +} + +// +-------------------------------------------------------------------+ + +void +ModConfig::Deploy() +{ + Save(); + + if (enabled.size() < 1) + return; + +#ifdef STARSHATTER_DEMO_RELEASE + Print("\nPACKAGED MODS ARE NOT SUPPORTED IN THIS DEMO\n"); +#else + Print("\nDEPLOYING MODS\n--------------\n"); + + int i = 1; + ListIter iter = enabled; + while (++iter) { + Text* name = iter.value(); + + if (IsDeployed(name->data())) { + Print(" %d. %s is already deployed (skipping)\n", i++, name->data()); + continue; + } + + Print(" %d. %s\n", i++, name->data()); + + ModInfo* mod_info = new(__FILE__,__LINE__) ModInfo; + + if (mod_info->Load(name->data()) && mod_info->Enable()) { + mods.append(mod_info); + } + else { + Print(" Could not deploy '%s' - disabling\n", name->data()); + + delete mod_info; + iter.removeItem(); + disabled.append(name); + } + } + + Print("\n"); + Game::UseLocale(0); +#endif +} + +void +ModConfig::Undeploy() +{ + Print("UNDEPLOYING MODS\n"); + mods.destroy(); + + ShipDesign::ClearModCatalog(); + WeaponDesign::ClearModCatalog(); +} + +void +ModConfig::Redeploy() +{ + Undeploy(); + Deploy(); + + Campaign::Close(); + Campaign::Initialize(); + Campaign::SelectCampaign("Single Missions"); +} + +// +-------------------------------------------------------------------+ + +void +ModConfig::EnableMod(const char* name) +{ +#ifdef STARSHATTER_DEMO_RELEASE + DisableMod(name); +#else + + if (!name || !*name) + return; + + Text* mod_name; + + ListIter iter = disabled; + while (++iter) { + Text* t = iter.value(); + + if (*t == name) { + mod_name = t; + iter.removeItem(); + break; + } + } + + if (mod_name) { + enabled.append(mod_name); + + if (!IsDeployed(*mod_name)) { + ModInfo* mod_info = new(__FILE__,__LINE__) ModInfo; + + if (mod_info->Load(*mod_name) && mod_info->Enable()) { + mods.append(mod_info); + } + } + } +#endif +} + +void +ModConfig::DisableMod(const char* name) +{ + if (!name || !*name) + return; + + Text* mod_name; + + ListIter iter = enabled; + while (++iter) { + Text* t = iter.value(); + + if (*t == name) { + mod_name = t; + iter.removeItem(); + break; + } + } + + if (mod_name) { + disabled.append(mod_name); + + ListIter iter = mods; + while (++iter) { + ModInfo* mod_info = iter.value(); + if (mod_info->Name() == *mod_name) { + delete iter.removeItem(); + break; + } + } + } +} + +// +-------------------------------------------------------------------+ + +void +ModConfig::IncreaseModPriority(int mod_index) +{ + if (mod_index > 0 && mod_index < enabled.size()) { + Text* mod1 = enabled.at(mod_index-1); + Text* mod2 = enabled.at(mod_index); + + enabled.at(mod_index-1) = mod2; + enabled.at(mod_index) = mod1; + } +} + +void +ModConfig::DecreaseModPriority(int mod_index) +{ + if (mod_index >= 0 && mod_index < enabled.size()-1) { + Text* mod1 = enabled.at(mod_index); + Text* mod2 = enabled.at(mod_index+1); + + enabled.at(mod_index) = mod2; + enabled.at(mod_index+1) = mod1; + } +} + diff --git a/Stars45/ModConfig.h b/Stars45/ModConfig.h new file mode 100644 index 0000000..9e1f703 --- /dev/null +++ b/Stars45/ModConfig.h @@ -0,0 +1,75 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: ModConfig.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Mod file deployment configuration and manager +*/ + + +#ifndef ModConfig_h +#define ModConfig_h + +#include "Types.h" +#include "Bitmap.h" +#include "Text.h" +#include "List.h" + +// +-------------------------------------------------------------------+ + +class ModConfig; +class ModInfo; +class ModCampaign; + +// +-------------------------------------------------------------------+ + +class ModConfig +{ +public: + static const char* TYPENAME() { return "ModConfig"; } + + ModConfig(); + ~ModConfig(); + + int operator == (const ModConfig& cfg) const { return this == &cfg; } + + static void Initialize(); + static void Close(); + static ModConfig* GetInstance(); + + void Load(); + void Save(); + void FindMods(); + + bool IsDeployed(const char* name); + void Deploy(); + void Undeploy(); + void Redeploy(); + + // these methods change the configuration only + // you must Redeploy() to have them take effect: + + void EnableMod(const char* name); + void DisableMod(const char* name); + void IncreaseModPriority(int mod_index); + void DecreaseModPriority(int mod_index); + + List& EnabledMods() { return enabled; } + List& DisabledMods() { return disabled; } + List& GetModInfoList() { return mods; } + + ModInfo* GetModInfo(const char* filename); + +private: + List enabled; + List disabled; + List mods; +}; + +#endif ModConfig_h \ No newline at end of file diff --git a/Stars45/ModDlg.cpp b/Stars45/ModDlg.cpp new file mode 100644 index 0000000..14096e3 --- /dev/null +++ b/Stars45/ModDlg.cpp @@ -0,0 +1,350 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: ModDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Mod Config Dialog Active Window class +*/ + +#include "MemDebug.h" +#include "ModDlg.h" +#include "ModInfoDlg.h" +#include "BaseScreen.h" +#include "ModConfig.h" + +#include "Game.h" +#include "DataLoader.h" +#include "Button.h" +#include "ListBox.h" +#include "Slider.h" +#include "Video.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "ParseUtil.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +DEF_MAP_CLIENT(ModDlg, OnIncrease); +DEF_MAP_CLIENT(ModDlg, OnDecrease); +DEF_MAP_CLIENT(ModDlg, OnEnable); +DEF_MAP_CLIENT(ModDlg, OnDisable); +DEF_MAP_CLIENT(ModDlg, OnSelectEnabled); +DEF_MAP_CLIENT(ModDlg, OnSelectDisabled); +DEF_MAP_CLIENT(ModDlg, OnAccept); +DEF_MAP_CLIENT(ModDlg, OnCancel); +DEF_MAP_CLIENT(ModDlg, OnAudio); +DEF_MAP_CLIENT(ModDlg, OnVideo); +DEF_MAP_CLIENT(ModDlg, OnOptions); +DEF_MAP_CLIENT(ModDlg, OnControls); +DEF_MAP_CLIENT(ModDlg, OnMod); + +// +--------------------------------------------------------------------+ + +ModDlg::ModDlg(Screen* s, FormDef& def, BaseScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), manager(mgr), + lst_disabled(0), lst_enabled(0), btn_enable(0), btn_disable(0), + btn_increase(0), btn_decrease(0), btn_accept(0), btn_cancel(0), + aud_btn(0), vid_btn(0), ctl_btn(0), opt_btn(0), mod_btn(0), + config(0), changed(false) +{ + config = ModConfig::GetInstance(); + Init(def); +} + +ModDlg::~ModDlg() +{ +} + +// +--------------------------------------------------------------------+ + +void +ModDlg::RegisterControls() +{ + btn_accept = (Button*) FindControl( 1); + btn_cancel = (Button*) FindControl( 2); + btn_enable = (Button*) FindControl(301); + btn_disable = (Button*) FindControl(302); + btn_increase = (Button*) FindControl(303); + btn_decrease = (Button*) FindControl(304); + + lst_disabled = (ListBox*) FindControl(201); + lst_enabled = (ListBox*) FindControl(202); + + if (btn_accept) + REGISTER_CLIENT(EID_CLICK, btn_accept, ModDlg, OnAccept); + + if (btn_cancel) + REGISTER_CLIENT(EID_CLICK, btn_cancel, ModDlg, OnCancel); + + if (lst_enabled) + REGISTER_CLIENT(EID_SELECT, lst_enabled, ModDlg, OnSelectEnabled); + + if (lst_disabled) + REGISTER_CLIENT(EID_SELECT, lst_disabled, ModDlg, OnSelectDisabled); + + if (btn_enable) { + REGISTER_CLIENT(EID_CLICK, btn_enable, ModDlg, OnEnable); + btn_enable->SetEnabled(false); + } + + if (btn_disable) { + REGISTER_CLIENT(EID_CLICK, btn_disable, ModDlg, OnDisable); + btn_disable->SetEnabled(false); + } + + if (btn_increase) { + char up_arrow[2]; + up_arrow[0] = Font::ARROW_UP; + up_arrow[1] = 0; + btn_increase->SetText(up_arrow); + btn_increase->SetEnabled(false); + REGISTER_CLIENT(EID_CLICK, btn_increase, ModDlg, OnIncrease); + } + + if (btn_decrease) { + char dn_arrow[2]; + dn_arrow[0] = Font::ARROW_DOWN; + dn_arrow[1] = 0; + btn_decrease->SetText(dn_arrow); + btn_decrease->SetEnabled(false); + REGISTER_CLIENT(EID_CLICK, btn_decrease, ModDlg, OnDecrease); + } + + vid_btn = (Button*) FindControl(901); + REGISTER_CLIENT(EID_CLICK, vid_btn, ModDlg, OnVideo); + + aud_btn = (Button*) FindControl(902); + REGISTER_CLIENT(EID_CLICK, aud_btn, ModDlg, OnAudio); + + ctl_btn = (Button*) FindControl(903); + REGISTER_CLIENT(EID_CLICK, ctl_btn, ModDlg, OnControls); + + opt_btn = (Button*) FindControl(904); + REGISTER_CLIENT(EID_CLICK, opt_btn, ModDlg, OnOptions); + + mod_btn = (Button*) FindControl(905); + if (mod_btn) + REGISTER_CLIENT(EID_CLICK, mod_btn, ModDlg, OnMod); +} + +// +--------------------------------------------------------------------+ + +void +ModDlg::Show() +{ + FormWindow::Show(); + UpdateLists(); + + if (vid_btn) vid_btn->SetButtonState(0); + if (aud_btn) aud_btn->SetButtonState(0); + if (ctl_btn) ctl_btn->SetButtonState(0); + if (opt_btn) opt_btn->SetButtonState(0); + if (mod_btn) mod_btn->SetButtonState(1); +} + +void +ModDlg::UpdateLists() +{ + config = ModConfig::GetInstance(); + + if (config && lst_disabled && lst_enabled) { + lst_disabled->ClearItems(); + lst_enabled->ClearItems(); + + ListIter iter_d = config->DisabledMods(); + while (++iter_d) { + Text* t = iter_d.value(); + lst_disabled->AddItem(*t); + } + + ListIter iter_e = config->EnabledMods(); + while (++iter_e) { + Text* t = iter_e.value(); + lst_enabled->AddItem(*t); + } + } + + if (btn_disable) + btn_disable->SetEnabled(false); + + if (btn_enable) + btn_enable->SetEnabled(false); + + if (btn_increase) + btn_increase->SetEnabled(false); + + if (btn_decrease) + btn_decrease->SetEnabled(false); +} + +// +--------------------------------------------------------------------+ + +void +ModDlg::ExecFrame() +{ + if (Keyboard::KeyDown(VK_RETURN)) { + if (btn_accept && btn_accept->IsEnabled()) + OnAccept(0); + } +} + +// +--------------------------------------------------------------------+ + +void +ModDlg::OnSelectEnabled(AWEvent* event) +{ + static DWORD click_time = 0; + + if (lst_enabled) { + if (btn_disable) + btn_disable->SetEnabled(lst_enabled->GetSelCount() == 1); + + if (btn_increase) + btn_increase->SetEnabled(lst_enabled->GetSelection() > 0); + + if (btn_decrease) + btn_decrease->SetEnabled(lst_enabled->GetSelection() < lst_enabled->NumItems() - 1); + + // double-click: + if (Game::RealTime() - click_time < 350) { + if (lst_enabled->GetSelCount() == 1) { + int index = lst_enabled->GetSelection(); + Text mod_name = lst_enabled->GetItemText(index); + ModInfo* mod_info = config->GetModInfo(mod_name); + ModInfoDlg* mod_info_dlg = manager->GetModInfoDlg(); + + if (mod_info && mod_info_dlg) { + mod_info_dlg->SetModInfo(mod_info); + manager->ShowModInfoDlg(); + } + } + } + } + + click_time = Game::RealTime(); +} + +void +ModDlg::OnSelectDisabled(AWEvent* event) +{ +#ifdef STARSHATTER_DEMO_RELEASE + if (btn_enable) { + btn_enable->SetEnabled(false); + } +#else + if (btn_enable && lst_disabled) { + btn_enable->SetEnabled(lst_disabled->GetSelCount() == 1); + } +#endif +} + +void +ModDlg::OnEnable(AWEvent* event) +{ + int index = lst_disabled->GetSelection(); + Text mod_name = lst_disabled->GetItemText(index); + + config->EnableMod(mod_name); + changed = true; + + UpdateLists(); + + ModInfo* mod_info = config->GetModInfo(mod_name); + ModInfoDlg* mod_info_dlg = manager->GetModInfoDlg(); + + if (mod_info && mod_info_dlg) { + mod_info_dlg->SetModInfo(mod_info); + manager->ShowModInfoDlg(); + } +} + +void +ModDlg::OnDisable(AWEvent* event) +{ + int index = lst_enabled->GetSelection(); + Text mod_name = lst_enabled->GetItemText(index); + + config->DisableMod(mod_name); + changed = true; + + UpdateLists(); +} + +void +ModDlg::OnIncrease(AWEvent* event) +{ + int index = lst_enabled->GetSelection(); + config->IncreaseModPriority(index--); + + UpdateLists(); + lst_enabled->SetSelected(index); + btn_disable->SetEnabled(true); + btn_increase->SetEnabled(index > 0); + btn_decrease->SetEnabled(index < lst_enabled->NumItems()-1); +} + +void +ModDlg::OnDecrease(AWEvent* event) +{ + int index = lst_enabled->GetSelection(); + config->DecreaseModPriority(index++); + + UpdateLists(); + lst_enabled->SetSelected(index); + btn_disable->SetEnabled(true); + btn_increase->SetEnabled(index > 0); + btn_decrease->SetEnabled(index < lst_enabled->NumItems()-1); +} + +// +--------------------------------------------------------------------+ + +void ModDlg::OnAudio(AWEvent* event) { manager->ShowAudDlg(); } +void ModDlg::OnVideo(AWEvent* event) { manager->ShowVidDlg(); } +void ModDlg::OnOptions(AWEvent* event) { manager->ShowOptDlg(); } +void ModDlg::OnControls(AWEvent* event) { manager->ShowCtlDlg(); } +void ModDlg::OnMod(AWEvent* event) { manager->ShowModDlg(); } + +// +--------------------------------------------------------------------+ + +void +ModDlg::Apply() +{ + if (changed) { + config->Save(); + config->FindMods(); + config->Redeploy(); + changed = false; + } +} + +void +ModDlg::Cancel() +{ + if (changed) { + config->Load(); + config->FindMods(); + config->Redeploy(); + changed = false; + } +} + +// +--------------------------------------------------------------------+ + +void +ModDlg::OnAccept(AWEvent* event) +{ + manager->ApplyOptions(); +} + +void +ModDlg::OnCancel(AWEvent* event) +{ + manager->CancelOptions(); +} diff --git a/Stars45/ModDlg.h b/Stars45/ModDlg.h new file mode 100644 index 0000000..b5b93b3 --- /dev/null +++ b/Stars45/ModDlg.h @@ -0,0 +1,94 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: ModDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Mod Config Dialog Active Window class +*/ + +#ifndef ModDlg_h +#define ModDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "Bitmap.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Font.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class BaseScreen; +class Campaign; +class ModConfig; + +// +--------------------------------------------------------------------+ + +class ModDlg : public FormWindow +{ +public: + ModDlg(Screen* s, FormDef& def, BaseScreen* mgr); + virtual ~ModDlg(); + + virtual void RegisterControls(); + virtual void Show(); + virtual void ExecFrame(); + + // Operations: + + virtual void OnIncrease(AWEvent* event); + virtual void OnDecrease(AWEvent* event); + + virtual void OnSelectEnabled(AWEvent* event); + virtual void OnSelectDisabled(AWEvent* event); + + virtual void OnEnable(AWEvent* event); + virtual void OnDisable(AWEvent* event); + + virtual void OnAccept(AWEvent* event); + virtual void OnCancel(AWEvent* event); + + virtual void Apply(); + virtual void Cancel(); + + virtual void OnAudio(AWEvent* event); + virtual void OnVideo(AWEvent* event); + virtual void OnOptions(AWEvent* event); + virtual void OnControls(AWEvent* event); + virtual void OnMod(AWEvent* event); + +protected: + void UpdateLists(); + + BaseScreen* manager; + + ListBox* lst_disabled; + ListBox* lst_enabled; + + Button* btn_accept; + Button* btn_cancel; + Button* btn_enable; + Button* btn_disable; + Button* btn_increase; + Button* btn_decrease; + + Button* aud_btn; + Button* vid_btn; + Button* opt_btn; + Button* ctl_btn; + Button* mod_btn; + + ModConfig* config; + bool changed; +}; + +#endif ModDlg_h + diff --git a/Stars45/ModInfo.cpp b/Stars45/ModInfo.cpp new file mode 100644 index 0000000..70ecb02 --- /dev/null +++ b/Stars45/ModInfo.cpp @@ -0,0 +1,292 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: ModInfo.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Information block for describing and deploying third party mods +*/ + + +#include "MemDebug.h" +#include "ModInfo.h" +#include "Campaign.h" +#include "ShipDesign.h" +#include "ParseUtil.h" + +#include "Archive.h" +#include "DataLoader.h" +#include "PCX.h" +#include "Bitmap.h" + +// +-------------------------------------------------------------------+ + +ModInfo::ModInfo() + : logo(0), distribute(false), enabled(false), catalog(0) +{ } + +ModInfo::ModInfo(const char* fname) + : logo(0), distribute(false), enabled(false), catalog(0) +{ + if (fname && *fname) { + Load(fname); + } +} + +ModInfo::ModInfo(const char* n, const char* v, const char* u) + : name(n), logo(0), version(v), url(u), distribute(false), enabled(false), catalog(0) +{ } + +ModInfo::~ModInfo() +{ + if (enabled) + Disable(); + + delete logo; + delete catalog; + campaigns.destroy(); +} + +// +-------------------------------------------------------------------+ + +bool +ModInfo::Load(const char* fname) +{ + bool ok = false; + + filename = fname; + DataArchive a(filename); + + int n = a.FindEntry("mod_info.def"); + if (n > -1) { + BYTE* buf = 0; + int len = a.ExpandEntry(n, buf, true); + + if (len > 0 && buf != 0) { + ok = ParseModInfo((const char*) buf); + delete [] buf; + } + } + + if (logoname.length()) { + PcxImage pcx; + + logo = new(__FILE__,__LINE__) Bitmap; + + n = a.FindEntry(logoname); + if (n > -1) { + BYTE* buf = 0; + int len = a.ExpandEntry(n, buf, true); + + pcx.LoadBuffer(buf, len); + delete [] buf; + } + + // now copy the image into the bitmap: + if (pcx.bitmap) { + logo->CopyImage(pcx.width, pcx.height, pcx.bitmap); + } + + else if (pcx.himap) { + logo->CopyHighColorImage(pcx.width, pcx.height, pcx.himap); + } + + else { + logo->ClearImage(); + } + } + + return ok; +} + +bool +ModInfo::ParseModInfo(const char* block) +{ + bool ok = false; + Parser parser(new(__FILE__,__LINE__) BlockReader(block)); + Term* term = parser.ParseTerm(); + + if (!term) { + Print("ERROR: could not parse '%s'\n", filename.data()); + return ok; + } + else { + TermText* file_type = term->isText(); + if (!file_type || file_type->value() != "MOD_INFO") { + Print("ERROR: invalid mod_info file '%s'\n", filename.data()); + term->print(10); + return ok; + } + } + + ok = true; + + do { + delete term; term = 0; + term = parser.ParseTerm(); + + if (term) { + TermDef* def = term->isDef(); + if (def) { + Text defname = def->name()->value(); + + if (defname == "name") + GetDefText(name, def, filename); + + else if (defname == "desc" || defname == "description") + GetDefText(desc, def, filename); + + else if (defname == "author") + GetDefText(author, def, filename); + + else if (defname == "url") + GetDefText(url, def, filename); + + else if (defname == "copyright") + GetDefText(copyright, def, filename); + + else if (defname == "logo") + GetDefText(logoname, def, filename); + + else if (defname == "version") { + if (def->term()) { + if (def->term()->isNumber()) { + int v = 0; + char buf[32]; + GetDefNumber(v, def, filename); + sprintf(buf, "%d", v); + + version = buf; + } + + else if (def->term()->isText()) { + GetDefText(version, def, filename); + } + } + } + + else if (defname == "distribute") + GetDefBool(distribute, def, filename); + + else if (defname == "campaign") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: campaign structure missing in mod_info.def for '%s'\n", name.data()); + } + else { + TermStruct* val = def->term()->isStruct(); + + ModCampaign* c = new(__FILE__,__LINE__) ModCampaign; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + defname = pdef->name()->value(); + + if (defname == "name") { + GetDefText(c->name, pdef, filename); + } + + else if (defname == "path") { + GetDefText(c->path, pdef, filename); + } + + else if (defname == "dynamic") { + GetDefBool(c->dynamic, pdef, filename); + } + } + } + + if (c->name.length() && c->path.length()) + campaigns.append(c); + else + delete c; + } + } + + else if (defname == "catalog") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: catalog structure missing in mod_info.def for '%s'\n", name.data()); + } + else { + TermStruct* val = def->term()->isStruct(); + + ModCatalog* c = new(__FILE__,__LINE__) ModCatalog; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + defname = pdef->name()->value(); + + if (defname == "file") { + GetDefText(c->file, pdef, filename); + } + + else if (defname == "path") { + GetDefText(c->path, pdef, filename); + + char last_char = c->path[c->path.length()-1]; + + if (last_char != '/' && last_char != '\\') + c->path += "/"; + } + } + } + + if (c->file.length() && c->path.length() && !catalog) + catalog = c; + else + delete c; + } + } + } // def + } // term + } + while (term); + + return ok; +} + +// +-------------------------------------------------------------------+ + +bool +ModInfo::Enable() +{ + DataLoader* loader = DataLoader::GetLoader(); + + if (loader && !enabled) { + loader->EnableDatafile(filename); + enabled = true; + + if (catalog) + ShipDesign::LoadCatalog(catalog->Path(), catalog->File(), true); + + ShipDesign::LoadSkins("/Mods/Skins/", filename); + + for (int i = 0; i < campaigns.size(); i++) { + ModCampaign* c = campaigns[i]; + Campaign::CreateCustomCampaign(c->Name(), c->Path()); + } + } + + return enabled; +} + +bool +ModInfo::Disable() +{ + DataLoader* loader = DataLoader::GetLoader(); + + if (loader) { + loader->DisableDatafile(filename); + enabled = false; + } + + return !enabled; +} + +// +-------------------------------------------------------------------+ diff --git a/Stars45/ModInfo.h b/Stars45/ModInfo.h new file mode 100644 index 0000000..997746c --- /dev/null +++ b/Stars45/ModInfo.h @@ -0,0 +1,122 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: ModInfo.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Information block for describing and deploying third party mods +*/ + + +#ifndef ModInfo_h +#define ModInfo_h + +#include "Types.h" +#include "Bitmap.h" +#include "Text.h" +#include "List.h" + +// +-------------------------------------------------------------------+ + +class ModInfo; +class ModCampaign; +class ModCatalog; + +// +-------------------------------------------------------------------+ + +class ModInfo +{ +public: + static const char* TYPENAME() { return "ModInfo"; } + + ModInfo(); + ModInfo(const char* filename); + ModInfo(const char* name, const char* version, const char* url); + ~ModInfo(); + + int operator == (const ModInfo& m) const { return name.length() && name == m.name; } + + const Text& Name() const { return name; } + const Text& Description() const { return desc; } + const Text& Author() const { return author; } + const Text& URL() const { return url; } + const Text& Filename() const { return filename; } + const Text& Copyright() const { return copyright; } + Bitmap* LogoImage() const { return logo; } + const Text& Version() const { return version; } + bool Distribute() const { return distribute; } + bool IsEnabled() const { return enabled; } + + List& GetCampaigns() { return campaigns; } + + bool Load(const char* filename); + bool ParseModInfo(const char* buffer); + + bool Enable(); + bool Disable(); + +private: + Text name; + Text desc; + Text author; + Text url; + Text filename; + Text copyright; + Bitmap* logo; + Text logoname; + Text version; + bool distribute; + bool enabled; + + List campaigns; + ModCatalog* catalog; +}; + +// +-------------------------------------------------------------------+ + +class ModCampaign +{ + friend class ModInfo; + +public: + static const char* TYPENAME() { return "ModCampaign"; } + + ModCampaign() : dynamic(false) { } + ~ModCampaign() { } + + const Text& Name() const { return name; } + const Text& Path() const { return path; } + bool IsDynamic() const { return dynamic; } + +private: + Text name; + Text path; + bool dynamic; +}; + +// +-------------------------------------------------------------------+ + +class ModCatalog +{ + friend class ModInfo; + +public: + static const char* TYPENAME() { return "ModCatalog"; } + + ModCatalog() { } + ~ModCatalog() { } + + const Text& File() const { return file; } + const Text& Path() const { return path; } + +private: + Text file; + Text path; +}; + +#endif ModInfo_h \ No newline at end of file diff --git a/Stars45/ModInfoDlg.cpp b/Stars45/ModInfoDlg.cpp new file mode 100644 index 0000000..f7dfb34 --- /dev/null +++ b/Stars45/ModInfoDlg.cpp @@ -0,0 +1,115 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: ModInfoDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Mod Config Dialog Active Window class +*/ + +#include "MemDebug.h" +#include "ModInfoDlg.h" +#include "BaseScreen.h" +#include "ModConfig.h" +#include "ModInfo.h" + +#include "Game.h" +#include "DataLoader.h" +#include "Button.h" +#include "ListBox.h" +#include "ImageBox.h" +#include "Video.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "ParseUtil.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +DEF_MAP_CLIENT(ModInfoDlg, OnAccept); + +// +--------------------------------------------------------------------+ + +ModInfoDlg::ModInfoDlg(Screen* s, FormDef& def, BaseScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), manager(mgr), + btn_accept(0), mod_info(0) +{ + Init(def); +} + +ModInfoDlg::~ModInfoDlg() +{ +} + +// +--------------------------------------------------------------------+ + +void +ModInfoDlg::RegisterControls() +{ + btn_accept = (Button*) FindControl( 1); + + if (btn_accept) + REGISTER_CLIENT(EID_CLICK, btn_accept, ModInfoDlg, OnAccept); + + lbl_name = FindControl(101); + lbl_desc = FindControl(102); + lbl_copy = FindControl(103); + + img_logo = (ImageBox*) FindControl(200); + + if (img_logo) { + img_logo->GetPicture(bmp_default); + img_logo->SetBlendMode(Video::BLEND_SOLID); + } +} + +// +--------------------------------------------------------------------+ + +void +ModInfoDlg::Show() +{ + FormWindow::Show(); +} + +// +--------------------------------------------------------------------+ + +void +ModInfoDlg::ExecFrame() +{ + if (Keyboard::KeyDown(VK_RETURN)) { + if (btn_accept && btn_accept->IsEnabled()) + OnAccept(0); + } +} + +// +--------------------------------------------------------------------+ + +void +ModInfoDlg::SetModInfo(ModInfo* info) +{ + mod_info = info; + + if (mod_info) { + if (lbl_name) lbl_name->SetText(mod_info->Name()); + if (lbl_desc) lbl_desc->SetText(mod_info->Description()); + if (lbl_copy) lbl_copy->SetText(mod_info->Copyright()); + + if (img_logo && mod_info->LogoImage() && mod_info->LogoImage()->Width() > 32) + img_logo->SetPicture(*mod_info->LogoImage()); + else if (img_logo) + img_logo->SetPicture(bmp_default); + } +} + +// +--------------------------------------------------------------------+ + +void +ModInfoDlg::OnAccept(AWEvent* event) +{ + manager->ShowModDlg(); +} diff --git a/Stars45/ModInfoDlg.h b/Stars45/ModInfoDlg.h new file mode 100644 index 0000000..c2dacde --- /dev/null +++ b/Stars45/ModInfoDlg.h @@ -0,0 +1,65 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: ModInfoDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Mod Info Splash Dialog Active Window class +*/ + +#ifndef ModInfoDlg_h +#define ModInfoDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "Bitmap.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Font.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class BaseScreen; +class ModConfig; +class ModInfo; + +// +--------------------------------------------------------------------+ + +class ModInfoDlg : public FormWindow +{ +public: + ModInfoDlg(Screen* s, FormDef& def, BaseScreen* mgr); + virtual ~ModInfoDlg(); + + virtual void RegisterControls(); + virtual void Show(); + virtual void ExecFrame(); + + // Operations: + virtual void SetModInfo(ModInfo* info); + virtual void OnAccept(AWEvent* event); + +protected: + BaseScreen* manager; + + ActiveWindow* lbl_name; + ActiveWindow* lbl_desc; + ActiveWindow* lbl_copy; + ImageBox* img_logo; + + Button* btn_accept; + + ModInfo* mod_info; + + Bitmap bmp_default; +}; + +#endif ModInfoDlg_h + diff --git a/Stars45/MsnDlg.cpp b/Stars45/MsnDlg.cpp new file mode 100644 index 0000000..0884bbe --- /dev/null +++ b/Stars45/MsnDlg.cpp @@ -0,0 +1,303 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: MsnDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Mission Briefing Dialog Active Window class +*/ + +#include "MemDebug.h" +#include "MsnDlg.h" +#include "PlanScreen.h" +#include "Starshatter.h" +#include "Campaign.h" +#include "Mission.h" +#include "Instruction.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "StarSystem.h" + +#include "NetLobby.h" + +#include "Game.h" +#include "FormWindow.h" +#include "FormatUtil.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "Button.h" +#include "ListBox.h" +#include "Slider.h" +#include "ParseUtil.h" + +// +--------------------------------------------------------------------+ + +MsnDlg::MsnDlg(PlanScreen* mgr) + : plan_screen(mgr), + commit(0), cancel(0), campaign(0), mission(0), + pkg_index(-1), info(0) +{ + campaign = Campaign::GetCampaign(); + + if (campaign) + mission = campaign->GetMission(); + + mission_name = 0; + mission_system = 0; + mission_sector = 0; + mission_time_start = 0; + mission_time_target = 0; + mission_time_target_label = 0; + + sit_button = 0; + pkg_button = 0; + nav_button = 0; + wep_button = 0; + commit = 0; + cancel = 0; +} + +MsnDlg::~MsnDlg() +{ +} + +// +--------------------------------------------------------------------+ + +void +MsnDlg::RegisterMsnControls(FormWindow* win) +{ + mission_name = win->FindControl(200); + mission_system = win->FindControl(202); + mission_sector = win->FindControl(204); + mission_time_start = win->FindControl(206); + mission_time_target = win->FindControl(208); + mission_time_target_label = win->FindControl(207); + + sit_button = (Button*) win->FindControl(900); + pkg_button = (Button*) win->FindControl(901); + nav_button = (Button*) win->FindControl(902); + wep_button = (Button*) win->FindControl(903); + commit = (Button*) win->FindControl(1); + cancel = (Button*) win->FindControl(2); +} + +// +--------------------------------------------------------------------+ + +void +MsnDlg::ShowMsnDlg() +{ + campaign = Campaign::GetCampaign(); + + mission = 0; + pkg_index = -1; + + if (campaign) { + mission = campaign->GetMission(); + + if (!mission) + ::Print("ERROR - MsnDlg::Show() no mission.\n"); + else + ::Print("MsnDlg::Show() mission id = %d name = '%s'\n", + mission->Identity(), + mission->Name()); + } + else { + ::Print("ERROR - MsnDlg::Show() no campaign.\n"); + } + + if (mission_name) { + if (mission) + mission_name->SetText(mission->Name()); + else + mission_name->SetText(Game::GetText("MsnDlg.no-mission")); + } + + if (mission_system) { + mission_system->SetText(""); + + if (mission) { + StarSystem* sys = mission->GetStarSystem(); + + if (sys) + mission_system->SetText(sys->Name()); + } + } + + if (mission_sector) { + mission_sector->SetText(""); + + if (mission) { + mission_sector->SetText(mission->GetRegion()); + } + } + + if (mission_time_start) { + if (mission) { + char txt[32]; + FormatDayTime(txt, mission->Start()); + mission_time_start->SetText(txt); + } + } + + if (mission_time_target) { + int time_on_target = CalcTimeOnTarget(); + + if (time_on_target) { + char txt[32]; + FormatDayTime(txt, time_on_target); + mission_time_target->SetText(txt); + mission_time_target_label->SetText(Game::GetText("MsnDlg.target")); + } + else { + mission_time_target->SetText(""); + mission_time_target_label->SetText(""); + } + } + + + if (sit_button) { + sit_button->SetButtonState(plan_screen->IsMsnObjShown()); + sit_button->SetEnabled(true); + } + + if (pkg_button) { + pkg_button->SetButtonState(plan_screen->IsMsnPkgShown()); + pkg_button->SetEnabled(true); + } + + if (nav_button) { + nav_button->SetButtonState(plan_screen->IsNavShown()); + nav_button->SetEnabled(true); + } + + if (wep_button) { + wep_button->SetButtonState(plan_screen->IsMsnWepShown()); + wep_button->SetEnabled(true); + } + + bool mission_ok = true; + + if (!mission || !mission->IsOK()) { + mission_ok = false; + + if (sit_button) sit_button->SetEnabled(false); + if (pkg_button) pkg_button->SetEnabled(false); + if (nav_button) nav_button->SetEnabled(false); + if (wep_button) wep_button->SetEnabled(false); + } + else { + MissionElement* player_elem = mission->GetPlayer(); + + if (wep_button && player_elem) + wep_button->SetEnabled(player_elem->Loadouts().size() > 0); + } + + if (wep_button && NetLobby::GetInstance()) + wep_button->SetEnabled(false); + + commit->SetEnabled(mission_ok); + cancel->SetEnabled(true); +} + +// +--------------------------------------------------------------------+ + +int +MsnDlg::CalcTimeOnTarget() +{ + if (mission) { + MissionElement* element = mission->GetElements()[0]; + if (element) { + Point loc = element->Location(); + loc.SwapYZ(); // navpts use Z for altitude, element loc uses Y for altitude. + + int mission_time = mission->Start(); + + int i = 0; + ListIter navpt = element->NavList(); + while (++navpt) { + int action = navpt->Action(); + + double dist = Point(loc - navpt->Location()).length(); + + int etr = 0; + + if (navpt->Speed() > 0) + etr = (int) (dist / navpt->Speed()); + else + etr = (int) (dist / 500); + + mission_time += etr; + + loc = navpt->Location(); + i++; + + if (action >= Instruction::ESCORT) { // this is the target! + return mission_time; + } + } + } + } + + return 0; +} + +// +--------------------------------------------------------------------+ + +void +MsnDlg::OnTabButton(AWEvent* event) +{ + if (event->window == sit_button) { + plan_screen->ShowMsnObjDlg(); + } + + if (event->window == pkg_button) { + plan_screen->ShowMsnPkgDlg(); + } + + if (event->window == nav_button) { + plan_screen->ShowNavDlg(); + } + + if (event->window == wep_button) { + plan_screen->ShowMsnWepDlg(); + } +} + +// +--------------------------------------------------------------------+ + +void +MsnDlg::OnCommit(AWEvent* event) +{ + Starshatter* stars = Starshatter::GetInstance(); + + if (stars) { + Mouse::Show(false); + stars->SetGameMode(Starshatter::LOAD_MODE); + } + + else + Game::Panic("MsnDlg::OnCommit() - Game instance not found"); +} + +void +MsnDlg::OnCancel(AWEvent* event) +{ + Starshatter* stars = Starshatter::GetInstance(); + + if (stars) { + Mouse::Show(false); + + if (campaign && (campaign->IsDynamic() || campaign->IsTraining())) + stars->SetGameMode(Starshatter::CMPN_MODE); + else + stars->SetGameMode(Starshatter::MENU_MODE); + } + + else + Game::Panic("MsnDlg::OnCancel() - Game instance not found"); +} diff --git a/Stars45/MsnDlg.h b/Stars45/MsnDlg.h new file mode 100644 index 0000000..8af6544 --- /dev/null +++ b/Stars45/MsnDlg.h @@ -0,0 +1,73 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: MsnDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Mission Briefing Dialog Active Window class +*/ + +#ifndef MsnDlg_h +#define MsnDlg_h + +#include "Types.h" +#include "Bitmap.h" +#include "Button.h" +#include "Font.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class FormWindow; +class PlanScreen; +class Campaign; +class Mission; +class MissionInfo; + +// +--------------------------------------------------------------------+ + +class MsnDlg +{ +public: + MsnDlg(PlanScreen* mgr); + virtual ~MsnDlg(); + + void RegisterMsnControls(FormWindow* win); + void ShowMsnDlg(); + + // Operations: + virtual void OnCommit(AWEvent* event); + virtual void OnCancel(AWEvent* event); + virtual void OnTabButton(AWEvent* event); + +protected: + virtual int CalcTimeOnTarget(); + + PlanScreen* plan_screen; + Button* commit; + Button* cancel; + Button* sit_button; + Button* pkg_button; + Button* nav_button; + Button* wep_button; + + ActiveWindow* mission_name; + ActiveWindow* mission_system; + ActiveWindow* mission_sector; + ActiveWindow* mission_time_start; + ActiveWindow* mission_time_target; + ActiveWindow* mission_time_target_label; + + Campaign* campaign; + Mission* mission; + MissionInfo* info; + int pkg_index; +}; + +#endif MsnDlg_h + diff --git a/Stars45/MsnEditDlg.cpp b/Stars45/MsnEditDlg.cpp new file mode 100644 index 0000000..05f64c5 --- /dev/null +++ b/Stars45/MsnEditDlg.cpp @@ -0,0 +1,896 @@ +/* Project Starshatter 5.0 + Destroyer Studios LLC + Copyright © 1997-2007. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: MsnEditDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Mission Editor Dialog Active Window class +*/ + +#include "MemDebug.h" +#include "MsnEditDlg.h" +#include "MsnElemDlg.h" +#include "MsnEventDlg.h" +#include "NavDlg.h" +#include "MenuScreen.h" +#include "Campaign.h" +#include "Mission.h" +#include "MissionEvent.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "StarSystem.h" +#include "Galaxy.h" + +#include "Game.h" +#include "DataLoader.h" +#include "Button.h" +#include "ListBox.h" +#include "EditBox.h" +#include "Video.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "ParseUtil.h" +#include "FormatUtil.h" +#include "Random.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +DEF_MAP_CLIENT(MsnEditDlg, OnAccept); +DEF_MAP_CLIENT(MsnEditDlg, OnCancel); +DEF_MAP_CLIENT(MsnEditDlg, OnTabButton); +DEF_MAP_CLIENT(MsnEditDlg, OnSystemSelect); +DEF_MAP_CLIENT(MsnEditDlg, OnElemAdd); +DEF_MAP_CLIENT(MsnEditDlg, OnElemEdit); +DEF_MAP_CLIENT(MsnEditDlg, OnElemDel); +DEF_MAP_CLIENT(MsnEditDlg, OnElemSelect); +DEF_MAP_CLIENT(MsnEditDlg, OnElemInc); +DEF_MAP_CLIENT(MsnEditDlg, OnElemDec); +DEF_MAP_CLIENT(MsnEditDlg, OnEventAdd); +DEF_MAP_CLIENT(MsnEditDlg, OnEventEdit); +DEF_MAP_CLIENT(MsnEditDlg, OnEventDel); +DEF_MAP_CLIENT(MsnEditDlg, OnEventSelect); +DEF_MAP_CLIENT(MsnEditDlg, OnEventInc); +DEF_MAP_CLIENT(MsnEditDlg, OnEventDec); + +// +--------------------------------------------------------------------+ + +MsnEditDlg::MsnEditDlg(Screen* s, FormDef& def, MenuScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), manager(mgr), + btn_accept(0), btn_cancel(0), + btn_elem_add(0), btn_elem_edit(0), btn_elem_del(0), btn_elem_inc(0), btn_elem_dec(0), + btn_event_add(0), btn_event_edit(0), btn_event_del(0), btn_event_inc(0), btn_event_dec(0), + btn_sit(0), btn_pkg(0), btn_map(0), + txt_name(0), cmb_type(0), cmb_system(0), cmb_region(0), + txt_description(0), txt_situation(0), txt_objective(0), + lst_elem(0), lst_event(0), mission(0), mission_info(0), current_tab(0), + exit_latch(true) +{ + Init(def); +} + +MsnEditDlg::~MsnEditDlg() +{ +} + +// +--------------------------------------------------------------------+ + +void +MsnEditDlg::SetMission(Mission* m) +{ + mission = m; + current_tab = 0; +} + +void +MsnEditDlg::SetMissionInfo(MissionInfo* m) +{ + mission_info = m; + current_tab = 0; +} + +// +--------------------------------------------------------------------+ + +void +MsnEditDlg::RegisterControls() +{ + btn_accept = (Button*) FindControl( 1); + btn_cancel = (Button*) FindControl( 2); + btn_sit = (Button*) FindControl(301); + btn_pkg = (Button*) FindControl(302); + btn_map = (Button*) FindControl(303); + + btn_elem_add = (Button*) FindControl(501); + btn_elem_edit = (Button*) FindControl(505); + btn_elem_del = (Button*) FindControl(502); + btn_elem_inc = (Button*) FindControl(503); + btn_elem_dec = (Button*) FindControl(504); + + btn_event_add = (Button*) FindControl(511); + btn_event_edit = (Button*) FindControl(515); + btn_event_del = (Button*) FindControl(512); + btn_event_inc = (Button*) FindControl(513); + btn_event_dec = (Button*) FindControl(514); + + txt_name = (EditBox*) FindControl(201); + cmb_type = (ComboBox*) FindControl(202); + cmb_system = (ComboBox*) FindControl(203); + cmb_region = (ComboBox*) FindControl(204); + + txt_description= (EditBox*) FindControl(410); + txt_situation = (EditBox*) FindControl(411); + txt_objective = (EditBox*) FindControl(412); + + lst_elem = (ListBox*) FindControl(510); + lst_event = (ListBox*) FindControl(520); + + if (btn_accept) + REGISTER_CLIENT(EID_CLICK, btn_accept, MsnEditDlg, OnAccept); + + if (btn_cancel) + REGISTER_CLIENT(EID_CLICK, btn_cancel, MsnEditDlg, OnCancel); + + if (btn_elem_add) + REGISTER_CLIENT(EID_CLICK, btn_elem_add, MsnEditDlg, OnElemAdd); + + if (btn_elem_edit) + REGISTER_CLIENT(EID_CLICK, btn_elem_edit, MsnEditDlg, OnElemEdit); + + if (btn_elem_del) + REGISTER_CLIENT(EID_CLICK, btn_elem_del, MsnEditDlg, OnElemDel); + + if (btn_elem_inc) { + char up_arrow[2]; + up_arrow[0] = Font::ARROW_UP; + up_arrow[1] = 0; + btn_elem_inc->SetText(up_arrow); + btn_elem_inc->SetEnabled(false); + REGISTER_CLIENT(EID_CLICK, btn_elem_inc, MsnEditDlg, OnElemInc); + } + + if (btn_elem_dec) { + char dn_arrow[2]; + dn_arrow[0] = Font::ARROW_DOWN; + dn_arrow[1] = 0; + btn_elem_dec->SetText(dn_arrow); + btn_elem_dec->SetEnabled(false); + REGISTER_CLIENT(EID_CLICK, btn_elem_dec, MsnEditDlg, OnElemDec); + } + + if (btn_event_add) + REGISTER_CLIENT(EID_CLICK, btn_event_add, MsnEditDlg, OnEventAdd); + + if (btn_event_edit) + REGISTER_CLIENT(EID_CLICK, btn_event_edit, MsnEditDlg, OnEventEdit); + + if (btn_event_del) + REGISTER_CLIENT(EID_CLICK, btn_event_del, MsnEditDlg, OnEventDel); + + if (btn_event_inc) { + char up_arrow[2]; + up_arrow[0] = Font::ARROW_UP; + up_arrow[1] = 0; + btn_event_inc->SetText(up_arrow); + btn_event_inc->SetEnabled(false); + REGISTER_CLIENT(EID_CLICK, btn_event_inc, MsnEditDlg, OnEventInc); + } + + if (btn_event_dec) { + char dn_arrow[2]; + dn_arrow[0] = Font::ARROW_DOWN; + dn_arrow[1] = 0; + btn_event_dec->SetText(dn_arrow); + btn_event_dec->SetEnabled(false); + REGISTER_CLIENT(EID_CLICK, btn_event_dec, MsnEditDlg, OnEventDec); + } + + if (btn_sit) + REGISTER_CLIENT(EID_CLICK, btn_sit, MsnEditDlg, OnTabButton); + + if (btn_pkg) + REGISTER_CLIENT(EID_CLICK, btn_pkg, MsnEditDlg, OnTabButton); + + if (btn_map) + REGISTER_CLIENT(EID_CLICK, btn_map, MsnEditDlg, OnTabButton); + + if (cmb_system) + REGISTER_CLIENT(EID_SELECT, cmb_system, MsnEditDlg, OnSystemSelect); + + if (lst_elem) + REGISTER_CLIENT(EID_SELECT, lst_elem, MsnEditDlg, OnElemSelect); + + if (lst_event) + REGISTER_CLIENT(EID_SELECT, lst_event, MsnEditDlg, OnEventSelect); +} + +// +--------------------------------------------------------------------+ + +void +MsnEditDlg::Show() +{ + FormWindow::Show(); + + if (!txt_name || !cmb_type) return; + + txt_name->SetText(""); + + if (cmb_system) { + cmb_system->ClearItems(); + + Galaxy* galaxy = Galaxy::GetInstance(); + ListIter iter = galaxy->GetSystemList(); + while (++iter) { + cmb_system->AddItem(iter->Name()); + } + } + + if (mission) { + int i; + + txt_name->SetText(mission->Name()); + cmb_type->SetSelection(mission->Type()); + + StarSystem* sys = mission->GetStarSystem(); + if (sys && cmb_system && cmb_region) { + for (i = 0; i < cmb_system->NumItems(); i++) { + if (!strcmp(cmb_system->GetItem(i), sys->Name())) { + cmb_system->SetSelection(i); + break; + } + } + + cmb_region->ClearItems(); + int sel_rgn = 0; + + List regions; + regions.append(sys->AllRegions()); + regions.sort(); + + i = 0; + ListIter iter = regions; + while (++iter) { + OrbitalRegion* region = iter.value(); + cmb_region->AddItem(region->Name()); + + if (!strcmp(mission->GetRegion(), region->Name())) { + sel_rgn = i; + } + + i++; + } + + cmb_region->SetSelection(sel_rgn); + } + + if (txt_description && mission_info) + txt_description->SetText(mission_info->description); + + if (txt_situation) + txt_situation->SetText(mission->Situation()); + + if (txt_objective) + txt_objective->SetText(mission->Objective()); + + DrawPackages(); + } + + ShowTab(current_tab); + + exit_latch = true; +} + +// +--------------------------------------------------------------------+ + +void +MsnEditDlg::DrawPackages() +{ + bool elem_del = false; + bool elem_edit = false; + bool elem_inc = false; + bool elem_dec = false; + + bool event_del = false; + bool event_edit = false; + bool event_inc = false; + bool event_dec = false; + + if (mission) { + if (lst_elem) { + bool cleared = false; + int index = lst_elem->GetSelection(); + + if (lst_elem->NumItems() != mission->GetElements().size()) { + if (lst_elem->NumItems() < mission->GetElements().size()) + index = lst_elem->NumItems(); + + lst_elem->ClearItems(); + cleared = true; + } + + int i = 0; + ListIter elem = mission->GetElements(); + while (++elem) { + char txt[256]; + + sprintf(txt, "%d", elem->Identity()); + + if (cleared) { + lst_elem->AddItemWithData(txt, elem->ElementID()); + } + else { + lst_elem->SetItemText(i, txt); + lst_elem->SetItemData(i, elem->ElementID()); + } + + sprintf(txt, "%d", elem->GetIFF()); + lst_elem->SetItemText(i, 1, txt); + lst_elem->SetItemText(i, 2, elem->Name()); + lst_elem->SetItemText(i, 4, elem->RoleName()); + lst_elem->SetItemText(i, 5, elem->Region()); + + const ShipDesign* design = elem->GetDesign(); + + if (design) { + if (elem->Count() > 1) + sprintf(txt, "%d %s", elem->Count(), design->abrv); + else + sprintf(txt, "%s %s", design->abrv, design->name); + } + else { + sprintf(txt, Game::GetText("MsnDlg.undefined").data()); + } + + lst_elem->SetItemText(i, 3, txt); + + i++; + } + + int nitems = lst_elem->NumItems(); + if (nitems) { + if (index >= nitems) + index = nitems - 1; + + if (index >= 0) { + lst_elem->SetSelected(index); + elem_del = true; + elem_edit = true; + elem_inc = index > 0; + elem_dec = index < nitems-1; + } + } + } + + if (lst_event) { + bool cleared = false; + int index = lst_event->GetSelection(); + + if (lst_event->NumItems() != mission->GetEvents().size()) { + if (lst_event->NumItems() < mission->GetEvents().size()) + index = lst_event->NumItems(); + + lst_event->ClearItems(); + cleared = true; + } + + int i = 0; + ListIter event = mission->GetEvents(); + while (++event) { + char txt[256]; + + sprintf(txt, "%d", event->EventID()); + if (cleared) { + lst_event->AddItemWithData(txt, event->EventID()); + } + else { + lst_event->SetItemText(i, txt); + lst_event->SetItemData(i, event->EventID()); + } + + if (event->Time()) { + FormatTime(txt, event->Time()); + } + else if (event->Delay()) { + txt[0] = '+'; + txt[1] = ' '; + FormatTime(txt+2, event->Delay()); + } + else { + strcpy(txt, " "); + } + + lst_event->SetItemText(i, 1, txt); + lst_event->SetItemText(i, 2, event->EventName()); + lst_event->SetItemText(i, 3, event->EventShip()); + lst_event->SetItemText(i, 4, event->EventMessage()); + + i++; + } + + int nitems = lst_event->NumItems(); + if (nitems) { + if (index >= nitems) + index = nitems - 1; + + if (index >= 0) { + lst_event->SetSelected(index); + event_del = true; + event_edit = true; + event_inc = index > 0; + event_dec = index < nitems-1; + } + } + } + } + + if (btn_elem_del) btn_elem_del->SetEnabled(elem_del); + if (btn_elem_edit) btn_elem_edit->SetEnabled(elem_edit); + if (btn_elem_inc) btn_elem_inc->SetEnabled(elem_inc); + if (btn_elem_dec) btn_elem_dec->SetEnabled(elem_del); + + if (btn_event_del) btn_event_del->SetEnabled(event_del); + if (btn_event_edit) btn_event_edit->SetEnabled(event_edit); + if (btn_event_inc) btn_event_inc->SetEnabled(event_inc); + if (btn_event_dec) btn_event_dec->SetEnabled(event_dec); +} + +// +--------------------------------------------------------------------+ + +void +MsnEditDlg::ExecFrame() +{ + if (Keyboard::KeyDown(VK_RETURN)) { + if (!exit_latch && btn_accept && btn_accept->IsEnabled()) + OnAccept(0); + } + + else if (Keyboard::KeyDown(VK_ESCAPE)) { + if (!exit_latch) + OnCancel(0); + } + + else { + exit_latch = false; + } +} + +// +--------------------------------------------------------------------+ + +void +MsnEditDlg::ScrapeForm() +{ + if (mission) { + if (txt_name) { + mission->SetName(txt_name->GetText()); + } + + if (cmb_type) { + mission->SetType(cmb_type->GetSelectedIndex()); + + if (mission_info) + mission_info->type = cmb_type->GetSelectedIndex(); + } + + Galaxy* galaxy = Galaxy::GetInstance(); + StarSystem* system = 0; + + if (galaxy) + system = galaxy->GetSystem(cmb_system->GetSelectedItem()); + + if (cmb_system && system) { + mission->ClearSystemList(); + mission->SetStarSystem(system); + + if (mission_info) + mission_info->system = system->Name(); + } + + if (cmb_region) { + mission->SetRegion(cmb_region->GetSelectedItem()); + + if (mission_info) + mission_info->region = cmb_region->GetSelectedItem(); + } + + if (txt_description && mission_info) { + mission_info->description = txt_description->GetText(); + mission->SetDescription(txt_description->GetText()); + } + + if (txt_situation) + mission->SetSituation(txt_situation->GetText()); + + if (txt_objective) + mission->SetObjective(txt_objective->GetText()); + } +} + +// +--------------------------------------------------------------------+ + +void +MsnEditDlg::ShowTab(int tab) +{ + current_tab = tab; + + if (current_tab < 0 || current_tab > 2) + current_tab = 0; + + if (btn_sit) btn_sit->SetButtonState(tab == 0 ? 1 : 0); + if (btn_pkg) btn_pkg->SetButtonState(tab == 1 ? 1 : 0); + if (btn_map) btn_map->SetButtonState(tab == 2 ? 1 : 0); + + DWORD low = 400 + tab*100; + DWORD high = low + 100; + + ListIter iter = Controls(); + while (++iter) { + ActiveWindow* a = iter.value(); + + if (a->GetID() < 400) + a->Show(); + + else if (a->GetID() >= low && a->GetID() < high) + a->Show(); + + else + a->Hide(); + } + + if (tab == 2) { + NavDlg* navdlg = manager->GetNavDlg(); + + if (navdlg) { + navdlg->SetMission(mission); + navdlg->SetEditorMode(true); + } + + manager->ShowNavDlg(); + } + else { + manager->HideNavDlg(); + } +} + +// +--------------------------------------------------------------------+ + +void +MsnEditDlg::OnTabButton(AWEvent* event) +{ + if (!event) return; + + if (event->window == btn_sit) + ShowTab(0); + + else if (event->window == btn_pkg) + ShowTab(1); + + else if (event->window == btn_map) + ShowTab(2); +} + +void +MsnEditDlg::OnSystemSelect(AWEvent* event) +{ + StarSystem* sys = 0; + + if (cmb_system) { + const char* name = cmb_system->GetSelectedItem(); + + Galaxy* galaxy = Galaxy::GetInstance(); + ListIter iter = galaxy->GetSystemList(); + while (++iter) { + StarSystem* s = iter.value(); + + if (!strcmp(s->Name(), name)) { + sys = s; + break; + } + } + } + + if (sys && cmb_region) { + cmb_region->ClearItems(); + + List regions; + regions.append(sys->AllRegions()); + regions.sort(); + + ListIter iter = regions; + while (++iter) { + OrbitalRegion* region = iter.value(); + cmb_region->AddItem(region->Name()); + } + } + + ScrapeForm(); + + NavDlg* navdlg = manager->GetNavDlg(); + + if (navdlg) + navdlg->SetMission(mission); +} + +// +--------------------------------------------------------------------+ + +void +MsnEditDlg::OnElemSelect(AWEvent* event) +{ + static DWORD click_time = 0; + + if (lst_elem && mission) { + int selection = lst_elem->GetSelection(); + + if (btn_elem_edit) + btn_elem_edit->SetEnabled(selection >= 0); + + if (btn_elem_del) + btn_elem_del->SetEnabled(selection >= 0); + + if (btn_elem_inc) + btn_elem_inc->SetEnabled(selection > 0); + + if (btn_elem_dec) + btn_elem_dec->SetEnabled(selection >= 0 && selection < lst_elem->NumItems() - 1); + + // double-click: + if (Game::RealTime() - click_time < 350) { + if (lst_elem->GetSelCount() == 1) { + int index = lst_elem->GetSelection(); + MissionElement* elem = mission->GetElements().at(index); + MsnElemDlg* msn_elem_dlg = manager->GetMsnElemDlg(); + + if (elem && msn_elem_dlg) { + ScrapeForm(); + msn_elem_dlg->SetMission(mission); + msn_elem_dlg->SetMissionElement(elem); + manager->ShowMsnElemDlg(); + } + } + } + } + + click_time = Game::RealTime(); +} + +void +MsnEditDlg::OnElemInc(AWEvent* event) +{ + int index = lst_elem->GetSelection(); + mission->IncreaseElemPriority(index--); + + DrawPackages(); + lst_elem->SetSelected(index); + btn_elem_edit->SetEnabled(true); + btn_elem_del->SetEnabled(true); + btn_elem_inc->SetEnabled(index > 0); + btn_elem_dec->SetEnabled(index >= 0 && index < lst_elem->NumItems()-1); +} + +void +MsnEditDlg::OnElemDec(AWEvent* event) +{ + int index = lst_elem->GetSelection(); + mission->DecreaseElemPriority(index++); + + DrawPackages(); + lst_elem->SetSelected(index); + btn_elem_edit->SetEnabled(true); + btn_elem_del->SetEnabled(true); + btn_elem_inc->SetEnabled(index > 0); + btn_elem_dec->SetEnabled(index >= 0 && index < lst_elem->NumItems()-1); +} + + +// +--------------------------------------------------------------------+ + +void +MsnEditDlg::OnElemAdd(AWEvent* event) +{ + if (lst_elem && mission) { + List& elements = mission->GetElements(); + MissionElement* elem = new(__FILE__,__LINE__) MissionElement; + + if (elements.size() > 0) + elem->SetLocation(RandomPoint()); + + if (cmb_region) + elem->SetRegion(cmb_region->GetSelectedItem()); + + elements.append(elem); + DrawPackages(); + + MsnElemDlg* msn_elem_dlg = manager->GetMsnElemDlg(); + + if (msn_elem_dlg) { + ScrapeForm(); + msn_elem_dlg->SetMission(mission); + msn_elem_dlg->SetMissionElement(elem); + manager->ShowMsnElemDlg(); + } + } +} + +void +MsnEditDlg::OnElemDel(AWEvent* event) +{ + if (lst_elem && mission) { + List& elements = mission->GetElements(); + delete elements.removeIndex(lst_elem->GetSelection()); + DrawPackages(); + } +} + +void +MsnEditDlg::OnElemEdit(AWEvent* event) +{ + if (lst_elem && mission && lst_elem->GetSelCount() == 1) { + int index = lst_elem->GetSelection(); + MissionElement* elem = mission->GetElements().at(index); + MsnElemDlg* msn_elem_dlg = manager->GetMsnElemDlg(); + + if (elem && msn_elem_dlg) { + ScrapeForm(); + msn_elem_dlg->SetMission(mission); + msn_elem_dlg->SetMissionElement(elem); + manager->ShowMsnElemDlg(); + } + } +} + + +// +--------------------------------------------------------------------+ + +void +MsnEditDlg::OnEventSelect(AWEvent* event) +{ + static DWORD click_time = 0; + + if (lst_event && mission) { + int selection = lst_event->GetSelection(); + + if (btn_event_edit) + btn_event_edit->SetEnabled(selection >= 0); + + if (btn_event_del) + btn_event_del->SetEnabled(selection >= 0); + + if (btn_event_inc) + btn_event_inc->SetEnabled(selection > 0); + + if (btn_event_dec) + btn_event_dec->SetEnabled(selection >= 0 && selection < lst_event->NumItems() - 1); + + // double-click: + if (Game::RealTime() - click_time < 350) { + if (lst_event->GetSelCount() == 1) { + int index = lst_event->GetSelection(); + MissionEvent* event = mission->GetEvents().at(index); + MsnEventDlg* msn_event_dlg = manager->GetMsnEventDlg(); + + if (event && msn_event_dlg) { + ScrapeForm(); + msn_event_dlg->SetMission(mission); + msn_event_dlg->SetMissionEvent(event); + manager->ShowMsnEventDlg(); + } + } + } + } + + click_time = Game::RealTime(); +} + +void +MsnEditDlg::OnEventInc(AWEvent* event) +{ + int index = lst_event->GetSelection(); + mission->IncreaseEventPriority(index--); + + DrawPackages(); + lst_event->SetSelected(index); + btn_event_edit->SetEnabled(true); + btn_event_del->SetEnabled(true); + btn_event_inc->SetEnabled(index > 0); + btn_event_dec->SetEnabled(index >= 0 && index < lst_event->NumItems()-1); +} + +void +MsnEditDlg::OnEventDec(AWEvent* event) +{ + int index = lst_event->GetSelection(); + mission->DecreaseEventPriority(index++); + + DrawPackages(); + lst_event->SetSelected(index); + btn_event_edit->SetEnabled(true); + btn_event_del->SetEnabled(true); + btn_event_inc->SetEnabled(index > 0); + btn_event_dec->SetEnabled(index >= 0 && index < lst_event->NumItems()-1); +} + + +// +--------------------------------------------------------------------+ + +void +MsnEditDlg::OnEventAdd(AWEvent* event) +{ + if (lst_event && mission) { + List& events = mission->GetEvents(); + MissionEvent* event = new(__FILE__,__LINE__) MissionEvent; + + int id = 1; + for (int i = 0; i < events.size(); i++) { + MissionEvent* e = events[i]; + if (e->EventID() >= id) + id = e->EventID() + 1; + } + + event->id = id; + + events.append(event); + DrawPackages(); + + MsnEventDlg* msn_event_dlg = manager->GetMsnEventDlg(); + + if (msn_event_dlg) { + ScrapeForm(); + msn_event_dlg->SetMission(mission); + msn_event_dlg->SetMissionEvent(event); + manager->ShowMsnEventDlg(); + } + } +} + +void +MsnEditDlg::OnEventDel(AWEvent* event) +{ + if (lst_event && mission) { + List& events = mission->GetEvents(); + delete events.removeIndex(lst_event->GetSelection()); + DrawPackages(); + } +} + +void +MsnEditDlg::OnEventEdit(AWEvent* event) +{ + if (lst_event && mission && lst_event->GetSelCount() == 1) { + int index = lst_event->GetSelection(); + MissionEvent* event = mission->GetEvents().at(index); + MsnEventDlg* msn_event_dlg = manager->GetMsnEventDlg(); + + if (event && msn_event_dlg) { + ScrapeForm(); + msn_event_dlg->SetMission(mission); + msn_event_dlg->SetMissionEvent(event); + manager->ShowMsnEventDlg(); + } + } +} + +// +--------------------------------------------------------------------+ + +void +MsnEditDlg::OnAccept(AWEvent* event) +{ + if (mission) { + ScrapeForm(); + mission_info->name = mission->Name(); + mission->Save(); + } + + manager->ShowMsnSelectDlg(); +} + +void +MsnEditDlg::OnCancel(AWEvent* event) +{ + if (mission) + mission->Load(); + + manager->ShowMsnSelectDlg(); +} diff --git a/Stars45/MsnEditDlg.h b/Stars45/MsnEditDlg.h new file mode 100644 index 0000000..03d1b33 --- /dev/null +++ b/Stars45/MsnEditDlg.h @@ -0,0 +1,116 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: MsnEditDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Mission Editor Dialog Active Window class +*/ + +#ifndef MsnEditDlg_h +#define MsnEditDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "Bitmap.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Font.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class MenuScreen; +class Campaign; +class Mission; +class MissionInfo; + +// +--------------------------------------------------------------------+ + +class MsnEditDlg : public FormWindow +{ +public: + friend class MsnEditNavDlg; + + MsnEditDlg(Screen* s, FormDef& def, MenuScreen* mgr); + virtual ~MsnEditDlg(); + + virtual void RegisterControls(); + virtual void Show(); + virtual void ExecFrame(); + virtual void ScrapeForm(); + + // Operations: + virtual void OnAccept(AWEvent* event); + virtual void OnCancel(AWEvent* event); + virtual void OnTabButton(AWEvent* event); + virtual void OnSystemSelect(AWEvent* event); + virtual void OnElemAdd(AWEvent* event); + virtual void OnElemEdit(AWEvent* event); + virtual void OnElemDel(AWEvent* event); + virtual void OnElemSelect(AWEvent* event); + virtual void OnElemInc(AWEvent* event); + virtual void OnElemDec(AWEvent* event); + virtual void OnEventAdd(AWEvent* event); + virtual void OnEventEdit(AWEvent* event); + virtual void OnEventDel(AWEvent* event); + virtual void OnEventSelect(AWEvent* event); + virtual void OnEventInc(AWEvent* event); + virtual void OnEventDec(AWEvent* event); + + virtual Mission* GetMission() const { return mission; } + virtual void SetMission(Mission* m); + virtual void SetMissionInfo(MissionInfo* m); + +protected: + virtual void DrawPackages(); + virtual void ShowTab(int tab); + + MenuScreen* manager; + + Button* btn_accept; + Button* btn_cancel; + + Button* btn_elem_add; + Button* btn_elem_edit; + Button* btn_elem_del; + Button* btn_elem_inc; + Button* btn_elem_dec; + + Button* btn_event_add; + Button* btn_event_edit; + Button* btn_event_del; + Button* btn_event_inc; + Button* btn_event_dec; + + Button* btn_sit; + Button* btn_pkg; + Button* btn_map; + + EditBox* txt_name; + ComboBox* cmb_type; + ComboBox* cmb_system; + ComboBox* cmb_region; + + EditBox* txt_description; + EditBox* txt_situation; + EditBox* txt_objective; + + ListBox* lst_elem; + ListBox* lst_event; + + Mission* mission; + MissionInfo* mission_info; + + int current_tab; + bool exit_latch; +}; + +#endif MsnEditDlg_h + diff --git a/Stars45/MsnEditNavDlg.cpp b/Stars45/MsnEditNavDlg.cpp new file mode 100644 index 0000000..88cf103 --- /dev/null +++ b/Stars45/MsnEditNavDlg.cpp @@ -0,0 +1,300 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: MsnEditNavDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Mission Briefing Dialog Active Window class +*/ + +#include "MemDebug.h" +#include "MsnEditNavDlg.h" +#include "MsnEditDlg.h" +#include "MenuScreen.h" +#include "Campaign.h" +#include "Galaxy.h" +#include "Instruction.h" +#include "Mission.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "StarSystem.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: +DEF_MAP_CLIENT(MsnEditNavDlg, OnCommit); +DEF_MAP_CLIENT(MsnEditNavDlg, OnCancel); +DEF_MAP_CLIENT(MsnEditNavDlg, OnTabButton); +DEF_MAP_CLIENT(MsnEditNavDlg, OnSystemSelect); + +// +--------------------------------------------------------------------+ + +MsnEditNavDlg::MsnEditNavDlg(Screen* s, FormDef& def, MenuScreen* mgr) + : NavDlg(s, def, mgr), menu_screen(mgr), mission_info(0), + btn_accept(0), btn_cancel(0), btn_sit(0), btn_pkg(0), btn_map(0) +{ + RegisterControls(); +} + +MsnEditNavDlg::~MsnEditNavDlg() +{ +} + +// +--------------------------------------------------------------------+ + +void +MsnEditNavDlg::RegisterControls() +{ + btn_accept = (Button*) FindControl( 1); + btn_cancel = (Button*) FindControl( 2); + btn_sit = (Button*) FindControl(301); + btn_pkg = (Button*) FindControl(302); + btn_map = (Button*) FindControl(303); + + txt_name = (EditBox*) FindControl(201); + cmb_type = (ComboBox*) FindControl(202); + cmb_system = (ComboBox*) FindControl(203); + cmb_region = (ComboBox*) FindControl(204); + + if (btn_accept) + REGISTER_CLIENT(EID_CLICK, btn_accept, MsnEditNavDlg, OnCommit); + + if (btn_cancel) + REGISTER_CLIENT(EID_CLICK, btn_cancel, MsnEditNavDlg, OnCancel); + + if (btn_sit) + REGISTER_CLIENT(EID_CLICK, btn_sit, MsnEditNavDlg, OnTabButton); + + if (btn_pkg) + REGISTER_CLIENT(EID_CLICK, btn_pkg, MsnEditNavDlg, OnTabButton); + + if (btn_map) + REGISTER_CLIENT(EID_CLICK, btn_map, MsnEditNavDlg, OnTabButton); + + if (cmb_system) + REGISTER_CLIENT(EID_SELECT, cmb_system, MsnEditNavDlg, OnSystemSelect); +} + +// +--------------------------------------------------------------------+ + +void +MsnEditNavDlg::Show() +{ + bool need_tab_update = !shown; + + NavDlg::Show(); + + if (txt_name && cmb_type) { + txt_name->SetText(""); + + if (cmb_system) { + cmb_system->ClearItems(); + + Galaxy* galaxy = Galaxy::GetInstance(); + ListIter iter = galaxy->GetSystemList(); + while (++iter) { + cmb_system->AddItem(iter->Name()); + } + } + + if (mission) { + int i; + + txt_name->SetText(mission->Name()); + cmb_type->SetSelection(mission->Type()); + + StarSystem* sys = mission->GetStarSystem(); + if (sys && cmb_system && cmb_region) { + for (i = 0; i < cmb_system->NumItems(); i++) { + if (!strcmp(cmb_system->GetItem(i), sys->Name())) { + cmb_system->SetSelection(i); + break; + } + } + + cmb_region->ClearItems(); + int sel_rgn = 0; + + List regions; + regions.append(sys->AllRegions()); + regions.sort(); + + i = 0; + ListIter iter = regions; + while (++iter) { + OrbitalRegion* region = iter.value(); + cmb_region->AddItem(region->Name()); + + if (!strcmp(mission->GetRegion(), region->Name())) { + sel_rgn = i; + } + + i++; + } + + cmb_region->SetSelection(sel_rgn); + } + } + } + + if (need_tab_update) { + ShowTab(2); + } + + exit_latch = true; +} + +void +MsnEditNavDlg::SetMissionInfo(MissionInfo* m) +{ + mission_info = m; +} + +// +--------------------------------------------------------------------+ + +void +MsnEditNavDlg::ScrapeForm() +{ + if (mission) { + if (txt_name) { + mission->SetName(txt_name->GetText()); + } + + if (cmb_type) { + mission->SetType(cmb_type->GetSelectedIndex()); + + if (mission_info) + mission_info->type = cmb_type->GetSelectedIndex(); + } + + Galaxy* galaxy = Galaxy::GetInstance(); + StarSystem* system = 0; + + if (galaxy) + system = galaxy->GetSystem(cmb_system->GetSelectedItem()); + + if (cmb_system && system) { + mission->ClearSystemList(); + mission->SetStarSystem(system); + + if (mission_info) + mission_info->system = system->Name(); + } + + if (cmb_region) { + mission->SetRegion(cmb_region->GetSelectedItem()); + + if (mission_info) + mission_info->region = cmb_region->GetSelectedItem(); + } + + SetSystem(system); + } +} + +// +--------------------------------------------------------------------+ + +void +MsnEditNavDlg::ShowTab(int tab) +{ + if (tab < 0 || tab > 2) + tab = 0; + + if (btn_sit) btn_sit->SetButtonState(tab == 0 ? 1 : 0); + if (btn_pkg) btn_pkg->SetButtonState(tab == 1 ? 1 : 0); + if (btn_map) btn_map->SetButtonState(tab == 2 ? 1 : 0); + + if (tab != 2) { + MsnEditDlg* msnEditDlg = menu_screen->GetMsnEditDlg(); + + if (msnEditDlg) + msnEditDlg->ShowTab(tab); + + menu_screen->ShowMsnEditDlg(); + } +} + +// +--------------------------------------------------------------------+ + +void +MsnEditNavDlg::OnTabButton(AWEvent* event) +{ + if (!event) return; + + if (event->window == btn_sit) + ShowTab(0); + + else if (event->window == btn_pkg) + ShowTab(1); + + else if (event->window == btn_map) + ShowTab(2); +} + +void +MsnEditNavDlg::OnSystemSelect(AWEvent* event) +{ + StarSystem* sys = 0; + + if (cmb_system) { + const char* name = cmb_system->GetSelectedItem(); + + Galaxy* galaxy = Galaxy::GetInstance(); + ListIter iter = galaxy->GetSystemList(); + while (++iter) { + StarSystem* s = iter.value(); + + if (!strcmp(s->Name(), name)) { + sys = s; + break; + } + } + } + + if (sys && cmb_region) { + cmb_region->ClearItems(); + + List regions; + regions.append(sys->AllRegions()); + regions.sort(); + + ListIter iter = regions; + while (++iter) { + OrbitalRegion* region = iter.value(); + cmb_region->AddItem(region->Name()); + } + } + + ScrapeForm(); +} + + +// +--------------------------------------------------------------------+ + +void +MsnEditNavDlg::OnCommit(AWEvent* event) +{ + if (mission) { + ScrapeForm(); + + if (mission_info) + mission_info->name = mission->Name(); + + mission->Save(); + } + + menu_screen->ShowMsnSelectDlg(); +} + +void +MsnEditNavDlg::OnCancel(AWEvent* event) +{ + if (mission) + mission->Load(); + + menu_screen->ShowMsnSelectDlg(); +} diff --git a/Stars45/MsnEditNavDlg.h b/Stars45/MsnEditNavDlg.h new file mode 100644 index 0000000..d00d3ce --- /dev/null +++ b/Stars45/MsnEditNavDlg.h @@ -0,0 +1,78 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: MsnEditNavDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Mission Briefing Dialog Active Window class +*/ + +#ifndef MsnEditNavDlg_h +#define MsnEditNavDlg_h + +#include "Types.h" +#include "NavDlg.h" +#include "Bitmap.h" +#include "Button.h" +#include "ComboBox.h" +#include "EditBox.h" +#include "ListBox.h" +#include "Font.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class MenuScreen; +class Campaign; +class Mission; +class MissionInfo; + +// +--------------------------------------------------------------------+ + +class MsnEditNavDlg : public NavDlg +{ +public: + friend class MsnEditDlg; + + MsnEditNavDlg(Screen* s, FormDef& def, MenuScreen* mgr); + virtual ~MsnEditNavDlg(); + + virtual void RegisterControls(); + virtual void Show(); + virtual void SetMissionInfo(MissionInfo* m); + virtual void ScrapeForm(); + + // Operations: + virtual void OnCommit(AWEvent* event); + virtual void OnCancel(AWEvent* event); + virtual void OnTabButton(AWEvent* event); + virtual void OnSystemSelect(AWEvent* event); + +protected: + virtual void ShowTab(int tab); + + MenuScreen* menu_screen; + + Button* btn_accept; + Button* btn_cancel; + + Button* btn_sit; + Button* btn_pkg; + Button* btn_map; + + EditBox* txt_name; + ComboBox* cmb_type; + ComboBox* cmb_system; + ComboBox* cmb_region; + MissionInfo* mission_info; + + bool exit_latch; +}; + +#endif MsnEditNavDlg_h + diff --git a/Stars45/MsnElemDlg.cpp b/Stars45/MsnElemDlg.cpp new file mode 100644 index 0000000..5d44bbf --- /dev/null +++ b/Stars45/MsnElemDlg.cpp @@ -0,0 +1,805 @@ +/* Project Starshatter 5.0 + Destroyer Studios LLC + Copyright © 1997-2007. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: MsnElemDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Mod Config Dialog Active Window class +*/ + +#include "MemDebug.h" +#include "MsnElemDlg.h" +#include "MsnEditDlg.h" +#include "MenuScreen.h" +#include "Campaign.h" +#include "Mission.h" +#include "Instruction.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "StarSystem.h" +#include "Galaxy.h" + +#include "Game.h" +#include "DataLoader.h" +#include "Button.h" +#include "ListBox.h" +#include "EditBox.h" +#include "Video.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "ParseUtil.h" +#include "Skin.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +DEF_MAP_CLIENT(MsnElemDlg, OnAccept); +DEF_MAP_CLIENT(MsnElemDlg, OnCancel); +DEF_MAP_CLIENT(MsnElemDlg, OnClassSelect); +DEF_MAP_CLIENT(MsnElemDlg, OnDesignSelect); +DEF_MAP_CLIENT(MsnElemDlg, OnObjectiveSelect); +DEF_MAP_CLIENT(MsnElemDlg, OnIFFChange); + +// +--------------------------------------------------------------------+ + +MsnElemDlg::MsnElemDlg(Screen* s, FormDef& def, MenuScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), manager(mgr), + btn_accept(0), btn_cancel(0), edt_name(0), cmb_class(0), cmb_design(0), + edt_size(0), edt_iff(0), cmb_role(0), cmb_region(0), cmb_skin(0), + edt_loc_x(0), edt_loc_y(0), edt_loc_z(0), cmb_heading(0), edt_hold_time(0), + btn_player(0), btn_playable(0), btn_alert(0), btn_command_ai(0), + edt_respawns(0), cmb_commander(0), cmb_carrier(0), cmb_squadron(0), + cmb_intel(0), cmb_loadout(0), cmb_objective(0), cmb_target(0), + mission(0), elem(0), exit_latch(true) +{ + Init(def); +} + +MsnElemDlg::~MsnElemDlg() +{ +} + +// +--------------------------------------------------------------------+ + +void +MsnElemDlg::RegisterControls() +{ + btn_accept = (Button*) FindControl( 1); + if (btn_accept) + REGISTER_CLIENT(EID_CLICK, btn_accept, MsnElemDlg, OnAccept); + + btn_cancel = (Button*) FindControl( 2); + if (btn_accept) + REGISTER_CLIENT(EID_CLICK, btn_cancel, MsnElemDlg, OnCancel); + + edt_name = (EditBox*) FindControl(201); + cmb_class = (ComboBox*) FindControl(202); + cmb_design = (ComboBox*) FindControl(203); + cmb_skin = (ComboBox*) FindControl(213); + edt_size = (EditBox*) FindControl(204); + edt_iff = (EditBox*) FindControl(205); + cmb_role = (ComboBox*) FindControl(206); + cmb_region = (ComboBox*) FindControl(207); + edt_loc_x = (EditBox*) FindControl(208); + edt_loc_y = (EditBox*) FindControl(209); + edt_loc_z = (EditBox*) FindControl(210); + cmb_heading = (ComboBox*) FindControl(211); + edt_hold_time = (EditBox*) FindControl(212); + + btn_player = (Button*) FindControl(221); + btn_alert = (Button*) FindControl(222); + btn_playable = (Button*) FindControl(223); + btn_command_ai = (Button*) FindControl(224); + edt_respawns = (EditBox*) FindControl(225); + cmb_commander = (ComboBox*) FindControl(226); + cmb_carrier = (ComboBox*) FindControl(227); + cmb_squadron = (ComboBox*) FindControl(228); + cmb_intel = (ComboBox*) FindControl(229); + cmb_loadout = (ComboBox*) FindControl(230); + cmb_objective = (ComboBox*) FindControl(231); + cmb_target = (ComboBox*) FindControl(232); + + if (cmb_class) + REGISTER_CLIENT(EID_SELECT, cmb_class, MsnElemDlg, OnClassSelect); + + if (cmb_design) + REGISTER_CLIENT(EID_SELECT, cmb_design, MsnElemDlg, OnDesignSelect); + + if (cmb_objective) + REGISTER_CLIENT(EID_SELECT, cmb_objective, MsnElemDlg, OnObjectiveSelect); + + if (edt_iff) + REGISTER_CLIENT(EID_KILL_FOCUS, edt_iff, MsnElemDlg, OnIFFChange); +} + +// +--------------------------------------------------------------------+ + +void +MsnElemDlg::Show() +{ + FormWindow::Show(); + + if (!elem) return; + + int current_class = 0; + + if (cmb_class) { + cmb_class->ClearItems(); + + cmb_class->AddItem(Ship::ClassName(Ship::DRONE)); + cmb_class->AddItem(Ship::ClassName(Ship::FIGHTER)); + cmb_class->AddItem(Ship::ClassName(Ship::ATTACK)); + + cmb_class->AddItem(Ship::ClassName(Ship::LCA)); + cmb_class->AddItem(Ship::ClassName(Ship::COURIER)); + cmb_class->AddItem(Ship::ClassName(Ship::CARGO)); + cmb_class->AddItem(Ship::ClassName(Ship::CORVETTE)); + cmb_class->AddItem(Ship::ClassName(Ship::FREIGHTER)); + cmb_class->AddItem(Ship::ClassName(Ship::FRIGATE)); + cmb_class->AddItem(Ship::ClassName(Ship::DESTROYER)); + cmb_class->AddItem(Ship::ClassName(Ship::CRUISER)); + cmb_class->AddItem(Ship::ClassName(Ship::BATTLESHIP)); + cmb_class->AddItem(Ship::ClassName(Ship::CARRIER)); + cmb_class->AddItem(Ship::ClassName(Ship::DREADNAUGHT)); + cmb_class->AddItem(Ship::ClassName(Ship::STATION)); + cmb_class->AddItem(Ship::ClassName(Ship::FARCASTER)); + + cmb_class->AddItem(Ship::ClassName(Ship::MINE)); + cmb_class->AddItem(Ship::ClassName(Ship::COMSAT)); + cmb_class->AddItem(Ship::ClassName(Ship::DEFSAT)); + cmb_class->AddItem(Ship::ClassName(Ship::SWACS)); + + cmb_class->AddItem(Ship::ClassName(Ship::BUILDING)); + cmb_class->AddItem(Ship::ClassName(Ship::FACTORY)); + cmb_class->AddItem(Ship::ClassName(Ship::SAM)); + cmb_class->AddItem(Ship::ClassName(Ship::EWR)); + cmb_class->AddItem(Ship::ClassName(Ship::C3I)); + cmb_class->AddItem(Ship::ClassName(Ship::STARBASE)); + + const ShipDesign* design = elem->GetDesign(); + + for (int i = 0; i < cmb_class->NumItems(); i++) { + const char* cname = cmb_class->GetItem(i); + int classid = Ship::ClassForName(cname); + + if (design && classid == design->type) { + cmb_class->SetSelection(i); + current_class = classid; + break; + } + } + } + + if (cmb_design) { + OnClassSelect(0); + OnDesignSelect(0); + } + + if (cmb_role) { + cmb_role->ClearItems(); + + for (int i = Mission::PATROL; i <= Mission::OTHER; i++) { + cmb_role->AddItem(Mission::RoleName(i)); + + if (i == 0) + cmb_role->SetSelection(0); + + else if (elem->MissionRole() == i) + cmb_role->SetSelection(cmb_role->NumItems()-1); + } + } + + if (cmb_region) { + cmb_region->ClearItems(); + + if (mission) { + StarSystem* sys = mission->GetStarSystem(); + if (sys) { + List regions; + regions.append(sys->AllRegions()); + regions.sort(); + + ListIter iter = regions; + while (++iter) { + OrbitalRegion* region = iter.value(); + cmb_region->AddItem(region->Name()); + + if (!strcmp(elem->Region(), region->Name())) + cmb_region->SetSelection(cmb_region->NumItems()-1); + } + } + } + } + + char buf[64]; + + if (edt_name) edt_name->SetText(elem->Name()); + + sprintf(buf, "%d", elem->Count()); + if (edt_size) edt_size->SetText(buf); + + sprintf(buf, "%d", elem->GetIFF()); + if (edt_iff) edt_iff->SetText(buf); + + sprintf(buf, "%d", (int) elem->Location().x / 1000); + if (edt_loc_x) edt_loc_x->SetText(buf); + + sprintf(buf, "%d", (int) elem->Location().y / 1000); + if (edt_loc_y) edt_loc_y->SetText(buf); + + sprintf(buf, "%d", (int) elem->Location().z / 1000); + if (edt_loc_z) edt_loc_z->SetText(buf); + + sprintf(buf, "%d", elem->RespawnCount()); + if (edt_respawns) edt_respawns->SetText(buf); + + sprintf(buf, "%d", elem->HoldTime()); + if (edt_hold_time) edt_hold_time->SetText(buf); + + if (btn_player) btn_player->SetButtonState(elem->Player() > 0 ? 1 : 0); + if (btn_playable) btn_playable->SetButtonState(elem->IsPlayable() ? 1 : 0); + if (btn_alert) btn_alert->SetButtonState(elem->IsAlert() ? 1 : 0); + if (btn_command_ai) btn_command_ai->SetButtonState(elem->CommandAI()); + + UpdateTeamInfo(); + + if (cmb_intel) { + cmb_intel->ClearItems(); + + for (int i = Intel::RESERVE; i < Intel::TRACKED; i++) { + cmb_intel->AddItem(Intel::NameFromIntel(i)); + + if (i == 0) + cmb_intel->SetSelection(0); + + else if (elem->IntelLevel() == i) + cmb_intel->SetSelection(cmb_intel->NumItems()-1); + } + } + + Instruction* instr = 0; + if (elem->Objectives().size() > 0) + instr = elem->Objectives().at(0); + + if (cmb_objective) { + cmb_objective->ClearItems(); + cmb_objective->AddItem(""); + cmb_objective->SetSelection(0); + + for (int i = 0; i < Instruction::NUM_ACTIONS; i++) { + cmb_objective->AddItem(Instruction::ActionName(i)); + + if (instr && instr->Action() == i) + cmb_objective->SetSelection(i+1); + } + } + + if (cmb_target) { + OnObjectiveSelect(0); + } + + if (cmb_heading) { + double heading = elem->Heading(); + + while (heading > 2*PI) + heading -= 2*PI; + + while (heading < 0) + heading += 2*PI; + + if (heading >= PI/4 && heading < 3*PI/4) + cmb_heading->SetSelection(1); + + else if (heading >= 3*PI/4 && heading < 5*PI/4) + cmb_heading->SetSelection(2); + + else if (heading >= 5*PI/4 && heading < 7*PI/4) + cmb_heading->SetSelection(3); + + else + cmb_heading->SetSelection(0); + } + + exit_latch = true; +} + +// +--------------------------------------------------------------------+ + +void +MsnElemDlg::ExecFrame() +{ + if (Keyboard::KeyDown(VK_RETURN)) { + if (!exit_latch && btn_accept && btn_accept->IsEnabled()) + OnAccept(0); + } + + else if (Keyboard::KeyDown(VK_ESCAPE)) { + if (!exit_latch) + OnCancel(0); + } + + else { + exit_latch = false; + } +} + +// +--------------------------------------------------------------------+ + +void +MsnElemDlg::SetMission(Mission* m) +{ + mission = m; +} + +void +MsnElemDlg::SetMissionElement(MissionElement* e) +{ + elem = e; +} + +// +--------------------------------------------------------------------+ + +void +MsnElemDlg::UpdateTeamInfo() +{ + if (cmb_commander) { + cmb_commander->ClearItems(); + cmb_commander->AddItem(""); + cmb_commander->SetSelection(0); + + if (mission && elem) { + ListIter iter = mission->GetElements(); + while (++iter) { + MissionElement* e = iter.value(); + + if (CanCommand(e, elem)) { + cmb_commander->AddItem(e->Name()); + + if (elem->Commander() == e->Name()) + cmb_commander->SetSelection(cmb_commander->NumItems()-1); + } + } + } + } + + if (cmb_squadron) { + cmb_squadron->ClearItems(); + cmb_squadron->AddItem(""); + cmb_squadron->SetSelection(0); + + if (mission && elem) { + ListIter iter = mission->GetElements(); + while (++iter) { + MissionElement* e = iter.value(); + + if (e->GetIFF() == elem->GetIFF() && e != elem && e->IsSquadron()) { + cmb_squadron->AddItem(e->Name()); + + if (elem->Squadron() == e->Name()) + cmb_squadron->SetSelection(cmb_squadron->NumItems()-1); + } + } + } + } + + if (cmb_carrier) { + cmb_carrier->ClearItems(); + cmb_carrier->AddItem(""); + cmb_carrier->SetSelection(0); + + if (mission && elem) { + ListIter iter = mission->GetElements(); + while (++iter) { + MissionElement* e = iter.value(); + + if (e->GetIFF() == elem->GetIFF() && e != elem && e->GetDesign() && e->GetDesign()->flight_decks.size()) { + cmb_carrier->AddItem(e->Name()); + + if (elem->Carrier() == e->Name()) + cmb_carrier->SetSelection(cmb_carrier->NumItems()-1); + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +MsnElemDlg::OnClassSelect(AWEvent* event) +{ + if (!cmb_class || !cmb_design) return; + + const char* cname = cmb_class->GetSelectedItem(); + int classid = Ship::ClassForName(cname); + + cmb_design->ClearItems(); + + List designs; + ShipDesign::GetDesignList(classid, designs); + + if (designs.size() > 0) { + const ShipDesign* design = elem->GetDesign(); + bool found = false; + + for (int i = 0; i < designs.size(); i++) { + const char* dsn = designs[i]->data(); + cmb_design->AddItem(dsn); + + if (design && !stricmp(dsn, design->name)) { + cmb_design->SetSelection(i); + found = true; + } + } + + if (!found) + cmb_design->SetSelection(0); + } + else { + cmb_design->AddItem(""); + cmb_design->SetSelection(0); + } + + OnDesignSelect(0); +} + +// +--------------------------------------------------------------------+ + +void +MsnElemDlg::OnDesignSelect(AWEvent* event) +{ + if (!cmb_design) return; + + ShipDesign* design = 0; + + const char* dname = cmb_design->GetSelectedItem(); + int load_index = 0; + + if (dname) + design = ShipDesign::Get(dname); + + if (cmb_loadout) { + cmb_loadout->ClearItems(); + + if (design) { + MissionLoad* mload = 0; + + if (elem && elem->Loadouts().size() > 0) + mload = elem->Loadouts().at(0); + + const List& loadouts = design->loadouts; + + if (loadouts.size() > 0) { + for (int i = 0; i < loadouts.size(); i++) { + const ShipLoad* load = loadouts[i]; + if (load->name[0]) { + cmb_loadout->AddItem(load->name); + + if (mload && mload->GetName() == load->name) { + load_index = cmb_loadout->NumItems()-1; + } + } + } + } + } + + if (cmb_loadout->NumItems() < 1) + cmb_loadout->AddItem(""); + + cmb_loadout->SetSelection(load_index); + } + + if (cmb_skin) { + cmb_skin->ClearItems(); + + if (design) { + cmb_skin->AddItem(Game::GetText("MsnDlg.default")); + cmb_skin->SetSelection(0); + + ListIter iter = design->skins; + + while (++iter) { + Skin* s = iter.value(); + cmb_skin->AddItem(s->Name()); + + if (elem && elem->GetSkin() && !strcmp(s->Name(), elem->GetSkin()->Name())) { + cmb_skin->SetSelection(cmb_skin->NumItems()-1); + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +MsnElemDlg::OnObjectiveSelect(AWEvent* event) +{ + if (!cmb_objective || !cmb_target) return; + + Instruction* instr = 0; + if (elem->Objectives().size() > 0) + instr = elem->Objectives().at(0); + + int objid = cmb_objective->GetSelectedIndex() - 1; + + cmb_target->ClearItems(); + cmb_target->AddItem(""); + + if (mission) { + ListIter iter = mission->GetElements(); + while (++iter) { + MissionElement* e = iter.value(); + + if (e != elem) { + bool add = false; + + if (objid < Instruction::PATROL) + add = e->GetIFF() == 0 || e->GetIFF() == elem->GetIFF(); + else + add = e->GetIFF() != elem->GetIFF(); + + if (add) { + cmb_target->AddItem(e->Name()); + + if (instr && !stricmp(instr->TargetName(), e->Name())) + cmb_target->SetSelection(cmb_target->NumItems()-1); + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +MsnElemDlg::OnIFFChange(AWEvent* event) +{ + if (edt_iff && elem) { + int elem_iff = 0; + sscanf(edt_iff->GetText().data(), "%d", &elem_iff); + + if (elem->GetIFF() == elem_iff) + return; + + elem->SetIFF(elem_iff); + } + + UpdateTeamInfo(); + + if (cmb_target) + OnObjectiveSelect(0); +} + +// +--------------------------------------------------------------------+ + +void +MsnElemDlg::OnAccept(AWEvent* event) +{ + if (mission && elem) { + char buf[64]; + int val; + + if (edt_name) { + elem->SetName(edt_name->GetText()); + } + + if (edt_size) { + strcpy(buf, edt_size->GetText()); + + if (isdigit(*buf)) + sscanf(buf, "%d", &val); + else + val = 1; + + elem->SetCount(val); + } + + if (edt_iff) { + strcpy(buf, edt_iff->GetText()); + + if (isdigit(*buf)) + sscanf(buf, "%d", &val); + else + val = 1; + + elem->SetIFF(val); + } + + if (edt_loc_x && edt_loc_y && edt_loc_z) { + Point loc; + + strcpy(buf, edt_loc_x->GetText()); + + if (isdigit(*buf) || *buf == '-') + sscanf(buf, "%d", &val); + else + val = 0; + + loc.x = val * 1000; + + strcpy(buf, edt_loc_y->GetText()); + + if (isdigit(*buf) || *buf == '-') + sscanf(buf, "%d", &val); + else + val = 0; + + loc.y = val * 1000; + + strcpy(buf, edt_loc_z->GetText()); + + if (isdigit(*buf) || *buf == '-') + sscanf(buf, "%d", &val); + else + val = 0; + + loc.z = val * 1000; + + elem->SetLocation(loc); + } + + if (edt_respawns) { + strcpy(buf, edt_respawns->GetText()); + + if (isdigit(*buf)) + sscanf(buf, "%d", &val); + else + val = 0; + + elem->SetRespawnCount(val); + } + + if (edt_hold_time) { + strcpy(buf, edt_hold_time->GetText()); + + if (isdigit(*buf)) + sscanf(buf, "%d", &val); + else + val = 0; + + elem->SetHoldTime(val); + } + + if (btn_player) { + if (btn_player->GetButtonState() > 0) { + mission->SetPlayer(elem); + } + else { + elem->SetPlayer(0); + } + } + + if (btn_playable) + elem->SetPlayable(btn_playable->GetButtonState() ? true : false); + + if (btn_alert) + elem->SetAlert(btn_alert->GetButtonState() ? true : false); + + if (btn_command_ai) + elem->SetCommandAI(btn_command_ai->GetButtonState() ? 1 : 0); + + if (cmb_design) { + ShipDesign* d = ShipDesign::Get(cmb_design->GetSelectedItem()); + + if (d) { + elem->SetDesign(d); + + if (cmb_skin) { + const char* skin_name = cmb_skin->GetSelectedItem(); + + if (strcmp(skin_name, Game::GetText("MsnDlg.default").data())) { + elem->SetSkin(d->FindSkin(skin_name)); + } + else { + elem->SetSkin(0); + } + } + } + } + + if (cmb_role) { + elem->SetMissionRole(cmb_role->GetSelectedIndex()); + } + + if (cmb_region) { + elem->SetRegion(cmb_region->GetSelectedItem()); + + if (elem->Player() > 0) { + mission->SetRegion(cmb_region->GetSelectedItem()); + } + } + + if (cmb_heading) { + switch (cmb_heading->GetSelectedIndex()) { + default: + case 0: elem->SetHeading(0); break; + case 1: elem->SetHeading(PI/2); break; + case 2: elem->SetHeading(PI); break; + case 3: elem->SetHeading(3*PI/2); break; + } + } + + if (cmb_commander) { + elem->SetCommander(cmb_commander->GetSelectedItem()); + } + + if (cmb_squadron) { + elem->SetSquadron(cmb_squadron->GetSelectedItem()); + } + + if (cmb_carrier) { + elem->SetCarrier(cmb_carrier->GetSelectedItem()); + } + + if (cmb_intel) { + elem->SetIntelLevel(Intel::IntelFromName(cmb_intel->GetSelectedItem())); + } + + if (cmb_loadout && cmb_loadout->NumItems()) { + elem->Loadouts().destroy(); + + const char* loadname = cmb_loadout->GetSelectedItem(); + if (loadname && *loadname) { + MissionLoad* mload = new(__FILE__,__LINE__) MissionLoad(-1, cmb_loadout->GetSelectedItem()); + elem->Loadouts().append(mload); + } + } + + if (cmb_objective && cmb_target) { + List& objectives = elem->Objectives(); + objectives.destroy(); + + int action = cmb_objective->GetSelectedIndex() - 1; + const char* target = cmb_target->GetSelectedItem(); + + if (action >= Instruction::VECTOR) { + Instruction* obj = new(__FILE__,__LINE__) Instruction(action, target); + objectives.append(obj); + } + } + + if (elem->Player()) { + mission->SetTeam(elem->GetIFF()); + } + } + + manager->ShowMsnEditDlg(); +} + +void +MsnElemDlg::OnCancel(AWEvent* event) +{ + manager->ShowMsnEditDlg(); +} + +bool +MsnElemDlg::CanCommand(const MissionElement* commander, + const MissionElement* subordinate) const +{ + if (mission && commander && subordinate && commander != subordinate) { + if (commander->GetIFF() != subordinate->GetIFF()) + return false; + + if (commander->IsSquadron()) + return false; + + if (commander->Commander().length() == 0) + return true; + + if (subordinate->Name() == commander->Commander()) + return false; + + MissionElement* elem = mission->FindElement(commander->Commander()); + + if (elem) + return CanCommand(elem, subordinate); + } + + return false; +} \ No newline at end of file diff --git a/Stars45/MsnElemDlg.h b/Stars45/MsnElemDlg.h new file mode 100644 index 0000000..fb7be33 --- /dev/null +++ b/Stars45/MsnElemDlg.h @@ -0,0 +1,99 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: MsnElemDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Mission Editor Element Dialog Active Window class +*/ + +#ifndef MsnElemDlg_h +#define MsnElemDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "Bitmap.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Font.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class MenuScreen; +class MsnEditDlg; +class Mission; +class MissionElement; + +// +--------------------------------------------------------------------+ + +class MsnElemDlg : public FormWindow +{ +public: + MsnElemDlg(Screen* s, FormDef& def, MenuScreen* mgr); + virtual ~MsnElemDlg(); + + virtual void RegisterControls(); + virtual void Show(); + virtual void ExecFrame(); + + // Operations: + virtual void SetMission(Mission* elem); + virtual void SetMissionElement(MissionElement* elem); + virtual void OnAccept(AWEvent* event); + virtual void OnCancel(AWEvent* event); + virtual void OnClassSelect(AWEvent* event); + virtual void OnDesignSelect(AWEvent* event); + virtual void OnObjectiveSelect(AWEvent* event); + virtual void OnIFFChange(AWEvent* event); + + virtual void UpdateTeamInfo(); + virtual bool CanCommand(const MissionElement* commander, + const MissionElement* subordinate) const; + +protected: + MenuScreen* manager; + + EditBox* edt_name; + ComboBox* cmb_class; + ComboBox* cmb_design; + ComboBox* cmb_skin; + EditBox* edt_size; + EditBox* edt_iff; + ComboBox* cmb_role; + ComboBox* cmb_region; + EditBox* edt_loc_x; + EditBox* edt_loc_y; + EditBox* edt_loc_z; + ComboBox* cmb_heading; + EditBox* edt_hold_time; + + Button* btn_player; + Button* btn_playable; + Button* btn_alert; + Button* btn_command_ai; + EditBox* edt_respawns; + ComboBox* cmb_carrier; + ComboBox* cmb_squadron; + ComboBox* cmb_commander; + ComboBox* cmb_intel; + ComboBox* cmb_loadout; + ComboBox* cmb_objective; + ComboBox* cmb_target; + + Button* btn_accept; + Button* btn_cancel; + + Mission* mission; + MissionElement* elem; + bool exit_latch; +}; + +#endif MsnElemDlg_h + diff --git a/Stars45/MsnEventDlg.cpp b/Stars45/MsnEventDlg.cpp new file mode 100644 index 0000000..c1b779b --- /dev/null +++ b/Stars45/MsnEventDlg.cpp @@ -0,0 +1,416 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: MsnEventDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Mod Config Dialog Active Window class +*/ + +#include "MemDebug.h" +#include "MsnEventDlg.h" +#include "MsnEditDlg.h" +#include "MenuScreen.h" +#include "Campaign.h" +#include "Mission.h" +#include "MissionEvent.h" +#include "Instruction.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "StarSystem.h" +#include "Galaxy.h" + +#include "Game.h" +#include "DataLoader.h" +#include "Button.h" +#include "ListBox.h" +#include "EditBox.h" +#include "Video.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "ParseUtil.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +DEF_MAP_CLIENT(MsnEventDlg, OnEventSelect); +DEF_MAP_CLIENT(MsnEventDlg, OnAccept); +DEF_MAP_CLIENT(MsnEventDlg, OnCancel); + +// +--------------------------------------------------------------------+ + +MsnEventDlg::MsnEventDlg(Screen* s, FormDef& def, MenuScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), manager(mgr), + btn_accept(0), btn_cancel(0), mission(0), event(0) +{ + Init(def); +} + +MsnEventDlg::~MsnEventDlg() +{ +} + +// +--------------------------------------------------------------------+ + +void +MsnEventDlg::RegisterControls() +{ + btn_accept = (Button*) FindControl( 1); + if (btn_accept) + REGISTER_CLIENT(EID_CLICK, btn_accept, MsnEventDlg, OnAccept); + + btn_cancel = (Button*) FindControl( 2); + if (btn_accept) + REGISTER_CLIENT(EID_CLICK, btn_cancel, MsnEventDlg, OnCancel); + + lbl_id = FindControl(201); + edt_time = (EditBox*) FindControl(202); + edt_delay = (EditBox*) FindControl(203); + cmb_event = (ComboBox*) FindControl(204); + cmb_event_ship = (ComboBox*) FindControl(205); + cmb_event_source = (ComboBox*) FindControl(206); + cmb_event_target = (ComboBox*) FindControl(207); + edt_event_param = (EditBox*) FindControl(208); + edt_event_chance = (EditBox*) FindControl(220); + edt_event_sound = (EditBox*) FindControl(209); + edt_event_message = (EditBox*) FindControl(210); + + cmb_trigger = (ComboBox*) FindControl(221); + cmb_trigger_ship = (ComboBox*) FindControl(222); + cmb_trigger_target = (ComboBox*) FindControl(223); + edt_trigger_param = (EditBox*) FindControl(224); + + if (cmb_event) + REGISTER_CLIENT(EID_SELECT, cmb_event, MsnEventDlg, OnEventSelect); +} + +// +--------------------------------------------------------------------+ + +void +MsnEventDlg::Show() +{ + FormWindow::Show(); + + if (!event) return; + + FillShipList(cmb_event_ship, event->EventShip()); + FillShipList(cmb_event_source, event->EventSource()); + + if (event->Event() == MissionEvent::JUMP) + FillRgnList(cmb_event_target, event->EventTarget()); + else + FillShipList(cmb_event_target,event->EventTarget()); + + FillShipList(cmb_trigger_ship, event->TriggerShip()); + FillShipList(cmb_trigger_target, event->TriggerTarget()); + + char buf[64]; + + sprintf(buf, "%d", event->EventID()); + if (lbl_id) lbl_id->SetText(buf); + + if (edt_time) { + sprintf(buf, "%.1f", event->Time()); + edt_time->SetText(buf); + } + + if (edt_delay) { + sprintf(buf, "%.1f", event->Delay()); + edt_delay->SetText(buf); + } + + if (edt_event_chance) { + sprintf(buf, "%d", event->EventChance()); + edt_event_chance->SetText(buf); + } + + sprintf(buf, "%d", event->EventParam()); + if (edt_event_param) edt_event_param->SetText(buf); + + if (edt_trigger_param) + edt_trigger_param->SetText(event->TriggerParamStr()); + + if (edt_event_message) + edt_event_message->SetText(event->EventMessage()); + + if (edt_event_sound) + edt_event_sound->SetText(event->EventSound()); + + if (cmb_event) { + cmb_event->ClearItems(); + + for (int i = 0; i < MissionEvent::NUM_EVENTS; i++) { + cmb_event->AddItem(MissionEvent::EventName(i)); + } + + cmb_event->SetSelection(event->Event()); + } + + if (cmb_trigger) { + cmb_trigger->ClearItems(); + + for (int i = 0; i < MissionEvent::NUM_TRIGGERS; i++) { + cmb_trigger->AddItem(MissionEvent::TriggerName(i)); + } + + cmb_trigger->SetSelection(event->Trigger()); + } +} + +// +--------------------------------------------------------------------+ + +void +MsnEventDlg::FillShipList(ComboBox* cmb, const char* seln) +{ + if (!cmb) return; + cmb->ClearItems(); + + if (!mission) return; + + int index = 1; + int selected_index = 0; + cmb->AddItem(""); + + List& list = mission->GetElements(); + for (int i = 0; i < list.size(); i++) { + MissionElement* elem = list[i]; + + if (elem->IsSquadron()) + continue; + + if (elem->Count() == 1) { + cmb->AddItem(elem->Name()); + + if (elem->Name() == seln) + selected_index = index; + + index++; + } + else { + char ship_name[256]; + + for (int n = 0; n < elem->Count(); n++) { + sprintf(ship_name, "%s %d", elem->Name().data(), n+1); + cmb->AddItem(ship_name); + + if (!stricmp(ship_name, seln)) + selected_index = index; + + index++; + } + } + } + + cmb->SetSelection(selected_index); +} + +void +MsnEventDlg::FillRgnList(ComboBox* cmb, const char* seln) +{ + if (!cmb) return; + cmb->ClearItems(); + + if (!mission) return; + + int selected_index = 0; + int i = 0; + + ListIter iter = mission->GetSystemList(); + while (++iter) { + StarSystem* sys = iter.value(); + + ListIter iter2 = sys->AllRegions(); + while (++iter2) { + OrbitalRegion* region = iter2.value(); + + if (!strcmp(region->Name(), seln)) + selected_index = i; + + cmb->AddItem(region->Name()); + i++; + } + } + + cmb->SetSelection(selected_index); +} + +// +--------------------------------------------------------------------+ + +void +MsnEventDlg::ExecFrame() +{ + if (Keyboard::KeyDown(VK_RETURN)) { + if (btn_accept && btn_accept->IsEnabled()) + OnAccept(0); + } +} + +// +--------------------------------------------------------------------+ + +void +MsnEventDlg::SetMission(Mission* m) +{ + mission = m; +} + +void +MsnEventDlg::SetMissionEvent(MissionEvent* e) +{ + event = e; +} + +// +--------------------------------------------------------------------+ + +void +MsnEventDlg::OnEventSelect(AWEvent* e) +{ + if (cmb_event->GetSelectedIndex() == MissionEvent::JUMP) + FillRgnList(cmb_event_target, event->EventTarget()); + + else + FillShipList(cmb_event_target, event->EventTarget()); +} + +// +--------------------------------------------------------------------+ + +void +MsnEventDlg::OnAccept(AWEvent* e) +{ + if (mission && event) { + char buf[64]; + int val; + + if (edt_time) { + strcpy(buf, edt_time->GetText()); + + float t = 0; + sscanf(buf, "%f", &t); + + event->time = t; + } + + if (edt_delay) { + strcpy(buf, edt_delay->GetText()); + + float t = 0; + sscanf(buf, "%f", &t); + + event->delay = t; + } + + if (edt_event_param) { + strcpy(buf, edt_event_param->GetText()); + + if (isdigit(*buf)) { + sscanf(buf, "%d", &val); + event->event_param[0] = val; + event->event_nparams = 1; + } + else if (*buf == '(') { + Parser parser(new(__FILE__,__LINE__) BlockReader(buf)); + Term* term = parser.ParseTerm(); + + if (term && term->isArray()) { + TermArray* val = term->isArray(); + if (val) { + int nelem = val->elements()->size(); + + if (nelem > 10) + nelem = 10; + + for (int i = 0; i < nelem; i++) + event->event_param[i] = (int) (val->elements()->at(i)->isNumber()->value()); + + event->event_nparams = nelem; + } + } + } + } + + if (edt_event_chance) { + strcpy(buf, edt_event_chance->GetText()); + + if (isdigit(*buf)) { + sscanf(buf, "%d", &val); + } + else { + val = 0; + } + + event->event_chance = val; + } + + if (edt_event_message) + event->event_message = edt_event_message->GetText(); + + if (edt_event_sound) + event->event_sound = edt_event_sound->GetText(); + + if (edt_trigger_param) { + strcpy(buf, edt_trigger_param->GetText()); + + ZeroMemory(event->trigger_param, sizeof(event->trigger_param)); + + if (isdigit(*buf)) { + sscanf(buf, "%d", &val); + event->trigger_param[0] = val; + event->trigger_nparams = 1; + } + + else if (*buf == '(') { + Parser parser(new(__FILE__,__LINE__) BlockReader(buf)); + Term* term = parser.ParseTerm(); + + if (term && term->isArray()) { + TermArray* val = term->isArray(); + if (val) { + int nelem = val->elements()->size(); + + if (nelem > 10) + nelem = 10; + + for (int i = 0; i < nelem; i++) + event->trigger_param[i] = (int) (val->elements()->at(i)->isNumber()->value()); + + event->trigger_nparams = nelem; + } + } + } + + } + + if (cmb_event) + event->event = cmb_event->GetSelectedIndex(); + + if (cmb_event_ship) + event->event_ship = cmb_event_ship->GetSelectedItem(); + + if (cmb_event_source) + event->event_source = cmb_event_source->GetSelectedItem(); + + if (cmb_event_target) + event->event_target = cmb_event_target->GetSelectedItem(); + + if (cmb_trigger) + event->trigger = cmb_trigger->GetSelectedIndex(); + + if (cmb_trigger_ship) + event->trigger_ship = cmb_trigger_ship->GetSelectedItem(); + + if (cmb_trigger_target) + event->trigger_target = cmb_trigger_target->GetSelectedItem(); + } + + manager->ShowMsnEditDlg(); +} + +void +MsnEventDlg::OnCancel(AWEvent* event) +{ + manager->ShowMsnEditDlg(); +} diff --git a/Stars45/MsnEventDlg.h b/Stars45/MsnEventDlg.h new file mode 100644 index 0000000..1bf85f0 --- /dev/null +++ b/Stars45/MsnEventDlg.h @@ -0,0 +1,86 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: MsnEventDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Mission Editor Element Dialog Active Window class +*/ + +#ifndef MsnEventDlg_h +#define MsnEventDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "Bitmap.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Font.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class MenuScreen; +class MsnEditDlg; +class Mission; +class MissionElement; +class MissionEvent; + +// +--------------------------------------------------------------------+ + +class MsnEventDlg : public FormWindow +{ +public: + MsnEventDlg(Screen* s, FormDef& def, MenuScreen* mgr); + virtual ~MsnEventDlg(); + + virtual void RegisterControls(); + virtual void Show(); + virtual void ExecFrame(); + + // Operations: + virtual void SetMission(Mission* msn); + virtual void SetMissionEvent(MissionEvent* event); + virtual void OnEventSelect(AWEvent* e); + virtual void OnAccept(AWEvent* e); + virtual void OnCancel(AWEvent* e); + +protected: + virtual void FillShipList(ComboBox* cmb, const char* seln); + virtual void FillRgnList(ComboBox* cmb, const char* seln); + + MenuScreen* manager; + + ActiveWindow* lbl_id; + EditBox* edt_time; + EditBox* edt_delay; + + ComboBox* cmb_event; + ComboBox* cmb_event_ship; + ComboBox* cmb_event_source; + ComboBox* cmb_event_target; + EditBox* edt_event_param; + EditBox* edt_event_chance; + EditBox* edt_event_sound; + EditBox* edt_event_message; + + ComboBox* cmb_trigger; + ComboBox* cmb_trigger_ship; + ComboBox* cmb_trigger_target; + EditBox* edt_trigger_param; + + Button* btn_accept; + Button* btn_cancel; + + Mission* mission; + MissionEvent* event; +}; + +#endif MsnEventDlg_h + diff --git a/Stars45/MsnNavDlg.cpp b/Stars45/MsnNavDlg.cpp new file mode 100644 index 0000000..1f3dfb2 --- /dev/null +++ b/Stars45/MsnNavDlg.cpp @@ -0,0 +1,104 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: MsnNavDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Mission Briefing Dialog Active Window class +*/ + +#include "MemDebug.h" +#include "MsnNavDlg.h" +#include "PlanScreen.h" +#include "Campaign.h" +#include "Mission.h" +#include "Instruction.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "StarSystem.h" + +#include "Keyboard.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: +DEF_MAP_CLIENT(MsnNavDlg, OnCommit); +DEF_MAP_CLIENT(MsnNavDlg, OnCancel); +DEF_MAP_CLIENT(MsnNavDlg, OnTabButton); + +// +--------------------------------------------------------------------+ + +MsnNavDlg::MsnNavDlg(Screen* s, FormDef& def, PlanScreen* mgr) + : NavDlg(s, def, mgr), MsnDlg(mgr) +{ + RegisterControls(); +} + +MsnNavDlg::~MsnNavDlg() +{ +} + +// +--------------------------------------------------------------------+ + +void +MsnNavDlg::RegisterControls() +{ + RegisterMsnControls(this); + + if (commit) + REGISTER_CLIENT(EID_CLICK, commit, MsnNavDlg, OnCommit); + + if (cancel) + REGISTER_CLIENT(EID_CLICK, cancel, MsnNavDlg, OnCancel); + + if (sit_button) + REGISTER_CLIENT(EID_CLICK, sit_button, MsnNavDlg, OnTabButton); + + if (pkg_button) + REGISTER_CLIENT(EID_CLICK, pkg_button, MsnNavDlg, OnTabButton); + + if (nav_button) + REGISTER_CLIENT(EID_CLICK, nav_button, MsnNavDlg, OnTabButton); + + if (wep_button) + REGISTER_CLIENT(EID_CLICK, wep_button, MsnNavDlg, OnTabButton); +} + +// +--------------------------------------------------------------------+ + +void +MsnNavDlg::Show() +{ + NavDlg::Show(); + ShowMsnDlg(); +} + +void +MsnNavDlg::ExecFrame() +{ + NavDlg::ExecFrame(); + + if (Keyboard::KeyDown(VK_RETURN)) { + OnCommit(0); + } +} + +// +--------------------------------------------------------------------+ + +void +MsnNavDlg::OnCommit(AWEvent* event) +{ + MsnDlg::OnCommit(event); + NavDlg::OnCommit(event); +} + +void +MsnNavDlg::OnCancel(AWEvent* event) +{ + MsnDlg::OnCancel(event); + NavDlg::OnCancel(event); +} diff --git a/Stars45/MsnNavDlg.h b/Stars45/MsnNavDlg.h new file mode 100644 index 0000000..869e076 --- /dev/null +++ b/Stars45/MsnNavDlg.h @@ -0,0 +1,54 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: MsnNavDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Mission Briefing Dialog Active Window class +*/ + +#ifndef MsnNavDlg_h +#define MsnNavDlg_h + +#include "Types.h" +#include "MsnDlg.h" +#include "NavDlg.h" +#include "Bitmap.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Font.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class PlanScreen; +class Campaign; +class Mission; +class MissionInfo; + +// +--------------------------------------------------------------------+ + +class MsnNavDlg : public NavDlg, + public MsnDlg +{ +public: + MsnNavDlg(Screen* s, FormDef& def, PlanScreen* mgr); + virtual ~MsnNavDlg(); + + virtual void RegisterControls(); + virtual void Show(); + virtual void ExecFrame(); + + // Operations: + virtual void OnCommit(AWEvent* event); + virtual void OnCancel(AWEvent* event); +}; + +#endif MsnNavDlg_h + diff --git a/Stars45/MsnObjDlg.cpp b/Stars45/MsnObjDlg.cpp new file mode 100644 index 0000000..cee0b86 --- /dev/null +++ b/Stars45/MsnObjDlg.cpp @@ -0,0 +1,316 @@ +/* Project Starshatter 5.0 + Destroyer Studios LLC + Copyright © 1997-2007. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: MsnObjDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Mission Briefing Dialog Active Window class +*/ + +#include "MemDebug.h" +#include "MsnObjDlg.h" +#include "PlanScreen.h" +#include "Campaign.h" +#include "Mission.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "ShipSolid.h" +#include "StarSystem.h" + +#include "Game.h" +#include "Mouse.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Slider.h" +#include "ParseUtil.h" +#include "FormatUtil.h" +#include "Light.h" +#include "Solid.h" +#include "Keyboard.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: +DEF_MAP_CLIENT(MsnObjDlg, OnCommit); +DEF_MAP_CLIENT(MsnObjDlg, OnCancel); +DEF_MAP_CLIENT(MsnObjDlg, OnTabButton); +DEF_MAP_CLIENT(MsnObjDlg, OnSkin); + +// +--------------------------------------------------------------------+ + +MsnObjDlg::MsnObjDlg(Screen* s, FormDef& def, PlanScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), MsnDlg(mgr), + objectives(0), sitrep(0), beauty(0), camview(0), player_desc(0), + ship(0) +{ + campaign = Campaign::GetCampaign(); + + if (campaign) + mission = campaign->GetMission(); + + Init(def); +} + +MsnObjDlg::~MsnObjDlg() +{ +} + +// +--------------------------------------------------------------------+ + +void +MsnObjDlg::RegisterControls() +{ + objectives = FindControl(400); + sitrep = FindControl(401); + beauty = FindControl(300); + player_desc = FindControl(301); + cmb_skin = (ComboBox*) FindControl(302); + + RegisterMsnControls(this); + + if (commit) + REGISTER_CLIENT(EID_CLICK, commit, MsnObjDlg, OnCommit); + + if (cancel) + REGISTER_CLIENT(EID_CLICK, cancel, MsnObjDlg, OnCancel); + + if (sit_button) + REGISTER_CLIENT(EID_CLICK, sit_button, MsnObjDlg, OnTabButton); + + if (pkg_button) + REGISTER_CLIENT(EID_CLICK, pkg_button, MsnObjDlg, OnTabButton); + + if (nav_button) + REGISTER_CLIENT(EID_CLICK, nav_button, MsnObjDlg, OnTabButton); + + if (wep_button) + REGISTER_CLIENT(EID_CLICK, wep_button, MsnObjDlg, OnTabButton); + + if (cmb_skin) { + REGISTER_CLIENT(EID_SELECT, cmb_skin, MsnObjDlg, OnSkin); + } + + if (beauty) { + scene.SetAmbient(Color(72,75,78)); + + Point light_pos(3e6, 5e6, 4e6); + + Light* main_light = new Light(1.2f); + main_light->MoveTo(light_pos); + main_light->SetType(Light::LIGHT_DIRECTIONAL); + main_light->SetColor(Color::White); + main_light->SetShadow(true); + + scene.AddLight(main_light); + + Light* back_light = new Light(0.35f); + back_light->MoveTo(light_pos * -1); + back_light->SetType(Light::LIGHT_DIRECTIONAL); + back_light->SetColor(Color::White); + back_light->SetShadow(false); + + scene.AddLight(back_light); + + camview = new(__FILE__,__LINE__) CameraView(beauty, &cam, &scene); + camview->SetProjectionType(Video::PROJECTION_PERSPECTIVE); + camview->SetFieldOfView(2); + + beauty->AddView(camview); + } +} + +// +--------------------------------------------------------------------+ + +void +MsnObjDlg::Show() +{ + bool update_scene = !shown; + + FormWindow::Show(); + ShowMsnDlg(); + + if (objectives) { + if (mission) { + if (mission->IsOK()) + objectives->SetText(mission->Objective()); + else + objectives->SetText(""); + } + else { + objectives->SetText(Game::GetText("MsnDlg.no-mission")); + } + } + + if (sitrep) { + if (mission) { + if (mission->IsOK()) + sitrep->SetText(mission->Situation()); + else + sitrep->SetText(Game::GetText("MsnDlg.found-errors") + + mission->ErrorMessage()); + } + else { + sitrep->SetText(Game::GetText("MsnDlg.no-mission")); + } + } + + if (cmb_skin) { + cmb_skin->ClearItems(); + cmb_skin->Hide(); + } + + if (beauty) { + if (mission && mission->IsOK()) { + MissionElement* elem = mission->GetPlayer(); + + if (elem) { + const ShipDesign* design = elem->GetDesign(); + + if (design && camview && update_scene) { + double az = -PI/6; + double el = PI/8; + double zoom = 1.8; + + scene.Graphics().clear(); + + if (elem->IsStarship()) { + az = -PI/8; + el = PI/12; + zoom = 1.7; + } + + if (design->beauty_cam.z > 0) { + az = design->beauty_cam.x; + el = design->beauty_cam.y; + zoom = design->beauty_cam.z; + } + + double r = design->radius; + double x = zoom * r * sin(az) * cos(el); + double y = zoom * r * cos(az) * cos(el); + double z = zoom * r * sin(el); + + cam.LookAt(Point(0,0,r/5), Point(x,z,y), Point(0,1,0)); + + int n = design->lod_levels; + + if (n >= 1) { + Model* model = design->models[n-1].at(0); + + if (model) { + ship = new(__FILE__,__LINE__) ShipSolid(0); + ship->UseModel(model); + ship->CreateShadows(1); + ship->SetSkin(elem->GetSkin()); + + Matrix o; + o.Pitch( 3 * DEGREES); + o.Roll( 13 * DEGREES); + + ship->SetOrientation(o); + + scene.Graphics().append(ship); + } + } + } + + if (cmb_skin && design && design->skins.size()) { + cmb_skin->Show(); + cmb_skin->AddItem(Game::GetText("MsnDlg.default")); + cmb_skin->SetSelection(0); + ListIter iter = ((ShipDesign*) design)->skins; + + while (++iter) { + Skin* s = iter.value(); + cmb_skin->AddItem(s->Name()); + + if (elem && elem->GetSkin() && !strcmp(s->Name(), elem->GetSkin()->Name())) { + cmb_skin->SetSelection(cmb_skin->NumItems()-1); + } + } + } + } + } + } + + if (player_desc) { + player_desc->SetText(""); + + if (mission && mission->IsOK()) { + MissionElement* elem = mission->GetPlayer(); + + if (elem) { + const ShipDesign* design = elem->GetDesign(); + + if (design) { + char txt[256]; + + if (design->type <= Ship::ATTACK) + sprintf(txt, "%s %s", design->abrv, design->display_name); + else + sprintf(txt, "%s %s", design->abrv, elem->Name().data()); + + player_desc->SetText(txt); + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +MsnObjDlg::ExecFrame() +{ + if (Keyboard::KeyDown(VK_RETURN)) { + OnCommit(0); + } +} + +// +--------------------------------------------------------------------+ + +void +MsnObjDlg::OnSkin(AWEvent* event) +{ + Text skin_name = cmb_skin->GetSelectedItem(); + + if (mission && mission->IsOK()) { + MissionElement* elem = mission->GetPlayer(); + + if (elem) { + const ShipDesign* design = elem->GetDesign(); + + if (design) { + const Skin* skin = design->FindSkin(skin_name); + + elem->SetSkin(skin); + + if (ship) + ship->SetSkin(skin); + } + } + } +} + +void +MsnObjDlg::OnCommit(AWEvent* event) +{ + MsnDlg::OnCommit(event); +} + +void +MsnObjDlg::OnCancel(AWEvent* event) +{ + MsnDlg::OnCancel(event); +} + +void +MsnObjDlg::OnTabButton(AWEvent* event) +{ + MsnDlg::OnTabButton(event); +} diff --git a/Stars45/MsnObjDlg.h b/Stars45/MsnObjDlg.h new file mode 100644 index 0000000..890ec11 --- /dev/null +++ b/Stars45/MsnObjDlg.h @@ -0,0 +1,68 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: MsnObjDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Mission Briefing Dialog Active Window class +*/ + +#ifndef MsnObjDlg_h +#define MsnObjDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "MsnDlg.h" +#include "Bitmap.h" +#include "Button.h" +#include "CameraView.h" +#include "Scene.h" +#include "Font.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class PlanScreen; +class Campaign; +class Mission; +class MissionInfo; +class ShipSolid; + +// +--------------------------------------------------------------------+ + +class MsnObjDlg : public FormWindow, + public MsnDlg +{ +public: + MsnObjDlg(Screen* s, FormDef& def, PlanScreen* mgr); + virtual ~MsnObjDlg(); + + virtual void RegisterControls(); + virtual void ExecFrame(); + virtual void Show(); + + // Operations: + virtual void OnCommit(AWEvent* event); + virtual void OnCancel(AWEvent* event); + virtual void OnTabButton(AWEvent* event); + virtual void OnSkin(AWEvent* event); + +protected: + ActiveWindow* objectives; + ActiveWindow* sitrep; + ActiveWindow* player_desc; + ActiveWindow* beauty; + ComboBox* cmb_skin; + CameraView* camview; + Scene scene; + Camera cam; + ShipSolid* ship; +}; + +#endif MsnObjDlg_h + diff --git a/Stars45/MsnPkgDlg.cpp b/Stars45/MsnPkgDlg.cpp new file mode 100644 index 0000000..ce7493c --- /dev/null +++ b/Stars45/MsnPkgDlg.cpp @@ -0,0 +1,336 @@ +/* Project Starshatter 5.0 + Destroyer Studios LLC + Copyright © 1997-2007. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: MsnPkgDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Mission Briefing Dialog Active Window class +*/ + +#include "MemDebug.h" +#include "MsnPkgDlg.h" +#include "PlanScreen.h" +#include "Campaign.h" +#include "Mission.h" +#include "Instruction.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "StarSystem.h" + +#include "Game.h" +#include "Mouse.h" +#include "Button.h" +#include "ListBox.h" +#include "Slider.h" +#include "ParseUtil.h" +#include "FormatUtil.h" +#include "Keyboard.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: +DEF_MAP_CLIENT(MsnPkgDlg, OnPackage); +DEF_MAP_CLIENT(MsnPkgDlg, OnCommit); +DEF_MAP_CLIENT(MsnPkgDlg, OnCancel); +DEF_MAP_CLIENT(MsnPkgDlg, OnTabButton); + +// +--------------------------------------------------------------------+ + +MsnPkgDlg::MsnPkgDlg(Screen* s, FormDef& def, PlanScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), MsnDlg(mgr) +{ + campaign = Campaign::GetCampaign(); + + if (campaign) + mission = campaign->GetMission(); + + Init(def); +} + +MsnPkgDlg::~MsnPkgDlg() +{ +} + +// +--------------------------------------------------------------------+ + +void +MsnPkgDlg::RegisterControls() +{ + pkg_list = (ListBox*) FindControl(320); + nav_list = (ListBox*) FindControl(330); + + for (int i = 0; i < 5; i++) + threat[i] = FindControl(251 + i); + + RegisterMsnControls(this); + + if (pkg_list) + REGISTER_CLIENT(EID_SELECT, pkg_list, MsnPkgDlg, OnPackage); + + if (commit) + REGISTER_CLIENT(EID_CLICK, commit, MsnPkgDlg, OnCommit); + + if (cancel) + REGISTER_CLIENT(EID_CLICK, cancel, MsnPkgDlg, OnCancel); + + if (sit_button) + REGISTER_CLIENT(EID_CLICK, sit_button, MsnPkgDlg, OnTabButton); + + if (pkg_button) + REGISTER_CLIENT(EID_CLICK, pkg_button, MsnPkgDlg, OnTabButton); + + if (nav_button) + REGISTER_CLIENT(EID_CLICK, nav_button, MsnPkgDlg, OnTabButton); + + if (wep_button) + REGISTER_CLIENT(EID_CLICK, wep_button, MsnPkgDlg, OnTabButton); +} + +// +--------------------------------------------------------------------+ + +void +MsnPkgDlg::Show() +{ + FormWindow::Show(); + ShowMsnDlg(); + + DrawPackages(); + DrawNavPlan(); + DrawThreats(); +} + +// +--------------------------------------------------------------------+ + +void +MsnPkgDlg::DrawPackages() +{ + if (mission) { + if (pkg_list) { + pkg_list->ClearItems(); + + int i = 0; + int elem_index = 0; + ListIter elem = mission->GetElements(); + while (++elem) { + // display this element? + if (elem->GetIFF() == mission->Team() && + !elem->IsSquadron() && + elem->Region() == mission->GetRegion() && + elem->GetDesign()->type < Ship::STATION) { + + char txt[256]; + + if (elem->Player() > 0) { + sprintf(txt, "==>"); + if (pkg_index < 0) + pkg_index = elem_index; + } + else { + strcpy(txt, " "); + } + + pkg_list->AddItemWithData(txt, elem->ElementID()); + pkg_list->SetItemText(i, 1, elem->Name()); + pkg_list->SetItemText(i, 2, elem->RoleName()); + + const ShipDesign* design = elem->GetDesign(); + + if (elem->Count() > 1) + sprintf(txt, "%d %s", elem->Count(), design->abrv); + else + sprintf(txt, "%s %s", design->abrv, design->name); + pkg_list->SetItemText(i, 3, txt); + + i++; + } + + elem_index++; + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +MsnPkgDlg::DrawNavPlan() +{ + if (mission) { + if (pkg_index < 0 || pkg_index >= mission->GetElements().size()) + pkg_index = 0; + + MissionElement* element = mission->GetElements()[pkg_index]; + if (nav_list && element) { + nav_list->ClearItems(); + + Point loc = element->Location(); + int i = 0; + + ListIter navpt = element->NavList(); + while (++navpt) { + char txt[256]; + sprintf(txt, "%d", i + 1); + + nav_list->AddItem(txt); + nav_list->SetItemText(i, 1, Instruction::ActionName(navpt->Action())); + nav_list->SetItemText(i, 2, navpt->RegionName()); + + double dist = Point(loc - navpt->Location()).length(); + FormatNumber(txt, dist); + nav_list->SetItemText(i, 3, txt); + + sprintf(txt, "%d", navpt->Speed()); + nav_list->SetItemText(i, 4, txt); + + loc = navpt->Location(); + i++; + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +MsnPkgDlg::DrawThreats() +{ + for (int i = 0; i < 5; i++) + if (threat[i]) + threat[i]->SetText(""); + + if (!mission) return; + + MissionElement* player = mission->GetPlayer(); + Text rgn0 = player->Region(); + Text rgn1; + int iff = player->GetIFF(); + + if (!player) + return; + + ListIter nav = player->NavList(); + while (++nav) { + if (rgn0 != nav->RegionName()) + rgn1 = nav->RegionName(); + } + + if (threat[0]) { + Point base_loc = mission->GetElements()[0]->Location(); + + int i = 0; + ListIter iter = mission->GetElements(); + while (++iter) { + MissionElement* elem = iter.value(); + + if (elem->GetIFF() == 0 || elem->GetIFF() == iff || elem->IntelLevel() <= Intel::SECRET) + continue; + + if (elem->IsSquadron()) + continue; + + if (elem->IsGroundUnit()) { + if (!elem->GetDesign() || + elem->GetDesign()->type != Ship::SAM) + continue; + + if (elem->Region() != rgn0 && + elem->Region() != rgn1) + continue; + } + + int mission_role = elem->MissionRole(); + + if (mission_role == Mission::STRIKE || + mission_role == Mission::INTEL || + mission_role >= Mission::TRANSPORT) + continue; + + char rng[32]; + char role[32]; + char txt[256]; + + if (mission_role == Mission::SWEEP || + mission_role == Mission::INTERCEPT || + mission_role == Mission::FLEET || + mission_role == Mission::BOMBARDMENT) + strcpy(role, Game::GetText("MsnDlg.ATTACK").data()); + else + strcpy(role, Game::GetText("MsnDlg.PATROL").data()); + + double dist = Point(base_loc - elem->Location()).length(); + FormatNumber(rng, dist); + + sprintf(txt, "%s - %d %s - %s", role, + elem->Count(), + elem->GetDesign()->abrv, + rng); + if (threat[i]) + threat[i]->SetText(txt); + + i++; + + if (i >= 5) + break; + } + } +} + +// +--------------------------------------------------------------------+ + +void +MsnPkgDlg::ExecFrame() +{ + if (Keyboard::KeyDown(VK_RETURN)) { + OnCommit(0); + } +} + +// +--------------------------------------------------------------------+ + +void +MsnPkgDlg::OnPackage(AWEvent* event) +{ + if (!pkg_list || !mission) + return; + + int seln = pkg_list->GetListIndex(); + int pkg = pkg_list->GetItemData(seln); + + int i = 0; + ListIter elem = mission->GetElements(); + while (++elem) { + if (elem->ElementID() == pkg) { + pkg_index = i; + //mission->SetPlayer(elem.value()); + } + + i++; + } + + //DrawPackages(); + DrawNavPlan(); +} + +// +--------------------------------------------------------------------+ + +void +MsnPkgDlg::OnCommit(AWEvent* event) +{ + MsnDlg::OnCommit(event); +} + +void +MsnPkgDlg::OnCancel(AWEvent* event) +{ + MsnDlg::OnCancel(event); +} + +void +MsnPkgDlg::OnTabButton(AWEvent* event) +{ + MsnDlg::OnTabButton(event); +} diff --git a/Stars45/MsnPkgDlg.h b/Stars45/MsnPkgDlg.h new file mode 100644 index 0000000..f64304f --- /dev/null +++ b/Stars45/MsnPkgDlg.h @@ -0,0 +1,66 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: MsnPkgDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Mission Briefing Dialog Active Window class +*/ + +#ifndef MsnPkgDlg_h +#define MsnPkgDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "MsnDlg.h" +#include "Bitmap.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Font.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class PlanScreen; +class Campaign; +class Mission; +class MissionInfo; + +// +--------------------------------------------------------------------+ + +class MsnPkgDlg : public FormWindow, + public MsnDlg +{ +public: + MsnPkgDlg(Screen* s, FormDef& def, PlanScreen* mgr); + virtual ~MsnPkgDlg(); + + virtual void RegisterControls(); + virtual void ExecFrame(); + virtual void Show(); + + // Operations: + virtual void OnCommit(AWEvent* event); + virtual void OnCancel(AWEvent* event); + virtual void OnTabButton(AWEvent* event); + virtual void OnPackage(AWEvent* event); + +protected: + virtual void DrawPackages(); + virtual void DrawNavPlan(); + virtual void DrawThreats(); + + ListBox* pkg_list; + ListBox* nav_list; + + ActiveWindow* threat[5]; +}; + +#endif MsnPkgDlg_h + diff --git a/Stars45/MsnSelectDlg.cpp b/Stars45/MsnSelectDlg.cpp new file mode 100644 index 0000000..fa5386a --- /dev/null +++ b/Stars45/MsnSelectDlg.cpp @@ -0,0 +1,499 @@ +/* Project Starshatter 5.0 + Destroyer Studios LLC + Copyright © 1997-2007. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: MsnSelectDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Mission Select Dialog Active Window class +*/ + +#include "MemDebug.h" +#include "MsnSelectDlg.h" +#include "MsnEditDlg.h" +#include "MsnEditNavDlg.h" +#include "ConfirmDlg.h" +#include "MenuScreen.h" +#include "Starshatter.h" +#include "Campaign.h" +#include "Mission.h" + +#include "Game.h" +#include "DataLoader.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Slider.h" +#include "Video.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "ParseUtil.h" +#include "FormatUtil.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +DEF_MAP_CLIENT(MsnSelectDlg, OnAccept); +DEF_MAP_CLIENT(MsnSelectDlg, OnCancel); +DEF_MAP_CLIENT(MsnSelectDlg, OnMod); +DEF_MAP_CLIENT(MsnSelectDlg, OnNew); +DEF_MAP_CLIENT(MsnSelectDlg, OnEdit); +DEF_MAP_CLIENT(MsnSelectDlg, OnDel); +DEF_MAP_CLIENT(MsnSelectDlg, OnDelConfirm); +DEF_MAP_CLIENT(MsnSelectDlg, OnCampaignSelect); +DEF_MAP_CLIENT(MsnSelectDlg, OnMissionSelect); + +// +--------------------------------------------------------------------+ + +static Mission* edit_mission; + +// +--------------------------------------------------------------------+ + +MsnSelectDlg::MsnSelectDlg(Screen* s, FormDef& def, MenuScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), manager(mgr), + cmb_campaigns(0), lst_campaigns(0), lst_missions(0), + btn_accept(0), btn_cancel(0), btn_mod(0), + btn_new(0), btn_edit(0), btn_del(0), editable(false), + description(0), stars(0), campaign(0), + selected_mission(-1), mission_id(0) +{ + stars = Starshatter::GetInstance(); + campaign = Campaign::GetCampaign(); + edit_mission = 0; + + Init(def); +} + +MsnSelectDlg::~MsnSelectDlg() +{ +} + +// +--------------------------------------------------------------------+ + +void +MsnSelectDlg::RegisterControls() +{ + btn_accept = (Button*) FindControl( 1); + btn_cancel = (Button*) FindControl( 2); + + if (btn_accept) { + btn_accept->SetEnabled(false); + REGISTER_CLIENT(EID_CLICK, btn_accept, MsnSelectDlg, OnAccept); + } + + if (btn_cancel) { + REGISTER_CLIENT(EID_CLICK, btn_cancel, MsnSelectDlg, OnCancel); + } + + btn_mod = (Button*) FindControl(300); + btn_new = (Button*) FindControl(301); + btn_edit = (Button*) FindControl(302); + btn_del = (Button*) FindControl(303); + + if (btn_mod) + REGISTER_CLIENT(EID_CLICK, btn_mod, MsnSelectDlg, OnMod); + + if (btn_new) + REGISTER_CLIENT(EID_CLICK, btn_new, MsnSelectDlg, OnNew); + + if (btn_edit) + REGISTER_CLIENT(EID_CLICK, btn_edit, MsnSelectDlg, OnEdit); + + if (btn_del) { + REGISTER_CLIENT(EID_CLICK, btn_del, MsnSelectDlg, OnDel); + REGISTER_CLIENT(EID_USER_1, btn_del, MsnSelectDlg, OnDelConfirm); + } + + description = FindControl(200); + + cmb_campaigns = (ComboBox*) FindControl(201); + lst_campaigns = (ListBox*) FindControl(203); + lst_missions = (ListBox*) FindControl(202); + + if (cmb_campaigns) { + REGISTER_CLIENT(EID_SELECT, cmb_campaigns, MsnSelectDlg, OnCampaignSelect); + } + + if (lst_campaigns) { + REGISTER_CLIENT(EID_SELECT, lst_campaigns, MsnSelectDlg, OnCampaignSelect); + + lst_campaigns->SetSelectedStyle(ListBox::LIST_ITEM_STYLE_FILLED_BOX); + lst_campaigns->SetLeading(4); + } + + if (lst_missions) { + REGISTER_CLIENT(EID_SELECT, lst_missions, MsnSelectDlg, OnMissionSelect); + + lst_missions->SetSelectedStyle(ListBox::LIST_ITEM_STYLE_FILLED_BOX); + lst_missions->SetLeading(4); + } +} + +// +--------------------------------------------------------------------+ + +void +MsnSelectDlg::Show() +{ + FormWindow::Show(); + campaign = Campaign::GetCampaign(); + + if (cmb_campaigns) { + int n = 0; + cmb_campaigns->ClearItems(); + ListIter iter = Campaign::GetAllCampaigns(); + while (++iter) { + Campaign* c = iter.value(); + + if (c->GetCampaignId() >= Campaign::SINGLE_MISSIONS) { + cmb_campaigns->AddItem(c->Name()); + + if (campaign->GetCampaignId() < Campaign::SINGLE_MISSIONS) { + campaign = Campaign::SelectCampaign(c->Name()); + cmb_campaigns->SetSelection(n); + } + + else if (campaign->GetCampaignId() == c->GetCampaignId()) { + cmb_campaigns->SetSelection(n); + } + + n++; + } + } + } + + else if (lst_campaigns) { + int n = 0; + lst_campaigns->ClearItems(); + ListIter iter = Campaign::GetAllCampaigns(); + while (++iter) { + Campaign* c = iter.value(); + + if (c->GetCampaignId() >= Campaign::SINGLE_MISSIONS) { + lst_campaigns->AddItem(c->Name()); + + if (campaign->GetCampaignId() < Campaign::SINGLE_MISSIONS) { + campaign = Campaign::SelectCampaign(c->Name()); + lst_campaigns->SetSelected(n); + } + + else if (campaign->GetCampaignId() == c->GetCampaignId()) { + lst_campaigns->SetSelected(n); + } + + n++; + } + } + } + + if (campaign) { + int id = campaign->GetCampaignId(); + editable = (id >= Campaign::MULTIPLAYER_MISSIONS && + id <= Campaign::CUSTOM_MISSIONS); + + if (btn_new) btn_new->SetEnabled(editable); + if (btn_edit) btn_edit->SetEnabled(false); + if (btn_del) btn_del->SetEnabled(false); + } + + if (description) + description->SetText(Game::GetText("MsnSelectDlg.choose")); + + if (lst_missions) { + lst_missions->ClearItems(); + + if (campaign) { + ListIter iter = campaign->GetMissionList(); + while (++iter) { + MissionInfo* info = iter.value(); + Mission* m = info->mission; + + lst_missions->AddItem(info->name); + + if (m && m == edit_mission) { + lst_missions->SetSelected(lst_missions->NumItems()-1); + } + } + + if (selected_mission >= 0 && lst_missions->GetSelCount() == 0) { + lst_missions->SetSelected(selected_mission); + } + } + + OnMissionSelect(0); + edit_mission = 0; + } +} + +// +--------------------------------------------------------------------+ + +void +MsnSelectDlg::OnCampaignSelect(AWEvent* event) +{ + const char* selected_campaign = 0; + + if (cmb_campaigns) + selected_campaign = cmb_campaigns->GetSelectedItem(); + else if (lst_campaigns) + selected_campaign = lst_campaigns->GetSelectedItem(); + + Campaign* c = Campaign::SelectCampaign(selected_campaign); + + if (c) { + campaign = c; + + if (cmb_campaigns) { + cmb_campaigns->ClearItems(); + + ListIter iter = campaign->GetMissionList(); + while (++iter) { + cmb_campaigns->AddItem(iter->name); + } + } + + else if (lst_missions) { + lst_missions->ClearItems(); + + ListIter iter = campaign->GetMissionList(); + while (++iter) { + lst_missions->AddItem(iter->name); + } + + lst_missions->ScrollTo(0); + } + + if (btn_accept) + btn_accept->SetEnabled(false); + + if (description) + description->SetText(Game::GetText("MsnSelectDlg.choose")); + + int id = c->GetCampaignId(); + editable = (id >= Campaign::MULTIPLAYER_MISSIONS && + id <= Campaign::CUSTOM_MISSIONS); + + if (btn_new) btn_new->SetEnabled(editable); + if (btn_edit) btn_edit->SetEnabled(false); + if (btn_del) btn_del->SetEnabled(false); + } +} + +void +MsnSelectDlg::OnMissionSelect(AWEvent* event) +{ + selected_mission = -1; + + for (int i = 0; i < lst_missions->NumItems(); i++) + if (lst_missions->IsSelected(i)) + selected_mission = i; + + if (btn_accept && description && campaign) { + List& mission_info_list = campaign->GetMissionList(); + + if (selected_mission >= 0 && selected_mission < mission_info_list.size()) { + MissionInfo* info = mission_info_list[selected_mission]; + mission_id = info->id; + + char time_buf[32]; + FormatDayTime(time_buf, info->start); + + Text d(""); + d += info->name; + d += "\n\n"; + d += Game::GetText("MsnSelectDlg.mission-type"); + d += "\n\t"; + d += Mission::RoleName(info->type); + d += "\n\n"; + d += Game::GetText("MsnSelectDlg.scenario"); + d += "\n\t"; + d += info->description; + d += "\n\n"; + d += Game::GetText("MsnSelectDlg.location"); + d += "\n\t"; + d += info->region; + d += " "; + d += Game::GetText("MsnSelectDlg.sector"); + d += " / "; + d += info->system; + d += " "; + d += Game::GetText("MsnSelectDlg.system"); + d += "\n\n"; + d += Game::GetText("MsnSelectDlg.start-time"); + d += "\n\t"; + d += time_buf; + + description->SetText(d); + btn_accept->SetEnabled(true); + + if (btn_edit) btn_edit->SetEnabled(editable); + if (btn_del) btn_del->SetEnabled(editable); + } + + else { + description->SetText(Game::GetText("MsnSelectDlg.choose")); + btn_accept->SetEnabled(false); + + if (btn_edit) btn_edit->SetEnabled(false); + if (btn_del) btn_del->SetEnabled(false); + } + } +} + +// +--------------------------------------------------------------------+ + +void +MsnSelectDlg::ExecFrame() +{ + if (Keyboard::KeyDown(VK_RETURN)) { + if (btn_accept && btn_accept->IsEnabled()) + OnAccept(0); + } +} + +// +--------------------------------------------------------------------+ + +void +MsnSelectDlg::OnMod(AWEvent* event) +{ +} + +void +MsnSelectDlg::OnNew(AWEvent* event) +{ + const char* cname = 0; + + if (cmb_campaigns) + cname = cmb_campaigns->GetSelectedItem(); + else if (lst_campaigns) + cname = lst_campaigns->GetSelectedItem(); + + Campaign* c = Campaign::SelectCampaign(cname); + if (!c) return; + + MissionInfo* info = c->CreateNewMission(); + if (!info || !info->mission) + return; + + mission_id = info->id; + + MsnEditDlg* editor = manager->GetMsnEditDlg(); + + if (editor) { + edit_mission = info->mission; + + editor->SetMissionInfo(info); + editor->SetMission(info->mission); + manager->ShowMsnEditDlg(); + } + + MsnEditNavDlg* navdlg = (MsnEditNavDlg*) manager->GetNavDlg(); + + if (navdlg) { + navdlg->SetMission(info->mission); + navdlg->SetMissionInfo(info); + } +} + +void +MsnSelectDlg::OnEdit(AWEvent* event) +{ + const char* cname = 0; + + if (cmb_campaigns) + cname = cmb_campaigns->GetSelectedItem(); + else if (lst_campaigns) + cname = lst_campaigns->GetSelectedItem(); + + Campaign* c = Campaign::SelectCampaign(cname); + if (!c) return; + + Mission* m = c->GetMission(mission_id); + if (!m) return; + + MsnEditDlg* editor = manager->GetMsnEditDlg(); + + if (editor) { + edit_mission = m; + + editor->SetMissionInfo(c->GetMissionInfo(mission_id)); + editor->SetMission(m); + manager->ShowMsnEditDlg(); + } +} + +void +MsnSelectDlg::OnDel(AWEvent* event) +{ + const char* cname = 0; + + if (cmb_campaigns) + cname = cmb_campaigns->GetSelectedItem(); + else if (lst_campaigns) + cname = lst_campaigns->GetSelectedItem(); + + Campaign* c = Campaign::SelectCampaign(cname); + if (!c) return; + + Mission* m = c->GetMission(mission_id); + if (!m) return; + + ConfirmDlg* confirm = manager->GetConfirmDlg(); + if (confirm) { + char msg[256]; + sprintf(msg, Game::GetText("MsnSelectDlg.are-you-sure").data(), m->Name()); + confirm->SetMessage(msg); + confirm->SetTitle(Game::GetText("MsnSelectDlg.confirm-delete")); + confirm->SetParentControl(btn_del); + + manager->ShowConfirmDlg(); + } + + else { + OnDelConfirm(event); + } +} + +void +MsnSelectDlg::OnDelConfirm(AWEvent* event) +{ + const char* cname = 0; + + if (cmb_campaigns) + cname = cmb_campaigns->GetSelectedItem(); + else if (lst_campaigns) + cname = lst_campaigns->GetSelectedItem(); + + Campaign* c = Campaign::SelectCampaign(cname); + if (!c) return; + + edit_mission = 0; + c->DeleteMission(mission_id); + Show(); +} + +// +--------------------------------------------------------------------+ + +void +MsnSelectDlg::OnAccept(AWEvent* event) +{ + if (selected_mission >= 0) { + Mouse::Show(false); + + int id = campaign->GetMissionList()[selected_mission]->id; + campaign->SetMissionId(id); + campaign->ReloadMission(id); + + stars->SetGameMode(Starshatter::PREP_MODE); + } +} + +void +MsnSelectDlg::OnCancel(AWEvent* event) +{ + manager->ShowMenuDlg(); +} + +// +--------------------------------------------------------------------+ diff --git a/Stars45/MsnSelectDlg.h b/Stars45/MsnSelectDlg.h new file mode 100644 index 0000000..a9cef55 --- /dev/null +++ b/Stars45/MsnSelectDlg.h @@ -0,0 +1,81 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: MsnSelectDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Mission Select Dialog Active Window class +*/ + +#ifndef MsnSelectDlg_h +#define MsnSelectDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "Bitmap.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Font.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class MenuScreen; +class Campaign; +class Starshatter; + +// +--------------------------------------------------------------------+ + +class MsnSelectDlg : public FormWindow +{ +public: + MsnSelectDlg(Screen* s, FormDef& def, MenuScreen* mgr); + virtual ~MsnSelectDlg(); + + virtual void RegisterControls(); + virtual void Show(); + virtual void ExecFrame(); + + // Operations: + virtual void OnCampaignSelect(AWEvent* event); + virtual void OnMissionSelect(AWEvent* event); + + virtual void OnMod(AWEvent* event); + virtual void OnNew(AWEvent* event); + virtual void OnEdit(AWEvent* event); + virtual void OnDel(AWEvent* event); + virtual void OnDelConfirm(AWEvent* event); + virtual void OnAccept(AWEvent* event); + virtual void OnCancel(AWEvent* event); + +protected: + MenuScreen* manager; + + Button* btn_mod; + Button* btn_new; + Button* btn_edit; + Button* btn_del; + Button* btn_accept; + Button* btn_cancel; + + ComboBox* cmb_campaigns; + ListBox* lst_campaigns; + ListBox* lst_missions; + + ActiveWindow* description; + + Starshatter* stars; + Campaign* campaign; + int selected_mission; + int mission_id; + bool editable; +}; + +#endif MsnSelectDlg_h + diff --git a/Stars45/MsnWepDlg.cpp b/Stars45/MsnWepDlg.cpp new file mode 100644 index 0000000..15cb125 --- /dev/null +++ b/Stars45/MsnWepDlg.cpp @@ -0,0 +1,517 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: MsnWepDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Mission Briefing Dialog Active Window class +*/ + +#include "MemDebug.h" +#include "MsnWepDlg.h" +#include "PlanScreen.h" +#include "Starshatter.h" +#include "Campaign.h" +#include "Mission.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "WeaponDesign.h" +#include "HardPoint.h" +#include "StarSystem.h" +#include "FormatUtil.h" + +#include "Game.h" +#include "Mouse.h" +#include "Button.h" +#include "ListBox.h" +#include "Slider.h" +#include "ParseUtil.h" +#include "Keyboard.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: +DEF_MAP_CLIENT(MsnWepDlg, OnCommit); +DEF_MAP_CLIENT(MsnWepDlg, OnCancel); +DEF_MAP_CLIENT(MsnWepDlg, OnTabButton); +DEF_MAP_CLIENT(MsnWepDlg, OnMount); +DEF_MAP_CLIENT(MsnWepDlg, OnLoadout); + +// +--------------------------------------------------------------------+ + +MsnWepDlg::MsnWepDlg(Screen* s, FormDef& def, PlanScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), MsnDlg(mgr), + elem(0), first_station(0), beauty(0), player_desc(0) +{ + campaign = Campaign::GetCampaign(); + + if (campaign) + mission = campaign->GetMission(); + + ZeroMemory(designs, sizeof(designs)); + ZeroMemory(mounts, sizeof(mounts)); + Init(def); +} + +MsnWepDlg::~MsnWepDlg() +{ +} + +// +--------------------------------------------------------------------+ + +void +MsnWepDlg::RegisterControls() +{ + lbl_element = FindControl(601); + lbl_type = FindControl(602); + lbl_weight = FindControl(603); + loadout_list = (ListBox*) FindControl(604); + beauty = (ImageBox*) FindControl(300); + player_desc = FindControl(301); + + if (loadout_list) + REGISTER_CLIENT(EID_SELECT, loadout_list, MsnWepDlg, OnLoadout); + + for (int i = 0; i < 8; i++) { + lbl_desc[i] = FindControl(500 + i*10); + lbl_station[i] = FindControl(401 + i); + + for (int n = 0; n < 8; n++) { + btn_load[i][n] = (Button*) FindControl(500 + i*10 + n + 1); + + if (btn_load[i][n]) { + if (i == 0) { + if (n == 0) + btn_load[i][n]->GetPicture(led_off); + else if (n == 1) + btn_load[i][n]->GetPicture(led_on); + } + + btn_load[i][n]->SetPicture(led_off); + btn_load[i][n]->SetPictureLocation(4); // centered + REGISTER_CLIENT(EID_CLICK, btn_load[i][n], MsnWepDlg, OnMount); + } + } + } + + RegisterMsnControls(this); + + if (commit) + REGISTER_CLIENT(EID_CLICK, commit, MsnWepDlg, OnCommit); + + if (cancel) + REGISTER_CLIENT(EID_CLICK, cancel, MsnWepDlg, OnCancel); + + if (sit_button) + REGISTER_CLIENT(EID_CLICK, sit_button, MsnWepDlg, OnTabButton); + + if (pkg_button) + REGISTER_CLIENT(EID_CLICK, pkg_button, MsnWepDlg, OnTabButton); + + if (nav_button) + REGISTER_CLIENT(EID_CLICK, nav_button, MsnWepDlg, OnTabButton); + + if (wep_button) + REGISTER_CLIENT(EID_CLICK, wep_button, MsnWepDlg, OnTabButton); +} + +// +--------------------------------------------------------------------+ + +void +MsnWepDlg::Show() +{ + FormWindow::Show(); + ShowMsnDlg(); + + if (mission) { + for (int i = 0; i < mission->GetElements().size(); i++) { + MissionElement* e = mission->GetElements().at(i); + if (e->Player()) { + elem = e; + break; + } + } + } + + if (elem) { + SetupControls(); + } +} + +// +--------------------------------------------------------------------+ + +void +MsnWepDlg::SetupControls() +{ + ShipDesign* design = (ShipDesign*) elem->GetDesign(); + + if (lbl_element) + lbl_element->SetText(elem->Name()); + + if (lbl_type) + lbl_type->SetText(design->name); + + BuildLists(); + + for (int i = 0; i < 8; i++) { + if (!lbl_desc[i]) continue; + + if (designs[i]) { + lbl_desc[i]->Show(); + lbl_desc[i]->SetText(designs[i]->group + " " + designs[i]->name); + + for (int n = 0; n < 8; n++) { + if (mounts[i][n]) { + btn_load[i][n]->Show(); + btn_load[i][n]->SetPicture((loads[n]==i) ? led_on : led_off); + } + else { + btn_load[i][n]->Hide(); + } + } + } + else { + lbl_desc[i]->Hide(); + + for (int n = 0; n < 8; n++) { + btn_load[i][n]->Hide(); + } + } + } + + double loaded_mass = 0; + char weight[32]; + + if (loadout_list) { + loadout_list->ClearItems(); + + if (design) { + ListIter sl = (List&) design->loadouts; + while (++sl) { + ShipLoad* load = sl.value(); + int item = loadout_list->AddItem(load->name) - 1; + + sprintf(weight, "%d kg", (int) ((design->mass + load->mass) * 1000)); + loadout_list->SetItemText(item, 1, weight); + loadout_list->SetItemData(item, 1, (DWORD) (load->mass * 1000)); + + if (elem->Loadouts().size() > 0 && + elem->Loadouts().at(0)->GetName() == load->name) { + loadout_list->SetSelected(item, true); + loaded_mass = design->mass + load->mass; + } + } + } + } + + if (lbl_weight) { + if (loaded_mass < 1) + loaded_mass = design->mass; + + sprintf(weight, "%d kg", (int) (loaded_mass * 1000)); + lbl_weight->SetText(weight); + } + + if (beauty && design) { + beauty->SetPicture(design->beauty); + } + + if (player_desc && design) { + char txt[256]; + + if (design->type <= Ship::ATTACK) + sprintf(txt, "%s %s", design->abrv, design->display_name); + else + sprintf(txt, "%s %s", design->abrv, elem->Name().data()); + + player_desc->SetText(txt); + } +} + +// +--------------------------------------------------------------------+ + +void +MsnWepDlg::BuildLists() +{ + ZeroMemory(designs, sizeof(designs)); + ZeroMemory(mounts, sizeof(mounts)); + + if (elem) { + ShipDesign* d = (ShipDesign*) elem->GetDesign(); + int nstations = d->hard_points.size(); + + first_station = (8 - nstations) / 2; + + int index = 0; + int station = first_station; + + for (int s = 0; s < 8; s++) + if (lbl_station[s]) + lbl_station[s]->SetText(""); + + ListIter iter = d->hard_points; + while (++iter) { + HardPoint* hp = iter.value(); + + if (lbl_station[station]) + lbl_station[station]->SetText(hp->GetAbbreviation()); + + for (int n = 0; n < HardPoint::MAX_DESIGNS; n++) { + WeaponDesign* wep_dsn = hp->GetWeaponDesign(n); + + if (wep_dsn) { + bool found = false; + + for (int i = 0; i < 8 && !found; i++) { + if (designs[i] == wep_dsn) { + found = true; + mounts[i][station] = true; + } + } + + if (!found) { + mounts[index][station] = true; + designs[index++] = wep_dsn; + } + } + } + + station++; + } + + if (elem->Loadouts().size()) { + MissionLoad* msn_load = elem->Loadouts().at(0); + + for (int i = 0; i < 8; i++) + loads[i] = -1; + + // map loadout: + int* loadout = 0; + if (msn_load->GetName().length()) { + ListIter sl = ((ShipDesign*) elem->GetDesign())->loadouts; + while (++sl) { + if (!stricmp(sl->name, msn_load->GetName())) + loadout = sl->load; + } + } + else { + loadout = msn_load->GetStations(); + } + + for (i = 0; i < nstations; i++) { + loads[i + first_station] = loadout[i]; + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +MsnWepDlg::ExecFrame() +{ + if (Keyboard::KeyDown(VK_RETURN)) { + OnCommit(0); + } +} + +// +--------------------------------------------------------------------+ + +int +MsnWepDlg::LoadToPointIndex(int n) +{ + int nn = n + first_station; + + if (!elem || nn < 0 || nn >= 8 || loads[nn] == -1) + return -1; + + int index = -1; + WeaponDesign* wep_design = designs[ loads[nn] ]; + ShipDesign* design = (ShipDesign*) elem->GetDesign(); + HardPoint* hard_point = design->hard_points[n]; + + for (int i = 0; i < 8 && index < 0; i++) { + if (hard_point->GetWeaponDesign(i) == wep_design) { + index = i; + } + } + + return index; +} + +int +MsnWepDlg::PointIndexToLoad(int n, int index) +{ + int nn = n + first_station; + + if (!elem || nn < 0 || nn >= 8) + return -1; + + int result = -1; + ShipDesign* design = (ShipDesign*) elem->GetDesign(); + HardPoint* hard_point = design->hard_points[n]; + WeaponDesign* wep_design = hard_point->GetWeaponDesign(index); + + for (int i = 0; i < 8 && result < 0; i++) { + if (designs[i] == wep_design) { + result = i; + } + } + + return result; +} + +// +--------------------------------------------------------------------+ + +void +MsnWepDlg::OnMount(AWEvent* event) +{ + int station = -1; + int item = -1; + + for (int i = 0; i < 8 && item < 0; i++) { + for (int n = 0; n < 8 && station < 0; n++) { + if (btn_load[i][n] == event->window) { + station = n; + item = i; + } + } + } + + if (item >= 0 && station >= 0) { + if (loads[station] == item) + item = -1; + + loads[station] = item; + + for (int n = 0; n < 8; n++) { + btn_load[n][station]->SetPicture(n == item ? led_on : led_off); + } + + if (elem) { + int nstations = elem->GetDesign()->hard_points.size(); + + if (elem->Loadouts().size() < 1) { + MissionLoad* l = new(__FILE__,__LINE__) MissionLoad; + elem->Loadouts().append(l); + + for (int n = 0; n < nstations; n++) + l->SetStation(n, LoadToPointIndex(n)); + } + else { + ListIter l = elem->Loadouts(); + while (++l) { + // if the player customizes the loadout, + // tell the sim loader not to use a named + // loadout from the ship design: + l->SetName(""); + + for (int n = 0; n < nstations; n++) + l->SetStation(n, LoadToPointIndex(n)); + } + } + } + } + + if (loadout_list) + loadout_list->ClearSelection(); + + if (lbl_weight && elem) { + ShipDesign* d = (ShipDesign*) elem->GetDesign(); + int nstations = d->hard_points.size(); + double mass = d->mass; + + for (int n = 0; n < nstations; n++) { + int item = loads[n+first_station]; + mass += d->hard_points[n]->GetCarryMass(item); + } + + char weight[32]; + sprintf(weight, "%d kg", (int) (mass * 1000)); + lbl_weight->SetText(weight); + } +} + +// +--------------------------------------------------------------------+ + +void +MsnWepDlg::OnLoadout(AWEvent* event) +{ + if (!elem) return; + + ShipDesign* design = (ShipDesign*) elem->GetDesign(); + ShipLoad* shipload = 0; + + if (loadout_list && design) { + int index = loadout_list->GetListIndex(); + Text loadname = loadout_list->GetItemText(index); + + ListIter sl = (List&) design->loadouts; + while (++sl) { + if (sl->name == loadname) { + shipload = sl.value(); + } + } + + if (!shipload) return; + + if (lbl_weight) { + char weight[32]; + sprintf(weight, "%d kg", (int) ((design->mass + shipload->mass) * 1000)); + lbl_weight->SetText(weight); + } + + if (elem->Loadouts().size() < 1) { + MissionLoad* l = new(__FILE__,__LINE__) MissionLoad(-1, shipload->name); + elem->Loadouts().append(l); + } + else { + ListIter l = elem->Loadouts(); + while (++l) { + // if the player chooses a std loadout, + // tell the sim loader to use a named + // loadout from the ship design: + l->SetName(shipload->name); + } + } + + int nstations = design->hard_points.size(); + int* loadout = shipload->load; + + for (int i = 0; i < 8; i++) + loads[i] = -1; + + for (i = 0; i < nstations; i++) + loads[i + first_station] = PointIndexToLoad(i, loadout[i]); + + for (i = 0; i < 8; i++) { + for (int n = 0; n < 8; n++) { + btn_load[i][n]->SetPicture(i == loads[n] ? led_on: led_off); + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +MsnWepDlg::OnCommit(AWEvent* event) +{ + MsnDlg::OnCommit(event); +} + +void +MsnWepDlg::OnCancel(AWEvent* event) +{ + MsnDlg::OnCancel(event); +} + +void +MsnWepDlg::OnTabButton(AWEvent* event) +{ + MsnDlg::OnTabButton(event); +} diff --git a/Stars45/MsnWepDlg.h b/Stars45/MsnWepDlg.h new file mode 100644 index 0000000..e9d82be --- /dev/null +++ b/Stars45/MsnWepDlg.h @@ -0,0 +1,86 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: MsnWepDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Mission Briefing Dialog Active Window class +*/ + +#ifndef MsnWepDlg_h +#define MsnWepDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "MsnDlg.h" +#include "Bitmap.h" +#include "Button.h" +#include "ImageBox.h" +#include "ListBox.h" +#include "Font.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class PlanScreen; +class Campaign; +class Mission; +class MissionElement; +class HardPoint; +class WeaponDesign; + +// +--------------------------------------------------------------------+ + +class MsnWepDlg : public FormWindow, + public MsnDlg +{ +public: + MsnWepDlg(Screen* s, FormDef& def, PlanScreen* mgr); + virtual ~MsnWepDlg(); + + virtual void RegisterControls(); + virtual void ExecFrame(); + virtual void Show(); + + // Operations: + virtual void OnCommit(AWEvent* event); + virtual void OnCancel(AWEvent* event); + virtual void OnTabButton(AWEvent* event); + virtual void OnMount(AWEvent* event); + virtual void OnLoadout(AWEvent* event); + +protected: + virtual void SetupControls(); + virtual void BuildLists(); + virtual int LoadToPointIndex(int n); + virtual int PointIndexToLoad(int n, int index); + + ActiveWindow* lbl_element; + ActiveWindow* lbl_type; + ActiveWindow* lbl_weight; + ActiveWindow* player_desc; + ImageBox* beauty; + + ActiveWindow* lbl_station[8]; + ActiveWindow* lbl_desc[8]; + Button* btn_load[8][8]; + + ListBox* loadout_list; + + MissionElement* elem; + WeaponDesign* designs[8]; + bool mounts[8][8]; + int loads[8]; + int first_station; + + Bitmap led_off; + Bitmap led_on; +}; + +#endif MsnWepDlg_h + diff --git a/Stars45/MusicDirector.cpp b/Stars45/MusicDirector.cpp new file mode 100644 index 0000000..76199af --- /dev/null +++ b/Stars45/MusicDirector.cpp @@ -0,0 +1,520 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: MusicDirector.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Music Director class to manage selection, setup, and playback + of background music tracks for both menu and game modes +*/ + + +#include "MemDebug.h" +#include "MusicDirector.h" +#include "MusicTrack.h" + +#include "Random.h" +#include "DataLoader.h" +#include "FormatUtil.h" +#include "Sound.h" + +static MusicDirector* music_director = 0; + +// +-------------------------------------------------------------------+ + +MusicDirector::MusicDirector() + : mode(0), transition(0), track(0), next_track(0), no_music(true), + hproc(0) +{ + music_director = this; + + ScanTracks(); + + if (!no_music) + StartThread(); +} + +MusicDirector::~MusicDirector() +{ + StopThread(); + + delete track; + delete next_track; + + menu_tracks.destroy(); + intro_tracks.destroy(); + brief_tracks.destroy(); + debrief_tracks.destroy(); + promote_tracks.destroy(); + flight_tracks.destroy(); + combat_tracks.destroy(); + launch_tracks.destroy(); + recovery_tracks.destroy(); + victory_tracks.destroy(); + defeat_tracks.destroy(); + credit_tracks.destroy(); + + if (this == music_director) + music_director = 0; +} + +// +--------------------------------------------------------------------+ + +void +MusicDirector::Initialize() +{ + if (music_director) delete music_director; + music_director = new(__FILE__,__LINE__) MusicDirector(); +} + +void +MusicDirector::Close() +{ + delete music_director; + music_director = 0; +} + +MusicDirector* +MusicDirector::GetInstance() +{ + return music_director; +} + +// +-------------------------------------------------------------------+ + +void +MusicDirector::ExecFrame() +{ + if (no_music) return; + + AutoThreadSync a(sync); + + if (next_track && !track) { + track = next_track; + next_track = 0; + } + + if (track) { + if (track->IsDone()) { + if (mode != NONE && mode != SHUTDOWN && next_track == 0) { + GetNextTrack(track->GetIndex()+1); + } + + delete track; + track = next_track; + next_track = 0; + } + + else if (track->IsLooped()) { + if (mode != NONE && mode != SHUTDOWN && next_track == 0) { + GetNextTrack(track->GetIndex()+1); + } + + track->FadeOut(); + track->ExecFrame(); + } + + else { + track->ExecFrame(); + } + } + + if (next_track) { + if (next_track->IsDone()) { + delete next_track; + next_track = 0; + } + + else if (next_track->IsLooped()) { + next_track->FadeOut(); + next_track->ExecFrame(); + } + + else { + next_track->ExecFrame(); + } + } +} + +// +-------------------------------------------------------------------+ + +void +MusicDirector::ScanTracks() +{ + DataLoader* loader = DataLoader::GetLoader(); + + bool old_file_system = loader->IsFileSystemEnabled(); + loader->UseFileSystem(true); + loader->SetDataPath("Music/"); + + List files; + loader->ListFiles("*.ogg", files, true); + + if (files.size() == 0) { + loader->UseFileSystem(old_file_system); + no_music = true; + return; + } + + no_music = false; + + ListIter iter = files; + while (++iter) { + Text* name = iter.value(); + Text* file = new(__FILE__,__LINE__) Text("Music/"); + + name->setSensitive(false); + file->append(*name); + + if (name->indexOf("Menu") == 0) { + menu_tracks.append(file); + } + + else if (name->indexOf("Intro") == 0) { + intro_tracks.append(file); + } + + else if (name->indexOf("Brief") == 0) { + brief_tracks.append(file); + } + + else if (name->indexOf("Debrief") == 0) { + debrief_tracks.append(file); + } + + else if (name->indexOf("Promot") == 0) { + promote_tracks.append(file); + } + + else if (name->indexOf("Flight") == 0) { + flight_tracks.append(file); + } + + else if (name->indexOf("Combat") == 0) { + combat_tracks.append(file); + } + + else if (name->indexOf("Launch") == 0) { + launch_tracks.append(file); + } + + else if (name->indexOf("Recovery") == 0) { + recovery_tracks.append(file); + } + + else if (name->indexOf("Victory") == 0) { + victory_tracks.append(file); + } + + else if (name->indexOf("Defeat") == 0) { + defeat_tracks.append(file); + } + + else if (name->indexOf("Credit") == 0) { + credit_tracks.append(file); + } + + else { + menu_tracks.append(file); + } + + delete iter.removeItem(); + } + + loader->UseFileSystem(old_file_system); + + menu_tracks.sort(); + intro_tracks.sort(); + brief_tracks.sort(); + debrief_tracks.sort(); + promote_tracks.sort(); + flight_tracks.sort(); + combat_tracks.sort(); + launch_tracks.sort(); + recovery_tracks.sort(); + victory_tracks.sort(); + defeat_tracks.sort(); + credit_tracks.sort(); +} + +// +-------------------------------------------------------------------+ + +const char* +MusicDirector::GetModeName(int mode) +{ + switch (mode) { + case NONE: return "NONE"; + case MENU: return "MENU"; + case INTRO: return "INTRO"; + case BRIEFING: return "BRIEFING"; + case DEBRIEFING: return "DEBRIEFING"; + case PROMOTION: return "PROMOTION"; + case FLIGHT: return "FLIGHT"; + case COMBAT: return "COMBAT"; + case LAUNCH: return "LAUNCH"; + case RECOVERY: return "RECOVERY"; + case VICTORY: return "VICTORY"; + case DEFEAT: return "DEFEAT"; + case CREDITS: return "CREDITS"; + case SHUTDOWN: return "SHUTDOWN"; + } + + return "UNKNOWN?"; +} + +// +-------------------------------------------------------------------+ + +void +MusicDirector::SetMode(int mode) +{ + if (!music_director || music_director->no_music) return; + + AutoThreadSync a(music_director->sync); + + // stay in intro mode until it is complete: + if (mode == MENU && (music_director->GetMode() == NONE || + music_director->GetMode() == INTRO)) + mode = INTRO; + + mode = music_director->CheckMode(mode); + + if (mode != music_director->mode) { + ::Print("MusicDirector::SetMode() old: %s new: %s\n", + GetModeName(music_director->mode), + GetModeName(mode)); + + music_director->mode = mode; + + MusicTrack* t = music_director->track; + if (t && t->GetState() && !t->IsDone()) { + if (mode == NONE || mode == SHUTDOWN) + t->SetFadeTime(0.5); + + t->FadeOut(); + } + + t = music_director->next_track; + if (t && t->GetState() && !t->IsDone()) { + if (mode == NONE || mode == SHUTDOWN) + t->SetFadeTime(0.5); + t->FadeOut(); + + delete music_director->track; + music_director->track = t; + music_director->next_track = 0; + } + + music_director->ShuffleTracks(); + music_director->GetNextTrack(0); + + if (music_director->next_track) + music_director->next_track->FadeIn(); + } +} + +int +MusicDirector::CheckMode(int req_mode) +{ + if (req_mode == RECOVERY && recovery_tracks.size() == 0) + req_mode = LAUNCH; + + if (req_mode == LAUNCH && launch_tracks.size() == 0) + req_mode = FLIGHT; + + if (req_mode == COMBAT && combat_tracks.size() == 0) + req_mode = FLIGHT; + + if (req_mode == FLIGHT && flight_tracks.size() == 0) + req_mode = NONE; + + if (req_mode == PROMOTION && promote_tracks.size() == 0) + req_mode = VICTORY; + + if (req_mode == DEBRIEFING && debrief_tracks.size() == 0) + req_mode = BRIEFING; + + if (req_mode == BRIEFING && brief_tracks.size() == 0) + req_mode = MENU; + + if (req_mode == INTRO && intro_tracks.size() == 0) + req_mode = MENU; + + if (req_mode == VICTORY && victory_tracks.size() == 0) + req_mode = MENU; + + if (req_mode == DEFEAT && defeat_tracks.size() == 0) + req_mode = MENU; + + if (req_mode == CREDITS && credit_tracks.size() == 0) + req_mode = MENU; + + if (req_mode == MENU && menu_tracks.size() == 0) + req_mode = NONE; + + return req_mode; +} + +// +-------------------------------------------------------------------+ + +bool +MusicDirector::IsNoMusic() +{ + if (music_director) + return music_director->no_music; + + return true; +} + +// +-------------------------------------------------------------------+ + +void +MusicDirector::GetNextTrack(int index) +{ + List* tracks = 0; + + switch (mode) { + case MENU: tracks = &menu_tracks; break; + case INTRO: tracks = &intro_tracks; break; + case BRIEFING: tracks = &brief_tracks; break; + case DEBRIEFING: tracks = &debrief_tracks; break; + case PROMOTION: tracks = &promote_tracks; break; + case FLIGHT: tracks = &flight_tracks; break; + case COMBAT: tracks = &combat_tracks; break; + case LAUNCH: tracks = &launch_tracks; break; + case RECOVERY: tracks = &recovery_tracks; break; + case VICTORY: tracks = &victory_tracks; break; + case DEFEAT: tracks = &defeat_tracks; break; + case CREDITS: tracks = &credit_tracks; break; + default: tracks = 0; break; + } + + if (tracks && tracks->size()) { + if (next_track) + delete next_track; + + if (index < 0 || index >= tracks->size()) { + index = 0; + + if (mode == INTRO) { + mode = MENU; + ShuffleTracks(); + tracks = &menu_tracks; + + ::Print("MusicDirector: INTRO mode complete, switching to MENU\n"); + + if (!tracks || !tracks->size()) + return; + } + } + + next_track = new(__FILE__,__LINE__) MusicTrack(*tracks->at(index), mode, index); + next_track->FadeIn(); + } + + else if (next_track) { + next_track->FadeOut(); + } +} + +// +-------------------------------------------------------------------+ + +void +MusicDirector::ShuffleTracks() +{ + List* tracks = 0; + + switch (mode) { + case MENU: tracks = &menu_tracks; break; + case INTRO: tracks = &intro_tracks; break; + case BRIEFING: tracks = &brief_tracks; break; + case DEBRIEFING: tracks = &debrief_tracks; break; + case PROMOTION: tracks = &promote_tracks; break; + case FLIGHT: tracks = &flight_tracks; break; + case COMBAT: tracks = &combat_tracks; break; + case LAUNCH: tracks = &launch_tracks; break; + case RECOVERY: tracks = &recovery_tracks; break; + case VICTORY: tracks = &victory_tracks; break; + case DEFEAT: tracks = &defeat_tracks; break; + case CREDITS: tracks = &credit_tracks; break; + default: tracks = 0; break; + } + + if (tracks && tracks->size() > 1) { + tracks->sort(); + + Text* t = tracks->at(0); + + if (!isdigit(*t[0])) + tracks->shuffle(); + } +} + +// +--------------------------------------------------------------------+ + +DWORD WINAPI MusicDirectorThreadProc(LPVOID link); + +void +MusicDirector::StartThread() +{ + if (hproc != 0) { + DWORD result = 0; + GetExitCodeThread(hproc, &result); + + if (result != STILL_ACTIVE) { + CloseHandle(hproc); + hproc = 0; + } + else { + return; + } + } + + if (hproc == 0) { + DWORD thread_id = 0; + hproc = CreateThread(0, 4096, MusicDirectorThreadProc, (LPVOID) this, 0, &thread_id); + + if (hproc == 0) { + static int report = 10; + if (report > 0) { + ::Print("WARNING: MusicDirector failed to create thread (err=%08x)\n", GetLastError()); + report--; + + if (report == 0) + ::Print(" Further warnings of this type will be supressed.\n"); + } + } + } +} + +void +MusicDirector::StopThread() +{ + if (hproc != 0) { + SetMode(SHUTDOWN); + WaitForSingleObject(hproc, 1500); + CloseHandle(hproc); + hproc = 0; + } +} + +DWORD WINAPI MusicDirectorThreadProc(LPVOID link) +{ + MusicDirector* dir = (MusicDirector*) link; + + if (dir) { + while (dir->GetMode() != MusicDirector::SHUTDOWN) { + dir->ExecFrame(); + Sleep(100); + } + + return 0; + } + + return (DWORD) E_POINTER; +} + diff --git a/Stars45/MusicDirector.h b/Stars45/MusicDirector.h new file mode 100644 index 0000000..6299d5b --- /dev/null +++ b/Stars45/MusicDirector.h @@ -0,0 +1,114 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: MusicDirector.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Music Director class to manage selection, setup, and playback + of background music tracks for both menu and game modes +*/ + + +#ifndef MusicDirector_h +#define MusicDirector_h + +#include "Types.h" +#include "List.h" +#include "Text.h" + +// +-------------------------------------------------------------------+ + +class MusicTrack; + +// +-------------------------------------------------------------------+ + +class MusicDirector +{ +public: + enum MODES { + NONE, + + // menu modes: + + MENU, + INTRO, + BRIEFING, + DEBRIEFING, + PROMOTION, + VICTORY, + DEFEAT, + CREDITS, + + // in game modes: + + FLIGHT, + COMBAT, + LAUNCH, + RECOVERY, + + // special modes: + SHUTDOWN + }; + + enum TRANSITIONS { + CUT, + FADE_OUT, + FADE_IN, + FADE_BOTH, + CROSS_FADE + }; + + MusicDirector(); + ~MusicDirector(); + + // Operations: + void ExecFrame(); + void ScanTracks(); + + int CheckMode(int mode); + int GetMode() const { return mode; } + + static void Initialize(); + static void Close(); + static MusicDirector* GetInstance(); + static void SetMode(int mode); + static const char* GetModeName(int mode); + static bool IsNoMusic(); + +protected: + void StartThread(); + void StopThread(); + void GetNextTrack(int index); + void ShuffleTracks(); + + int mode; + int transition; + + MusicTrack* track; + MusicTrack* next_track; + + List menu_tracks; + List intro_tracks; + List brief_tracks; + List debrief_tracks; + List promote_tracks; + List flight_tracks; + List combat_tracks; + List launch_tracks; + List recovery_tracks; + List victory_tracks; + List defeat_tracks; + List credit_tracks; + + bool no_music; + + HANDLE hproc; + ThreadSync sync; +}; + +#endif MusicDirector_h \ No newline at end of file diff --git a/Stars45/MusicTrack.cpp b/Stars45/MusicTrack.cpp new file mode 100644 index 0000000..8e69890 --- /dev/null +++ b/Stars45/MusicTrack.cpp @@ -0,0 +1,273 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: MusicTrack.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Music Director class to manage selection, setup, and playback + of background music tracks for both menu and game modes +*/ + + +#include "MemDebug.h" +#include "MusicTrack.h" +#include "MusicDirector.h" +#include "Starshatter.h" +#include "AudioConfig.h" + +#include "Game.h" +#include "Sound.h" + +// +-------------------------------------------------------------------+ + +const double FADE_TIME = 1.5; +const double SILENCE = -5000; + +// +-------------------------------------------------------------------+ + +MusicTrack::MusicTrack(const Text& txt, int m, int n) + : name(txt), sound(0), state(NONE), mode(m), index(n), + fade(0), fade_time(FADE_TIME) +{ + long max_vol = 0; + + if (mode >= MusicDirector::FLIGHT) + max_vol = AudioConfig::GameMusic(); + else + max_vol = AudioConfig::MenuMusic(); + + if (max_vol <= AudioConfig::Silence()) + return; + + name.setSensitive(false); + + if (name.contains(".ogg")) { + sound = Sound::CreateOggStream(name); + + if (name.contains("-loop")) { + sound->SetFlags(Sound::STREAMED | + Sound::OGGVORBIS | + Sound::LOOP | + Sound::LOCKED); + } + + else { + sound->SetFlags(Sound::STREAMED | + Sound::OGGVORBIS | + Sound::LOCKED); + } + + sound->SetVolume((long) SILENCE); + } +} + +MusicTrack::~MusicTrack() +{ + if (sound) { + sound->Stop(); + sound->Release(); + } +} + +// +--------------------------------------------------------------------+ + +void +MusicTrack::ExecFrame() +{ + bool music_pause = false; + + Starshatter* stars = Starshatter::GetInstance(); + if (stars) { + music_pause = (stars->GetGameMode() == Starshatter::PLAY_MODE) && + Game::Paused(); + } + + if (sound && !music_pause) { + double fvol = 1; + long volume = 0; + + switch (state) { + case PLAY: + if (sound->IsReady()) + sound->Play(); + SetVolume(volume); + break; + + case FADE_IN: + if (sound->IsReady()) + sound->Play(); + + if (fade > 0) { + fvol = fade/fade_time; + volume = (long) (fvol * SILENCE); + SetVolume(volume); + } + + if (fade < 0.01) + state = PLAY; + break; + + case FADE_OUT: + if (sound->IsReady()) + sound->Play(); + + if (fade > 0) { + fvol = 1 - fade/fade_time; + volume = (long) (fvol * SILENCE); + SetVolume(volume); + } + + if (fade < 0.01) + state = STOP; + break; + + case STOP: + if (sound->IsPlaying()) { + sound->Stop(); + sound->Release(); + sound = 0; + } + break; + } + + if (fade > 0) + fade -= Game::GUITime(); + + if (fade < 0) + fade = 0; + } +} + +// +--------------------------------------------------------------------+ + +void +MusicTrack::Play() +{ + state = PLAY; + fade = 0; +} + +void +MusicTrack::Stop() +{ + state = STOP; + fade = 0; +} + +void +MusicTrack::FadeIn() +{ + if (state != FADE_IN && state != PLAY) { + state = FADE_IN; + fade = fade_time; + } +} + +void +MusicTrack::FadeOut() +{ + if (state != FADE_OUT && state != STOP) { + state = FADE_OUT; + fade = fade_time; + } +} + +// +--------------------------------------------------------------------+ + +int +MusicTrack::IsReady() const +{ + if (sound) + return sound->IsReady(); + + return false; +} + +int +MusicTrack::IsPlaying() const +{ + if (sound) + return sound->IsPlaying(); + + return false; +} + +int +MusicTrack::IsDone() const +{ + if (sound) + return sound->IsDone() || sound->LoopCount() >= 5; + + return true; +} + +int +MusicTrack::IsLooped() const +{ + if (sound) + return sound->IsDone() || sound->LoopCount() >= 4; + + return true; +} + +// +--------------------------------------------------------------------+ + +long +MusicTrack::GetVolume() const +{ + if (sound) + return sound->GetVolume(); + + return 0; +} + +void +MusicTrack::SetVolume(long v) +{ + if (sound) { + long max_vol = 0; + + if (mode >= MusicDirector::FLIGHT) + max_vol = AudioConfig::GameMusic(); + else + max_vol = AudioConfig::MenuMusic(); + + if (v > max_vol) + v = max_vol; + + sound->SetVolume(v); + } +} + +double +MusicTrack::GetTotalTime() const +{ + if (sound) + return sound->GetTotalTime(); + + return 0; +} + +double +MusicTrack::GetTimeRemaining() const +{ + if (sound) + return sound->GetTimeRemaining(); + + return 0; +} + +double +MusicTrack::GetTimeElapsed() const +{ + if (sound) + return sound->GetTimeElapsed(); + + return 0; +} + diff --git a/Stars45/MusicTrack.h b/Stars45/MusicTrack.h new file mode 100644 index 0000000..366e810 --- /dev/null +++ b/Stars45/MusicTrack.h @@ -0,0 +1,77 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: MusicTrack.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + MusicTrack class +*/ + + +#ifndef MusicTrack_h +#define MusicTrack_h + +#include "Types.h" +#include "List.h" +#include "Text.h" + +// +-------------------------------------------------------------------+ + +class Sound; + +// +-------------------------------------------------------------------+ + +class MusicTrack +{ +public: + enum STATE { NONE, FADE_IN, PLAY, FADE_OUT, STOP }; + + MusicTrack(const Text& name, int mode=0, int index=0); + virtual ~MusicTrack(); + + // Operations: + virtual void ExecFrame(); + + virtual void Play(); + virtual void Stop(); + virtual void FadeIn(); + virtual void FadeOut(); + + // accessors / mutators + const Text& Name() const { return name; } + Sound* GetSound() const { return sound; } + int GetState() const { return state; } + int GetMode() const { return mode; } + int GetIndex() const { return index; } + + int IsReady() const; + int IsPlaying() const; + int IsDone() const; + int IsLooped() const; + + virtual long GetVolume() const; + virtual void SetVolume(long v); + + virtual double GetTotalTime() const; + virtual double GetTimeRemaining() const; + virtual double GetTimeElapsed() const; + + virtual double GetFadeTime() const { return fade_time; } + virtual void SetFadeTime(double t) { fade_time = t; } + +protected: + Text name; + Sound* sound; + int state; + int mode; + int index; + double fade; + double fade_time; +}; + +#endif MusicTrack_h \ No newline at end of file diff --git a/Stars45/NPClient.h b/Stars45/NPClient.h new file mode 100644 index 0000000..04a34b1 --- /dev/null +++ b/Stars45/NPClient.h @@ -0,0 +1,190 @@ +// ******************************************************************************* +// * +// * Module Name: +// * NPClient.h +// * +// * Doyle Nickless -- 13 Jan, 2003 -- for Eye Control Technology. +// * +// * Abstract: +// * Header for NaturalPoint Game Client API. +// * +// * Environment: +// * Microsoft Windows -- User mode +// * +// ******************************************************************************* + +#ifndef _NPCLIENT_H_DEFINED_ +#define _NPCLIENT_H_DEFINED_ + +#pragma pack( push, npclient_h ) // Save current pack value +#pragma pack(1) + +////////////////// +/// Defines ////////////////////////////////////////////////////////////////////// +///////////////// +#define VERSION_MAJOR 1 +#define VERSION_MINOR 0 +#define VERSION_BUILD 1 + +// magic to get the preprocessor to do what we want +#define lita(arg) #arg +#define xlita(arg) lita(arg) +#define cat3(w,x,z) w##.##x##.##z##\000 +#define xcat3(w,x,z) cat3(w,x,z) +#define VERSION_STRING xlita(xcat3(VERSION_MAJOR,VERSION_MINOR,VERSION_BUILD)) +// +// Versioning hasn't been worked out yet... +// +// The following is the previous spec definition of versioning info -- I can probably do +// something very similar to this -- will keep you posted. +// +// request version information using 2 messages, they cannot be expected to arrive in a specific order - so always parse using the High byte +// the messages have a NPCONTROL byte in the first parameter, and the second parameter has packed bytes. +// Message 1) (first parameter)NPCONTROL : (second parameter) (High Byte)NPVERSIONMAJOR (Low Byte) major version number data +// Message 2) (first parameter)NPCONTROL : (second parameter) (High Byte)NPVERSIONMINOR (Low Byte) minor version number data + +#define NPQUERYVERSION 1040 + +#define NPSTATUS_REMOTEACTIVE 0 +#define NPSTATUS_REMOTEDISABLED 1 + +// CONTROL DATA SUBFIELDS +#define NPVERSIONMAJOR 1 +#define NPVERSIONMINOR 2 + +// DATA FIELDS +#define NPControl 8 // indicates a control data field + // the second parameter of a message bearing control data information contains a packed data format. + // The High byte indicates what the data is, and the Low byte contains the actual data +// roll, pitch, yaw +#define NPRoll 1 // +/- 16383 (representing +/- 180) [data = input - 16383] +#define NPPitch 2 // +/- 16383 (representing +/- 180) [data = input - 16383] +#define NPYaw 4 // +/- 16383 (representing +/- 180) [data = input - 16383] + +// x, y, z - remaining 6dof coordinates +#define NPX 16 // +/- 16383 [data = input - 16383] +#define NPY 32 // +/- 16383 [data = input - 16383] +#define NPZ 64 // +/- 16383 [data = input - 16383] + +// raw object position from imager +#define NPRawX 128 // 0..25600 (actual value is multiplied x 100 to pass two decimal places of precision) [data = input / 100] +#define NPRawY 256 // 0..25600 (actual value is multiplied x 100 to pass two decimal places of precision) [data = input / 100] +#define NPRawZ 512 // 0..25600 (actual value is multiplied x 100 to pass two decimal places of precision) [data = input / 100] + +// x, y, z deltas from raw imager position +#define NPDeltaX 1024 // +/- 2560 (actual value is multiplied x 10 to pass two decimal places of precision) [data = (input / 10) - 256] +#define NPDeltaY 2048 // +/- 2560 (actual value is multiplied x 10 to pass two decimal places of precision) [data = (input / 10) - 256] +#define NPDeltaZ 4096 // +/- 2560 (actual value is multiplied x 10 to pass two decimal places of precision) [data = (input / 10) - 256] + +// raw object position from imager +#define NPSmoothX 8192 // 0..32766 (actual value is multiplied x 10 to pass one decimal place of precision) [data = input / 10] +#define NPSmoothY 16384 // 0..32766 (actual value is multiplied x 10 to pass one decimal place of precision) [data = input / 10] +#define NPSmoothZ 32768 // 0..32766 (actual value is multiplied x 10 to pass one decimal place of precision) [data = input / 10] + + +////////////////// +/// Typedefs ///////////////////////////////////////////////////////////////////// +///////////////// + +// NPESULT values are returned from the Game Client API functions. +// +typedef enum tagNPResult +{ + NP_OK = 0, + NP_ERR_DEVICE_NOT_PRESENT, + NP_ERR_UNSUPPORTED_OS, + NP_ERR_INVALID_ARG, + NP_ERR_DLL_NOT_FOUND, + NP_ERR_NO_DATA, + NP_ERR_INTERNAL_DATA + +} NPRESULT; + +typedef struct tagTrackIRSignature +{ + char DllSignature[200]; + char AppSignature[200]; + +} SIGNATUREDATA, *LPTRACKIRSIGNATURE; + +typedef struct tagTrackIRData +{ + unsigned short wNPStatus; + unsigned short wPFrameSignature; + unsigned long dwNPIOData; + + float fNPRoll; + float fNPPitch; + float fNPYaw; + float fNPX; + float fNPY; + float fNPZ; + float fNPRawX; + float fNPRawY; + float fNPRawZ; + float fNPDeltaX; + float fNPDeltaY; + float fNPDeltaZ; + float fNPSmoothX; + float fNPSmoothY; + float fNPSmoothZ; + +} TRACKIRDATA, *LPTRACKIRDATA; + + +// +// Typedef for pointer to the notify callback function that is implemented within +// the client -- this function receives head tracker reports from the game client API +// +typedef NPRESULT (__stdcall *PF_NOTIFYCALLBACK)( unsigned short, unsigned short ); + +// Typedefs for game client API functions (useful for declaring pointers to these +// functions within the client for use during GetProcAddress() ops) +// +typedef NPRESULT (__stdcall *PF_NP_REGISTERWINDOWHANDLE)( HWND ); +typedef NPRESULT (__stdcall *PF_NP_UNREGISTERWINDOWHANDLE)( void ); +typedef NPRESULT (__stdcall *PF_NP_REGISTERPROGRAMPROFILEID)( unsigned short ); +typedef NPRESULT (__stdcall *PF_NP_QUERYVERSION)( unsigned short* ); +typedef NPRESULT (__stdcall *PF_NP_REQUESTDATA)( unsigned short ); +typedef NPRESULT (__stdcall *PF_NP_GETSIGNATURE)( LPTRACKIRSIGNATURE ); +typedef NPRESULT (__stdcall *PF_NP_GETDATA)( LPTRACKIRDATA ); +typedef NPRESULT (__stdcall *PF_NP_REGISTERNOTIFY)( PF_NOTIFYCALLBACK ); +typedef NPRESULT (__stdcall *PF_NP_UNREGISTERNOTIFY)( void ); +typedef NPRESULT (__stdcall *PF_NP_STARTCURSOR)( void ); +typedef NPRESULT (__stdcall *PF_NP_STOPCURSOR)( void ); +typedef NPRESULT (__stdcall *PF_NP_RECENTER)( void ); +typedef NPRESULT (__stdcall *PF_NP_STARTDATATRANSMISSION)( void ); +typedef NPRESULT (__stdcall *PF_NP_STOPDATATRANSMISSION)( void ); + +//// Function Prototypes /////////////////////////////////////////////// +// +// Functions exported from game client API DLL ( note __stdcall calling convention +// is used for ease of interface to clients of differing implementations including +// C, C++, Pascal (Delphi) and VB. ) +// +NPRESULT __stdcall NP_RegisterWindowHandle( HWND hWnd ); +NPRESULT __stdcall NP_UnregisterWindowHandle( void ); +NPRESULT __stdcall NP_RegisterProgramProfileID( unsigned short wPPID ); +NPRESULT __stdcall NP_QueryVersion( unsigned short* pwVersion ); +NPRESULT __stdcall NP_RequestData( unsigned short wDataReq ); +NPRESULT __stdcall NP_GetSignature( LPTRACKIRSIGNATURE pSignature ); +NPRESULT __stdcall NP_GetData( LPTRACKIRDATA pTID ); +NPRESULT __stdcall NP_RegisterNotify( PF_NOTIFYCALLBACK pfNotify ); +NPRESULT __stdcall NP_UnregisterNotify( void ); +NPRESULT __stdcall NP_StartCursor( void ); +NPRESULT __stdcall NP_StopCursor( void ); +NPRESULT __stdcall NP_ReCenter( void ); +NPRESULT __stdcall NP_StartDataTransmission( void ); +NPRESULT __stdcall NP_StopDataTransmission( void ); + +///////////////////////////////////////////////////////////////////////// + +#pragma pack( pop, npclient_h ) // Ensure previous pack value is restored + +#endif // #ifdef NPCLIENT_H_DEFINED_ + +// +// *** End of file: NPClient.h *** +// + + diff --git a/Stars45/NPClientWraps.cpp b/Stars45/NPClientWraps.cpp new file mode 100644 index 0000000..015526c --- /dev/null +++ b/Stars45/NPClientWraps.cpp @@ -0,0 +1,257 @@ +// ******************************************************************************* +// * +// * Module Name: +// * NPClientWraps.cpp +// * +// * Software Engineer: +// * Doyle Nickless - GoFlight Inc., for Eye Control Technology. +// * +// * Abstract: +// * This module implements the wrapper code for interfacing to the NaturalPoint +// * Game Client API. Developers of client apps can include this module into +// * their projects to simplify communication with the NaturalPoint software. +// * +// * This is necessary since the NPClient DLL is run-time linked rather than +// * load-time linked, avoiding the need to link a static library into the +// * client program (only this module is needed, and can be supplied in source +// * form.) +// * +// * Environment: +// * User mode +// * +// ******************************************************************************* +// +#include "MemDebug.h" +#include "Game.h" +#include "Text.h" + +#include "NPClient.h" +#include "NPClientWraps.h" + +///////////// +// Defines /////////////////////////////////////////////////////////////////////// +///////////// +// + +///////////////// +// Global Data /////////////////////////////////////////////////////////////////// +///////////////// +// +PF_NP_REGISTERWINDOWHANDLE gpfNP_RegisterWindowHandle = NULL; +PF_NP_UNREGISTERWINDOWHANDLE gpfNP_UnregisterWindowHandle = NULL; +PF_NP_REGISTERPROGRAMPROFILEID gpfNP_RegisterProgramProfileID = NULL; +PF_NP_QUERYVERSION gpfNP_QueryVersion = NULL; +PF_NP_REQUESTDATA gpfNP_RequestData = NULL; +PF_NP_GETSIGNATURE gpfNP_GetSignature = NULL; +PF_NP_GETDATA gpfNP_GetData = NULL; +PF_NP_STARTCURSOR gpfNP_StartCursor = NULL; +PF_NP_STOPCURSOR gpfNP_StopCursor = NULL; +PF_NP_RECENTER gpfNP_ReCenter = NULL; +PF_NP_STARTDATATRANSMISSION gpfNP_StartDataTransmission = NULL; +PF_NP_STOPDATATRANSMISSION gpfNP_StopDataTransmission = NULL; + +HMODULE ghNPClientDLL = (HMODULE)NULL; + +//////////////////////////////////////////////////// +// NaturalPoint Game Client API function wrappers ///////////////////////////// +//////////////////////////////////////////////////// +// +NPRESULT __stdcall NP_RegisterWindowHandle( HWND hWnd ) +{ + NPRESULT result = NP_ERR_DLL_NOT_FOUND; + + if( NULL != gpfNP_RegisterWindowHandle ) + result = (*gpfNP_RegisterWindowHandle)( hWnd ); + + return result; +} // NP_RegisterWindowHandle() + + +NPRESULT __stdcall NP_UnregisterWindowHandle() +{ + NPRESULT result = NP_ERR_DLL_NOT_FOUND; + + if( NULL != gpfNP_UnregisterWindowHandle ) + result = (*gpfNP_UnregisterWindowHandle)(); + + return result; +} // NP_UnregisterWindowHandle() + + +NPRESULT __stdcall NP_RegisterProgramProfileID( unsigned short wPPID ) +{ + NPRESULT result = NP_ERR_DLL_NOT_FOUND; + + if( NULL != gpfNP_RegisterProgramProfileID ) + result = (*gpfNP_RegisterProgramProfileID)( wPPID ); + + return result; +} // NP_RegisterProgramProfileID() + + +NPRESULT __stdcall NP_QueryVersion( unsigned short* pwVersion ) +{ + NPRESULT result = NP_ERR_DLL_NOT_FOUND; + + if( NULL != gpfNP_QueryVersion ) + result = (*gpfNP_QueryVersion)( pwVersion ); + + return result; +} // NP_QueryVersion() + + +NPRESULT __stdcall NP_RequestData( unsigned short wDataReq ) +{ + NPRESULT result = NP_ERR_DLL_NOT_FOUND; + + if( NULL != gpfNP_RequestData ) + result = (*gpfNP_RequestData)( wDataReq ); + + return result; +} // NP_RequestData() + +NPRESULT __stdcall NP_GetSignature( LPTRACKIRSIGNATURE pSignature ) +{ + NPRESULT result = NP_ERR_DLL_NOT_FOUND; + + if( NULL != gpfNP_GetSignature ) + result = (*gpfNP_GetSignature)( pSignature ); + + return result; +} // NP_GetSignature() + + +NPRESULT __stdcall NP_GetData( LPTRACKIRDATA pTID ) +{ + NPRESULT result = NP_ERR_DLL_NOT_FOUND; + + if( NULL != gpfNP_GetData ) + result = (*gpfNP_GetData)( pTID ); + + return result; +} // NP_GetData() + + +NPRESULT __stdcall NP_StartCursor() +{ + NPRESULT result = NP_ERR_DLL_NOT_FOUND; + + if( NULL != gpfNP_StartCursor ) + result = (*gpfNP_StartCursor)(); + + return result; +} // NP_StartCursor() + + +NPRESULT __stdcall NP_StopCursor() +{ + NPRESULT result = NP_ERR_DLL_NOT_FOUND; + + if( NULL != gpfNP_StopCursor ) + result = (*gpfNP_StopCursor)(); + + return result; +} // NP_StopCursor() + + +NPRESULT __stdcall NP_ReCenter() +{ + NPRESULT result = NP_ERR_DLL_NOT_FOUND; + + if( NULL != gpfNP_ReCenter ) + result = (*gpfNP_ReCenter)(); + + return result; +} // NP_ReCenter() + + +NPRESULT __stdcall NP_StartDataTransmission() +{ + NPRESULT result = NP_ERR_DLL_NOT_FOUND; + + if( NULL != gpfNP_StartDataTransmission ) + result = (*gpfNP_StartDataTransmission)(); + + return result; +} // NP_StartDataTransmission() + + +NPRESULT __stdcall NP_StopDataTransmission() +{ + NPRESULT result = NP_ERR_DLL_NOT_FOUND; + + if( NULL != gpfNP_StopDataTransmission ) + result = (*gpfNP_StopDataTransmission)(); + + return result; +} // NP_StopDataTransmission() + + +////////////////////////////////////////////////////////////////////////////// +// NPClientInit() -- Loads the DLL and retrieves pointers to all exports +// +NPRESULT NPClient_Init( const char* csDLLPath ) +{ + NPRESULT result = NP_OK; + + Text csNPClientDLLFullPath; + + if (csDLLPath && *csDLLPath) + csNPClientDLLFullPath = Text(csDLLPath) + "\\"; + csNPClientDLLFullPath += "NPClient.dll"; + + ghNPClientDLL = ::LoadLibrary( csNPClientDLLFullPath.data() ); + + if (NULL != ghNPClientDLL) { + // verify the dll signature + gpfNP_GetSignature = (PF_NP_GETSIGNATURE)::GetProcAddress( ghNPClientDLL, "NP_GetSignature" ); + + SIGNATUREDATA pSignature; + SIGNATUREDATA verifySignature; + // init the signatures + strcpy(verifySignature.DllSignature, "precise head tracking\n put your head into the game\n now go look around\n\n Copyright EyeControl Technologies"); + strcpy(verifySignature.AppSignature, "hardware camera\n software processing data\n track user movement\n\n Copyright EyeControl Technologies"); + // query the dll and compare the results + NPRESULT vresult = NP_GetSignature( &pSignature ); + if( vresult == NP_OK ) + { + if ((strcmp(verifySignature.DllSignature,pSignature.DllSignature)==0) + && (strcmp(verifySignature.AppSignature,pSignature.AppSignature)==0)) + { + result = NP_OK; + + // Get addresses of all exported functions + gpfNP_RegisterWindowHandle = (PF_NP_REGISTERWINDOWHANDLE)::GetProcAddress( ghNPClientDLL, "NP_RegisterWindowHandle" ); + gpfNP_UnregisterWindowHandle = (PF_NP_UNREGISTERWINDOWHANDLE)::GetProcAddress( ghNPClientDLL, "NP_UnregisterWindowHandle" ); + gpfNP_RegisterProgramProfileID = (PF_NP_REGISTERPROGRAMPROFILEID)::GetProcAddress( ghNPClientDLL, "NP_RegisterProgramProfileID" ); + gpfNP_QueryVersion = (PF_NP_QUERYVERSION)::GetProcAddress( ghNPClientDLL, "NP_QueryVersion" ); + gpfNP_RequestData = (PF_NP_REQUESTDATA)::GetProcAddress( ghNPClientDLL, "NP_RequestData" ); + gpfNP_GetData = (PF_NP_GETDATA)::GetProcAddress( ghNPClientDLL, "NP_GetData" ); + gpfNP_StartCursor = (PF_NP_STARTCURSOR)::GetProcAddress( ghNPClientDLL, "NP_StartCursor" ); + gpfNP_StopCursor = (PF_NP_STOPCURSOR)::GetProcAddress( ghNPClientDLL, "NP_StopCursor" ); + gpfNP_ReCenter = (PF_NP_RECENTER)::GetProcAddress( ghNPClientDLL, "NP_ReCenter" ); + gpfNP_StartDataTransmission = (PF_NP_STARTDATATRANSMISSION)::GetProcAddress( ghNPClientDLL, "NP_StartDataTransmission" ); + gpfNP_StopDataTransmission = (PF_NP_STOPDATATRANSMISSION)::GetProcAddress( ghNPClientDLL, "NP_StopDataTransmission" ); + } + else + { + result = NP_ERR_DLL_NOT_FOUND; + } + } + else + { + result = NP_ERR_DLL_NOT_FOUND; + } + } + else + result = NP_ERR_DLL_NOT_FOUND; + + return result; + +} // NPClient_Init() + +////////////////////////////////////////////////////////////////////////////// + + + + diff --git a/Stars45/NPClientWraps.h b/Stars45/NPClientWraps.h new file mode 100644 index 0000000..dbe579d --- /dev/null +++ b/Stars45/NPClientWraps.h @@ -0,0 +1,33 @@ +// ******************************************************************************* +// * +// * Module Name: +// * NPClientWraps.h +// * +// * Software Engineer: +// * Doyle Nickless - GoFlight Inc., for Eye Control Technology. +// * +// * Abstract: +// * Header file for NPClientWraps.cpp module. +// * +// * Environment: +// * User mode +// * +// ******************************************************************************* +// +#ifndef _NPCLIENTWRAPS_H_DEFINED_ +#define _NPCLIENTWRAPS_H_DEFINED_ + +#include "NPClient.h" + +///////////// +// Defines /////////////////////////////////////////////////////////////////////// +///////////// +// + +///////////////////////// +// Function Prototypes /////////////////////////////////////////////////////////// +///////////////////////// +// +NPRESULT NPClient_Init( const char* csDLLPath ); + +#endif // #ifdef _NPCLIENTWRAPS_H_DEFINED_ diff --git a/Stars45/NavAI.cpp b/Stars45/NavAI.cpp new file mode 100644 index 0000000..c863064 --- /dev/null +++ b/Stars45/NavAI.cpp @@ -0,0 +1,622 @@ +/* Project Starshatter 5.0 + Destroyer Studios LLC + Copyright © 1997-2007. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NavAI.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Automatic Navigator Artificial Intelligence class +*/ + +#include "MemDebug.h" +#include "NavAI.h" +#include "TacticalAI.h" +#include "Instruction.h" +#include "NavSystem.h" +#include "QuantumDrive.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "ShipCtrl.h" +#include "Drive.h" +#include "Farcaster.h" +#include "Shield.h" +#include "Sim.h" +#include "StarSystem.h" +#include "KeyMap.h" +#include "HUDView.h" +#include "HUDSounds.h" + +#include "Game.h" + +// +----------------------------------------------------------------------+ + +NavAI::NavAI(Ship* s) + : ShipAI(s), complete(false), brakes(0), + drop_state(0), quantum_state(0), farcaster(0), terrain_warning(false) +{ + seek_gain = 20; + seek_damp = 0.55; + + delete tactical; + tactical = 0; +} + +// +--------------------------------------------------------------------+ + +NavAI::~NavAI() +{ } + +// +--------------------------------------------------------------------+ + +void +NavAI::ExecFrame(double s) +{ + if (!ship) return; + + seconds = s; + + ship->SetDirectorInfo(" "); + + if (ship->GetFlightPhase() == Ship::TAKEOFF) + takeoff = true; + + else if (takeoff && ship->MissionClock() > 10000) + takeoff = false; + + FindObjective(); + Navigator(); + + // watch for disconnect: + if (ShipCtrl::Toggled(KEY_AUTO_NAV)) { + NavSystem* navsys = ship->GetNavSystem(); + if (navsys) { + HUDView::GetInstance()->SetHUDMode(HUDView::HUD_MODE_TAC); + navsys->DisengageAutoNav(); + + Sim* sim = Sim::GetSim(); + if (sim) { + ship->SetControls(sim->GetControls()); + return; + } + } + } + + static double time_til_change = 0.0; + + if (time_til_change < 0.001) { + if (ship->GetShield()) { + Shield* shield = ship->GetShield(); + double level = shield->GetPowerLevel(); + + if (ShipCtrl::KeyDown(KEY_SHIELDS_FULL)) { + HUDSounds::PlaySound(HUDSounds::SND_SHIELD_LEVEL); + shield->SetPowerLevel(100); + time_til_change = 0.5f; + } + + else if (ShipCtrl::KeyDown(KEY_SHIELDS_ZERO)) { + HUDSounds::PlaySound(HUDSounds::SND_SHIELD_LEVEL); + shield->SetPowerLevel(0); + time_til_change = 0.5f; + } + + else if (ShipCtrl::KeyDown(KEY_SHIELDS_UP)) { + if (level < 25) level = 25; + else if (level < 50) level = 50; + else if (level < 75) level = 75; + else level = 100; + + HUDSounds::PlaySound(HUDSounds::SND_SHIELD_LEVEL); + shield->SetPowerLevel(level); + time_til_change = 0.5f; + } + + else if (ShipCtrl::KeyDown(KEY_SHIELDS_DOWN)) { + if (level > 75) level = 75; + else if (level > 50) level = 50; + else if (level > 25) level = 25; + else level = 0; + + HUDSounds::PlaySound(HUDSounds::SND_SHIELD_LEVEL); + shield->SetPowerLevel(level); + time_til_change = 0.5f; + } + + } + } + else { + time_til_change -= seconds; + } + + if (ShipCtrl::Toggled(KEY_DECOY)) + ship->FireDecoy(); + + if (ShipCtrl::Toggled(KEY_LAUNCH_PROBE)) + ship->LaunchProbe(); + + if (ShipCtrl::Toggled(KEY_GEAR_TOGGLE)) + ship->ToggleGear(); + + if (ShipCtrl::Toggled(KEY_NAVLIGHT_TOGGLE)) + ship->ToggleNavlights(); + + if (drop_state < 0) { + ship->DropOrbit(); + return; + } + + if (drop_state > 0) { + ship->MakeOrbit(); + return; + } +} + +// +--------------------------------------------------------------------+ + +void +NavAI::FindObjective() +{ + navpt = 0; + distance = 0; + + // runway takeoff: + if (takeoff) { + obj_w = ship->Location() + ship->Heading() * 10e3; + obj_w.y = ship->Location().y + 2e3; + + // transform into camera coords: + objective = Transform(obj_w); + ship->SetDirectorInfo(Game::GetText("ai.takeoff")); + return; + } + + // PART I: Find next NavPoint: + if (ship->GetNavSystem()) + navpt = ship->GetNextNavPoint(); + + complete = !navpt; + if (complete) return; + + // PART II: Compute Objective from NavPoint: + Point npt = navpt->Location(); + Sim* sim = Sim::GetSim(); + SimRegion* self_rgn = ship->GetRegion(); + SimRegion* nav_rgn = navpt->Region(); + + if (self_rgn && !nav_rgn) { + nav_rgn = self_rgn; + navpt->SetRegion(nav_rgn); + } + + if (self_rgn == nav_rgn) { + if (farcaster) { + if (farcaster->GetShip()->GetRegion() != self_rgn) + farcaster = farcaster->GetDest()->GetFarcaster(); + + obj_w = farcaster->EndPoint(); + } + + else { + obj_w = npt.OtherHand(); + } + + // distance from self to navpt: + distance = Point(obj_w - self->Location()).length(); + + // transform into camera coords: + objective = Transform(obj_w); + + if (!ship->IsStarship()) + objective.Normalize(); + + if (farcaster && distance < 1000) + farcaster = 0; + } + + // PART III: Deal with orbital transitions: + else if (ship->IsDropship()) { + if (nav_rgn->GetOrbitalRegion()->Primary() == + self_rgn->GetOrbitalRegion()->Primary()) { + + Point npt = nav_rgn->Location() - self_rgn->Location(); + obj_w = npt.OtherHand(); + + // distance from self to navpt: + distance = Point(obj_w - ship->Location()).length(); + + // transform into camera coords: + objective = Transform(obj_w); + + if (nav_rgn->IsAirSpace()) { + drop_state = -1; + } + else if (nav_rgn->IsOrbital()) { + drop_state = 1; + } + } + + // PART IIIa: Deal with farcaster jumps: + else if (nav_rgn->IsOrbital() && self_rgn->IsOrbital()) { + ListIter s = self_rgn->Ships(); + while (++s && !farcaster) { + if (s->GetFarcaster()) { + const Ship* dest = s->GetFarcaster()->GetDest(); + if (dest && dest->GetRegion() == nav_rgn) { + farcaster = s->GetFarcaster(); + } + } + } + + if (farcaster) { + Point apt = farcaster->ApproachPoint(0); + Point npt = farcaster->StartPoint(); + double r1 = (ship->Location() - npt).length(); + + if (r1 > 50e3) { + obj_w = apt; + distance = r1; + objective = Transform(obj_w); + } + + else { + double r2 = (ship->Location() - apt).length(); + double r3 = (npt - apt).length(); + + if (r1+r2 < 1.2*r3) { + obj_w = npt; + distance = r1; + objective = Transform(obj_w); + } + else { + obj_w = apt; + distance = r2; + objective = Transform(obj_w); + } + } + } + } + } + + // PART IV: Deal with quantum jumps: + else if (ship->IsStarship()) { + quantum_state = 1; + + Point npt = nav_rgn->Location() + navpt->Location(); + npt -= self_rgn->Location(); + obj_w = npt.OtherHand(); + + // distance from self to navpt: + distance = Point(obj_w - ship->Location()).length(); + + // transform into camera coords: + objective = Transform(obj_w); + } +} + +// +--------------------------------------------------------------------+ + +void +NavAI::Navigator() +{ + accumulator.Clear(); + magnitude = 0; + brakes = 0; + hold = false; + + if (navpt) { + if (navpt->Status() == Instruction::COMPLETE && navpt->HoldTime() > 0) { + ship->SetDirectorInfo(Game::GetText("ai.auto-hold")); + hold = true; + } + else { + ship->SetDirectorInfo(Game::GetText("ai.auto-nav")); + } + } + else { + ship->SetDirectorInfo(Game::GetText("ai.auto-stop")); + } + + Accumulate(AvoidTerrain()); + Accumulate(AvoidCollision()); + + if (!hold) + accumulator = SeekTarget(); + + HelmControl(); + ThrottleControl(); +} + +// +--------------------------------------------------------------------+ + +void +NavAI::HelmControl() +{ + // ---------------------------------------------------------- + // STARSHIP HELM MODE + // ---------------------------------------------------------- + + if (ship->IsStarship()) { + ship->SetFLCSMode(Ship::FLCS_HELM); + ship->SetHelmHeading(accumulator.yaw); + + if (accumulator.pitch > 45*DEGREES) + ship->SetHelmPitch(45*DEGREES); + + else if (accumulator.pitch < -45*DEGREES) + ship->SetHelmPitch(-45*DEGREES); + + else + ship->SetHelmPitch(accumulator.pitch); + } + + // ---------------------------------------------------------- + // FIGHTER FLCS AUTO MODE + // ---------------------------------------------------------- + + else { + ship->SetFLCSMode(Ship::FLCS_AUTO); + + // are we being asked to flee? + if (fabs(accumulator.yaw) == 1.0 && accumulator.pitch == 0.0) { + accumulator.pitch = -0.7f; + accumulator.yaw *= 0.25f; + } + + self->ApplyRoll((float) (accumulator.yaw * -0.4)); + self->ApplyYaw((float) (accumulator.yaw * 0.2)); + + if (fabs(accumulator.yaw) > 0.5 && fabs(accumulator.pitch) < 0.1) + accumulator.pitch -= 0.1f; + + if (accumulator.pitch != 0) + self->ApplyPitch((float) accumulator.pitch); + + // if not turning, roll to orient with world coords: + if (fabs(accumulator.yaw) < 0.1) { + Point vrt = ((Camera*) &(self->Cam()))->vrt(); + double deflection = vrt.y; + if (deflection != 0) { + double theta = asin(deflection/vrt.length()); + self->ApplyRoll(-theta); + } + } + + if (!ship->IsAirborne() || ship->AltitudeAGL() > 100) + ship->RaiseGear(); + } + + ship->SetTransX(0); + ship->SetTransY(0); + ship->SetTransZ(0); + ship->ExecFLCSFrame(); +} + +// +--------------------------------------------------------------------+ + +void +NavAI::ThrottleControl() +{ + double ship_speed = ship->Velocity() * ship->Heading(); + bool augmenter = false; + + if (hold) { + throttle = 0; + brakes = 1; + } + + else if (navpt) { + double speed = navpt->Speed(); + + if (speed < 10) + speed = 250; + + throttle = 0; + + if (Ship::GetFlightModel() > 0) { + if (ship_speed > speed + 10) + throttle = old_throttle - 0.25; + + else if (ship_speed < speed - 10) + throttle = old_throttle + 0.25; + + else + throttle = old_throttle; + } + + else { + if (ship_speed > speed+5) + brakes = 0.25; + + else if (ship_speed < speed-5) + throttle = 50; + } + } + else { + throttle = 0; + brakes = 0.25; + } + + if (ship->IsAirborne() && ship->Class() < Ship::LCA) { + if (ship_speed < 250) { + throttle = 100; + brakes = 0; + + if (ship_speed < 200) + augmenter = true; + } + + else if (throttle < 20) { + throttle = 20; + } + } + + old_throttle = throttle; + ship->SetThrottle(throttle); + ship->SetAugmenter(augmenter); + + if (ship_speed > 1 && brakes > 0) + ship->SetTransY(-brakes * ship->Design()->trans_y); +} + +// +--------------------------------------------------------------------+ + +Steer +NavAI::SeekTarget() +{ + if (!ship) + return Steer(); + + if (takeoff) + return Seek(objective); + + if (navpt) { + if (quantum_state == 1) { + QuantumDrive* q = ship->GetQuantumDrive(); + + if (q) { + if (q->ActiveState() == QuantumDrive::ACTIVE_READY) { + q->SetDestination(navpt->Region(), navpt->Location()); + q->Engage(); + } + + else if (q->ActiveState() == QuantumDrive::ACTIVE_POSTWARP) { + quantum_state = 0; + } + } + } + + if (distance < 2 * self->Radius()) { + ship->SetNavptStatus(navpt, Instruction::COMPLETE); + + return Steer(); + } + else { + return Seek(objective); + } + } + + return Steer(); +} + +// +--------------------------------------------------------------------+ + +void +NavAI::Disengage() +{ + throttle = 0; +} + +// +--------------------------------------------------------------------+ + +Point +NavAI::Transform(const Point& point) +{ + if (ship && ship->IsStarship()) + return point - self->Location(); + + return SteerAI::Transform(point); +} + +Steer +NavAI::Seek(const Point& point) +{ + // if ship is starship, the point is in relative world coordinates + // x: distance east(-) / west(+) + // y: altitude down(-) / up(+) + // z: distance north(-) / south(+) + + if (ship && ship->IsStarship()) { + Steer result; + + result.yaw = atan2(point.x, point.z) + PI; + + double adjacent = sqrt(point.x * point.x + point.z * point.z); + if (fabs(point.y) > ship->Radius() && adjacent > ship->Radius()) + result.pitch = atan(point.y / adjacent); + + if (_isnan(result.yaw)) + result.yaw = 0; + + if (_isnan(result.pitch)) + result.pitch = 0; + + return result; + } + + return SteerAI::Seek(point); +} + +Steer +NavAI::Flee(const Point& point) +{ + if (ship && ship->IsStarship()) { + Steer result = Seek(point); + result.yaw += PI; + result.pitch *= -1; + return result; + } + + return SteerAI::Flee(point); +} + +Steer +NavAI::Avoid(const Point& point, float radius) +{ + if (ship && ship->IsStarship()) { + Steer result = Seek(point); + + if (point * ship->BeamLine() > 0) + result.yaw -= PI/2; + else + result.yaw += PI/2; + + return result; + } + + return SteerAI::Avoid(point, radius); +} + +// +--------------------------------------------------------------------+ + +Steer +NavAI::AvoidTerrain() +{ + Steer avoid; + + terrain_warning = false; + + if (!ship || !ship->GetRegion() || !ship->GetRegion()->IsActive() || + takeoff || (navpt && navpt->Action() == Instruction::LAUNCH)) + return avoid; + + if (ship->IsAirborne() && ship->GetFlightPhase() == Ship::ACTIVE) { + // too low? + if (ship->AltitudeAGL() < 1000) { + terrain_warning = true; + ship->SetDirectorInfo(Game::GetText("ai.too-low")); + + // way too low? + if (ship->AltitudeAGL() < 750) { + ship->SetDirectorInfo(Game::GetText("ai.way-too-low")); + } + + // where will we be? + Point selfpt = ship->Location() + ship->Velocity() + Point(0, 10e3, 0); + + // transform into camera coords: + Point obj = Transform(selfpt); + + // pull up! + avoid = Seek(obj); + } + } + + return avoid; +} + + + diff --git a/Stars45/NavAI.h b/Stars45/NavAI.h new file mode 100644 index 0000000..99dc724 --- /dev/null +++ b/Stars45/NavAI.h @@ -0,0 +1,74 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NavAI.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Automatic Navigator +*/ + +#ifndef NavAI_h +#define NavAI_h + +#include "Types.h" +#include "Geometry.h" +#include "System.h" +#include "ShipAI.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class Farcaster; + +// +--------------------------------------------------------------------+ + +class NavAI : public ShipAI +{ +public: + NavAI(Ship* s); + virtual ~NavAI(); + + enum { DIR_TYPE = 2000 }; + virtual int Type() const { return DIR_TYPE; } + + virtual void ExecFrame(double seconds); + virtual int Subframe() const { return true; } + void Disengage(); + bool Complete() const { return complete; } + +protected: + // behaviors: + virtual Steer SeekTarget(); + + // steering functions: + virtual Point Transform(const Point& pt); + virtual Steer Seek(const Point& point); + virtual Steer Flee(const Point& point); + virtual Steer Avoid(const Point& point, float radius); + virtual Steer AvoidTerrain(); + + // accumulate behaviors: + virtual void Navigator(); + virtual void FindObjective(); + + virtual void HelmControl(); + virtual void ThrottleControl(); + + bool complete; + int drop_state; + int quantum_state; + int terrain_warning; + double brakes; + Farcaster* farcaster; +}; + +// +--------------------------------------------------------------------+ + + +#endif NavAI_h + diff --git a/Stars45/NavDlg.cpp b/Stars45/NavDlg.cpp new file mode 100644 index 0000000..5c702c4 --- /dev/null +++ b/Stars45/NavDlg.cpp @@ -0,0 +1,1126 @@ +/* Project Starshatter 5.0 + Destroyer Studios LLC + Copyright © 1997-2007. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NavDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== +*/ + +#include "MemDebug.h" +#include "NavDlg.h" +#include "MapView.h" +#include "MsnElemDlg.h" +#include "BaseScreen.h" +#include "Element.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "Sim.h" +#include "Galaxy.h" +#include "StarSystem.h" +#include "Instruction.h" +#include "NavSystem.h" +#include "FormatUtil.h" +#include "Campaign.h" +#include "Contact.h" +#include "Mission.h" + +#include "Game.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "ListBox.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: +DEF_MAP_CLIENT(NavDlg, OnView); +DEF_MAP_CLIENT(NavDlg, OnFilter); +DEF_MAP_CLIENT(NavDlg, OnSelect); +DEF_MAP_CLIENT(NavDlg, OnCommit); +DEF_MAP_CLIENT(NavDlg, OnCancel); +DEF_MAP_CLIENT(NavDlg, OnEngage); +DEF_MAP_CLIENT(NavDlg, OnMapDown); +DEF_MAP_CLIENT(NavDlg, OnMapMove); +DEF_MAP_CLIENT(NavDlg, OnMapClick); +DEF_MAP_CLIENT(NavDlg, OnClose); + +// +--------------------------------------------------------------------+ + +static char* filter_name[] = { + "SYSTEM", "PLANET", + "SECTOR", "STATION", + "STARSHIP", "FIGHTER" +}; + +static char* commit_name = "Commit"; +static char* cancel_name = "Cancel"; + +static Color commit_color(53,159,67); +static Color cancel_color(160,8,8); + + +// Supported Selection Modes: + +const int SELECT_NONE = -1; +const int SELECT_SYSTEM = 0; +const int SELECT_PLANET = 1; +const int SELECT_REGION = 2; +const int SELECT_STATION = 3; +const int SELECT_STARSHIP = 4; +const int SELECT_FIGHTER = 5; + +const int VIEW_GALAXY = 0; +const int VIEW_SYSTEM = 1; +const int VIEW_REGION = 2; + +// +--------------------------------------------------------------------+ + +NavDlg::NavDlg(Screen* s, FormDef& def, BaseScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), manager(mgr), + loc_labels(0), dst_labels(0), loc_data(0), dst_data(0), + seln_list(0), info_list(0), seln_mode(SELECT_REGION), + nav_edit_mode(NAV_EDIT_NONE), star_map(0), map_win(0), + star_system(0), ship(0), mission(0), editor(false) +{ + Init(def); +} + +NavDlg::~NavDlg() +{ } + +void +NavDlg::RegisterControls() +{ + int i; + + map_win = FindControl(100); + + if (map_win) { + star_map = new(__FILE__,__LINE__) MapView(map_win); + + REGISTER_CLIENT(EID_LBUTTON_DOWN, map_win, NavDlg, OnMapDown); + REGISTER_CLIENT(EID_MOUSE_MOVE, map_win, NavDlg, OnMapMove); + REGISTER_CLIENT(EID_MAP_CLICK, map_win, NavDlg, OnMapClick); + } + + for (i = 0; i < 3; i++) { + view_btn[i] = (Button*) FindControl(101 + i); + REGISTER_CLIENT(EID_CLICK, view_btn[i], NavDlg, OnView); + } + + close_btn = (Button*) FindControl(2); + + if (close_btn) + REGISTER_CLIENT(EID_CLICK, close_btn, NavDlg, OnClose); + + zoom_in_btn = (Button*) FindControl(110); + zoom_out_btn = (Button*) FindControl(111); + + for (i = 0; i < 6; i++) { + filter_btn[i] = (Button*) FindControl(401 + i); + REGISTER_CLIENT(EID_CLICK, filter_btn[i], NavDlg, OnFilter); + } + + commit_btn = (Button*) FindControl(1); + + if (commit_btn) { + REGISTER_CLIENT(EID_CLICK, commit_btn, NavDlg, OnEngage); + } + + loc_labels = FindControl(601); + dst_labels = FindControl(602); + loc_data = FindControl(701); + dst_data = FindControl(702); + + seln_list = (ListBox*) FindControl(801); + + if (seln_list) { + seln_list->SetSelectedStyle(ListBox::LIST_ITEM_STYLE_FILLED_BOX); + REGISTER_CLIENT(EID_SELECT, seln_list, NavDlg, OnSelect); + } + + info_list = (ListBox*) FindControl(802); + + if (star_map) { + star_map->SetViewMode(VIEW_SYSTEM); + view_btn[1]->SetButtonState(1); + + star_map->SetSelectionMode(2); + } + + UpdateSelection(); +} + +// +--------------------------------------------------------------------+ + +void +NavDlg::SetSystem(StarSystem* s) +{ + if (star_system == s) + return; + + star_system = s; + + if (star_map) { + Campaign* c = Campaign::GetCampaign(); + Sim* sim = Sim::GetSim(); + + if (sim && sim->GetSystemList().size()) { + star_map->SetGalaxy(sim->GetSystemList()); + } + else if (mission && mission->GetSystemList().size()) { + star_map->SetGalaxy(mission->GetSystemList()); + } + else if (c && c->GetSystemList().size()) { + star_map->SetGalaxy(c->GetSystemList()); + } + else { + Galaxy* g = Galaxy::GetInstance(); + if (g) + star_map->SetGalaxy(g->GetSystemList()); + } + + star_map->SetSystem(s); + } + + // flush old object pointers: + stars.clear(); + planets.clear(); + regions.clear(); + contacts.clear(); + + if (star_system) { + // insert objects from star system: + ListIter star = star_system->Bodies(); + while (++star) { + switch (star->Type()) { + case Orbital::STAR: stars.append(star.value()); + break; + case Orbital::PLANET: + case Orbital::MOON: planets.append(star.value()); + break; + } + + ListIter planet = star->Satellites(); + while (++planet) { + planets.append(planet.value()); + + ListIter moon = planet->Satellites(); + while (++moon) { + planets.append(moon.value()); + } + } + } + + ListIter rgn = star_system->AllRegions(); + while (++rgn) + regions.append(rgn.value()); + } + + // sort region list by distance from the star: + regions.sort(); +} + +// +--------------------------------------------------------------------+ + +void +NavDlg::SetShip(Ship* s) +{ + if (ship == s) + return; + + ship = s; + + if (ship) + SetSystem(ship->GetRegion()->System()); + + if (star_map) { + Sim* sim = Sim::GetSim(); + + if (sim && sim->GetSystemList().size()) + star_map->SetGalaxy(sim->GetSystemList()); + + star_map->SetShip(ship); + + if (ship) { + star_map->SetRegion(ship->GetRegion()->GetOrbitalRegion()); + UseViewMode(VIEW_REGION); + star_map->SetSelectedShip(ship); + } + } + + for (int i = 0; i < 6; i++) + filter_btn[i]->SetButtonState(0); + + filter_btn[SELECT_REGION]->SetButtonState(1); + UseFilter(SELECT_STARSHIP); + UpdateSelection(); +} + +// +--------------------------------------------------------------------+ + +void +NavDlg::SetMission(Mission* m) +{ + if (!m && mission == m) + return; + + if (mission == m && star_system == m->GetStarSystem()) + return; + + mission = m; + + if (mission) { + SetSystem(mission->GetStarSystem()); + } + + if (star_map) { + Campaign* c = Campaign::GetCampaign(); + Sim* sim = Sim::GetSim(); + + star_map->SetMission(0); // prevent building map menu twice + + if (sim && sim->GetSystemList().size()) { + star_map->SetGalaxy(sim->GetSystemList()); + } + else if (mission && mission->GetSystemList().size()) { + star_map->SetGalaxy(mission->GetSystemList()); + } + else if (c && c->GetSystemList().size()) { + star_map->SetGalaxy(c->GetSystemList()); + } + else { + Galaxy* g = Galaxy::GetInstance(); + if (g) + star_map->SetGalaxy(g->GetSystemList()); + } + + if (mission) { + star_map->SetMission(mission); + star_map->SetRegionByName(mission->GetRegion()); + + if (star_map->GetViewMode() == VIEW_REGION) { + ListIter elem = mission->GetElements(); + while (++elem) { + MissionElement* e = elem.value(); + + if (e->Player()) + star_map->SetSelectedElem(e); + } + } + } + } + + bool updated = false; + + if (mission) { + Orbital* rgn = 0; + rgn = mission->GetStarSystem()->FindOrbital(mission->GetRegion()); + + if (rgn) { + SelectRegion((OrbitalRegion*) rgn); + updated = true; + } + } + + if (!updated) + UpdateSelection(); +} + +// +--------------------------------------------------------------------+ + +void +NavDlg::SetEditorMode(bool e) +{ + editor = e; + + if (star_map) + star_map->SetEditorMode(editor); +} + +// +--------------------------------------------------------------------+ + +void +NavDlg::ExecFrame() +{ + Sim* sim = Sim::GetSim(); + + if (loc_labels && ship) { + char loc_buf[512]; + char x[16]; + char y[16]; + char z[16]; + char d[16]; + + FormatNumber(x, -ship->Location().x); + FormatNumber(y, ship->Location().z); + FormatNumber(z, ship->Location().y); + + strcpy(loc_buf, Game::GetText("NavDlg.loc-labels").data()); + loc_labels->SetText(loc_buf); + + if (sim->GetActiveRegion()) { + sprintf(loc_buf, "\n%s\n%s\n%s, %s, %s", + (const char*) star_system->Name(), + (const char*) sim->GetActiveRegion()->Name(), + x, y, z); + + } + else { + sprintf(loc_buf, "\n%s\nPlanck Space?\n%s, %s, %s", + (const char*) star_system->Name(), x, y, z); + } + + loc_data->SetText(loc_buf); + + if (ship) { + NavSystem* navsys = ship->GetNavSystem(); + + if (ship->GetNextNavPoint() == 0 || !navsys) { + commit_btn->SetText(Game::GetText("NavDlg.commit")); + commit_btn->SetBackColor(commit_color); + commit_btn->SetEnabled(false); + } + else if (navsys) { + commit_btn->SetEnabled(true); + + if (navsys->AutoNavEngaged()) { + commit_btn->SetText(Game::GetText("NavDlg.cancel")); + commit_btn->SetBackColor(cancel_color); + } + else { + commit_btn->SetText(Game::GetText("NavDlg.commit")); + commit_btn->SetBackColor(commit_color); + } + } + } + + if (dst_labels) { + Instruction* navpt = ship->GetNextNavPoint(); + + if (navpt && navpt->Region()) { + FormatNumber(x, navpt->Location().x); + FormatNumber(y, navpt->Location().y); + FormatNumber(z, navpt->Location().z); + + double distance = 0; + Point npt = navpt->Region()->Location() + navpt->Location(); + if (sim->GetActiveRegion()) + npt -= sim->GetActiveRegion()->Location(); + + npt = npt.OtherHand(); + + // distance from self to navpt: + distance = Point(npt - ship->Location()).length(); + FormatNumber(d, distance); + + strcpy(loc_buf, Game::GetText("NavDlg.dst-labels").data()); + dst_labels->SetText(loc_buf); + + sprintf(loc_buf, "\n%s\n%s\n%s, %s, %s\n%s", + (const char*) star_system->Name(), + (const char*) navpt->Region()->Name(), + x, y, z, d); + dst_data->SetText(loc_buf); + } + else { + dst_labels->SetText(Game::GetText("NavDlg.destination")); + dst_data->SetText(Game::GetText("NavDlg.not-avail")); + } + } + } + + UpdateSelection(); + UpdateLists(); + + if (Keyboard::KeyDown(VK_ADD) || + (zoom_in_btn && zoom_in_btn->GetButtonState() > 0)) { + star_map->ZoomIn(); + } + else if (Keyboard::KeyDown(VK_SUBTRACT) || + (zoom_out_btn && zoom_out_btn->GetButtonState() > 0)) { + star_map->ZoomOut(); + } + + else if (star_map->TargetRect().Contains(Mouse::X(),Mouse::Y())) { + + if (Mouse::Wheel() > 0) { + star_map->ZoomIn(); + star_map->ZoomIn(); + star_map->ZoomIn(); + } + + else if (Mouse::Wheel() < 0) { + star_map->ZoomOut(); + star_map->ZoomOut(); + star_map->ZoomOut(); + } + } + + if (nav_edit_mode == NAV_EDIT_NONE) + Mouse::SetCursor(Mouse::ARROW); + else + Mouse::SetCursor(Mouse::CROSS); +} + +// +--------------------------------------------------------------------+ + +void +NavDlg::OnView(AWEvent* event) +{ + int use_filter_mode = -1; + + view_btn[VIEW_GALAXY]->SetButtonState(0); + view_btn[VIEW_SYSTEM]->SetButtonState(0); + view_btn[VIEW_REGION]->SetButtonState(0); + + if (view_btn[0] == event->window) { + star_map->SetViewMode(VIEW_GALAXY); + view_btn[VIEW_GALAXY]->SetButtonState(1); + use_filter_mode = SELECT_SYSTEM; + } + + else if (view_btn[VIEW_SYSTEM] == event->window) { + star_map->SetViewMode(VIEW_SYSTEM); + view_btn[VIEW_SYSTEM]->SetButtonState(1); + use_filter_mode = SELECT_REGION; + } + + else if (view_btn[VIEW_REGION] == event->window) { + star_map->SetViewMode(VIEW_REGION); + view_btn[VIEW_REGION]->SetButtonState(1); + use_filter_mode = SELECT_STARSHIP; + } + + if (use_filter_mode >= 0) { + for (int i = 0; i < 6; i++) { + if (i == use_filter_mode) + filter_btn[i]->SetButtonState(1); + else + filter_btn[i]->SetButtonState(0); + } + + UseFilter(use_filter_mode); + } +} + +// +--------------------------------------------------------------------+ + +void +NavDlg::OnFilter(AWEvent* event) +{ + int filter_index = -1; + for (int i = 0; i < 6; i++) { + if (filter_btn[i] == event->window) { + filter_index = i; + filter_btn[i]->SetButtonState(1); + } + else { + filter_btn[i]->SetButtonState(0); + } + } + + if (filter_index >= 0) + UseFilter(filter_index); +} + +void +NavDlg::UseFilter(int filter_index) +{ + seln_mode = filter_index; + + star_map->SetSelectionMode(seln_mode); + UpdateSelection(); + UpdateLists(); +} + +// +--------------------------------------------------------------------+ + +void +NavDlg::UseViewMode(int mode) +{ + if (mode >= 0 && mode < 3) { + int use_filter_mode = -1; + + view_btn[VIEW_GALAXY]->SetButtonState(0); + view_btn[VIEW_SYSTEM]->SetButtonState(0); + view_btn[VIEW_REGION]->SetButtonState(0); + + if (mode == 0) { + star_map->SetViewMode(VIEW_GALAXY); + view_btn[VIEW_GALAXY]->SetButtonState(1); + use_filter_mode = SELECT_SYSTEM; + } + + else if (mode == 1) { + star_map->SetViewMode(VIEW_SYSTEM); + view_btn[VIEW_SYSTEM]->SetButtonState(1); + use_filter_mode = SELECT_REGION; + } + + else if (mode == 2) { + star_map->SetViewMode(VIEW_REGION); + view_btn[VIEW_REGION]->SetButtonState(1); + use_filter_mode = SELECT_STARSHIP; + } + + if (use_filter_mode >= 0) { + for (int i = 0; i < 6; i++) { + filter_btn[i]->SetButtonState(i == use_filter_mode); + } + + UseFilter(use_filter_mode); + } + } +} + +void +NavDlg::SelectStar(Orbital* star) +{ + UseViewMode(0); + + if (stars.size()) { + int sel = 0; + + ListIter iter = stars; + while (++iter) { + if (iter.value() == star) { + int old_seln_mode = seln_mode; + UseFilter(SELECT_SYSTEM); + SelectObject(sel); + UseFilter(old_seln_mode); + return; + } + + sel++; + } + } +} + +void +NavDlg::SelectPlanet(Orbital* planet) +{ + UseViewMode(1); + + if (planets.size()) { + int sel = 0; + + ListIter iter = planets; + while (++iter) { + if (iter.value() == planet) { + int old_seln_mode = seln_mode; + UseFilter(SELECT_PLANET); + SelectObject(sel); + UseFilter(old_seln_mode); + return; + } + + sel++; + } + } +} + +void +NavDlg::SelectRegion(OrbitalRegion* rgn) +{ + UseViewMode(2); + + if (regions.size()) { + int sel = 0; + + ListIter iter = regions; + while (++iter) { + if (iter.value() == rgn) { + int old_seln_mode = seln_mode; + UseFilter(SELECT_REGION); + SelectObject(sel); + UseFilter(old_seln_mode); + return; + } + + sel++; + } + } +} + +// +--------------------------------------------------------------------+ + +void +NavDlg::OnSelect(AWEvent* event) +{ + int index = -1; + + for (int i = 0; i < seln_list->NumItems(); i++) + if (seln_list->IsSelected(i)) + index = i; + + if (index >= 0) + SelectObject(index); +} + +// +--------------------------------------------------------------------+ + +void +NavDlg::SelectObject(int index) +{ + Text selected = seln_list->GetItemText(index); + int selection = seln_list->GetItemData(index); + + star_map->SetSelection(selection); + SetSystem(star_map->GetSystem()); +} + +// +--------------------------------------------------------------------+ + +void +NavDlg::UpdateSelection() +{ + if (!info_list) + return; + + if (!star_map) + return; + + info_list->ClearItems(); + + Text units_km = Text(" ") + Game::GetText("NavDlg.units.kilometers"); + Text units_tonnes = Text(" ") + Game::GetText("NavDlg.units.tonnes"); + + if (seln_mode <= SELECT_REGION) { + Orbital* s = star_map->GetSelection(); + + if (s) { + char radius[32]; + char mass[32]; + char orbit[32]; + char period[32]; + char units[32]; + + double p = s->Period(); + + if (p < 60) { + sprintf(units, " %s", Game::GetText("NavDlg.units.seconds").data()); + } + else if (p < 3600) { + p /= 60; + sprintf(units, " %s", Game::GetText("NavDlg.units.minutes").data()); + } + else if (p < 24 * 3600) { + p /= 3600; + sprintf(units, " %s", Game::GetText("NavDlg.units.hours").data()); + } + else if (p < 365.25 * 24 * 3600) { + p /= 24*3600; + sprintf(units, " %s", Game::GetText("NavDlg.units.days").data()); + } + else { + p /= 365.25*24*3600; + sprintf(units, " %s", Game::GetText("NavDlg.units.years").data()); + } + + FormatNumberExp(radius, s->Radius()/1000); + FormatNumberExp(mass, s->Mass()/1000); + FormatNumberExp(orbit, s->Orbit()/1000); + FormatNumberExp(period, p); + + strcat(radius, units_km.data()); + strcat(mass, units_tonnes.data()); + strcat(orbit, units_km.data()); + strcat(period, units); + + if (seln_mode >= SELECT_SYSTEM) { + info_list->AddItem(Game::GetText(Text("NavDlg.filter.") + filter_name[seln_mode])); + info_list->AddItem(Game::GetText("NavDlg.radius")); + if (s->Mass() > 0) + info_list->AddItem(Game::GetText("NavDlg.mass")); + info_list->AddItem(Game::GetText("NavDlg.orbit")); + info_list->AddItem(Game::GetText("NavDlg.period")); + + int row = 0; + info_list->SetItemText(row++, 1, s->Name()); + info_list->SetItemText(row++, 1, radius); + if (s->Mass() > 0) + info_list->SetItemText(row++, 1, mass); + info_list->SetItemText(row++, 1, orbit); + info_list->SetItemText(row++, 1, period); + } + } + } + + else if (seln_mode == SELECT_STATION || + seln_mode == SELECT_STARSHIP || + seln_mode == SELECT_FIGHTER) { + + Ship* sel_ship = star_map->GetSelectedShip(); + MissionElement* sel_elem = star_map->GetSelectedElem(); + + if (sel_ship) { + Text order_desc = Game::GetText("NavDlg.none"); + char shield[16]; + char hull[16]; + char range[32]; + + sprintf(shield, "%03d", sel_ship->ShieldStrength()); + sprintf(hull, "%03d", sel_ship->HullStrength()); + sprintf(range, Game::GetText("NavDlg.not-avail").data()); + + if (ship) { + FormatNumberExp(range, Point(sel_ship->Location()-ship->Location()).length()/1000); + strcat(range, units_km.data()); + } + + info_list->AddItem(Game::GetText("NavDlg.name")); + info_list->AddItem(Game::GetText("NavDlg.class")); + info_list->AddItem(Game::GetText("NavDlg.sector")); + info_list->AddItem(Game::GetText("NavDlg.shield")); + info_list->AddItem(Game::GetText("NavDlg.hull")); + info_list->AddItem(Game::GetText("NavDlg.range")); + info_list->AddItem(Game::GetText("NavDlg.orders")); + + int row = 0; + info_list->SetItemText(row++, 1, sel_ship->Name()); + info_list->SetItemText(row++, 1, Text(sel_ship->Abbreviation()) + Text(" ") + Text(sel_ship->Design()->display_name)); + info_list->SetItemText(row++, 1, sel_ship->GetRegion()->Name()); + info_list->SetItemText(row++, 1, shield); + info_list->SetItemText(row++, 1, hull); + info_list->SetItemText(row++, 1, range); + info_list->SetItemText(row++, 1, order_desc); + } + + else if (sel_elem) { + Text order_desc = Game::GetText("NavDlg.none"); + char range[32]; + + MissionElement* self = mission->GetElements()[0]; + if (self) + FormatNumberExp(range, Point(sel_elem->Location()-self->Location()).length()/1000); + else + strcpy(range, "0"); + + strcat(range, units_km.data()); + + info_list->AddItem(Game::GetText("NavDlg.name")); + info_list->AddItem(Game::GetText("NavDlg.class")); + info_list->AddItem(Game::GetText("NavDlg.sector")); + info_list->AddItem(Game::GetText("NavDlg.range")); + info_list->AddItem(Game::GetText("NavDlg.orders")); + + int row = 0; + info_list->SetItemText(row++, 1, sel_elem->Name()); + + if (sel_elem->GetDesign()) + info_list->SetItemText(row++, 1, sel_elem->Abbreviation() + Text(" ") + sel_elem->GetDesign()->name); + else + info_list->SetItemText(row++, 1, Game::GetText("NavDlg.unknown")); + + info_list->SetItemText(row++, 1, sel_elem->Region()); + info_list->SetItemText(row++, 1, range); + info_list->SetItemText(row++, 1, order_desc); + } + } +} + +// +--------------------------------------------------------------------+ + +void +NavDlg::UpdateLists() +{ + if (!seln_list) + return; + + int seln_index = -1; + int top_index = -1; + + if (seln_list->IsSelecting()) + seln_index = seln_list->GetListIndex(); + + top_index = seln_list->GetTopIndex(); + + seln_list->ClearItems(); + + switch (seln_mode) { + case SELECT_SYSTEM: + { + seln_list->SetColumnTitle(0, Game::GetText(Text("NavDlg.filter.") + filter_name[seln_mode])); + int i = 0; + ListIter iter = star_map->GetGalaxy(); + while (++iter) + seln_list->AddItemWithData(iter->Name(), i++); + } + break; + + case SELECT_PLANET: + { + seln_list->SetColumnTitle(0, Game::GetText(Text("NavDlg.filter.") + filter_name[seln_mode])); + int i = 0; + ListIter iter = planets; + while (++iter) { + if (iter->Type() == Orbital::MOON) + seln_list->AddItemWithData(Text("- ") + Text(iter->Name()), i++); + else + seln_list->AddItemWithData(iter->Name(), i++); + } + } + break; + + case SELECT_REGION: + { + seln_list->SetColumnTitle(0, Game::GetText(Text("NavDlg.filter.") + filter_name[seln_mode])); + int i = 0; + ListIter iter = regions; + while (++iter) { + seln_list->AddItemWithData(iter->Name(), i++); + } + } + break; + + case SELECT_STATION: + case SELECT_STARSHIP: + case SELECT_FIGHTER: + { + seln_list->SetColumnTitle(0, Game::GetText(Text("NavDlg.filter.") + filter_name[seln_mode])); + int i = 0; + + if (mission) { + ListIter elem = mission->GetElements(); + while (++elem) { + MissionElement* e = elem.value(); + bool filter_ok = + (seln_mode == SELECT_STATION && e->IsStatic()) || + (seln_mode == SELECT_STARSHIP && e->IsStarship() && !e->IsStatic()) || + (seln_mode == SELECT_FIGHTER && e->IsDropship() && !e->IsSquadron()); + + if (filter_ok) { + bool visible = editor || + e->GetIFF() == 0 || + e->GetIFF() == mission->Team() || + e->IntelLevel() > Intel::KNOWN; + + if (visible) + seln_list->AddItemWithData(e->Name(), e->Identity()); + } + } + } + + else if (ship) { + Sim* sim = Sim::GetSim(); + ListIter r_iter = sim->GetRegions(); + while (++r_iter) { + SimRegion* rgn = r_iter.value(); + + ListIter s_iter = rgn->Ships(); + while (++s_iter) { + Ship* s = s_iter.value(); + bool filter_ok = + (seln_mode == SELECT_STATION && s->IsStatic()) || + (seln_mode == SELECT_STARSHIP && s->IsStarship() && !s->IsStatic()) || + (seln_mode == SELECT_FIGHTER && s->IsDropship()); + + + if (filter_ok) { + bool visible = s->GetIFF() == 0 || + s->GetIFF() == ship->GetIFF() || + s->GetElement() && + s->GetElement()->IntelLevel() > Intel::KNOWN; + + if (visible) + seln_list->AddItemWithData(s->Name(), s->Identity()); + } + } + } + } + } + break; + + default: + break; + } + + if (top_index >= 0) + seln_list->ScrollTo(top_index); + + if (seln_index >= 0) + seln_list->SetSelected(seln_index); + + else + CoordinateSelection(); +} + +// +--------------------------------------------------------------------+ + +void +NavDlg::OnEngage(AWEvent* event) +{ + bool hide = false; + + if (ship) { + NavSystem* navsys = ship->GetNavSystem(); + if (navsys) { + if (navsys->AutoNavEngaged()) { + navsys->DisengageAutoNav(); + commit_btn->SetText(Game::GetText("NavDlg.commit")); + commit_btn->SetBackColor(commit_color); + } + else { + navsys->EngageAutoNav(); + commit_btn->SetText(Game::GetText("NavDlg.cancel")); + commit_btn->SetBackColor(cancel_color); + hide = true; + } + + Sim* sim = Sim::GetSim(); + if (sim) + ship->SetControls(sim->GetControls()); + } + } + + if (manager && hide) + manager->ShowNavDlg(); // also hides +} + +// +--------------------------------------------------------------------+ + +void +NavDlg::OnCommit(AWEvent* event) +{ + if (manager) + manager->ShowNavDlg(); // also hides +} + + +// +--------------------------------------------------------------------+ + +void +NavDlg::OnCancel(AWEvent* event) +{ + if (manager) + manager->ShowNavDlg(); // also hides +} + +// +--------------------------------------------------------------------+ + +void +NavDlg::SetNavEditMode(int mode) +{ + if (nav_edit_mode != mode) { + if (mode != NAV_EDIT_NONE) { + int map_mode = star_map->GetSelectionMode(); + if (map_mode != -1) + seln_mode = map_mode; + + star_map->SetSelectionMode(-1); + } + else { + star_map->SetSelectionMode(seln_mode); + } + + nav_edit_mode = mode; + } +} + +int +NavDlg::GetNavEditMode() +{ + return nav_edit_mode; +} + +void +NavDlg::OnMapDown(AWEvent* event) +{ +} + +void +NavDlg::OnMapMove(AWEvent* event) +{ +} + +void +NavDlg::OnMapClick(AWEvent* event) +{ + static DWORD click_time = 0; + + SetSystem(star_map->GetSystem()); + CoordinateSelection(); + + // double-click: + if (Game::RealTime() - click_time < 350) { + MissionElement* elem = star_map->GetSelectedElem(); + MsnElemDlg* msn_elem_dlg = manager->GetMsnElemDlg(); + + if (elem && msn_elem_dlg) { + msn_elem_dlg->SetMission(mission); + msn_elem_dlg->SetMissionElement(elem); + manager->ShowMsnElemDlg(); + } + } + + click_time = Game::RealTime(); +} + +void +NavDlg::CoordinateSelection() +{ + if (!seln_list || !star_map) + return; + + switch (seln_mode) { + default: + case SELECT_SYSTEM: + case SELECT_PLANET: + case SELECT_REGION: + { + seln_list->ClearSelection(); + + Orbital* selected = star_map->GetSelection(); + + if (selected) { + for (int i = 0; i < seln_list->NumItems(); i++) { + if (seln_list->GetItemText(i) == selected->Name() || + seln_list->GetItemText(i) == Text("- ") + selected->Name()) { + seln_list->SetSelected(i, true); + } + } + } + } + break; + + case SELECT_STATION: + case SELECT_STARSHIP: + case SELECT_FIGHTER: + { + seln_list->ClearSelection(); + + Ship* selected = star_map->GetSelectedShip(); + MissionElement* elem = star_map->GetSelectedElem(); + + if (selected) { + for (int i = 0; i < seln_list->NumItems(); i++) { + if (seln_list->GetItemText(i) == selected->Name()) { + seln_list->SetSelected(i, true); + } + } + } + + else if (elem) { + for (int i = 0; i < seln_list->NumItems(); i++) { + if (seln_list->GetItemText(i) == elem->Name()) { + seln_list->SetSelected(i, true); + } + } + } + } + break; + } +} + +// +--------------------------------------------------------------------+ + +void +NavDlg::OnClose(AWEvent* event) +{ + if (manager) + manager->HideNavDlg(); +} diff --git a/Stars45/NavDlg.h b/Stars45/NavDlg.h new file mode 100644 index 0000000..0c51b5c --- /dev/null +++ b/Stars45/NavDlg.h @@ -0,0 +1,119 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NavDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Navigation Active Window class +*/ + +#ifndef NavDlg_h +#define NavDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "Bitmap.h" +#include "Button.h" +#include "Font.h" + +class BaseScreen; +class MapView; +class StarSystem; +class Ship; +class SimRegion; +class Orbital; +class OrbitalRegion; +class Mission; + +// +--------------------------------------------------------------------+ + +class NavDlg : public FormWindow +{ +public: + NavDlg(Screen* s, FormDef& def, BaseScreen* mgr); + virtual ~NavDlg(); + + virtual void RegisterControls(); + + // Operations: + virtual void OnView(AWEvent* event); + virtual void OnFilter(AWEvent* event); + virtual void OnSelect(AWEvent* event); + virtual void OnCommit(AWEvent* event); + virtual void OnCancel(AWEvent* event); + virtual void OnEngage(AWEvent* event); + virtual void OnMapDown(AWEvent* event); + virtual void OnMapMove(AWEvent* event); + virtual void OnMapClick(AWEvent* event); + virtual void OnClose(AWEvent* event); + + virtual void ExecFrame(); + StarSystem* GetSystem() const { return star_system; } + void SetSystem(StarSystem* s); + Mission* GetMission() const { return mission; } + void SetMission(Mission* m); + Ship* GetShip() const { return ship; } + void SetShip(Ship* s); + + bool GetEditorMode() const { return editor; } + void SetEditorMode(bool b); + + void UseViewMode(int mode); + void UseFilter(int index); + void SelectObject(int index); + void UpdateSelection(); + void UpdateLists(); + void CoordinateSelection(); + + void SelectStar(Orbital* star); + void SelectPlanet(Orbital* planet); + void SelectRegion(OrbitalRegion* rgn); + + enum NAV_EDIT_MODE { NAV_EDIT_NONE = 0, + NAV_EDIT_ADD = 1, + NAV_EDIT_DEL = 2, + NAV_EDIT_MOVE = 3 }; + + void SetNavEditMode(int mode); + int GetNavEditMode(); + +protected: + Button* view_btn[3]; + Button* filter_btn[6]; + Button* commit_btn; + Button* zoom_in_btn; + Button* zoom_out_btn; + Button* close_btn; + + MapView* star_map; + ActiveWindow* map_win; + ActiveWindow* loc_labels; + ActiveWindow* dst_labels; + ActiveWindow* loc_data; + ActiveWindow* dst_data; + + ListBox* seln_list; + ListBox* info_list; + + BaseScreen* manager; + int seln_mode; + int nav_edit_mode; + + StarSystem* star_system; + List stars; + List planets; + List regions; + List contacts; + + Ship* ship; + Mission* mission; + bool editor; +}; + +#endif NavDlg_h + diff --git a/Stars45/NavLight.cpp b/Stars45/NavLight.cpp new file mode 100644 index 0000000..2a8b447 --- /dev/null +++ b/Stars45/NavLight.cpp @@ -0,0 +1,188 @@ +/* Project Starshatter 5.0 + Destroyer Studios LLC + Copyright © 1997-2007. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NavLight.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Navigation Light System class +*/ + +#include "MemDebug.h" +#include "NavLight.h" + +#include "Game.h" +#include "Bitmap.h" +#include "DataLoader.h" + +// +----------------------------------------------------------------------+ + +static Bitmap* images[4]; + +// +----------------------------------------------------------------------+ + +NavLight::NavLight(double p, double s) + : System(COMPUTER, 32, "Navigation Lights", 1, 0), + period(p), nlights(0), scale(s), enable(true) +{ + name = Game::GetText("sys.nav-light"); + abrv = Game::GetText("sys.nav-light.abrv"); + + ZeroMemory(beacon, sizeof(beacon)); + ZeroMemory(beacon_type, sizeof(beacon_type)); + ZeroMemory(pattern, sizeof(pattern)); +} + +// +----------------------------------------------------------------------+ + +NavLight::NavLight(const NavLight& c) + : System(c), period(c.period), scale(c.scale), + nlights(0), enable(true) +{ + Mount(c); + SetAbbreviation(c.Abbreviation()); + ZeroMemory(beacon, sizeof(beacon)); + ZeroMemory(beacon_type, sizeof(beacon_type)); + + nlights = c.nlights; + + for (int i = 0; i < nlights; i++) { + loc[i] = c.loc[i]; + pattern[i] = c.pattern[i]; + beacon_type[i] = c.beacon_type[i]; + + DriveSprite* rep = new(__FILE__,__LINE__) DriveSprite(images[beacon_type[i]]); + rep->Scale(c.scale); + + beacon[i] = rep; + } + + offset = rand(); +} + +// +--------------------------------------------------------------------+ + +NavLight::~NavLight() +{ + for (int i = 0; i < nlights; i++) + GRAPHIC_DESTROY(beacon[i]); +} + +// +--------------------------------------------------------------------+ + +void +NavLight::Initialize() +{ + static int initialized = 0; + if (initialized) return; + + DataLoader* loader = DataLoader::GetLoader(); + loader->LoadTexture("beacon1.pcx", images[0], Bitmap::BMP_TRANSLUCENT); + loader->LoadTexture("beacon2.pcx", images[1], Bitmap::BMP_TRANSLUCENT); + loader->LoadTexture("beacon3.pcx", images[2], Bitmap::BMP_TRANSLUCENT); + loader->LoadTexture("beacon4.pcx", images[3], Bitmap::BMP_TRANSLUCENT); + + initialized = 1; +} + +void +NavLight::Close() +{ +} + +// +--------------------------------------------------------------------+ + +void +NavLight::ExecFrame(double seconds) +{ + if (enable && power_on) { + double t = (Game::GameTime()+offset) / 1000.0; + DWORD n = (int) (fmod(t, period) * 32 / period); + DWORD code = 1 << n; + + for (int i = 0; i < nlights; i++) { + if (beacon[i]) { + if (pattern[i] & code) + beacon[i]->SetShade(1); + else + beacon[i]->SetShade(0); + } + } + } + else { + for (int i = 0; i < nlights; i++) { + if (beacon[i]) { + beacon[i]->SetShade(0); + } + } + } +} + +void +NavLight::Enable() +{ + enable = true; +} + +void +NavLight::Disable() +{ + enable = false; +} + +void +NavLight::AddBeacon(Point l, DWORD ptn, int t) +{ + if (nlights < MAX_LIGHTS) { + loc[nlights] = l; + pattern[nlights] = ptn; + beacon_type[nlights] = t; + + DriveSprite* rep = new(__FILE__,__LINE__) DriveSprite(images[t]); + rep->Scale(scale); + + beacon[nlights] = rep; + nlights++; + } +} + +void +NavLight::SetPeriod(double p) +{ + period = p; +} + +void +NavLight::SetPattern(int index, DWORD ptn) +{ + if (index >= 0 && index < nlights) + pattern[index] = ptn; +} + +void +NavLight::SetOffset(DWORD o) +{ + offset = o; +} + +// +--------------------------------------------------------------------+ + +void +NavLight::Orient(const Physical* rep) +{ + System::Orient(rep); + + const Matrix& orientation = rep->Cam().Orientation(); + Point ship_loc = rep->Location(); + + for (int i = 0; i < nlights; i++) { + Point projector = (loc[i] * orientation) + ship_loc; + if (beacon[i]) beacon[i]->MoveTo(projector); + } +} + + diff --git a/Stars45/NavLight.h b/Stars45/NavLight.h new file mode 100644 index 0000000..1d3e4fc --- /dev/null +++ b/Stars45/NavLight.h @@ -0,0 +1,67 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NavLight.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Navigation Lights System class +*/ + +#ifndef NavLight_h +#define NavLight_h + +#include "Types.h" +#include "System.h" +#include "DriveSprite.h" +#include "Geometry.h" + +// +--------------------------------------------------------------------+ + +class NavLight : public System +{ +public: + enum Constants { MAX_LIGHTS = 8 }; + + NavLight(double period, double scale); + NavLight(const NavLight& rhs); + virtual ~NavLight(); + + static void Initialize(); + static void Close(); + + virtual void ExecFrame(double seconds); + + int NumBeacons() const { return nlights; } + Sprite* Beacon(int index) const { return beacon[index]; } + bool IsEnabled() const { return enable; } + + virtual void Enable(); + virtual void Disable(); + virtual void AddBeacon(Point loc, DWORD pattern, int type=1); + virtual void SetPeriod(double p); + virtual void SetPattern(int index, DWORD p); + virtual void SetOffset(DWORD o); + + virtual void Orient(const Physical* rep); + +protected: + double period; + double scale; + bool enable; + + int nlights; + + Point loc[MAX_LIGHTS]; + DriveSprite* beacon[MAX_LIGHTS]; + DWORD pattern[MAX_LIGHTS]; + int beacon_type[MAX_LIGHTS]; + DWORD offset; +}; + +#endif NavLight_h + diff --git a/Stars45/NavSystem.cpp b/Stars45/NavSystem.cpp new file mode 100644 index 0000000..c0a620c --- /dev/null +++ b/Stars45/NavSystem.cpp @@ -0,0 +1,113 @@ +/* Project Starshatter 5.0 + Destroyer Studios LLC + Copyright © 1997-2007. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NavSystem.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Navigation System class implementation +*/ + +#include "MemDebug.h" +#include "NavSystem.h" +#include "Ship.h" +#include "Sim.h" +#include "HUDSounds.h" +#include "Button.h" +#include "Game.h" + +// +----------------------------------------------------------------------+ + +NavSystem::NavSystem() + : System(COMPUTER, 2, "Auto Nav System", 1, 1,1,1), + autonav(0) +{ + name = Game::GetText("sys.nav-system"); + abrv = Game::GetText("sys.nav-system.abrv"); + + power_flags = POWER_WATTS | POWER_CRITICAL; +} + +// +----------------------------------------------------------------------+ + +NavSystem::NavSystem(const NavSystem& s) + : System(s), autonav(0) +{ + Mount(s); + + power_flags = POWER_WATTS | POWER_CRITICAL; +} + +// +--------------------------------------------------------------------+ + +NavSystem::~NavSystem() +{ } + +// +--------------------------------------------------------------------+ + +void +NavSystem::ExecFrame(double seconds) +{ + if (autonav && ship && !ship->GetNextNavPoint()) + autonav = false; + + energy = 0.0f; + System::ExecFrame(seconds); +} + +// +----------------------------------------------------------------------+ + +bool +NavSystem::AutoNavEngaged() +{ + return ship && autonav && IsPowerOn(); +} + +void +NavSystem::EngageAutoNav() +{ + if (IsPowerOn() && !autonav) { + if (!ship->GetNextNavPoint()) { + Button::PlaySound(Button::SND_REJECT); + } + else { + HUDSounds::PlaySound(HUDSounds::SND_NAV_MODE); + autonav = true; + } + } +} + +void +NavSystem::DisengageAutoNav() +{ + if (autonav) + HUDSounds::PlaySound(HUDSounds::SND_NAV_MODE); + + autonav = false; +} + +// +--------------------------------------------------------------------+ + +void +NavSystem::Distribute(double delivered_energy, double seconds) +{ + if (IsPowerOn()) { + // convert Joules to Watts: + energy = (float) (delivered_energy/seconds); + + // brown out: + if (energy < capacity*0.75f) + power_on = false; + + // spike: + else if (energy > capacity*1.5f) { + power_on = false; + ApplyDamage(50); + } + } +} + diff --git a/Stars45/NavSystem.h b/Stars45/NavSystem.h new file mode 100644 index 0000000..1345839 --- /dev/null +++ b/Stars45/NavSystem.h @@ -0,0 +1,55 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NavSystem.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Nav Points and so on... +*/ + +#ifndef NavSystem_h +#define NavSystem_h + +#include "Types.h" +#include "Geometry.h" +#include "System.h" + +// +--------------------------------------------------------------------+ + +class StarSystem; +class Orbital; +class OrbitalBody; +class OrbitalRegion; +class Ship; + +// +--------------------------------------------------------------------+ + +class NavSystem : public System +{ +public: + NavSystem(); + NavSystem(const NavSystem& rhs); + virtual ~NavSystem(); + + virtual void ExecFrame(double seconds); + + virtual void Distribute(double delivered_energy, double seconds); + + bool AutoNavEngaged(); + void EngageAutoNav(); + void DisengageAutoNav(); + +protected: + bool autonav; +}; + +// +--------------------------------------------------------------------+ + + +#endif NavSystem_h + diff --git a/Stars45/NetAddrDlg.cpp b/Stars45/NetAddrDlg.cpp new file mode 100644 index 0000000..15156ce --- /dev/null +++ b/Stars45/NetAddrDlg.cpp @@ -0,0 +1,160 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetAddrDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Mission Select Dialog Active Window class +*/ + +#include "MemDebug.h" +#include "NetAddrDlg.h" +#include "MenuScreen.h" +#include "NetClientConfig.h" + +#include "Game.h" +#include "DataLoader.h" +#include "Button.h" +#include "EditBox.h" +#include "ListBox.h" +#include "Slider.h" +#include "Video.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "ParseUtil.h" +#include "FormatUtil.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +DEF_MAP_CLIENT(NetAddrDlg, OnSave); +DEF_MAP_CLIENT(NetAddrDlg, OnCancel); + +// +--------------------------------------------------------------------+ + +NetAddrDlg::NetAddrDlg(Screen* s, FormDef& def, MenuScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), manager(mgr), + btn_save(0), btn_cancel(0), edt_name(0), edt_addr(0), edt_port(0), edt_pass(0) +{ + Init(def); +} + +NetAddrDlg::~NetAddrDlg() +{ +} + +// +--------------------------------------------------------------------+ + +void +NetAddrDlg::RegisterControls() +{ + btn_save = (Button*) FindControl(1); + btn_cancel = (Button*) FindControl(2); + + REGISTER_CLIENT(EID_CLICK, btn_save, NetAddrDlg, OnSave); + REGISTER_CLIENT(EID_CLICK, btn_cancel, NetAddrDlg, OnCancel); + + edt_name = (EditBox*) FindControl(200); + edt_addr = (EditBox*) FindControl(201); + edt_port = (EditBox*) FindControl(202); + edt_pass = (EditBox*) FindControl(203); + + if (edt_name) edt_name->SetText(""); + if (edt_addr) edt_addr->SetText(""); + if (edt_port) edt_port->SetText(""); + if (edt_pass) edt_pass->SetText(""); +} + +// +--------------------------------------------------------------------+ + +void +NetAddrDlg::Show() +{ + if (!IsShown()) { + FormWindow::Show(); + + if (edt_name) edt_name->SetText(""); + if (edt_addr) edt_addr->SetText(""); + if (edt_port) edt_port->SetText(""); + if (edt_pass) edt_pass->SetText(""); + + if (edt_name) edt_name->SetFocus(); + } +} + +// +--------------------------------------------------------------------+ + +static bool tab_latch = false; + +void +NetAddrDlg::ExecFrame() +{ + if (Keyboard::KeyDown(VK_RETURN)) { + OnSave(0); + } +} + +// +--------------------------------------------------------------------+ + +void +NetAddrDlg::OnSave(AWEvent* event) +{ + NetClientConfig* config = NetClientConfig::GetInstance(); + + if (config && + edt_addr && edt_addr->GetText().length() > 0 && + edt_port && edt_port->GetText().length() > 0) + { + Text name; + Text addr; + Text pass; + int port; + + sscanf(edt_port->GetText().data(), "%d", &port); + + if (edt_name && edt_name->GetText().length() < 250) { + char buffer[256]; + strcpy(buffer, edt_name->GetText().data()); + char* p = strpbrk(buffer, "\n\r\t"); + if (p) *p = 0; + + name = SafeQuotes(buffer); + } + + if (edt_pass && edt_pass->GetText().length() < 250) { + char buffer[256]; + strcpy(buffer, edt_pass->GetText().data()); + char* p = strpbrk(buffer, "\n\r\t"); + if (p) *p = 0; + + pass = SafeQuotes(buffer); + } + + if (edt_addr && edt_addr->GetText().length() < 250) { + char buffer[256]; + strcpy(buffer, edt_addr->GetText().data()); + char* p = strpbrk(buffer, "\n\r\t"); + if (p) *p = 0; + + addr = SafeQuotes(buffer); + } + + config->AddServer(name, addr, port, pass, true); + config->Save(); + } + + if (manager) + manager->ShowNetClientDlg(); +} + +void +NetAddrDlg::OnCancel(AWEvent* event) +{ + if (manager) + manager->ShowNetClientDlg(); +} diff --git a/Stars45/NetAddrDlg.h b/Stars45/NetAddrDlg.h new file mode 100644 index 0000000..91b8182 --- /dev/null +++ b/Stars45/NetAddrDlg.h @@ -0,0 +1,60 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetAddrDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Network Server Address/Port Dialog Active Window class +*/ + +#ifndef NetAddrDlg_h +#define NetAddrDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "Bitmap.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Font.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class MenuScreen; + +// +--------------------------------------------------------------------+ + +class NetAddrDlg : public FormWindow +{ +public: + NetAddrDlg(Screen* s, FormDef& def, MenuScreen* mgr); + virtual ~NetAddrDlg(); + + virtual void RegisterControls(); + virtual void Show(); + + // Operations: + virtual void ExecFrame(); + + virtual void OnSave(AWEvent* event); + virtual void OnCancel(AWEvent* event); + +protected: + MenuScreen* manager; + + Button* btn_save; + Button* btn_cancel; + EditBox* edt_name; + EditBox* edt_addr; + EditBox* edt_port; + EditBox* edt_pass; +}; + +#endif NetAddrDlg_h + diff --git a/Stars45/NetAdminChat.cpp b/Stars45/NetAdminChat.cpp new file mode 100644 index 0000000..f5d2549 --- /dev/null +++ b/Stars45/NetAdminChat.cpp @@ -0,0 +1,122 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetAdminServer.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + HTTP Servlet Engine for Multiplayer Admin +*/ + + +#include "MemDebug.h" +#include "NetAdminChat.h" +#include "NetLobbyServer.h" +#include "NetServerConfig.h" +#include "NetUser.h" +#include "NetChat.h" +#include "NetUtil.h" + +#include "HttpServlet.h" +#include "NetLayer.h" +#include "FormatUtil.h" + +// +-------------------------------------------------------------------+ + +NetAdminChat::NetAdminChat() +{ } + +// +-------------------------------------------------------------------+ + +bool +NetAdminChat::DoGet(HttpRequest& request, HttpResponse& response) +{ + if (CheckUser(request, response)) { + NetLobbyServer* lobby = NetLobbyServer::GetInstance(); + + if (lobby) { + Text msg = request.GetParam("msg"); + Text act = request.GetParam("action"); + + if (msg.length()) { + lobby->AddChat(user, msg); + + if (user) + NetUtil::SendChat(0xffff, user->Name(), msg); + } + + else if (act.length()) { + if (act == "clear") + lobby->ClearChat(); + + else if (act == "save") + lobby->SaveChat(); + } + } + + response.SetStatus(HttpResponse::SC_OK); + response.AddHeader("MIME-Version", "1.0"); + response.AddHeader("Content-Type", "text/html"); + response.AddHeader("Cache-Control", "no-cache"); + response.AddHeader("Expires", "-1"); + + response.SetContent(GetHead("Chat") + + GetTitleBar(GetStatLine(), + "onLoad=\"self.focus();document.chatForm.msg.focus();\"") + + GetContent() + + GetBodyClose()); + } + + return true; +} + +// +-------------------------------------------------------------------+ + +Text +NetAdminChat::GetContent() +{ + Text content = "
\n"; + + int nchat = 0; + NetLobbyServer* lobby = NetLobbyServer::GetInstance(); + if (lobby) { + content += "\n\n"; + + ListIter iter = lobby->GetChat(); + while (++iter) { + NetChatEntry* c = iter.value(); + + content += " \n"; + } + + content += "
"; + content += FormatTimeString(c->GetTime()); + content += ""; + content += c->GetUser(); + content += ""; + content += c->GetMessage(); + content += "
\n\n"; + } + + content += "
\n
\n\ +
\n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ +
    
  Refresh\ +  ·  Save  ·  Clear
\n\ +
\n\ +
\n\n"; + + content += GetCopyright(); + return content; +} diff --git a/Stars45/NetAdminChat.h b/Stars45/NetAdminChat.h new file mode 100644 index 0000000..233f5f6 --- /dev/null +++ b/Stars45/NetAdminChat.h @@ -0,0 +1,33 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetAdminChat.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + HTTP Servlet Engine for Multiplayer Admin +*/ + + +#ifndef NetAdminChat_h +#define NetAdminChat_h + +#include "NetAdminServer.h" + +// +-------------------------------------------------------------------+ + +class NetAdminChat : public NetAdminServlet +{ +public: + NetAdminChat(); + virtual ~NetAdminChat() { } + + virtual bool DoGet(HttpRequest& request, HttpResponse& response); + virtual Text GetContent(); +}; + +#endif NetAdminChat_h \ No newline at end of file diff --git a/Stars45/NetAdminServer.cpp b/Stars45/NetAdminServer.cpp new file mode 100644 index 0000000..7f38ff6 --- /dev/null +++ b/Stars45/NetAdminServer.cpp @@ -0,0 +1,887 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetAdminServer.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + HTTP Servlet Engine for Multiplayer Admin +*/ + + +#include "MemDebug.h" +#include "NetAdminServer.h" +#include "NetLobbyServer.h" +#include "NetServerConfig.h" +#include "NetClientConfig.h" +#include "NetAdminChat.h" +#include "NetUser.h" +#include "NetChat.h" + +#include "StarServer.h" +#include "HttpServlet.h" +#include "NetLayer.h" + +#include "DataLoader.h" +#include "FormatUtil.h" +#include "MachineInfo.h" + +extern const char* versionInfo; + +// +-------------------------------------------------------------------+ +// +-------------------------------------------------------------------+ +// +-------------------------------------------------------------------+ + +class NetAdminLogin : public NetAdminServlet +{ +public: + NetAdminLogin() { } + virtual ~NetAdminLogin() { } + + virtual bool DoGet(HttpRequest& request, HttpResponse& response) { + NetServerConfig* config = NetServerConfig::GetInstance(); + + Text admin_name = "system"; + Text admin_pass = "manager"; + + if (config) { + admin_name = config->GetAdminName(); + admin_pass = config->GetAdminPass(); + } + + Text name = request.GetParam("user"); + Text pass = request.GetParam("pass"); + + Sleep(500); + + if (CheckUser(request, response)) { + response.SetStatus(HttpResponse::SC_TEMPORARY_REDIRECT); + response.SetHeader("MIME-Version", "1.0"); + response.SetHeader("Content-Type", "text/html"); + response.SetHeader("Cache-Control", "no-cache"); + response.SetHeader("Expires", "-1"); + response.SetHeader("Location", "/home"); + + response.SetContent(GetHead("Login") + + "
You are already logged in.
" + + GetBodyClose()); + } + + else if (name == admin_name && pass == admin_pass) { + user = new(__FILE__,__LINE__) NetUser(name); + user->SetAddress(request.GetClientAddr()); + + if (session) + user->SetSessionID(session->GetID()); + + admin->AddUser(user); + + response.SetStatus(HttpResponse::SC_TEMPORARY_REDIRECT); + response.SetHeader("MIME-Version", "1.0"); + response.SetHeader("Content-Type", "text/html"); + response.SetHeader("Cache-Control", "no-cache"); + response.SetHeader("Expires", "-1"); + response.SetHeader("Location", "/home"); + + response.SetContent(GetHead("Login") + + "
You have successfully logged in.
" + + GetBodyClose()); + } + + else { + response.SetStatus(HttpResponse::SC_OK); + response.SetHeader("MIME-Version", "1.0"); + response.SetHeader("Content-Type", "text/html"); + response.SetHeader("Cache-Control", "no-cache"); + response.SetHeader("Expires", "-1"); + + response.SetContent(GetHead("Login") + + GetTitleBar(0, "onLoad=\"self.focus();document.loginForm.user.focus();\"") + + GetContent() + + GetBodyClose()); + } + + return true; + } + + virtual Text GetContent() { Text content = +" \n\ + \n\ + \n\ + \n\ + \n\ +
  

\n\ + Welcome to the Starshatter Server!

\n\ + Login to access the server "; + + NetServerConfig* config = NetServerConfig::GetInstance(); + if (config) + content += config->Name(); + else + content += "server"; + + content += "
\n\ +
\n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ +
Username:
Password:
 
\n\ +
\n\ +
 
\n"; + + return content; + } +}; + +// +-------------------------------------------------------------------+ +// +-------------------------------------------------------------------+ +// +-------------------------------------------------------------------+ + +class NetAdminServerMgr : public NetAdminServlet +{ +public: + NetAdminServerMgr() { } + virtual ~NetAdminServerMgr() { } + + virtual bool DoGet(HttpRequest& request, HttpResponse& response) { + if (CheckUser(request, response)) { + Text action = request.GetParam("action"); + action.setSensitive(false); + + bool completed = false; + + if (action == "restart") { + StarServer* svr = StarServer::GetInstance(); + + if (svr) { + svr->Shutdown(true); + completed = true; + + response.SetStatus(HttpResponse::SC_OK); + response.SetHeader("MIME-Version", "1.0"); + response.SetHeader("Content-Type", "text/html"); + response.SetHeader("Cache-Control", "no-cache"); + response.SetHeader("Expires", "-1"); + + response.SetContent(GetHead("Restart") + + GetTitleBar() + + "
The Starshatter Server will restart in three (3) seconds.
" + + GetBodyClose()); + } + } + + else if (action == "shutdown") { + StarServer* svr = StarServer::GetInstance(); + + if (svr) { + svr->Shutdown(false); + completed = true; + + response.SetStatus(HttpResponse::SC_OK); + response.SetHeader("MIME-Version", "1.0"); + response.SetHeader("Content-Type", "text/html"); + response.SetHeader("Cache-Control", "no-cache"); + response.SetHeader("Expires", "-1"); + + response.SetContent(GetHead("Restart") + + GetTitleBar() + + "
The Starshatter Server will shutdown in three (3) seconds.
" + + GetBodyClose()); + } + } + + if (!completed) { + response.SetStatus(HttpResponse::SC_TEMPORARY_REDIRECT); + response.SetHeader("MIME-Version", "1.0"); + response.SetHeader("Content-Type", "text/html"); + response.SetHeader("Cache-Control", "no-cache"); + response.SetHeader("Expires", "-1"); + response.SetHeader("Location", "/home"); + + response.SetContent(GetHead("Login") + + "
Unknown Action.
" + + GetBodyClose()); + } + } + + return true; + } +}; + +// +-------------------------------------------------------------------+ +// +-------------------------------------------------------------------+ +// +-------------------------------------------------------------------+ + +class NetAdminFile : public NetAdminServlet +{ +public: + NetAdminFile() { } + virtual ~NetAdminFile() { } + + virtual bool DoGet(HttpRequest& request, HttpResponse& response) { + if (!CheckUser(request, response)) + return true; + + Text content; + Text path = request.GetParam("path"); + Text name = request.GetParam("name"); + + if (name.length()) { + BYTE* buffer = 0; + DataLoader* loader = DataLoader::GetLoader(); + + if (loader) { + bool use_file_system = loader->IsFileSystemEnabled(); + + loader->UseFileSystem(true); + loader->SetDataPath(path); + int len = loader->LoadBuffer(name, buffer); + + if (len) { + content = Text((const char*) buffer, len); + } + + loader->ReleaseBuffer(buffer); + loader->SetDataPath(0); + loader->UseFileSystem(use_file_system); + } + } + + response.SetStatus(HttpResponse::SC_OK); + response.AddHeader("MIME-Version", "1.0"); + response.AddHeader("Cache-Control", "no-cache"); + response.AddHeader("Expires", "-1"); + response.AddHeader("Content-Type", "text/plain"); + response.SetContent(content); + + return true; + } +}; + +// +-------------------------------------------------------------------+ +// +-------------------------------------------------------------------+ +// +-------------------------------------------------------------------+ + +class NetAdminUserList : public NetAdminServlet +{ +public: + NetAdminUserList() { } + virtual ~NetAdminUserList() { } + + virtual bool DoGet(HttpRequest& request, HttpResponse& response) { + if (CheckUser(request, response)) { + response.SetStatus(HttpResponse::SC_OK); + response.SetHeader("MIME-Version", "1.0"); + response.SetHeader("Content-Type", "text/html"); + response.SetHeader("Cache-Control", "no-cache"); + response.SetHeader("Expires", "-1"); + + response.SetContent(GetHead("User List") + + GetTitleBar() + + GetContent() + + GetBodyClose()); + } + + return true; + } + + virtual Text GetContent() { + Text content = +"\n\ +
\n\ + \n\ + \n\ + \n\ + \n\ +
\n\ +  User List\n\ +
\n\n"; + + content += +" \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n"; + + NetLobbyServer* lobby = NetLobbyServer::GetInstance(); + + if (lobby) { + ListIter u_iter = lobby->GetUsers(); + while (++u_iter) { + NetUser* u = u_iter.value(); + NetAddr a = u->GetAddress(); + + char addr_dotted[32]; + char addr_hex[16]; + char user_stats[16]; + + sprintf(addr_dotted, "%d.%d.%d.%d", a.B1(), a.B2(), a.B3(), a.B4()); + sprintf(addr_hex, "%08x", a.IPAddr()); + sprintf(user_stats, "%d / %d / %d", u->Missions(), u->Kills(), u->Losses()); + + content += "\n\n\ + \n"; + } + } + + content += "
 NameAddressIs HostSquadronStatsBan
 "; + content += u->Name(); + content += ""; + content += addr_dotted; + content += ""; + content += u->IsHost() ? "*" : " "; + content += ""; + content += u->Squadron(); + content += ""; + content += user_stats; + content += ""; + content += "Name()); + content += "&addr="; + content += addr_hex; + content += "\">BAN
\n\n"; + + content += "
\n\n"; + content += GetCopyright(); + return content; + } +}; + +// +-------------------------------------------------------------------+ +// +-------------------------------------------------------------------+ +// +-------------------------------------------------------------------+ + +class NetAdminBanUser : public NetAdminServlet +{ +public: + NetAdminBanUser() { } + virtual ~NetAdminBanUser() { } + + virtual bool DoGet(HttpRequest& request, HttpResponse& response) { + if (CheckUser(request, response)) { + Text name = request.GetParam("name"); + bool completed = false; + + NetLobbyServer* lobby = NetLobbyServer::GetInstance(); + + if (lobby) { + ListIter u_iter = lobby->GetUsers(); + while (++u_iter && !completed) { + NetUser* u = u_iter.value(); + + if (u->Name() == name) { + NetLobbyServer* nls = NetLobbyServer::GetInstance(); + + if (nls) { + nls->BanUser(u); + completed = true; + } + } + } + } + + response.SetStatus(HttpResponse::SC_TEMPORARY_REDIRECT); + response.SetHeader("MIME-Version", "1.0"); + response.SetHeader("Content-Type", "text/html"); + response.SetHeader("Cache-Control", "no-cache"); + response.SetHeader("Expires", "-1"); + response.SetHeader("Location", "/users"); + + response.SetContent(GetHead("User List") + + GetTitleBar() + + "
User Banned.
" + + GetBodyClose()); + } + + return true; + } +}; + +// +-------------------------------------------------------------------+ +// +-------------------------------------------------------------------+ +// +-------------------------------------------------------------------+ + +static NetAdminServer* net_Admin_server = 0; + +NetAdminServer* +NetAdminServer::GetInstance(WORD port) +{ + if (!net_Admin_server && port > 0) + net_Admin_server = new(__FILE__,__LINE__) NetAdminServer(port); + + return net_Admin_server; +} + +NetAdminServer::NetAdminServer(WORD port) + : HttpServletExec(port) +{ + http_server_name = Text("Starshatter NetAdminServer ") + versionInfo; +} + +NetAdminServer::~NetAdminServer() +{ + if (net_Admin_server == this) + net_Admin_server = 0; +} + +// +--------------------------------------------------------------------+ + +HttpServlet* +NetAdminServer::GetServlet(HttpRequest& request) +{ + Text path = request.URI(); + path.setSensitive(false); + + if (path.indexOf("/login") == 0) + return new(__FILE__,__LINE__) NetAdminLogin; + + if (path.indexOf("/chat") == 0) + return new(__FILE__,__LINE__) NetAdminChat; + + if (path.indexOf("/server") == 0) + return new(__FILE__,__LINE__) NetAdminServerMgr; + + if (path.indexOf("/file") == 0) + return new(__FILE__,__LINE__) NetAdminFile; + + if (path.indexOf("/user") == 0) + return new(__FILE__,__LINE__) NetAdminUserList; + + if (path.indexOf("/ban") == 0) + return new(__FILE__,__LINE__) NetAdminBanUser; + + return new(__FILE__,__LINE__) NetAdminServlet; +} + +// +-------------------------------------------------------------------+ + +void +NetAdminServer::AddChat(NetUser* user, const char* msg) +{ + if (user && msg && *msg) { + NetLobbyServer* lobby = NetLobbyServer::GetInstance(); + + if (lobby) + lobby->AddChat(user, msg); + } +} + +ListIter +NetAdminServer::GetChat() +{ + NetLobbyServer* lobby = NetLobbyServer::GetInstance(); + + if (lobby) + return lobby->GetChat(); + + static List idle_chatter; + return idle_chatter; +} + +// +-------------------------------------------------------------------+ + +void +NetAdminServer::AddUser(NetUser* user) +{ + if (user && !admin_users.contains(user)) + admin_users.append(user); +} + +void +NetAdminServer::DelUser(NetUser* user) +{ + if (user) { + admin_users.remove(user); + delete user; + } +} + +int +NetAdminServer::NumUsers() +{ + return admin_users.size(); +} + + +List& +NetAdminServer::GetUsers() +{ + return admin_users; +} + +bool +NetAdminServer::HasHost() +{ + bool result = false; + + NetLobbyServer* lobby = NetLobbyServer::GetInstance(); + + if (lobby) + result = lobby->HasHost(); + + return result; +} + +NetUser* +NetAdminServer::FindUserBySession(Text id) +{ + ListIter iter = admin_users; + while (++iter) { + NetUser* u = iter.value(); + if (u->GetSessionID() == id) + return u; + } + + return 0; +} + +void +NetAdminServer::DoSyncedCheck() +{ + ListIter iter = admin_users; + while (++iter) { + NetUser* u = iter.value(); + + bool found = false; + + ListIter s_iter = sessions; + while (++s_iter && !found) { + HttpSession* s = s_iter.value(); + + if (s->GetID() == u->GetSessionID()) + found = true; + } + + if (!found) + delete iter.removeItem(); + } +} + + +// +-------------------------------------------------------------------+ +// +-------------------------------------------------------------------+ +// +-------------------------------------------------------------------+ + +NetAdminServlet::NetAdminServlet() +{ + admin = NetAdminServer::GetInstance(); + user = 0; +} + +// +-------------------------------------------------------------------+ + +bool +NetAdminServlet::DoGet(HttpRequest& request, HttpResponse& response) +{ + if (CheckUser(request, response)) { + + if (request.URI() == "/home") + response.SetStatus(HttpResponse::SC_OK); + else + response.SetStatus(HttpResponse::SC_TEMPORARY_REDIRECT); + + response.SetHeader("MIME-Version", "1.0"); + response.SetHeader("Content-Type", "text/html"); + response.SetHeader("Cache-Control", "no-cache"); + response.SetHeader("Expires", "-1"); + response.SetHeader("Location", "/home"); + + response.SetContent(GetHead() + + GetTitleBar(GetStatLine()) + + GetContent() + + GetBodyClose()); + } + + return true; +} + +// +-------------------------------------------------------------------+ + +bool +NetAdminServlet::CheckUser(HttpRequest& request, HttpResponse& response) +{ + if (!user) { + if (session) + user = admin->FindUserBySession(session->GetID()); + + if (!user) { + response.SetStatus(HttpResponse::SC_TEMPORARY_REDIRECT); + response.SetHeader("MIME-Version", "1.0"); + response.SetHeader("Content-Type", "text/plain"); + response.SetHeader("Cache-Control", "no-cache"); + response.SetHeader("Expires", "-1"); + response.SetHeader("Location", "/login"); + response.SetContent("You are not logged in."); + } + } + + return user != 0; +} + +// +-------------------------------------------------------------------+ + +Text +NetAdminServlet::GetCSS() +{ + return + +"body { font-family:arial,helvetica,sans-serif; color:black; background-color:white }\n\ +a:link { text-decoration:none; font-weight:normal; font-size:10pt; color:black }\n\ +a:visited { text-decoration:none; font-weight:normal; font-size:10pt; color:black }\n\ +a:hover { text-decoration:underline; font-weight:normal; font-size:10pt; color:black }\n\ +.std { font-size:10pt }\n\ +.tiny { font-size:8pt }\n\ +.heading { font-size:14pt; font-weight:bold; background-color:#99BBEE }\n\ +.subhead { font-size:11pt; font-weight:bold }\n\ +.status { font-size:10pt; color:white }\n\ +.content { padding-right: 4pt; padding-left: 4pt; padding-bottom: 4pt; padding-top: 4pt; margin: 4pt; }\n\ +.copy { font-size:8pt; }\n\ +.top-bar { color: white; background-color: #336699 }\n\ +.top-line { color: yellow; background-color: black }\n\ +.topbarbig { line-height:24px; color:white; font-size:18px; font-weight:bold; }\n\ +.topbarsmall { line-height:18px; color:white; font-size:14px; }\n"; +} + +Text +NetAdminServlet::GetHead(const char* title) +{ + Text head = "\n\nStarshatter Server"; + + if (title && *title) { + head += " - "; + head += title; + } + + head += "\n\n\n"; + + return head; +} + +Text +NetAdminServlet::GetBody() +{ + return GetTitleBar(GetStatLine()) + + GetContent() + + GetBodyClose(); +} + +Text +NetAdminServlet::GetTitleBar(const char* statline, const char* onload) +{ + Text bar = "\n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ +
 \n\ + Administration Console
\n"; + + if (statline) { + bar += ""; + } + + bar += "Starshatter Server "; + bar += versionInfo; + bar += ""; + + if (statline) { + bar += ""; + } + + bar += "\n\ +
"; + + if (statline && *statline) + bar += statline; + else + bar += " "; + + bar += "
\n\n"; + + return bar; +} + +Text +NetAdminServlet::GetStatLine() +{ + NetServerConfig* config = NetServerConfig::GetInstance(); + + Text line = +" \n\ + \n\ + \n\ + \n\ + \n\ + \n\ +
\n\ +   Connected to "; + + char buffer[256]; + sprintf(buffer, "%s:%d", config->Name().data(), config->GetAdminPort()); + line += buffer; + + line += "\n\ + \n\ + Server Mode: "; + + NetLobbyServer* lobby = NetLobbyServer::GetInstance(); + if (lobby) { + switch (lobby->GetStatus()) { + default: + case NetServerInfo::OFFLINE: line += "Offline"; break; + case NetServerInfo::LOBBY: line += "Lobby"; break; + case NetServerInfo::BRIEFING: line += "Briefing"; break; + case NetServerInfo::ACTIVE: line += "Active"; break; + case NetServerInfo::DEBRIEFING: line += "Debriefing"; break; + case NetServerInfo::PERSISTENT: line += "PERSISTENT"; break; + } + } + else { + line += "Unknown"; + } + + line += "\n\ + \n\ + "; + + line += FormatTimeString(); + + line += "  \n\ +
\n"; + + return line; +} + +Text +NetAdminServlet::GetContent() +{ + Text content = +"\n\ +
\n\ + \n\ + \n\ + \n\ + \n\ +
\n\ +  Game Admin Functions\n\ +
\n\n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ +
 \n\ + Lobby Chat\n\ + \n\ + Mission List\n\ +
 \n\ + View Error Log\n\ + \n\ + Player List\n\ +
 \n\ + View Server Log\n\ + \n\ + Ban List\n\ +
 
\n\n"; + + content += +" \n\ + \n\ + \n\ + \n\ +
\n\ +  Server Admin Functions\n\ +
\n\n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ +
 \n\ + Restart Server\n\ + \n\ + Shutdown Server\n\ +
\n\n"; + + content += "
\n\n"; + content += GetCopyright(); + return content; +} + +Text +NetAdminServlet::GetBodyClose() +{ + return "\n\n\n\n"; +} + +Text +NetAdminServlet::GetCopyright() +{ + return "
    Copyright © 1997-2004 Destroyer Studios. All rights reserved.
"; +} diff --git a/Stars45/NetAdminServer.h b/Stars45/NetAdminServer.h new file mode 100644 index 0000000..a285adf --- /dev/null +++ b/Stars45/NetAdminServer.h @@ -0,0 +1,91 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetAdminServer.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + HTTP Servlet Engine for Multiplayer Admin +*/ + + +#ifndef NetAdminServer_h +#define NetAdminServer_h + +#include "HttpServletExec.h" +#include "HttpServlet.h" + +// +-------------------------------------------------------------------+ + +class Mission; +class MissionElement; +class NetChatEntry; +class NetUser; + +// +-------------------------------------------------------------------+ + +class NetAdminServer : public HttpServletExec +{ +public: + virtual ~NetAdminServer(); + + int operator == (const NetAdminServer& s) const { return this == &s; } + + virtual HttpServlet* GetServlet(HttpRequest& request); + + virtual void AddUser(NetUser* user); + virtual void DelUser(NetUser* user); + virtual int NumUsers(); + virtual bool HasHost(); + virtual List& GetUsers(); + + virtual NetUser* FindUserBySession(Text id); + + virtual void AddChat(NetUser* user, const char* msg); + ListIter GetChat(); + DWORD GetStartTime() const { return start_time; } + + virtual void GameOn() { } + virtual void GameOff() { } + + // singleton locator: + static NetAdminServer* GetInstance(WORD port=0); + +protected: + NetAdminServer(WORD port); + virtual void DoSyncedCheck(); + + DWORD start_time; + List admin_users; +}; + +// +-------------------------------------------------------------------+ + +class NetAdminServlet : public HttpServlet +{ +public: + NetAdminServlet(); + virtual ~NetAdminServlet() { } + + virtual bool DoGet(HttpRequest& request, HttpResponse& response); + virtual bool CheckUser(HttpRequest& request, HttpResponse& response); + + virtual Text GetCSS(); + virtual Text GetHead(const char* title=0); + virtual Text GetBody(); + virtual Text GetTitleBar(const char* statline=0, const char* onload=0); + virtual Text GetStatLine(); + virtual Text GetCopyright(); + virtual Text GetContent(); + virtual Text GetBodyClose(); + +protected: + NetAdminServer* admin; + NetUser* user; +}; + +#endif NetAdminServer_h \ No newline at end of file diff --git a/Stars45/NetAuth.cpp b/Stars45/NetAuth.cpp new file mode 100644 index 0000000..2e06f0b --- /dev/null +++ b/Stars45/NetAuth.cpp @@ -0,0 +1,216 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetAuth.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + This class represents a user connecting to the multiplayer lobby +*/ + + +#include "MemDebug.h" +#include "NetAuth.h" +#include "NetLobby.h" +#include "NetUser.h" +#include "ModConfig.h" +#include "ModInfo.h" +#include "Random.h" +#include "sha1.h" + +static int auth_level = NetAuth::NET_AUTH_MINIMAL; + +// +-------------------------------------------------------------------+ + +int +NetAuth::AuthLevel() +{ + return auth_level; +} + + +void +NetAuth::SetAuthLevel(int n) +{ + if (n >= NET_AUTH_MINIMAL && n <= NET_AUTH_SECURE) + auth_level = n; +} + +// +-------------------------------------------------------------------+ + +Text +NetAuth::CreateAuthRequest(NetUser* u) +{ + Text request; + + if (u) { + u->SetAuthLevel(auth_level); + + if (auth_level == NET_AUTH_MINIMAL) { + u->SetAuthState(NET_AUTH_OK); + u->SetSalt("Very Low Sodium"); + } + + else if (auth_level == NET_AUTH_STANDARD) { + u->SetAuthState(NET_AUTH_INITIAL); + u->SetSalt("Very Low Sodium"); + + request = "level 1"; + } + + else { + char salt[33]; + + for (int i = 0; i < 32; i++) + salt[i] = (char) ('0' + (int) Random(0, 9.4)); + + salt[32] = 0; + u->SetSalt(salt); + u->SetAuthState(NET_AUTH_INITIAL); + + request = "level 2 salt "; + request += salt; + } + } + + return request; +} + +// +-------------------------------------------------------------------+ + +static Text Digest(const char* salt, const char* file) +{ + int length = 0; + int offset = 0; + char block[4096]; + char digest[64]; + + ZeroMemory(digest, sizeof(digest)); + + if (file) { + FILE* f = fopen(file, "rb"); + + if (f) { + SHA1 sha1; + + if (salt) { + sha1.Input(salt, strlen(salt)); + } + + fseek(f, 0, SEEK_END); + length = ftell(f); + fseek(f, 0, SEEK_SET); + + while (offset < length) { + int n = fread(block, sizeof(char), 4096, f); + sha1.Input(block, n); + offset += n; + } + + fclose(f); + + unsigned result[5]; + if (sha1.Result(result)) { + sprintf(digest, "SHA1_%08X_%08X_%08X_%08X_%08X", + result[0], result[1], result[2], result[3], result[4]); + } + } + } + + return digest; +} + +// +-------------------------------------------------------------------+ + +Text +NetAuth::CreateAuthResponse(int level, const char* salt) +{ + Text response; + ModConfig* config = ModConfig::GetInstance(); + + if (level == NET_AUTH_SECURE) { + +#ifdef STARSHATTER_DEMO_RELEASE + + response += "exe "; + response += Digest(salt, "StarDemo.exe"); // XXX should look up name of this exe + response += " "; + +#else + + response += "exe "; + response += Digest(salt, "stars.exe"); // XXX should look up name of this exe + response += " "; + +#endif + + response += "dat "; + response += Digest(salt, "shatter.dat"); + response += " "; + + response += "etc "; + response += Digest(salt, "start.dat"); + response += " "; + } + + if (level >= NET_AUTH_STANDARD) { + List& mods = config->GetModInfoList(); + ListIter mod_iter = mods; + + char buffer[32]; + sprintf(buffer, "num %d ", mods.size()); + response += buffer; + + while (++mod_iter) { + ModInfo* info = mod_iter.value(); + + response += "mod \""; + response += info->Name(); + response += "\" ver \""; + response += info->Version(); + response += "\" "; + + if (level == NET_AUTH_SECURE) { + response += "sha "; + response += Digest(salt, info->Filename()); + response += " "; + } + } + } + + return response; +} + +// +-------------------------------------------------------------------+ + +bool +NetAuth::AuthUser(NetUser* u, Text response) +{ + bool authentic = false; + + if (auth_level == NET_AUTH_MINIMAL) { // (this case should not occur) + if (u) { + u->SetAuthLevel(auth_level); + u->SetAuthState(NET_AUTH_OK); + } + + authentic = (u != 0); + } + + else if (u) { + Text expected_response = CreateAuthResponse(auth_level, u->Salt()); + if (expected_response == response) + authentic = true; + + u->SetAuthState(authentic ? NET_AUTH_OK : NET_AUTH_FAILED); + } + + return authentic; +} + +// +-------------------------------------------------------------------+ + diff --git a/Stars45/NetAuth.h b/Stars45/NetAuth.h new file mode 100644 index 0000000..8966170 --- /dev/null +++ b/Stars45/NetAuth.h @@ -0,0 +1,51 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetAuth.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + This class authenticates a user connecting to the multiplayer lobby +*/ + + +#ifndef NetAuth_h +#define NetAuth_h + +#include "Types.h" +#include "NetAddr.h" +#include "NetLobby.h" +#include "Text.h" + +// +-------------------------------------------------------------------+ + +class NetAuth +{ +public: + enum AUTH_STATE { + NET_AUTH_INITIAL = 0, + NET_AUTH_FAILED = 1, + NET_AUTH_OK = 2 + }; + + enum AUTH_LEVEL { + NET_AUTH_MINIMAL = 0, + NET_AUTH_STANDARD = 1, + NET_AUTH_SECURE = 2 + }; + + static int AuthLevel(); + static void SetAuthLevel(int n); + + static Text CreateAuthRequest(NetUser* u); + static Text CreateAuthResponse(int level, const char* salt); + static bool AuthUser(NetUser* u, Text response); +}; + +// +-------------------------------------------------------------------+ + +#endif NetAuth_h \ No newline at end of file diff --git a/Stars45/NetBrokerClient.cpp b/Stars45/NetBrokerClient.cpp new file mode 100644 index 0000000..5f5369d --- /dev/null +++ b/Stars45/NetBrokerClient.cpp @@ -0,0 +1,239 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetBrokerClient.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Client for Starshatter.com GameNet Broker +*/ + + +#include "MemDebug.h" +#include "NetBrokerClient.h" + +#include "HttpClient.h" +#include "HttpServlet.h" +#include "NetLayer.h" + +#include "Game.h" + +extern const char* versionInfo; +const char* HOSTNAME = "www.starshatter.com"; +const WORD HTTPPORT = 80; + +bool NetBrokerClient::broker_available = true; +bool NetBrokerClient::broker_found = false; + +// +--------------------------------------------------------------------+ + +bool +NetBrokerClient::GameOn(const char* name, + const char* type, + const char* addr, + WORD port, + const char* password) +{ + bool result = false; + + if (!broker_available) + return result; + + Text msg; + char buffer[8]; + NetAddr broker(HOSTNAME, HTTPPORT); + + if (broker.IPAddr() == 0) + return result; + + sprintf(buffer, "%d", port); + + msg = "GET http://"; + msg += HOSTNAME; + msg += "/GameNet/GameOn.php?name="; + msg += HttpRequest::EncodeParam(name); + msg += "&addr="; + msg += HttpRequest::EncodeParam(addr); + msg += "&game="; + msg += HttpRequest::EncodeParam(type); + msg += "&vers="; + msg += HttpRequest::EncodeParam(versionInfo); + msg += "&port="; + msg += buffer; + msg += "&pass="; + msg += HttpRequest::EncodeParam(password); + msg += " HTTP/1.1\n\n"; + + HttpClient client(broker); + HttpRequest request(msg); + request.SetHeader("Host", HOSTNAME); + + HttpResponse* response = client.DoRequest(request); + + if (response && response->Status() == 200) { + broker_found = true; + result = true; + } + else { + ::Print("NetBrokerClient unable to contact GameNet!\n"); + + if (response) { + ::Print(" Response Status: %d\n", response->Status()); + ::Print(" Response Content: %s\n", response->Content()); + } + else { + ::Print(" No response.\n"); + } + + if (!broker_found) + broker_available = false; + } + + delete response; + return result; +} + +bool +NetBrokerClient::GameList(const char* type, List& server_list) +{ + bool result = false; + + if (!broker_available) + return result; + + Text msg; + NetAddr broker(HOSTNAME, HTTPPORT); + + if (broker.IPAddr() == 0) + return result; + + msg = "GET http://"; + msg += HOSTNAME; + msg += "/GameNet/GameList.php?game="; + msg += HttpRequest::EncodeParam(type); + msg += "&vers="; + msg += HttpRequest::EncodeParam(versionInfo); + msg += " HTTP/1.1\n\n"; + + HttpClient client(broker); + HttpRequest request(msg); + request.SetHeader("Host", HOSTNAME); + + HttpResponse* response = client.DoRequest(request); + + if (response && response->Status() == 200) { + result = true; + + Text name; + Text type; + Text addr; + int port; + Text pass; + Text vers; + char buff[1024]; + + const char* p = response->Content(); + + // skip size + while (*p && strncmp(p, "name:", 5)) + p++; + + while (*p) { + if (!strncmp(p, "name:", 5)) { + p += 5; + ZeroMemory(buff, sizeof(buff)); + char* d = buff; + while (*p && *p != '\n') *d++ = *p++; + if (*p) p++; + + name = buff; + } + else if (!strncmp(p, "addr:", 5)) { + p += 5; + ZeroMemory(buff, sizeof(buff)); + char* d = buff; + while (*p && *p != '\n') *d++ = *p++; + if (*p) p++; + + addr = buff; + } + else if (!strncmp(p, "port:", 5)) { + p += 5; + ZeroMemory(buff, sizeof(buff)); + char* d = buff; + while (*p && *p != '\n') *d++ = *p++; + if (*p) p++; + + sscanf(buff, "%d", &port); + } + else if (!strncmp(p, "pass:", 5)) { + p += 5; + ZeroMemory(buff, sizeof(buff)); + char* d = buff; + while (*p && *p != '\n') *d++ = *p++; + if (*p) p++; + + pass = buff; + } + else if (!strncmp(p, "game:", 5)) { + p += 5; + ZeroMemory(buff, sizeof(buff)); + char* d = buff; + while (*p && *p != '\n') *d++ = *p++; + if (*p) p++; + + type = buff; + type.setSensitive(false); + + if (type.contains("lan")) + type = "LAN"; + else + type = "Public"; + } + else if (!strncmp(p, "vers:", 5)) { + p += 5; + ZeroMemory(buff, sizeof(buff)); + char* d = buff; + while (*p && *p != '\n') *d++ = *p++; + if (*p) p++; + + vers = buff; + } + else if (!strncmp(p, "time:", 5)) { + while (*p && *p != '\n') p++; + if (*p) p++; + } + + else if (!strncmp(p, "###", 3)) { + NetServerInfo* server = new(__FILE__,__LINE__) NetServerInfo; + server->name = name; + server->hostname = addr; + server->type = type; + server->addr = NetAddr(addr, port); + server->port = port; + server->password = pass; + server->version = vers; + server->save = false; + + server_list.append(server); + + while (*p && strncmp(p, "name:", 5)) + p++; + } + else { + while (*p && *p != '\n') p++; + if (*p) p++; + } + } + } + else if (!broker_found) { + broker_available = false; + } + + delete response; + return result; +} diff --git a/Stars45/NetBrokerClient.h b/Stars45/NetBrokerClient.h new file mode 100644 index 0000000..9dba076 --- /dev/null +++ b/Stars45/NetBrokerClient.h @@ -0,0 +1,43 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetBrokerClient.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Client for Starshatter.com GameNet Broker +*/ + + +#ifndef NetBrokerClient_h +#define NetBrokerClient_h + +#include "HttpClient.h" +#include "NetLobby.h" + +// +-------------------------------------------------------------------+ + +class NetBrokerClient +{ +public: + static void Enable() { broker_available = true; } + static void Disable() { broker_available = false; } + + static bool GameOn(const char* name, + const char* type, + const char* addr, + WORD port, + const char* password); + static bool GameList(const char* type, List& server_list); + +protected: + static bool broker_available; + static bool broker_found; +}; + + +#endif NetBrokerClient_h \ No newline at end of file diff --git a/Stars45/NetChat.cpp b/Stars45/NetChat.cpp new file mode 100644 index 0000000..572236b --- /dev/null +++ b/Stars45/NetChat.cpp @@ -0,0 +1,53 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetChat.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Single chat message and sender +*/ + + +#include "MemDebug.h" +#include "NetChat.h" +#include "NetLayer.h" + +// +-------------------------------------------------------------------+ + +static int chat_id_key = 1000; + +// +-------------------------------------------------------------------+ + +NetChatEntry::NetChatEntry(const NetUser* u, const char* s) + : id(chat_id_key++), msg(s) +{ + if (u) { + user = u->Name(); + color = u->GetColor(); + } + else { + user = "unknown"; + color = Color::Gray; + } + + time = NetLayer::GetUTC(); +} + +NetChatEntry::NetChatEntry(int msg_id, const char* u, const char* s) + : id(msg_id), user(u), msg(s) +{ + color = Color::Gray; + time = NetLayer::GetUTC(); + + if (id >= chat_id_key) + chat_id_key = id + 1; +} + +NetChatEntry::~NetChatEntry() +{ } + diff --git a/Stars45/NetChat.h b/Stars45/NetChat.h new file mode 100644 index 0000000..6a0936c --- /dev/null +++ b/Stars45/NetChat.h @@ -0,0 +1,52 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetChat.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Single chat message and sender +*/ + + +#ifndef NetChat_h +#define NetChat_h + +#include "Types.h" +#include "NetUser.h" + +// +-------------------------------------------------------------------+ + +class NetChatEntry +{ +public: + static const char* TYPENAME() { return "NetChatEntry"; } + + NetChatEntry(const NetUser* user, const char* msg); + NetChatEntry(int id, const char* user, const char* msg); + ~NetChatEntry(); + + int operator == (const NetChatEntry& c) const { return id == c.id; } + int operator < (const NetChatEntry& c) const { return id < c.id; } + + int GetID() const { return id; } + const Text& GetUser() const { return user; } + Color GetColor() const { return color; } + const Text& GetMessage() const { return msg; } + DWORD GetTime() const { return time; } + +private: + int id; + Text user; + Text msg; + Color color; + DWORD time; +}; + +// +-------------------------------------------------------------------+ + +#endif NetChat_h \ No newline at end of file diff --git a/Stars45/NetClientConfig.cpp b/Stars45/NetClientConfig.cpp new file mode 100644 index 0000000..f4986c8 --- /dev/null +++ b/Stars45/NetClientConfig.cpp @@ -0,0 +1,332 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars + FILE: NetClientConfig.cpp + AUTHOR: John DiCamillo + +*/ + +#include "MemDebug.h" + +#include "NetClientConfig.h" +#include "NetLobbyClient.h" +#include "NetBrokerClient.h" + +#include "NetLayer.h" +#include "NetAddr.h" +#include "NetHost.h" + +#include "Token.h" +#include "Game.h" +#include "DataLoader.h" +#include "ParseUtil.h" +#include "Resource.h" + +// +--------------------------------------------------------------------+ + +NetClientConfig* NetClientConfig::instance = 0; + +// +--------------------------------------------------------------------+ + +NetClientConfig::NetClientConfig() + : server_index(-1), host_request(false), conn(0) +{ + instance = this; + Load(); +} + +NetClientConfig::~NetClientConfig() +{ + Logout(); + + instance = 0; + servers.destroy(); +} + +// +--------------------------------------------------------------------+ + +void +NetClientConfig::Initialize() +{ + if (!instance) + instance = new(__FILE__,__LINE__) NetClientConfig(); +} + +void +NetClientConfig::Close() +{ + delete instance; + instance = 0; +} + +// +--------------------------------------------------------------------+ + +void +NetClientConfig::AddServer(const char* name, const char* addr, WORD port, const char* pass, bool save) +{ + if (!addr || !*addr || port < 1024 || port > 48000) + return; + + char buffer[1024]; + if (name && *name) + strcpy(buffer, name); + else + sprintf(buffer, "%s:%d", addr, port); + + NetServerInfo* server = new(__FILE__,__LINE__) NetServerInfo; + server->name = buffer; + server->hostname = addr; + server->addr = NetAddr(addr, port); + server->port = port; + server->password = pass; + server->save = save; + + if (server->addr.IPAddr() == 0) { + Print("NetClientConfig::AddServer(%s, %s, %d) failed to resolve IP Addr\n", + name, addr, port); + } + + servers.append(server); +} + +void +NetClientConfig::DelServer(int index) +{ + if (index >= 0 && index < servers.size()) { + delete servers.removeIndex(index); + } +} + +// +--------------------------------------------------------------------+ + +NetServerInfo* +NetClientConfig::GetServerInfo(int n) +{ + if (n >= 0 && n < servers.size()) + return servers.at(n); + + return 0; +} + +// +--------------------------------------------------------------------+ + +NetServerInfo* +NetClientConfig::GetSelectedServer() +{ + if (server_index >= 0 && server_index < servers.size()) + return servers.at(server_index); + + return 0; +} + +// +--------------------------------------------------------------------+ + +void +NetClientConfig::Download() +{ + Load(); + + List list; + if (NetBrokerClient::GameList("Starshatter", list)) { + servers.append(list); + } +} + +// +--------------------------------------------------------------------+ + +void +NetClientConfig::Load() +{ + server_index = -1; + + // read the config file: + BYTE* block = 0; + int blocklen = 0; + + char filename[64]; + strcpy(filename, "client.cfg"); + + FILE* f = ::fopen(filename, "rb"); + + if (f) { + ::fseek(f, 0, SEEK_END); + blocklen = ftell(f); + ::fseek(f, 0, SEEK_SET); + + block = new(__FILE__,__LINE__) BYTE[blocklen+1]; + block[blocklen] = 0; + + ::fread(block, blocklen, 1, f); + ::fclose(f); + } + + if (blocklen == 0) + return; + + servers.destroy(); + + Parser parser(new(__FILE__,__LINE__) BlockReader((const char*) block, blocklen)); + Term* term = parser.ParseTerm(); + + if (!term) { + Print("ERROR: could not parse '%s'.\n", filename); + return; + } + else { + TermText* file_type = term->isText(); + if (!file_type || file_type->value() != "CLIENT_CONFIG") { + Print("WARNING: invalid '%s' file. Using defaults\n", filename); + return; + } + } + + do { + delete term; + + term = parser.ParseTerm(); + + if (term) { + TermDef* def = term->isDef(); + if (def) { + if (def->name()->value() == "server") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: server struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + + Text name; + Text addr; + Text pass; + int port; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + if (pdef->name()->value() == "name") + GetDefText(name, pdef, filename); + else if (pdef->name()->value() == "addr") + GetDefText(addr, pdef, filename); + else if (pdef->name()->value() == "pass") + GetDefText(pass, pdef, filename); + else if (pdef->name()->value() == "port") + GetDefNumber(port, pdef, filename); + } + } + + AddServer(name, addr, (WORD) port, pass, true); + } + } + else { + Print("WARNING: unknown label '%s' in '%s'\n", + def->name()->value().data(), filename); + } + } + } + } + while (term); + + delete [] block; +} + +// +--------------------------------------------------------------------+ + +void +NetClientConfig::Save() +{ + FILE* f = fopen("client.cfg", "w"); + if (f) { + fprintf(f, "CLIENT_CONFIG\n\n"); + + ListIter iter = servers; + while (++iter) { + NetServerInfo* server = iter.value(); + + if (server->save) { + int port = (int) server->port; + fprintf(f, "server: {\n"); + fprintf(f, " name: \"%s\",\n", (const char*) server->name); + fprintf(f, " addr: \"%s\",\n", (const char*) server->hostname); + fprintf(f, " port: %d,\n", port); + + if (server->password.length()) + fprintf(f, " pass: \"%s\",\n", (const char*) server->password); + + fprintf(f, "}\n\n"); + } + } + + fclose(f); + } +} + +// +--------------------------------------------------------------------+ + +void +NetClientConfig::CreateConnection() +{ + NetServerInfo* s = GetSelectedServer(); + + if (s) { + NetAddr addr = s->addr; + + if (conn) { + if (conn->GetServerAddr().IPAddr() != addr.IPAddr() || + conn->GetServerAddr().Port() != addr.Port()) { + conn->Logout(); + DropConnection(); + } + } + + if (addr.IPAddr() && addr.Port() && !conn) { + conn = new(__FILE__,__LINE__) NetLobbyClient; // (addr); + } + } + + else if (conn) { + conn->Logout(); + DropConnection(); + } +} + +NetLobbyClient* +NetClientConfig::GetConnection() +{ + return conn; +} + +bool +NetClientConfig::Login() +{ + bool result = false; + + if (!conn) + CreateConnection(); + + if (conn) + result = conn->Login(host_request); + + return result; +} + +bool +NetClientConfig::Logout() +{ + bool result = false; + + if (conn) { + result = conn->Logout(); + DropConnection(); + } + + return result; +} + +void +NetClientConfig::DropConnection() +{ + delete conn; + conn = 0; +} diff --git a/Stars45/NetClientConfig.h b/Stars45/NetClientConfig.h new file mode 100644 index 0000000..ba1e340 --- /dev/null +++ b/Stars45/NetClientConfig.h @@ -0,0 +1,73 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars + FILE: NetClientConfig.h + AUTHOR: John DiCamillo + +*/ + +#ifndef NetClientConfig_h +#define NetClientConfig_h + +#include "Types.h" +#include "Game.h" +#include "Text.h" +#include "List.h" + +#include "NetAddr.h" + +// +--------------------------------------------------------------------+ + +class NetLobbyClient; +class NetServerInfo; + +// +--------------------------------------------------------------------+ + +class NetClientConfig +{ +public: + NetClientConfig(); + ~NetClientConfig(); + + void AddServer(const char* name, + const char* addr, + WORD port, + const char* password, + bool save=false); + void DelServer(int index); + + List& GetServerList() { return servers; } + NetServerInfo* GetServerInfo(int n); + void Download(); + void Load(); + void Save(); + + void SetServerIndex(int n) { server_index = n; } + int GetServerIndex() const { return server_index; } + void SetHostRequest(bool n) { host_request = n; } + bool GetHostRequest() const { return host_request; } + NetServerInfo* GetSelectedServer(); + + void CreateConnection(); + NetLobbyClient* GetConnection(); + bool Login(); + bool Logout(); + void DropConnection(); + + static void Initialize(); + static void Close(); + static NetClientConfig* GetInstance() { return instance; } + +private: + List servers; + int server_index; + bool host_request; + + NetLobbyClient* conn; + + static NetClientConfig* instance; +}; + +#endif NetClientConfig_h diff --git a/Stars45/NetClientDlg.cpp b/Stars45/NetClientDlg.cpp new file mode 100644 index 0000000..401e412 --- /dev/null +++ b/Stars45/NetClientDlg.cpp @@ -0,0 +1,431 @@ +/* Project Starshatter 5.0 + Destroyer Studios LLC + Copyright © 1997-2007. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetClientDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Main Menu Dialog Active Window class +*/ + +#include "MemDebug.h" +#include "NetClientDlg.h" +#include "NetClientConfig.h" +#include "NetLobbyClient.h" +#include "MenuScreen.h" +#include "Starshatter.h" +#include "Ship.h" +#include "HUDView.h" + +#include "NetAddr.h" + +#include "DataLoader.h" +#include "Button.h" +#include "ListBox.h" +#include "Slider.h" +#include "Video.h" +#include "Keyboard.h" +#include "MachineInfo.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +DEF_MAP_CLIENT(NetClientDlg, OnSelect); +DEF_MAP_CLIENT(NetClientDlg, OnAdd); +DEF_MAP_CLIENT(NetClientDlg, OnDel); +DEF_MAP_CLIENT(NetClientDlg, OnServer); +DEF_MAP_CLIENT(NetClientDlg, OnHost); +DEF_MAP_CLIENT(NetClientDlg, OnJoin); +DEF_MAP_CLIENT(NetClientDlg, OnCancel); + +// +--------------------------------------------------------------------+ + +NetClientDlg::NetClientDlg(Screen* s, FormDef& def, MenuScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), manager(mgr), + server_index(-1), ping_index(0), hnet(0) +{ + config = NetClientConfig::GetInstance(); + Init(def); +} + +NetClientDlg::~NetClientDlg() +{ + StopNetProc(); +} + +void +NetClientDlg::StopNetProc() +{ + if (hnet != 0) { + WaitForSingleObject(hnet, 500); + CloseHandle(hnet); + hnet = 0; + } +} + +// +--------------------------------------------------------------------+ + +void +NetClientDlg::RegisterControls() +{ + btn_add = (Button*) FindControl(101); + btn_del = (Button*) FindControl(102); + lst_servers = (ListBox*) FindControl(200); + lbl_info = FindControl(210); + btn_server = (Button*) FindControl(301); + btn_host = (Button*) FindControl(302); + btn_join = (Button*) FindControl(303); + btn_cancel = (Button*) FindControl(2); + + REGISTER_CLIENT(EID_CLICK, btn_add, NetClientDlg, OnAdd); + REGISTER_CLIENT(EID_CLICK, btn_del, NetClientDlg, OnDel); + REGISTER_CLIENT(EID_SELECT, lst_servers, NetClientDlg, OnSelect); + REGISTER_CLIENT(EID_CLICK, btn_server, NetClientDlg, OnServer); + REGISTER_CLIENT(EID_CLICK, btn_host, NetClientDlg, OnHost); + REGISTER_CLIENT(EID_CLICK, btn_join, NetClientDlg, OnJoin); + REGISTER_CLIENT(EID_CLICK, btn_cancel, NetClientDlg, OnCancel); +} + +// +--------------------------------------------------------------------+ + +void +NetClientDlg::Show() +{ + int selected_index = -1; + + if (!IsShown()) { + FormWindow::Show(); + + // try to retrieve list of active servers from web broker + if (config && lst_servers) { + selected_index = config->GetServerIndex(); + lst_servers->ClearItems(); + config->Download(); + } + } + + if (config && lst_servers) { + if (lst_servers->NumItems() != config->GetServerList().size()) + ShowServers(); + else + UpdateServers(); + + if (selected_index >= 0 && selected_index < lst_servers->NumItems()) { + config->SetServerIndex(selected_index); + lst_servers->SetSelected(selected_index); + OnSelect(0); + } + } +} + +// +--------------------------------------------------------------------+ + +void +NetClientDlg::ExecFrame() +{ + if (!config || !lst_servers) return; + + if (lst_servers->NumItems() != config->GetServerList().size()) + ShowServers(); + else + UpdateServers(); + + NetServerInfo* info = config->GetServerInfo(server_index); + + bool del_enabled = info != 0; + bool join_enabled = info != 0 && info->status > NetServerInfo::OFFLINE; + bool host_enabled = join_enabled && info->hosted == 0; + + if (btn_host) + btn_host->SetEnabled(host_enabled); + + if (btn_join) + btn_join->SetEnabled(join_enabled); + + if (btn_del) + btn_del->SetEnabled(del_enabled); +} + +// +--------------------------------------------------------------------+ + +void +NetClientDlg::ShowServers() +{ + if (!config || !lst_servers) return; + + lst_servers->ClearItems(); + lst_servers->SetSelectedStyle(ListBox::LIST_ITEM_STYLE_FILLED_BOX); + lst_servers->SetLeading(2); + + int i = 0; + ListIter iter = config->GetServerList(); + while (++iter) { + NetServerInfo* info = iter.value(); + + lst_servers->AddItemWithData(info->name, (DWORD) i); + lst_servers->SetItemText(i, 1, info->type); + lst_servers->SetItemText(i, 2, info->password); + lst_servers->SetItemText(i, 3, Game::GetText("NetClientDlg.offline")); + lst_servers->SetItemText(i, 4, "0"); + lst_servers->SetItemText(i, 5, Game::GetText("NetClientDlg.not-avail")); + + i++; + } + + ping_index = 0; + server_index = -1; + + if (btn_host) btn_host->SetEnabled(false); + if (btn_join) btn_join->SetEnabled(false); + if (btn_del) btn_del->SetEnabled(false); +} + +void +NetClientDlg::UpdateServers() +{ + if (!config || !lst_servers || lst_servers->NumItems() < 1) return; + + if (!PingComplete()) + return; + + PingServer(ping_index); + + for (int i = 0; i < lst_servers->NumItems(); i++) { + int n = lst_servers->GetItemData(i); + + NetServerInfo* info = config->GetServerList().at(n); + lst_servers->SetItemText(i, 0, info->name); + + Text status = Game::GetText("NetClientDlg.offline"); + + if (info->ping_time > 0 && info->ping_time < 10000) { + char buffer[32]; + sprintf(buffer, "%d ms", info->ping_time); + lst_servers->SetItemText(i, 5, buffer); + + switch (info->status) { + default: + case NetServerInfo::OFFLINE: status = Game::GetText("NetClientDlg.offline"); break; + case NetServerInfo::LOBBY: status = Game::GetText("NetClientDlg.lobby"); break; + case NetServerInfo::BRIEFING: status = Game::GetText("NetClientDlg.briefing"); break; + case NetServerInfo::ACTIVE: status = Game::GetText("NetClientDlg.active"); break; + case NetServerInfo::DEBRIEFING: status = Game::GetText("NetClientDlg.debriefing"); break; + case NetServerInfo::PERSISTENT: status = Game::GetText("NetClientDlg.persistent"); break; + } + } + else { + lst_servers->SetItemText(i, 5, Game::GetText("NetClientDlg.not-avail")); + } + + lst_servers->SetItemText(i, 3, status); + + char num_users[16]; + sprintf(num_users, "%d", info->nplayers); + lst_servers->SetItemText(i, 4, num_users); + } +} + +// +--------------------------------------------------------------------+ + +DWORD WINAPI NetClientPingProc(LPVOID link); + +void +NetClientDlg::PingServer(int n) +{ + if (hnet == 0) { + NetClientConfig* config = NetClientConfig::GetInstance(); + if (!config) return; + + NetServerInfo* info = config->GetServerInfo(n); + if (!info) return; + + // copy info from server list + ping_info = *info; + + DWORD thread_id = 0; + hnet = CreateThread(0, 4096, NetClientPingProc, (LPVOID) &ping_info, 0, &thread_id); + + if (hnet == 0) { + static int report = 10; + if (report > 0) { + ::Print("WARNING: NetClientDlg() failed to create PING thread for server '%s' (err=%08x)\n", info->name.data(), GetLastError()); + report--; + + if (report == 0) + ::Print(" Further warnings of this type will be supressed.\n"); + } + } + } +} + +bool +NetClientDlg::PingComplete() +{ + if (hnet != 0) { + DWORD result = 0; + GetExitCodeThread(hnet, &result); + + if (result != STILL_ACTIVE) { + CloseHandle(hnet); + hnet = 0; + + NetClientConfig* config = NetClientConfig::GetInstance(); + if (config) { + NetServerInfo* info = config->GetServerInfo(ping_index); + if (info) { + // copy result back into server list + info->machine_info = ping_info.machine_info; + info->gameport = ping_info.gameport; + info->status = ping_info.status; + info->nplayers = ping_info.nplayers; + info->hosted = ping_info.hosted; + info->ping_time = ping_info.ping_time; + } + } + + if (lst_servers && ping_index >= lst_servers->NumItems()-1) + ping_index = 0; + else + ping_index++; + } + } + + return !hnet; +} + +// +--------------------------------------------------------------------+ + +DWORD WINAPI NetClientPingProc(LPVOID link) +{ + NetServerInfo* info = (NetServerInfo*) link; + if (!info) { + Print("NetClientPingProc() no link\n"); + Sleep(200); + return 1; + } + + NetAddr addr = info->addr; + if (!addr.IPAddr()) { + Sleep(200); + return 1; + } + + NetLobbyClient conn(addr); + + if (conn.Ping()) { + info->machine_info = conn.GetMachineInfo(); + info->gameport = conn.GetGamePort(); + info->status = conn.GetStatus(); + info->nplayers = conn.NumUsers(); + info->hosted = conn.HasHost(); + info->ping_time = conn.GetLag(); + } + + else { + info->machine_info = Text(); + info->nplayers = 0; + info->hosted = 0; + info->status = NetServerInfo::OFFLINE; + info->ping_time = 0; + } + + return 0; +} + +// +--------------------------------------------------------------------+ + +void +NetClientDlg::OnAdd(AWEvent* event) +{ + manager->ShowNetAddrDlg(); +} + +void +NetClientDlg::OnDel(AWEvent* event) +{ + if (config && server_index >= 0) + config->DelServer(server_index); +} + +void +NetClientDlg::OnSelect(AWEvent* event) +{ + if (lst_servers) { + server_index = lst_servers->GetSelection(); + NetServerInfo* info = config->GetServerInfo(server_index); + + if (lbl_info) { + if (info) + lbl_info->SetText(info->machine_info); + else + lbl_info->SetText(""); + } + + if (btn_host) { + btn_host->SetEnabled(info && info->status > NetServerInfo::OFFLINE && info->hosted == 0); + } + + if (btn_join) { + btn_join->SetEnabled(info && info->status > NetServerInfo::OFFLINE); + } + } +} + +// +--------------------------------------------------------------------+ + +void +NetClientDlg::OnServer(AWEvent* event) +{ + manager->ShowNetServerDlg(); +} + +void +NetClientDlg::OnHost(AWEvent* event) +{ + if (config) { + config->SetServerIndex(server_index); + config->SetHostRequest(true); + config->Save(); + + NetServerInfo* info = config->GetServerInfo(server_index); + if (info && info->password == "Yes") { + manager->ShowNetPassDlg(); + } + else { + manager->ShowNetLobbyDlg(); + } + } +} + +void +NetClientDlg::OnJoin(AWEvent* event) +{ + if (config) { + config->SetServerIndex(server_index); + config->SetHostRequest(false); + config->Save(); + + NetServerInfo* info = config->GetServerInfo(server_index); + if (info && info->password == "Yes") { + manager->ShowNetPassDlg(); + } + else { + manager->ShowNetLobbyDlg(); + } + } +} + +void +NetClientDlg::OnCancel(AWEvent* event) +{ + if (config) { + config->Save(); + config->SetServerIndex(-1); + } + + manager->ShowMenuDlg(); +} diff --git a/Stars45/NetClientDlg.h b/Stars45/NetClientDlg.h new file mode 100644 index 0000000..9dfdb60 --- /dev/null +++ b/Stars45/NetClientDlg.h @@ -0,0 +1,80 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetClientDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Main Menu Dialog Active Window class +*/ + +#ifndef NetClientDlg_h +#define NetClientDlg_h + +#include "Types.h" +#include "NetClientConfig.h" +#include "NetLobby.h" + +#include "FormWindow.h" +#include "Bitmap.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Font.h" + +// +--------------------------------------------------------------------+ + +class MenuScreen; + +// +--------------------------------------------------------------------+ + +class NetClientDlg : public FormWindow +{ +public: + NetClientDlg(Screen* s, FormDef& def, MenuScreen* mgr); + virtual ~NetClientDlg(); + + virtual void RegisterControls(); + virtual void Show(); + virtual void ExecFrame(); + + // Operations: + virtual void OnSelect(AWEvent* event); + virtual void OnAdd(AWEvent* event); + virtual void OnDel(AWEvent* event); + virtual void OnServer(AWEvent* event); + virtual void OnHost(AWEvent* event); + virtual void OnJoin(AWEvent* event); + virtual void OnCancel(AWEvent* event); + + virtual void ShowServers(); + virtual void UpdateServers(); + virtual void PingServer(int n); + virtual bool PingComplete(); + virtual void StopNetProc(); + +protected: + MenuScreen* manager; + NetClientConfig* config; + + Button* btn_add; + Button* btn_del; + ListBox* lst_servers; + ActiveWindow* lbl_info; + int server_index; + int ping_index; + HANDLE hnet; + NetServerInfo ping_info; + + Button* btn_server; + Button* btn_host; + Button* btn_join; + Button* btn_cancel; +}; + +#endif NetClientDlg_h + diff --git a/Stars45/NetData.cpp b/Stars45/NetData.cpp new file mode 100644 index 0000000..93d0cba --- /dev/null +++ b/Stars45/NetData.cpp @@ -0,0 +1,1453 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetData.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Payload structures for multiplayer network packets +*/ + +#include "MemDebug.h" +#include "NetData.h" +#include "NetLink.h" +#include "NetMsg.h" +#include "RadioMessage.h" +#include "Ship.h" +#include "Shot.h" +#include "Sim.h" +#include "Instruction.h" +#include "Weapon.h" +#include "Element.h" +#include "System.h" +#include "Power.h" +#include "Shield.h" + +#include "Game.h" + +// +--------------------------------------------------------------------+ +// DATA SIZE OFFSET +// -------- ----------------- --------- +// type: 1 0 +// size: 1 1 +// objid: 2 2 +// loc: 9 (3 x 24 bits) 4 +// vel: 6 (3 x 16 bits) 13 +// euler: 4 (3 x 10 bits) 19 +// status: 1 23 + +const int LOCATION_OFFSET = 8000000; +const int VELOCITY_OFFSET = 32000; +const double EULER_SCALE = 2*PI / (1<<10); + +BYTE* +NetObjLoc::Pack() +{ + data[ 0] = TYPE; + data[ 1] = SIZE; + + // obj id + data[ 2] = (BYTE) ((objid & 0xff00) >> 8); + data[ 3] = (BYTE) ((objid & 0x00ff) ); + + // location + DWORD x = (DWORD) (((int) location.x + LOCATION_OFFSET) & 0x00ffffff); + DWORD y = (DWORD) (((int) location.y + LOCATION_OFFSET) & 0x00ffffff); + DWORD z = (DWORD) (((int) location.z + LOCATION_OFFSET) & 0x00ffffff); + + data[ 4] = (BYTE) ((x & 0x00ff0000) >> 16); + data[ 5] = (BYTE) ((x & 0x0000ff00) >> 8); + data[ 6] = (BYTE) ((x & 0x000000ff) ); + + data[ 7] = (BYTE) ((y & 0x00ff0000) >> 16); + data[ 8] = (BYTE) ((y & 0x0000ff00) >> 8); + data[ 9] = (BYTE) ((y & 0x000000ff) ); + + data[10] = (BYTE) ((z & 0x00ff0000) >> 16); + data[11] = (BYTE) ((z & 0x0000ff00) >> 8); + data[12] = (BYTE) ((z & 0x000000ff) ); + + // velocity + WORD vx = (WORD) (((int) velocity.x + VELOCITY_OFFSET) & 0x0000ffff); + WORD vy = (WORD) (((int) velocity.y + VELOCITY_OFFSET) & 0x0000ffff); + WORD vz = (WORD) (((int) velocity.z + VELOCITY_OFFSET) & 0x0000ffff); + + data[13] = (BYTE) ((vx & 0xff00) >> 8); + data[14] = (BYTE) ((vx & 0x00ff)); + + data[15] = (BYTE) ((vy & 0xff00) >> 8); + data[16] = (BYTE) ((vy & 0x00ff)); + + data[17] = (BYTE) ((vz & 0xff00) >> 8); + data[18] = (BYTE) ((vz & 0x00ff)); + + // orientation + if (_finite(euler.x)) { + while (euler.x < 0) euler.x += 2*PI; + while (euler.x > 2*PI) euler.x -= 2*PI; + } + else { + euler.x = 0; + } + + if (_finite(euler.y)) { + while (euler.y < 0) euler.y += 2*PI; + while (euler.y > 2*PI) euler.y -= 2*PI; + } + else { + euler.y = 0; + } + + if (_finite(euler.z)) { + while (euler.z < 0) euler.z += 2*PI; + while (euler.z > 2*PI) euler.z -= 2*PI; + } + else { + euler.z = 0; + } + + WORD ox = (WORD) (((int) (euler.x / EULER_SCALE)) & 0x000003ff); + WORD oy = (WORD) (((int) (euler.y / EULER_SCALE)) & 0x000003ff); + WORD oz = (WORD) (((int) (euler.z / EULER_SCALE)) & 0x000003ff); + + DWORD o = (ox << 20) | (oy << 10) | (oz); + + data[19] = (BYTE) ((o & 0xff000000) >> 24); + data[20] = (BYTE) ((o & 0x00ff0000) >> 16); + data[21] = (BYTE) ((o & 0x0000ff00) >> 8); + data[22] = (BYTE) ((o & 0x000000ff) ); + + // status bits + data[23] = throttle << 7 | + augmenter << 6 | + gear << 5 | + (shield >> 2) & 0x1f; + + return data; +} + +bool +NetObjLoc::Unpack(const BYTE* p) +{ + if (p && p[0] == TYPE && p[1] == SIZE) { + CopyMemory(data, p, SIZE); + + objid = (data[ 2] << 8) | + (data[ 3] ); + + int x = (data[ 4] << 16) | + (data[ 5] << 8) | + (data[ 6] ); + + int y = (data[ 7] << 16) | + (data[ 8] << 8) | + (data[ 9] ); + + int z = (data[10] << 16) | + (data[11] << 8) | + (data[12] ); + + int vx = (data[13] << 8) | + (data[14] ); + + int vy = (data[15] << 8) | + (data[16] ); + + int vz = (data[17] << 8) | + (data[18] ); + + DWORD o = (data[19] << 24) | + (data[20] << 16) | + (data[21] << 8) | + (data[22] ); + + WORD ox = (WORD) ((o >> 20) & 0x03ff); + WORD oy = (WORD) ((o >> 10) & 0x03ff); + WORD oz = (WORD) ((o ) & 0x03ff); + + throttle = data[23] & 0x80 ? true : false; + augmenter = data[23] & 0x40 ? true : false; + gear = data[23] & 0x20 ? true : false; + shield = (data[23] & 0x1f) << 2; + + location = Point(x -LOCATION_OFFSET, y -LOCATION_OFFSET, z -LOCATION_OFFSET); + velocity = Point(vx-VELOCITY_OFFSET, vy-VELOCITY_OFFSET, vz-VELOCITY_OFFSET); + euler = Point(ox*EULER_SCALE, oy*EULER_SCALE, oz*EULER_SCALE); + return true; + } + + return false; +} + +// +--------------------------------------------------------------------+ + +BYTE* +NetJoinRequest::Pack() +{ + ZeroMemory(data, SIZE); + + data[0] = TYPE; + data[1] = SIZE; + + for (int i = 0; i < name.length() && i < 16; i++) + data[2+i] = name[i]; + + for (i = 0; i < pass.length() && i < 16; i++) + data[18+i] = pass[i]; + + for (i = 0; i < elem.length() && i < 31; i++) + data[34+i] = elem[i]; + + data[65] = (BYTE) index; + + for (i = 0; i < serno.length() && i < 60; i++) + data[66+i] = serno[i]; + + return data; +} + +bool +NetJoinRequest::Unpack(const BYTE* p) +{ + if (p && p[0] == TYPE && p[1] == SIZE) { + CopyMemory(data, p, SIZE); + + char buf[64]; + + CopyMemory(buf, data+2, 16); + buf[16] = 0; + name = buf; + + CopyMemory(buf, data+18, 16); + buf[16] = 0; + pass = buf; + + CopyMemory(buf, data+34, 31); + buf[31] = 0; + elem = buf; + + index = data[65]; + + CopyMemory(buf, data+66, 60); + buf[61] = 0; + serno = buf; + return true; + } + + return false; +} + +// +--------------------------------------------------------------------+ + +NetJoinAnnounce::NetJoinAnnounce() + : index(0), integrity(0), respawns(0), decoys(0), probes(0), fuel(0), + shield(0), nid(0) +{ + ZeroMemory(ammo, sizeof(ammo)); +} + +void +NetJoinAnnounce::SetAmmo(const int* a) +{ + if (a) { + CopyMemory(ammo, a, sizeof(ammo)); + } +} + +void +NetJoinAnnounce::SetShip(Ship* s) +{ + SetName(s->Name()); + SetObjID(s->GetObjID()); + + if (s->GetElement()) { + SetElement(s->GetElement()->Name()); + SetIndex(s->GetElementIndex()); + } + + if (s->GetRegion()) + SetRegion(s->GetRegion()->Name()); + + SetLocation(s->Location()); + SetVelocity(s->Velocity()); + SetIntegrity(s->Integrity()); + SetRespawns(s->RespawnCount()); + + if (s->GetDecoy()) + SetDecoys(s->GetDecoy()->Ammo()); + + if (s->GetProbeLauncher()) + SetProbes(s->GetProbeLauncher()->Ammo()); + + if (s->Reactors().size()) + SetFuel(s->Reactors()[0]->Charge()); + + Shield* shield = s->GetShield(); + if (shield) + SetShield((int) shield->GetPowerLevel()); +} + +BYTE* +NetJoinAnnounce::Pack() +{ + ZeroMemory(data, SIZE); + + data[ 0] = TYPE; + data[ 1] = SIZE; + data[ 2] = (BYTE) ((objid & 0xff00) >> 8); + data[ 3] = (BYTE) ((objid & 0x00ff) ); + + float* f = (float*) (data + 4); + *f++ = (float) loc.x; // bytes 4 - 7 + *f++ = (float) loc.y; // bytes 8 - 11 + *f++ = (float) loc.z; // bytes 12 - 15 + *f++ = (float) integrity; // bytes 16 - 19 + + for (int i = 0; i < name.length() && i < 16; i++) + data[20+i] = name[i]; + + for (i = 0; i < elem.length() && i < 32; i++) + data[36+i] = elem[i]; + + for (i = 0; i < region.length() && i < 32; i++) + data[68+i] = region[i]; + + int* p = (int*) (data + 100); + *p++ = respawns; // bytes 100 - 103 + *p++ = decoys; // bytes 104 - 107 + *p++ = probes; // bytes 108 - 111 + + data[112]= (BYTE) fuel; // byte 112 + data[113]= (BYTE) shield; // byte 113 + + BYTE* a = data + 116; + for (i = 0; i < 16; i++) { // bytes 116 - 179 + if (ammo[i] >= 0) { + *a++ = ammo[i]; + } + else { + *a++ = 255; + } + } + + data[180] = (BYTE) index; + + f = (float*) (data + 184); + *f++ = (float) velocity.x; + *f++ = (float) velocity.y; + *f++ = (float) velocity.z; + + return data; +} + +bool +NetJoinAnnounce::Unpack(const BYTE* p) +{ + if (p && p[0] == TYPE && p[1] == SIZE) { + CopyMemory(data, p, SIZE); + + objid = (data[2] << 8) | data[3]; + + float* f = (float*) (data + 4); + loc.x = *f++; + loc.y = *f++; + loc.z = *f++; + integrity = *f++; + + char buf[64]; + CopyMemory(buf, data+20, 16); + buf[16] = 0; + name = buf; + + CopyMemory(buf, data+36, 32); + buf[16] = 0; + elem = buf; + + CopyMemory(buf, data+68, 32); + buf[16] = 0; + region = buf; + + int* p = (int*) (data + 100); + respawns = *p++; + decoys = *p++; + probes = *p++; + + fuel = data[112]; + shield = data[113]; + + CopyMemory(ammo, data+116, 16); + + index = data[180]; + + f = (float*) (data + 184); + velocity.x = *f++; + velocity.y = *f++; + velocity.z = *f++; + + return true; + } + + return false; +} + +// +--------------------------------------------------------------------+ + +BYTE* +NetQuitAnnounce::Pack() +{ + ZeroMemory(data, SIZE); + + data[0] = TYPE; + data[1] = SIZE; + + data[2] = (BYTE) ((objid & 0xff00) >> 8); + data[3] = (BYTE) ((objid & 0x00ff) ); + data[4] = (BYTE) (disconnected); + + return data; +} + +bool +NetQuitAnnounce::Unpack(const BYTE* p) +{ + if (p && p[0] == TYPE && p[1] == SIZE) { + CopyMemory(data, p, SIZE); + + objid = (data[2] << 8) | data[3]; + disconnected = data[4] ? true : false; + + return true; + } + + return false; +} + +// +--------------------------------------------------------------------+ + +BYTE* +NetDisconnect::Pack() +{ + ZeroMemory(data, SIZE); + + data[0] = TYPE; + data[1] = SIZE; + + return data; +} + +bool +NetDisconnect::Unpack(const BYTE* p) +{ + if (p && p[0] == TYPE && p[1] == SIZE) { + CopyMemory(data, p, SIZE); + return true; + } + + return false; +} + +// +--------------------------------------------------------------------+ + +BYTE* +NetObjDamage::Pack() +{ + ZeroMemory(data, SIZE); + + data[0] = TYPE; + data[1] = SIZE; + + data[2] = (BYTE) ((objid & 0xff00) >> 8); + data[3] = (BYTE) ((objid & 0x00ff) ); + data[4] = (BYTE) ((shotid & 0xff00) >> 8); + data[5] = (BYTE) ((shotid & 0x00ff) ); + + float* p = (float*) (data + 6); + *p = damage; + + return data; +} + +bool +NetObjDamage::Unpack(const BYTE* p) +{ + if (p && p[0] == TYPE && p[1] == SIZE) { + CopyMemory(data, p, SIZE); + + objid = (data[2] << 8) | data[3]; + shotid = (data[4] << 8) | data[5]; + damage = *(float*) (data + 6); + + return true; + } + + return false; +} + +// +--------------------------------------------------------------------+ + +BYTE* +NetSysDamage::Pack() +{ + ZeroMemory(data, SIZE); + + data[0] = TYPE; + data[1] = SIZE; + + data[2] = (BYTE) ((objid & 0xff00) >> 8); + data[3] = (BYTE) ((objid & 0x00ff) ); + + float* p = (float*) (data + 4); + *p = (float) damage; + + data[8] = (BYTE) (sysix+1); + data[9] = dmgtype; + + return data; +} + +bool +NetSysDamage::Unpack(const BYTE* p) +{ + if (p && p[0] == TYPE && p[1] == SIZE) { + CopyMemory(data, p, SIZE); + + objid = (data[2] << 8) | data[3]; + damage = *(float*) (data + 4); + sysix = data[8]; + dmgtype = data[9]; + + sysix--; + return true; + } + + return false; +} + +// +--------------------------------------------------------------------+ + +BYTE* +NetSysStatus::Pack() +{ + ZeroMemory(data, SIZE); + + data[0] = TYPE; + data[1] = SIZE; + + data[2] = (BYTE) ((objid & 0xff00) >> 8); + data[3] = (BYTE) ((objid & 0x00ff) ); + data[4] = (BYTE) (sysix+1); + data[5] = (BYTE) (status); + data[6] = (BYTE) (power); + data[7] = (BYTE) (reactor); + + float* f = (float*) (data + 8); + *f = (float) avail; + + return data; +} + +bool +NetSysStatus::Unpack(const BYTE* p) +{ + if (p && p[0] == TYPE && p[1] == SIZE) { + CopyMemory(data, p, SIZE); + + objid = (data[2] << 8) | data[3]; + sysix = data[4]; + status = data[5]; + power = data[6]; + reactor = data[7]; + + float* f = (float*) (data + 8); + avail = *f; + + sysix--; + return true; + } + + return false; +} + +// +--------------------------------------------------------------------+ + +BYTE* +NetObjKill::Pack() +{ + ZeroMemory(data, SIZE); + + data[0] = TYPE; + data[1] = SIZE; + + data[2] = (BYTE) ((objid & 0xff00) >> 8); + data[3] = (BYTE) ((objid & 0x00ff) ); + data[4] = (BYTE) ((kill_id & 0xff00) >> 8); + data[5] = (BYTE) ((kill_id & 0x00ff) ); + data[6] = (BYTE) killtype; + data[7] = (BYTE) respawn; + + float* f = (float*) (data + 8); + *f++ = (float) loc.x; + *f++ = (float) loc.y; + *f++ = (float) loc.z; + + data[20] = (BYTE) deck; + + return data; +} + +bool +NetObjKill::Unpack(const BYTE* p) +{ + if (p && p[0] == TYPE && p[1] == SIZE) { + CopyMemory(data, p, SIZE); + + objid = (data[2] << 8) | data[3]; + kill_id = (data[4] << 8) | data[5]; + killtype = data[6]; + respawn = data[7] ? true : false; + + float* f = (float*) (data + 8); + loc.x = *f++; + loc.y = *f++; + loc.z = *f++; + + deck = data[20]; + + return true; + } + + return false; +} + +// +--------------------------------------------------------------------+ + +BYTE* +NetObjHyper::Pack() +{ + ZeroMemory(data, SIZE); + + data[0] = TYPE; + data[1] = SIZE; + + data[2] = (BYTE) ((objid & 0xff00) >> 8); + data[3] = (BYTE) ((objid & 0x00ff) ); + data[4] = (BYTE) ((fc_src & 0xff00) >> 8); + data[5] = (BYTE) ((fc_src & 0x00ff) ); + data[6] = (BYTE) ((fc_dst & 0xff00) >> 8); + data[7] = (BYTE) ((fc_dst & 0x00ff) ); + data[8] = (BYTE) transtype; + + float* f = (float*) (data + 12); + *f++ = (float) location.x; // bytes 12 - 15 + *f++ = (float) location.y; // bytes 16 - 19 + *f++ = (float) location.z; // bytes 20 - 23 + + char* p = (char*) (data + 24); + strncpy(p, region.data(), 31); + + return data; +} + +bool +NetObjHyper::Unpack(const BYTE* p) +{ + if (p && p[0] == TYPE && p[1] == SIZE) { + CopyMemory(data, p, SIZE); + + objid = (data[2] << 8) | data[3]; + fc_src = (data[4] << 8) | data[5]; + fc_dst = (data[6] << 8) | data[7]; + transtype = data[8]; + + float* f = (float*) (data + 12); + location.x = *f++; + location.y = *f++; + location.z = *f++; + + region = (char*) (data + 24); + + return true; + } + + return false; +} + +// +--------------------------------------------------------------------+ + +BYTE* +NetObjTarget::Pack() +{ + ZeroMemory(data, SIZE); + + data[0] = TYPE; + data[1] = SIZE; + + data[2] = (BYTE) ((objid & 0xff00) >> 8); + data[3] = (BYTE) ((objid & 0x00ff) ); + data[4] = (BYTE) ((tgtid & 0xff00) >> 8); + data[5] = (BYTE) ((tgtid & 0x00ff) ); + data[6] = (BYTE) (sysix+1); + + return data; +} + +bool +NetObjTarget::Unpack(const BYTE* p) +{ + if (p && p[0] == TYPE && p[1] == SIZE) { + CopyMemory(data, p, SIZE); + + objid = (data[2] << 8) | data[3]; + tgtid = (data[4] << 8) | data[5]; + sysix = data[6]; + + sysix--; + return true; + } + + return false; +} + +// +--------------------------------------------------------------------+ + +BYTE* +NetObjEmcon::Pack() +{ + ZeroMemory(data, SIZE); + + data[0] = TYPE; + data[1] = SIZE; + + data[2] = (BYTE) ((objid & 0xff00) >> 8); + data[3] = (BYTE) ((objid & 0x00ff) ); + data[4] = (BYTE) (emcon); + + return data; +} + +bool +NetObjEmcon::Unpack(const BYTE* p) +{ + if (p && p[0] == TYPE && p[1] == SIZE) { + CopyMemory(data, p, SIZE); + + objid = (data[2] << 8) | data[3]; + emcon = data[4]; + + return true; + } + + return false; +} + +// +--------------------------------------------------------------------+ + +BYTE* +NetWepTrigger::Pack() +{ + ZeroMemory(data, SIZE); + + data[0] = TYPE; + data[1] = SIZE; + + data[2] = (BYTE) ((objid & 0xff00) >> 8); + data[3] = (BYTE) ((objid & 0x00ff) ); + data[4] = (BYTE) ((tgtid & 0xff00) >> 8); + data[5] = (BYTE) ((tgtid & 0x00ff) ); + data[6] = (BYTE) (sysix+1); + data[7] = (BYTE) index; + data[8] = (BYTE) count; + data[9] = ((BYTE) decoy << 1) | + ((BYTE) probe ); + + return data; +} + +bool +NetWepTrigger::Unpack(const BYTE* p) +{ + if (p && p[0] == TYPE && p[1] == SIZE) { + CopyMemory(data, p, SIZE); + + objid = (data[2] << 8) | data[3]; + tgtid = (data[4] << 8) | data[5]; + sysix = data[6]; + index = data[7]; + count = data[8]; + decoy = (data[9] & 0x02) ? true : false; + probe = (data[9] & 0x01) ? true : false; + + sysix--; + return true; + } + + return false; +} + +// +--------------------------------------------------------------------+ + +BYTE* +NetWepRelease::Pack() +{ + ZeroMemory(data, SIZE); + + data[0] = TYPE; + data[1] = SIZE; + + data[2] = (BYTE) ((objid & 0xff00) >> 8); + data[3] = (BYTE) ((objid & 0x00ff) ); + data[4] = (BYTE) ((tgtid & 0xff00) >> 8); + data[5] = (BYTE) ((tgtid & 0x00ff) ); + data[6] = (BYTE) ((wepid & 0xff00) >> 8); + data[7] = (BYTE) ((wepid & 0x00ff) ); + data[8] = (BYTE) (sysix+1); + data[9] = (BYTE) index; + data[10] = ((BYTE) decoy << 1) | + ((BYTE) probe ); + + return data; +} + +bool +NetWepRelease::Unpack(const BYTE* p) +{ + if (p && p[0] == TYPE && p[1] == SIZE) { + CopyMemory(data, p, SIZE); + + objid = (data[2] << 8) | data[3]; + tgtid = (data[4] << 8) | data[5]; + wepid = (data[6] << 8) | data[7]; + sysix = data[8]; + index = data[9]; + decoy = (data[10] & 0x02) ? true : false; + probe = (data[10] & 0x01) ? true : false; + + sysix--; + return true; + } + + return false; +} + +// +--------------------------------------------------------------------+ + +BYTE* +NetWepDestroy::Pack() +{ + ZeroMemory(data, SIZE); + + data[0] = TYPE; + data[1] = SIZE; + data[2] = (BYTE) ((objid & 0xff00) >> 8); + data[3] = (BYTE) ((objid & 0x00ff) ); + + return data; +} + +bool +NetWepDestroy::Unpack(const BYTE* p) +{ + if (p && p[0] == TYPE && p[1] == SIZE) { + CopyMemory(data, p, SIZE); + objid = (data[2] << 8) | data[3]; + return true; + } + + return false; +} + +// +--------------------------------------------------------------------+ + +NetCommMsg::~NetCommMsg() +{ + delete radio_message; +} + +void +NetCommMsg::SetRadioMessage(RadioMessage* m) +{ + radio_message = new(__FILE__,__LINE__) RadioMessage(*m); +} + +BYTE* +NetCommMsg::Pack() +{ + ZeroMemory(data, SIZE); + + if (radio_message) { + length = 55 + radio_message->Info().length(); + + if (length > SIZE) + length = SIZE; + + data[0] = TYPE; + data[1] = (BYTE) length; + + if (radio_message->Sender()) { + objid = radio_message->Sender()->GetObjID(); + data[2] = (BYTE) ((objid & 0xff00) >> 8); + data[3] = (BYTE) ((objid & 0x00ff) ); + } + + if (radio_message->DestinationShip()) { + DWORD dstid = radio_message->DestinationShip()->GetObjID(); + data[4] = (BYTE) ((dstid & 0xff00) >> 8); + data[5] = (BYTE) ((dstid & 0x00ff) ); + } + + data[6] = (BYTE) radio_message->Action(); + data[7] = (BYTE) radio_message->Channel(); + + if (radio_message->TargetList().size() > 0) { + SimObject* tgt = radio_message->TargetList().at(0); + DWORD tgtid = tgt->GetObjID(); + data[8] = (BYTE) ((tgtid & 0xff00) >> 8); + data[9] = (BYTE) ((tgtid & 0x00ff) ); + } + + float* f = (float*) (data + 10); + *f++ = (float) radio_message->Location().x; // bytes 10 - 13 + *f++ = (float) radio_message->Location().y; // bytes 14 - 17 + *f++ = (float) radio_message->Location().z; // bytes 18 - 21 + + char* p = (char*) (data + 22); + + Element* dst_elem = radio_message->DestinationElem(); + if (dst_elem) + strncpy(p, dst_elem->Name().data(), 31); + + p = (char*) (data + 55); + strncpy(p, radio_message->Info().data(), 128); + + data[SIZE-1] = 0; + } + + return data; +} + +bool +NetCommMsg::Unpack(const BYTE* p) +{ + if (p && p[0] == TYPE) { + length = p[1]; + ZeroMemory(data, SIZE); + CopyMemory(data, p, length); + + DWORD dstid = 0; + DWORD tgtid = 0; + int action = 0; + int channel = 0; + Point loc; + Text elem_name; + Text info; + + objid = (data[2] << 8) | data[3]; + dstid = (data[4] << 8) | data[5]; + tgtid = (data[8] << 8) | data[9]; + action = data[6]; + channel = data[7]; + + float* f = (float*) (data + 10); + loc.x = *f++; + loc.y = *f++; + loc.z = *f++; + + elem_name = (char*) (data + 22); + + if (length > 55) + info = (char*) (data + 55); + + Sim* sim = Sim::GetSim(); + Ship* src = sim->FindShipByObjID(objid); + Ship* dst = sim->FindShipByObjID(dstid); + Element* elem = sim->FindElement(elem_name); + + delete radio_message; + if (elem) + radio_message = new(__FILE__,__LINE__) RadioMessage(elem, src, action); + else + radio_message = new(__FILE__,__LINE__) RadioMessage(dst, src, action); + + radio_message->SetChannel(channel); + radio_message->SetLocation(loc); + radio_message->SetInfo(info); + + if (tgtid) { + SimObject* tgt = sim->FindShipByObjID(tgtid); + + if (!tgt) + tgt = sim->FindShotByObjID(tgtid); + + if (tgt) + radio_message->AddTarget(tgt); + } + + return true; + } + + return false; +} + +// +--------------------------------------------------------------------+ + +BYTE* +NetChatMsg::Pack() +{ + ZeroMemory(data, SIZE); + + int chatlen = text.length(); + + if (chatlen > MAX_CHAT) + chatlen = MAX_CHAT; + + length = HDR_LEN + NAME_LEN + chatlen; + + data[0] = TYPE; + data[1] = (BYTE) length; + data[2] = (BYTE) ((dstid & 0xff00) >> 8); + data[3] = (BYTE) ((dstid & 0x00ff) ); + + char* p = (char*) (data + HDR_LEN); + strncpy(p, name.data(), NAME_LEN); + + p = (char*) (data + HDR_LEN + NAME_LEN); + strncpy(p, text.data(), chatlen); + + return data; +} + +bool +NetChatMsg::Unpack(const BYTE* p) +{ + if (p && p[0] == TYPE) { + length = p[1]; + ZeroMemory(data, SIZE); + CopyMemory(data, p, length); + + dstid = (data[2] << 8) | data[3]; + + char buffer[NAME_LEN+1]; + ZeroMemory(buffer, NAME_LEN+1); + CopyMemory(buffer, data + HDR_LEN, NAME_LEN); + + name = buffer; + + if (length > HDR_LEN + NAME_LEN) + text = (char*) (data + HDR_LEN + NAME_LEN); + + return true; + } + + return false; +} + +// +--------------------------------------------------------------------+ + +NetElemRequest::NetElemRequest() +{ } + +BYTE* +NetElemRequest::Pack() +{ + ZeroMemory(data, SIZE); + + data[0] = TYPE; + data[1] = SIZE; + + strncpy((char*) (data + 8), name.data(), NAME_LEN-1); + + return data; +} + +bool +NetElemRequest::Unpack(const BYTE* p) +{ + if (p && p[0] == TYPE) { + ZeroMemory(data, SIZE); + CopyMemory(data, p, SIZE); + + name = (const char*) (data + 8); + + return true; + } + + return false; +} + +// +--------------------------------------------------------------------+ + +NetElemCreate::NetElemCreate() + : iff(0), type(0), intel(0), alert(false), in_flight(false) +{ + for (int i = 0; i < 16; i++) + load[i] = -1; +} + +void +NetElemCreate::SetLoadout(int* l) +{ + if (l) { + CopyMemory(load, l, sizeof(load)); + } + else { + for (int i = 0; i < 16; i++) + load[i] = -1; + } +} + +void +NetElemCreate::SetSlots(int* s) +{ + if (s) { + CopyMemory(slots, s, sizeof(slots)); + } + else { + for (int i = 0; i < 4; i++) + slots[i] = -1; + } +} + +BYTE* +NetElemCreate::Pack() +{ + ZeroMemory(data, SIZE); + + data[0] = TYPE; + data[1] = SIZE; + + data[2] = (BYTE) iff; + data[3] = (BYTE) type; + data[4] = (BYTE) intel; + data[5] = (BYTE) obj_code; + + for (int i = 0; i < 16; i++) + data[6+i] = (BYTE) load[i]; + + strncpy((char*) (data + 22), name.data(), NAME_LEN-1); + strncpy((char*) (data + 54), commander.data(), NAME_LEN-1); + strncpy((char*) (data + 86), objective.data(), NAME_LEN-1); + strncpy((char*) (data + 118), carrier.data(), NAME_LEN-1); + + data[150] = (BYTE) squadron; + data[151] = (BYTE) slots[0]; + data[152] = (BYTE) slots[1]; + data[153] = (BYTE) slots[2]; + data[154] = (BYTE) slots[3]; + data[155] = (BYTE) alert; + data[156] = (BYTE) in_flight; + + return data; +} + +bool +NetElemCreate::Unpack(const BYTE* p) +{ + if (p && p[0] == TYPE) { + ZeroMemory(data, SIZE); + CopyMemory(data, p, SIZE); + + iff = data[2]; + type = data[3]; + intel = data[4]; + obj_code = data[5]; + + for (int i = 0; i < 16; i++) { + load[i] = data[6+i] == 255 ? -1 : data[6+i]; + } + + name = (const char*) (data + 22); + commander = (const char*) (data + 54); + objective = (const char*) (data + 86); + carrier = (const char*) (data + 118); + + squadron = data[150]; + + for (i = 0; i < 4; i++) { + slots[i] = data[151+i]; + if (slots[i] >= 255) + slots[i] = -1; + } + + alert = data[155] ? true : false; + in_flight = data[156] ? true : false; + + return true; + } + + return false; +} + +// +--------------------------------------------------------------------+ + +BYTE* +NetShipLaunch::Pack() +{ + ZeroMemory(data, SIZE); + + data[ 0] = TYPE; + data[ 1] = SIZE; + + DWORD* p = (DWORD*) (data + 4); + *p++ = (DWORD) objid; + *p++ = (DWORD) squadron; + *p++ = (DWORD) slot; + + return data; +} + +bool +NetShipLaunch::Unpack(const BYTE* p) +{ + if (p && p[0] == TYPE) { + ZeroMemory(data, SIZE); + CopyMemory(data, p, SIZE); + + DWORD* p = (DWORD*) (data + 4); + objid = *p++; + squadron = (int) *p++; + slot = (int) *p++; + + return true; + } + + return false; +} + +// +--------------------------------------------------------------------+ + +NetNavData::NetNavData() + : objid(0), create(true), index(0), navpoint(0) +{ +} + +NetNavData::~NetNavData() +{ + delete navpoint; +} + +void +NetNavData::SetNavPoint(Instruction* n) +{ + if (navpoint) { + delete navpoint; + navpoint = 0; + } + + if (n) + navpoint = new(__FILE__,__LINE__) Instruction(*n); +} + +BYTE* +NetNavData::Pack() +{ + ZeroMemory(data, SIZE); + + data[ 0] = TYPE; + data[ 1] = SIZE; + + data[ 2] = (BYTE) ((objid & 0xff00) >> 8); + data[ 3] = (BYTE) ((objid & 0x00ff) ); + data[ 4] = (BYTE) create; + data[ 5] = (BYTE) index; + + if (!navpoint) + return data; + + data[ 6] = (BYTE) navpoint->Action(); + data[ 7] = (BYTE) navpoint->Formation(); + data[ 8] = (BYTE) navpoint->Status(); + data[ 9] = (BYTE) navpoint->EMCON(); + data[10] = (BYTE) navpoint->WeaponsFree(); + data[11] = (BYTE) navpoint->Farcast(); + + Point loc = navpoint->Location(); + + float* f = (float*) (data + 12); + *f++ = (float) loc.x; // bytes 12 - 15 + *f++ = (float) loc.y; // bytes 16 - 19 + *f++ = (float) loc.z; // bytes 20 - 23 + *f++ = (float) navpoint->HoldTime(); // bytes 24 - 27 + *f++ = (float) navpoint->Speed(); // bytes 28 - 31 + + WORD tgtid = 0; + if (navpoint->GetTarget()) + tgtid = (WORD) navpoint->GetTarget()->GetObjID(); + + data[32] = (BYTE) ((tgtid & 0xff00) >> 8); + data[33] = (BYTE) ((tgtid & 0x00ff) ); + + strncpy((char*) (data + 34), navpoint->RegionName(), NAME_LEN-1); + strncpy((char*) (data + 66), navpoint->TargetName(), NAME_LEN-1); + strncpy((char*) (data + 98), elem.data(), NAME_LEN-1); + + return data; +} + +bool +NetNavData::Unpack(const BYTE* p) +{ + if (p && p[0] == TYPE) { + ZeroMemory(data, SIZE); + CopyMemory(data, p, SIZE); + + int action; + int formation; + int status; + int emcon; + int wep_free; + int farcast; + int speed; + float hold_time; + Point loc; + WORD tgtid = 0; + + const char* rgn_name = 0; + const char* tgt_name = 0; + + objid = (data[ 2] << 8) | + (data[ 3] ); + + tgtid = (data[32] << 8) | + (data[33] ); + + create = data[ 4] ? true : false; + index = data[ 5]; + action = data[ 6]; + formation = data[ 7]; + status = data[ 8]; + emcon = data[ 9]; + wep_free = data[10]; + farcast = data[11]; + + float* f = (float*) (data + 12); + loc.x = *f++; + loc.y = *f++; + loc.z = *f++; + hold_time = *f++; + speed = (int) *f++; + + rgn_name = (const char*) (data + 34); + tgt_name = (const char*) (data + 66); + elem = (const char*) (data + 98); + + if (navpoint) { + delete navpoint; + navpoint = 0; + } + + Sim* sim = Sim::GetSim(); + SimRegion* rgn = 0; + + if (sim) + rgn = sim->FindRegion(rgn_name); + + if (rgn) + navpoint = new(__FILE__,__LINE__) Instruction(rgn, loc, action); + else + navpoint = new(__FILE__,__LINE__) Instruction(rgn_name, loc, action); + + navpoint->SetFormation(formation); + navpoint->SetStatus(status); + navpoint->SetEMCON(emcon); + navpoint->SetWeaponsFree(wep_free); + navpoint->SetFarcast(farcast); + navpoint->SetHoldTime(hold_time); + navpoint->SetSpeed(speed); + navpoint->SetTarget(tgt_name); + + if (tgtid) { + Sim* sim = Sim::GetSim(); + Ship* tgt = sim->FindShipByObjID(tgtid); + + if (tgt) + navpoint->SetTarget(tgt); + } + + if (index >= 255) + index = -1; + + return true; + } + + return false; +} + +// +--------------------------------------------------------------------+ + +BYTE* +NetNavDelete::Pack() +{ + ZeroMemory(data, SIZE); + + data[ 0] = TYPE; + data[ 1] = SIZE; + + data[ 2] = (BYTE) ((objid & 0xff00) >> 8); + data[ 3] = (BYTE) ((objid & 0x00ff) ); + data[ 4] = (BYTE) index; + + strncpy((char*) (data + 6), elem.data(), 31); + + return data; +} + +bool +NetNavDelete::Unpack(const BYTE* p) +{ + if (p && p[0] == TYPE) { + ZeroMemory(data, SIZE); + CopyMemory(data, p, SIZE); + int index = 0; + + objid = (data[ 2] << 8) | + (data[ 3] ); + + index = data[4]; + elem = (const char*) (data + 6); + + if (index >= 255) + index = -1; + + return true; + } + + return false; +} + +// +--------------------------------------------------------------------+ + +BYTE* +NetSelfDestruct::Pack() +{ + ZeroMemory(data, SIZE); + + data[0] = TYPE; + data[1] = SIZE; + + data[2] = (BYTE) ((objid & 0xff00) >> 8); + data[3] = (BYTE) ((objid & 0x00ff) ); + + float* p = (float*) (data + 4); + *p = damage; + + return data; +} + +bool +NetSelfDestruct::Unpack(const BYTE* p) +{ + if (p && p[0] == TYPE && p[1] == SIZE) { + CopyMemory(data, p, SIZE); + + objid = (data[2] << 8) | data[3]; + damage = *(float*) (data + 4); + + return true; + } + + return false; +} diff --git a/Stars45/NetData.h b/Stars45/NetData.h new file mode 100644 index 0000000..bd933d9 --- /dev/null +++ b/Stars45/NetData.h @@ -0,0 +1,966 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetData.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Payload structures for multiplayer network packets +*/ + +#ifndef NetData_h +#define NetData_h + +#include "Types.h" +#include "Geometry.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +// UNRELIABLE: 0x01 - 0x0F + +const BYTE NET_PING = 0x01; +const BYTE NET_PONG = 0x02; +const BYTE NET_OBJ_LOC = 0x03; + +// RELIABLE: 0x10 - 0x7F + +const BYTE NET_JOIN_REQUEST = 0x10; +const BYTE NET_JOIN_ANNOUNCE = 0x11; +const BYTE NET_QUIT_REQUEST = 0x12; +const BYTE NET_QUIT_ANNOUNCE = 0x13; +const BYTE NET_KICK_REQUEST = 0x14; +const BYTE NET_KICK_ANNOUNCE = 0x15; +const BYTE NET_GAME_OVER = 0x16; +const BYTE NET_COMM_MESSAGE = 0x17; +const BYTE NET_CHAT_MESSAGE = 0x18; +const BYTE NET_DISCONNECT = 0x19; + +const BYTE NET_OBJ_DAMAGE = 0x20; +const BYTE NET_OBJ_KILL = 0x21; +const BYTE NET_OBJ_SPAWN = 0x22; +const BYTE NET_OBJ_HYPER = 0x23; +const BYTE NET_OBJ_TARGET = 0x24; +const BYTE NET_OBJ_EMCON = 0x25; +const BYTE NET_SYS_DAMAGE = 0x26; +const BYTE NET_SYS_STATUS = 0x27; + +const BYTE NET_ELEM_CREATE = 0x28; +const BYTE NET_SHIP_LAUNCH = 0x29; +const BYTE NET_NAV_DATA = 0x2A; +const BYTE NET_NAV_DELETE = 0x2B; +const BYTE NET_ELEM_REQUEST = 0x2C; + +const BYTE NET_WEP_TRIGGER = 0x30; +const BYTE NET_WEP_RELEASE = 0x31; +const BYTE NET_WEP_DESTROY = 0x32; + +const BYTE NET_SELF_DESTRUCT = 0x3F; + +// +--------------------------------------------------------------------+ + +class Ship; + +// +--------------------------------------------------------------------+ + +class NetData +{ +public: + static const char* TYPENAME() { return "NetData"; } + + NetData() { } + virtual ~NetData() { } + + virtual int Type() const { return 0; } + virtual int Length() const { return 0; } + virtual BYTE* Pack() { return 0; } + virtual bool Unpack(const BYTE* data) { return 0; } + + virtual DWORD GetObjID() const { return 0; } + virtual void SetObjID(DWORD o) { } +}; + +// +--------------------------------------------------------------------+ + +class NetObjLoc : public NetData +{ +public: + static const char* TYPENAME() { return "NetObjLoc"; } + + NetObjLoc() : objid(0), throttle(false), augmenter(false), shield(0) { } + NetObjLoc(DWORD oid, const Point& pos, const Point& orient, const Point& vel) : + objid(oid), location(pos), euler(orient), velocity(vel), + throttle(false), augmenter(false), gear(false), shield(0) { } + + enum { TYPE=NET_OBJ_LOC, SIZE=24 }; + + virtual BYTE* Pack(); + virtual bool Unpack(const BYTE* data); + virtual int Type() const { return TYPE; } + virtual int Length() const { return SIZE; } + + virtual DWORD GetObjID() const { return objid; } + virtual void SetObjID(DWORD id) { objid = id; } + + Point GetLocation() const { return location; } + Point GetVelocity() const { return velocity; } + Point GetOrientation() const { return euler; } + bool GetThrottle() const { return throttle; } + bool GetAugmenter() const { return augmenter; } + bool GetGearDown() const { return gear; } + int GetShield() const { return shield; } + + void SetLocation(const Point& loc) { location = loc; } + void SetVelocity(const Point& v) { velocity = v; } + void SetOrientation(const Point& o) { euler = o; } + void SetThrottle(bool t) { throttle = t; } + void SetAugmenter(bool a) { augmenter = a; } + void SetGearDown(bool g) { gear = g; } + void SetShield(int s) { shield = s; } + +private: + DWORD objid; + Point location; + Point velocity; + Point euler; + bool throttle; + bool augmenter; + bool gear; + int shield; + + BYTE data[SIZE]; +}; + +// +--------------------------------------------------------------------+ + +class NetJoinRequest : public NetData +{ +public: + static const char* TYPENAME() { return "NetJoinRequest"; } + + NetJoinRequest() : index(0) { } + + enum { TYPE=NET_JOIN_REQUEST, SIZE=128 }; + + virtual BYTE* Pack(); + virtual bool Unpack(const BYTE* data); + virtual int Type() const { return TYPE; } + virtual int Length() const { return SIZE; } + + const char* GetName() const { return name; } + const char* GetPassword() const { return pass; } + const char* GetSerialNumber() const { return serno; } + const char* GetElement() const { return elem; } + int GetIndex() const { return index; } + + void SetName(const char* n) { name = n; } + void SetPassword(const char* p) { pass = p; } + void SetSerialNumber(const char* s){ serno = s; } + void SetElement(const char* n) { elem = n; } + void SetIndex(int n) { index = n; } + +private: + Text name; // callsign + Text pass; // password + Text serno; // box cdkey + Text elem; // element to join + int index; // one-based index + + BYTE data[SIZE]; +}; + +// +--------------------------------------------------------------------+ + +class NetJoinAnnounce : public NetData +{ +public: + static const char* TYPENAME() { return "NetJoinAnnounce"; } + + NetJoinAnnounce(); + + enum { TYPE=NET_JOIN_ANNOUNCE, SIZE=200 }; + + virtual BYTE* Pack(); + virtual bool Unpack(const BYTE* data); + virtual int Type() const { return TYPE; } + virtual int Length() const { return SIZE; } + + virtual DWORD GetObjID() const { return objid; } + virtual void SetObjID(DWORD o) { objid = o; } + + const char* GetName() const { return name; } + const char* GetElement() const { return elem; } + const char* GetRegion() const { return region; } + const Point& GetLocation() const { return loc; } + const Point& GetVelocity() const { return velocity; } + int GetIndex() const { return index; } + double GetIntegrity() const { return integrity; } + int GetRespawns() const { return respawns; } + int GetDecoys() const { return decoys; } + int GetProbes() const { return probes; } + int GetFuel() const { return fuel; } + int GetShield() const { return shield; } + const int* GetAmmo() const { return ammo; } + + void SetShip(Ship* s); + + void SetName(const char* n) { name = n; } + void SetElement(const char* n) { elem = n; } + void SetRegion(const char* r) { region = r; } + void SetLocation(const Point& l) { loc = l; } + void SetVelocity(const Point& v) { velocity = v; } + void SetIndex(int n) { index = n; } + void SetIntegrity(double n) { integrity = (float) n; } + void SetRespawns(int n) { respawns = n; } + void SetDecoys(int n) { decoys = n; } + void SetProbes(int n) { probes = n; } + void SetFuel(int n) { fuel = n; } + void SetShield(int n) { shield = n; } + void SetAmmo(const int* a); + + virtual DWORD GetNetID() const { return nid; } + virtual void SetNetID(DWORD n) { nid = n; } + +private: + Text name; // callsign + Text elem; // element to join + Text region; // region ship is in + Point loc; // location of ship + Point velocity; // velocity of ship + int index; // one-based index + float integrity; // hull integrity + int respawns; + int decoys; + int probes; + int fuel; + int shield; + int ammo[16]; + DWORD objid; + DWORD nid; // not sent over network + + BYTE data[SIZE]; +}; + +// +--------------------------------------------------------------------+ + +class NetQuitAnnounce : public NetData +{ +public: + static const char* TYPENAME() { return "NetQuitAnnounce"; } + + NetQuitAnnounce() : objid(0), disconnected(false) { } + + enum { TYPE=NET_QUIT_ANNOUNCE, SIZE=5 }; + + virtual BYTE* Pack(); + virtual bool Unpack(const BYTE* data); + virtual int Type() const { return TYPE; } + virtual int Length() const { return SIZE; } + + virtual DWORD GetObjID() const { return objid; } + virtual void SetObjID(DWORD o) { objid = o; } + virtual bool GetDisconnected() const { return disconnected; } + virtual void SetDisconnected(bool d) { disconnected = d; } + +private: + DWORD objid; + bool disconnected; + + BYTE data[SIZE]; +}; + +// +--------------------------------------------------------------------+ + +class NetDisconnect : public NetData +{ +public: + static const char* TYPENAME() { return "NetDisconnect"; } + + NetDisconnect() { } + + enum { TYPE=NET_DISCONNECT, SIZE=2 }; + + virtual BYTE* Pack(); + virtual bool Unpack(const BYTE* data); + virtual int Type() const { return TYPE; } + virtual int Length() const { return SIZE; } + +private: + BYTE data[SIZE]; +}; + +// +--------------------------------------------------------------------+ + +class NetObjDamage : public NetData +{ +public: + static const char* TYPENAME() { return "NetObjDamage"; } + + NetObjDamage() : objid(0), damage(0), shotid(0) { } + + enum { TYPE=NET_OBJ_DAMAGE, SIZE=12 }; + + virtual BYTE* Pack(); + virtual bool Unpack(const BYTE* data); + virtual int Type() const { return TYPE; } + virtual int Length() const { return SIZE; } + + virtual DWORD GetObjID() const { return objid; } + virtual void SetObjID(DWORD o) { objid = o; } + + virtual DWORD GetShotID() const { return shotid; } + virtual void SetShotID(DWORD o) { shotid = o; } + + float GetDamage() const { return damage; } + void SetDamage(float d) { damage = d; } + +private: + DWORD objid; + float damage; + DWORD shotid; + + BYTE data[SIZE]; +}; + +// +--------------------------------------------------------------------+ + +class NetObjKill : public NetData +{ +public: + static const char* TYPENAME() { return "NetObjKill"; } + + NetObjKill() : objid(0), kill_id(0), killtype(0), respawn(false), deck(0) { } + + enum { TYPE=NET_OBJ_KILL, SIZE=24, + KILL_MISC = 0, + KILL_PRIMARY, + KILL_SECONDARY, + KILL_COLLISION, + KILL_CRASH, + KILL_DOCK + }; + + virtual BYTE* Pack(); + virtual bool Unpack(const BYTE* data); + virtual int Type() const { return TYPE; } + virtual int Length() const { return SIZE; } + + virtual DWORD GetObjID() const { return objid; } + virtual void SetObjID(DWORD o) { objid = o; } + virtual DWORD GetKillerID() const { return kill_id; } + virtual void SetKillerID(DWORD o) { kill_id = o; } + virtual int GetKillType() const { return killtype;} + virtual void SetKillType(int t) { killtype = t; } + virtual bool GetRespawn() const { return respawn; } + virtual void SetRespawn(bool r) { respawn = r; } + virtual Point GetRespawnLoc() const { return loc; } + virtual void SetRespawnLoc(const Point& p) { loc = p; } + virtual int GetFlightDeck() const { return deck; } + virtual void SetFlightDeck(int n) { deck = n; } + +private: + DWORD objid; + DWORD kill_id; + int killtype; + bool respawn; + Point loc; + int deck; + + BYTE data[SIZE]; +}; + +// +--------------------------------------------------------------------+ + +class NetObjHyper : public NetData +{ +public: + static const char* TYPENAME() { return "NetObjHyper"; } + + NetObjHyper() : objid(0), fc_src(0), fc_dst(0), transtype(0) { } + + enum { TYPE=NET_OBJ_HYPER, SIZE=56 }; + + virtual BYTE* Pack(); + virtual bool Unpack(const BYTE* data); + virtual int Type() const { return TYPE; } + virtual int Length() const { return SIZE; } + + virtual DWORD GetObjID() const { return objid; } + virtual void SetObjID(DWORD id) { objid = id; } + + const Point& GetLocation() const { return location; } + const Text& GetRegion() const { return region; } + DWORD GetFarcaster1() const { return fc_src; } + DWORD GetFarcaster2() const { return fc_dst; } + int GetTransitionType() const { return transtype; } + + void SetLocation(const Point& loc) { location = loc; } + void SetRegion(const char* rgn) { region = rgn; } + void SetFarcaster1(DWORD f) { fc_src = f; } + void SetFarcaster2(DWORD f) { fc_dst = f; } + void SetTransitionType(int t) { transtype = t; } + +private: + DWORD objid; + Point location; + Text region; + DWORD fc_src; + DWORD fc_dst; + int transtype; + + BYTE data[SIZE]; +}; + +// +--------------------------------------------------------------------+ + +class NetObjTarget : public NetData +{ +public: + static const char* TYPENAME() { return "NetObjTarget"; } + + NetObjTarget() : objid(0), tgtid(0), sysix(0) { } + + enum { TYPE=NET_OBJ_TARGET, SIZE=7 }; + + virtual BYTE* Pack(); + virtual bool Unpack(const BYTE* data); + virtual int Type() const { return TYPE; } + virtual int Length() const { return SIZE; } + + virtual DWORD GetObjID() const { return objid; } + virtual void SetObjID(DWORD o) { objid = o; } + + DWORD GetTgtID() const { return tgtid; } + void SetTgtID(DWORD o) { tgtid = o; } + int GetSubtarget() const { return sysix; } + void SetSubtarget(int n) { sysix = n; } + +private: + DWORD objid; + DWORD tgtid; + int sysix; + + BYTE data[SIZE]; +}; + +// +--------------------------------------------------------------------+ + +class NetObjEmcon : public NetData +{ +public: + static const char* TYPENAME() { return "NetObjEmcon"; } + + NetObjEmcon() : objid(0), emcon(0) { } + + enum { TYPE=NET_OBJ_EMCON, SIZE=5 }; + + virtual BYTE* Pack(); + virtual bool Unpack(const BYTE* data); + virtual int Type() const { return TYPE; } + virtual int Length() const { return SIZE; } + + virtual DWORD GetObjID() const { return objid; } + virtual void SetObjID(DWORD o) { objid = o; } + + int GetEMCON() const { return emcon; } + void SetEMCON(int n) { emcon = n; } + +private: + DWORD objid; + int emcon; + + BYTE data[SIZE]; +}; + +// +--------------------------------------------------------------------+ + +class NetSysDamage : public NetData +{ +public: + static const char* TYPENAME() { return "NetSysDamage"; } + + NetSysDamage() : objid(0), sysix(-1), dmgtype(0), damage(0) { } + + enum { TYPE=NET_SYS_DAMAGE, SIZE=12 }; + + virtual BYTE* Pack(); + virtual bool Unpack(const BYTE* data); + virtual int Type() const { return TYPE; } + virtual int Length() const { return SIZE; } + + virtual DWORD GetObjID() const { return objid; } + virtual void SetObjID(DWORD o) { objid = o; } + + int GetSystem() const { return sysix; } + void SetSystem(int n) { sysix = n; } + BYTE GetDamageType() const { return dmgtype; } + void SetDamageType(BYTE t) { dmgtype = t; } + double GetDamage() const { return damage; } + void SetDamage(double d) { damage = d; } + +private: + DWORD objid; + int sysix; + BYTE dmgtype; + double damage; + + BYTE data[SIZE]; +}; + +// +--------------------------------------------------------------------+ + +class NetSysStatus : public NetData +{ +public: + static const char* TYPENAME() { return "NetSysStatus"; } + + NetSysStatus() : objid(0), sysix(-1), status(0), power(0), reactor(0), + avail(1) { } + + enum { TYPE=NET_SYS_STATUS, SIZE=12 }; + + virtual BYTE* Pack(); + virtual bool Unpack(const BYTE* data); + virtual int Type() const { return TYPE; } + virtual int Length() const { return SIZE; } + + virtual DWORD GetObjID() const { return objid; } + virtual void SetObjID(DWORD o) { objid = o; } + + int GetSystem() const { return sysix; } + void SetSystem(int n) { sysix = n; } + BYTE GetStatus() const { return status; } + void SetStatus(BYTE s) { status = s; } + int GetPower() const { return power; } + void SetPower(int n) { power = n; } + int GetReactor() const { return reactor; } + void SetReactor(int n) { reactor = n; } + double GetAvailability() const { return avail; } + void SetAvailablility(double a) { avail = a; } + +private: + DWORD objid; + int sysix; + int status; + int power; + int reactor; + double avail; + + BYTE data[SIZE]; +}; + +// +--------------------------------------------------------------------+ + +class NetWepTrigger : public NetData +{ +public: + static const char* TYPENAME() { return "NetWepTrigger"; } + + NetWepTrigger() : objid(0), tgtid(0), sysix(-1), index(0), count(0), + decoy(false), probe(false) { } + + enum { TYPE=NET_WEP_TRIGGER, SIZE=10 }; + + virtual BYTE* Pack(); + virtual bool Unpack(const BYTE* data); + virtual int Type() const { return TYPE; } + virtual int Length() const { return SIZE; } + + virtual DWORD GetObjID() const { return objid; } + virtual void SetObjID(DWORD o) { objid = o; } + + DWORD GetTgtID() const { return tgtid; } + void SetTgtID(DWORD o) { tgtid = o; } + int GetSubtarget() const { return sysix; } + void SetSubtarget(int n) { sysix = n; } + int GetIndex() const { return index; } + void SetIndex(int n) { index = n; } + int GetCount() const { return count; } + void SetCount(int n) { count = n; } + bool GetDecoy() const { return decoy; } + void SetDecoy(bool d) { decoy = d; } + bool GetProbe() const { return probe; } + void SetProbe(bool p) { probe = p; } + +private: + DWORD objid; + DWORD tgtid; + int sysix; + int index; + int count; + bool decoy; + bool probe; + + BYTE data[SIZE]; +}; + +// +--------------------------------------------------------------------+ + +class NetWepRelease : public NetData +{ +public: + static const char* TYPENAME() { return "NetWepRelease"; } + + NetWepRelease() : objid(0), tgtid(0), wepid(0), sysix(-1), index(0), + decoy(false), probe(false) { } + + enum { TYPE=NET_WEP_RELEASE, SIZE=11 }; + + virtual BYTE* Pack(); + virtual bool Unpack(const BYTE* data); + virtual int Type() const { return TYPE; } + virtual int Length() const { return SIZE; } + + virtual DWORD GetObjID() const { return objid; } + virtual void SetObjID(DWORD o) { objid = o; } + + DWORD GetTgtID() const { return tgtid; } + void SetTgtID(DWORD o) { tgtid = o; } + int GetSubtarget() const { return sysix; } + void SetSubtarget(int n) { sysix = n; } + DWORD GetWepID() const { return wepid; } + void SetWepID(DWORD o) { wepid = o; } + int GetIndex() const { return index; } + void SetIndex(int n) { index = n; } + bool GetDecoy() const { return decoy; } + void SetDecoy(bool d) { decoy = d; } + bool GetProbe() const { return probe; } + void SetProbe(bool p) { probe = p; } + +private: + DWORD objid; + DWORD tgtid; + DWORD wepid; + int sysix; + int index; + bool decoy; + bool probe; + + BYTE data[SIZE]; +}; + +// +--------------------------------------------------------------------+ + +class NetWepDestroy : public NetData +{ +public: + static const char* TYPENAME() { return "NetWepDestroy"; } + + NetWepDestroy() : objid(0) { } + + enum { TYPE=NET_WEP_DESTROY, SIZE=4 }; + + virtual BYTE* Pack(); + virtual bool Unpack(const BYTE* data); + virtual int Type() const { return TYPE; } + virtual int Length() const { return SIZE; } + + virtual DWORD GetObjID() const { return objid; } + virtual void SetObjID(DWORD o) { objid = o; } + +private: + DWORD objid; + + BYTE data[SIZE]; +}; + +// +--------------------------------------------------------------------+ + +class RadioMessage; +class NetCommMsg : public NetData +{ +public: + static const char* TYPENAME() { return "NetCommMsg"; } + + NetCommMsg() : objid(0), radio_message(0), length(0) { } + virtual ~NetCommMsg(); + + enum { TYPE=NET_COMM_MESSAGE, SIZE=200 }; + + virtual BYTE* Pack(); + virtual bool Unpack(const BYTE* data); + virtual int Type() const { return TYPE; } + virtual int Length() const { return length; } + + virtual DWORD GetObjID() const { return objid; } + virtual void SetObjID(DWORD o) { objid = o; } + + RadioMessage* GetRadioMessage() { return radio_message; } + void SetRadioMessage(RadioMessage* m); + +private: + DWORD objid; + RadioMessage* radio_message; + + int length; + BYTE data[SIZE]; +}; + +// +--------------------------------------------------------------------+ + +class NetChatMsg : public NetData +{ +public: + static const char* TYPENAME() { return "NetChatMsg"; } + + NetChatMsg() : dstid(0), length(0) { } + + enum { TYPE=NET_CHAT_MESSAGE, SIZE=210, MAX_CHAT=160, HDR_LEN=4, NAME_LEN=32 }; + + virtual BYTE* Pack(); + virtual bool Unpack(const BYTE* data); + virtual int Type() const { return TYPE; } + virtual int Length() const { return length; } + + virtual DWORD GetDstID() const { return dstid; } + virtual void SetDstID(DWORD d) { dstid = d; } + const Text& GetName() const { return name; } + void SetName(const char* m) { name = m; } + const Text& GetText() const { return text; } + void SetText(const char* m) { text = m; } + +private: + DWORD dstid; + Text name; + Text text; + + int length; + BYTE data[SIZE]; +}; + +// +--------------------------------------------------------------------+ + +class NetElemRequest : public NetData +{ +public: + static const char* TYPENAME() { return "NetElemRequest"; } + + NetElemRequest(); + + enum { TYPE=NET_ELEM_REQUEST, SIZE=64, NAME_LEN=32 }; + + virtual BYTE* Pack(); + virtual bool Unpack(const BYTE* data); + virtual int Type() const { return TYPE; } + virtual int Length() const { return SIZE; } + + const Text& GetName() const { return name; } + void SetName(const char* m) { name = m; } + +private: + Text name; + + BYTE data[SIZE]; +}; + +// +--------------------------------------------------------------------+ + +class NetElemCreate : public NetData +{ +public: + static const char* TYPENAME() { return "NetElemCreate"; } + + NetElemCreate(); + + enum { TYPE=NET_ELEM_CREATE, SIZE=192, NAME_LEN=32 }; + + virtual BYTE* Pack(); + virtual bool Unpack(const BYTE* data); + virtual int Type() const { return TYPE; } + virtual int Length() const { return SIZE; } + + const Text& GetName() const { return name; } + void SetName(const char* m) { name = m; } + const Text& GetCommander() const { return commander; } + void SetCommander(const char* m) { commander = m; } + const Text& GetObjective() const { return objective; } + void SetObjective(const char* m) { objective = m; } + const Text& GetCarrier() const { return carrier; } + void SetCarrier(const char* m) { carrier = m; } + + int GetIFF() const { return iff; } + void SetIFF(int n) { iff = n; } + int GetType() const { return type; } + void SetType(int n) { type = n; } + int GetIntel() const { return intel; } + void SetIntel(int n) { intel = n; } + int GetObjCode() const { return obj_code; } + void SetObjCode(int n) { obj_code = n; } + int GetSquadron() const { return squadron; } + void SetSquadron(int n) { squadron = n; } + + int* GetLoadout() { return load; } + void SetLoadout(int* n); + int* GetSlots() { return slots; } + void SetSlots(int* n); + + bool GetAlert() const { return alert; } + void SetAlert(bool a) { alert = a; } + + bool GetInFlight() const { return in_flight; } + void SetInFlight(bool f) { in_flight = f; } + +private: + Text name; + int iff; + int type; + int intel; + int obj_code; + int squadron; + + Text commander; + Text objective; + Text carrier; + + int load[16]; + int slots[4]; + bool alert; + bool in_flight; + + BYTE data[SIZE]; +}; + +// +--------------------------------------------------------------------+ + +class NetShipLaunch : public NetData +{ +public: + static const char* TYPENAME() { return "NetShipLaunch"; } + + NetShipLaunch() : objid(0), squadron(0), slot(0) { } + + enum { TYPE=NET_SHIP_LAUNCH, SIZE=16 }; + + virtual BYTE* Pack(); + virtual bool Unpack(const BYTE* data); + virtual int Type() const { return TYPE; } + virtual int Length() const { return SIZE; } + + virtual DWORD GetObjID() const { return objid; } + virtual int GetSquadron() const { return squadron; } + virtual int GetSlot() const { return slot; } + + virtual void SetObjID(DWORD o) { objid = o; } + virtual void SetSquadron(int s) { squadron = s; } + virtual void SetSlot(int s) { slot = s; } + +private: + DWORD objid; + int squadron; + int slot; + + BYTE data[SIZE]; +}; + +// +--------------------------------------------------------------------+ + +class Instruction; + +class NetNavData : public NetData +{ +public: + static const char* TYPENAME() { return "NetNavData"; } + + NetNavData(); + virtual ~NetNavData(); + + enum { TYPE=NET_NAV_DATA, SIZE=144, NAME_LEN=32 }; + + virtual BYTE* Pack(); + virtual bool Unpack(const BYTE* data); + virtual int Type() const { return TYPE; } + virtual int Length() const { return SIZE; } + + virtual DWORD GetObjID() const { return objid; } + bool IsAdd() const { return create; } + bool IsEdit() const { return !create; } + const Text& GetElem() const { return elem; } + int GetIndex() const { return index; } + Instruction* GetNavPoint() const { return navpoint; } + + virtual void SetObjID(DWORD o) { objid = o; } + void SetAdd(bool b) { create = b; } + void SetElem(const char* e) { elem = e; } + void SetIndex(int n) { index = n; } + void SetNavPoint(Instruction* n); + +private: + DWORD objid; + bool create; + Text elem; + int index; + Instruction* navpoint; + + BYTE data[SIZE]; +}; + +// +--------------------------------------------------------------------+ + +class NetNavDelete : public NetData +{ +public: + static const char* TYPENAME() { return "NetNavDelete"; } + + NetNavDelete() : objid(0), index(0) { } + + enum { TYPE=NET_NAV_DELETE, SIZE=40 }; + + virtual BYTE* Pack(); + virtual bool Unpack(const BYTE* data); + virtual int Type() const { return TYPE; } + virtual int Length() const { return SIZE; } + + virtual DWORD GetObjID() const { return objid; } + const Text& GetElem() const { return elem; } + int GetIndex() const { return index; } + + virtual void SetObjID(DWORD o) { objid = o; } + void SetElem(const char* e) { elem = e; } + void SetIndex(int n) { index = n; } + +private: + DWORD objid; + Text elem; + int index; + + BYTE data[SIZE]; +}; + +// +--------------------------------------------------------------------+ + +class NetSelfDestruct : public NetData +{ +public: + static const char* TYPENAME() { return "NetSelfDestruct"; } + + NetSelfDestruct() : objid(0), damage(0) { } + + enum { TYPE=NET_SELF_DESTRUCT, SIZE=8 }; + + virtual BYTE* Pack(); + virtual bool Unpack(const BYTE* data); + virtual int Type() const { return TYPE; } + virtual int Length() const { return SIZE; } + + virtual DWORD GetObjID() const { return objid; } + virtual void SetObjID(DWORD o) { objid = o; } + + float GetDamage() const { return damage; } + void SetDamage(float d) { damage = d; } + +private: + DWORD objid; + float damage; + + BYTE data[SIZE]; +}; + + +// +--------------------------------------------------------------------+ + +#endif NetData_h + diff --git a/Stars45/NetFileServlet.cpp b/Stars45/NetFileServlet.cpp new file mode 100644 index 0000000..803347b --- /dev/null +++ b/Stars45/NetFileServlet.cpp @@ -0,0 +1,118 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetFileServlet.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + HTTP Servlet for File Transfer +*/ + + +#include "MemDebug.h" +#include "NetFileServlet.h" +#include "NetAdminServer.h" +#include "NetLayer.h" + +#include "DataLoader.h" + +// +-------------------------------------------------------------------+ + +bool +NetFileServlet::DoGet(HttpRequest& request, HttpResponse& response) +{ + if (!CheckUser(request, response)) + return true; + + Text content; + Text path = request.GetParam("path"); + Text name = request.GetParam("name"); + + if (name.length()) { + BYTE* buffer = 0; + DataLoader* loader = DataLoader::GetLoader(); + + if (loader) { + loader->SetDataPath(path); + int len = loader->LoadBuffer(name, buffer); + + if (len) { + content = Text((const char*) buffer, len); + } + + loader->ReleaseBuffer(buffer); + loader->SetDataPath(0); + } + } + + response.SetStatus(HttpResponse::SC_OK); + response.AddHeader("MIME-Version", "1.0"); + response.AddHeader("Cache-Control", "no-cache"); + response.AddHeader("Expires", "-1"); + response.AddHeader("Content-Type", "text/plain"); + response.SetContent(content); + + return true; +} + +// +-------------------------------------------------------------------+ + +bool +NetWebServlet::DoGet(HttpRequest& request, HttpResponse& response) +{ + Text content; + Text name = request.URI(); + bool found = false; + + if (name.length() > 4) { + char filename[256]; + strcpy(filename, name.data() + 1); // skip leading '/' + + FILE* f = ::fopen(filename, "rb"); + + if (f) { + ::fseek(f, 0, SEEK_END); + int len = ftell(f); + ::fseek(f, 0, SEEK_SET); + + BYTE* buf = new(__FILE__,__LINE__) BYTE[len]; + + ::fread(buf, len, 1, f); + ::fclose(f); + + content = Text((const char*) buf, len); + delete [] buf; + + found = true; + ::Print("weblog: 200 OK %s %d bytes\n", name.data(), len); + } + else { + ::Print("weblog: 404 Not Found %s\n", name.data()); + } + } + + if (found) { + Text content_type = "text/plain"; + + if (name.contains(".gif")) + content_type = "image/gif"; + else if (name.contains(".jpg")) + content_type = "image/jpeg"; + else if (name.contains(".htm")) + content_type = "text/html"; + + response.SetStatus(HttpResponse::SC_OK); + response.AddHeader("MIME-Version", "1.0"); + response.AddHeader("Content-Type", content_type); + response.SetContent(content); + } + else { + response.SetStatus(HttpResponse::SC_NOT_FOUND); + } + + return true; +} diff --git a/Stars45/NetFileServlet.h b/Stars45/NetFileServlet.h new file mode 100644 index 0000000..c7a3273 --- /dev/null +++ b/Stars45/NetFileServlet.h @@ -0,0 +1,50 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetFileServlet.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + HTTP Servlet for File Transfer +*/ + + +#ifndef NetFileServlet_h +#define NetFileServlet_h + +#include "Types.h" +#include "NetAdminServer.h" +#include "NetUser.h" + +// +-------------------------------------------------------------------+ + +class Campaign; +class File; + +// +-------------------------------------------------------------------+ + +class NetFileServlet : public NetAdminServlet +{ +public: + NetFileServlet() { } + virtual ~NetFileServlet() { } + + virtual bool DoGet(HttpRequest& request, HttpResponse& response); +}; + +// +-------------------------------------------------------------------+ + +class NetWebServlet : public NetAdminServlet +{ +public: + NetWebServlet() { } + virtual ~NetWebServlet() { } + + virtual bool DoGet(HttpRequest& request, HttpResponse& response); +}; + +#endif NetFileServlet_h \ No newline at end of file diff --git a/Stars45/NetGame.cpp b/Stars45/NetGame.cpp new file mode 100644 index 0000000..2e0e8eb --- /dev/null +++ b/Stars45/NetGame.cpp @@ -0,0 +1,330 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetGame.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Network Game Manager class +*/ + +#include "MemDebug.h" +#include "NetGame.h" +#include "NetGameClient.h" +#include "NetGameServer.h" +#include "NetClientConfig.h" +#include "NetServerConfig.h" +#include "NetPlayer.h" + +#include "NetMsg.h" +#include "NetData.h" +#include "NetLayer.h" + +#include "Player.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "Sim.h" +#include "Element.h" +#include "HUDView.h" + +#include "NetHost.h" +#include "Game.h" +#include "Light.h" + +// +--------------------------------------------------------------------+ + +const int MAX_NET_FPS = 20; +const int MIN_NET_FRAME = 1000 / MAX_NET_FPS; + +const DWORD SHIP_ID_START = 0x0010; +const DWORD SHOT_ID_START = 0x0400; + +static NetGame* netgame = 0; + +static DWORD ship_id_key = SHIP_ID_START; +static DWORD shot_id_key = SHOT_ID_START; + +static long start_time = 0; + +// +--------------------------------------------------------------------+ + +NetGame::NetGame() + : objid(0), netid(0), link(0), local_player(0), last_send_time(0), active(true) +{ + netgame = this; + sim = Sim::GetSim(); + + ship_id_key = SHIP_ID_START; + shot_id_key = SHOT_ID_START; + + if (sim) + local_player = sim->GetPlayerShip(); + + Player* player = Player::GetCurrentPlayer(); + if (player) { + player_name = player->Name(); + player_pass = player->Password(); + } + + start_time = NetLayer::GetUTC(); +} + +NetGame::~NetGame() +{ + netgame = 0; + local_player = 0; + players.destroy(); + + if (link) { + double delta = fabs(NetLayer::GetUTC() - start_time); + double bandwidth = 10.0 * (link->GetBytesSent() + link->GetBytesRecv()) / delta; + double recvrate = link->GetPacketsRecv() / delta; + + Print("NetGame Stats\n-------------\n"); + Print(" packets sent %d\n", link->GetPacketsSent()); + Print(" packets recv %d\n", link->GetPacketsRecv()); + Print(" bytes sent %d\n", link->GetBytesSent()); + Print(" bytes recv %d\n", link->GetBytesRecv()); + Print(" retries %d\n", link->GetRetries()); + Print(" drops %d\n", link->GetDrops()); + Print(" avg lag %d msec\n", link->GetLag()); + Print(" time %d sec\n", (int) delta); + Print(" bandwidth %d bps\n", (int) bandwidth); + Print(" packet rate %d pps in\n\n", (int) recvrate); + + delete link; + } +} + +// +--------------------------------------------------------------------+ + +NetGame* +NetGame::Create() +{ + if (!netgame) { + if (NetServerConfig::GetInstance()) + netgame = new(__FILE__,__LINE__) NetGameServer; + + else if (NetClientConfig::GetInstance() && NetClientConfig::GetInstance()->GetSelectedServer()) + netgame = new(__FILE__,__LINE__) NetGameClient; + } + + return netgame; +} + +NetGame* +NetGame::GetInstance() +{ + return netgame; +} + +DWORD +NetGame::GetObjID() const +{ + if (local_player) + return local_player->GetObjID(); + + return 0; +} + +DWORD +NetGame::GetNextObjID(int type) +{ + if (type == SHIP) { + if (ship_id_key >= SHOT_ID_START) + ship_id_key = SHIP_ID_START; + + return ship_id_key++; + } + + else if (type == SHOT) { + if (shot_id_key >= 0xFFFE) + shot_id_key = SHOT_ID_START; + + return shot_id_key++; + } + + return 0; +} + +// +--------------------------------------------------------------------+ + +void +NetGame::ExecFrame() +{ + Send(); + Recv(); +} + +void +NetGame::Recv() +{ + NetMsg* msg = link->GetMessage(); + + while (msg) { + if (active) { + // For Debug Convenience: + // NetPlayer* player = FindPlayerByNetID(msg->NetID()); + + switch (msg->Type()) { + case NET_JOIN_REQUEST: DoJoinRequest(msg); break; + case NET_JOIN_ANNOUNCE: DoJoinAnnounce(msg); break; + case NET_QUIT_REQUEST: DoQuitRequest(msg); break; + case NET_QUIT_ANNOUNCE: DoQuitAnnounce(msg); break; + case NET_GAME_OVER: DoGameOver(msg); break; + case NET_DISCONNECT: DoDisconnect(msg); break; + + case NET_OBJ_LOC: DoObjLoc(msg); break; + case NET_OBJ_DAMAGE: DoObjDamage(msg); break; + case NET_OBJ_KILL: DoObjKill(msg); break; + case NET_OBJ_SPAWN: DoObjSpawn(msg); break; + case NET_OBJ_HYPER: DoObjHyper(msg); break; + case NET_OBJ_TARGET: DoObjTarget(msg); break; + case NET_OBJ_EMCON: DoObjEmcon(msg); break; + case NET_SYS_DAMAGE: DoSysDamage(msg); break; + case NET_SYS_STATUS: DoSysStatus(msg); break; + + case NET_ELEM_CREATE: DoElemCreate(msg); break; + case NET_ELEM_REQUEST: DoElemRequest(msg); break; + case NET_SHIP_LAUNCH: DoShipLaunch(msg); break; + case NET_NAV_DATA: DoNavData(msg); break; + case NET_NAV_DELETE: DoNavDelete(msg); break; + + case NET_WEP_TRIGGER: DoWepTrigger(msg); break; + case NET_WEP_RELEASE: DoWepRelease(msg); break; + case NET_WEP_DESTROY: DoWepDestroy(msg); break; + + case NET_COMM_MESSAGE: DoCommMsg(msg); break; + case NET_CHAT_MESSAGE: DoChatMsg(msg); break; + case NET_SELF_DESTRUCT: DoSelfDestruct(msg); break; + } + } + + delete msg; + msg = link->GetMessage(); + } +} + +// +--------------------------------------------------------------------+ + +void +NetGame::Send() +{ +} + +int +NetGame::NumPlayers() +{ + if (netgame) { + int num_players = netgame->players.size(); + + if (netgame->local_player) + num_players++; + + return num_players; + } + + return 0; +} + +NetPlayer* +NetGame::FindPlayerByName(const char* name) +{ + for (int i = 0; i < players.size(); i++) { + NetPlayer* p = players[i]; + + if (!strcmp(p->Name(), name)) + return p; + } + + return 0; +} + +NetPlayer* +NetGame::FindPlayerByNetID(DWORD netid) +{ + for (int i = 0; i < players.size(); i++) { + NetPlayer* p = players[i]; + + if (p->GetNetID() == netid) + return p; + } + + return 0; +} + +NetPlayer* +NetGame::FindPlayerByObjID(DWORD objid) +{ + for (int i = 0; i < players.size(); i++) { + NetPlayer* p = players[i]; + + if (p->GetObjID() == objid) + return p; + } + + return 0; +} + +Ship* +NetGame::FindShipByObjID(DWORD objid) +{ + if (sim) + return sim->FindShipByObjID(objid); + + return 0; +} + +Shot* +NetGame::FindShotByObjID(DWORD objid) +{ + if (sim) + return sim->FindShotByObjID(objid); + + return 0; +} + +NetPeer* +NetGame::GetPeer(NetPlayer* player) +{ + if (player && link) { + return link->FindPeer(player->GetNetID()); + } + + return 0; +} + +// +--------------------------------------------------------------------+ + +void +NetGame::Respawn(DWORD objid, Ship* spawn) +{ +} + +// +--------------------------------------------------------------------+ + +bool +NetGame::IsNetGame() +{ + return netgame != 0; +} + +bool +NetGame::IsNetGameClient() +{ + if (netgame) + return netgame->IsClient(); + return false; +} + +bool +NetGame::IsNetGameServer() +{ + if (netgame) + return netgame->IsServer(); + return false; +} diff --git a/Stars45/NetGame.h b/Stars45/NetGame.h new file mode 100644 index 0000000..249b695 --- /dev/null +++ b/Stars45/NetGame.h @@ -0,0 +1,127 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetGame.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Network Game Manager class +*/ + +#ifndef NetGame_h +#define NetGame_h + +#include "Types.h" +#include "Geometry.h" +#include "NetLink.h" +#include "Director.h" +#include "List.h" + +// +--------------------------------------------------------------------+ + +class Sim; +class Ship; +class Shot; +class NetData; +class NetMsg; +class NetPlayer; + +// +--------------------------------------------------------------------+ + +class NetGame +{ +public: + static const char* TYPENAME() { return "NetGame"; } + + enum { SHIP, SHOT }; + + NetGame(); + virtual ~NetGame(); + + virtual bool IsClient() const { return false; } + virtual bool IsServer() const { return false; } + virtual bool IsActive() const { return active; } + + virtual DWORD GetNetID() const { return netid; } + virtual DWORD GetObjID() const; + + virtual void ExecFrame(); + virtual void Recv(); + virtual void Send(); + + virtual void SendData(NetData* data) { } + + virtual NetPlayer* FindPlayerByName(const char* name); + virtual NetPlayer* FindPlayerByNetID(DWORD netid); + virtual NetPlayer* FindPlayerByObjID(DWORD objid); + virtual Ship* FindShipByObjID(DWORD objid); + virtual Shot* FindShotByObjID(DWORD objid); + + virtual NetPeer* GetPeer(NetPlayer* player); + + virtual void Respawn(DWORD objid, Ship* spawn); + + static NetGame* Create(); + static NetGame* GetInstance(); + static bool IsNetGame(); + static bool IsNetGameClient(); + static bool IsNetGameServer(); + static int NumPlayers(); + + static DWORD GetNextObjID(int type=SHIP); + +protected: + virtual void DoJoinRequest(NetMsg* msg) { } + virtual void DoJoinAnnounce(NetMsg* msg) { } + virtual void DoQuitRequest(NetMsg* msg) { } + virtual void DoQuitAnnounce(NetMsg* msg) { } + virtual void DoGameOver(NetMsg* msg) { } + virtual void DoDisconnect(NetMsg* msg) { } + + virtual void DoObjLoc(NetMsg* msg) { } + virtual void DoObjDamage(NetMsg* msg) { } + virtual void DoObjKill(NetMsg* msg) { } + virtual void DoObjSpawn(NetMsg* msg) { } + virtual void DoObjHyper(NetMsg* msg) { } + virtual void DoObjTarget(NetMsg* msg) { } + virtual void DoObjEmcon(NetMsg* msg) { } + virtual void DoSysDamage(NetMsg* msg) { } + virtual void DoSysStatus(NetMsg* msg) { } + + virtual void DoElemCreate(NetMsg* msg) { } + virtual void DoElemRequest(NetMsg* msg) { } + virtual void DoShipLaunch(NetMsg* msg) { } + virtual void DoNavData(NetMsg* msg) { } + virtual void DoNavDelete(NetMsg* msg) { } + + virtual void DoWepTrigger(NetMsg* msg) { } + virtual void DoWepRelease(NetMsg* msg) { } + virtual void DoWepDestroy(NetMsg* msg) { } + + virtual void DoCommMsg(NetMsg* msg) { } + virtual void DoChatMsg(NetMsg* msg) { } + virtual void DoSelfDestruct(NetMsg* msg) { } + + List players; + NetLink* link; + + DWORD objid; + DWORD netid; + Ship* local_player; + Text player_name; + Text player_pass; + Ship* target; + Sim* sim; + bool active; + + DWORD last_send_time; +}; + +// +--------------------------------------------------------------------+ + +#endif NetGame_h + diff --git a/Stars45/NetGameClient.cpp b/Stars45/NetGameClient.cpp new file mode 100644 index 0000000..76fb527 --- /dev/null +++ b/Stars45/NetGameClient.cpp @@ -0,0 +1,1069 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetGameClient.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Network Game Manager class +*/ + +#include "MemDebug.h" +#include "NetGameClient.h" +#include "NetClientConfig.h" +#include "NetLobby.h" +#include "NetPlayer.h" +#include "NetData.h" +#include "NetUtil.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "Shield.h" +#include "Shot.h" +#include "Sim.h" +#include "SimEvent.h" +#include "Weapon.h" +#include "Element.h" +#include "Explosion.h" +#include "HUDView.h" +#include "RadioView.h" +#include "Instruction.h" +#include "Hangar.h" +#include "FlightDeck.h" +#include "Mission.h" + +#include "NetMsg.h" +#include "NetHost.h" +#include "NetLayer.h" +#include "NetPeer.h" + +#include "Game.h" +#include "Light.h" + +// +--------------------------------------------------------------------+ + +const int MAX_NET_FPS = 20; +const int MIN_NET_FRAME = 1000 / MAX_NET_FPS; + +const char* FormatGameTime(); + +// +--------------------------------------------------------------------+ + +NetGameClient::NetGameClient() + : server_id(0), join_req_time(0) +{ + Print("Constructing NetGameClient\n"); + + NetHost me; + Text server_name; + WORD port = 11101; + + NetClientConfig* ncc = NetClientConfig::GetInstance(); + if (ncc) { + NetServerInfo* info = ncc->GetSelectedServer(); + + if (info) { + server_name = info->hostname; + port = info->gameport; + } + } + + if (server_name.length() && port > 0) { + Print(" '%s' is a client of '%s'\n", me.Name(), server_name); + link = new(__FILE__,__LINE__) NetLink; + server_id = link->AddPeer(NetAddr(server_name, port)); + SendJoinRequest(); + } + else if (port == 0) { + Print(" '%s' invalid game port number %d\n", me.Name(), port); + } + else { + Print(" '%s' is a client without a server\n", me.Name()); + } +} + +NetGameClient::~NetGameClient() +{ + link->SendMessage(server_id, NET_QUIT_REQUEST, 0, 0, NetMsg::RELIABLE); + + // wait for message to be delivered before shutting down the link: + Sleep(500); + + join_backlog.destroy(); +} + +// +--------------------------------------------------------------------+ + +void +NetGameClient::SendJoinRequest() +{ + if ((NetLayer::GetTime() - join_req_time) < 5000) + return; + + if (local_player && local_player->GetElement() && server_id) { + Print(" sending join request - name: '%s' elem: '%s' index: %d\n", + player_name.data(), + local_player->GetElement()->Name().data(), + local_player->GetElementIndex()); + + NetJoinRequest join_req; + join_req.SetName(player_name); + join_req.SetPassword(player_pass); + join_req.SetElement(local_player->GetElement()->Name()); + join_req.SetIndex(local_player->GetElementIndex()); + + link->SendMessage(server_id, join_req.Pack(), NetJoinRequest::SIZE, NetMsg::RELIABLE); + join_req_time = NetLayer::GetTime(); + + local_player->SetNetObserver(true); + } +} + +// +--------------------------------------------------------------------+ + +void +NetGameClient::DoJoinRequest(NetMsg* msg) +{ + if (!msg) return; + + NetJoinRequest join_req; + if (join_req.Unpack(msg->Data())) { + Print("Client received Join Request from '%s'\n", join_req.GetName()); + } +} + +void +NetGameClient::DoJoinAnnounce(NetMsg* msg) +{ + if (!msg) return; + + Sim* sim = Sim::GetSim(); + if (!sim) return; + + NetJoinAnnounce* join_ann = new(__FILE__,__LINE__) NetJoinAnnounce; + bool saved = false; + + if (join_ann->Unpack(msg->Data())) { + DWORD nid = msg->NetID(); + DWORD oid = join_ann->GetObjID(); + Text name = join_ann->GetName(); + Text elem_name = join_ann->GetElement(); + Text region = join_ann->GetRegion(); + Point loc = join_ann->GetLocation(); + Point velocity = join_ann->GetVelocity(); + int index = join_ann->GetIndex(); + int shld_lvl = join_ann->GetShield(); + join_ann->SetNetID(nid); + Ship* ship = 0; + char ship_name[128]; + + strcpy(ship_name, Game::GetText("NetGameClient.no-ship").data()); + + if (local_player && player_name == name) { + HUDView::Message(Game::GetText("NetGameClient.local-accept"), name.data(), local_player->Name()); + + objid = oid; + netid = nid; + local_player->SetObjID(oid); + local_player->SetNetObserver(false); + Observe(local_player); + + SimRegion* rgn = local_player->GetRegion(); + if (rgn && region != rgn->Name()) { + SimRegion* dst = sim->FindRegion(region); + if (dst) dst->InsertObject(local_player); + } + + local_player->MoveTo(loc); + local_player->SetVelocity(velocity); + + Shield* shield = local_player->GetShield(); + if (shield) + shield->SetNetShieldLevel(shld_lvl); + } + else { + NetPlayer* remote_player = FindPlayerByObjID(oid); + if (remote_player) { + remote_player->SetName(name); + remote_player->SetObjID(oid); + + if (index > 0) + sprintf(ship_name, "%s %d", elem_name.data(), index); + else + sprintf(ship_name, "%s", elem_name.data()); + } + else { + Element* element = sim->FindElement(elem_name); + + if (element) { + ship = element->GetShip(index); + } + else { + Print("NetGameClient::DoJoinAnnounce() could not find elem %s for player '%s' objid %d\n", + elem_name.data(), name.data(), oid); + + NetUtil::SendElemRequest(elem_name.data()); + } + + if (!ship) { + // save it for later: + join_backlog.append(join_ann); + saved = true; + } + else { + strcpy(ship_name, ship->Name()); + + SimRegion* rgn = ship->GetRegion(); + if (rgn && region != rgn->Name()) { + SimRegion* dst = sim->FindRegion(region); + if (dst) dst->InsertObject(ship); + } + + ship->MoveTo(loc); + ship->SetVelocity(velocity); + + Shield* shield = ship->GetShield(); + if (shield) + shield->SetNetShieldLevel(shld_lvl); + + NetPlayer* remote_player = new(__FILE__,__LINE__) NetPlayer(nid); + remote_player->SetName(name); + remote_player->SetObjID(oid); + remote_player->SetShip(ship); + + players.append(remote_player); + + if (name == "Server A.I. Ship") { + Print("Remote Player '%s' has joined as '%s' with ID %d\n", name.data(), ship_name, oid); + } + else { + HUDView::Message(Game::GetText("NetGameClient.remote-join").data(), name.data(), ship_name); + } + } + } + } + } + + if (!saved) + delete join_ann; +} + +bool +NetGameClient::DoJoinBacklog(NetJoinAnnounce* join_ann) +{ + bool finished = false; + + if (!join_ann) + return finished; + + Sim* sim = Sim::GetSim(); + if (!sim) + return finished; + + DWORD nid = join_ann->GetNetID(); + DWORD oid = join_ann->GetObjID(); + Text name = join_ann->GetName(); + Text elem_name = join_ann->GetElement(); + Text region = join_ann->GetRegion(); + Point loc = join_ann->GetLocation(); + Point velocity = join_ann->GetVelocity(); + int index = join_ann->GetIndex(); + int shld_lvl = join_ann->GetShield(); + Ship* ship = 0; + char ship_name[128]; + + strcpy(ship_name, Game::GetText("NetGameClient.no-ship").data()); + + if (nid && oid) { + NetPlayer* remote_player = FindPlayerByObjID(oid); + if (remote_player) { + remote_player->SetName(name); + remote_player->SetObjID(oid); + + if (index > 0) + sprintf(ship_name, "%s %d", elem_name.data(), index); + else + sprintf(ship_name, "%s", elem_name.data()); + } + else { + Element* element = sim->FindElement(elem_name); + + if (element) { + ship = element->GetShip(index); + } + + if (ship) { + strcpy(ship_name, ship->Name()); + + SimRegion* rgn = ship->GetRegion(); + if (rgn && region != rgn->Name()) { + SimRegion* dst = sim->FindRegion(region); + if (dst) dst->InsertObject(ship); + } + + ship->MoveTo(loc); + ship->SetVelocity(velocity); + + Shield* shield = ship->GetShield(); + if (shield) + shield->SetNetShieldLevel(shld_lvl); + + NetPlayer* remote_player = new(__FILE__,__LINE__) NetPlayer(nid); + remote_player->SetName(name); + remote_player->SetObjID(oid); + remote_player->SetShip(ship); + + players.append(remote_player); + finished = true; + + if (name == "Server A.I. Ship") { + Print("NetGameClient::DoJoinBacklog() Remote Player '%s' has joined as '%s' with ID %d\n", name.data(), ship_name, oid); + } + else { + HUDView::Message(Game::GetText("NetGameClient.remote-join").data(), name.data(), ship_name); + } + } + } + } + + return finished; +} + + +void +NetGameClient::DoQuitRequest(NetMsg* msg) +{ + if (!msg) return; + + Print("Client received Quit Request from NetID: %08X\n", msg->NetID()); +} + +void +NetGameClient::DoQuitAnnounce(NetMsg* msg) +{ + if (!msg) return; + + NetQuitAnnounce quit_ann; + quit_ann.Unpack(msg->Data()); + + NetPlayer* player = FindPlayerByObjID(quit_ann.GetObjID()); + + if (player) { + NetPlayer* zombie = players.remove(player); + + // return remote ship to ship pool: + Ship* s = zombie->GetShip(); + if (s) { + s->SetNetworkControl(0); + zombie->SetShip(0); + } + + if (quit_ann.GetDisconnected()) + HUDView::Message(Game::GetText("NetGameClient.remote-discon").data(), zombie->Name()); + else + HUDView::Message(Game::GetText("NetGameClient.remote-quit").data(), zombie->Name()); + delete zombie; + } + else { + Print("Quit Announce for unknown player %08X disconnected = %d\n", msg->NetID(), quit_ann.GetDisconnected()); + } +} + +void +NetGameClient::DoGameOver(NetMsg* msg) +{ + if (!msg) return; + + HUDView::Message(Game::GetText("NetGameClient.game-over").data()); + players.destroy(); + active = false; +} + +void +NetGameClient::DoDisconnect(NetMsg* msg) +{ + if (!msg) return; + + HUDView::Message(Game::GetText("NetGameClient.discon-detect").data()); + HUDView::Message(Game::GetText("NetGameClient.please-exit").data()); + players.destroy(); + active = false; +} + +void +NetGameClient::DoObjLoc(NetMsg* msg) +{ + if (!msg) return; + + NetObjLoc obj_loc; + obj_loc.Unpack(msg->Data()); + + NetPlayer* player = FindPlayerByObjID(obj_loc.GetObjID()); + if (player) + player->DoObjLoc(&obj_loc); +} + +void +NetGameClient::DoObjDamage(NetMsg* msg) +{ + if (!msg) return; + + NetObjDamage obj_damage; + obj_damage.Unpack(msg->Data()); + + Ship* ship = FindShipByObjID(obj_damage.GetObjID()); + if (ship) { + Sim* sim = Sim::GetSim(); + Shot* shot = FindShotByObjID(obj_damage.GetShotID()); + const Ship* owner = 0; + const char* owner_name = "[NET]"; + + ship->InflictNetDamage(obj_damage.GetDamage(), shot); + + if (shot && sim) { + if (shot->Owner()) { + owner = shot->Owner(); + owner_name = owner->Name(); + } + + if (shot->IsMissile()) { + SimRegion* region = ship->GetRegion(); + float scale = ship->Design()->explosion_scale; + + if (scale <= 0) + scale = ship->Design()->scale; + + if (owner) { + const ShipDesign* owner_design = owner->Design(); + if (owner_design && owner_design->scale < scale) + scale = (float) owner_design->scale; + } + + sim->CreateExplosion(shot->Location(), Point(), Explosion::SHOT_BLAST, 20.0f * scale, scale, region); + } + + if (!shot->IsBeam()) { + if (owner) { + ShipStats* stats = ShipStats::Find(owner_name); + + if (stats) { + if (shot->IsPrimary()) + stats->AddGunHit(); + else if (shot->Damage() > 0) + stats->AddMissileHit(); + } + } + + shot->Destroy(); + } + } + } +} + +void +NetGameClient::DoObjKill(NetMsg* msg) +{ + if (!msg) return; + + NetObjKill obj_kill; + obj_kill.Unpack(msg->Data()); + + Ship* ship = FindShipByObjID(obj_kill.GetObjID()); + if (ship) { + Ship* killer = FindShipByObjID(obj_kill.GetKillerID()); + Text killer_name = Game::GetText("NetGameClient.unknown"); + + if (killer) + killer_name = killer->Name(); + + // log the kill: + switch (obj_kill.GetKillType()) { + default: + case NetObjKill::KILL_MISC: + Print("Ship '%s' destroyed (misc) (%s)\n", ship->Name(), FormatGameTime()); + break; + + case NetObjKill::KILL_PRIMARY: + case NetObjKill::KILL_SECONDARY: + Print("Ship '%s' killed by '%s' (%s)\n", ship->Name(), killer_name.data(), FormatGameTime()); + break; + + case NetObjKill::KILL_COLLISION: + Print("Ship '%s' killed in collision with '%s' (%s)\n", ship->Name(), killer_name.data(), FormatGameTime()); + break; + + case NetObjKill::KILL_CRASH: + Print("Ship '%s' destroyed (crash) (%s)\n", ship->Name(), FormatGameTime()); + + case NetObjKill::KILL_DOCK: + Print("Ship '%s' docked (%s)\n", ship->Name(), FormatGameTime()); + } + + // record the kill in the stats: + if (killer && obj_kill.GetKillType() != NetObjKill::KILL_DOCK) { + ShipStats* kstats = ShipStats::Find(killer->Name()); + if (kstats) { + if (obj_kill.GetKillType() == NetObjKill::KILL_PRIMARY) + kstats->AddEvent(SimEvent::GUNS_KILL, ship->Name()); + + else if (obj_kill.GetKillType() == NetObjKill::KILL_SECONDARY) + kstats->AddEvent(SimEvent::MISSILE_KILL, ship->Name()); + } + + if (killer && killer->GetIFF() != ship->GetIFF()) { + if (ship->GetIFF() > 0 || killer->GetIFF() > 1) + kstats->AddPoints(ship->Value()); + } + } + + ShipStats* killee = ShipStats::Find(ship->Name()); + if (killee) { + if (obj_kill.GetKillType() == NetObjKill::KILL_DOCK) + killee->AddEvent(SimEvent::DOCK, killer_name); + else + killee->AddEvent(SimEvent::DESTROYED, killer_name); + } + + if (obj_kill.GetKillType() == NetObjKill::KILL_DOCK) { + FlightDeck* deck = killer->GetFlightDeck(obj_kill.GetFlightDeck()); + sim->NetDockShip(ship, killer, deck); + } + else { + ship->InflictNetDamage(ship->Integrity()); + ship->DeathSpiral(); + + if (!obj_kill.GetRespawn()) + ship->SetRespawnCount(0); + else + ship->SetRespawnLoc(obj_kill.GetRespawnLoc()); + } + } + + // this shouldn't happen in practice, + // but if it does, this is what should be done: + else { + Shot* shot = FindShotByObjID(obj_kill.GetObjID()); + + if (shot) { + ::Print("NetGameClient::DoObjKill destroying shot '%s'\n", shot->Name()); + shot->Destroy(); + } + } +} + +void +NetGameClient::DoObjSpawn(NetMsg* msg) +{ +} + +void +NetGameClient::DoObjHyper(NetMsg* msg) +{ + if (!msg) return; + Print("Client received OBJ HYPER from NetID: %08x\n", msg->NetID()); + + NetObjHyper obj_hyper; + obj_hyper.Unpack(msg->Data()); + + NetPlayer* player = FindPlayerByObjID(obj_hyper.GetObjID()); + if (player && player->GetShip()) + player->DoObjHyper(&obj_hyper); +} + +void +NetGameClient::DoObjTarget(NetMsg* msg) +{ + if (!msg) return; + + NetObjTarget obj_target; + obj_target.Unpack(msg->Data()); + + NetPlayer* player = FindPlayerByObjID(obj_target.GetObjID()); + if (player) + player->DoObjTarget(&obj_target); +} + +void +NetGameClient::DoObjEmcon(NetMsg* msg) +{ + if (!msg) return; + + NetObjEmcon obj_emcon; + obj_emcon.Unpack(msg->Data()); + + NetPlayer* player = FindPlayerByObjID(obj_emcon.GetObjID()); + if (player) + player->DoObjEmcon(&obj_emcon); +} + +// +--------------------------------------------------------------------+ + +void +NetGameClient::DoSysDamage(NetMsg* msg) +{ + if (!msg) return; + + NetSysDamage sys_damage; + sys_damage.Unpack(msg->Data()); + + NetPlayer* player = FindPlayerByObjID(sys_damage.GetObjID()); + if (player && player->GetShip()) + player->DoSysDamage(&sys_damage); +} + +void +NetGameClient::DoSysStatus(NetMsg* msg) +{ + if (!msg) return; + + NetSysStatus sys_status; + sys_status.Unpack(msg->Data()); + + NetPlayer* player = FindPlayerByObjID(sys_status.GetObjID()); + if (player && player->GetShip()) + player->DoSysStatus(&sys_status); +} + +// +--------------------------------------------------------------------+ + +void +NetGameClient::DoElemCreate(NetMsg* msg) +{ + if (!msg) return; + + NetElemCreate elem_create; + elem_create.Unpack(msg->Data()); + + const char* elem_name = elem_create.GetName().data(); + + ::Print("NetGameClient::DoElemCreate name: %s iff: %d type %s\n", + elem_name, + elem_create.GetIFF(), + Mission::RoleName(elem_create.GetType())); + + Sim* sim = Sim::GetSim(); + Element* elem = sim->FindElement(elem_name); + if (elem) { + ::Print(" element '%' already exists - ignored\n", elem_name); + return; + } + + elem = sim->CreateElement(elem_name, + elem_create.GetIFF(), + elem_create.GetType()); + + int* load = elem_create.GetLoadout(); + int* slots = elem_create.GetSlots(); + int squadron = elem_create.GetSquadron(); + int code = elem_create.GetObjCode(); + Text target = elem_create.GetObjective(); + bool alert = elem_create.GetAlert(); + bool active = elem_create.GetInFlight(); + + elem->SetIntelLevel(elem_create.GetIntel()); + elem->SetLoadout(load); + + if (code > Instruction::RTB || target.length() > 0) { + Instruction* obj = new(__FILE__,__LINE__) Instruction(code, target); + elem->AddObjective(obj); + } + + Ship* carrier = sim->FindShip(elem_create.GetCarrier()); + if (carrier) { + elem->SetCarrier(carrier); + + Hangar* hangar = carrier->GetHangar(); + if (hangar) { + Text squadron_name = hangar->SquadronName(squadron); + elem->SetSquadron(squadron_name); + + if (active) { + for (int i = 0; i < 4; i++) { + int slot = slots[i]; + if (slot > -1) { + hangar->GotoActiveFlight(squadron, slot, elem, load); + } + } + } + + else { + FlightDeck* deck = 0; + int queue = 1000; + + for (int i = 0; i < carrier->NumFlightDecks(); i++) { + FlightDeck* d = carrier->GetFlightDeck(i); + + if (d && d->IsLaunchDeck()) { + int dq = hangar->PreflightQueue(d); + + if (dq < queue) { + queue = dq; + deck = d; + } + } + } + + for (i = 0; i < 4; i++) { + int slot = slots[i]; + if (slot > -1) { + hangar->GotoAlert(squadron, slot, deck, elem, load, !alert); + } + } + } + } + } +} + +void +NetGameClient::DoShipLaunch(NetMsg* msg) +{ + if (!msg) return; + + NetShipLaunch ship_launch; + ship_launch.Unpack(msg->Data()); + + Sim* sim = Sim::GetSim(); + int squadron = ship_launch.GetSquadron(); + int slot = ship_launch.GetSlot(); + + Ship* carrier = FindShipByObjID(ship_launch.GetObjID()); + + if (carrier) { + Hangar* hangar = carrier->GetHangar(); + + if (hangar) { + hangar->Launch(squadron, slot); + } + } +} + +void +NetGameClient::DoWepTrigger(NetMsg* msg) +{ + if (!msg) return; + + NetWepTrigger trigger; + trigger.Unpack(msg->Data()); + + NetPlayer* player = FindPlayerByObjID(trigger.GetObjID()); + if (player) + player->DoWepTrigger(&trigger); +} + +void +NetGameClient::DoWepRelease(NetMsg* msg) +{ + if (!msg) return; + + NetWepRelease release; + release.Unpack(msg->Data()); + + NetPlayer* player = FindPlayerByObjID(release.GetObjID()); + if (player) { + player->DoWepRelease(&release); + } + + else { + Ship* shooter = FindShipByObjID(release.GetObjID()); + if (shooter) { + int index = release.GetIndex(); + DWORD tgtid = release.GetTgtID(); + DWORD wepid = release.GetWepID(); + int subid = release.GetSubtarget(); + bool decoy = release.GetDecoy(); + bool probe = release.GetProbe(); + + Weapon* w = 0; + + if (decoy) w = shooter->GetDecoy(); + else if (probe) w = shooter->GetProbeLauncher(); + else w = shooter->GetWeaponByIndex(index); + + if (w && !w->IsPrimary()) { + SimObject* target = FindShipByObjID(tgtid); + System* subtgt = 0; + + if (target) { + if (subid >= 0) { + Ship* tgt_ship = (Ship*) target; + subtgt = tgt_ship->Systems().at(subid); + } + } + else { + target = FindShotByObjID(tgtid); + } + + Shot* shot = w->NetFireSecondary(target, subtgt, wepid); + + if (shot && shot->IsDrone()) { + if (probe) + shooter->SetProbe((Drone*) shot); + + else if (decoy) + shooter->AddActiveDecoy((Drone*) shot); + } + } + } + } +} + +void +NetGameClient::DoNavData(NetMsg* msg) +{ + if (!msg) return; + + NetNavData nav_data; + nav_data.Unpack(msg->Data()); + + Element* elem = sim->FindElement(nav_data.GetElem()); + Ship* ship = FindShipByObjID(nav_data.GetObjID()); + + if (elem) { + if (nav_data.IsAdd()) { + Instruction* navpt = new(__FILE__,__LINE__) Instruction(*nav_data.GetNavPoint()); + Instruction* after = 0; + int index = nav_data.GetIndex(); + + if (index >= 0 && index < elem->GetFlightPlan().size()) + after = elem->GetFlightPlan().at(index); + + elem->AddNavPoint(navpt, after, false); + } + + else { + Instruction* navpt = nav_data.GetNavPoint(); + Instruction* exist = 0; + int index = nav_data.GetIndex(); + + if (navpt && index >= 0 && index < elem->GetFlightPlan().size()) { + exist = elem->GetFlightPlan().at(index); + + *exist = *navpt; + } + } + } +} + +void +NetGameClient::DoNavDelete(NetMsg* msg) +{ + if (!msg) return; + + NetNavDelete nav_delete; + nav_delete.Unpack(msg->Data()); + + Element* elem = sim->FindElement(nav_delete.GetElem()); + Ship* ship = FindShipByObjID(nav_delete.GetObjID()); + + if (elem) { + int index = nav_delete.GetIndex(); + + if (index < 0) { + elem->ClearFlightPlan(false); + } + + else if (index < elem->FlightPlanLength()) { + Instruction* npt = elem->GetFlightPlan().at(index); + elem->DelNavPoint(npt, false); + } + } +} + +void +NetGameClient::DoWepDestroy(NetMsg* msg) +{ + if (!msg) return; + + NetWepDestroy destroy; + destroy.Unpack(msg->Data()); + + Shot* shot = FindShotByObjID(destroy.GetObjID()); + if (shot) { + if (shot->IsBeam()) + ::Print("NetGameClient::DoWepDestroy shot '%s'\n", shot->Name()); + + shot->Destroy(); + } +} + +void +NetGameClient::DoCommMsg(NetMsg* msg) +{ + if (!msg) return; + + NetCommMsg comm_msg; + comm_msg.Unpack(msg->Data()); + + NetPlayer* player = FindPlayerByObjID(comm_msg.GetObjID()); + if (player) + player->DoCommMessage(&comm_msg); +} + +void +NetGameClient::DoChatMsg(NetMsg* msg) +{ + if (!msg) return; + + NetChatMsg chat_msg; + chat_msg.Unpack(msg->Data()); + + Text name = chat_msg.GetName(); + if (name.length() < 1) + name = Game::GetText("NetGameClient.chat.unknown"); + + HUDView::Message("%s> %s", name.data(), chat_msg.GetText().data()); +} + +void +NetGameClient::DoSelfDestruct(NetMsg* msg) +{ + if (!msg) return; + + NetSelfDestruct self_destruct; + self_destruct.Unpack(msg->Data()); + + Ship* ship = FindShipByObjID(self_destruct.GetObjID()); + if (ship) { + ship->InflictNetDamage(self_destruct.GetDamage()); + } +} + +// +--------------------------------------------------------------------+ + +void +NetGameClient::Send() +{ + DWORD time = Game::GameTime(); + + // don't flood the network... + if (time - last_send_time < MIN_NET_FRAME) + return; + + last_send_time = time; + + if (local_player && objid && server_id) { + double r, p, y; + local_player->Cam().Orientation().ComputeEulerAngles(r,p,y); + + NetObjLoc obj_loc; + + obj_loc.SetObjID(objid); + obj_loc.SetLocation(local_player->Location()); + obj_loc.SetVelocity(local_player->Velocity()); + obj_loc.SetOrientation(Point(r,p,y)); + obj_loc.SetThrottle(local_player->Throttle() > 10); + obj_loc.SetAugmenter(local_player->Augmenter()); + obj_loc.SetGearDown(local_player->IsGearDown()); + + Shield* shield = local_player->GetShield(); + if (shield) + obj_loc.SetShield((int) shield->GetPowerLevel()); + else + obj_loc.SetShield(0); + + BYTE* obj_loc_data = obj_loc.Pack(); + + link->SendMessage(server_id, obj_loc_data, NetObjLoc::SIZE); + } +} + +// +--------------------------------------------------------------------+ + +void +NetGameClient::SendData(NetData* net_data) +{ + if (!net_data || !server_id) + return; + + if (local_player || net_data->Type() < 0x20) { + BYTE* data = net_data->Pack(); + BYTE flags = 0; + + if (net_data->Type() >= 0x10) + flags |= NetMsg::RELIABLE; + + link->SendMessage(server_id, data, net_data->Length(), flags); + } +} + +// +--------------------------------------------------------------------+ + +void +NetGameClient::ExecFrame() +{ + if (local_player) { + if (local_player->GetObjID() == 0) { + SendJoinRequest(); + } + + else if (active) { + // check health of server: + NetPeer* server_peer = link->FindPeer(server_id); + if (server_peer && (NetLayer::GetUTC() - server_peer->LastReceiveTime() > 15)) { + NetMsg net_disco(0, NET_DISCONNECT, 0, 0, 0); + DoDisconnect(&net_disco); + } + + // if server is still there, + else if (server_peer) { + + // check if any old join announcements still need to be processed: + ListIter iter = join_backlog; + while (++iter) { + NetJoinAnnounce* join_ann = iter.value(); + + if (DoJoinBacklog(join_ann)) { + iter.removeItem(); + delete join_ann; + } + } + } + } + } + + NetGame::ExecFrame(); +} + +// +--------------------------------------------------------------------+ + +bool +NetGameClient::Update(SimObject* obj) +{ + if (obj->Type() == SimObject::SIM_SHIP) { + Ship* s = (Ship*) obj; + if (local_player == s) + local_player = 0; + } + + return SimObserver::Update(obj); +} + +const char* +NetGameClient::GetObserverName() const +{ + return "NetGameClient"; +} + +// +--------------------------------------------------------------------+ + +void +NetGameClient::Respawn(DWORD oid, Ship* spawn) +{ + if (!oid || !spawn) return; + + Print("NetGameClient::Respawn(%d, %s)\n", oid, spawn->Name()); + spawn->SetObjID(oid); + Observe(spawn); + + NetPlayer* p = FindPlayerByObjID(oid); + if (p) + p->SetShip(spawn); + + if (objid == oid) { + Print(" RESPAWN LOCAL PLAYER\n\n"); + local_player = spawn; + } +} diff --git a/Stars45/NetGameClient.h b/Stars45/NetGameClient.h new file mode 100644 index 0000000..71b8b02 --- /dev/null +++ b/Stars45/NetGameClient.h @@ -0,0 +1,87 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetGameClient.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Network Game Manager class +*/ + +#ifndef NetGameClient_h +#define NetGameClient_h + +#include "NetGame.h" +#include "SimObject.h" + +// +--------------------------------------------------------------------+ + +class NetJoinAnnounce; + +// +--------------------------------------------------------------------+ + +class NetGameClient : public NetGame, public SimObserver +{ +public: + NetGameClient(); + virtual ~NetGameClient(); + + virtual bool IsClient() const { return true; } + virtual bool IsServer() const { return false; } + + virtual void ExecFrame(); + virtual void Send(); + virtual void SendData(NetData* data); + virtual void Respawn(DWORD objid, Ship* spawn); + + virtual bool Update(SimObject* obj); + virtual const char* GetObserverName() const; + +protected: + virtual void DoJoinRequest(NetMsg* msg); + virtual void DoJoinAnnounce(NetMsg* msg); + virtual void DoQuitRequest(NetMsg* msg); + virtual void DoQuitAnnounce(NetMsg* msg); + virtual void DoGameOver(NetMsg* msg); + virtual void DoDisconnect(NetMsg* msg); + + virtual void DoObjLoc(NetMsg* msg); + virtual void DoObjDamage(NetMsg* msg); + virtual void DoObjKill(NetMsg* msg); + virtual void DoObjSpawn(NetMsg* msg); + virtual void DoObjHyper(NetMsg* msg); + virtual void DoObjTarget(NetMsg* msg); + virtual void DoObjEmcon(NetMsg* msg); + virtual void DoSysDamage(NetMsg* msg); + virtual void DoSysStatus(NetMsg* msg); + + virtual void DoElemCreate(NetMsg* msg); + virtual void DoShipLaunch(NetMsg* msg); + virtual void DoNavData(NetMsg* msg); + virtual void DoNavDelete(NetMsg* msg); + + virtual void DoWepTrigger(NetMsg* msg); + virtual void DoWepRelease(NetMsg* msg); + virtual void DoWepDestroy(NetMsg* msg); + + virtual void DoCommMsg(NetMsg* msg); + virtual void DoChatMsg(NetMsg* msg); + virtual void DoSelfDestruct(NetMsg* msg); + + virtual void SendJoinRequest(); + + virtual bool DoJoinBacklog(NetJoinAnnounce* join_ann); + + DWORD server_id; + DWORD join_req_time; + List join_backlog; +}; + +// +--------------------------------------------------------------------+ + +#endif NetGameClient_h + diff --git a/Stars45/NetGameServer.cpp b/Stars45/NetGameServer.cpp new file mode 100644 index 0000000..68b4c95 --- /dev/null +++ b/Stars45/NetGameServer.cpp @@ -0,0 +1,1199 @@ +/* Project Starshatter 5.0 + Destroyer Studios LLC + Copyright © 1997-2007. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetGameServer.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Server-Side Network Game Manager class +*/ + +#include "MemDebug.h" +#include "NetGameServer.h" +#include "NetServerConfig.h" +#include "NetLobbyServer.h" +#include "NetPlayer.h" +#include "NetUser.h" +#include "NetMsg.h" +#include "NetData.h" +#include "StarServer.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "Shield.h" +#include "Sim.h" +#include "SimEvent.h" +#include "Element.h" +#include "HUDView.h" +#include "RadioMessage.h" +#include "RadioView.h" +#include "Instruction.h" +#include "Hangar.h" +#include "FlightDeck.h" +#include "Mission.h" + +#include "NetLayer.h" +#include "NetHost.h" +#include "NetPeer.h" +#include "NetUtil.h" +#include "Game.h" +#include "Light.h" + +// +--------------------------------------------------------------------+ + +const int MAX_NET_FPS = 20; +const int MIN_NET_FRAME = 1000 / MAX_NET_FPS; + +// +--------------------------------------------------------------------+ + +NetGameServer::NetGameServer() +{ + Print("Constructing NetGameServer\n"); + + WORD server_port = 11101; + + if (NetServerConfig::GetInstance()) + server_port = NetServerConfig::GetInstance()->GetGamePort(); + + NetAddr server(NetHost().Address().IPAddr(), server_port); + + link = new(__FILE__,__LINE__) NetLink(server); + + ListIter rgn_iter = sim->GetRegions(); + while (++rgn_iter) { + SimRegion* rgn = rgn_iter.value(); + + ListIter iter = rgn->Ships(); + while (++iter) { + Ship* s = iter.value(); + s->SetObjID(GetNextObjID()); + Observe(s); + ships.append(s); + } + } + + if (local_player) { + Observe(local_player); + objid = local_player->GetObjID(); + } +} + +NetGameServer::~NetGameServer() +{ + ListIter player = players; + while (++player) { + NetPlayer* p = player.value(); + + if (p->GetShip()) + p->GetShip()->SetRespawnCount(0); + + link->SendMessage(p->GetNetID(), NET_GAME_OVER, 0, 0, NetMsg::RELIABLE); + } + + Sleep(500); + + ListIter iter = ships; + while (++iter) { + Ship* s = iter.value(); + s->SetRespawnCount(0); + } + + zombies.destroy(); + ships.clear(); +} + +// +--------------------------------------------------------------------+ + +void +NetGameServer::ExecFrame() +{ + NetGame::ExecFrame(); + CheckSessions(); + + ListIter rgn_iter = sim->GetRegions(); + while (++rgn_iter) { + SimRegion* rgn = rgn_iter.value(); + + ListIter iter = rgn->Ships(); + while (++iter) { + Ship* s = iter.value(); + + if (s->GetObjID() == 0) { + s->SetObjID(GetNextObjID()); + Observe(s); + ships.append(s); + + NetJoinAnnounce join_ann; + join_ann.SetShip(s); + join_ann.SetName("Server A.I. Ship"); + SendData(&join_ann); + } + } + } + + + static DWORD time_mark = 0; + + if (!time_mark) time_mark = Game::RealTime(); + else if (Game::RealTime() - time_mark > 60000) { + time_mark = Game::RealTime(); + + if (link && players.size() > 0) { + Print("Server Stats\n-------------\n"); + Print(" packets sent %d\n", link->GetPacketsSent()); + Print(" packets recv %d\n", link->GetPacketsRecv()); + Print(" bytes sent %d\n", link->GetBytesSent()); + Print(" bytes recv %d\n", link->GetBytesRecv()); + Print(" retries %d\n", link->GetRetries()); + Print(" drops %d\n", link->GetDrops()); + Print(" avg lag %d msec\n", link->GetLag()); + } + } +} + +void +NetGameServer::CheckSessions() +{ + if (!link) + return; + + ListIter iter = players; + while (++iter) { + NetPlayer* player = iter.value(); + NetPeer* peer = link->FindPeer(player->GetNetID()); + + if (peer && (NetLayer::GetUTC() - peer->LastReceiveTime()) > NET_DISCONNECT_TIME) { + // announce drop: + NetPlayer* zombie = iter.removeItem(); + HUDView::Message(Game::GetText("NetGameServer.remote-discon").data(), zombie->Name()); + + // tell everyone else: + NetQuitAnnounce quit_ann; + quit_ann.SetObjID(zombie->GetObjID()); + quit_ann.SetDisconnected(true); + SendData(&quit_ann); + + // return remote ship to ship pool: + Ship* s = zombie->GetShip(); + if (s) { + Observe(s); + ships.append(s); + s->SetNetworkControl(0); + zombie->SetShip(0); + + NetJoinAnnounce join_ann; + join_ann.SetShip(s); + join_ann.SetName("Server A.I. Ship"); + SendData(&join_ann); + } + + zombies.append(zombie); + } + } +} + +NetPlayer* +NetGameServer::FindZombieByObjID(DWORD objid) +{ + for (int i = 0; i < zombies.size(); i++) { + NetPlayer* p = zombies[i]; + + if (p->GetObjID() == objid) + return p; + } + + return 0; +} + +// +--------------------------------------------------------------------+ + +void +NetGameServer::DoJoinRequest(NetMsg* msg) +{ + if (!msg) return; + + bool unpause = players.isEmpty(); + + NetJoinRequest join_req; + if (join_req.Unpack(msg->Data())) { + HUDView::Message(Game::GetText("NetGameServer::join-request").data(), join_req.GetName(), join_req.GetElement(), join_req.GetIndex()); + + DWORD nid = msg->NetID(); + Text name = join_req.GetName(); + Text serno = join_req.GetSerialNumber(); + Text elem_name = join_req.GetElement(); + int index = join_req.GetIndex(); + Ship* ship = 0; + Sim* sim = Sim::GetSim(); + + if (sim) { + Element* element = sim->FindElement(elem_name); + + if (element) + ship = element->GetShip(index); + } + + if (!ship) { + Print(" JOIN DENIED: could not locate ship for remote player\n"); + return; + } + + if (!ship->GetObjID()) { + Print(" JOIN DENIED: remote player requested ship with objid = 0\n"); + return; + } + + NetLobbyServer* lobby = NetLobbyServer::GetInstance(); + + if (lobby) { + NetUser* user = lobby->FindUserByName(name); + + if (!user) + user = lobby->FindUserByNetID(nid); + + if (!user) { + Print(" JOIN DENIED: remote player '%s' not found in lobby\n", name.data()); + return; + } + + else if (!user->IsAuthOK()) { + Print(" JOIN DENIED: remote player '%s' not authenticated\n", name.data()); + return; + } + } + + NetPlayer* remote_player = FindPlayerByNetID(nid); + if (remote_player && remote_player->GetShip() != ship) { + Print(" disconnecting remote player from ship '%s'\n", ship->Name()); + players.remove(remote_player); + delete remote_player; + remote_player = 0; + } + + if (!remote_player) { + Ignore(ship); + ships.remove(ship); + + remote_player = new(__FILE__,__LINE__) NetPlayer(nid); + remote_player->SetName(name); + remote_player->SetSerialNumber(serno); + remote_player->SetObjID(ship->GetObjID()); + remote_player->SetShip(ship); + + HUDView::Message(Game::GetText("NetGameServer::join-announce").data()); + Print("remote player name = %s\n", name.data()); + Print(" obj = %d\n", ship->GetObjID()); + Print(" ship = %s\n", ship->Name()); + + remote_player->SetObjID(ship->GetObjID()); + + // tell the new player about the server: + if (local_player) { + NetJoinAnnounce join_ann; + join_ann.SetShip(local_player); + join_ann.SetName(player_name); + link->SendMessage(remote_player->GetNetID(), join_ann.Pack(), NetJoinAnnounce::SIZE, NetMsg::RELIABLE); + } + + // tell the new player about the existing remote players: + ListIter iter = players; + while (++iter) { + Ship* s = iter->GetShip(); + + if (s) { + NetJoinAnnounce join_ann; + join_ann.SetShip(s); + join_ann.SetName(iter->Name()); + join_ann.SetObjID(iter->GetObjID()); + link->SendMessage(remote_player->GetNetID(), join_ann.Pack(), NetJoinAnnounce::SIZE, NetMsg::RELIABLE); + } + } + + // tell the new player about the A.I. controlled ships: + ListIter ai_iter = ships; + while (++ai_iter) { + Ship* s = ai_iter.value(); + if (s != local_player) { + NetJoinAnnounce join_ann; + join_ann.SetShip(s); + join_ann.SetName("Server A.I. Ship"); + link->SendMessage(remote_player->GetNetID(), join_ann.Pack(), NetJoinAnnounce::SIZE, NetMsg::RELIABLE); + } + } + + // make the new player an "existing" remote player: + players.append(remote_player); + + // tell existing players about the new player: + // NOTE, this also provides the net id to the new player! + iter.reset(); + while (++iter) { + Ship* s = remote_player->GetShip(); + + NetJoinAnnounce join_ann; + join_ann.SetShip(s); + join_ann.SetName(remote_player->Name()); + join_ann.SetObjID(remote_player->GetObjID()); + link->SendMessage(iter->GetNetID(), join_ann.Pack(), NetJoinAnnounce::SIZE, NetMsg::RELIABLE); + } + + if (unpause) { + StarServer* s = StarServer::GetInstance(); + if (s) + s->Pause(false); + } + } + } +} + +void +NetGameServer::DoJoinAnnounce(NetMsg* msg) +{ + if (!msg) return; + + NetJoinAnnounce join_ann; + if (join_ann.Unpack(msg->Data())) { + Print("Server received Join Announce from '%s'\n", join_ann.GetName()); + } +} + +void +NetGameServer::DoQuitRequest(NetMsg* msg) +{ + if (!msg) return; + + NetPlayer* player = FindPlayerByNetID(msg->NetID()); + + if (player) { + NetPlayer* zombie = players.remove(player); + HUDView::Message(Game::GetText("NetGameServer.remote-quit").data(), zombie->Name()); + + // tell everyone else: + NetQuitAnnounce quit_ann; + quit_ann.SetObjID(zombie->GetObjID()); + SendData(&quit_ann); + + // return remote ship to ship pool: + Ship* s = zombie->GetShip(); + if (s) { + Observe(s); + ships.append(s); + s->SetNetworkControl(0); + zombie->SetShip(0); + + NetJoinAnnounce join_ann; + join_ann.SetShip(s); + join_ann.SetName("Server A.I. Ship"); + SendData(&join_ann); + } + + zombies.append(zombie); + } + else { + Print("Quit Request from unknown player NetID: %08X\n", msg->NetID()); + } +} + +void +NetGameServer::DoQuitAnnounce(NetMsg* msg) +{ + if (!msg) return; + Print("Server received Quit Announce from NetID: %08x\n", msg->NetID()); +} + +void +NetGameServer::DoGameOver(NetMsg* msg) +{ + if (!msg) return; + Print("Server received Game Over from NetID: %08x\n", msg->NetID()); +} + +void +NetGameServer::DoDisconnect(NetMsg* msg) +{ + if (!msg) return; + Print("Server received Disconnect from NetID: %08x\n", msg->NetID()); +} + +void +NetGameServer::DoObjLoc(NetMsg* msg) +{ + if (!msg) return; + + NetObjLoc obj_loc; + obj_loc.Unpack(msg->Data()); + + NetPlayer* player = FindPlayerByObjID(obj_loc.GetObjID()); + if (player && player->GetShip()) { + player->DoObjLoc(&obj_loc); + } + + else { + player = FindZombieByObjID(obj_loc.GetObjID()); + + if (player) + SendDisconnect(player); + } +} + +void +NetGameServer::DoObjDamage(NetMsg* msg) +{ + if (!msg) return; + Print("Server received OBJ DAMAGE from NetID: %08x (ignored)\n", msg->NetID()); +} + +void +NetGameServer::DoObjKill(NetMsg* msg) +{ + if (!msg) return; + + NetObjKill obj_kill; + obj_kill.Unpack(msg->Data()); + + if (obj_kill.GetKillType() != NetObjKill::KILL_DOCK) { + Print("Server received OBJ KILL from NetID: %08x (ignored)\n", msg->NetID()); + return; + } + + Ship* ship = FindShipByObjID(obj_kill.GetObjID()); + if (ship) { + Ship* killer = FindShipByObjID(obj_kill.GetKillerID()); + Text killer_name = Game::GetText("NetGameServer.unknown"); + + if (killer) + killer_name = killer->Name(); + + ShipStats* killee = ShipStats::Find(ship->Name()); + if (killee) + killee->AddEvent(SimEvent::DOCK, killer_name); + + FlightDeck* deck = killer->GetFlightDeck(obj_kill.GetFlightDeck()); + sim->NetDockShip(ship, killer, deck); + } +} + +void +NetGameServer::DoObjSpawn(NetMsg* msg) +{ + if (!msg) return; + Print("Server received OBJ SPAWN from NetID: %08x (ignored)\n", msg->NetID()); +} + +void +NetGameServer::DoObjHyper(NetMsg* msg) +{ + if (!msg) return; + Print("Server received OBJ HYPER from NetID: %d\n", msg->NetID()); + + NetObjHyper obj_hyper; + obj_hyper.Unpack(msg->Data()); + + NetPlayer* player = FindPlayerByObjID(obj_hyper.GetObjID()); + if (player && player->GetShip()) { + if (player->DoObjHyper(&obj_hyper)) { + SendData(&obj_hyper); + } + } + else { + player = FindZombieByObjID(obj_hyper.GetObjID()); + + if (player) + SendDisconnect(player); + } +} + +void +NetGameServer::DoObjTarget(NetMsg* msg) +{ + if (!msg) return; + + NetObjTarget obj_target; + obj_target.Unpack(msg->Data()); + + NetPlayer* player = FindPlayerByObjID(obj_target.GetObjID()); + if (player) { + player->DoObjTarget(&obj_target); + } + else { + player = FindZombieByObjID(obj_target.GetObjID()); + + if (player) + SendDisconnect(player); + } +} + +void +NetGameServer::DoObjEmcon(NetMsg* msg) +{ + if (!msg) return; + + NetObjEmcon obj_emcon; + obj_emcon.Unpack(msg->Data()); + + NetPlayer* player = FindPlayerByObjID(obj_emcon.GetObjID()); + if (player) { + player->DoObjEmcon(&obj_emcon); + } + else { + player = FindZombieByObjID(obj_emcon.GetObjID()); + + if (player) + SendDisconnect(player); + } +} + +// +--------------------------------------------------------------------+ + +void +NetGameServer::DoSysDamage(NetMsg* msg) +{ + if (!msg) return; + + NetSysDamage sys_damage; + sys_damage.Unpack(msg->Data()); + + NetPlayer* player = FindZombieByObjID(sys_damage.GetObjID()); + + if (player) + SendDisconnect(player); +} + +void +NetGameServer::DoSysStatus(NetMsg* msg) +{ + if (!msg) return; + + NetSysStatus sys_status; + sys_status.Unpack(msg->Data()); + + Ship* ship = FindShipByObjID(sys_status.GetObjID()); + NetPlayer* player = FindPlayerByNetID(msg->NetID()); + + if (ship) { + if (!player || ship->GetObjID() != player->GetObjID()) { + /** + Print("NetGameServer::DoSysStatus - received request for ship '%s' from wrong player %s\n", + ship->Name(), player ? player->Name() : "null"); + **/ + + return; + } + + player->DoSysStatus(&sys_status); + + // rebroadcast: + System* sys = ship->GetSystem(sys_status.GetSystem()); + NetUtil::SendSysStatus(ship, sys); + } + + else { + player = FindZombieByObjID(sys_status.GetObjID()); + + if (player) + SendDisconnect(player); + } +} + +// +--------------------------------------------------------------------+ + +void +NetGameServer::DoElemRequest(NetMsg* msg) +{ + if (!msg) return; + + NetElemRequest elem_request; + elem_request.Unpack(msg->Data()); + + Sim* sim = Sim::GetSim(); + Element* elem = sim->FindElement(elem_request.GetName()); + + if (elem) { + int squadron = -1; + int slots[] = { -1,-1,-1,-1 }; + Ship* carrier = elem->GetCarrier(); + + if (carrier && carrier->GetHangar()) { + Hangar* hangar = carrier->GetHangar(); + + for (int i = 0; i < 4; i++) { + hangar->FindSquadronAndSlot(elem->GetShip(i+1), squadron, slots[i]); + } + } + + NetUtil::SendElemCreate(elem, squadron, slots, false, true); + } +} + +// +--------------------------------------------------------------------+ + +void +NetGameServer::DoElemCreate(NetMsg* msg) +{ + if (!msg) return; + + NetElemCreate elem_create; + elem_create.Unpack(msg->Data()); + + Sim* sim = Sim::GetSim(); + Element* elem = sim->CreateElement(elem_create.GetName(), + elem_create.GetIFF(), + elem_create.GetType()); + + int* load = elem_create.GetLoadout(); + int* slots = elem_create.GetSlots(); + int squadron = elem_create.GetSquadron(); + int code = elem_create.GetObjCode(); + Text target = elem_create.GetObjective(); + bool alert = elem_create.GetAlert(); + + elem->SetIntelLevel(elem_create.GetIntel()); + elem->SetLoadout(load); + + if (code > Instruction::RTB || target.length() > 0) { + Instruction* obj = new(__FILE__,__LINE__) Instruction(code, target); + elem->AddObjective(obj); + } + + Ship* carrier = sim->FindShip(elem_create.GetCarrier()); + if (carrier) { + elem->SetCarrier(carrier); + + Hangar* hangar = carrier->GetHangar(); + if (hangar) { + Text squadron_name = hangar->SquadronName(squadron); + elem->SetSquadron(squadron_name); + + FlightDeck* deck = 0; + int queue = 1000; + + for (int i = 0; i < carrier->NumFlightDecks(); i++) { + FlightDeck* d = carrier->GetFlightDeck(i); + + if (d && d->IsLaunchDeck()) { + int dq = hangar->PreflightQueue(d); + + if (dq < queue) { + queue = dq; + deck = d; + } + } + } + + for (i = 0; i < 4; i++) { + int slot = slots[i]; + if (slot > -1) { + hangar->GotoAlert(squadron, slot, deck, elem, load, !alert); + } + } + } + } + + NetUtil::SendElemCreate(elem, + elem_create.GetSquadron(), + elem_create.GetSlots(), + elem_create.GetAlert()); +} + +void +NetGameServer::DoShipLaunch(NetMsg* msg) +{ + if (!msg) return; + + NetShipLaunch ship_launch; + ship_launch.Unpack(msg->Data()); + + Sim* sim = Sim::GetSim(); + int squadron = ship_launch.GetSquadron(); + int slot = ship_launch.GetSlot(); + + NetPlayer* player = FindPlayerByObjID(ship_launch.GetObjID()); + if (player) { + Ship* carrier = player->GetShip(); + + if (carrier) { + Hangar* hangar = carrier->GetHangar(); + + if (hangar) { + hangar->Launch(squadron, slot); + } + + NetUtil::SendShipLaunch(carrier, squadron, slot); + } + } +} + +void +NetGameServer::DoNavData(NetMsg* msg) +{ + if (!msg) return; + + NetNavData nav_data; + nav_data.Unpack(msg->Data()); + + Element* elem = sim->FindElement(nav_data.GetElem()); + + if (elem) { + if (nav_data.IsAdd()) { + Instruction* navpt = new(__FILE__,__LINE__) Instruction(*nav_data.GetNavPoint()); + Instruction* after = 0; + int index = nav_data.GetIndex(); + + if (index >= 0 && index < elem->GetFlightPlan().size()) + after = elem->GetFlightPlan().at(index); + + elem->AddNavPoint(navpt, after, false); + } + + else { + Instruction* navpt = nav_data.GetNavPoint(); + Instruction* exist = 0; + int index = nav_data.GetIndex(); + + if (navpt && index >= 0 && index < elem->GetFlightPlan().size()) { + exist = elem->GetFlightPlan().at(index); + *exist = *navpt; + } + } + + SendData(&nav_data); + } +} + +void +NetGameServer::DoNavDelete(NetMsg* msg) +{ + if (!msg) return; + + NetNavDelete nav_delete; + nav_delete.Unpack(msg->Data()); + + Element* elem = sim->FindElement(nav_delete.GetElem()); + + if (elem) { + int index = nav_delete.GetIndex(); + + if (index < 0) { + elem->ClearFlightPlan(false); + } + + else if (index < elem->FlightPlanLength()) { + Instruction* npt = elem->GetFlightPlan().at(index); + elem->DelNavPoint(npt, false); + } + + SendData(&nav_delete); + } +} + +void +NetGameServer::DoWepTrigger(NetMsg* msg) +{ + if (!msg) return; + + NetWepTrigger trigger; + trigger.Unpack(msg->Data()); + + NetPlayer* player = FindPlayerByObjID(trigger.GetObjID()); + if (player) { + player->DoWepTrigger(&trigger); + } + else { + player = FindZombieByObjID(trigger.GetObjID()); + + if (player) + SendDisconnect(player); + } +} + +void +NetGameServer::DoWepRelease(NetMsg* msg) +{ + if (!msg) return; + + NetWepRelease release; + release.Unpack(msg->Data()); + + NetPlayer* player = FindPlayerByObjID(release.GetObjID()); + if (player) { + player->DoWepRelease(&release); + } + else { + player = FindZombieByObjID(release.GetObjID()); + + if (player) + SendDisconnect(player); + } + + Print("WEP RELEASE on server? objid = %d\n", release.GetObjID()); +} + +void +NetGameServer::DoWepDestroy(NetMsg* msg) +{ +} + +void +NetGameServer::DoCommMsg(NetMsg* msg) +{ + if (!msg) return; + + NetCommMsg comm_msg; + comm_msg.Unpack(msg->Data()); + + RadioMessage* radio_msg = comm_msg.GetRadioMessage(); + + NetPlayer* player = FindPlayerByObjID(comm_msg.GetObjID()); + if (player && radio_msg) { + player->DoCommMessage(&comm_msg); + + int channel = comm_msg.GetRadioMessage()->Channel(); + + ListIter dst = players; + while (++dst) { + NetPlayer* remote_player = dst.value(); + if (remote_player->GetNetID() && + (channel == 0 || channel == remote_player->GetIFF())) { + + BYTE* data = comm_msg.Pack(); + int size = comm_msg.Length(); + + link->SendMessage(remote_player->GetNetID(), data, size, NetMsg::RELIABLE); + } + } + } + else { + player = FindZombieByObjID(comm_msg.GetObjID()); + + if (player) + SendDisconnect(player); + } +} + +void +NetGameServer::DoChatMsg(NetMsg* msg) +{ + if (!msg) return; + + NetChatMsg chat_msg; + chat_msg.Unpack(msg->Data()); + + RouteChatMsg(chat_msg); +} + +void +NetGameServer::RouteChatMsg(NetChatMsg& chat_msg) +{ + DWORD dst_id = chat_msg.GetDstID(); + + // broadcast or team: + if (dst_id == 0xffff || dst_id <= 10) { + BYTE* data = chat_msg.Pack(); + int size = chat_msg.Length(); + + ListIter dst = players; + while (++dst) { + NetPlayer* remote_player = dst.value(); + if (remote_player->GetNetID() && chat_msg.GetName() != remote_player->Name()) { + if (dst_id == 0xffff || dst_id == 0 || remote_player->GetIFF() == (int) dst_id-1) + link->SendMessage(remote_player->GetNetID(), data, size); + } + } + + if (local_player && (dst_id == 0xffff || dst_id == 0 || local_player->GetIFF() == (int) dst_id-1)) { + Text name = chat_msg.GetName(); + if (name.length() < 1) + name = Game::GetText("NetGameServer.chat.unknown"); + + // don't echo general messages from the local player. + // they are already displayed by the chat entry code + // in starshatter.cpp + + if (name != player_name) + HUDView::Message("%s> %s", name.data(), chat_msg.GetText().data()); + } + } + + // direct to local player: + else if (local_player && local_player->GetObjID() == dst_id) { + Text name = chat_msg.GetName(); + if (name.length() < 1) + name = Game::GetText("NetGameServer.chat.unknown"); + + HUDView::Message("%s> %s", name.data(), chat_msg.GetText().data()); + } + + // ship-to-ship, but not to local player: + else if (!local_player || local_player->GetObjID() != dst_id) { + NetPlayer* remote_player = FindPlayerByObjID(dst_id); + if (remote_player && remote_player->GetNetID()) { + BYTE* data = chat_msg.Pack(); + int size = chat_msg.Length(); + link->SendMessage(remote_player->GetNetID(), data, size); + } + + // record message in server log: + ::Print("%s> %s\n", chat_msg.GetName().data(), chat_msg.GetText().data()); + } + + if (dst_id == 0xffff) + return; + + // record message in chat log: + NetLobbyServer* lobby = NetLobbyServer::GetInstance(); + + if (!lobby) + return; + + NetUser* user = lobby->FindUserByName(chat_msg.GetName()); + + if (user) + lobby->AddChat(user, chat_msg.GetText(), false); // don't re-route +} + +// +--------------------------------------------------------------------+ + +const char* FormatGameTime(); + +void +NetGameServer::DoSelfDestruct(NetMsg* msg) +{ + if (!msg) return; + + NetSelfDestruct self_destruct; + self_destruct.Unpack(msg->Data()); + + Ship* ship = FindShipByObjID(self_destruct.GetObjID()); + NetPlayer* player = FindPlayerByNetID(msg->NetID()); + + if (ship) { + if (!player || ship->GetObjID() != player->GetObjID()) { + Print("NetGameServer::DoSelfDestruct - received request for ship '%s' from wrong player %s\n", + ship->Name(), player ? player->Name() : "null"); + + return; + } + + ship->InflictNetDamage(self_destruct.GetDamage()); + + SendData(&self_destruct); + + int ship_destroyed = (!ship->InTransition() && ship->Integrity() < 1.0f); + + // then delete the ship: + if (ship_destroyed) { + NetUtil::SendObjKill(ship, 0, NetObjKill::KILL_MISC); + Print(" %s Self Destruct (%s)\n", ship->Name(), FormatGameTime()); + + ShipStats* killee = ShipStats::Find(ship->Name()); + if (killee) + killee->AddEvent(SimEvent::DESTROYED, ship->Name()); + + ship->DeathSpiral(); + } + } +} + +// +--------------------------------------------------------------------+ + +void +NetGameServer::Send() +{ + if (players.isEmpty()) + return; + + DWORD time = Game::GameTime(); + + // don't flood the network... + if (time - last_send_time < MIN_NET_FRAME) + return; + + last_send_time = time; + + // tell the remote players where *we* are: + if (local_player && !local_player->IsNetObserver() && objid) { + double r, p, y; + local_player->Cam().Orientation().ComputeEulerAngles(r,p,y); + + NetObjLoc obj_loc; + obj_loc.SetObjID(objid); + obj_loc.SetLocation(local_player->Location()); + obj_loc.SetVelocity(local_player->Velocity()); + obj_loc.SetOrientation(Point(r,p,y)); + obj_loc.SetThrottle(local_player->Throttle() > 10); + obj_loc.SetAugmenter(local_player->Augmenter()); + obj_loc.SetGearDown(local_player->IsGearDown()); + + Shield* shield = local_player->GetShield(); + if (shield) + obj_loc.SetShield((int) shield->GetPowerLevel()); + else + obj_loc.SetShield(0); + + SendData(&obj_loc); + } + + // tell each remote player where all the others are: + ListIter src = players; + while (++src) { + NetPlayer* player = src.value(); + + Ship* player_ship = player->GetShip(); + + if (player_ship) { + double r, p, y; + player_ship->Cam().Orientation().ComputeEulerAngles(r,p,y); + + NetObjLoc obj_loc; + obj_loc.SetObjID(player->GetObjID()); + obj_loc.SetLocation(player_ship->Location()); + obj_loc.SetVelocity(player_ship->Velocity()); + obj_loc.SetOrientation(Point(r,p,y)); + obj_loc.SetThrottle(player_ship->Throttle() > 10); + obj_loc.SetAugmenter(player_ship->Augmenter()); + obj_loc.SetGearDown(player_ship->IsGearDown()); + + Shield* shield = player_ship->GetShield(); + if (shield) + obj_loc.SetShield((int) shield->GetPowerLevel()); + else + obj_loc.SetShield(0); + + BYTE* obj_loc_data = obj_loc.Pack(); + + ListIter dst = players; + while (++dst) { + NetPlayer* remote_player = dst.value(); + if (remote_player->GetNetID() && remote_player != player) + link->SendMessage(remote_player->GetNetID(), obj_loc_data, NetObjLoc::SIZE); + } + } + } + + // tell each remote player where all the A.I. ships are: + ListIter ai_iter = ships; + while (++ai_iter) { + Ship* s = ai_iter.value(); + + if (s && !s->IsStatic()) { + double r, p, y; + s->Cam().Orientation().ComputeEulerAngles(r,p,y); + + NetObjLoc obj_loc; + obj_loc.SetObjID(s->GetObjID()); + obj_loc.SetLocation(s->Location()); + obj_loc.SetVelocity(s->Velocity()); + obj_loc.SetOrientation(Point(r,p,y)); + obj_loc.SetThrottle(s->Throttle() > 10); + obj_loc.SetAugmenter(s->Augmenter()); + obj_loc.SetGearDown(s->IsGearDown()); + + Shield* shield = s->GetShield(); + if (shield) + obj_loc.SetShield((int) shield->GetPowerLevel()); + else + obj_loc.SetShield(0); + + SendData(&obj_loc); + } + } +} + +// +--------------------------------------------------------------------+ + +void +NetGameServer::SendData(NetData* net_data) +{ + if (net_data) { + BYTE* data = net_data->Pack(); + int size = net_data->Length(); + BYTE flags = 0; + bool all = true; // include player with objid in net_data? + DWORD oid = net_data->GetObjID(); + + BYTE msg_type = net_data->Type(); + + if (msg_type >= 0x10) + flags |= NetMsg::RELIABLE; + + if (msg_type == NET_WEP_TRIGGER || + msg_type == NET_COMM_MESSAGE || + msg_type == NET_CHAT_MESSAGE || + msg_type == NET_OBJ_HYPER || + msg_type == NET_ELEM_CREATE || + msg_type == NET_NAV_DATA || + msg_type == NET_NAV_DELETE) { + all = false; + } + + ListIter dst = players; + while (++dst) { + NetPlayer* remote_player = dst.value(); + if (remote_player->GetNetID() && (all || oid != remote_player->GetObjID())) + link->SendMessage(remote_player->GetNetID(), data, size, flags); + } + } +} + +void +NetGameServer::SendDisconnect(NetPlayer* zombie) +{ + if (zombie) { + NetDisconnect disconnect; + BYTE* data = disconnect.Pack(); + int size = disconnect.Length(); + BYTE flags = NetMsg::RELIABLE; + + if (zombie->GetNetID()) + link->SendMessage(zombie->GetNetID(), data, size, flags); + } +} + +// +--------------------------------------------------------------------+ + +bool +NetGameServer::Update(SimObject* obj) +{ + if (obj->Type() == SimObject::SIM_SHIP) { + Ship* s = (Ship*) obj; + if (local_player == s) + local_player = 0; + + if (ships.contains(s)) + ships.remove(s); + } + + return SimObserver::Update(obj); +} + +const char* +NetGameServer::GetObserverName() const +{ + return "NetGameServer"; +} + +// +--------------------------------------------------------------------+ + +void +NetGameServer::Respawn(DWORD oid, Ship* spawn) +{ + if (!oid || !spawn) return; + + Print("NetGameServer::Respawn(%d, %s)\n", oid, spawn->Name()); + spawn->SetObjID(oid); + Observe(spawn); + + NetPlayer* p = FindPlayerByObjID(oid); + if (p) + p->SetShip(spawn); + else + ships.append(spawn); + + if (objid == oid) { + Print(" RESPAWN LOCAL PLAYER\n\n"); + local_player = spawn; + } +} diff --git a/Stars45/NetGameServer.h b/Stars45/NetGameServer.h new file mode 100644 index 0000000..545aca2 --- /dev/null +++ b/Stars45/NetGameServer.h @@ -0,0 +1,90 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetGameServer.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Server-Side Network Game Manager class +*/ + +#ifndef NetGameServer_h +#define NetGameServer_h + +#include "NetGame.h" +#include "SimObject.h" + +// +--------------------------------------------------------------------+ + +class NetChatMsg; + +// +--------------------------------------------------------------------+ + +class NetGameServer : public NetGame, public SimObserver +{ +public: + NetGameServer(); + virtual ~NetGameServer(); + + virtual bool IsClient() const { return false; } + virtual bool IsServer() const { return true; } + + virtual void ExecFrame(); + virtual void CheckSessions(); + + virtual void Send(); + virtual void SendData(NetData* data); + virtual void Respawn(DWORD objid, Ship* spawn); + + virtual bool Update(SimObject* obj); + virtual const char* GetObserverName() const; + + virtual void RouteChatMsg(NetChatMsg& chat_msg); + +protected: + virtual void DoJoinRequest(NetMsg* msg); + virtual void DoJoinAnnounce(NetMsg* msg); + virtual void DoQuitRequest(NetMsg* msg); + virtual void DoQuitAnnounce(NetMsg* msg); + virtual void DoGameOver(NetMsg* msg); + virtual void DoDisconnect(NetMsg* msg); + + virtual void DoObjLoc(NetMsg* msg); + virtual void DoObjDamage(NetMsg* msg); + virtual void DoObjKill(NetMsg* msg); + virtual void DoObjSpawn(NetMsg* msg); + virtual void DoObjHyper(NetMsg* msg); + virtual void DoObjTarget(NetMsg* msg); + virtual void DoObjEmcon(NetMsg* msg); + virtual void DoSysDamage(NetMsg* msg); + virtual void DoSysStatus(NetMsg* msg); + + virtual void DoElemRequest(NetMsg* msg); + virtual void DoElemCreate(NetMsg* msg); + virtual void DoShipLaunch(NetMsg* msg); + virtual void DoNavData(NetMsg* msg); + virtual void DoNavDelete(NetMsg* msg); + + virtual void DoWepTrigger(NetMsg* msg); + virtual void DoWepRelease(NetMsg* msg); + virtual void DoWepDestroy(NetMsg* msg); + + virtual void DoCommMsg(NetMsg* msg); + virtual void DoChatMsg(NetMsg* msg); + virtual void DoSelfDestruct(NetMsg* msg); + + virtual NetPlayer* FindZombieByObjID(DWORD objid); + virtual void SendDisconnect(NetPlayer* zombie); + + List ships; + List zombies; +}; + +// +--------------------------------------------------------------------+ + +#endif NetGameServer_h + diff --git a/Stars45/NetLobby.cpp b/Stars45/NetLobby.cpp new file mode 100644 index 0000000..6258f77 --- /dev/null +++ b/Stars45/NetLobby.cpp @@ -0,0 +1,630 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetLobby.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Base Class for Multiplayer Game Lobby classes +*/ + +#include "MemDebug.h" +#include "NetLobby.h" +#include "NetLobbyClient.h" +#include "NetLobbyServer.h" +#include "NetClientConfig.h" +#include "NetServerConfig.h" +#include "NetChat.h" +#include "NetUser.h" + +#include "NetMsg.h" +#include "NetData.h" +#include "NetLayer.h" + +#include "Player.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "Campaign.h" +#include "Element.h" +#include "Mission.h" + +#include "NetHost.h" +#include "Game.h" +#include "Light.h" +#include "parseutil.h" + +// +--------------------------------------------------------------------+ + +const int MAX_NET_FPS = 50; +const int MIN_NET_FRAME = 1000 / MAX_NET_FPS; + +const DWORD SHIP_ID_START = 0x0010; +const DWORD SHOT_ID_START = 0x0400; + +static NetLobby* instance = 0; + +static DWORD ship_id_key = SHIP_ID_START; +static DWORD shot_id_key = SHOT_ID_START; + +// +--------------------------------------------------------------------+ + +NetLobby::NetLobby(bool temporary) + : link(0), local_user(0), last_send_time(0), active(true), + selected_mission(0), mission(0), status(0) +{ + if (!temporary) + instance = this; +} + +NetLobby::~NetLobby() +{ + if (instance == this) + instance = 0; + + unit_map.destroy(); + users.destroy(); + campaigns.destroy(); + chat_log.destroy(); + + if (local_user) { + delete local_user; + local_user = 0; + } + + if (link) { + delete link; + link = 0; + } + + mission = 0; +} + +// +--------------------------------------------------------------------+ + +NetLobby* +NetLobby::GetInstance() +{ + return instance; +} + +// +--------------------------------------------------------------------+ + +void +NetLobby::ExecFrame() +{ + Send(); + Recv(); +} + +void +NetLobby::Recv() +{ + NetMsg* msg = link->GetMessage(); + + while (msg) { + if (active) { + NetPeer* peer = link->FindPeer(msg->NetID()); + + static char buffer[256]; + char* text = 0; + + if (msg->Length() > 2) { + if (msg->Length() < 250) { + ZeroMemory(buffer, sizeof(buffer)); + CopyMemory(buffer, msg->Data()+2, msg->Length()-2); + text = buffer; + } + else if (msg->Length() < 1024 * 1024) { + text = new(__FILE__,__LINE__) char[msg->Length()]; + ZeroMemory(text, msg->Length()); + CopyMemory(text, msg->Data()+2, msg->Length()-2); + } + } + + switch (msg->Type()) { + case NET_LOBBY_PING: DoPing(peer, text); break; + case NET_LOBBY_SERVER_INFO: DoServerInfo(peer, text); break; + case NET_LOBBY_SERVER_MODS: DoServerMods(peer, text); break; + case NET_LOBBY_LOGIN: DoLogin(peer, text); break; + case NET_LOBBY_LOGOUT: DoLogout(peer, text); break; + + case NET_LOBBY_AUTH_USER: DoAuthUser(peer, text); break; + case NET_LOBBY_USER_AUTH: DoUserAuth(peer, text); break; + + case NET_LOBBY_CHAT: DoChat(peer, text); break; + case NET_LOBBY_USER_LIST: DoUserList(peer, text); break; + case NET_LOBBY_BAN_USER: DoBanUser(peer, text); break; + case NET_LOBBY_MISSION_LIST: DoMissionList(peer, text); break; + case NET_LOBBY_MISSION_SELECT: DoMissionSelect(peer, text); break; + case NET_LOBBY_MISSION_DATA: DoMissionData(peer, text); break; + case NET_LOBBY_UNIT_LIST: DoUnitList(peer, text); break; + case NET_LOBBY_MAP_UNIT: DoMapUnit(peer, text); break; + case NET_LOBBY_GAME_START: DoGameStart(peer, text); break; + case NET_LOBBY_GAME_STOP: DoGameStop(peer, text); break; + case NET_LOBBY_EXIT: DoExit(peer, text); break; + } + + if (text && text != buffer) + delete [] text; + } + + delete msg; + msg = link->GetMessage(); + } +} + +// +--------------------------------------------------------------------+ + +void +NetLobby::Send() +{ +} + +// +-------------------------------------------------------------------+ + +DWORD +NetLobby::GetLag() +{ + if (link) + return link->GetLag(); + + return 0; +} + +// +-------------------------------------------------------------------+ + +void +NetLobby::SetLocalUser(NetUser* user) +{ + if (user != local_user) { + if (local_user) { + delete local_user; + local_user = 0; + } + + if (user) { + local_user = user; + local_user->SetHost(true); + } + } +} + +void +NetLobby::BanUser(NetUser* user) +{ +} + +void +NetLobby::AddUser(NetUser* user) +{ + if (user) { + if (user != local_user && !users.contains(user)) { + users.append(user); + Print("NetLobby User Logged In - name: '%s' id: %d host: %d\n", + user->Name().data(), + user->GetNetID(), + user->IsHost()); + } + } +} + +void +NetLobby::DelUser(NetUser* user) +{ + if (user) { + if (user == local_user) + local_user = 0; + + else + users.remove(user); + + UnmapUnit(user->Name()); + + Print("NetLobby User Logged Out - name: '%s' id: %d host: %d\n", + user->Name().data(), + user->GetNetID(), + user->IsHost()); + + user->SetHost(false); + delete user; + } +} + +int +NetLobby::NumUsers() +{ + int num = 0; + + if (local_user) + num = 1; + + num += users.size(); + + return num; +} + +NetUser* +NetLobby::GetHost() +{ + for (int i = 0; i < users.size(); i++) { + NetUser* u = users[i]; + + if (u->IsHost()) + return u; + } + + return 0; +} + +bool +NetLobby::HasHost() +{ + bool host = false; + + if (local_user && local_user->IsHost()) + host = true; + + for (int i = 0; i < users.size() && !host; i++) { + if (users[i]->IsHost()) + host = true; + } + + if (status > NetServerInfo::LOBBY) + host = true; + + return host; +} + +bool +NetLobby::SetUserHost(NetUser* user, bool host) +{ + bool ok = false; + + if (user && users.contains(user)) { + if (host && !HasHost()) { + user->SetHost(true); + ok = true; + } + else if (!host) { + user->SetHost(false); + ok = true; + } + } + + return ok; +} + +NetUser* +NetLobby::GetLocalUser() +{ + return local_user; +} + +List& +NetLobby::GetUsers() +{ + return users; +} + +List& +NetLobby::GetServerMods() +{ + return server_mods; +} + +NetUser* +NetLobby::FindUserByAddr(const NetAddr& addr) +{ + for (int i = 0; i < users.size(); i++) { + NetUser* u = users[i]; + if (u->GetAddress().IPAddr() == addr.IPAddr()) + return u; + } + + return 0; +} + +NetUser* +NetLobby::FindUserByName(const char* name) +{ + if (local_user && !stricmp(local_user->Name(), name)) + return local_user; + + for (int i = 0; i < users.size(); i++) { + NetUser* u = users[i]; + if (!stricmp(u->Name(), name)) + return u; + } + + return 0; +} + +NetUser* +NetLobby::FindUserByNetID(DWORD id) +{ + for (int i = 0; i < users.size(); i++) { + NetUser* u = users[i]; + if (u->GetNetID() == id) + return u; + } + + return 0; +} + +// +--------------------------------------------------------------------+ + +void +NetLobby::ParseMsg(Text msg, List& params) +{ + params.destroy(); + + BlockReader reader(msg.data(), msg.length()); + Scanner lexer(&reader); + + Token name = lexer.Get(); + + while (name.type() == Token::AlphaIdent) { + Token value = lexer.Get(); + if (value.type() != Token::EOT) { + Text val = value.symbol(); + + if (val[0] == '"' && val[val.length()-1] == '"') { + val = val.substring(1, val.length()-2); + } + + NetLobbyParam* param = new(__FILE__,__LINE__) NetLobbyParam(name.symbol(), val); + params.append(param); + + name = lexer.Get(); + } + + else { + name = Token(Token::EOT); + } + } +} + +// +--------------------------------------------------------------------+ + +bool +NetLobby::Ping() +{ + Sleep(500); + return false; +} + +// +-------------------------------------------------------------------+ + +void +NetLobby::AddChat(NetUser* user, const char* msg, bool route) +{ + if (user && msg && *msg) + chat_log.append(new(__FILE__,__LINE__) NetChatEntry(user, msg)); +} + +List& +NetLobby::GetChat() +{ + return chat_log; +} + +void +NetLobby::ClearChat() +{ + chat_log.destroy(); +} + +// +-------------------------------------------------------------------+ + +List& +NetLobby::GetCampaigns() +{ + return campaigns; +} + +// +-------------------------------------------------------------------+ + +void +NetLobby::AddUnitMap(MissionElement* elem, int index) +{ + if (elem) + unit_map.append(new(__FILE__,__LINE__) NetUnitEntry(elem, index)); +} + +List& +NetLobby::GetUnitMap() +{ + return unit_map; +} + +void +NetLobby::ClearUnitMap() +{ + unit_map.destroy(); +} + +void +NetLobby::MapUnit(int n, const char* user, bool lock) +{ + if (n >= 0 && n < unit_map.size()) { + NetUnitEntry* unit = unit_map[n]; + + if (!lock && unit->GetLocked()) + return; + + if (user && *user) { + for (int i = 0; i < unit_map.size(); i++) { + NetUnitEntry* u = unit_map[i]; + if (u->GetUserName() == user) { + if (!lock && u->GetLocked()) return; + u->SetUserName(""); + } + } + } + + unit->SetUserName(user); + unit->SetLock(lock); + } +} + +void +NetLobby::UnmapUnit(const char* user) +{ + if (user && *user) { + for (int i = 0; i < unit_map.size(); i++) { + NetUnitEntry* u = unit_map[i]; + if (u->GetUserName() == user) { + u->SetUserName(""); + u->SetLock(false); + return; + } + } + } +} + +bool +NetLobby::IsMapped(const char* user) +{ + if (user && *user) { + for (int i = 0; i < unit_map.size(); i++) { + NetUnitEntry* u = unit_map[i]; + if (u->GetUserName() == user) { + return true; + } + } + } + + return false; +} + +// +--------------------------------------------------------------------+ + +void +NetLobby::SelectMission(DWORD id) +{ + if (selected_mission != id) { + selected_mission = id; + ClearUnitMap(); + + int mission_id = selected_mission; + int campaign_id = -1; + Campaign* campaign = 0; + mission = 0; + + campaign_id = mission_id >> NET_CAMPAIGN_SHIFT; + mission_id = mission_id & NET_MISSION_MASK; + + ListIter c_iter = Campaign::GetAllCampaigns(); + while (++c_iter) { + campaign = c_iter.value(); + + if (campaign->GetCampaignId() == campaign_id) { + mission = campaign->GetMission(mission_id); + break; + } + } + + if (campaign && mission) { + Campaign::SelectCampaign(campaign->Name()); + campaign->SetMissionId(mission_id); + + ListIter iter = mission->GetElements(); + while (++iter) { + MissionElement* elem = iter.value(); + + if (elem->IsPlayable()) { + if (elem->Count() == 1) { + AddUnitMap(elem); + } + else { + for (int i = 0; i < elem->Count(); i++) + AddUnitMap(elem, i+1); + } + } + } + } + + else { + selected_mission = 0; + mission = 0; + } + } +} + +// +--------------------------------------------------------------------+ + +bool +NetLobby::IsNetLobbyClient() +{ + if (instance) + return instance->IsClient(); + return false; +} + +bool +NetLobby::IsNetLobbyServer() +{ + if (instance) + return instance->IsServer(); + return false; +} + +// +-------------------------------------------------------------------+ +// +-------------------------------------------------------------------+ +// +-------------------------------------------------------------------+ + +NetUnitEntry::NetUnitEntry(MissionElement* e, int n) + : index(n), lock(false), lives(1), hull(100), role(0) +{ + if (e) { + elem = e->Name(); + iff = e->GetIFF(); + + if (e->GetDesign()) + design = e->GetDesign()->name; + } +} + +NetUnitEntry::NetUnitEntry(const char* e, const char* d, int i, int n) + : elem(e), design(d), iff(i), index(n), lock(false), lives(1), + hull(100), role(0) +{ } + +NetUnitEntry::~NetUnitEntry() +{ } + +Text +NetUnitEntry::GetDescription() const +{ + if (elem) { + static char buffer[1024]; + + sprintf(buffer, "name \"%s\" index %d design \"%s\" iff %d user \"%s\" lives %d hull %d role %d lock %d ", + elem.data(), + index, + design.data(), + iff, + user.data(), + lives, + hull, + role, + lock); + + return buffer; + } + + return "name \"Not Found\" "; +} + +// +--------------------------------------------------------------------+ +// +--------------------------------------------------------------------+ +// +--------------------------------------------------------------------+ + +NetServerInfo::NetServerInfo() + : nplayers(0), hosted(0), status(0), port(0), gameport(0), save(false), ping_time(0) +{ } + diff --git a/Stars45/NetLobby.h b/Stars45/NetLobby.h new file mode 100644 index 0000000..474ebff --- /dev/null +++ b/Stars45/NetLobby.h @@ -0,0 +1,289 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetLobby.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Base Class for Multiplayer Game Lobby classes +*/ + +#ifndef NetLobby_h +#define NetLobby_h + +#include "Types.h" +#include "NetLink.h" +#include "List.h" + +#define NET_CAMPAIGN_SHIFT 12 +#define NET_MISSION_MASK 0xfff +#define NET_DISCONNECT_TIME 30 + +// +-------------------------------------------------------------------+ + +class Campaign; +class Mission; +class MissionElement; +class MissionInfo; +class NetCampaignInfo; +class NetChatEntry; +class NetUser; +class NetUnitEntry; +class NetLobbyParam; +class ModInfo; + +// +--------------------------------------------------------------------+ + +class NetLobby +{ +public: + static const char* TYPENAME() { return "NetLobby"; } + + NetLobby(bool temporary = false); + virtual ~NetLobby(); + + virtual bool IsClient() const { return false; } + virtual bool IsServer() const { return false; } + virtual bool IsActive() const { return active; } + + virtual void ExecFrame(); + virtual void Recv(); + virtual void Send(); + virtual int GetLastError() const { return 0; } + + virtual NetUser* FindUserByAddr(const NetAddr& addr); + virtual NetUser* FindUserByName(const char* name); + virtual NetUser* FindUserByNetID(DWORD id); + + virtual void BanUser(NetUser* user); + virtual void AddUser(NetUser* user); + virtual void DelUser(NetUser* user); + virtual bool SetUserHost(NetUser* user, bool host); + virtual int NumUsers(); + virtual NetUser* GetHost(); + virtual bool HasHost(); + virtual bool IsHost() const { return false; } + virtual List& GetUsers(); + + virtual List& GetServerMods(); + + virtual NetUser* GetLocalUser(); + virtual void SetLocalUser(NetUser* user); + + virtual int GetStatus() const { return status; } + virtual void SetStatus(int s) { status = s; } + + virtual void AddChat(NetUser* user, const char* msg, bool route=true); + virtual List& + GetChat(); + virtual void ClearChat(); + virtual void SaveChat() { } + virtual DWORD GetStartTime() const { return start_time; } + + virtual List& + GetCampaigns(); + + virtual void AddUnitMap(MissionElement* elem, int index=0); + virtual List& + GetUnitMap(); + virtual void ClearUnitMap(); + virtual void MapUnit(int n, const char* user, bool lock=false); + virtual void UnmapUnit(const char* user); + virtual bool IsMapped(const char* user); + + virtual Mission* GetSelectedMission() { return mission; } + virtual DWORD GetSelectedMissionID() const { return selected_mission; } + virtual void SelectMission(DWORD id); + + virtual const Text& GetMachineInfo() { return machine_info; } + virtual WORD GetGamePort() { return 0; } + + // actions: + virtual bool Ping(); + virtual void GameStart() { } + virtual void GameStop() { } + virtual DWORD GetLag(); + + // instance management: + static NetLobby* GetInstance(); + static bool IsNetLobbyClient(); + static bool IsNetLobbyServer(); + +protected: + virtual void DoPing(NetPeer* peer, Text msg) { } + virtual void DoServerInfo(NetPeer* peer, Text msg) { } + virtual void DoServerMods(NetPeer* peer, Text msg) { } + virtual void DoLogin(NetPeer* peer, Text msg) { } + virtual void DoLogout(NetPeer* peer, Text msg) { } + virtual void DoAuthUser(NetPeer* peer, Text msg) { } + virtual void DoUserAuth(NetPeer* peer, Text msg) { } + virtual void DoChat(NetPeer* peer, Text msg) { } + virtual void DoUserList(NetPeer* peer, Text msg) { } + virtual void DoBanUser(NetPeer* peer, Text msg) { } + virtual void DoMissionList(NetPeer* peer, Text msg) { } + virtual void DoMissionSelect(NetPeer* peer, Text msg) { } + virtual void DoMissionData(NetPeer* peer, Text msg) { } + virtual void DoUnitList(NetPeer* peer, Text msg) { } + virtual void DoMapUnit(NetPeer* peer, Text msg) { } + virtual void DoGameStart(NetPeer* peer, Text msg) { } + virtual void DoGameStop(NetPeer* peer, Text msg) { } + virtual void DoExit(NetPeer* peer, Text msg) { } + + virtual void ParseMsg(Text msg, List& params); + + NetLink* link; + NetUser* local_user; + List users; + List chat_log; + List campaigns; + List unit_map; + Text machine_info; + List server_mods; + + bool active; + DWORD last_send_time; + DWORD start_time; + DWORD selected_mission; + Mission* mission; + int status; +}; + +// +-------------------------------------------------------------------+ + +class NetLobbyParam +{ +public: + static const char* TYPENAME() { return "NetLobbyParam"; } + + NetLobbyParam(const char* n, const char* v) : name(n), value(v) { } + + int operator == (const NetLobbyParam& p) const { return name == p.name; } + + Text name; + Text value; +}; + +// +-------------------------------------------------------------------+ + +class NetUnitEntry +{ +public: + static const char* TYPENAME() { return "NetUnitEntry"; } + + NetUnitEntry(MissionElement* elem, int index=0); + NetUnitEntry(const char* elem, const char* design, int iff, int index=0); + ~NetUnitEntry(); + + int operator == (const NetUnitEntry& e) const { return (elem == e.elem) && (index == e.index); } + + Text GetDescription() const; + const Text& GetUserName() const { return user; } + const Text& GetElemName() const { return elem; } + const Text& GetDesign() const { return design; } + int GetIFF() const { return iff; } + int GetIndex() const { return index; } + int GetLives() const { return lives; } + int GetIntegrity() const { return hull; } + int GetMissionRole() const { return role; } + bool GetLocked() const { return lock; } + + void SetUserName(const char* u) { user = u; } + void SetElemName(const char* e) { elem = e; } + void SetDesign(const char* d) { design = d; } + void SetIFF(int i) { iff = i; } + void SetIndex(int i) { index = i; } + void SetLives(int i) { lives = i; } + void SetIntegrity(int i) { hull = i; } + void SetMissionRole(int i) { role = i; } + void SetLock(bool l) { lock = l; } + +private: + Text user; + Text elem; + Text design; + int iff; + int index; + int lives; + int hull; + int role; + bool lock; +}; + +// +--------------------------------------------------------------------+ + +class NetServerInfo +{ +public: + static const char* TYPENAME() { return "NetServerInfo"; } + + enum STATUS { OFFLINE, LOBBY, BRIEFING, ACTIVE, DEBRIEFING, PERSISTENT }; + + NetServerInfo(); + + Text name; + Text hostname; + Text password; + Text type; + NetAddr addr; + WORD port; + WORD gameport; + bool save; + + Text version; + Text machine_info; + int nplayers; + int hosted; + int status; + + DWORD ping_time; +}; + +// +--------------------------------------------------------------------+ + +class NetCampaignInfo +{ +public: + static const char* TYPENAME() { return "NetCampaignInfo"; } + + NetCampaignInfo() : id(0) { } + ~NetCampaignInfo() { } + + int id; + Text name; + + List missions; +}; + +// +--------------------------------------------------------------------+ + +enum NET_LOBBY_MESSAGES { + NET_LOBBY_PING = 0x10, + NET_LOBBY_SERVER_INFO, + NET_LOBBY_SERVER_MODS, + NET_LOBBY_LOGIN, + NET_LOBBY_LOGOUT, + NET_LOBBY_CHAT, + NET_LOBBY_USER_LIST, + NET_LOBBY_BAN_USER, + NET_LOBBY_MISSION_LIST, + NET_LOBBY_MISSION_SELECT, + NET_LOBBY_MISSION_DATA, + NET_LOBBY_UNIT_LIST, + NET_LOBBY_MAP_UNIT, + + NET_LOBBY_AUTH_USER, + NET_LOBBY_USER_AUTH, + + NET_LOBBY_GAME_START, + NET_LOBBY_GAME_STOP, + NET_LOBBY_EXIT +}; + +// +--------------------------------------------------------------------+ + +#endif NetLobby_h + diff --git a/Stars45/NetLobbyClient.cpp b/Stars45/NetLobbyClient.cpp new file mode 100644 index 0000000..6d04add --- /dev/null +++ b/Stars45/NetLobbyClient.cpp @@ -0,0 +1,809 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetLobbyClient.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Stream-oriented network client class +*/ + + +#include "MemDebug.h" +#include "NetLobbyClient.h" +#include "NetClientConfig.h" +#include "NetAuth.h" +#include "NetChat.h" +#include "Campaign.h" +#include "Player.h" +#include "Starshatter.h" +#include "ModInfo.h" + +#include "NetHost.h" +#include "NetPeer.h" +#include "NetLink.h" +#include "NetMsg.h" +#include "NetLayer.h" +#include "FormatUtil.h" + +extern const char* versionInfo; + +// +-------------------------------------------------------------------+ + +NetLobbyClient::NetLobbyClient() + : NetLobby(false), server_id(0), host(false), exit_code(0), temporary(false) +{ + NetHost me; + Text server_name; + WORD port = 11101; + + ping_req_time = 0; + chat_req_time = 0; + user_req_time = 0; + camp_req_time = 0; + unit_req_time = 0; + mods_req_time = 0; + + NetClientConfig* ncc = NetClientConfig::GetInstance(); + if (ncc) { + NetServerInfo* info = ncc->GetSelectedServer(); + + if (info) { + server_name = info->hostname; + addr = info->addr; + port = info->port; + gamepass = info->password; + } + } + + if (server_name.length() && port > 0) { + Print(" '%s' is a client of '%s'\n", me.Name(), server_name); + link = new(__FILE__,__LINE__) NetLink; + server_id = link->AddPeer(NetAddr(server_name, port)); + } + else if (port == 0) { + Print(" '%s' invalid lobby port number %d\n", me.Name(), port); + } + else { + Print(" '%s' is a client without a server\n", me.Name()); + } +} + +NetLobbyClient::NetLobbyClient(const NetAddr& server_addr) + : NetLobby(true), server_id(0), addr(server_addr), host(false), exit_code(0), + temporary(true) +{ + ping_req_time = 0; + chat_req_time = 0; + user_req_time = 0; + camp_req_time = 0; + unit_req_time = 0; + mods_req_time = 0; + + if (addr.IPAddr() != 0) { + link = new(__FILE__,__LINE__) NetLink; + server_id = link->AddPeer(addr); + } +} + +NetLobbyClient::~NetLobbyClient() +{ + missions.destroy(); +} + +// +--------------------------------------------------------------------+ + +void +NetLobbyClient::SendData(int type, Text msg) +{ + if (link && server_id && type > 0 && type < 255) { + if (msg.length()) + link->SendMessage(server_id, (BYTE) type, msg.data(), msg.length(), NetMsg::RELIABLE); + else + link->SendMessage(server_id, (BYTE) type, 0, 0, NetMsg::RELIABLE); + } +} + +// +--------------------------------------------------------------------+ + +void +NetLobbyClient::ExecFrame() +{ + NetLobby::ExecFrame(); + + if (!temporary) { + // check health of server: + NetPeer* s = link->FindPeer(server_id); + if (s && (NetLayer::GetUTC() - s->LastReceiveTime() > NET_DISCONNECT_TIME)) { + exit_code = 2; + } + + // send keep alive ping: + if ((NetLayer::GetUTC() - ping_req_time > NET_DISCONNECT_TIME/3)) { + SendData(NET_LOBBY_SERVER_INFO, Text()); + ping_req_time = NetLayer::GetUTC(); + } + } +} + +// +--------------------------------------------------------------------+ + +bool +NetLobbyClient::Login(bool host_req) +{ + Player* player = Player::GetCurrentPlayer(); + if (!player) + return false; + + host = host_req; + + Text login = "name \""; + login += SafeQuotes(player->Name()); + login += "\" pass \""; + login += SafeQuotes(player->Password()); + login += "\" version \""; + login += versionInfo; + login += "\" "; + + char buffer[256]; + + sprintf(buffer, "host %s rank %d time %d miss %d kill %d loss %d ", + host ? "true" : "false", + player->Rank(), + player->FlightTime(), + player->Missions(), + player->Kills(), + player->Losses()); + + login += buffer; + + login += "sig \""; + login += SafeQuotes(player->Signature()); + login += "\" squad \""; + login += SafeQuotes(player->Squadron()); + login += "\" "; + + if (gamepass.length() > 0) { + login += "gamepass \""; + login += SafeQuotes(gamepass); + login += "\" "; + } + + SendData(NET_LOBBY_LOGIN, login); + ExecFrame(); + return true; +} + +bool +NetLobbyClient::Logout() +{ + if (host) + GameStop(); + + SendData(NET_LOBBY_LOGOUT, Text()); + Sleep(250); + ExecFrame(); + return true; +} + +// +--------------------------------------------------------------------+ + +bool +NetLobbyClient::Ping() +{ + Text no_data; + + SendData(NET_LOBBY_PING, no_data); + Sleep(100); + + SendData(NET_LOBBY_PING, no_data); + Sleep(100); + + SendData(NET_LOBBY_PING, no_data); + Sleep(100); + + SendData(NET_LOBBY_SERVER_INFO, no_data); + Sleep(700); + ExecFrame(); + + return (server_info.status > NetServerInfo::OFFLINE); +} + +void +NetLobbyClient::GameStart() +{ + SendData(NET_LOBBY_GAME_START, Text()); + Sleep(100); + + SetStatus(NetServerInfo::ACTIVE); + Starshatter::GetInstance()->SetGameMode(Starshatter::PREP_MODE); + + // discard unit map selection data so that + // it will be refreshed when we return to + // the lobby after the mission: + + ClearUnitMap(); +} + +void +NetLobbyClient::GameStop() +{ + SendData(NET_LOBBY_GAME_STOP, Text()); + ExecFrame(); + Sleep(100); + + SetStatus(NetServerInfo::LOBBY); +} + +// +--------------------------------------------------------------------+ + +const Text& +NetLobbyClient::GetMachineInfo() +{ + if (server_info.status > NetServerInfo::OFFLINE) + return server_info.machine_info; + + return NetLobby::GetMachineInfo(); +} + +int +NetLobbyClient::GetStatus() const +{ + if (server_info.status > NetServerInfo::OFFLINE) + return server_info.status; + + return NetLobby::GetStatus(); +} + +int +NetLobbyClient::NumUsers() +{ + if (server_info.status > NetServerInfo::OFFLINE) + return server_info.nplayers; + + return NetLobby::NumUsers(); +} + +bool +NetLobbyClient::HasHost() +{ + if (server_info.status > NetServerInfo::OFFLINE) + return server_info.hosted ? true : false; + + return NetLobby::HasHost(); +} + +WORD +NetLobbyClient::GetGamePort() +{ + if (server_info.status > NetServerInfo::OFFLINE) + return server_info.gameport; + + return NetLobby::GetGamePort(); +} + +// +-------------------------------------------------------------------+ + +void +NetLobbyClient::AddChat(NetUser* user, const char* msg, bool route) +{ + if (!msg || !*msg) return; + + char buffer[280]; + sprintf(buffer, "msg \"%s\"", SafeQuotes(msg)); + + SendData(NET_LOBBY_CHAT, buffer); + ExecFrame(); +} + +List& +NetLobbyClient::GetChat() +{ + if (chat_log.size() < 1 && (NetLayer::GetUTC() - chat_req_time > 3)) { + SendData(NET_LOBBY_CHAT, Text()); + chat_req_time = NetLayer::GetUTC(); + } + + return chat_log; +} + +List& +NetLobbyClient::GetUsers() +{ + if (users.size() < 1 && (NetLayer::GetUTC() - user_req_time > 2)) { + SendData(NET_LOBBY_USER_LIST, Text()); + user_req_time = NetLayer::GetUTC(); + } + + return users; +} + +List& +NetLobbyClient::GetServerMods() +{ + if (server_mods.size() < 1 && (NetLayer::GetUTC() - mods_req_time > 2)) { + SendData(NET_LOBBY_SERVER_MODS, Text()); + mods_req_time = NetLayer::GetUTC(); + } + + return server_mods; +} + +List& +NetLobbyClient::GetUnitMap() +{ + bool request = selected_mission && + unit_map.size() < 1 && + (NetLayer::GetUTC() - unit_req_time > 2); + + if (selected_mission && GetStatus() == NetServerInfo::ACTIVE && (NetLayer::GetUTC() - unit_req_time > 5)) + request = true; + + if (request) { + SendData(NET_LOBBY_UNIT_LIST, Text()); + unit_req_time = NetLayer::GetUTC(); + } + + return unit_map; +} + +// +--------------------------------------------------------------------+ + +List& +NetLobbyClient::GetCampaigns() +{ + if (campaigns.size() < 1 && (NetLayer::GetUTC() - camp_req_time > 3)) { + SendData(NET_LOBBY_MISSION_LIST, Text()); + camp_req_time = NetLayer::GetUTC(); + } + + return campaigns; +} + +// +--------------------------------------------------------------------+ + +void +NetLobbyClient::BanUser(NetUser* user) +{ + char buffer[512]; + sprintf(buffer, "user \"%s\"", SafeQuotes(user->Name())); + SendData(NET_LOBBY_BAN_USER, buffer); +} + +// +--------------------------------------------------------------------+ + +void +NetLobbyClient::SelectMission(DWORD id) +{ + char buffer[32]; + sprintf(buffer, "m_id 0x%08x", id); + SendData(NET_LOBBY_MISSION_SELECT, buffer); +} + +void +NetLobbyClient::MapUnit(int n, const char* user, bool lock) +{ + if (user && strlen(user) > 250) + return; + + char buffer[512]; + sprintf(buffer, "id %d user \"%s\" lock %s", + n, SafeQuotes(user), lock ? "true" : "false"); + SendData(NET_LOBBY_MAP_UNIT, buffer); +} + +Mission* +NetLobbyClient::GetSelectedMission() +{ + mission = 0; + + // ask server for mission: + SendData(NET_LOBBY_MISSION_DATA, Text()); + + // wait for answer: + int i = 150; + while (i-- > 0 && !mission) { + Sleep(100); + ExecFrame(); + } + + return mission; +} + +// +--------------------------------------------------------------------+ + +void +NetLobbyClient::DoAuthUser(NetPeer* peer, Text msg) +{ + List params; + ParseMsg(msg, params); + + int level = NetAuth::NET_AUTH_STANDARD; + Text salt; + + for (int i = 0; i < params.size(); i++) { + NetLobbyParam* p = params[i]; + + int num = 0; + sscanf(p->value, "%d", &num); + + if (p->name == "level") { + level = num; + } + + else if (p->name == "salt") { + salt = p->value; + } + } + + Text response = NetAuth::CreateAuthResponse(level, salt); + if (response.length() > 0) + SendData(NET_LOBBY_USER_AUTH, response); +} + +void +NetLobbyClient::DoServerInfo(NetPeer* peer, Text msg) +{ + List params; + ParseMsg(msg, params); + + for (int i = 0; i < params.size(); i++) { + NetLobbyParam* p = params[i]; + + int num = 0; + sscanf(p->value, "%d", &num); + + if (p->name == "info") { + server_info.machine_info = p->value; + } + + else if (p->name == "version") { + server_info.version = p->value; + } + + else if (p->name == "mode") { + server_info.status = num; + } + + else if (p->name == "users") { + server_info.nplayers = num; + } + + else if (p->name == "host") { + server_info.hosted = (p->value == "true"); + } + + else if (p->name == "port") { + server_info.gameport = (WORD) num; + } + } + + params.destroy(); +} + +void +NetLobbyClient::DoChat(NetPeer* peer, Text msg) +{ + List params; + ParseMsg(msg, params); + + int id = 0; + Text user_name; + Text chat_msg; + + for (int i = 0; i < params.size(); i++) { + NetLobbyParam* p = params[i]; + + int num = 0; + sscanf(p->value, "%d", &num); + + if (p->name == "id") + id = num; + + else if (p->name == "user") + user_name = p->value; + + else if (p->name == "msg") + chat_msg = p->value; + } + + params.destroy(); + + // receive chat from server: + if (id && chat_msg.length()) { + NetChatEntry* entry = new(__FILE__,__LINE__) NetChatEntry(id, user_name, chat_msg); + + if (!chat_log.contains(entry)) + chat_log.insertSort(entry); + else + delete entry; // received duplicate + } +} + +void +NetLobbyClient::DoServerMods(NetPeer* peer, Text msg) +{ + mods_req_time = NetLayer::GetUTC() + 3600; + + List params; + ParseMsg(msg, params); + server_mods.destroy(); + + Text name; + Text version; + Text url; + + for (int i = 0; i < params.size(); i++) { + NetLobbyParam* p = params[i]; + + if (p->name == "mod") { + name = p->value; + } + + else if (p->name == "url") { + url = p->value; + } + + else if (p->name == "ver") { + version = p->value; + + ModInfo* info = new(__FILE__,__LINE__) ModInfo(name, version, url); + server_mods.append(info); + } + } + + params.destroy(); +} + +void +NetLobbyClient::DoUserList(NetPeer* peer, Text msg) +{ + List params; + ParseMsg(msg, params); + + users.destroy(); + + Text user_name; + Text host_flag; + Text signature; + Text squadron; + int rank = 0; + int flight_time = 0; + int mission_count = 0; + int kills = 0; + int losses = 0; + + for (int i = 0; i < params.size(); i++) { + NetLobbyParam* p = params[i]; + + int num = 0; + sscanf(p->value, "%d", &num); + + if (p->name == "name") + user_name = p->value; + + else if (p->name == "sig") + signature = p->value; + + else if (p->name == "squad") + squadron = p->value; + + else if (p->name == "rank") + rank = num; + + else if (p->name == "time") + flight_time = num; + + else if (p->name == "miss") + mission_count = num; + + else if (p->name == "kill") + kills = num; + + else if (p->name == "loss") + losses = num; + + else if (p->name == "host") { + host_flag = p->value; + + NetUser* u = new(__FILE__,__LINE__) NetUser(user_name); + u->SetHost((host_flag == "true") ? true : false); + u->SetSignature(signature); + u->SetSquadron(squadron); + u->SetRank(rank); + u->SetFlightTime(flight_time); + u->SetMissions(mission_count); + u->SetKills(kills); + u->SetLosses(losses); + + AddUser(u); + } + } + + params.destroy(); +} + +void +NetLobbyClient::DoMissionList(NetPeer* peer, Text msg) +{ + List params; + ParseMsg(msg, params); + + if (params.size() > 2) { + campaigns.destroy(); + + NetCampaignInfo* c = 0; + MissionInfo* m = 0; + + for (int i = 0; i < params.size(); i++) { + NetLobbyParam* p = params[i]; + + if (p->name == "c_id") { + c = new(__FILE__,__LINE__) NetCampaignInfo; + sscanf(p->value, "0x%x", &c->id); + campaigns.append(c); + + m = 0; + } + + else if (c && p->name == "c_name") { + c->name = p->value; + } + + else if (p->name == "m_id") { + int id = 0; + sscanf(p->value, "0x%x", &id); + + int m_id = id & NET_MISSION_MASK; + int c_id = id >> NET_CAMPAIGN_SHIFT; + + for (int i = 0; i < campaigns.size(); i++) { + NetCampaignInfo* c = campaigns[i]; + if (c->id == c_id) { + m = new(__FILE__,__LINE__) MissionInfo; + m->id = m_id; + c->missions.append(m); + missions.append(m); // for later garbage collection + break; + } + } + } + + else if (m && p->name == "m_name") { + m->name = p->value; + } + + else if (m && p->name == "m_desc") { + m->description = p->value; + } + } + } + + params.destroy(); +} + +void +NetLobbyClient::DoMissionSelect(NetPeer* peer, Text msg) +{ + List params; + ParseMsg(msg, params); + + for (int i = 0; i < params.size(); i++) { + NetLobbyParam* p = params[i]; + + int num = 0; + sscanf(p->value, "0x%x", &num); + + if (p->name == "m_id") { + if (selected_mission != (DWORD) num) { + selected_mission = num; + ClearUnitMap(); + } + } + } + + params.destroy(); +} + +void +NetLobbyClient::DoMissionData(NetPeer* peer, Text msg) +{ + Campaign* c = Campaign::SelectCampaign("Multiplayer Missions"); + + if (c) { + c->LoadNetMission(99999, msg.data()); + mission = c->GetMission(99999); + } + + if (msg.length()) { + FILE* f = ::fopen("multi_mission_recv.def", "w"); + if (f) { + ::fwrite(msg.data(), msg.length(), 1, f); + ::fclose(f); + } + } +} + +void +NetLobbyClient::DoUnitList(NetPeer* peer, Text msg) +{ + List params; + ParseMsg(msg, params); + + if (params.size() > 2) { + ClearUnitMap(); + + Text elem_name; + Text design; + Text user_name; + int iff; + int index; + int lives = 1; + int hull = 100; + int role = 0; + int lock = 0; + + for (int i = 0; i < params.size(); i++) { + NetLobbyParam* p = params[i]; + + if (p->name == "name") { + elem_name = p->value; + } + else if (p->name == "design") { + design = p->value; + } + else if (p->name == "user") { + user_name = p->value; + } + else if (p->name == "index") { + sscanf(p->value, "%d", &index); + } + else if (p->name == "iff") { + sscanf(p->value, "%d", &iff); + } + else if (p->name == "lives") { + sscanf(p->value, "%d", &lives); + } + else if (p->name == "hull") { + sscanf(p->value, "%d", &hull); + } + else if (p->name == "role") { + sscanf(p->value, "%d", &role); + } + else if (p->name == "lock") { + sscanf(p->value, "%d", &lock); + + NetUnitEntry* entry = new(__FILE__,__LINE__) NetUnitEntry(elem_name, design, iff, index); + entry->SetUserName(user_name); + entry->SetLives(lives); + entry->SetIntegrity(hull); + entry->SetMissionRole(role); + entry->SetLock(lock ? true : false); + + unit_map.append(entry); + } + } + } + + params.destroy(); +} + +void +NetLobbyClient::DoMapUnit(NetPeer* peer, Text msg) +{ +} + +void +NetLobbyClient::DoGameStart(NetPeer* peer, Text msg) +{ +} + +void +NetLobbyClient::DoExit(NetPeer* peer, Text msg) +{ + exit_code = 1; +} diff --git a/Stars45/NetLobbyClient.h b/Stars45/NetLobbyClient.h new file mode 100644 index 0000000..9cb4549 --- /dev/null +++ b/Stars45/NetLobbyClient.h @@ -0,0 +1,104 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetLobbyClient.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + UDP-oriented network lobby client class +*/ + + +#ifndef NetLobbyClient_h +#define NetLobbyClient_h + +#include "NetLobby.h" +#include "NetClientConfig.h" + +// +-------------------------------------------------------------------+ + +class NetLobbyClient : public NetLobby +{ +public: + NetLobbyClient(); + NetLobbyClient(const NetAddr& server_addr); + virtual ~NetLobbyClient(); + + int operator == (const NetLobbyClient& c) const { return this == &c; } + + virtual void ExecFrame(); + virtual bool Login(bool host=false); + virtual bool Logout(); + virtual int GetLastError() const { return exit_code; } + + // actions: + virtual bool Ping(); + virtual void GameStart(); + virtual void GameStop(); + + virtual void BanUser(NetUser* user); + + virtual void AddChat(NetUser* user, const char* msg, bool route=true); + virtual List& + GetChat(); + + NetAddr GetServerAddr() const { return addr; } + virtual bool IsHost() const { return host; } + virtual bool IsClient() const { return true; } + + virtual List& GetUsers(); + virtual List& GetCampaigns(); + virtual List& GetUnitMap(); + virtual void MapUnit(int n, const char* user, bool lock=false); + virtual void SelectMission(DWORD id); + virtual Mission* GetSelectedMission(); + + virtual List& GetServerMods(); + + // overrides for ping support: + virtual const Text& GetMachineInfo(); + virtual int GetStatus() const; + virtual int NumUsers(); + virtual bool HasHost(); + virtual WORD GetGamePort(); + +protected: + virtual void SendData(int type, Text msg); + virtual void DoServerInfo(NetPeer* peer, Text msg); + virtual void DoServerMods(NetPeer* peer, Text msg); + virtual void DoAuthUser(NetPeer* peer, Text msg); + virtual void DoChat(NetPeer* peer, Text msg); + virtual void DoUserList(NetPeer* peer, Text msg); + virtual void DoMissionList(NetPeer* peer, Text msg); + virtual void DoMissionSelect(NetPeer* peer, Text msg); + virtual void DoMissionData(NetPeer* peer, Text msg); + virtual void DoUnitList(NetPeer* peer, Text msg); + virtual void DoMapUnit(NetPeer* peer, Text msg); + virtual void DoGameStart(NetPeer* peer, Text msg); + virtual void DoExit(NetPeer* peer, Text msg); + + DWORD server_id; + NetAddr addr; + bool host; + Text gamepass; + int exit_code; + + NetServerInfo server_info; + List missions; + + bool temporary; + DWORD ping_req_time; + DWORD chat_req_time; + DWORD user_req_time; + DWORD camp_req_time; + DWORD unit_req_time; + DWORD mods_req_time; +}; + +// +-------------------------------------------------------------------+ + +#endif NetLobbyClient_h \ No newline at end of file diff --git a/Stars45/NetLobbyDlg.cpp b/Stars45/NetLobbyDlg.cpp new file mode 100644 index 0000000..f725b8c --- /dev/null +++ b/Stars45/NetLobbyDlg.cpp @@ -0,0 +1,475 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetLobbyDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Main Menu Dialog Active Window class +*/ + +#include "MemDebug.h" +#include "NetLobbyDlg.h" +#include "NetUnitDlg.h" +#include "NetClientConfig.h" +#include "MenuScreen.h" +#include "Starshatter.h" +#include "Ship.h" +#include "Player.h" +#include "Campaign.h" + +#include "NetAddr.h" +#include "NetLobbyClient.h" +#include "NetLobbyServer.h" +#include "NetUser.h" +#include "NetChat.h" + +#include "DataLoader.h" +#include "Video.h" +#include "Keyboard.h" +#include "MachineInfo.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +DEF_MAP_CLIENT(NetLobbyDlg, OnCampaignSelect); +DEF_MAP_CLIENT(NetLobbyDlg, OnMissionSelect); +DEF_MAP_CLIENT(NetLobbyDlg, OnApply); +DEF_MAP_CLIENT(NetLobbyDlg, OnCancel); + +// +--------------------------------------------------------------------+ + +NetLobbyDlg::NetLobbyDlg(Screen* s, FormDef& def, MenuScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), manager(mgr), + net_lobby(0) +{ + selected_campaign = 0; + selected_mission = 0; + last_chat = 0; + host_mode = false; + + Init(def); +} + +NetLobbyDlg::~NetLobbyDlg() +{ +} + +// +--------------------------------------------------------------------+ + +void +NetLobbyDlg::RegisterControls() +{ + lst_campaigns = (ComboBox*) FindControl(200); + REGISTER_CLIENT(EID_SELECT, lst_campaigns, NetLobbyDlg, OnCampaignSelect); + + lst_missions = (ListBox*) FindControl(201); + REGISTER_CLIENT(EID_SELECT, lst_missions, NetLobbyDlg, OnMissionSelect); + + txt_desc = FindControl(202); + lst_players = (ListBox*) FindControl(210); + lst_chat = (ListBox*) FindControl(211); + edt_chat = (EditBox*) FindControl(212); + + if (edt_chat) + edt_chat->SetText(""); + + apply = (Button*) FindControl(1); + REGISTER_CLIENT(EID_CLICK, apply, NetLobbyDlg, OnApply); + + cancel = (Button*) FindControl(2); + REGISTER_CLIENT(EID_CLICK, cancel, NetLobbyDlg, OnCancel); +} + +// +--------------------------------------------------------------------+ + +void +NetLobbyDlg::Show() +{ + if (!IsShown()) { + // clear server data: + if (lst_chat) lst_chat->ClearItems(); + if (lst_campaigns) lst_campaigns->ClearItems(); + if (lst_missions) lst_missions->ClearItems(); + if (txt_desc) txt_desc->SetText(""); + if (apply) apply->SetEnabled(false); + + if (lst_missions) { + lst_missions->SetSelectedStyle(ListBox::LIST_ITEM_STYLE_FILLED_BOX); + lst_missions->SetLeading(2); + } + + selected_campaign = 0; + selected_mission = 0; + last_chat = 0; + + FormWindow::Show(); + + net_lobby = NetLobby::GetInstance(); + + if (!net_lobby) { + Starshatter* stars = Starshatter::GetInstance(); + if (stars) + stars->StartLobby(); + + net_lobby = NetLobby::GetInstance(); + } + + if (net_lobby) { + if (net_lobby->IsServer()) { + host_mode = true; + + NetUser* user = net_lobby->GetLocalUser(); + + if (!user) { + Player* player = Player::GetCurrentPlayer(); + if (player) { + user = new(__FILE__,__LINE__) NetUser(player); + user->SetHost(true); + } + else { + ::Print("NetLobbyDlg::Show() Host mode - no current player?\n"); + } + } + + net_lobby->SetLocalUser(user); + } + + SelectMission(); + } + } +} + +// +--------------------------------------------------------------------+ + +void +NetLobbyDlg::ExecFrame() +{ + ExecLobbyFrame(); + + if (Keyboard::KeyDown(VK_RETURN)) { + if (edt_chat && edt_chat->GetText().length() > 0) { + SendChat(edt_chat->GetText()); + edt_chat->SetText(""); + } + } + + GetPlayers(); + GetChat(); + + if (lst_campaigns) { + if (lst_campaigns->NumItems() < 1) + GetMissions(); + else + GetSelectedMission(); + } +} + +// +--------------------------------------------------------------------+ + +void +NetLobbyDlg::ExecLobbyFrame() +{ + if (net_lobby && net_lobby->GetLastError() != 0) { + if (net_lobby->IsClient()) { + Starshatter* stars = Starshatter::GetInstance(); + if (stars) + stars->StopLobby(); + + net_lobby = 0; + manager->ShowNetClientDlg(); + } + } +} + +// +--------------------------------------------------------------------+ + +void +NetLobbyDlg::GetPlayers() +{ + if (!lst_players) return; + + if (net_lobby) { + lst_players->ClearItems(); + + NetUser* u = net_lobby->GetLocalUser(); + if (u) { + Text name = Player::RankAbrv(u->Rank()); + name += " "; + name += u->Name(); + + int count = lst_players->AddItem(u->IsHost() ? "*" : " "); + lst_players->SetItemText(count-1, 1, name); + host_mode = true; + } + + ListIter iter = net_lobby->GetUsers(); + while (++iter) { + NetUser* u = iter.value(); + int count = lst_players->AddItem(u->IsHost() ? "*" : " "); + + Text name = Player::RankAbrv(u->Rank()); + name += " "; + name += u->Name(); + + lst_players->SetItemText(count-1, 1, name); + + if (Player::GetCurrentPlayer()->Name() == u->Name()) + host_mode = u->IsHost(); + } + } +} + +// +--------------------------------------------------------------------+ + +void +NetLobbyDlg::GetChat() +{ + if (!lst_chat) return; + + if (net_lobby) { + int last_item = lst_chat->NumItems() - 1; + int count = 0; + bool added = false; + + ListIter iter = net_lobby->GetChat(); + while (++iter) { + NetChatEntry* c = iter.value(); + + if (count++ > last_item) { + int n = lst_chat->AddItem(c->GetUser()); + lst_chat->SetItemText(n-1, 1, c->GetMessage()); + added = true; + } + } + + if (added) + lst_chat->EnsureVisible(lst_chat->NumItems()+1); + } +} + +void +NetLobbyDlg::SendChat(Text msg) +{ + if (msg.length() < 1) return; + + Player* player = Player::GetCurrentPlayer(); + + if (msg[0] >= '0' && msg[0] <= '9') { + if (player) { + Text macro = player->ChatMacro(msg[0] - '0'); + + if (macro.length()) + msg = macro; + } + } + + if (net_lobby) + net_lobby->AddChat(net_lobby->GetLocalUser(), msg); +} + +// +--------------------------------------------------------------------+ + +void +NetLobbyDlg::GetMissions() +{ + if (!lst_campaigns || !lst_missions) + return; + + if (net_lobby) { + lst_campaigns->ClearItems(); + + List& campaigns = net_lobby->GetCampaigns(); + + if (campaigns.size()) { + ListIter c_iter = campaigns; + while (++c_iter) { + NetCampaignInfo* c = c_iter.value(); + lst_campaigns->AddItem(c->name); + } + + lst_campaigns->SetSelection(0); + NetCampaignInfo* c = campaigns[0]; + + lst_missions->ClearItems(); + + ListIter m_iter = c->missions; + while (++m_iter) { + MissionInfo* m = m_iter.value(); + lst_missions->AddItem(m->name); + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +NetLobbyDlg::GetSelectedMission() +{ + if (!lst_campaigns || !lst_missions) return; + + if (net_lobby) { + if (net_lobby->GetSelectedMissionID()) { + int id = net_lobby->GetSelectedMissionID(); + + selected_campaign = id >> NET_CAMPAIGN_SHIFT; + selected_mission = id & NET_MISSION_MASK; + + List& campaigns = net_lobby->GetCampaigns(); + + for (int i = 0; i < campaigns.size(); i++) { + NetCampaignInfo* c = campaigns[i]; + + if (c->id == selected_campaign) { + lst_campaigns->SetSelection(i); + OnCampaignSelect(0); + + for (int j = 0; j < c->missions.size(); j++) { + MissionInfo* m = c->missions[j]; + + if (m->id == selected_mission) { + lst_missions->SetSelected(j); + OnMissionSelect(0); + } + } + } + } + } + else if (selected_campaign) { + selected_campaign = 0; + selected_mission = 0; + } + + lst_campaigns->SetEnabled(selected_campaign == 0); + lst_missions->SetEnabled(selected_mission == 0); + + if (!host_mode) + apply->SetEnabled(selected_mission != 0); + } +} + +// +--------------------------------------------------------------------+ + +void +NetLobbyDlg::SelectMission() +{ + if (!lst_campaigns || !lst_missions) return; + + bool selected = false; + + if (net_lobby) { + int c_index = lst_campaigns->GetSelectedIndex(); + int m_index = lst_missions->GetSelection(); + + List& campaigns = net_lobby->GetCampaigns(); + + if (c_index >= 0 && c_index < campaigns.size() && m_index >= 0) { + NetCampaignInfo* c = campaigns[c_index]; + + if (m_index < c->missions.size()) { + MissionInfo* m = c->missions[m_index]; + + DWORD id = (c->id << NET_CAMPAIGN_SHIFT) + + (m->id & NET_MISSION_MASK); + + net_lobby->SelectMission(id); + selected = true; + } + } + + if (!selected) + net_lobby->SelectMission(0); + } +} + +// +--------------------------------------------------------------------+ + +void +NetLobbyDlg::OnCampaignSelect(AWEvent* event) +{ + if (net_lobby) { + List& campaigns = net_lobby->GetCampaigns(); + + if (lst_campaigns && lst_missions && campaigns.size()) { + int index = lst_campaigns->GetSelectedIndex(); + + if (index >= 0 && index < campaigns.size()) { + NetCampaignInfo* c = campaigns[index]; + + lst_missions->ClearItems(); + txt_desc->SetText(""); + + ListIter iter = c->missions; + while (++iter) { + MissionInfo* m = iter.value(); + lst_missions->AddItem(m->name); + } + } + } + } +} + +void +NetLobbyDlg::OnMissionSelect(AWEvent* event) +{ + if (net_lobby) { + List& campaigns = net_lobby->GetCampaigns(); + + if (lst_campaigns && lst_missions && txt_desc && campaigns.size()) { + txt_desc->SetText(""); + + if (host_mode && apply) + apply->SetEnabled(false); + + int c_index = lst_campaigns->GetSelectedIndex(); + int m_index = lst_missions->GetSelection(); + + if (c_index >= 0 && c_index < campaigns.size()) { + NetCampaignInfo* c = campaigns[c_index]; + + if (m_index >= 0 && m_index < c->missions.size()) { + MissionInfo* m = c->missions[m_index]; + txt_desc->SetText(m->description); + + if (host_mode && apply) + apply->SetEnabled(true); + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +NetLobbyDlg::OnApply(AWEvent* event) +{ + if (host_mode) + SelectMission(); + + manager->ShowNetUnitDlg(); + + NetUnitDlg* unit_dlg = manager->GetNetUnitDlg(); + if (unit_dlg) + unit_dlg->SetHostMode(host_mode); +} + +void +NetLobbyDlg::OnCancel(AWEvent* event) +{ + if (net_lobby) { + net_lobby->SelectMission(0); + net_lobby = 0; + + Starshatter* stars = Starshatter::GetInstance(); + if (stars) + stars->StopLobby(); + } + + manager->ShowNetClientDlg(); +} diff --git a/Stars45/NetLobbyDlg.h b/Stars45/NetLobbyDlg.h new file mode 100644 index 0000000..caf7be3 --- /dev/null +++ b/Stars45/NetLobbyDlg.h @@ -0,0 +1,89 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetLobbyDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Main Menu Dialog Active Window class +*/ + +#ifndef NetLobbyDlg_h +#define NetLobbyDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "Bitmap.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "EditBox.h" +#include "Font.h" + +// +--------------------------------------------------------------------+ + +class MenuScreen; +class NetClientConfig; +class NetLobby; +class NetCampaignInfo; +class MissionInfo; +class NetChatEntry; +class NetUser; + +// +--------------------------------------------------------------------+ + +class NetLobbyDlg : public FormWindow +{ +public: + NetLobbyDlg(Screen* s, FormDef& def, MenuScreen* mgr); + virtual ~NetLobbyDlg(); + + virtual void RegisterControls(); + virtual void Show(); + virtual void ExecFrame(); + + // Operations: + virtual void OnCampaignSelect(AWEvent* event); + virtual void OnMissionSelect(AWEvent* event); + + virtual void OnApply(AWEvent* event); + virtual void OnCancel(AWEvent* event); + + virtual void ExecLobbyFrame(); + +protected: + virtual void GetPlayers(); + virtual void GetChat(); + virtual void GetMissions(); + virtual void GetSelectedMission(); + virtual void SendChat(Text msg); + virtual void SelectMission(); + + MenuScreen* manager; + + ComboBox* lst_campaigns; + ListBox* lst_missions; + ActiveWindow* txt_desc; + ListBox* lst_players; + ListBox* lst_chat; + EditBox* edt_chat; + + Button* apply; + Button* cancel; + + NetLobby* net_lobby; + + int selected_campaign; + int selected_mission; + int last_chat; + bool host_mode; +}; + +// +--------------------------------------------------------------------+ + +#endif NetLobbyDlg_h + diff --git a/Stars45/NetLobbyServer.cpp b/Stars45/NetLobbyServer.cpp new file mode 100644 index 0000000..d66b209 --- /dev/null +++ b/Stars45/NetLobbyServer.cpp @@ -0,0 +1,1359 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetLobbyServer.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + NetLink Engine for Multiplayer Lobby +*/ + +#include "MemDebug.h" +#include "NetLobbyServer.h" +#include "NetServerConfig.h" +#include "NetClientConfig.h" +#include "NetBrokerClient.h" +#include "NetAuth.h" +#include "NetChat.h" +#include "NetUser.h" +#include "Campaign.h" +#include "Mission.h" +#include "StarServer.h" +#include "Starshatter.h" +#include "StarServer.h" +#include "ShipDesign.h" +#include "Sim.h" + +#include "ModConfig.h" +#include "ModInfo.h" + +#include "NetGame.h" +#include "NetPlayer.h" +#include "NetUtil.h" + +#include "NetPeer.h" +#include "NetLayer.h" +#include "NetHost.h" +#include "NetMsg.h" + +#include "MachineInfo.h" +#include "Game.h" +#include "FormatUtil.h" + +extern const char* versionInfo; + +// +-------------------------------------------------------------------+ + +static NetLobbyServer* net_lobby_server = 0; + +NetLobbyServer::NetLobbyServer() + : announce_time(0), server_config(0), motd_index(1) +{ + status = NetServerInfo::LOBBY; + server_name = Text("Starshatter NetLobbyServer ") + versionInfo; + start_time = NetLayer::GetUTC(); + + selected_mission = 0; + + WORD server_port = 11100; + + server_config = NetServerConfig::GetInstance(); + if (server_config) { + server_name = server_config->Name(); + server_port = server_config->GetLobbyPort(); + server_mission = server_config->GetMission(); + + NetAuth::SetAuthLevel(server_config->GetAuthLevel()); + + server_addr = NetAddr(NetHost().Address().IPAddr(), server_port); + link = new(__FILE__,__LINE__) NetLink(server_addr); + } + + LoadMOTD(); + + StarServer* star_server = StarServer::GetInstance(); + DWORD mission_id = 0; + + // only one mission: + if (star_server && server_mission.length() > 0) { + NetCampaignInfo* c = new(__FILE__,__LINE__) NetCampaignInfo; + c->id = Campaign::MULTIPLAYER_MISSIONS; + c->name = "Persistent Multiplayer"; + campaigns.append(c); + + ListIter c_iter = Campaign::GetAllCampaigns(); + while (++c_iter && !mission_id) { + Campaign* campaign = c_iter.value(); + + if (campaign->GetCampaignId() == Campaign::MULTIPLAYER_MISSIONS) { + ListIter m_iter = campaign->GetMissionList(); + while (++m_iter && !mission_id) { + MissionInfo* m = m_iter.value(); + + if (m->script == server_mission) { + c->missions.append(m); + mission_id = (Campaign::MULTIPLAYER_MISSIONS << NET_CAMPAIGN_SHIFT) + m->id; + + SelectMission(mission_id); + star_server->SetGameMode(StarServer::LOAD_MODE); + + // lock in mission: + SetStatus(NetServerInfo::PERSISTENT); + } + } + } + } + } + + // player host may select mission: + if (!mission_id) { + campaigns.destroy(); + + ListIter c_iter = Campaign::GetAllCampaigns(); + while (++c_iter) { + Campaign* campaign = c_iter.value(); + + if (campaign->GetCampaignId() >= Campaign::MULTIPLAYER_MISSIONS) { + NetCampaignInfo* c = new(__FILE__,__LINE__) NetCampaignInfo; + c->id = campaign->GetCampaignId(); + c->name = campaign->Name(); + campaigns.append(c); + + ListIter m_iter = campaign->GetMissionList(); + while (++m_iter) { + MissionInfo* m = m_iter.value(); + c->missions.append(m); + } + } + } + } + + ModConfig* config = ModConfig::GetInstance(); + List& mods = config->GetModInfoList(); + + server_mods.clear(); + server_mods.append(mods); + + net_lobby_server = this; +} + +NetLobbyServer::~NetLobbyServer() +{ + ListIter iter = users; + while (++iter) { + NetUser* u = iter.value(); + SendData(u, NET_LOBBY_EXIT, Text()); + ExecFrame(); + } + + Sleep(500); + + unit_map.destroy(); + chat_log.destroy(); + users.destroy(); + motd.destroy(); + + if (net_lobby_server == this) + net_lobby_server = 0; +} + +NetLobbyServer* +NetLobbyServer::GetInstance() +{ + return net_lobby_server; +} + +// +--------------------------------------------------------------------+ + +void +NetLobbyServer::LoadMOTD() +{ + motd.destroy(); + + FILE* f = fopen("motd.txt", "r"); + + if (f) { + char line[256]; + + while (fgets(line, 256, f)) { + int n = strlen(line) - 1; + + while (n >= 0 && isspace(line[n])) + line[n--] = 0; + + motd.append(new(__FILE__,__LINE__) Text(line)); + } + } +} + +void +NetLobbyServer::SendMOTD(NetUser* user) +{ + if (motd.size() < 1) return; + + char buffer[512]; + + for (int i = 0; i < motd.size(); i++) { + Text* line = motd[i]; + + sprintf(buffer, "id %d user \" \" msg \"%s\"", + motd_index++, *line); + + SendData(user, NET_LOBBY_CHAT, buffer); + } + + sprintf(buffer, "id %d user \" \" msg \" \"", motd_index++); + SendData(user, NET_LOBBY_CHAT, buffer); +} + +void +NetLobbyServer::SendMods(NetUser* user) +{ + char buffer[300]; + + ModConfig* config = ModConfig::GetInstance(); + List& mods = config->GetModInfoList(); + + if (mods.size() < 1) return; + + for (int i = 0; i < mods.size(); i++) { + ModInfo* info = mods[i]; + + sprintf(buffer, "id %d user \"Enabled Mods:\" msg \"%d. '%s' ", + motd_index++, i+1, info->Name().data()); + + Text msg = buffer; + + if (info->Version().length() > 0) { + msg += "version "; + msg += info->Version().data(); + } + + if (info->URL().length() > 0) { + msg += " - "; + msg += info->URL().data(); + } + + msg += "\""; + + SendData(user, NET_LOBBY_CHAT, msg); + } + + sprintf(buffer, "id %d user \" \" msg \" \"", motd_index++); + SendData(user, NET_LOBBY_CHAT, buffer); +} + +// +--------------------------------------------------------------------+ + +void +NetLobbyServer::ExecFrame() +{ + NetLobby::ExecFrame(); + + if (announce_time == 0 || Game::RealTime() - announce_time > 200000) { + GameOn(); + announce_time = Game::RealTime(); + } + + if (GetStatus() == NetServerInfo::BRIEFING) { + NetGame* net_game = NetGame::GetInstance(); + + if (net_game && net_game->NumPlayers() > 0) { + SetStatus(NetServerInfo::ACTIVE); + } + } + + StarServer* star_server = StarServer::GetInstance(); + DWORD mission_id = 0; + + // restart persistent mission? + if (star_server && + star_server->GetGameMode() == StarServer::MENU_MODE && + server_mission.length() > 0) { + + NetCampaignInfo* c = campaigns.last(); + + if (!c || c->name != "Persistent Multiplayer") { + c = new(__FILE__,__LINE__) NetCampaignInfo; + c->id = Campaign::MULTIPLAYER_MISSIONS; + c->name = "Persistent Multiplayer"; + campaigns.append(c); + } + else { + c->missions.clear(); + } + + ListIter c_iter = Campaign::GetAllCampaigns(); + while (++c_iter && !mission_id) { + Campaign* campaign = c_iter.value(); + + if (campaign->GetCampaignId() == Campaign::MULTIPLAYER_MISSIONS) { + ListIter m_iter = campaign->GetMissionList(); + while (++m_iter && !mission_id) { + MissionInfo* m = m_iter.value(); + + if (m->script == server_mission) { + c->missions.append(m); + mission_id = (Campaign::MULTIPLAYER_MISSIONS << NET_CAMPAIGN_SHIFT) + m->id; + + // unlock old mission: + SetStatus(NetServerInfo::LOBBY); + + SelectMission(mission_id); + + if (star_server->GetGameMode() == StarServer::MENU_MODE) { + star_server->SetGameMode(StarServer::LOAD_MODE); + } + + // lock in new mission: + SetStatus(NetServerInfo::PERSISTENT); + } + } + } + } + } + + CheckSessions(); +} + +// +-------------------------------------------------------------------+ + +void +NetLobbyServer::SendData(NetUser* dst, int type, Text msg) +{ + if (link && dst && type > 0 && type < 255) { + if (msg.length()) + link->SendMessage(dst->GetNetID(), (BYTE) type, msg.data(), msg.length(), NetMsg::RELIABLE); + else + link->SendMessage(dst->GetNetID(), (BYTE) type, 0, 0, NetMsg::RELIABLE); + } +} + +// +-------------------------------------------------------------------+ + +void +NetLobbyServer::CheckSessions() +{ + if (!link) + return; + + bool dropped = false; + + ListIter u_iter = users; + while (++u_iter) { + NetUser* u = u_iter.value(); + NetPeer* p = link->FindPeer(u->GetNetID()); + + if (p && (NetLayer::GetUTC() - p->LastReceiveTime()) > NET_DISCONNECT_TIME) { + // check game peer for activity: + NetGame* game = NetGame::GetInstance(); + NetPlayer* player = 0; + NetPeer* p2 = 0; + + if (game) { + player = game->FindPlayerByName(u->Name()); + + if (player) { + p2 = game->GetPeer(player); + + if (p2 && (NetLayer::GetUTC() - p2->LastReceiveTime()) < NET_DISCONNECT_TIME) { + p->SetLastReceiveTime(p2->LastReceiveTime()); + continue; + } + } + else { + ::Print("NetLobbyServer::CheckSessions() Could not find player for '%s'\n", u->Name().data()); + } + } + else { + ::Print("NetLobbyServer::CheckSessions() Could not find net game for '%s'\n", u->Name().data()); + } + + // announce drop: + char timestr[64]; + FormatTime(timestr, Game::RealTime()/1000); + Print("NetLobbyServer: Dropped inactive connection '%s' %s\n", + u->Name().data(), timestr); + + if (u->IsHost()) { + Print(" User was host - ending net game.\n"); + GameStop(); + } + + u_iter.removeItem(); // first remove user from list + NetLobby::UnmapUnit(u->Name()); // then unmap unit + delete u; // now it is safe to discard the inactive user + + dropped = true; + } + } + + if (dropped) { + SendUsers(); + SendUnits(); + } +} + +// +-------------------------------------------------------------------+ + +void +NetLobbyServer::GameStart() +{ + if (status < NetServerInfo::ACTIVE) { + SetStatus(NetServerInfo::BRIEFING); + + if (Starshatter::GetInstance()) { + Starshatter::GetInstance()->SetGameMode(Starshatter::PREP_MODE); + } + else { + StarServer* s = StarServer::GetInstance(); + if (s && s->GetGameMode() == StarServer::MENU_MODE) { + s->SetGameMode(StarServer::LOAD_MODE); + } + } + } +} + +void +NetLobbyServer::GameStop() +{ + if (GetStatus() != NetServerInfo::PERSISTENT) { + SetStatus(NetServerInfo::LOBBY); + + StarServer* s = StarServer::GetInstance(); + if (s && s->GetGameMode() != StarServer::MENU_MODE) { + s->SetGameMode(StarServer::MENU_MODE); + } + } +} + +// +-------------------------------------------------------------------+ + +void +NetLobbyServer::BanUser(NetUser* user) +{ + if (user && !user->IsHost()) { + ::Print("NetLobbyServer::BanUser name '%s' addr %d.%d.%d.%d\n", + user->Name().data(), + user->GetAddress().B1(), + user->GetAddress().B2(), + user->GetAddress().B3(), + user->GetAddress().B4()); + + SendData(user, NET_LOBBY_EXIT, Text()); + + if (server_config) + server_config->BanUser(user); + + DelUser(user); + } +} + +void +NetLobbyServer::AddUser(NetUser* user) +{ + if (server_config && server_config->IsUserBanned(user)) { + delete user; + return; + } + + NetLobby::AddUser(user); + SendUsers(); +} + +void +NetLobbyServer::DelUser(NetUser* user) +{ + NetLobby::DelUser(user); + SendUsers(); +} + +void +NetLobbyServer::SendUsers() +{ + Text content; + + ListIter u_iter = users; + while (++u_iter) { + NetUser* u = u_iter.value(); + content += u->GetDescription(); + } + + u_iter.reset(); + + while (++u_iter) { + NetUser* u = u_iter.value(); + SendData(u, NET_LOBBY_USER_LIST, content); + } +} + +// +-------------------------------------------------------------------+ + +void +NetLobbyServer::RequestAuth(NetUser* user) +{ + if (user) { + Text request = NetAuth::CreateAuthRequest(user); + + if (request.length() > 0) + SendData(user, NET_LOBBY_AUTH_USER, request); + } +} + +// +-------------------------------------------------------------------+ + +void +NetLobbyServer::AddChat(NetUser* user, const char* msg, bool route) +{ + NetChatEntry* entry = 0; + + if (user && msg && *msg) { + bool msg_ok = false; + const char* p = msg; + + while (*p && !msg_ok) { + if (!isspace(*p++)) + msg_ok = true; + } + + if (msg_ok) { + entry = new(__FILE__,__LINE__) NetChatEntry(user, msg); + + chat_log.append(entry); + + // forward to all clients: + if (users.size()) { + char buffer[768]; + char msg_buf[256]; + char usr_buf[256]; + + // safe quotes uses a static buffer, + // so make sure to save copies of the + // results when using more than one in + // a function call... + + strcpy(msg_buf, SafeQuotes(msg)); + strcpy(usr_buf, SafeQuotes(user->Name())); + + sprintf(buffer, "id %d user \"%s\" msg \"%s\"", + entry->GetID(), usr_buf, msg_buf); + + ListIter iter = users; + while (++iter) { + NetUser* u = iter.value(); + SendData(u, NET_LOBBY_CHAT, buffer); + } + + if (route) { + // send to active game: + NetUtil::SendChat(0xffff, usr_buf, msg_buf); + } + } + } + } +} + +void +NetLobbyServer::ClearChat() +{ + NetLobby::ClearChat(); +} + +void +NetLobbyServer::SaveChat() +{ + FILE* f = fopen("chat.txt", "w"); + if (f) { + for (int i = 0; i < chat_log.size(); i++) { + NetChatEntry* c = chat_log[i]; + fprintf(f, "%08x [%s] %s\n", + c->GetTime(), + c->GetUser().data(), + c->GetMessage().data()); + } + + fclose(f); + } +} + +// +-------------------------------------------------------------------+ + +void +NetLobbyServer::SelectMission(DWORD id) +{ + if (GetStatus() == NetServerInfo::PERSISTENT) + return; + + NetLobby::SelectMission(id); + + // inform all users of the selection: + char buffer[32]; + sprintf(buffer, "m_id 0x%08x", selected_mission); + + ListIter iter = users; + while (++iter) { + NetUser* u = iter.value(); + SendData(u, NET_LOBBY_MISSION_SELECT, buffer); + } +} + +// +-------------------------------------------------------------------+ + +List& +NetLobbyServer::GetUnitMap() +{ + if (!mission) { + unit_map.destroy(); + return unit_map; + } + + List units; + ListIter iter = mission->GetElements(); + int i = 0; + + Sim* sim = Sim::GetSim(); + if (sim && sim->GetElements().size() > 0) + iter = sim->GetMissionElements(); + + // create new entries for the playable elements in the mission or simulation: + while (++iter) { + MissionElement* elem = iter.value(); + + if (elem->IsPlayable()) { + NetUnitEntry* u = 0; + if (elem->Count() == 1) { + u = new(__FILE__,__LINE__) NetUnitEntry(elem, 0); + u->SetLives(elem->RespawnCount() + 1); + u->SetMissionRole(elem->MissionRole()); + u->SetIFF(elem->GetIFF()); + + if (elem->GetDesign()) + u->SetDesign(elem->GetDesign()->name); + + if (elem->Ships().size() > 0) { + MissionShip* s = elem->Ships()[0]; + u->SetIntegrity((int) s->Integrity()); + } + units.append(u); + } + else { + for (int i = 0; i < elem->Count(); i++) { + u = new(__FILE__,__LINE__) NetUnitEntry(elem, i+1); + u->SetMissionRole(elem->MissionRole()); + u->SetIFF(elem->GetIFF()); + + if (elem->GetDesign()) + u->SetDesign(elem->GetDesign()->name); + + if (elem->Ships().size() > i) { + MissionShip* s = elem->Ships()[i]; + u->SetLives(s->Respawns() + 1); + u->SetIntegrity((int) s->Integrity()); + } + units.append(u); + } + } + } + } + + // match new entries with any existing map entries: + if (unit_map.size()) { + for (i = 0; i < units.size(); i++) { + NetUnitEntry* e_new = units[i]; + NetUnitEntry* e_old = unit_map.find(e_new); + + if (e_old) { + e_new->SetUserName(e_old->GetUserName()); + e_new->SetLock(e_old->GetLocked()); + } + } + } + + // rewrite the unit map with the new entries: + ClearUnitMap(); + for (i = 0; i < units.size(); i++) { + unit_map.append(units[i]); + } + + return unit_map; +} + +void +NetLobbyServer::MapUnit(int n, const char* user, bool lock) +{ + NetLobby::MapUnit(n, user, lock); + + Text reply; + + ListIter map_iter = GetUnitMap(); + while (++map_iter) { + NetUnitEntry* unit = map_iter.value(); + reply += unit->GetDescription(); + } + + ListIter u_iter = users; + while (++u_iter) { + NetUser* u = u_iter.value(); + SendData(u, NET_LOBBY_UNIT_LIST, reply); + } +} + +void +NetLobbyServer::UnmapUnit(const char* user) +{ + NetLobby::UnmapUnit(user); + + Text reply; + + ListIter map_iter = GetUnitMap(); + while (++map_iter) { + NetUnitEntry* unit = map_iter.value(); + reply += unit->GetDescription(); + } + + ListIter u_iter = users; + while (++u_iter) { + NetUser* u = u_iter.value(); + SendData(u, NET_LOBBY_UNIT_LIST, reply); + } +} + +void +NetLobbyServer::SendUnits() +{ + Text content; + + ListIter map_iter = GetUnitMap(); + while (++map_iter) { + NetUnitEntry* unit = map_iter.value(); + content += unit->GetDescription(); + } + + ListIter u_iter = users; + while (++u_iter) { + NetUser* u = u_iter.value(); + SendData(u, NET_LOBBY_UNIT_LIST, content); + } +} + +// +-------------------------------------------------------------------+ + +Text +NetLobbyServer::Serialize(Mission* m, NetUser* user) +{ + Text s; + + if (!m || !user) + return s; + + NetUnitEntry* unit = 0; + + ListIter u_iter = GetUnitMap(); + while (++u_iter && !unit) { + NetUnitEntry* u = u_iter.value(); + if (u->GetUserName() == user->Name()) + unit = u; + } + + if (unit) + s = m->Serialize(unit->GetElemName(), unit->GetIndex()); + + return s; +} + +Mission* +NetLobbyServer::GetSelectedMission() +{ + if (mission) { + Text content = Serialize(mission, GetLocalUser()); + Campaign* c = Campaign::SelectCampaign("Multiplayer Missions"); + + if (c) { + c->LoadNetMission(99999, content.data()); + return c->GetMission(99999); + } + } + + return mission; +} + +// +-------------------------------------------------------------------+ + +void +NetLobbyServer::GameOn() +{ + NetHost host; + const char* type = "Starshatter"; + const char* password = "No"; + char address[32]; + + strcpy(address, "0"); + + if (server_config) { + if (server_config->GetGameType() == NetServerConfig::NET_GAME_PRIVATE) + return; + + if (server_config->GetGameType() == NetServerConfig::NET_GAME_LAN) { + type = "Starshatter-LAN"; + sprintf(address, "%d.%d.%d.%d", + host.Address().B1(), + host.Address().B2(), + host.Address().B3(), + host.Address().B4()); + } + else { + type = "Starshatter"; + sprintf(address, "0.0.0.0"); + } + + if (server_config->GetGamePass().length() > 0) + password = "Yes"; + } + + NetBrokerClient::GameOn(server_name, + type, + address, + server_addr.Port(), + password); +} + +void +NetLobbyServer::GameOff() +{ +} + +// +-------------------------------------------------------------------+ + +void +NetLobbyServer::DoPing(NetPeer* peer, Text s) +{ } + +void +NetLobbyServer::DoServerInfo(NetPeer* peer, Text s) +{ + if (peer && peer->NetID()) { + char buffer[1024]; + WORD gameport = 11101; + + if (server_config) + gameport = server_config->GetGamePort(); + + sprintf(buffer, "info \"%s\" version \"%s\" mode %d users %d host %s port %d", + MachineInfo::GetShortDescription(), + versionInfo, + GetStatus(), + NumUsers(), + HasHost() ? "true" : "false", + gameport); + + link->SendMessage(peer->NetID(), (BYTE) NET_LOBBY_SERVER_INFO, buffer, strlen(buffer), NetMsg::RELIABLE); + } +} + +void +NetLobbyServer::DoServerMods(NetPeer* peer, Text s) +{ + if (peer && peer->NetID()) { + Text response; + ModConfig* config = ModConfig::GetInstance(); + List& mods = config->GetModInfoList(); + ListIter mod_iter = mods; + + char buffer[32]; + sprintf(buffer, "num %d ", mods.size()); + response += buffer; + + while (++mod_iter) { + ModInfo* info = mod_iter.value(); + + response += "mod \""; + response += info->Name(); + response += "\" url \""; + response += info->URL(); + response += "\" ver \""; + response += info->Version(); + response += "\" "; + } + + link->SendMessage(peer->NetID(), (BYTE) NET_LOBBY_SERVER_MODS, response, response.length(), NetMsg::RELIABLE); + } +} + +// +-------------------------------------------------------------------+ + +void +NetLobbyServer::DoLogin(NetPeer* peer, Text msg) +{ + List params; + ParseMsg(msg, params); + + Text name; + Text pass; + Text host; + Text gamepass; + Text signature; + Text squadron; + Text version; + int rank = 0; + int flight_time = 0; + int missions = 0; + int kills = 0; + int losses = 0; + + for (int i = 0; i < params.size(); i++) { + NetLobbyParam* p = params[i]; + + int num = 0; + sscanf(p->value, "%d", &num); + + if (p->name == "name") + name = p->value; + + else if (p->name == "pass") + pass = p->value; + + else if (p->name == "gamepass") + gamepass = p->value; + + else if (p->name == "host") + host = p->value; + + else if (p->name == "sig") + signature = p->value; + + else if (p->name == "squad") + squadron = p->value; + + else if (p->name == "version") + version = p->value; + + else if (p->name == "rank") + rank = num; + + else if (p->name == "time") + flight_time = num; + + else if (p->name == "miss") + missions = num; + + else if (p->name == "kill") + kills = num; + + else if (p->name == "loss") + losses = num; + } + + params.destroy(); + + // first check the game version: + if (version != versionInfo) { + Print("NetLobbyServer - user '%s' tried to login with invalid game version '%s'\n", + name.data(), version.data()); + + return; + } + + // next check the game password: + if (server_config && server_config->GetGamePass().length() > 0) { + if (gamepass != server_config->GetGamePass()) { + Print("NetLobbyServer - user '%s' tried to login with invalid game password '%s'\n", + name.data(), gamepass.data()); + + return; + } + } + + // now try to log the user in: + NetUser* pre_existing = FindUserByName(name); + + // is user already logged in? + if (pre_existing) { + if (pre_existing->Pass() == pass && + pre_existing->GetAddress().IPAddr() == peer->Address().IPAddr()) { + } + } + + // otherwise, create a new user: + else { + NetUser* user = new(__FILE__,__LINE__) NetUser(name); + user->SetAddress(peer->Address()); + user->SetNetID(peer->NetID()); + user->SetPass(pass); + user->SetSignature(signature); + user->SetSquadron(squadron); + user->SetRank(rank); + user->SetFlightTime(flight_time); + user->SetMissions(missions); + user->SetKills(kills); + user->SetLosses(losses); + + if (host == "true" && !HasHost()) + user->SetHost(true); + + AddUser(user); + RequestAuth(user); + SendMOTD(user); + SendMods(user); + } +} + +void +NetLobbyServer::DoLogout(NetPeer* peer, Text msg) +{ + NetUser* user = FindUserByNetID(peer->NetID()); + + if (user) { + if (user->IsHost()) + GameStop(); + + DelUser(user); + } +} + +void +NetLobbyServer::DoUserAuth(NetPeer* peer, Text msg) +{ + NetUser* user = FindUserByNetID(peer->NetID()); + + if (user) { + NetAuth::AuthUser(user, msg); + + if (!user->IsAuthOK()) { + char buffer[256]; + + sprintf(buffer, "id %d user \"SERVER\" msg \"**********\"", motd_index++); + SendData(user, NET_LOBBY_CHAT, buffer); + + sprintf(buffer, "id %d user \"SERVER\" msg \"*** Your game configuration does not match the server.\"", motd_index++); + SendData(user, NET_LOBBY_CHAT, buffer); + + if (server_mods.size() > 0) { + sprintf(buffer, "id %d user \"SERVER\" msg \"*** Please check that you have the proper mods deployed in\"", motd_index++); + SendData(user, NET_LOBBY_CHAT, buffer); + sprintf(buffer, "id %d user \"SERVER\" msg \"*** the order shown above.\"", motd_index++); + SendData(user, NET_LOBBY_CHAT, buffer); + } + + else { + sprintf(buffer, "id %d user \"SERVER\" msg \"*** Please verify that you have no mods deployed.\"", motd_index++); + SendData(user, NET_LOBBY_CHAT, buffer); + } + + sprintf(buffer, "id %d user \"SERVER\" msg \"*** You will not be permitted to join the game with an invalid\"", motd_index++); + SendData(user, NET_LOBBY_CHAT, buffer); + + sprintf(buffer, "id %d user \"SERVER\" msg \"*** configuration. You may reconnect to this server after you\"", motd_index++); + SendData(user, NET_LOBBY_CHAT, buffer); + + sprintf(buffer, "id %d user \"SERVER\" msg \"*** have corrected your mod configuration.\"", motd_index++); + SendData(user, NET_LOBBY_CHAT, buffer); + + sprintf(buffer, "id %d user \"SERVER\" msg \"**********\"", motd_index++); + SendData(user, NET_LOBBY_CHAT, buffer); + + sprintf(buffer, "id %d user \" \" msg \" \"", motd_index++); + SendData(user, NET_LOBBY_CHAT, buffer); + } + } +} + +void +NetLobbyServer::DoChat(NetPeer* peer, Text msg) +{ + List params; + ParseMsg(msg, params); + + Text chat_msg; + + for (int i = 0; i < params.size(); i++) { + NetLobbyParam* p = params[i]; + + int num = 0; + sscanf(p->value, "%d", &num); + + if (p->name == "msg") { + chat_msg = p->value; + } + } + + params.destroy(); + + NetUser* user = FindUserByNetID(peer->NetID()); + + if (user) { + // receive chat from client: + if (chat_msg.length()) { + AddChat(user, chat_msg); + } + + // request for chat log: + else { + ListIter iter = chat_log; + while (++iter) { + NetChatEntry* entry = iter.value(); + + char buffer[512]; + char msg_buf[256]; + char usr_buf[256]; + + // safe quotes uses a static buffer, + // so make sure to save copies of the + // results when using more than one in + // a function call... + + strcpy(msg_buf, SafeQuotes(entry->GetMessage())); + strcpy(usr_buf, SafeQuotes(entry->GetUser())); + + sprintf(buffer, "id %d user \"%s\" msg \"%s\"", + entry->GetID(), usr_buf, msg_buf); + + SendData(user, NET_LOBBY_CHAT, buffer); + } + } + } +} + +void +NetLobbyServer::DoUserList(NetPeer* peer, Text msg) +{ + NetUser* user = FindUserByNetID(peer->NetID()); + + if (user) { + Text content; + + if (local_user) + content += local_user->GetDescription(); + + ListIter iter = users; + while (++iter) { + NetUser* u = iter.value(); + content += u->GetDescription(); + } + + SendData(user, NET_LOBBY_USER_LIST, content); + } +} + +void +NetLobbyServer::DoBanUser(NetPeer* peer, Text msg) +{ + NetUser* user = FindUserByNetID(peer->NetID()); + + if (user && user->IsHost() && user->IsAuthOK()) { + List params; + ParseMsg(msg, params); + + if (params.size() > 0) { + NetLobbyParam* p = params[0]; + + if (p->name == "user") { + Text user_name = p->value; + + NetUser* u = FindUserByName(user_name); + if (u && !u->IsHost()) + BanUser(u); + } + } + + params.destroy(); + } +} + +void +NetLobbyServer::DoMissionList(NetPeer* peer, Text msg) +{ + NetUser* user = FindUserByNetID(peer->NetID()); + + if (user) { + Text reply; + char buffer[4096]; + + ListIter c_iter = Campaign::GetAllCampaigns(); + while (++c_iter) { + Campaign* c = c_iter.value(); + + if (c->GetCampaignId() >= Campaign::MULTIPLAYER_MISSIONS) { + sprintf(buffer, "c_id 0x%08x c_name \"%s\" ", + c->GetCampaignId(), + SafeQuotes(c->Name())); + + reply += buffer; + } + } + + c_iter.reset(); + + while (++c_iter) { + Campaign* c = c_iter.value(); + + if (c->GetCampaignId() >= Campaign::MULTIPLAYER_MISSIONS) { + + ListIter m_iter = c->GetMissionList(); + while (++m_iter) { + MissionInfo* m = m_iter.value(); + + int mission_id = (c->GetCampaignId() << NET_CAMPAIGN_SHIFT) + m->id; + + sprintf(buffer, "m_id 0x%08x ", mission_id); + reply += buffer; + + reply += "m_name \""; + reply += SafeQuotes(m->name); + + // long version of safe quotes: + int n = 0; + const char* s = m->description.data(); + + while (*s && n < 4090) { + if (*s == '"') { + buffer[n++] = '\''; + s++; + } + else if (*s == '\n') { + buffer[n++] = '\\'; + buffer[n++] = 'n'; + s++; + } + else if (*s == '\t') { + buffer[n++] = '\\'; + buffer[n++] = 't'; + s++; + } + else { + buffer[n++] = *s++; + } + } + + // don't forget the null terminator! + buffer[n] = 0; + + reply += "\" m_desc \""; + reply += buffer; + + reply += "\" "; + } + } + } + + SendData(user, NET_LOBBY_MISSION_LIST, reply); + + sprintf(buffer, "m_id 0x%08x", selected_mission); + SendData(user, NET_LOBBY_MISSION_SELECT, buffer); + } +} + +void +NetLobbyServer::DoMissionSelect(NetPeer* peer, Text msg) +{ + if (GetStatus() == NetServerInfo::PERSISTENT) + return; + + NetUser* user = FindUserByNetID(peer->NetID()); + + if (user && user->IsHost() && user->IsAuthOK()) { + List params; + ParseMsg(msg, params); + + for (int i = 0; i < params.size(); i++) { + NetLobbyParam* p = params[i]; + + int num = 0; + sscanf(p->value, "0x%x", &num); + + if (p->name == "m_id") { + SelectMission(num); + } + } + + params.destroy(); + } +} + +void +NetLobbyServer::DoMissionData(NetPeer* peer, Text msg) +{ + NetUser* user = FindUserByNetID(peer->NetID()); + + if (user && mission && user->IsAuthOK()) { + Text reply = Serialize(mission, user); + SendData(user, NET_LOBBY_MISSION_DATA, reply); + + FILE* f = ::fopen("multi_mission_send.def", "w"); + if (f) { + ::fwrite(reply.data(), reply.length(), 1, f); + ::fclose(f); + } + } +} + +void +NetLobbyServer::DoUnitList(NetPeer* peer, Text msg) +{ + NetUser* user = FindUserByNetID(peer->NetID()); + + if (user && unit_map.size() && user->IsAuthOK()) { + Text reply; + + ListIter iter = GetUnitMap(); + while (++iter) { + NetUnitEntry* unit = iter.value(); + reply += unit->GetDescription(); + } + + SendData(user, NET_LOBBY_UNIT_LIST, reply); + } +} + +void +NetLobbyServer::DoMapUnit(NetPeer* peer, Text msg) +{ + NetUser* user = FindUserByNetID(peer->NetID()); + + if (user && unit_map.size() && user->IsAuthOK()) { + List params; + ParseMsg(msg, params); + + int id = 0; + bool lock = false; + Text user_name; + + for (int i = 0; i < params.size(); i++) { + NetLobbyParam* p = params[i]; + + if (p->name == "id") { + sscanf(p->value, "%d", &id); + } + + else if (p->name == "user") { + user_name = p->value; + } + + else if (p->name == "lock") { + lock = (p->value == "true") ? true : false; + } + } + + params.destroy(); + + MapUnit(id, user_name, lock); + } +} + +void +NetLobbyServer::DoGameStart(NetPeer* peer, Text msg) +{ + GameStart(); +} + +void +NetLobbyServer::DoGameStop(NetPeer* peer, Text msg) +{ + NetUser* user = FindUserByNetID(peer->NetID()); + + if (user && user->IsHost() && user->IsAuthOK()) + GameStop(); +} diff --git a/Stars45/NetLobbyServer.h b/Stars45/NetLobbyServer.h new file mode 100644 index 0000000..0a58230 --- /dev/null +++ b/Stars45/NetLobbyServer.h @@ -0,0 +1,119 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetLobbyServer.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + UDP Server Engine for Multiplayer Lobby +*/ + + +#ifndef NetLobbyServer_h +#define NetLobbyServer_h + +// FOR NETWORK MENU TESTING (extra latency in msec): +// #define EXTRA_LATENCY 300 + +#include "NetLobby.h" + +// +-------------------------------------------------------------------+ + +class Mission; +class MissionElement; +class NetChatEntry; +class NetServerConfig; +class NetUser; +class NetUnitEntry; + +// +-------------------------------------------------------------------+ + +class NetLobbyServer : public NetLobby +{ +public: + NetLobbyServer(); + virtual ~NetLobbyServer(); + + int operator == (const NetLobbyServer& s) const { return this == &s; } + + virtual void ExecFrame(); + virtual bool IsHost() const { return true; } + virtual bool IsServer() const { return true; } + + virtual void BanUser(NetUser* user); + virtual void AddUser(NetUser* user); + virtual void DelUser(NetUser* user); + virtual void SendUsers(); + virtual void RequestAuth(NetUser* user); + + virtual void AddChat(NetUser* user, const char* msg, bool route=true); + virtual void ClearChat(); + virtual void SaveChat(); + + virtual List& + GetUnitMap(); + virtual void MapUnit(int n, const char* user, bool lock=false); + virtual void UnmapUnit(const char* user); + virtual void SendUnits(); + + virtual void SelectMission(DWORD id); + virtual Text Serialize(Mission* m, NetUser* u=0); + virtual Mission* GetSelectedMission(); + + virtual Text GetServerName() const { return server_name; } + virtual void SetServerName(const char* s) { server_name = s; } + virtual Text GetServerMission() const { return server_mission; } + virtual void SetServerMission(const char* script) + { server_mission = script; } + + virtual void GameStart(); + virtual void GameStop(); + + virtual void GameOn(); + virtual void GameOff(); + + // singleton locator: + static NetLobbyServer* GetInstance(); + +protected: + virtual void CheckSessions(); + + virtual void SendData(NetUser* dst, int type, Text msg); + virtual void DoPing(NetPeer* peer, Text msg); + virtual void DoServerInfo(NetPeer* peer, Text msg); + virtual void DoServerMods(NetPeer* peer, Text msg); + virtual void DoLogin(NetPeer* peer, Text msg); + virtual void DoLogout(NetPeer* peer, Text msg); + virtual void DoUserAuth(NetPeer* peer, Text msg); + virtual void DoChat(NetPeer* peer, Text msg); + virtual void DoUserList(NetPeer* peer, Text msg); + virtual void DoBanUser(NetPeer* peer, Text msg); + virtual void DoMissionList(NetPeer* peer, Text msg); + virtual void DoMissionSelect(NetPeer* peer, Text msg); + virtual void DoMissionData(NetPeer* peer, Text msg); + virtual void DoUnitList(NetPeer* peer, Text msg); + virtual void DoMapUnit(NetPeer* peer, Text msg); + virtual void DoGameStart(NetPeer* peer, Text msg); + virtual void DoGameStop(NetPeer* peer, Text msg); + + virtual void LoadMOTD(); + virtual void SendMOTD(NetUser* user); + virtual void SendMods(NetUser* user); + + Text server_name; + NetAddr server_addr; + DWORD announce_time; + NetServerConfig* server_config; + Text server_mission; + int motd_index; + + List motd; +}; + +// +-------------------------------------------------------------------+ + +#endif NetLobbyServer_h \ No newline at end of file diff --git a/Stars45/NetPacket.cpp b/Stars45/NetPacket.cpp new file mode 100644 index 0000000..35a189d --- /dev/null +++ b/Stars45/NetPacket.cpp @@ -0,0 +1,356 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetGame.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Network Game Manager and Player classes +*/ + +#include "MemDebug.h" +#include "NetPacket.h" +#include "NetLink.h" +#include "NetMsg.h" +#include "Ship.h" + +#include "Game.h" + +// +--------------------------------------------------------------------+ + +const int PING_SIZE = 8; +const int SHIP_LOC_SIZE = 36; +const int JOIN_REQ_SIZE = 116; +const int JOIN_ANN_SIZE = 116; +const int QUIT_ANN_SIZE = 8; + +// +--------------------------------------------------------------------+ + +NetPacket::NetPacket(NetMsg* g) + : msg(g) +{ } + +NetPacket::NetPacket(DWORD netid, BYTE type) +{ + int len = 0; + char buf[256]; + ZeroMemory(buf, 256); + + switch (type) { + case NET_PING: len = PING_SIZE; break; + case NET_PONG: len = PING_SIZE; break; + case NET_OBJ_LOC: len = SHIP_LOC_SIZE; break; + + case NET_JOIN_REQUEST: len = JOIN_REQ_SIZE; break; + case NET_JOIN_ANNOUNCE: len = JOIN_ANN_SIZE; break; + case NET_QUIT_ANNOUNCE: len = QUIT_ANN_SIZE; break; + + default: len = JOIN_REQ_SIZE; break; + } + + msg = new(__FILE__,__LINE__) NetMsg(netid, type, buf, len); +} + +NetPacket::~NetPacket() +{ + delete msg; +} + +bool +NetPacket::Send(NetLink& link) +{ + bool sent = false; + + if (msg) + sent = link.SendMessage(msg); + + msg = 0; + return sent; +} + +// +--------------------------------------------------------------------+ + +DWORD +NetPacket::NetID() const +{ + if (msg) + return msg->NetID(); + + return 0; +} + +BYTE +NetPacket::Type() const +{ + if (msg) + return msg->Type(); + + return 0; +} + +// +--------------------------------------------------------------------+ + +DWORD +NetPacket::GetPingSequence() +{ + if (msg && msg->Length() >= PING_SIZE) { + DWORD* data = (DWORD*) (msg->Data()+4); + return *data; + } + + return 0; +} + +void +NetPacket::SetPingSequence(DWORD seq) +{ + if (msg && msg->Length() >= PING_SIZE) { + DWORD* data = (DWORD*) (msg->Data()+4); + *data = seq; + } +} + +// +--------------------------------------------------------------------+ + +DWORD +NetPacket::GetNetID() +{ + if (msg && msg->Length() >= PING_SIZE) { + DWORD* data = (DWORD*) (msg->Data()+4); + return *data; + } + + return 0; +} + +void +NetPacket::SetNetID(DWORD id) +{ + if (msg && msg->Length() >= PING_SIZE) { + DWORD* data = (DWORD*) (msg->Data()+4); + *data = id; + } +} + +// +--------------------------------------------------------------------+ + +Point +NetPacket::GetShipLocation() +{ + if (msg && msg->Length() >= SHIP_LOC_SIZE) { + long* data = (long*) (msg->Data()+8); + long x = *(data + 0); + long y = *(data + 1); + long z = *(data + 2); + + return Point(x/100.0, y/100.0, z/100.0); + } + + return Point(); +} + +void +NetPacket::SetShipLocation(const Point& loc) +{ + if (msg && msg->Length() >= SHIP_LOC_SIZE) { + long x = (long) (loc.x * 100); + long y = (long) (loc.y * 100); + long z = (long) (loc.z * 100); + + long* data = (long*) (msg->Data()+8); + + *(data + 0) = x; + *(data + 1) = y; + *(data + 2) = z; + } +} + +// +--------------------------------------------------------------------+ + +Point +NetPacket::GetShipVelocity() +{ + if (msg && msg->Length() >= SHIP_LOC_SIZE) { + short* data = (short*) (msg->Data()+20); + + short dx = *(data + 0); + short dy = *(data + 1); + short dz = *(data + 2); + + return Point(dx, dy, dz); + } + + return Point(); +} + +void +NetPacket::SetShipVelocity(const Point& vel) +{ + if (msg && msg->Length() >= SHIP_LOC_SIZE) { + short* data = (short*) (msg->Data()+20); + + *(data + 0) = (short) vel.x; + *(data + 1) = (short) vel.y; + *(data + 2) = (short) vel.z; + } +} + +// +--------------------------------------------------------------------+ + +Point +NetPacket::GetShipOrientation() +{ + if (msg && msg->Length() >= SHIP_LOC_SIZE) { + short* data = (short*) (msg->Data()+26); + + short r = *(data + 0); + short p = *(data + 1); + short y = *(data + 2); + + return Point(2*PI*r/32767, 2*PI*p/32767, 2*PI*y/32767); + } + + return Point(); +} + +void +NetPacket::SetShipOrientation(const Point& rpy) +{ + if (msg && msg->Length() >= SHIP_LOC_SIZE) { + short* data = (short*) (msg->Data()+26); + + *(data + 0) = (short) (32767*rpy.x/(2*PI)); + *(data + 1) = (short) (32767*rpy.y/(2*PI)); + *(data + 2) = (short) (32767*rpy.z/(2*PI)); + } +} + +// +--------------------------------------------------------------------+ + +double +NetPacket::GetThrottle() +{ + if (msg && msg->Length() >= SHIP_LOC_SIZE) { + BYTE* data = (BYTE*) msg->Data()+32; + + return (double) *data; + } + + return 0; +} + +void +NetPacket::SetThrottle(double t) +{ + if (msg && msg->Length() >= SHIP_LOC_SIZE) { + BYTE* data = (BYTE*) msg->Data()+32; + + *data = (BYTE) t; + } +} + +// +--------------------------------------------------------------------+ + +bool +NetPacket::GetTrigger(int i) +{ + if (i >= 0 && i < 8 && msg && msg->Length() >= SHIP_LOC_SIZE) { + BYTE* data = (BYTE*) msg->Data()+33; + + BYTE select = 1 << i; + return (*data & select)?true:false; + } + + return false; +} + +void +NetPacket::SetTrigger(int i, bool trigger) +{ + if (i >= 0 && i < 8 && msg && msg->Length() >= SHIP_LOC_SIZE) { + BYTE* data = (BYTE*) msg->Data()+33; + + BYTE select = 1 << i; + + if (trigger) + *data = *data | select; + else + *data = *data & ~select; + } +} + +// +--------------------------------------------------------------------+ + +const char* +NetPacket::GetName() +{ + if (msg && msg->Length() >= JOIN_REQ_SIZE) { + BYTE* data = (BYTE*) msg->Data()+20; + + return (const char*) data; + } + + return 0; +} + +void +NetPacket::SetName(const char* name) +{ + if (msg && msg->Length() >= JOIN_REQ_SIZE) { + BYTE* data = (BYTE*) msg->Data()+20; + strncpy((char*) data, name, 32); + } +} + +// +--------------------------------------------------------------------+ + +const char* +NetPacket::GetDesign() +{ + if (msg && msg->Length() >= JOIN_REQ_SIZE) { + BYTE* data = (BYTE*) msg->Data()+52; + + return (const char*) data; + } + + return 0; +} + +void +NetPacket::SetDesign(const char* design) +{ + if (msg && msg->Length() >= JOIN_REQ_SIZE) { + BYTE* data = (BYTE*) msg->Data()+52; + strncpy((char*) data, design, 32); + } +} + +// +--------------------------------------------------------------------+ + +const char* +NetPacket::GetRegion() +{ + if (msg && msg->Length() >= JOIN_REQ_SIZE) { + BYTE* data = (BYTE*) msg->Data()+84; + + return (const char*) data; + } + + return 0; +} + +void +NetPacket::SetRegion(const char* rgn_name) +{ + if (msg && msg->Length() >= JOIN_REQ_SIZE) { + BYTE* data = (BYTE*) msg->Data()+84; + strncpy((char*) data, rgn_name, 32); + } +} + +// +--------------------------------------------------------------------+ + diff --git a/Stars45/NetPacket.h b/Stars45/NetPacket.h new file mode 100644 index 0000000..a3527dc --- /dev/null +++ b/Stars45/NetPacket.h @@ -0,0 +1,74 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetPacket.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Wrapper for low-level datagram class +*/ + +#ifndef NetPacket_h +#define NetPacket_h + +#include "Types.h" +#include "Geometry.h" +#include "NetData.h" + +// +--------------------------------------------------------------------+ + +class NetLink; +class NetMsg; + +// +--------------------------------------------------------------------+ + +class NetPacket +{ +public: + static const char* TYPENAME() { return "NetPacket"; } + + NetPacket(NetMsg* g); + NetPacket(DWORD netid, BYTE type); + ~NetPacket(); + + bool Send(NetLink& link); + + // various accessors: + DWORD NetID() const; + BYTE Type() const; + + DWORD GetPingSequence(); + void SetPingSequence(DWORD seq); + DWORD GetNetID(); + void SetNetID(DWORD id); + Point GetShipLocation(); + void SetShipLocation(const Point& loc); + Point GetShipVelocity(); + void SetShipVelocity(const Point& vel); + Point GetShipOrientation(); + void SetShipOrientation(const Point& rpy); + double GetThrottle(); + void SetThrottle(double t); + const char* GetName(); + void SetName(const char* name); + const char* GetDesign(); + void SetDesign(const char* design); + const char* GetRegion(); + void SetRegion(const char* rgn_name); + bool GetTrigger(int i); + void SetTrigger(int i, bool trigger); + + +protected: + NetMsg* msg; +}; + + +// +--------------------------------------------------------------------+ + +#endif NetPacket_h + diff --git a/Stars45/NetPassDlg.cpp b/Stars45/NetPassDlg.cpp new file mode 100644 index 0000000..faeccb3 --- /dev/null +++ b/Stars45/NetPassDlg.cpp @@ -0,0 +1,143 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetPassDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Network Server Password Dialog Active Window class +*/ + +#include "MemDebug.h" +#include "NetPassDlg.h" +#include "MenuScreen.h" +#include "NetClientConfig.h" +#include "NetLobby.h" + +#include "Game.h" +#include "DataLoader.h" +#include "Button.h" +#include "EditBox.h" +#include "ListBox.h" +#include "Slider.h" +#include "Video.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "ParseUtil.h" +#include "FormatUtil.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +DEF_MAP_CLIENT(NetPassDlg, OnApply); +DEF_MAP_CLIENT(NetPassDlg, OnCancel); + +// +--------------------------------------------------------------------+ + +NetPassDlg::NetPassDlg(Screen* s, FormDef& def, MenuScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), manager(mgr), + btn_apply(0), btn_cancel(0), edt_pass(0), lbl_name(0) +{ + Init(def); +} + +NetPassDlg::~NetPassDlg() +{ +} + +// +--------------------------------------------------------------------+ + +void +NetPassDlg::RegisterControls() +{ + btn_apply = (Button*) FindControl(1); + btn_cancel = (Button*) FindControl(2); + + REGISTER_CLIENT(EID_CLICK, btn_apply, NetPassDlg, OnApply); + REGISTER_CLIENT(EID_CLICK, btn_cancel, NetPassDlg, OnCancel); + + lbl_name = FindControl(110); + edt_pass = (EditBox*) FindControl(200); + + if (edt_pass) + edt_pass->SetText(""); +} + +// +--------------------------------------------------------------------+ + +void +NetPassDlg::Show() +{ + if (!IsShown()) { + FormWindow::Show(); + + NetClientConfig* config = NetClientConfig::GetInstance(); + + if (config && lbl_name) { + NetServerInfo* info = config->GetSelectedServer(); + + if (info) + lbl_name->SetText(info->name); + } + + if (edt_pass) { + edt_pass->SetText(""); + edt_pass->SetFocus(); + } + } +} + +// +--------------------------------------------------------------------+ + +static bool tab_latch = false; + +void +NetPassDlg::ExecFrame() +{ + if (Keyboard::KeyDown(VK_RETURN)) { + OnApply(0); + } +} + +// +--------------------------------------------------------------------+ + +void +NetPassDlg::OnApply(AWEvent* event) +{ + NetClientConfig* config = NetClientConfig::GetInstance(); + + if (config && edt_pass) { + NetServerInfo* info = config->GetSelectedServer(); + + if (info && edt_pass->GetText().length() < 250) { + char buffer[256]; + strcpy(buffer, edt_pass->GetText().data()); + + // trim from first occurrence of invalid character + char* p = strpbrk(buffer, "\n\r\t"); + if (p) *p = 0; + + info->password = SafeQuotes(buffer); + + if (manager) { + manager->ShowNetLobbyDlg(); + return; + } + } + } + + if (manager) { + manager->ShowNetClientDlg(); + } +} + +void +NetPassDlg::OnCancel(AWEvent* event) +{ + if (manager) + manager->ShowNetClientDlg(); +} diff --git a/Stars45/NetPassDlg.h b/Stars45/NetPassDlg.h new file mode 100644 index 0000000..f91c5b4 --- /dev/null +++ b/Stars45/NetPassDlg.h @@ -0,0 +1,58 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetPassDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Network Server Password Dialog Active Window class +*/ + +#ifndef NetPassDlg_h +#define NetPassDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "Bitmap.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Font.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class MenuScreen; + +// +--------------------------------------------------------------------+ + +class NetPassDlg : public FormWindow +{ +public: + NetPassDlg(Screen* s, FormDef& def, MenuScreen* mgr); + virtual ~NetPassDlg(); + + virtual void RegisterControls(); + virtual void Show(); + + // Operations: + virtual void ExecFrame(); + + virtual void OnApply(AWEvent* event); + virtual void OnCancel(AWEvent* event); + +protected: + MenuScreen* manager; + + Button* btn_apply; + Button* btn_cancel; + EditBox* edt_pass; + ActiveWindow* lbl_name; +}; + +#endif NetPassDlg_h + diff --git a/Stars45/NetPlayer.cpp b/Stars45/NetPlayer.cpp new file mode 100644 index 0000000..31d5c38 --- /dev/null +++ b/Stars45/NetPlayer.cpp @@ -0,0 +1,464 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetPlayer.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Network Player (Director) class +*/ + +#include "MemDebug.h" +#include "NetPlayer.h" +#include "NetGame.h" +#include "NetMsg.h" +#include "NetData.h" +#include "NetUtil.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "Shield.h" +#include "Shot.h" +#include "Sim.h" +#include "SimEvent.h" +#include "System.h" +#include "Weapon.h" +#include "WeaponGroup.h" +#include "Element.h" +#include "HUDView.h" +#include "Explosion.h" +#include "Farcaster.h" +#include "RadioMessage.h" +#include "RadioTraffic.h" + +#include "NetHost.h" +#include "Game.h" +#include "Light.h" + +// +--------------------------------------------------------------------+ + +NetPlayer::~NetPlayer() +{ + if (ship) { + ship->SetNetworkControl(); + + Sim* sim = Sim::GetSim(); + sim->DestroyShip(ship); + } +} + +// +--------------------------------------------------------------------+ + +void +NetPlayer::SetShip(Ship* s) +{ + if (ship != s) { + if (ship) { + ship->EnableRepair(true); + Ignore(ship); + } + + ship = s; + + if (ship) { + Observe(ship); + ship->SetNetworkControl(this); + ship->SetObjID(objid); + + iff = ship->GetIFF(); + + // Turn off auto-repair. All repair data should + // come in over the network from the remote player: + + ship->EnableRepair(false); + + // Set all ship weapons back to manual fire control. + // All trigger events should come over the network, + // not from weapon auto aiming ai: + + ListIter iter = ship->Weapons(); + while (++iter) { + WeaponGroup* group = iter.value(); + + ListIter w_iter = group->GetWeapons(); + while (++w_iter) { + Weapon* weapon = w_iter.value(); + + weapon->SetFiringOrders(Weapon::MANUAL); + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +const double BLEED = 0.5; + +bool +NetPlayer::DoObjLoc(NetObjLoc* obj_loc) +{ + if (ship && obj_loc) { + loc_error = obj_loc->GetLocation() - ship->Location(); + bleed_time = BLEED; + + ship->SetVelocity(obj_loc->GetVelocity()); + Point o = obj_loc->GetOrientation(); + ship->SetAbsoluteOrientation(o.x, o.y, o.z); + ship->SetThrottle(obj_loc->GetThrottle() ? 100 : 0); + ship->SetAugmenter(obj_loc->GetAugmenter()); + + if (obj_loc->GetGearDown()) + ship->LowerGear(); + else + ship->RaiseGear(); + + Shield* shield = ship->GetShield(); + if (shield) + shield->SetPowerLevel(obj_loc->GetShield()); + + return true; + } + + return false; +} + +bool +NetPlayer::DoObjHyper(NetObjHyper* obj_hyper) +{ + if (ship && obj_hyper) { + Sim* sim = Sim::GetSim(); + SimRegion* rgn = sim->FindRegion(obj_hyper->GetRegion()); + DWORD fc1_id = obj_hyper->GetFarcaster1(); + DWORD fc2_id = obj_hyper->GetFarcaster2(); + Ship* fc1 = 0; + Ship* fc2 = 0; + int trans = obj_hyper->GetTransitionType(); + + if (ship->GetRegion() == rgn) { + ::Print("NetPlayer::DoObjHyper ship: '%s' rgn: '%s' trans: %d (IGNORED)\n\n", + ship->Name(), obj_hyper->GetRegion().data(), trans); + + return false; + } + + ::Print("NetPlayer::DoObjHyper ship: '%s' rgn: '%s' trans: %d\n\n", + ship->Name(), obj_hyper->GetRegion().data(), trans); + + // orbital transition? + if (trans == Ship::TRANSITION_DROP_ORBIT) { + ship->SetTransition(1.0f, Ship::TRANSITION_DROP_ORBIT, ship->Location()); + ship->CompleteTransition(); + } + + else if (trans == Ship::TRANSITION_MAKE_ORBIT) { + ship->SetTransition(1.0f, Ship::TRANSITION_MAKE_ORBIT, ship->Location()); + ship->CompleteTransition(); + } + + else { + if (fc1_id) + fc1 = sim->FindShipByObjID(fc1_id); + + if (fc2_id) + fc2 = sim->FindShipByObjID(fc2_id); + + sim->CreateExplosion(ship->Location(), Point(0,0,0), + Explosion::QUANTUM_FLASH, 1.0f, 0, ship->GetRegion()); + + sim->RequestHyperJump(ship, rgn, obj_hyper->GetLocation(), trans, fc1, fc2); + + ShipStats* stats = ShipStats::Find(ship->Name()); + stats->AddEvent(SimEvent::QUANTUM_JUMP, rgn->Name()); + } + + return true; + } + + return false; +} + +bool +NetPlayer::DoObjTarget(NetObjTarget* obj_target) +{ + if (ship && obj_target) { + DWORD tgtid = obj_target->GetTgtID(); + int subid = obj_target->GetSubtarget(); + SimObject* target = 0; + System* subtgt = 0; + + NetGame* net_game = NetGame::GetInstance(); + if (net_game && tgtid) { + target = net_game->FindShipByObjID(tgtid); + + if (target) { + if (subid >= 0) { + Ship* tgt_ship = (Ship*) target; + subtgt = tgt_ship->Systems().at(subid); + } + } + else { + target = net_game->FindShotByObjID(tgtid); + } + } + + ship->SetTarget(target, subtgt, true); // from net = true (don't resend) + + return true; + } + + return false; +} + +bool +NetPlayer::DoObjEmcon(NetObjEmcon* obj_emcon) +{ + if (ship && obj_emcon) { + int emcon = obj_emcon->GetEMCON(); + ship->SetEMCON(emcon, true); // from net = true (don't resend) + + return true; + } + + return false; +} + +bool +NetPlayer::DoWepTrigger(NetWepTrigger* trigger) +{ + if (ship && trigger) { + int index = trigger->GetIndex(); + int count = trigger->GetCount(); + DWORD tgtid = trigger->GetTgtID(); + int subid = trigger->GetSubtarget(); + bool decoy = trigger->GetDecoy(); + bool probe = trigger->GetProbe(); + + Weapon* w = 0; + + if (decoy) w = ship->GetDecoy(); + else if (probe) w = ship->GetProbeLauncher(); + else w = ship->GetWeaponByIndex(index); + + if (w) { + SimObject* target = 0; + System* subtgt = 0; + + NetGame* net_game = NetGame::GetInstance(); + if (net_game) { + target = net_game->FindShipByObjID(tgtid); + + if (target) { + if (subid >= 0) { + Ship* tgt_ship = (Ship*) target; + subtgt = tgt_ship->Systems().at(subid); + } + } + else { + target = net_game->FindShotByObjID(tgtid); + } + + // re-broadcast: + if (net_game->IsServer()) { + if (w->IsPrimary()) { + w->NetFirePrimary(target, subtgt, count); + net_game->SendData(trigger); + } + else { + DWORD wepid = NetGame::GetNextObjID(NetGame::SHOT); + Shot* shot = w->NetFireSecondary(target, subtgt, wepid); + + if (shot && shot->IsDrone()) { + if (probe) + ship->SetProbe((Drone*) shot); + + else if (decoy) + ship->AddActiveDecoy((Drone*) shot); + } + + NetWepRelease release; + release.SetObjID(objid); + release.SetTgtID(tgtid); + release.SetSubtarget(subid); + release.SetWepID(wepid); + release.SetIndex(index); + release.SetDecoy(decoy); + release.SetProbe(probe); + + net_game->SendData(&release); + } + } + + else { + if (w->IsPrimary()) { + w->NetFirePrimary(target, subtgt, count); + } + } + + return true; + } + } + + } + return false; +} + +bool +NetPlayer::DoWepRelease(NetWepRelease* release) +{ + if (ship && release) { + int index = release->GetIndex(); + DWORD tgtid = release->GetTgtID(); + DWORD wepid = release->GetWepID(); + int subid = release->GetSubtarget(); + bool decoy = release->GetDecoy(); + bool probe = release->GetProbe(); + + Weapon* w = 0; + + if (decoy) w = ship->GetDecoy(); + else if (probe) w = ship->GetProbeLauncher(); + else w = ship->GetWeaponByIndex(index); + + if (w && !w->IsPrimary()) { + SimObject* target = 0; + System* subtgt = 0; + + NetGame* net_game = NetGame::GetInstance(); + if (net_game) { + target = net_game->FindShipByObjID(tgtid); + + if (target) { + if (subid >= 0) { + Ship* tgt_ship = (Ship*) target; + subtgt = tgt_ship->Systems().at(subid); + } + } + else { + target = net_game->FindShotByObjID(tgtid); + } + } + + Shot* shot = w->NetFireSecondary(target, subtgt, wepid); + + if (shot && shot->IsDrone()) { + if (probe) + ship->SetProbe((Drone*) shot); + + else if (decoy) + ship->AddActiveDecoy((Drone*) shot); + } + + return true; + } + } + + return false; +} + +bool +NetPlayer::DoCommMessage(NetCommMsg* comm_msg) +{ + if (ship && comm_msg) { + RadioTraffic* traffic = RadioTraffic::GetInstance(); + RadioMessage* radio_msg = comm_msg->GetRadioMessage(); + + if (traffic && radio_msg) { + if (radio_msg->DestinationElem() || radio_msg->DestinationShip()) { + // radio traffic owns the sent message, + // so we must give it a cloned object that is + // safe to delete: + traffic->SendMessage(new(__FILE__,__LINE__) RadioMessage(*radio_msg)); + return true; + } + } + } + + return false; +} + +// +--------------------------------------------------------------------+ + +bool +NetPlayer::DoSysDamage(NetSysDamage* sys_damage) +{ + if (ship && sys_damage) { + System* sys = ship->GetSystem(sys_damage->GetSystem()); + ship->InflictNetSystemDamage( sys, + sys_damage->GetDamage(), + sys_damage->GetDamageType()); + + return true; + } + + return false; +} + +bool +NetPlayer::DoSysStatus(NetSysStatus* sys_status) +{ + if (ship && sys_status) { + System* sys = ship->GetSystem(sys_status->GetSystem()); + ship->SetNetSystemStatus( sys, + sys_status->GetStatus(), + sys_status->GetPower(), + sys_status->GetReactor(), + sys_status->GetAvailability()); + + return true; + } + + return false; +} + +// +--------------------------------------------------------------------+ + +void +NetPlayer::ExecFrame(double seconds) +{ + if (ship) { + // bleed off the location error: + if (loc_error.length() > 0 && bleed_time > 0) { + double fragment = min(seconds / BLEED, bleed_time); + ship->MoveTo(ship->Location() + loc_error * fragment); + bleed_time -= fragment; + } + + // update the ship location by dead reckoning: + ship->MoveTo(ship->Location() + ship->Velocity() * seconds); + + // let the FLCS run, so that the drive flares will work: + ship->ExecFLCSFrame(); + + // now update the graphic rep and light sources: + if (ship->Rep()) { + ship->Rep()->MoveTo(ship->Location()); + ship->Rep()->SetOrientation(ship->Cam().Orientation()); + } + + if (ship->LightSrc()) { + ship->LightSrc()->MoveTo(ship->Location()); + } + } +} + +// +--------------------------------------------------------------------+ + +bool +NetPlayer::Update(SimObject* obj) +{ + if (obj == ship) { + ship = 0; + } + + return SimObserver::Update(obj); +} + +const char* +NetPlayer::GetObserverName() const +{ + return "NetPlayer"; +} diff --git a/Stars45/NetPlayer.h b/Stars45/NetPlayer.h new file mode 100644 index 0000000..7c44e40 --- /dev/null +++ b/Stars45/NetPlayer.h @@ -0,0 +1,98 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetPlayer.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Network Player (Director) class +*/ + +#ifndef NetPlayer_h +#define NetPlayer_h + +#include "Types.h" +#include "Geometry.h" +#include "Director.h" +#include "SimObject.h" +#include "List.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class Sim; +class Ship; +class NetMsg; +class NetObjLoc; +class NetObjHyper; +class NetObjTarget; +class NetObjEmcon; +class NetSysDamage; +class NetSysStatus; +class NetWepTrigger; +class NetWepRelease; +class NetWepDestroy; +class NetCommMsg; + +// +--------------------------------------------------------------------+ + +class NetPlayer : public Director, public SimObserver +{ +public: + static const char* TYPENAME() { return "NetPlayer"; } + + NetPlayer(DWORD nid) : netid(nid), objid(0), ship(0), iff(0) { } + ~NetPlayer(); + + int operator == (const NetPlayer& p) const { return netid == p.netid; } + + DWORD GetNetID() const { return netid; } + DWORD GetObjID() const { return objid; } + void SetObjID(DWORD o) { objid = o; } + + int GetIFF() const { return iff; } + Ship* GetShip() const { return ship; } + void SetShip(Ship* s); + + const char* Name() const { return name; } + void SetName(const char* n) { name = n; } + const char* SerialNumber() const { return serno; } + void SetSerialNumber(const char* n) { serno = n; } + + virtual void ExecFrame(double seconds); + + bool DoObjLoc(NetObjLoc* obj_loc); + bool DoObjHyper(NetObjHyper* obj_hyper); + bool DoObjTarget(NetObjTarget* obj_target); + bool DoObjEmcon(NetObjEmcon* obj_emcon); + bool DoWepTrigger(NetWepTrigger* trigger); + bool DoWepRelease(NetWepRelease* release); + bool DoCommMessage(NetCommMsg* comm_msg); + bool DoSysDamage(NetSysDamage* sys_damage); + bool DoSysStatus(NetSysStatus* sys_status); + + virtual int Type() const { return 2; } + + virtual bool Update(SimObject* obj); + virtual const char* GetObserverName() const; + +protected: + DWORD netid; + DWORD objid; + Text name; + Text serno; + Ship* ship; + int iff; + + Point loc_error; + double bleed_time; +}; + +// +--------------------------------------------------------------------+ + +#endif NetPlayer_h + diff --git a/Stars45/NetServerConfig.cpp b/Stars45/NetServerConfig.cpp new file mode 100644 index 0000000..bf0652a --- /dev/null +++ b/Stars45/NetServerConfig.cpp @@ -0,0 +1,463 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars + FILE: NetServerConfig.cpp + AUTHOR: John DiCamillo + +*/ + +#include "MemDebug.h" +#include "NetServerConfig.h" +#include "NetUser.h" + +#include "NetLayer.h" +#include "NetAddr.h" +#include "NetGame.h" +#include "NetHost.h" +#include "NetServer.h" +#include "HttpServer.h" +#include "HttpServletExec.h" +#include "NetLobbyServer.h" +#include "NetAuth.h" + +#include "token.h" +#include "Game.h" +#include "DataLoader.h" +#include "ParseUtil.h" +#include "Resource.h" + +// +--------------------------------------------------------------------+ + +NetServerConfig* NetServerConfig::instance = 0; +extern const char* versionInfo; + +// +--------------------------------------------------------------------+ + +NetServerConfig::NetServerConfig() +{ + instance = this; + + name = "Starshatter "; + admin_name = "system"; + admin_pass = "manager"; + admin_port = 11111; + lobby_port = 11100; + game_port = 11101; + game_type = NET_GAME_PUBLIC; + auth_level = NetAuth::NET_AUTH_STANDARD; + poolsize = 8; + session_timeout = 300; + + name += versionInfo; + + Load(); +} + +NetServerConfig::~NetServerConfig() +{ + instance = 0; + + banned_addrs.destroy(); + banned_names.destroy(); +} + +// +--------------------------------------------------------------------+ + +void +NetServerConfig::Initialize() +{ + if (!instance) + instance = new(__FILE__,__LINE__) NetServerConfig(); +} + +void +NetServerConfig::Close() +{ + delete instance; + instance = 0; +} + +// +--------------------------------------------------------------------+ + +void +NetServerConfig::Load() +{ + // read the config file: + BYTE* block = 0; + int blocklen = 0; + int port = 0; + + char filename[64]; + strcpy(filename, "server.cfg"); + + FILE* f = ::fopen(filename, "rb"); + + if (f) { + ::fseek(f, 0, SEEK_END); + blocklen = ftell(f); + ::fseek(f, 0, SEEK_SET); + + block = new(__FILE__,__LINE__) BYTE[blocklen+1]; + block[blocklen] = 0; + + ::fread(block, blocklen, 1, f); + ::fclose(f); + } + + if (blocklen == 0) { + delete [] block; + return; + } + + Parser parser(new(__FILE__,__LINE__) BlockReader((const char*) block, blocklen)); + Term* term = parser.ParseTerm(); + + if (!term) { + Print("ERROR: could not parse '%s'.\n", filename); + delete [] block; + return; + } + else { + TermText* file_type = term->isText(); + if (!file_type || file_type->value() != "SERVER_CONFIG") { + Print("WARNING: invalid '%s' file. Using defaults\n", filename); + delete [] block; + return; + } + } + + do { + delete term; + + term = parser.ParseTerm(); + + if (term) { + TermDef* def = term->isDef(); + if (def) { + if (def->name()->value() == "name") { + GetDefText(instance->name, def, filename); + } + + else if (def->name()->value() == "admin_name") { + GetDefText(instance->admin_name, def, filename); + } + + else if (def->name()->value() == "admin_pass") { + GetDefText(instance->admin_pass, def, filename); + } + + else if (def->name()->value() == "game_pass") { + GetDefText(instance->game_pass, def, filename); + } + + else if (def->name()->value() == "mission") { + GetDefText(instance->mission, def, filename); + } + + else if (def->name()->value() == "auth_level") { + Text level; + + if (def->term() && def->term()->isText()) { + GetDefText(level, def, filename); + + level.toLower(); + + if (level.indexOf("min") == 0) + instance->auth_level = NetAuth::NET_AUTH_MINIMAL; + + else if (level == "standard" || level == "std") + instance->auth_level = NetAuth::NET_AUTH_STANDARD; + + else if (level == "secure") + instance->auth_level = NetAuth::NET_AUTH_SECURE; + } + + else { + GetDefNumber(instance->auth_level, def, filename); + } + } + + else if (def->name()->value() == "admin_port") { + GetDefNumber(port, def, filename); + if (port > 1024 && port < 48000) + instance->admin_port = (WORD) port; + } + + else if (def->name()->value() == "lobby_port") { + GetDefNumber(port, def, filename); + if (port > 1024 && port < 48000) + instance->lobby_port = (WORD) port; + } + + else if (def->name()->value() == "game_port") { + GetDefNumber(port, def, filename); + if (port > 1024 && port < 48000) + instance->game_port = (WORD) port; + } + + else if (def->name()->value() == "game_type") { + Text type; + GetDefText(type, def, filename); + type.setSensitive(false); + + if (type == "LAN") + instance->game_type = NET_GAME_LAN; + + else if (type == "private") + instance->game_type = NET_GAME_PRIVATE; + + else + instance->game_type = NET_GAME_PUBLIC; + } + + else if (def->name()->value() == "poolsize") { + GetDefNumber(instance->poolsize, def, filename); + } + + else if (def->name()->value() == "session_timeout") { + GetDefNumber(instance->session_timeout, def, filename); + } + + else + Print("WARNING: unknown label '%s' in '%s'\n", + def->name()->value().data(), filename); + } + else { + Print("WARNING: term ignored in '%s'\n", filename); + term->print(); + } + } + } + while (term); + + delete [] block; + + LoadBanList(); +} + +// +--------------------------------------------------------------------+ + +void +NetServerConfig::Save() +{ + FILE* f = fopen("server.cfg", "w"); + if (f) { + fprintf(f, "SERVER_CONFIG\n\n"); + fprintf(f, "name: \"%s\"\n", instance->name.data()); + fprintf(f, "admin_name: \"%s\"\n", instance->admin_name.data()); + fprintf(f, "admin_pass: \"%s\"\n", instance->admin_pass.data()); + fprintf(f, "game_pass: \"%s\"\n", instance->game_pass.data()); + fprintf(f, "\n"); + fprintf(f, "admin_port: %d\n", instance->admin_port); + fprintf(f, "lobby_port: %d\n", instance->lobby_port); + fprintf(f, "game_port: %d\n", instance->game_port); + + switch (instance->game_type) { + case NET_GAME_LAN: + fprintf(f, "game_type: LAN\n"); + break; + + case NET_GAME_PRIVATE: + fprintf(f, "game_type: private\n"); + break; + + case NET_GAME_PUBLIC: + default: + fprintf(f, "game_type: public\n"); + break; + } + + switch (instance->auth_level) { + case NetAuth::NET_AUTH_MINIMAL: + fprintf(f, "auth_level: minimal\n"); + break; + + case NetAuth::NET_AUTH_STANDARD: + default: + fprintf(f, "auth_level: standard\n"); + break; + + case NetAuth::NET_AUTH_SECURE: + fprintf(f, "auth_level: secure\n"); + break; + } + + fprintf(f, "\n"); + fprintf(f, "poolsize: %d\n", instance->poolsize); + fprintf(f, "session_timeout: %d\n", instance->session_timeout); + + if (mission.length() > 0) { + fprintf(f, "\nmission: \"%s\"\n", instance->mission.data()); + } + + fclose(f); + } +} + +// +--------------------------------------------------------------------+ + +Text +NetServerConfig::Clean(const char* s) +{ + if (!s || !*s) return Text(); + + int len = strlen(s); + char* buff = new(__FILE__,__LINE__) char[len+1]; + ZeroMemory(buff, len+1); + + char* p = buff; + + for (int i = 0; i < len; i++) { + char c = s[i]; + + if (c >= 32 && c < 127) + *p++ = c; + } + + Text result(buff); + delete [] buff; + + return result; +} + +// +--------------------------------------------------------------------+ + +void +NetServerConfig::LoadBanList() +{ + // read the config file: + BYTE* block = 0; + int blocklen = 0; + int port = 0; + + char filename[64]; + strcpy(filename, "banned.cfg"); + + FILE* f = ::fopen(filename, "rb"); + + if (f) { + ::fseek(f, 0, SEEK_END); + blocklen = ftell(f); + ::fseek(f, 0, SEEK_SET); + + block = new(__FILE__,__LINE__) BYTE[blocklen+1]; + block[blocklen] = 0; + + ::fread(block, blocklen, 1, f); + ::fclose(f); + } + + if (blocklen == 0) { + delete [] block; + return; + } + + Parser parser(new(__FILE__,__LINE__) BlockReader((const char*) block, blocklen)); + Term* term = parser.ParseTerm(); + + if (!term) { + Print("ERROR: could not parse '%s'.\n", filename); + delete [] block; + return; + } + else { + TermText* file_type = term->isText(); + if (!file_type || file_type->value() != "BANNED_CONFIG") { + Print("WARNING: invalid '%s' file.\n", filename); + delete [] block; + return; + } + } + + banned_addrs.destroy(); + banned_names.destroy(); + + do { + delete term; + + term = parser.ParseTerm(); + + if (term) { + TermDef* def = term->isDef(); + if (def) { + if (def->name()->value() == "name") { + Text name; + GetDefText(name, def, filename); + banned_names.append(new(__FILE__,__LINE__) Text(name)); + } + + else if (def->name()->value() == "addr") { + DWORD addr; + GetDefNumber(addr, def, filename); + banned_addrs.append(new(__FILE__,__LINE__) NetAddr(addr)); + } + } + + else { + Print("WARNING: term ignored in '%s'\n", filename); + term->print(); + } + } + } + while (term); + + delete [] block; +} + +void +NetServerConfig::BanUser(NetUser* user) +{ + if (!user || IsUserBanned(user)) + return; + + NetAddr* user_addr = new(__FILE__,__LINE__) NetAddr(user->GetAddress().IPAddr()); + Text* user_name = new(__FILE__,__LINE__) Text(user->Name()); + + banned_addrs.append(user_addr); + banned_names.append(user_name); + + FILE* f = fopen("banned.cfg", "w"); + if (f) { + fprintf(f, "BANNED_CONFIG\n\n"); + + ListIter a_iter = banned_addrs; + while (++a_iter) { + NetAddr* addr = a_iter.value(); + fprintf(f, "addr: 0x%08x // %d.%d.%d.%d\n", + addr->IPAddr(), + addr->B1(), + addr->B2(), + addr->B3(), + addr->B4()); + } + + fprintf(f, "\n"); + + ListIter n_iter = banned_names; + while (++n_iter) { + Text* name = n_iter.value(); + fprintf(f, "name: \"%s\"\n", name->data()); + } + + fclose(f); + } +} + +bool +NetServerConfig::IsUserBanned(NetUser* user) +{ + if (user) { + NetAddr user_addr = user->GetAddress(); + Text user_name = user->Name(); + + user_addr.SetPort(0); + + return banned_addrs.contains(&user_addr) || + banned_names.contains(&user_name); + } + + return false; +} + diff --git a/Stars45/NetServerConfig.h b/Stars45/NetServerConfig.h new file mode 100644 index 0000000..14e8484 --- /dev/null +++ b/Stars45/NetServerConfig.h @@ -0,0 +1,99 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars + FILE: NetServerConfig.h + AUTHOR: John DiCamillo + +*/ + +#ifndef NetServerConfig_h +#define NetServerConfig_h + +#include "Types.h" +#include "Game.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class NetAddr; +class NetUser; + +// +--------------------------------------------------------------------+ + +class NetServerConfig +{ +public: + static const char* TYPENAME() { return "NetServerConfig"; } + + NetServerConfig(); + ~NetServerConfig(); + + enum GAME_TYPE { + NET_GAME_LAN, + NET_GAME_PRIVATE, + NET_GAME_PUBLIC + }; + + const Text& Name() const { return name; } + const Text& GetAdminName() const { return admin_name; } + const Text& GetAdminPass() const { return admin_pass; } + const Text& GetGamePass() const { return game_pass; } + const Text& GetMission() const { return mission; } + WORD GetAdminPort() const { return admin_port; } + WORD GetLobbyPort() const { return lobby_port; } + WORD GetGamePort() const { return game_port; } + int GetPoolsize() const { return poolsize; } + int GetSessionTimeout() const { return session_timeout; } + int GetGameType() const { return game_type; } + int GetAuthLevel() const { return auth_level; } + + void SetName(const char* s) { name = Clean(s); } + void SetAdminName(const char* s){ admin_name = Clean(s); } + void SetAdminPass(const char* s){ admin_pass = Clean(s); } + void SetGamePass(const char* s) { game_pass = Clean(s); } + void SetMission(const char* s) { mission = Clean(s); } + void SetGameType(int t) { game_type = t; } + void SetAdminPort(WORD p) { admin_port = p; } + void SetLobbyPort(WORD p) { lobby_port = p; } + void SetGamePort(WORD p) { game_port = p; } + void SetPoolsize(int s) { poolsize = s; } + void SetSessionTimeout(int t) { session_timeout = t; } + void SetAuthLevel(int n) { auth_level = n; } + + void Load(); + void Save(); + + bool IsUserBanned(NetUser* user); + void BanUser(NetUser* user); + + static void Initialize(); + static void Close(); + static NetServerConfig* GetInstance() { return instance; } + +private: + void LoadBanList(); + Text Clean(const char* s); + + Text name; + Text admin_name; + Text admin_pass; + Text game_pass; + Text mission; + + WORD admin_port; + WORD lobby_port; + WORD game_port; + int poolsize; + int session_timeout; + int game_type; + int auth_level; + + List banned_addrs; + List banned_names; + + static NetServerConfig* instance; +}; + +#endif NetServerConfig_h diff --git a/Stars45/NetServerDlg.cpp b/Stars45/NetServerDlg.cpp new file mode 100644 index 0000000..0135b34 --- /dev/null +++ b/Stars45/NetServerDlg.cpp @@ -0,0 +1,179 @@ +/* Project Starshatter 5.0 + Destroyer Studios LLC + Copyright © 1997-2007. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetServerDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Main Menu Dialog Active Window class +*/ + +#include "MemDebug.h" +#include "NetServerDlg.h" +#include "NetServerConfig.h" +#include "MenuScreen.h" +#include "Starshatter.h" + +#include "NetAddr.h" +#include "HttpClient.h" +#include "HttpServer.h" + +#include "DataLoader.h" +#include "Button.h" +#include "EditBox.h" +#include "ComboBox.h" +#include "Video.h" +#include "Keyboard.h" +#include "MachineInfo.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +DEF_MAP_CLIENT(NetServerDlg, OnApply); +DEF_MAP_CLIENT(NetServerDlg, OnCancel); + +// +--------------------------------------------------------------------+ + +NetServerDlg::NetServerDlg(Screen* s, FormDef& def, MenuScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), manager(mgr) +{ + config = NetServerConfig::GetInstance(); + Init(def); +} + +NetServerDlg::~NetServerDlg() +{ +} + +// +--------------------------------------------------------------------+ + +void +NetServerDlg::RegisterControls() +{ + edt_name = (EditBox*) FindControl(200); + cmb_type = (ComboBox*) FindControl(201); + edt_game_port = (EditBox*) FindControl(202); + edt_admin_port = (EditBox*) FindControl(203); + edt_game_pass = (EditBox*) FindControl(204); + edt_admin_name = (EditBox*) FindControl(205); + edt_admin_pass = (EditBox*) FindControl(206); + + btn_apply = (Button*) FindControl(1); + btn_cancel = (Button*) FindControl(2); + + REGISTER_CLIENT(EID_CLICK, btn_apply, NetServerDlg, OnApply); + REGISTER_CLIENT(EID_CLICK, btn_cancel, NetServerDlg, OnCancel); +} + +// +--------------------------------------------------------------------+ + +void +NetServerDlg::Show() +{ + if (!IsShown()) + FormWindow::Show(); + + NetServerConfig::Initialize(); + config = NetServerConfig::GetInstance(); + + if (config) { + config->Load(); + + char buff[32]; + + if (edt_name) { + edt_name->SetText(config->Name()); + edt_name->SetFocus(); + } + + if (cmb_type) + cmb_type->SetSelection(config->GetGameType()); + + if (edt_game_port) { + sprintf(buff, "%d", config->GetLobbyPort()); + edt_game_port->SetText(buff); + } + + if (edt_admin_port) { + sprintf(buff, "%d", config->GetAdminPort()); + edt_admin_port->SetText(buff); + } + + if (edt_game_pass) + edt_game_pass->SetText(config->GetGamePass()); + + if (edt_admin_name) + edt_admin_name->SetText(config->GetAdminName()); + + if (edt_admin_pass) + edt_admin_pass->SetText(config->GetAdminPass()); + } +} + +// +--------------------------------------------------------------------+ + +void +NetServerDlg::ExecFrame() +{ +} + +// +--------------------------------------------------------------------+ + +void +NetServerDlg::OnApply(AWEvent* event) +{ + if (config) { + if (edt_name) + config->SetName(edt_name->GetText()); + + if (cmb_type) + config->SetGameType(cmb_type->GetSelectedIndex()); + + if (edt_game_port) { + int port = 0; + sscanf(edt_game_port->GetText(), "%d", &port); + config->SetLobbyPort((WORD) port); + config->SetGamePort((WORD) port+1); + } + + if (edt_admin_port) { + int port = 0; + sscanf(edt_admin_port->GetText(), "%d", &port); + config->SetAdminPort((WORD) port); + } + + if (edt_game_pass) + config->SetGamePass(edt_game_pass->GetText()); + + if (edt_admin_name) + config->SetAdminName(edt_admin_name->GetText()); + + if (edt_admin_pass) + config->SetAdminPass(edt_admin_pass->GetText()); + + + config->Save(); + } + + Starshatter* stars = Starshatter::GetInstance(); + + if (stars) { + ::Print("\nSTART LOCAL SERVER\n\n"); + stars->SetLobbyMode(Starshatter::NET_LOBBY_SERVER); + manager->ShowNetLobbyDlg(); + } + else { + manager->ShowMenuDlg(); + } +} + +void +NetServerDlg::OnCancel(AWEvent* event) +{ + NetServerConfig::Close(); + manager->ShowNetClientDlg(); +} diff --git a/Stars45/NetServerDlg.h b/Stars45/NetServerDlg.h new file mode 100644 index 0000000..57d64f4 --- /dev/null +++ b/Stars45/NetServerDlg.h @@ -0,0 +1,64 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetServerDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Main Menu Dialog Active Window class +*/ + +#ifndef NetServerDlg_h +#define NetServerDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "Bitmap.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Font.h" + +// +--------------------------------------------------------------------+ + +class MenuScreen; +class NetServerConfig; + +// +--------------------------------------------------------------------+ + +class NetServerDlg : public FormWindow +{ +public: + NetServerDlg(Screen* s, FormDef& def, MenuScreen* mgr); + virtual ~NetServerDlg(); + + virtual void RegisterControls(); + virtual void Show(); + virtual void ExecFrame(); + + // Operations: + virtual void OnApply(AWEvent* event); + virtual void OnCancel(AWEvent* event); + +protected: + MenuScreen* manager; + NetServerConfig* config; + + EditBox* edt_name; + ComboBox* cmb_type; + EditBox* edt_game_port; + EditBox* edt_admin_port; + EditBox* edt_game_pass; + EditBox* edt_admin_name; + EditBox* edt_admin_pass; + + Button* btn_apply; + Button* btn_cancel; +}; + +#endif NetServerDlg_h + diff --git a/Stars45/NetUnitDlg.cpp b/Stars45/NetUnitDlg.cpp new file mode 100644 index 0000000..4d227e6 --- /dev/null +++ b/Stars45/NetUnitDlg.cpp @@ -0,0 +1,641 @@ +/* Project Starshatter 5.0 + Destroyer Studios LLC + Copyright © 1997-2007. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetUnitDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Main Menu Dialog Active Window class +*/ + +#include "MemDebug.h" +#include "NetUnitDlg.h" +#include "NetClientConfig.h" +#include "ConfirmDlg.h" +#include "MenuScreen.h" +#include "Starshatter.h" +#include "Campaign.h" +#include "Mission.h" +#include "Ship.h" +#include "Player.h" +#include "Campaign.h" +#include "ShipDesign.h" + +#include "NetAddr.h" +#include "NetLobbyClient.h" +#include "NetLobbyServer.h" +#include "NetUser.h" +#include "NetChat.h" + +#include "DataLoader.h" +#include "Video.h" +#include "Keyboard.h" +#include "MachineInfo.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +DEF_MAP_CLIENT(NetUnitDlg, OnSelect); +DEF_MAP_CLIENT(NetUnitDlg, OnUnit); +DEF_MAP_CLIENT(NetUnitDlg, OnMap); +DEF_MAP_CLIENT(NetUnitDlg, OnUnMap); +DEF_MAP_CLIENT(NetUnitDlg, OnBan); +DEF_MAP_CLIENT(NetUnitDlg, OnBanConfirm); +DEF_MAP_CLIENT(NetUnitDlg, OnApply); +DEF_MAP_CLIENT(NetUnitDlg, OnCancel); + +// +--------------------------------------------------------------------+ + +NetUnitDlg::NetUnitDlg(Screen* s, FormDef& def, MenuScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), manager(mgr), + net_lobby(0), unit_index(-1) +{ + last_chat = 0; + host_mode = false; + + Init(def); +} + +NetUnitDlg::~NetUnitDlg() +{ +} + +// +--------------------------------------------------------------------+ + +void +NetUnitDlg::RegisterControls() +{ + lst_players = (ListBox*) FindControl(201); + lst_units = (ListBox*) FindControl(202); + lst_chat = (ListBox*) FindControl(211); + edt_chat = (EditBox*) FindControl(212); + + REGISTER_CLIENT(EID_SELECT, lst_units, NetUnitDlg, OnUnit); + + if (edt_chat) + edt_chat->SetText(""); + + btn_select = (Button*) FindControl(206); + REGISTER_CLIENT(EID_CLICK, btn_select, NetUnitDlg, OnSelect); + + btn_map = (Button*) FindControl(203); + REGISTER_CLIENT(EID_CLICK, btn_map, NetUnitDlg, OnMap); + + btn_unmap = (Button*) FindControl(204); + REGISTER_CLIENT(EID_CLICK, btn_unmap, NetUnitDlg, OnUnMap); + + btn_ban = (Button*) FindControl(205); + + if (btn_ban) { + REGISTER_CLIENT(EID_CLICK, btn_ban, NetUnitDlg, OnBan); + REGISTER_CLIENT(EID_USER_1, btn_ban, NetUnitDlg, OnBanConfirm); + } + + btn_apply = (Button*) FindControl(1); + REGISTER_CLIENT(EID_CLICK, btn_apply, NetUnitDlg, OnApply); + + btn_cancel = (Button*) FindControl(2); + REGISTER_CLIENT(EID_CLICK, btn_cancel, NetUnitDlg, OnCancel); +} + +// +--------------------------------------------------------------------+ + +void +NetUnitDlg::Show() +{ + if (!IsShown()) { + FormWindow::Show(); + + // clear server data: + if (lst_players) { + lst_players->ClearItems(); + lst_players->SetSelectedStyle(ListBox::LIST_ITEM_STYLE_FILLED_BOX); + lst_players->SetLeading(2); + } + + if (lst_units) { + lst_units->ClearItems(); + lst_units->SetSelectedStyle(ListBox::LIST_ITEM_STYLE_FILLED_BOX); + lst_units->SetLeading(2); + } + + if (lst_chat) lst_chat->ClearItems(); + last_chat = 0; + + if (btn_apply) + btn_apply->SetEnabled(false); + + net_lobby = NetLobby::GetInstance(); + host_mode = false; + + if (net_lobby) { + host_mode = net_lobby->IsHost(); + } + + if (host_mode) { + btn_select->Hide(); + btn_map->Show(); + btn_unmap->Show(); + btn_ban->Show(); + } + else { + btn_select->Show(); + btn_map->Hide(); + btn_unmap->Hide(); + btn_ban->Hide(); + } + } +} + +// +--------------------------------------------------------------------+ + +void +NetUnitDlg::ExecFrame() +{ + ExecLobbyFrame(); + + if (!net_lobby) + return; + + Text player_name; + + if (Player::GetCurrentPlayer()) + player_name = Player::GetCurrentPlayer()->Name(); + + if (btn_select) { + bool enable = false; + + if (lst_players && lst_units && btn_select->IsVisible()) { + int sel_unit = lst_units->GetSelection(); + + enable = sel_unit >= 0 && + lst_units->GetItemText(sel_unit).length() == 0; + } + + btn_select->SetEnabled(enable); + } + + if (btn_map) { + bool enable = false; + + if (lst_players && lst_units && btn_map->IsVisible()) { + int sel_play = lst_players->GetSelection(); + int sel_unit = lst_units->GetSelection(); + + enable = sel_unit >= 0 && sel_play >= 0 && + lst_units->GetItemText(sel_unit).length() == 0; + + if (enable && !host_mode) { + NetUser* u = (NetUser*) lst_players->GetItemData(sel_play); + if (!u || u->Name() != player_name) + enable = false; + } + } + + btn_map->SetEnabled(enable); + } + + if (btn_unmap) { + bool enable = false; + + if (lst_players && lst_units && btn_unmap->IsVisible()) { + int sel_play = lst_players->GetSelection(); + int sel_unit = lst_units->GetSelection(); + + enable = sel_unit >= 0 && lst_units->GetItemText(sel_unit).length() > 0; + + if (enable && !host_mode) { + NetUser* u = (NetUser*) lst_units->GetItemData(sel_unit); + if (!u || u->Name() != Player::GetCurrentPlayer()->Name()) + enable = false; + } + } + + btn_unmap->SetEnabled(enable); + } + + if (btn_ban) { + bool enable = false; + + if (lst_players && lst_units && host_mode && btn_ban->IsVisible()) { + int sel_play = lst_players->GetSelection(); + int sel_unit = lst_units->GetSelection(); + + enable = sel_play >= 0 && sel_unit < 0; + + if (enable) { + NetUser* u = (NetUser*) lst_players->GetItemData(sel_play); + if (u && u->Name() == player_name) + enable = false; + } + } + + btn_ban->SetEnabled(enable); + } + + if (btn_apply) { + bool ok_to_start = net_lobby->IsMapped(player_name); + + NetUser* host = net_lobby->GetHost(); + if (host && !net_lobby->IsMapped(host->Name())) + ok_to_start = false; + + btn_apply->SetEnabled(ok_to_start); + } + + if (Keyboard::KeyDown(VK_RETURN)) { + if (edt_chat && edt_chat->GetText().length() > 0) { + SendChat(edt_chat->GetText()); + edt_chat->SetText(""); + } + } + + CheckUnitMapping(); + GetChat(); + GetUnits(); + GetAvailable(); +} + +// +--------------------------------------------------------------------+ + +void +NetUnitDlg::ExecLobbyFrame() +{ + Starshatter* stars = Starshatter::GetInstance(); + + if (!net_lobby) { + manager->ShowNetClientDlg(); + } + + else if (net_lobby->GetLastError() != 0) { + if (net_lobby->IsClient()) { + if (stars) + stars->StopLobby(); + + net_lobby = 0; + manager->ShowNetClientDlg(); + } + } +} + +// +--------------------------------------------------------------------+ + +static bool assignment_change = false; +static int num_users = 0; + +void +NetUnitDlg::CheckUnitMapping() +{ + if (net_lobby && lst_units) { + ListIter iter = net_lobby->GetUnitMap(); + List& units = iter.container(); + List& users = net_lobby->GetUsers(); + + if (users.size() != num_users) { + assignment_change = true; + num_users = users.size(); + } + + if (units.size() != lst_units->NumItems()) { + assignment_change = true; + } + + else if (lst_units->NumItems()) { + for (int i = 0; i < units.size(); i++) { + NetUnitEntry* e = units.at(i); + Text user_name = e->GetUserName(); + NetUser* u = net_lobby->FindUserByName(user_name); + + if (lst_units->GetItemData(i) != (DWORD) u) + assignment_change = true; + } + } + } +} + +void +NetUnitDlg::GetAvailable() +{ + if (!lst_players) return; + + Text player_name; + + if (Player::GetCurrentPlayer()) + player_name = Player::GetCurrentPlayer()->Name(); + + if (net_lobby) { + List available_users; + + NetUser* u = net_lobby->GetLocalUser(); + if (u) { + bool assigned = false; + ListIter iter = net_lobby->GetUnitMap(); + while (++iter) { + NetUnitEntry* unit = iter.value(); + if (unit->GetUserName() == u->Name()) + assigned = true; + } + + if (!assigned) + available_users.append(u); + } + + ListIter iter = net_lobby->GetUsers(); + while (++iter) { + NetUser* u = iter.value(); + bool assigned = false; + ListIter iter = net_lobby->GetUnitMap(); + while (++iter) { + NetUnitEntry* unit = iter.value(); + if (unit->GetUserName() == u->Name()) + assigned = true; + } + + if (!assigned) { + available_users.append(u); + } + } + + if (available_users.size() != lst_players->NumItems()) { + assignment_change = true; + lst_players->ClearItems(); + + for (int i = 0; i < available_users.size(); i++) { + NetUser* u = available_users[i]; + + Text name = Player::RankAbrv(u->Rank()); + name += " "; + name += u->Name(); + + lst_players->AddItemWithData(name.data(), (DWORD) u); + + if (!host_mode && u->Name() == player_name) { + lst_players->SetSelected(lst_players->NumItems()-1); + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +NetUnitDlg::GetUnits() +{ + if (!lst_units) return; + + if (net_lobby) { + ListIter iter = net_lobby->GetUnitMap(); + List& units = iter.container(); + List& users = net_lobby->GetUsers(); + + if (assignment_change) { + lst_units->ClearItems(); + + for (int i = 0; i < units.size(); i++) { + NetUnitEntry* e = units.at(i); + + char name[64]; + char team[16]; + + if (e->GetIndex()) + sprintf(name, "%s %d", e->GetElemName().data(), e->GetIndex()); + else + strcpy(name, e->GetElemName().data()); + + sprintf(team, "%d", e->GetIFF()); + + Text user_name = e->GetUserName(); + + NetUser* u = net_lobby->FindUserByName(user_name); + if (u) { + user_name = Player::RankAbrv(u->Rank()); + user_name += " "; + user_name += u->Name(); + } + + int count = lst_units->AddItemWithData(user_name, (DWORD) u); + lst_units->SetItemText(count-1, 1, name); + lst_units->SetItemText(count-1, 2, e->GetDesign()); + + if (lst_units->NumColumns() > 4) { + lst_units->SetItemText(count-1, 3, Mission::RoleName(e->GetMissionRole())); + lst_units->SetItemText(count-1, 4, team); + } + else if (lst_units->NumColumns() > 3) { + lst_units->SetItemText(count-1, 3, team); + } + } + } + } + + assignment_change = false; +} + +// +--------------------------------------------------------------------+ + +void +NetUnitDlg::GetChat() +{ + if (!lst_chat) return; + + if (net_lobby) { + int last_item = lst_chat->NumItems() - 1; + int count = 0; + bool added = false; + + ListIter iter = net_lobby->GetChat(); + while (++iter) { + NetChatEntry* c = iter.value(); + + if (count++ > last_item) { + int n = lst_chat->AddItem(c->GetUser()); + lst_chat->SetItemText(n-1, 1, c->GetMessage()); + added = true; + } + } + + if (added) + lst_chat->EnsureVisible(lst_chat->NumItems()+1); + } +} + + +void +NetUnitDlg::SendChat(Text msg) +{ + if (msg.length() < 1) return; + + Player* player = Player::GetCurrentPlayer(); + + if (msg[0] >= '0' && msg[0] <= '9') { + if (player) { + Text macro = player->ChatMacro(msg[0] - '0'); + + if (macro.length()) + msg = macro; + } + } + + if (net_lobby) + net_lobby->AddChat(0, msg); +} + +// +--------------------------------------------------------------------+ + +void +NetUnitDlg::OnUnit(AWEvent* event) +{ + if (!lst_units || host_mode) return; + + static DWORD unit_click_time = 0; + + int list_index = lst_units->GetListIndex(); + + // double-click: + if (list_index == unit_index && Game::RealTime() - unit_click_time < 350) { + OnSelect(0); + } + + unit_click_time = Game::RealTime(); + unit_index = list_index; +} + +// +--------------------------------------------------------------------+ + +void +NetUnitDlg::OnSelect(AWEvent* event) +{ + if (!lst_players || !lst_units) return; + + Text player_name; + + if (Player::GetCurrentPlayer()) + player_name = Player::GetCurrentPlayer()->Name(); + + int sel_unit = lst_units->GetSelection(); + + if (net_lobby) { + net_lobby->MapUnit(sel_unit, player_name, host_mode); + lst_units->ClearItems(); + } + + assignment_change = true; + + GetUnits(); + GetAvailable(); +} + +// +--------------------------------------------------------------------+ + +void +NetUnitDlg::OnMap(AWEvent* event) +{ + if (!lst_players || !lst_units) return; + + int sel_player = lst_players->GetSelection(); + int sel_unit = lst_units->GetSelection(); + + if (net_lobby) { + NetUser* u = (NetUser*) lst_players->GetItemData(sel_player); + net_lobby->MapUnit(sel_unit, u->Name(), host_mode); + lst_units->ClearItems(); + } + + assignment_change = true; + + GetUnits(); + GetAvailable(); +} + +void +NetUnitDlg::OnUnMap(AWEvent* event) +{ + if (!lst_players || !lst_units) return; + + if (net_lobby) { + net_lobby->MapUnit(lst_units->GetSelection(), 0, host_mode); + lst_units->ClearItems(); + } + + assignment_change = true; + + GetUnits(); + GetAvailable(); +} + +void +NetUnitDlg::OnBan(AWEvent* event) +{ + if (!lst_players) return; + + int sel_player = lst_players->GetSelection(); + + if (net_lobby) { + NetUser* u = (NetUser*) lst_players->GetItemData(sel_player); + + ConfirmDlg* confirm = manager->GetConfirmDlg(); + if (confirm) { + char msg[512]; + sprintf(msg, Game::GetText("NetUnitDlg.are-you-sure").data(), u->Name()); + confirm->SetMessage(msg); + confirm->SetTitle(Game::GetText("NetUnitDlg.confirm-ban")); + confirm->SetParentControl(btn_ban); + + manager->ShowConfirmDlg(); + } + + else { + OnBanConfirm(event); + } + } +} + +void +NetUnitDlg::OnBanConfirm(AWEvent* event) +{ + if (!lst_players) return; + + int sel_player = lst_players->GetSelection(); + + if (net_lobby) { + NetUser* u = (NetUser*) lst_players->GetItemData(sel_player); + net_lobby->BanUser(u); + lst_units->ClearItems(); + } + + GetUnits(); + GetAvailable(); +} + +void +NetUnitDlg::OnApply(AWEvent* event) +{ + bool ok = false; + + if (net_lobby) { + Mission* mission = net_lobby->GetSelectedMission(); + + if (mission) { + net_lobby->GameStart(); + ok = true; + } + } + + if (!ok) OnCancel(0); +} + +void +NetUnitDlg::OnCancel(AWEvent* event) +{ + if (net_lobby) { + net_lobby->SelectMission(0); + net_lobby = 0; + } + + manager->ShowNetLobbyDlg(); +} diff --git a/Stars45/NetUnitDlg.h b/Stars45/NetUnitDlg.h new file mode 100644 index 0000000..5b8b4fd --- /dev/null +++ b/Stars45/NetUnitDlg.h @@ -0,0 +1,93 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetUnitDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Multiplayer Unit Selection Dialog Active Window class +*/ + +#ifndef NetUnitDlg_h +#define NetUnitDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "Bitmap.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "EditBox.h" +#include "Font.h" + +// +--------------------------------------------------------------------+ + +class MenuScreen; +class NetClientConfig; +class NetLobby; +class NetChatEntry; +class NetUser; + +// +--------------------------------------------------------------------+ + +class NetUnitDlg : public FormWindow +{ +public: + NetUnitDlg(Screen* s, FormDef& def, MenuScreen* mgr); + virtual ~NetUnitDlg(); + + virtual void RegisterControls(); + virtual void Show(); + virtual void ExecFrame(); + + // Operations: + virtual void OnSelect(AWEvent* event); + virtual void OnUnit(AWEvent* event); + virtual void OnMap(AWEvent* event); + virtual void OnUnMap(AWEvent* event); + virtual void OnBan(AWEvent* event); + virtual void OnBanConfirm(AWEvent* event); + virtual void OnApply(AWEvent* event); + virtual void OnCancel(AWEvent* event); + + virtual void ExecLobbyFrame(); + + virtual bool GetHostMode() const { return host_mode; } + virtual void SetHostMode(bool h) { host_mode = h; } + +protected: + virtual void GetAvailable(); + virtual void GetUnits(); + virtual void GetChat(); + virtual void SendChat(Text msg); + virtual void CheckUnitMapping(); + + MenuScreen* manager; + + ListBox* lst_players; + ListBox* lst_units; + ListBox* lst_chat; + EditBox* edt_chat; + + Button* btn_select; + Button* btn_map; + Button* btn_unmap; + Button* btn_ban; + Button* btn_apply; + Button* btn_cancel; + + NetLobby* net_lobby; + + int last_chat; + int unit_index; + bool host_mode; +}; + +// +--------------------------------------------------------------------+ + +#endif NetUnitDlg_h + diff --git a/Stars45/NetUser.cpp b/Stars45/NetUser.cpp new file mode 100644 index 0000000..ce9217f --- /dev/null +++ b/Stars45/NetUser.cpp @@ -0,0 +1,117 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetUser.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + This class represents a user connecting to the multiplayer lobby +*/ + + +#include "MemDebug.h" +#include "NetUser.h" +#include "NetAuth.h" +#include "NetLobby.h" +#include "Player.h" +#include "FormatUtil.h" + +// +-------------------------------------------------------------------+ + +static Color user_colors[] = { + Color::BrightBlue, + Color::BrightRed, + Color::BrightGreen, + Color::Cyan, + Color::Orange, + Color::Magenta, + Color::Yellow, + Color::White, + Color::Gray, + Color::DarkRed, + Color::Tan, + Color::Violet +}; + +static int user_color_index = 0; + +// +-------------------------------------------------------------------+ + +NetUser::NetUser(const char* n) + : name(n), netid(0), host(false), + rank(0), flight_time(0), missions(0), kills(0), losses(0), + auth_state(NetAuth::NET_AUTH_INITIAL), + auth_level(NetAuth::NET_AUTH_MINIMAL) +{ + if (user_color_index < 0 || user_color_index >= 12) + user_color_index = 0; + + color = user_colors[user_color_index++]; + + ZeroMemory(salt, sizeof(salt)); +} + +NetUser::NetUser(const Player* player) + : netid(0), host(false), + rank(0), flight_time(0), missions(0), kills(0), losses(0), + auth_state(NetAuth::NET_AUTH_INITIAL), + auth_level(NetAuth::NET_AUTH_MINIMAL) +{ + if (user_color_index < 0 || user_color_index >= 12) + user_color_index = 0; + + color = user_colors[user_color_index++]; + + if (player) { + name = player->Name(); + pass = player->Password(); + signature = player->Signature(); + squadron = player->Squadron(); + rank = player->Rank(); + flight_time = player->FlightTime(); + missions = player->Missions(); + kills = player->Kills(); + losses = player->Losses(); + } + + ZeroMemory(salt, sizeof(salt)); +} + +NetUser::~NetUser() +{ } + +// +-------------------------------------------------------------------+ + +Text +NetUser::GetDescription() +{ + Text content; + + content += "name \""; + content += SafeQuotes(name); + content += "\" sig \""; + content += SafeQuotes(signature); + content += "\" squad \""; + content += SafeQuotes(squadron); + + char buffer[256]; + sprintf(buffer, "\" rank %d time %d miss %d kill %d loss %d host %s ", + rank, flight_time, missions, kills, losses, + host ? "true" : "false"); + + content += buffer; + + return content; +} + +// +-------------------------------------------------------------------+ + +bool +NetUser::IsAuthOK() const +{ + return auth_state == NetAuth::NET_AUTH_OK; +} \ No newline at end of file diff --git a/Stars45/NetUser.h b/Stars45/NetUser.h new file mode 100644 index 0000000..6d8c9eb --- /dev/null +++ b/Stars45/NetUser.h @@ -0,0 +1,112 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetUser.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + This class represents a user connecting to the multiplayer lobby +*/ + + +#ifndef NetUser_h +#define NetUser_h + +#include "Types.h" +#include "NetAddr.h" +#include "NetLobby.h" +#include "Color.h" + +// +-------------------------------------------------------------------+ + +class Player; + +// +-------------------------------------------------------------------+ + +class NetUser +{ +public: + static const char* TYPENAME() { return "NetUser"; } + + NetUser(const char* name); + NetUser(const Player* player); + virtual ~NetUser(); + + int operator == (const NetUser& u) const { return this == &u; } + + const Text& Name() const { return name; } + const Text& Pass() const { return pass; } + const NetAddr& GetAddress() const { return addr; } + Color GetColor() const { return color; } + const Text& GetSessionID() const { return session_id; } + DWORD GetNetID() const { return netid; } + bool IsHost() const { return host; } + + int AuthLevel() const { return auth_level; } + int AuthState() const { return auth_state; } + const char* Salt() const { return salt; } + bool IsAuthOK() const; + + const Text& Squadron() const { return squadron; } + const Text& Signature() const { return signature; } + int Rank() const { return rank; } + int FlightTime() const { return flight_time; } + int Missions() const { return missions; } + int Kills() const { return kills; } + int Losses() const { return losses; } + + void SetName(const char* n) { name = n; } + void SetPass(const char* p) { pass = p; } + void SetAddress(const NetAddr& a) + { addr = a; } + + void SetColor(Color c) { color = c; } + void SetNetID(DWORD id) { netid = id; } + void SetSessionID(Text s) { session_id = s; } + void SetHost(bool h) { host = h; } + + void SetAuthLevel(int n) { auth_level = n; } + void SetAuthState(int n) { auth_state = n; } + void SetSalt(const char* s) { strcpy(salt, s);} + + void SetSquadron(const char* s) { squadron = s; } + void SetSignature(const char* s){ signature = s; } + void SetRank(int n) { rank = n; } + void SetFlightTime(int n) { flight_time = n;} + void SetMissions(int n) { missions = n; } + void SetKills(int n) { kills = n; } + void SetLosses(int n) { losses = n; } + + Text GetDescription(); + +protected: + Text name; + Text pass; + Text session_id; + NetAddr addr; + DWORD netid; + Color color; + bool host; + + // authentication: + int auth_state; + int auth_level; + char salt[33]; + + // stats: + Text squadron; + Text signature; + int rank; + int flight_time; + int missions; + int kills; + int losses; +}; + +// +-------------------------------------------------------------------+ + +#endif NetUser_h \ No newline at end of file diff --git a/Stars45/NetUtil.cpp b/Stars45/NetUtil.cpp new file mode 100644 index 0000000..d0360f7 --- /dev/null +++ b/Stars45/NetUtil.cpp @@ -0,0 +1,424 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetUtil.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Utility class to simplify sending NetData messages. +*/ + + +#include "MemDebug.h" +#include "NetUtil.h" +#include "NetData.h" +#include "NetGame.h" +#include "NetGameServer.h" + +#include "Element.h" +#include "Instruction.h" +#include "Random.h" +#include "Ship.h" +#include "Shot.h" +#include "System.h" +#include "Weapon.h" + +// +-------------------------------------------------------------------+ + +void +NetUtil::SendObjDamage(SimObject* obj, double dmg, Shot* shot) +{ + NetGame* net_game = NetGame::GetInstance(); + if (!net_game || !obj) return; + + if (net_game->IsServer() && obj->GetObjID()) { + NetObjDamage damage; + damage.SetObjID(obj->GetObjID()); + damage.SetDamage((float) dmg); + + if (shot) + damage.SetShotID(shot->GetObjID()); + + net_game->SendData(&damage); + } +} + +// +-------------------------------------------------------------------+ + +void +NetUtil::SendSysDamage(Ship* obj, System* sys, double dmg) +{ + NetGame* net_game = NetGame::GetInstance(); + if (!net_game || !obj || !sys) return; + + if (net_game->IsServer() && obj->GetObjID()) { + NetSysDamage damage; + damage.SetObjID(obj->GetObjID()); + damage.SetDamage(dmg); + damage.SetSystem(sys->GetID()); + + net_game->SendData(&damage); + } +} + +// +-------------------------------------------------------------------+ + +void +NetUtil::SendSysStatus(Ship* obj, System* sys) +{ + NetGame* net_game = NetGame::GetInstance(); + if (!net_game || !obj || !sys) return; + + if (obj->GetObjID()) { + NetSysStatus status; + status.SetObjID(obj->GetObjID()); + status.SetSystem( (int) sys->GetID()); + status.SetStatus( (int) sys->Status()); + status.SetPower( (int) sys->GetPowerLevel()); + status.SetReactor((int) sys->GetSourceIndex()); + status.SetAvailablility(sys->Availability()); + + net_game->SendData(&status); + } +} + +// +-------------------------------------------------------------------+ + +void +NetUtil::SendObjKill(Ship* obj, const Ship* killer, int type, int deck) +{ + NetGame* net_game = NetGame::GetInstance(); + if (!net_game || !obj) return; + + if (type == NetObjKill::KILL_DOCK || (net_game->IsServer() && obj->GetObjID())) { + NetObjKill kill; + kill.SetObjID(obj->GetObjID()); + kill.SetFlightDeck(deck); + + if (killer) + kill.SetKillerID(killer->GetObjID()); + + kill.SetKillType(type); + + if (type != NetObjKill::KILL_DOCK && obj->RespawnCount() > 0) { + Print("NetObjKill preparing respawn for %s\n", obj->Name()); + + Point respawn_loc = RandomPoint() * 1.75; + kill.SetRespawn(true); + kill.SetRespawnLoc(respawn_loc); + obj->SetRespawnLoc(respawn_loc); + } + else { + Print("NetObjKill no respawn for %s\n", obj->Name()); + } + + net_game->SendData(&kill); + } +} + +// +-------------------------------------------------------------------+ + +void +NetUtil::SendObjHyper(Ship* obj, const char* rgn, const Point& loc, + const Ship* fc1, const Ship* fc2, int transtype) +{ + NetGame* net_game = NetGame::GetInstance(); + if (!net_game || !obj) return; + + if (obj->GetObjID()) { + NetObjHyper obj_hyper; + obj_hyper.SetObjID(obj->GetObjID()); + obj_hyper.SetRegion(rgn); + obj_hyper.SetLocation(loc); + obj_hyper.SetTransitionType(transtype); + + if (fc1) + obj_hyper.SetFarcaster1(fc1->GetObjID()); + + if (fc2) + obj_hyper.SetFarcaster2(fc2->GetObjID()); + + net_game->SendData(&obj_hyper); + } +} + +// +-------------------------------------------------------------------+ + +void +NetUtil::SendObjTarget(Ship* obj) +{ + NetGame* net_game = NetGame::GetInstance(); + if (!net_game || !obj) return; + + if (obj->GetObjID()) { + NetObjTarget obj_target; + obj_target.SetObjID(obj->GetObjID()); + + SimObject* target = obj->GetTarget(); + if (target) { + obj_target.SetTgtID(target->GetObjID()); + + System* subtarget = obj->GetSubTarget(); + + if (subtarget) { + Ship* s = (Ship*) target; + obj_target.SetSubtarget(subtarget->GetID()); + } + } + + net_game->SendData(&obj_target); + } +} + +// +-------------------------------------------------------------------+ + +void +NetUtil::SendObjEmcon(Ship* obj) +{ + NetGame* net_game = NetGame::GetInstance(); + if (!net_game || !obj) return; + + if (obj->GetObjID()) { + NetObjEmcon obj_emcon; + obj_emcon.SetObjID(obj->GetObjID()); + obj_emcon.SetEMCON(obj->GetEMCON()); + net_game->SendData(&obj_emcon); + } +} + +// +-------------------------------------------------------------------+ + +void +NetUtil::SendWepTrigger(Weapon* wep, int count) +{ + NetGame* net_game = NetGame::GetInstance(); + if (!net_game || !wep) return; + + if (wep->IsPrimary() || net_game->IsClient()) { + NetWepTrigger trigger; + trigger.SetObjID(wep->Owner()->GetObjID()); + + SimObject* target = wep->GetTarget(); + if (target) { + trigger.SetTgtID(target->GetObjID()); + + System* subtarget = wep->GetSubTarget(); + + if (subtarget) { + Ship* s = (Ship*) target; + trigger.SetSubtarget(subtarget->GetID()); + } + } + + trigger.SetIndex(wep->GetIndex()); + trigger.SetCount(count); + trigger.SetDecoy(wep->IsDecoy()); + trigger.SetProbe(wep->IsProbe()); + + net_game->SendData(&trigger); + } +} + +// +-------------------------------------------------------------------+ + +void +NetUtil::SendWepRelease(Weapon* wep, Shot* shot) +{ + NetGame* net_game = NetGame::GetInstance(); + if (!net_game || !wep || !shot) return; + + if (net_game->IsServer() && wep->IsMissile()) { + DWORD wepid = NetGame::GetNextObjID(NetGame::SHOT); + shot->SetObjID(wepid); + + NetWepRelease release; + release.SetObjID(wep->Owner()->GetObjID()); + + SimObject* target = wep->GetTarget(); + if (target) + release.SetTgtID(target->GetObjID()); + + System* subtarget = wep->GetSubTarget(); + if (target && subtarget && target->Type() == SimObject::SIM_SHIP) { + Ship* tgt = (Ship*) target; + release.SetSubtarget(subtarget->GetID()); + } + + release.SetWepID(wepid); + release.SetIndex(wep->GetIndex()); + release.SetDecoy(shot->IsDecoy()); + release.SetProbe(shot->IsProbe()); + + net_game->SendData(&release); + } +} + +// +-------------------------------------------------------------------+ + +void +NetUtil::SendWepDestroy(Shot* shot) +{ + NetGame* net_game = NetGame::GetInstance(); + if (!net_game || !shot) return; + + if (net_game->IsServer() && shot->GetObjID()) { + NetWepDestroy destroy; + + destroy.SetObjID(shot->GetObjID()); + + net_game->SendData(&destroy); + } +} + +// +-------------------------------------------------------------------+ + +void +NetUtil::SendChat(DWORD dst, const char* name, const char* text) +{ + NetGame* net_game = NetGame::GetInstance(); + if (!net_game || !name || !*name || !text || !*text) return; + + NetChatMsg chat_msg; + chat_msg.SetDstID(dst); + chat_msg.SetName(name); + chat_msg.SetText(text); + + if (net_game->IsClient()) { + net_game->SendData(&chat_msg); + } + + else { + NetGameServer* net_game_server = (NetGameServer*) net_game; + net_game_server->RouteChatMsg(chat_msg); + } +} + +// +-------------------------------------------------------------------+ + +void +NetUtil::SendElemRequest(const char* name) +{ + NetGame* net_game = NetGame::GetInstance(); + if (!net_game || !name) return; + + NetElemRequest elem_request; + elem_request.SetName(name); + + ::Print("NetUtil::SendElemRequest name: '%s'\n", name); + net_game->SendData(&elem_request); +} + +// +-------------------------------------------------------------------+ + +void +NetUtil::SendElemCreate(Element* elem, int squadron, int* slots, bool alert, bool in_flight) +{ + NetGame* net_game = NetGame::GetInstance(); + if (!net_game || !elem) return; + + NetElemCreate elem_create; + elem_create.SetName(elem->Name()); + elem_create.SetType(elem->Type()); + elem_create.SetIFF(elem->GetIFF()); + elem_create.SetIntel(elem->IntelLevel()); + elem_create.SetLoadout(elem->Loadout()); + elem_create.SetSquadron(squadron); + elem_create.SetSlots(slots); + elem_create.SetAlert(alert); + elem_create.SetInFlight(in_flight); + + if (elem->NumObjectives() > 0) { + Instruction* obj = elem->GetObjective(0); + + if (obj) { + elem_create.SetObjCode(obj->Action()); + elem_create.SetObjective(obj->TargetName()); + } + } + + if (elem->GetCarrier()) + elem_create.SetCarrier(elem->GetCarrier()->Name()); + + if (elem->GetCommander()) + elem_create.SetCommander(elem->GetCommander()->Name()); + + ::Print("NetUtil::SendElemCreate iff: %d name: '%s'\n", elem->GetIFF(), elem->Name().data()); + net_game->SendData(&elem_create); +} + +// +-------------------------------------------------------------------+ + +void +NetUtil::SendShipLaunch(Ship* carrier, int squadron, int slot) +{ + NetGame* net_game = NetGame::GetInstance(); + if (!net_game || !carrier) return; + + if (carrier->GetObjID()) { + NetShipLaunch ship_launch; + + ship_launch.SetObjID(carrier->GetObjID()); + ship_launch.SetSquadron(squadron); + ship_launch.SetSlot(slot); + + net_game->SendData(&ship_launch); + } +} + +// +-------------------------------------------------------------------+ + +void +NetUtil::SendNavData(bool add, Element* elem, int index, Instruction* navpt) +{ + NetGame* net_game = NetGame::GetInstance(); + if (!net_game || !elem || !navpt) return; + + // resolve rloc before copying the navpoint into the net nav data structure: + Point loc = navpt->Location(); + + NetNavData nav_data; + nav_data.SetObjID(net_game->GetObjID()); + nav_data.SetAdd(add); + nav_data.SetElem(elem->Name()); + nav_data.SetIndex(index); + nav_data.SetNavPoint(navpt); + + net_game->SendData(&nav_data); +} + +// +-------------------------------------------------------------------+ + +void +NetUtil::SendNavDelete(Element* elem, int index) +{ + NetGame* net_game = NetGame::GetInstance(); + if (!net_game || !elem) return; + + NetNavDelete nav_delete; + nav_delete.SetObjID(net_game->GetObjID()); + nav_delete.SetElem(elem->Name()); + nav_delete.SetIndex(index); + + net_game->SendData(&nav_delete); +} + +// +-------------------------------------------------------------------+ + +void +NetUtil::SendSelfDestruct(Ship* obj, double dmg) +{ + NetGame* net_game = NetGame::GetInstance(); + if (!net_game || !obj) return; + + if (obj->GetObjID()) { + NetSelfDestruct sd; + sd.SetObjID(obj->GetObjID()); + sd.SetDamage((float) dmg); + + net_game->SendData(&sd); + } +} diff --git a/Stars45/NetUtil.h b/Stars45/NetUtil.h new file mode 100644 index 0000000..d0b277e --- /dev/null +++ b/Stars45/NetUtil.h @@ -0,0 +1,56 @@ +/* Project STARSHATTER + John DiCamillo + Copyright © 1997-2002. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: NetUtil.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Utility class to simplify sending NetData messages. +*/ + + +#ifndef NetUtil_h +#define NetUtil_h + +#include "Types.h" +#include "Geometry.h" + +// +-------------------------------------------------------------------+ + +class Element; +class Instruction; +class SimObject; +class Ship; +class Shot; +class System; +class Weapon; + +// +-------------------------------------------------------------------+ + +class NetUtil +{ +public: + static void SendObjDamage(SimObject* obj, double damage, Shot* shot=0); + static void SendObjKill(Ship* obj, const Ship* killer, int type=0, int deck=0); + static void SendObjHyper(Ship* obj, const char* rgn, const Point& loc, const Ship* fc1=0, const Ship* fc2=0, int ttype=0); + static void SendObjTarget(Ship* obj); + static void SendObjEmcon(Ship* obj); + static void SendSysDamage(Ship* obj, System* sys, double damage); + static void SendSysStatus(Ship* obj, System* sys); + static void SendWepTrigger(Weapon* wep, int count=1); + static void SendWepRelease(Weapon* wep, Shot* shot); + static void SendWepDestroy(Shot* shot); + static void SendChat(DWORD dst, const char* name, const char* text); + static void SendElemRequest(const char* name); + static void SendElemCreate(Element* elem, int squadron, int* slots, bool alert, bool in_flight=false); + static void SendShipLaunch(Ship* carrier, int squadron, int slot); + static void SendNavData(bool add, Element* elem, int index, Instruction* navpt); + static void SendNavDelete(Element* elem, int index); + static void SendSelfDestruct(Ship* ship, double damage); +}; + +#endif NetUtil_h \ No newline at end of file diff --git a/Stars45/OptDlg.cpp b/Stars45/OptDlg.cpp new file mode 100644 index 0000000..e750a9b --- /dev/null +++ b/Stars45/OptDlg.cpp @@ -0,0 +1,322 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: OptDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Main Menu Dialog Active Window class +*/ + +#include "MemDebug.h" +#include "OptDlg.h" +#include "MenuScreen.h" +#include "Starshatter.h" +#include "Ship.h" +#include "HUDView.h" +#include "Player.h" + +#include "DataLoader.h" +#include "Button.h" +#include "ListBox.h" +#include "Slider.h" +#include "Video.h" +#include "Keyboard.h" +#include "MachineInfo.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +DEF_MAP_CLIENT(OptDlg, OnApply); +DEF_MAP_CLIENT(OptDlg, OnCancel); +DEF_MAP_CLIENT(OptDlg, OnEnter); +DEF_MAP_CLIENT(OptDlg, OnExit); +DEF_MAP_CLIENT(OptDlg, OnAudio); +DEF_MAP_CLIENT(OptDlg, OnVideo); +DEF_MAP_CLIENT(OptDlg, OnOptions); +DEF_MAP_CLIENT(OptDlg, OnControls); +DEF_MAP_CLIENT(OptDlg, OnMod); + +// +--------------------------------------------------------------------+ + +OptDlg::OptDlg(Screen* s, FormDef& def, BaseScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), manager(mgr), + apply(0), cancel(0), vid_btn(0), aud_btn(0), ctl_btn(0), opt_btn(0), mod_btn(0), + closed(true) +{ + Init(def); +} + +OptDlg::~OptDlg() +{ +} + +// +--------------------------------------------------------------------+ + +void +OptDlg::RegisterControls() +{ + if (apply) + return; + + flight_model = (ComboBox*) FindControl(201); + flying_start = (ComboBox*) FindControl(211); + landings = (ComboBox*) FindControl(202); + ai_difficulty = (ComboBox*) FindControl(203); + hud_mode = (ComboBox*) FindControl(204); + hud_color = (ComboBox*) FindControl(205); + ff_mode = (ComboBox*) FindControl(206); + grid_mode = (ComboBox*) FindControl(207); + gunsight = (ComboBox*) FindControl(208); + description = FindControl(500); + apply = (Button*) FindControl(1); + cancel = (Button*) FindControl(2); + vid_btn = (Button*) FindControl(901); + aud_btn = (Button*) FindControl(902); + ctl_btn = (Button*) FindControl(903); + opt_btn = (Button*) FindControl(904); + mod_btn = (Button*) FindControl(905); + + if (flight_model) { + REGISTER_CLIENT(EID_MOUSE_ENTER, flight_model, OptDlg, OnEnter); + REGISTER_CLIENT(EID_MOUSE_EXIT, flight_model, OptDlg, OnExit); + } + + if (flying_start) { + REGISTER_CLIENT(EID_MOUSE_ENTER, flying_start, OptDlg, OnEnter); + REGISTER_CLIENT(EID_MOUSE_EXIT, flying_start, OptDlg, OnExit); + } + + if (landings) { + REGISTER_CLIENT(EID_MOUSE_ENTER, landings, OptDlg, OnEnter); + REGISTER_CLIENT(EID_MOUSE_EXIT, landings, OptDlg, OnExit); + } + + if (ai_difficulty) { + REGISTER_CLIENT(EID_MOUSE_ENTER, ai_difficulty, OptDlg, OnEnter); + REGISTER_CLIENT(EID_MOUSE_EXIT, ai_difficulty, OptDlg, OnExit); + } + + if (hud_mode) { + REGISTER_CLIENT(EID_MOUSE_ENTER, hud_mode, OptDlg, OnEnter); + REGISTER_CLIENT(EID_MOUSE_EXIT, hud_mode, OptDlg, OnExit); + } + + if (hud_color) { + REGISTER_CLIENT(EID_MOUSE_ENTER, hud_color, OptDlg, OnEnter); + REGISTER_CLIENT(EID_MOUSE_EXIT, hud_color, OptDlg, OnExit); + } + + if (ff_mode) { + REGISTER_CLIENT(EID_MOUSE_ENTER, ff_mode, OptDlg, OnEnter); + REGISTER_CLIENT(EID_MOUSE_EXIT, ff_mode, OptDlg, OnExit); + } + + if (grid_mode) { + REGISTER_CLIENT(EID_MOUSE_ENTER, grid_mode, OptDlg, OnEnter); + REGISTER_CLIENT(EID_MOUSE_EXIT, grid_mode, OptDlg, OnExit); + } + + if (gunsight) { + REGISTER_CLIENT(EID_MOUSE_ENTER, gunsight, OptDlg, OnEnter); + REGISTER_CLIENT(EID_MOUSE_EXIT, gunsight, OptDlg, OnExit); + } + + if (apply) + REGISTER_CLIENT(EID_CLICK, apply, OptDlg, OnApply); + + if (cancel) + REGISTER_CLIENT(EID_CLICK, cancel, OptDlg, OnCancel); + + if (vid_btn) + REGISTER_CLIENT(EID_CLICK, vid_btn, OptDlg, OnVideo); + + if (aud_btn) + REGISTER_CLIENT(EID_CLICK, aud_btn, OptDlg, OnAudio); + + if (ctl_btn) + REGISTER_CLIENT(EID_CLICK, ctl_btn, OptDlg, OnControls); + + if (opt_btn) + REGISTER_CLIENT(EID_CLICK, opt_btn, OptDlg, OnOptions); + + if (mod_btn) + REGISTER_CLIENT(EID_CLICK, mod_btn, OptDlg, OnMod); +} + +// +--------------------------------------------------------------------+ + +void +OptDlg::Show() +{ + FormWindow::Show(); + + if (closed) { + Starshatter* stars = Starshatter::GetInstance(); + + if (stars) { + if (flight_model) + flight_model->SetSelection(Ship::GetFlightModel()); + + if (landings) + landings->SetSelection(Ship::GetLandingModel()); + + if (hud_mode) + hud_mode->SetSelection(HUDView::IsArcade() ? 1 : 0); + + if (hud_color) + hud_color->SetSelection(HUDView::DefaultColorSet()); + + if (ff_mode) + ff_mode->SetSelection((int) (Ship::GetFriendlyFireLevel() * 4)); + } + + Player* player = Player::GetCurrentPlayer(); + if (player) { + if (flying_start) + flying_start->SetSelection(player->FlyingStart()); + + if (ai_difficulty) + ai_difficulty->SetSelection(ai_difficulty->NumItems() - player->AILevel() - 1); + + if (grid_mode) + grid_mode->SetSelection(player->GridMode()); + + if (gunsight) + gunsight->SetSelection(player->Gunsight()); + } + } + + if (vid_btn) vid_btn->SetButtonState(0); + if (aud_btn) aud_btn->SetButtonState(0); + if (ctl_btn) ctl_btn->SetButtonState(0); + if (opt_btn) opt_btn->SetButtonState(1); + if (mod_btn) mod_btn->SetButtonState(0); + + closed = false; +} + +// +--------------------------------------------------------------------+ + +void +OptDlg::ExecFrame() +{ + if (Keyboard::KeyDown(VK_RETURN)) { + OnApply(0); + } +} + +// +--------------------------------------------------------------------+ + +void OptDlg::OnAudio(AWEvent* event) { manager->ShowAudDlg(); } +void OptDlg::OnVideo(AWEvent* event) { manager->ShowVidDlg(); } +void OptDlg::OnOptions(AWEvent* event) { manager->ShowOptDlg(); } +void OptDlg::OnControls(AWEvent* event) { manager->ShowCtlDlg(); } +void OptDlg::OnMod(AWEvent* event) { manager->ShowModDlg(); } + +// +--------------------------------------------------------------------+ + +void +OptDlg::OnApply(AWEvent* event) +{ + manager->ApplyOptions(); +} + +void +OptDlg::OnCancel(AWEvent* event) +{ + manager->CancelOptions(); +} + +// +--------------------------------------------------------------------+ + +void +OptDlg::OnEnter(AWEvent* event) +{ + ActiveWindow* src = event->window; + + if (src && description) + description->SetText(src->GetAltText()); +} + +void +OptDlg::OnExit(AWEvent* event) +{ + ComboBox* cmb = (ComboBox*) event->window; + + if (!cmb || cmb->IsListShowing()) + return; + + if (description) + description->SetText(""); +} + +// +--------------------------------------------------------------------+ + +void +OptDlg::Apply() +{ + if (closed) return; + + Player* player = Player::GetCurrentPlayer(); + if (player) { + if (flight_model) + player->SetFlightModel(flight_model->GetSelectedIndex()); + + if (flying_start) + player->SetFlyingStart(flying_start->GetSelectedIndex()); + + if (landings) + player->SetLandingModel(landings->GetSelectedIndex()); + + if (ai_difficulty) + player->SetAILevel(ai_difficulty->NumItems() - ai_difficulty->GetSelectedIndex() - 1); + + if (hud_mode) + player->SetHUDMode(hud_mode->GetSelectedIndex()); + + if (hud_color) + player->SetHUDColor(hud_color->GetSelectedIndex()); + + if (ff_mode) + player->SetFriendlyFire(ff_mode->GetSelectedIndex()); + + if (grid_mode) + player->SetGridMode(grid_mode->GetSelectedIndex()); + + if (gunsight) + player->SetGunsight(gunsight->GetSelectedIndex()); + + Player::Save(); + } + + if (flight_model) + Ship::SetFlightModel(flight_model->GetSelectedIndex()); + + if (landings) + Ship::SetLandingModel(landings->GetSelectedIndex()); + + if (hud_mode) + HUDView::SetArcade(hud_mode->GetSelectedIndex() > 0); + + if (hud_color) + HUDView::SetDefaultColorSet(hud_color->GetSelectedIndex()); + + if (ff_mode) + Ship::SetFriendlyFireLevel(ff_mode->GetSelectedIndex() / 4.0); + + HUDView* hud = HUDView::GetInstance(); + if (hud) hud->SetHUDColorSet(hud_color->GetSelectedIndex()); + + closed = true; +} + +void +OptDlg::Cancel() +{ + closed = true; +} diff --git a/Stars45/OptDlg.h b/Stars45/OptDlg.h new file mode 100644 index 0000000..dfc8f84 --- /dev/null +++ b/Stars45/OptDlg.h @@ -0,0 +1,87 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: OptDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Main Menu Dialog Active Window class +*/ + +#ifndef OptDlg_h +#define OptDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "Bitmap.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Font.h" + +// +--------------------------------------------------------------------+ + +class BaseScreen; + +// +--------------------------------------------------------------------+ + +class OptDlg : public FormWindow +{ +public: + OptDlg(Screen* s, FormDef& def, BaseScreen* mgr); + virtual ~OptDlg(); + + virtual void RegisterControls(); + virtual void Show(); + virtual void ExecFrame(); + + // Operations: + virtual void Apply(); + virtual void Cancel(); + + virtual void OnApply(AWEvent* event); + virtual void OnCancel(AWEvent* event); + + virtual void OnEnter(AWEvent* event); + virtual void OnExit(AWEvent* event); + + virtual void OnAudio(AWEvent* event); + virtual void OnVideo(AWEvent* event); + virtual void OnOptions(AWEvent* event); + virtual void OnControls(AWEvent* event); + virtual void OnMod(AWEvent* event); + +protected: + BaseScreen* manager; + + ComboBox* flight_model; + ComboBox* flying_start; + ComboBox* landings; + ComboBox* ai_difficulty; + ComboBox* hud_mode; + ComboBox* hud_color; + ComboBox* joy_mode; + ComboBox* ff_mode; + ComboBox* grid_mode; + ComboBox* gunsight; + + ActiveWindow* description; + + Button* aud_btn; + Button* vid_btn; + Button* opt_btn; + Button* ctl_btn; + Button* mod_btn; + + Button* apply; + Button* cancel; + + bool closed; +}; + +#endif OptDlg_h + diff --git a/Stars45/PlanScreen.cpp b/Stars45/PlanScreen.cpp new file mode 100644 index 0000000..77c08f4 --- /dev/null +++ b/Stars45/PlanScreen.cpp @@ -0,0 +1,395 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars + FILE: PlanScreen.cpp + AUTHOR: John DiCamillo + +*/ + +#include "MemDebug.h" +#include "PlanScreen.h" +#include "FormDef.h" +#include "MsnObjDlg.h" +#include "MsnPkgDlg.h" +#include "MsnWepDlg.h" +#include "MsnNavDlg.h" +#include "DebriefDlg.h" +#include "AwardDlg.h" +#include "Campaign.h" +#include "Mission.h" +#include "Sim.h" +#include "Starshatter.h" +#include "StarSystem.h" + +#include "Game.h" +#include "Video.h" +#include "Screen.h" +#include "ActiveWindow.h" +#include "Mouse.h" +#include "Keyboard.h" +#include "FadeView.h" +#include "Color.h" +#include "Bitmap.h" +#include "Font.h" +#include "FontMgr.h" +#include "EventDispatch.h" +#include "DataLoader.h" +#include "Resource.h" + +// +--------------------------------------------------------------------+ + +PlanScreen::PlanScreen() + : screen(0), navdlg(0), award_dlg(0), debrief_dlg(0), + objdlg(0), pkgdlg(0), wepdlg(0), isShown(false) +{ + loader = DataLoader::GetLoader(); +} + +PlanScreen::~PlanScreen() +{ + TearDown(); +} + +// +--------------------------------------------------------------------+ + +void +PlanScreen::Setup(Screen* s) +{ + if (!s) + return; + + screen = s; + + // create windows + loader->UseFileSystem(true); + + FormDef msn_obj_def("MsnObjDlg", 0); + msn_obj_def.Load("MsnObjDlg"); + objdlg = new(__FILE__,__LINE__) MsnObjDlg(screen, msn_obj_def, this); + + FormDef msn_pkg_def("MsnPkgDlg", 0); + msn_pkg_def.Load("MsnPkgDlg"); + pkgdlg = new(__FILE__,__LINE__) MsnPkgDlg(screen, msn_pkg_def, this); + + FormDef msn_nav_def("MsnNavDlg", 0); + msn_nav_def.Load("MsnNavDlg"); + navdlg = new(__FILE__,__LINE__) MsnNavDlg(screen, msn_nav_def, this); + + FormDef msn_wep_def("MsnWepDlg", 0); + msn_wep_def.Load("MsnWepDlg"); + wepdlg = new(__FILE__,__LINE__) MsnWepDlg(screen, msn_wep_def, this); + + FormDef award_def("AwardDlg", 0); + award_def.Load("AwardDlg"); + award_dlg = new(__FILE__,__LINE__) AwardDlg(screen, award_def, this); + + FormDef debrief_def("DebriefDlg", 0); + debrief_def.Load("DebriefDlg"); + debrief_dlg = new(__FILE__,__LINE__) DebriefDlg(screen, debrief_def, this); + + loader->UseFileSystem(Starshatter::UseFileSystem()); + ShowMsnDlg(); +} + +// +--------------------------------------------------------------------+ + +void +PlanScreen::TearDown() +{ + if (screen) { + screen->DelWindow(objdlg); + screen->DelWindow(pkgdlg); + screen->DelWindow(wepdlg); + screen->DelWindow(navdlg); + screen->DelWindow(debrief_dlg); + screen->DelWindow(award_dlg); + } + + delete objdlg; + delete pkgdlg; + delete wepdlg; + delete navdlg; + delete debrief_dlg; + delete award_dlg; + + objdlg = 0; + pkgdlg = 0; + wepdlg = 0; + navdlg = 0; + debrief_dlg = 0; + award_dlg = 0; + screen = 0; +} + +// +--------------------------------------------------------------------+ + +void +PlanScreen::ExecFrame() +{ + Game::SetScreenColor(Color::Black); + + Mission* mission = 0; + Campaign* campaign = Campaign::GetCampaign(); + + if (campaign) + mission = campaign->GetMission(); + + if (navdlg) { + navdlg->SetMission(mission); + + if (navdlg->IsShown()) + navdlg->ExecFrame(); + } + + if (objdlg && objdlg->IsShown()) { + objdlg->ExecFrame(); + } + + if (pkgdlg && pkgdlg->IsShown()) { + pkgdlg->ExecFrame(); + } + + if (wepdlg && wepdlg->IsShown()) { + wepdlg->ExecFrame(); + } + + if (award_dlg && award_dlg->IsShown()) { + award_dlg->ExecFrame(); + } + + if (debrief_dlg && debrief_dlg->IsShown()) { + debrief_dlg->ExecFrame(); + } +} + +// +--------------------------------------------------------------------+ + +bool +PlanScreen::CloseTopmost() +{ + if (debrief_dlg->IsShown()) { + debrief_dlg->OnClose(0); + } + + if (award_dlg->IsShown()) { + return true; + } + + return false; +} + +void +PlanScreen::Show() +{ + if (!isShown) { + ShowMsnDlg(); + isShown = true; + } +} + +void +PlanScreen::Hide() +{ + HideAll(); + isShown = false; +} + +// +--------------------------------------------------------------------+ + +void +PlanScreen::ShowMsnDlg() +{ + HideAll(); + Mouse::Show(true); + objdlg->Show(); +} + +// +--------------------------------------------------------------------+ + +void +PlanScreen::HideMsnDlg() +{ + HideAll(); + Mouse::Show(true); + objdlg->Show(); +} + +bool +PlanScreen::IsMsnShown() +{ + return IsMsnObjShown() || IsMsnPkgShown() || IsMsnWepShown(); +} + +// +--------------------------------------------------------------------+ + +void +PlanScreen::ShowMsnObjDlg() +{ + HideAll(); + Mouse::Show(true); + objdlg->Show(); +} + +// +--------------------------------------------------------------------+ + +void +PlanScreen::HideMsnObjDlg() +{ + HideAll(); + Mouse::Show(true); +} + +// +--------------------------------------------------------------------+ + +bool +PlanScreen::IsMsnObjShown() +{ + return objdlg && objdlg->IsShown(); +} + +// +--------------------------------------------------------------------+ + +void +PlanScreen::ShowMsnPkgDlg() +{ + HideAll(); + Mouse::Show(true); + pkgdlg->Show(); +} + +// +--------------------------------------------------------------------+ + +void +PlanScreen::HideMsnPkgDlg() +{ + HideAll(); + Mouse::Show(true); +} + +// +--------------------------------------------------------------------+ + +bool +PlanScreen::IsMsnPkgShown() +{ + return pkgdlg && pkgdlg->IsShown(); +} + +// +--------------------------------------------------------------------+ + +void +PlanScreen::ShowMsnWepDlg() +{ + HideAll(); + Mouse::Show(true); + wepdlg->Show(); +} + +// +--------------------------------------------------------------------+ + +void +PlanScreen::HideMsnWepDlg() +{ + HideAll(); + Mouse::Show(true); +} + +// +--------------------------------------------------------------------+ + +bool +PlanScreen::IsMsnWepShown() +{ + return wepdlg && wepdlg->IsShown(); +} + +// +--------------------------------------------------------------------+ + +void +PlanScreen::ShowNavDlg() +{ + if (navdlg && !navdlg->IsShown()) { + HideAll(); + Mouse::Show(true); + navdlg->Show(); + } +} + +// +--------------------------------------------------------------------+ + +void +PlanScreen::HideNavDlg() +{ + if (navdlg && navdlg->IsShown()) { + HideAll(); + Mouse::Show(true); + } +} + +// +--------------------------------------------------------------------+ + +bool +PlanScreen::IsNavShown() +{ + return navdlg && navdlg->IsShown(); +} + +// +--------------------------------------------------------------------+ + +void +PlanScreen::ShowDebriefDlg() +{ + HideAll(); + Mouse::Show(true); + debrief_dlg->Show(); +} + +void +PlanScreen::HideDebriefDlg() +{ + HideAll(); + Mouse::Show(true); +} + +bool +PlanScreen::IsDebriefShown() +{ + return debrief_dlg && debrief_dlg->IsShown(); +} + +// +--------------------------------------------------------------------+ + +void +PlanScreen::ShowAwardDlg() +{ + HideAll(); + Mouse::Show(true); + award_dlg->Show(); +} + +void +PlanScreen::HideAwardDlg() +{ + HideAll(); + Mouse::Show(true); +} + +bool +PlanScreen::IsAwardShown() +{ + return award_dlg && award_dlg->IsShown(); +} + + +// +--------------------------------------------------------------------+ + +void +PlanScreen::HideAll() +{ + if (objdlg) objdlg->Hide(); + if (pkgdlg) pkgdlg->Hide(); + if (wepdlg) wepdlg->Hide(); + if (navdlg) navdlg->Hide(); + if (award_dlg) award_dlg->Hide(); + if (debrief_dlg) debrief_dlg->Hide(); +} \ No newline at end of file diff --git a/Stars45/PlanScreen.h b/Stars45/PlanScreen.h new file mode 100644 index 0000000..25dcb52 --- /dev/null +++ b/Stars45/PlanScreen.h @@ -0,0 +1,108 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: PlanScreen.h + AUTHOR: John DiCamillo + +*/ + +#ifndef PlanScreen_h +#define PlanScreen_h + +#include "Types.h" +#include "Bitmap.h" +#include "Screen.h" +#include "BaseScreen.h" + +// +--------------------------------------------------------------------+ + +class MsnObjDlg; +class MsnPkgDlg; +class MsnWepDlg; +class MsnNavDlg; +class DebriefDlg; +class AwardDlg; + +class Bitmap; +class DataLoader; +class Font; +class Screen; +class Video; +class VideoFactory; + +// +--------------------------------------------------------------------+ + +class PlanScreen : public BaseScreen +{ +public: + PlanScreen(); + virtual ~PlanScreen(); + + virtual void Setup(Screen* screen); + virtual void TearDown(); + virtual bool CloseTopmost(); + + virtual bool IsShown() const { return isShown; } + virtual void Show(); + virtual void Hide(); + + virtual void ShowMsnDlg(); + virtual void HideMsnDlg(); + virtual bool IsMsnShown(); + + virtual void ShowMsnObjDlg(); + virtual void HideMsnObjDlg(); + virtual bool IsMsnObjShown(); + virtual MsnObjDlg* GetMsnObjDlg() { return objdlg; } + + virtual void ShowMsnPkgDlg(); + virtual void HideMsnPkgDlg(); + virtual bool IsMsnPkgShown(); + virtual MsnPkgDlg* GetMsnPkgDlg() { return pkgdlg; } + + virtual void ShowMsnWepDlg(); + virtual void HideMsnWepDlg(); + virtual bool IsMsnWepShown(); + virtual MsnWepDlg* GetMsnWepDlg() { return wepdlg; } + + virtual void ShowNavDlg(); + virtual void HideNavDlg(); + virtual bool IsNavShown(); + virtual NavDlg* GetNavDlg() { return (NavDlg*) navdlg; } + + virtual void ShowDebriefDlg(); + virtual void HideDebriefDlg(); + virtual bool IsDebriefShown(); + virtual DebriefDlg* GetDebriefDlg() { return debrief_dlg; } + + virtual void ShowAwardDlg(); + virtual void HideAwardDlg(); + virtual bool IsAwardShown(); + virtual AwardDlg* GetAwardDlg() { return award_dlg; } + + virtual void ExecFrame(); + virtual void HideAll(); + + +private: + Screen* screen; + + MsnObjDlg* objdlg; + MsnPkgDlg* pkgdlg; + MsnWepDlg* wepdlg; + MsnNavDlg* navdlg; + DebriefDlg* debrief_dlg; + AwardDlg* award_dlg; + + DataLoader* loader; + + int wc, hc; + bool isShown; +}; + +// +--------------------------------------------------------------------+ + +#endif PlanScreen_h + diff --git a/Stars45/Player.cpp b/Stars45/Player.cpp new file mode 100644 index 0000000..4a7c8aa --- /dev/null +++ b/Stars45/Player.cpp @@ -0,0 +1,1552 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Player.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Player / Logbook class +*/ + + +#include "MemDebug.h" +#include "Player.h" +#include "NetLobbyServer.h" +#include "NetLayer.h" +#include "Ship.h" +#include "SimEvent.h" +#include "Campaign.h" +#include "CampaignSaveGame.h" +#include "Random.h" +#include "HUDView.h" +#include "MFD.h" + +#include "DataLoader.h" +#include "Encrypt.h" +#include "ParseUtil.h" +#include "FormatUtil.h" +#include "Bitmap.h" +#include "Game.h" + +// +-------------------------------------------------------------------+ + +class AwardInfo +{ +public: + static const char* TYPENAME() { return "AwardInfo"; } + + AwardInfo() : + type(RANK), + id(0), + large_insignia(0), + small_insignia(0), + granted_ship_classes(0x7), + total_points(0), + mission_points(0), + total_missions(0), + kills(0), + lost(0), + collision(0), + campaign_id(0), + campaign_complete(false), + dynamic_campaign(false), + ceremony(true), + required_awards(0), + lottery(0), + min_rank(0), + max_rank((int) 1e9), + min_ship_class(0), + max_ship_class((int) 1e9) + { } + ~AwardInfo() { } + + enum TYPE { RANK, MEDAL }; + + int type; + int id; + Text name; + Text abrv; + Text desc; + Text grant; + Text desc_sound; + Text grant_sound; + Bitmap* large_insignia; + Bitmap* small_insignia; + int granted_ship_classes; + + int total_points; + int mission_points; + int total_missions; + int kills; + int lost; + int collision; + int campaign_id; + bool campaign_complete; + bool dynamic_campaign; + bool ceremony; + + int required_awards; + int lottery; + int min_rank; + int max_rank; + int min_ship_class; + int max_ship_class; +}; + +static List rank_table; +static List medal_table; +static bool config_exists = false; + +// +-------------------------------------------------------------------+ + +Player::Player(const char* n) + : uid(0), name(n), create_date(0), points(0), medals(0), flight_time(0), + missions(0), kills(0), losses(0), campaigns(0), trained(0), + flight_model(0), flying_start(0), landing_model(0), + ai_level(1), hud_mode(0), hud_color(1), + ff_level(4), grid(1), gunsight(0), award(0) +{ + name.setSensitive(false); + + mfd[0] = -1; + mfd[1] = -1; + mfd[2] = -1; + mfd[3] = -1; +} + +Player::Player() + : uid(0), create_date(0), points(0), medals(0), flight_time(0), + missions(0), kills(0), losses(0), campaigns(0), trained(0), + flight_model(0), flying_start(0), landing_model(0), + ai_level(1), hud_mode(0), hud_color(1), + ff_level(4), grid(1), gunsight(0), award(0) +{ + name.setSensitive(false); + + mfd[0] = -1; + mfd[1] = -1; + mfd[2] = -1; + mfd[3] = -1; +} + +Player::~Player() +{ } + +// +-------------------------------------------------------------------+ + +void +Player::SetName(const char* n) +{ + if (n && *n) + name = n; +} + +void +Player::SetPassword(const char* p) +{ + if (p && *p) { + pass = p; + + if (pass.length() > 16) + pass = pass.substring(0, 16); + } +} + +void +Player::SetSquadron(const char* s) +{ + if (s && *s) + squadron = s; +} + +void +Player::SetSignature(const char* s) +{ + if (s && *s) + signature = s; +} + +const Text& +Player::ChatMacro(int n) const +{ + if (n >= 0 && n < 10) + return chat_macros[n]; + + return chat_macros[0]; +} + +void +Player::SetChatMacro(int n, const char* m) +{ + if (n >= 0 && n < 10 && m && *m) + chat_macros[n] = m; +} + +void +Player::SetPoints(int p) +{ + if (p >= 0) + points = p; +} + +void +Player::SetMedals(int m) +{ + medals = m; +} + +void +Player::SetCampaigns(int n) +{ + campaigns = n; +} + +void +Player::SetTrained(int n) +{ + if (n == 0) + trained = 0; + + else if (n > 0 && n <= 20) + trained = trained | (1 << (n-1)); + + else if (n > 20) + trained = n; +} + +bool +Player::HasTrained(int n) const +{ + if (n > 0 && n <= 20) + return (trained & (1 << (n-1))) ? true : false; + + return false; +} + +bool +Player::HasCompletedCampaign(int id) const +{ + if (id > 0 && id < 30) + return (campaigns & (1 << id)) ? true : false; + + return false; +} + +void +Player::SetCampaignComplete(int id) +{ + if (id > 0 && id < 30) { + campaigns = campaigns | (1 << id); + Save(); + } +} + +void +Player::SetCreateDate(int d) +{ + if (d >= 0) + create_date = d; +} + +void +Player::SetFlightTime(int t) +{ + if (t >= 0) + flight_time = t; +} + +void +Player::SetMissions(int m) +{ + if (m >= 0) + missions = m; +} + +void +Player::SetKills(int k) +{ + if (k >= 0) + kills = k; +} + +void +Player::SetLosses(int l) +{ + if (l >= 0) + losses = l; +} + +// +-------------------------------------------------------------------+ + +void +Player::AddFlightTime(int t) +{ + if (t > 0) + flight_time += t; +} + +void +Player::AddPoints(int p) +{ + if (p > 0) + points += p; +} + +void +Player::AddMissions(int m) +{ + if (m > 0) + missions += m; +} + +void +Player::AddKills(int k) +{ + if (k > 0) + kills += k; +} + +void +Player::AddLosses(int l) +{ + if (l > 0) + losses += l; +} + +// +-------------------------------------------------------------------+ + +void +Player::SetFlightModel(int n) +{ + if (n >= Ship::FM_STANDARD && n <= Ship::FM_ARCADE) { + flight_model = n; + Ship::SetFlightModel(n); + } +} + +void +Player::SetFlyingStart(int n) +{ + flying_start = n; +} + +void +Player::SetLandingModel(int n) +{ + if (n >= Ship::LM_STANDARD && n <= Ship::LM_EASIER) { + landing_model = n; + Ship::SetLandingModel(landing_model); + } +} + +void +Player::SetAILevel(int n) +{ + ai_level = n; +} + +void +Player::SetHUDMode(int n) +{ + hud_mode = n; + HUDView::SetArcade(n > 0); +} + +void +Player::SetHUDColor(int n) +{ + hud_color = n; + HUDView::SetDefaultColorSet(n); +} + +void +Player::SetFriendlyFire(int n) +{ + if (n >= 0 && n <= 4) { + ff_level = n; + Ship::SetFriendlyFireLevel(n/4.0); + } +} + +void +Player::SetGridMode(int n) +{ + if (n >= 0 && n <= 1) { + grid = n; + } +} + +void +Player::SetGunsight(int n) +{ + if (n >= 0 && n <= 1) { + gunsight = n; + } +} + +void +Player::ClearShowAward() +{ + award = 0; +} + +Text +Player::AwardName() const +{ + if (award) + return award->name; + + return Text(); +} + +Text +Player::AwardDesc() const +{ + if (award) + return award->grant; + + return Text(); +} + +Bitmap* +Player::AwardImage() const +{ + if (award) + return award->large_insignia; + + return 0; +} + +Sound* +Player::AwardSound() const +{ + if (award && award->grant_sound.length()) { + DataLoader* loader = DataLoader::GetLoader(); + Sound* result = 0; + + loader->LoadSound(award->grant_sound, result); + return result; + } + + return 0; +} + +// +-------------------------------------------------------------------+ + +const char* +Player::RankName(int rank) +{ + ListIter iter = rank_table; + while (++iter) { + AwardInfo* award = iter.value(); + if (award->id == rank) + return award->name; + } + + return "Conscript"; +} + +const char* +Player::RankAbrv(int rank) +{ + ListIter iter = rank_table; + while (++iter) { + AwardInfo* award = iter.value(); + if (award->id == rank) + return award->abrv; + } + + return ""; +} + +Bitmap* +Player::RankInsignia(int rank, int size) +{ + ListIter iter = rank_table; + while (++iter) { + AwardInfo* award = iter.value(); + if (award->id == rank) { + if (size == 0) + return award->small_insignia; + + if (size == 1) + return award->large_insignia; + } + } + + return 0; +} + +const char* +Player::RankDescription(int rank) +{ + ListIter iter = rank_table; + while (++iter) { + AwardInfo* award = iter.value(); + if (award->id == rank) + return award->desc; + } + + return ""; +} + +int +Player::RankFromName(const char* name) +{ + ListIter iter = rank_table; + while (++iter) { + AwardInfo* award = iter.value(); + if (award->name == name) + return award->id; + } + + return 0; +} + +int +Player::Rank() const +{ + for (int i = rank_table.size()-1; i >= 0; i--) { + AwardInfo* award = rank_table[i]; + if (points >= award->total_points) + return award->id; + } + + return 0; +} + +void +Player::SetRank(int r) +{ + ListIter iter = rank_table; + while (++iter) { + AwardInfo* award = iter.value(); + if (r == award->id) + points = award->total_points; + } +} + +int +Player::Medal(int n) const +{ + if (n < 0) + return 0; + + for (int i = 0; i < 16; i++) { + int selector = 1 << (15-i); + + // found a medal: + if (medals & selector) { + + // and it's the nth medal! + if (n == 0) { + return selector; + } + + n--; + } + } + + return 0; +} + +// +-------------------------------------------------------------------+ + +const char* +Player::MedalName(int medal) +{ + ListIter iter = medal_table; + while (++iter) { + AwardInfo* award = iter.value(); + if (award->id == medal) + return award->name; + } + + return ""; +} + +Bitmap* +Player::MedalInsignia(int medal, int size) +{ + ListIter iter = medal_table; + while (++iter) { + AwardInfo* award = iter.value(); + if (award->id == medal) { + if (size == 0) + return award->small_insignia; + + if (size == 1) + return award->large_insignia; + } + } + + return 0; +} + +const char* +Player::MedalDescription(int medal) +{ + ListIter iter = medal_table; + while (++iter) { + AwardInfo* award = iter.value(); + if (award->id == medal) + return award->desc; + } + + return ""; +} + +// +-------------------------------------------------------------------+ + +bool +Player::CanCommand(int ship_class) +{ + if (ship_class <= Ship::ATTACK) + return true; + + for (int i = rank_table.size()-1; i >= 0; i--) { + AwardInfo* award = rank_table[i]; + if (points > award->total_points) { + return (ship_class & award->granted_ship_classes) != 0; + } + } + + return false; +} + +int +Player::CommandRankRequired(int ship_class) +{ + for (int i = 0; i < rank_table.size(); i++) { + AwardInfo* award = rank_table[i]; + if ((ship_class & award->granted_ship_classes) != 0) { + return i; + } + } + + return rank_table.size()-1; +} + +// +-------------------------------------------------------------------+ + +int +Player::GetMissionPoints(ShipStats* s, DWORD start_time) +{ + int result = 0; + + if (s) { + result = s->GetPoints(); + + int flight_time = (Game::GameTime() - start_time) / 1000; + + // if player survived mission, award one experience point + // for each minute of action, in ten point blocks: + if (!s->GetDeaths() && !s->GetColls()) { + int minutes = flight_time / 60; + minutes /= 10; + minutes *= 10; + result += minutes; + + if (s->HasEvent(SimEvent::DOCK)) + result += 100; + } + else { + result -= (int) (2.5 * Ship::Value(s->GetShipClass())); + } + + if (result < 0) + result = 0; + } + + return result; +} + +// +-------------------------------------------------------------------+ + +void +Player::ProcessStats(ShipStats* s, DWORD start_time) +{ + if (!s) return; + + int old_rank = Rank(); + int pts = GetMissionPoints(s, start_time); + + AddPoints(pts); + AddPoints(s->GetCommandPoints()); + AddKills(s->GetGunKills()); + AddKills(s->GetMissileKills()); + AddLosses(s->GetDeaths()); + AddLosses(s->GetColls()); + AddMissions(1); + AddFlightTime((Game::GameTime() - start_time) / 1000); + + int rank = Rank(); + + // did the player earn a promotion? + if (old_rank != rank) { + ListIter iter = rank_table; + while (++iter) { + AwardInfo* a = iter.value(); + if (rank == a->id) { + award = a; + } + } + } + + // if not, did the player earn a medal? + else { + ListIter iter = medal_table; + while (++iter) { + AwardInfo* a = iter.value(); + + if (EarnedAward(a, s) && a->ceremony) { + award = a; + break; + } + } + } + + // persist all stats, promotions, and medals: + Save(); +} + +bool +Player::EarnedAward(AwardInfo* a, ShipStats* s) +{ + if (!a || !s) + return false; + + // already earned this medal? + if (a->id & medals) + return false; + + // eligible for this medal? + int rank = Rank(); + if (a->min_rank > rank || a->max_rank < rank) + return false; + + if ((a->required_awards & medals) < a->required_awards) + return false; + + if (a->min_ship_class > s->GetShipClass() || a->max_ship_class < s->GetShipClass()) + return false; + + if (a->total_points > points) + return false; + + if (a->total_missions > missions) + return false; + + if (a->campaign_id && a->campaign_complete) { + if (!HasCompletedCampaign(a->campaign_id)) + return false; + } + + else { + // campaign related requirements + Campaign* c = Campaign::GetCampaign(); + + if (c) { + if (a->dynamic_campaign && !c->IsDynamic()) + return false; + } + } + + // sufficient merit for this medal? + if (a->mission_points > s->GetPoints()) + return false; + + if (a->kills > s->GetGunKills() + s->GetMissileKills()) + return false; + + if (a->mission_points > s->GetPoints()) + return false; + + // player must survive mission if lost = -1 + if (a->lost < 0 && (s->GetDeaths() || s->GetColls())) + return false; + + // do we need to be wounded in battle? + if (a->lost > s->GetDeaths() || a->collision > s->GetColls()) + return false; + + // final lottery check: + if (a->lottery < 2 || RandomChance(1, a->lottery)) { + medals |= a->id; + return true; + } + + // what do we have for the losers, judge? + return false; +} + +// +-------------------------------------------------------------------+ + +static List player_roster; +static Player* current_player = 0; + +List& +Player::GetRoster() +{ + return player_roster; +} + +Player* +Player::GetCurrentPlayer() +{ + return current_player; +} + +void +Player::SelectPlayer(Player* p) +{ + HUDView* hud = HUDView::GetInstance(); + + if (current_player && current_player != p) { + if (hud) { + for (int i = 0; i < 3; i++) { + MFD* mfd = hud->GetMFD(i); + + if (mfd) + current_player->mfd[i] = mfd->GetMode(); + } + } + } + + if (player_roster.contains(p)) { + current_player = p; + + Ship::SetFlightModel(p->flight_model); + Ship::SetLandingModel(p->landing_model); + HUDView::SetArcade(p->hud_mode > 0); + HUDView::SetDefaultColorSet(p->hud_color); + + if (hud) { + for (int i = 0; i < 3; i++) { + if (p->mfd[i] >= 0) { + MFD* mfd = hud->GetMFD(i); + + if (mfd) + mfd->SetMode(p->mfd[i]); + } + } + } + } +} + +Player* +Player::Find(const char* name) +{ + for (int i = 0; i < player_roster.size(); i++) { + Player* p = player_roster.at(i); + if (p->Name() == name) + return p; + } + + return 0; +} + +Player* +Player::Create(const char* name) +{ + if (name && *name) { + // check for existence: + if (Find(name)) + return 0; + + Player* newbie = new(__FILE__,__LINE__) Player(name); + newbie->SetCreateDate(NetLayer::GetUTC()); + + player_roster.append(newbie); + newbie->CreateUniqueID(); + return newbie; + } + + return 0; +} + +void +Player::Destroy(Player* p) +{ + if (p) { + player_roster.remove(p); + + if (p == current_player) { + current_player = 0; + + if (player_roster.size()) + current_player = player_roster.at(0); + } + + CampaignSaveGame::RemovePlayer(p); + delete p; + } +} + +// +-------------------------------------------------------------------+ + +void +Player::Initialize() +{ + LoadAwardTables(); + Load(); + + if (!current_player) { + if (!player_roster.size()) { + Create("Pilot"); + } + + SelectPlayer(player_roster.at(0)); + } +} + +void +Player::Close() +{ + if (current_player && !player_roster.contains(current_player)) + delete current_player; + + player_roster.destroy(); + current_player = 0; + + rank_table.destroy(); + medal_table.destroy(); +} + +// +-------------------------------------------------------------------+ + +bool +Player::ConfigExists() +{ + return config_exists; +} + +// +-------------------------------------------------------------------+ + +#define GET_DEF_BOOL(x) if(pdef->name()->value()==(#x))GetDefBool(player->x,pdef,filename) +#define GET_DEF_TEXT(x) if(pdef->name()->value()==(#x))GetDefText(player->x,pdef,filename) +#define GET_DEF_NUM(x) if(pdef->name()->value()==(#x))GetDefNumber(player->x,pdef,filename) + +void +Player::Load() +{ + config_exists = false; + + // read the config file: + BYTE* block = 0; + int blocklen = 0; + + char filename[64]; + strcpy(filename, "player.cfg"); + + FILE* f = ::fopen(filename, "rb"); + + if (f) { + config_exists = true; + + ::fseek(f, 0, SEEK_END); + blocklen = ftell(f); + ::fseek(f, 0, SEEK_SET); + + block = new(__FILE__,__LINE__) BYTE[blocklen+1]; + block[blocklen] = 0; + + ::fread(block, blocklen, 1, f); + ::fclose(f); + } + + if (blocklen == 0) + return; + + Parser parser(new(__FILE__,__LINE__) BlockReader((const char*) block, blocklen)); + Term* term = parser.ParseTerm(); + + if (!term) { + Print("ERROR: could not parse '%s'.\n", filename); + return; + } + else { + TermText* file_type = term->isText(); + if (!file_type || file_type->value() != "PLAYER_CONFIG") { + Print("WARNING: invalid '%s' file. Using defaults\n", filename); + return; + } + } + + if (current_player && !player_roster.contains(current_player)) + delete current_player; + player_roster.destroy(); + current_player = 0; + + do { + delete term; + + term = parser.ParseTerm(); + + if (term) { + TermDef* def = term->isDef(); + if (def) { + if (def->name()->value() == "player") { + + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: player structure missing in '%s'\n", filename); + } + else { + Player* player = new(__FILE__,__LINE__) Player; + bool current = false; + TermStruct* val = def->term()->isStruct(); + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + GET_DEF_TEXT(name); + else GET_DEF_TEXT(squadron); + else GET_DEF_TEXT(signature); + + else GET_DEF_NUM(uid); + else GET_DEF_NUM(flight_model); + else GET_DEF_NUM(flying_start); + else GET_DEF_NUM(landing_model); + else GET_DEF_NUM(ai_level); + else GET_DEF_NUM(hud_mode); + else GET_DEF_NUM(hud_color); + else GET_DEF_NUM(ff_level); + else GET_DEF_NUM(grid); + else GET_DEF_NUM(gunsight); + + else if (pdef->name()->value() == ("chat_0")) + GetDefText(player->chat_macros[0], pdef, filename); + + else if (pdef->name()->value() == ("chat_1")) + GetDefText(player->chat_macros[1], pdef, filename); + + else if (pdef->name()->value() == ("chat_2")) + GetDefText(player->chat_macros[2], pdef, filename); + + else if (pdef->name()->value() == ("chat_3")) + GetDefText(player->chat_macros[3], pdef, filename); + + else if (pdef->name()->value() == ("chat_4")) + GetDefText(player->chat_macros[4], pdef, filename); + + else if (pdef->name()->value() == ("chat_5")) + GetDefText(player->chat_macros[5], pdef, filename); + + else if (pdef->name()->value() == ("chat_6")) + GetDefText(player->chat_macros[6], pdef, filename); + + else if (pdef->name()->value() == ("chat_7")) + GetDefText(player->chat_macros[7], pdef, filename); + + else if (pdef->name()->value() == ("chat_8")) + GetDefText(player->chat_macros[8], pdef, filename); + + else if (pdef->name()->value() == ("chat_9")) + GetDefText(player->chat_macros[9], pdef, filename); + + else if (pdef->name()->value() == ("mfd0")) + GetDefNumber(player->mfd[0], pdef, filename); + + else if (pdef->name()->value() == ("mfd1")) + GetDefNumber(player->mfd[1], pdef, filename); + + else if (pdef->name()->value() == ("mfd2")) + GetDefNumber(player->mfd[2], pdef, filename); + + else if (pdef->name()->value() == ("current")) + GetDefBool(current, pdef, filename); + + else if (pdef->name()->value() == ("trained")) + GetDefNumber(player->trained, pdef, filename); + + else if (pdef->name()->value() == ("stats")) { + Text stats; + GetDefText(stats, pdef, filename); + player->DecodeStats(stats); + } + + else if (pdef->name()->value().indexOf("XXX_CHEAT_A1B2C3_") == 0) { + if (pdef->name()->value().contains("points")) + GetDefNumber(player->points, pdef, filename); + + else if (pdef->name()->value().contains("rank")) { + int rank=0; + GetDefNumber(rank, pdef, filename); + player->SetRank(rank); + } + + else if (pdef->name()->value().contains("medals")) + GetDefNumber(player->medals, pdef, filename); + + else if (pdef->name()->value().contains("campaigns")) + GetDefNumber(player->campaigns, pdef, filename); + + else if (pdef->name()->value().contains("missions")) + GetDefNumber(player->missions, pdef, filename); + + else if (pdef->name()->value().contains("kills")) + GetDefNumber(player->kills, pdef, filename); + + else if (pdef->name()->value().contains("losses")) + GetDefNumber(player->losses, pdef, filename); + + else if (pdef->name()->value().contains("flight_time")) + GetDefNumber(player->flight_time, pdef, filename); + } + } + } + + player_roster.append(player); + player->CreateUniqueID(); + + if (current) + SelectPlayer(player); + } + + } + else { + Print("WARNING: unknown label '%s' in '%s'\n", + def->name()->value().data(), filename); + } + } + else { + Print("WARNING: term ignored in '%s'\n", filename); + term->print(); + } + } + } + while (term); + + delete [] block; +} + +// +-------------------------------------------------------------------+ + +void +Player::Save() +{ + HUDView* hud = HUDView::GetInstance(); + if (hud && current_player) { + for (int i = 0; i < 3; i++) { + MFD* mfd = hud->GetMFD(i); + + if (mfd) + current_player->mfd[i] = mfd->GetMode(); + } + } + + FILE* f = fopen("player.cfg", "w"); + if (f) { + fprintf(f, "PLAYER_CONFIG\n\n"); + + ListIter iter = player_roster; + while (++iter) { + Player* p = iter.value(); + + fprintf(f, "player: {\n"); + fprintf(f, " uid: %d,\n", p->uid); + fprintf(f, " name: \"%s\",\n", SafeQuotes(p->name.data())); + fprintf(f, " squadron: \"%s\",\n", SafeQuotes(p->squadron.data())); + fprintf(f, " signature: \"%s\",\n", SafeQuotes(p->signature.data())); + + Text stat_data = p->EncodeStats(); + + if (stat_data.length() > 32) { + char tmp[64]; + int len = stat_data.length(); + + for (int n = 0; n < len; n += 32) { + ZeroMemory(tmp, sizeof(tmp)); + const char* p = stat_data.data() + n; + strncpy(tmp, p, 32); + + if (n == 0) + fprintf(f, " stats: \"%s\"\n", tmp); + else if (n < len-32) + fprintf(f, " \"%s\"\n", tmp); + else + fprintf(f, " \"%s\",\n", tmp); + } + } + + if (p == current_player) + fprintf(f, " current: true,\n"); + else + fprintf(f, " current: false,\n"); + + fprintf(f, " trained: %d,\n", p->trained); + fprintf(f, " flight_model: %d,\n", p->flight_model); + fprintf(f, " flying_start: %d,\n", p->flying_start); + fprintf(f, " landing_model: %d,\n", p->landing_model); + fprintf(f, " ai_level: %d,\n", p->ai_level); + fprintf(f, " hud_mode: %d,\n", p->hud_mode); + fprintf(f, " hud_color: %d,\n", p->hud_color); + fprintf(f, " ff_level: %d,\n", p->ff_level); + fprintf(f, " grid: %d,\n", p->grid); + fprintf(f, " gunsight: %d,\n", p->gunsight); + + for (int i = 0; i < 10; i++) { + fprintf(f, " chat_%d: \"%s\",\n", i, SafeQuotes(p->chat_macros[i].data())); + } + + for (i = 0; i < 3; i++) { + if (p->mfd[i] >= 0) { + fprintf(f, " mfd%d: %d,\n", i, p->mfd[i]); + } + } + + fprintf(f, "}\n\n"); + } + + fclose(f); + + config_exists = true; + } +} + +// +-------------------------------------------------------------------+ + +static char stat_buf[280]; +static char code_buf[280]; + +Text +Player::EncodeStats() +{ + ZeroMemory(stat_buf, 280); + ZeroMemory(code_buf, 280); + + sprintf(stat_buf, "%-16s%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x", + pass.data(), + create_date, + points, + flight_time, + missions, + kills, + losses, + medals, + campaigns, + 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32); + + for (int i = 0; i < 16; i++) + for (int j = 0; j < 16; j++) + code_buf[i*16 + j] = stat_buf[j*16 + i]; + + return Encryption::Encode(Encryption::Encrypt(code_buf)); +} + +void +Player::DecodeStats(const char* stats) +{ + ZeroMemory(stat_buf, 280); + ZeroMemory(code_buf, 280); + + if (!stats || !*stats) { + Print("Player::DecodeStats() invalid or missing stats\n"); + create_date = NetLayer::GetUTC(); + return; + } + + Text plain = Encryption::Decrypt(Encryption::Decode(stats)); + + if (plain.length() == 64) { + for (int i = 0; i < 8; i++) + for (int j = 0; j < 8; j++) + stat_buf[j*8 + i] = plain[i*8 +j]; + } + + else if (plain.length() == 256) { + for (int i = 0; i < 16; i++) + for (int j = 0; j < 16; j++) + stat_buf[j*16 + i] = plain[i*16 +j]; + } + + else { + Print("Player::DecodeStats() invalid plain text length %d\n", plain.length()); + create_date = NetLayer::GetUTC(); + return; + } + + char work[32]; + ZeroMemory(work, 32); + CopyMemory(work, stat_buf, 16); + for (int i = 15; i > 0; i--) + if (work[i] == ' ') work[i] = 0; + else break; + pass = work; + + ZeroMemory(work, 16); + CopyMemory(work, stat_buf+16, 8); + sscanf(work, "%x", &create_date); + + ZeroMemory(work, 16); + CopyMemory(work, stat_buf+24, 8); + sscanf(work, "%x", &points); + if (points < 0) points = 0; + + ZeroMemory(work, 16); + CopyMemory(work, stat_buf+32, 8); + sscanf(work, "%x", &flight_time); + if (flight_time < 0) flight_time = 0; + + ZeroMemory(work, 16); + CopyMemory(work, stat_buf+40, 8); + sscanf(work, "%x", &missions); + if (missions < 0) missions = 0; + + ZeroMemory(work, 16); + CopyMemory(work, stat_buf+48, 8); + sscanf(work, "%x", &kills); + if (kills < 0) kills = 0; + + ZeroMemory(work, 16); + CopyMemory(work, stat_buf+56, 8); + sscanf(work, "%x", &losses); + if (losses < 0) losses = 0; + + if (plain.length() > 64) { + ZeroMemory(work, 16); + CopyMemory(work, stat_buf+64, 8); + sscanf(work, "%x", &medals); + + ZeroMemory(work, 16); + CopyMemory(work, stat_buf+72, 8); + sscanf(work, "%x", &campaigns); + } + + if (create_date == 0) { + ::Print("WARNING - loaded player with zero stats '%s'\n", name.data()); + create_date = NetLayer::GetUTC(); + } +} + +// +-------------------------------------------------------------------+ + +void +Player::LoadAwardTables() +{ + DataLoader* loader = DataLoader::GetLoader(); + + if (!loader) return; + + BYTE* block = 0; + const char* filename = "awards.def"; + + loader->SetDataPath("Awards/"); + loader->LoadBuffer(filename, block, true); + Parser parser(new(__FILE__,__LINE__) BlockReader((const char*) block)); + + Term* term = parser.ParseTerm(); + + if (!term) { + return; + } + else { + TermText* file_type = term->isText(); + if (!file_type || file_type->value() != "AWARDS") { + return; + } + } + + rank_table.destroy(); + medal_table.destroy(); + + ::Print("Loading Ranks and Medals\n"); + + do { + delete term; term = 0; + term = parser.ParseTerm(); + + if (term) { + TermDef* def = term->isDef(); + if (def) { + if (def->name()->value() == "award") { + + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: award structure missing in '%s'\n", filename); + } + else { + AwardInfo* award = new(__FILE__,__LINE__) AwardInfo; + TermStruct* val = def->term()->isStruct(); + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + if (pdef->name()->value() == ("name")) { + GetDefText(award->name, pdef, filename); + award->name = Game::GetText(award->name); + } + + else if (pdef->name()->value() == ("abrv")) { + GetDefText(award->abrv, pdef, filename); + award->abrv = Game::GetText(award->abrv); + } + + else if (pdef->name()->value() == ("desc")) { + GetDefText(award->desc, pdef, filename); + if (award->desc.length() <= 40) + award->desc = Game::GetText(award->desc); + } + + else if (pdef->name()->value() == ("award")) { + GetDefText(award->grant, pdef, filename); + if (award->grant.length() <= 40) + award->grant = Game::GetText(award->grant); + } + + else if (pdef->name()->value() == ("desc_sound")) + GetDefText(award->desc_sound, pdef, filename); + + else if (pdef->name()->value() == ("award_sound")) + GetDefText(award->grant_sound, pdef, filename); + + else if (pdef->name()->value().indexOf("large") == 0) { + Text txt; + GetDefText(txt, pdef, filename); + txt.setSensitive(false); + + if (!txt.contains(".pcx")) + txt.append(".pcx"); + + loader->CacheBitmap(txt, award->large_insignia); + } + + else if (pdef->name()->value().indexOf("small") == 0) { + Text txt; + GetDefText(txt, pdef, filename); + txt.setSensitive(false); + + if (!txt.contains(".pcx")) + txt.append(".pcx"); + + loader->CacheBitmap(txt, award->small_insignia); + + if (award->small_insignia) + award->small_insignia->AutoMask(); + } + + else if (pdef->name()->value() == ("type")) { + Text txt; + GetDefText(txt, pdef, filename); + txt.setSensitive(false); + + if (txt == "rank") + award->type = AwardInfo::RANK; + + else if (txt == "medal") + award->type = AwardInfo::MEDAL; + } + + else if (pdef->name()->value() == ("id")) + GetDefNumber(award->id, pdef, filename); + + else if (pdef->name()->value() == ("total_points")) + GetDefNumber(award->total_points, pdef, filename); + + else if (pdef->name()->value() == ("mission_points")) + GetDefNumber(award->mission_points, pdef, filename); + + else if (pdef->name()->value() == ("total_missions")) + GetDefNumber(award->total_missions, pdef, filename); + + else if (pdef->name()->value() == ("kills")) + GetDefNumber(award->kills, pdef, filename); + + else if (pdef->name()->value() == ("lost")) + GetDefNumber(award->lost, pdef, filename); + + else if (pdef->name()->value() == ("collision")) + GetDefNumber(award->collision, pdef, filename); + + else if (pdef->name()->value() == ("campaign_id")) + GetDefNumber(award->campaign_id, pdef, filename); + + else if (pdef->name()->value() == ("campaign_complete")) + GetDefBool(award->campaign_complete, pdef, filename); + + else if (pdef->name()->value() == ("dynamic_campaign")) + GetDefBool(award->dynamic_campaign, pdef, filename); + + else if (pdef->name()->value() == ("ceremony")) + GetDefBool(award->ceremony, pdef, filename); + + else if (pdef->name()->value() == ("required_awards")) + GetDefNumber(award->required_awards, pdef, filename); + + else if (pdef->name()->value() == ("lottery")) + GetDefNumber(award->lottery, pdef, filename); + + else if (pdef->name()->value() == ("min_rank")) + GetDefNumber(award->min_rank, pdef, filename); + + else if (pdef->name()->value() == ("max_rank")) + GetDefNumber(award->max_rank, pdef, filename); + + else if (pdef->name()->value() == ("min_ship_class")) { + Text classname; + GetDefText(classname, pdef, filename); + award->min_ship_class = Ship::ClassForName(classname); + } + + else if (pdef->name()->value() == ("max_ship_class")) { + Text classname; + GetDefText(classname, pdef, filename); + award->max_ship_class = Ship::ClassForName(classname); + } + + else if (pdef->name()->value().indexOf("grant") == 0) + GetDefNumber(award->granted_ship_classes, pdef, filename); + } + } + + if (award->type == AwardInfo::RANK) { + rank_table.append(award); + } + + else if (award->type == AwardInfo::MEDAL) { + medal_table.append(award); + } + + else { + delete award; + } + } + } + else { + Print("WARNING: unknown label '%s' in '%s'\n", + def->name()->value().data(), filename); + } + } + else { + Print("WARNING: term ignored in '%s'\n", filename); + term->print(); + } + } + } + while (term); + + loader->ReleaseBuffer(block); + loader->SetDataPath(0); +} + +// +-------------------------------------------------------------------+ + +void +Player::CreateUniqueID() +{ + ListIter iter = player_roster; + while (++iter) { + Player* p = iter.value(); + + if (p != this && p->uid >= uid) + uid = p->uid + 1; + } + + if (uid < 1) + uid = 1; +} diff --git a/Stars45/Player.h b/Stars45/Player.h new file mode 100644 index 0000000..39dbfcd --- /dev/null +++ b/Stars45/Player.h @@ -0,0 +1,187 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Player.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Player / Logbook class +*/ + + +#ifndef Player_h +#define Player_h + +#include "Types.h" +#include "List.h" +#include "Text.h" + +// +-------------------------------------------------------------------+ + +class Player; +class Bitmap; +class ShipStats; +class AwardInfo; +class Sound; + +// +-------------------------------------------------------------------+ + +class Player +{ +public: + static const char* TYPENAME() { return "Player"; } + + Player(const char* name); + virtual ~Player(); + + int operator == (const Player& u) const { return name == u.name; } + + int Identity() const { return uid; } + const Text& Name() const { return name; } + const Text& Password() const { return pass; } + const Text& Squadron() const { return squadron; } + const Text& Signature() const { return signature; } + const Text& ChatMacro(int n) const; + int CreateDate() const { return create_date; } + int Rank() const; + int Medal(int n) const; + int Points() const { return points; } + int Medals() const { return medals; } + int FlightTime() const { return flight_time; } + int Missions() const { return missions; } + int Kills() const { return kills; } + int Losses() const { return losses; } + int Campaigns() const { return campaigns; } + int Trained() const { return trained; } + + int FlightModel() const { return flight_model; } + int FlyingStart() const { return flying_start; } + int LandingModel() const { return landing_model; } + int AILevel() const { return ai_level; } + int HUDMode() const { return hud_mode; } + int HUDColor() const { return hud_color; } + int FriendlyFire() const { return ff_level; } + int GridMode() const { return grid; } + int Gunsight() const { return gunsight; } + + bool ShowAward() const { return award != 0; } + Text AwardName() const; + Text AwardDesc() const; + Bitmap* AwardImage() const; + Sound* AwardSound() const; + + bool CanCommand(int ship_class); + + void SetName(const char* n); + void SetPassword(const char* p); + void SetSquadron(const char* s); + void SetSignature(const char* s); + void SetChatMacro(int n, const char* m); + void SetCreateDate(int d); + void SetRank(int r); + void SetPoints(int p); + void SetMedals(int m); + void SetCampaigns(int n); + void SetTrained(int n); + void SetFlightTime(int t); + void SetMissions(int m); + void SetKills(int k); + void SetLosses(int l); + + void AddFlightTime(int t); + void AddPoints(int p); + void AddMedal(int m); + void AddMissions(int m); + void AddKills(int k); + void AddLosses(int l); + + bool HasTrained(int n) const; + bool HasCompletedCampaign(int id) const; + void SetCampaignComplete(int id); + + void SetFlightModel(int n); + void SetFlyingStart(int n); + void SetLandingModel(int n); + void SetAILevel(int n); + void SetHUDMode(int n); + void SetHUDColor(int n); + void SetFriendlyFire(int n); + void SetGridMode(int n); + void SetGunsight(int n); + + void ClearShowAward(); + + Text EncodeStats(); + void DecodeStats(const char* stats); + + int GetMissionPoints(ShipStats* stats, DWORD start_time); + void ProcessStats(ShipStats* stats, DWORD start_time); + bool EarnedAward(AwardInfo* a, ShipStats* s); + + static const char* RankName(int rank); + static const char* RankAbrv(int rank); + static int RankFromName(const char* name); + static Bitmap* RankInsignia(int rank, int size); + static const char* RankDescription(int rank); + static const char* MedalName(int medal); + static Bitmap* MedalInsignia(int medal, int size); + static const char* MedalDescription(int medal); + static int CommandRankRequired(int ship_class); + + static List& GetRoster(); + static Player* GetCurrentPlayer(); + static void SelectPlayer(Player* p); + static Player* Create(const char* name); + static void Destroy(Player* p); + static Player* Find(const char* name); + static void Initialize(); + static void Close(); + static void Load(); + static void Save(); + static bool ConfigExists(); + static void LoadAwardTables(); + +protected: + Player(); + + void CreateUniqueID(); + + int uid; + Text name; + Text pass; + Text squadron; + Text signature; + Text chat_macros[10]; + int mfd[4]; + + // stats: + int create_date; + int points; + int medals; // bitmap of earned medals + int flight_time; + int missions; + int kills; + int losses; + int campaigns; // bitmap of completed campaigns + int trained; // id of highest training mission completed + + // gameplay options: + int flight_model; + int flying_start; + int landing_model; + int ai_level; + int hud_mode; + int hud_color; + int ff_level; + int grid; + int gunsight; + + // transient: + AwardInfo* award; +}; + +#endif Player_h \ No newline at end of file diff --git a/Stars45/PlayerDlg.cpp b/Stars45/PlayerDlg.cpp new file mode 100644 index 0000000..d0eb8d9 --- /dev/null +++ b/Stars45/PlayerDlg.cpp @@ -0,0 +1,389 @@ +/* Project Starshatter 5.0 + Destroyer Studios LLC + Copyright © 1997-2007. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: PlayerDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Main Menu Dialog Active Window class +*/ + +#include "MemDebug.h" +#include "PlayerDlg.h" +#include "AwardShowDlg.h" +#include "ConfirmDlg.h" +#include "MenuScreen.h" +#include "Player.h" + +#include "FormatUtil.h" + +#include "Game.h" +#include "DataLoader.h" +#include "Button.h" +#include "ListBox.h" +#include "EditBox.h" +#include "ImageBox.h" +#include "Slider.h" +#include "Video.h" +#include "Keyboard.h" +#include "MachineInfo.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +DEF_MAP_CLIENT(PlayerDlg, OnApply); +DEF_MAP_CLIENT(PlayerDlg, OnCancel); +DEF_MAP_CLIENT(PlayerDlg, OnSelectPlayer); +DEF_MAP_CLIENT(PlayerDlg, OnAdd); +DEF_MAP_CLIENT(PlayerDlg, OnDel); +DEF_MAP_CLIENT(PlayerDlg, OnDelConfirm); +DEF_MAP_CLIENT(PlayerDlg, OnRank); +DEF_MAP_CLIENT(PlayerDlg, OnMedal); + +// +--------------------------------------------------------------------+ + +PlayerDlg::PlayerDlg(Screen* s, FormDef& def, MenuScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), manager(mgr), + lst_roster(0), btn_add(0), btn_del(0), + txt_name(0), txt_password(0), txt_squadron(0), txt_signature(0), + img_rank(0) +{ + Init(def); +} + +PlayerDlg::~PlayerDlg() +{ +} + +// +--------------------------------------------------------------------+ + +void +PlayerDlg::RegisterControls() +{ + lst_roster = (ListBox*) FindControl(200); + btn_add = (Button*) FindControl(101); + btn_del = (Button*) FindControl(102); + txt_name = (EditBox*) FindControl(201); + txt_password = (EditBox*) FindControl(202); + txt_squadron = (EditBox*) FindControl(203); + txt_signature = (EditBox*) FindControl(204); + + lbl_createdate = FindControl(205); + lbl_rank = FindControl(206); + lbl_flighttime = FindControl(207); + lbl_missions = FindControl(208); + lbl_kills = FindControl(209); + lbl_losses = FindControl(210); + lbl_points = FindControl(211); + + img_rank = (ImageBox*) FindControl(220); + REGISTER_CLIENT(EID_CLICK, img_rank, PlayerDlg, OnRank); + + for (int i = 0; i < 15; i++) { + medals[i] = -1; + img_medals[i] = (ImageBox*) FindControl(230 + i); + if (img_medals[i]) + REGISTER_CLIENT(EID_CLICK, img_medals[i], PlayerDlg, OnMedal); + } + + for (i = 0; i < 10; i++) { + txt_chat[i] = (EditBox*) FindControl(300 + i); + } + + REGISTER_CLIENT(EID_SELECT, lst_roster, PlayerDlg, OnSelectPlayer); + REGISTER_CLIENT(EID_CLICK, btn_add, PlayerDlg, OnAdd); + REGISTER_CLIENT(EID_CLICK, btn_del, PlayerDlg, OnDel); + REGISTER_CLIENT(EID_USER_1, btn_del, PlayerDlg, OnDelConfirm); + + apply = (Button*) FindControl(1); + REGISTER_CLIENT(EID_CLICK, apply, PlayerDlg, OnApply); + + cancel = (Button*) FindControl(2); + REGISTER_CLIENT(EID_CLICK, cancel, PlayerDlg, OnCancel); +} + +// +--------------------------------------------------------------------+ + +void +PlayerDlg::Show() +{ + FormWindow::Show(); + + if (!lst_roster) return; + lst_roster->ClearItems(); + lst_roster->SetSelectedStyle(ListBox::LIST_ITEM_STYLE_FILLED_BOX); + lst_roster->SetLeading(2); + + int current_index = 0; + + List& roster = Player::GetRoster(); + for (int i = 0; i < roster.size(); i++) { + Player* p = roster[i]; + lst_roster->AddItem(p->Name()); + if (p == Player::GetCurrentPlayer()) + current_index = i; + } + + lst_roster->SetSelected(current_index); + ShowPlayer(); + + apply->SetEnabled(roster.size() > 0); +} + +// +--------------------------------------------------------------------+ + +void +PlayerDlg::ExecFrame() +{ + if (Keyboard::KeyDown(VK_RETURN)) { + OnApply(0); + } +} + +// +--------------------------------------------------------------------+ + +void +PlayerDlg::ShowPlayer() +{ + Player* p = Player::GetCurrentPlayer(); + + if (p) { + if (txt_name) txt_name->SetText(p->Name()); + if (txt_password) txt_password->SetText(p->Password()); + if (txt_squadron) txt_squadron->SetText(p->Squadron()); + if (txt_signature) txt_signature->SetText(p->Signature()); + + char flight_time[64], missions[16], kills[16], losses[16], points[16]; + FormatTime(flight_time, p->FlightTime()); + sprintf(missions, "%d", p->Missions()); + sprintf(kills, "%d", p->Kills()); + sprintf(losses, "%d", p->Losses()); + sprintf(points, "%d", p->Points()); + + if (lbl_createdate) lbl_createdate->SetText(FormatTimeString(p->CreateDate())); + if (lbl_rank) lbl_rank->SetText(Player::RankName(p->Rank())); + if (lbl_flighttime) lbl_flighttime->SetText(flight_time); + if (lbl_missions) lbl_missions->SetText(missions); + if (lbl_kills) lbl_kills->SetText(kills); + if (lbl_losses) lbl_losses->SetText(losses); + if (lbl_points) lbl_points->SetText(points); + + if (img_rank) { + img_rank->SetPicture(*Player::RankInsignia(p->Rank(), 0)); + img_rank->Show(); + } + + for (int i = 0; i < 15; i++) { + if (img_medals[i]) { + int medal = p->Medal(i); + if (medal) { + medals[i] = medal; + img_medals[i]->SetPicture(*Player::MedalInsignia(medal, 0)); + img_medals[i]->Show(); + } + else { + medals[i] = -1; + img_medals[i]->Hide(); + } + } + } + + for (i = 0; i < 10; i++) { + if (txt_chat[i]) + txt_chat[i]->SetText(p->ChatMacro(i)); + } + } + else { + if (txt_name) txt_name->SetText(""); + if (txt_password) txt_password->SetText(""); + if (txt_squadron) txt_squadron->SetText(""); + if (txt_signature) txt_signature->SetText(""); + + if (lbl_createdate) lbl_createdate->SetText(""); + if (lbl_rank) lbl_rank->SetText(""); + if (lbl_flighttime) lbl_flighttime->SetText(""); + if (lbl_missions) lbl_missions->SetText(""); + if (lbl_kills) lbl_kills->SetText(""); + if (lbl_losses) lbl_losses->SetText(""); + if (lbl_points) lbl_points->SetText(""); + + if (img_rank) img_rank->Hide(); + + for (int i = 0; i < 15; i++) { + medals[i] = -1; + if (img_medals[i]) + img_medals[i]->Hide(); + } + + for (i = 0; i < 10; i++) { + if (txt_chat[i]) + txt_chat[i]->SetText(""); + } + } +} + +// +--------------------------------------------------------------------+ + +void +PlayerDlg::UpdatePlayer() +{ + Player* p = Player::GetCurrentPlayer(); + + if (p) { + if (txt_name) p->SetName(txt_name->GetText()); + if (txt_password) p->SetPassword(txt_password->GetText()); + if (txt_squadron) p->SetSquadron(txt_squadron->GetText()); + if (txt_signature) p->SetSignature(txt_signature->GetText()); + + for (int i = 0; i < 10; i++) { + if (txt_chat[i]) + p->SetChatMacro(i, txt_chat[i]->GetText()); + } + } +} + +// +--------------------------------------------------------------------+ + +void +PlayerDlg::OnSelectPlayer(AWEvent* event) +{ + if (!lst_roster) return; + + UpdatePlayer(); + + int index = lst_roster->GetSelection(); + + List& roster = Player::GetRoster(); + if (index >= 0 && index < roster.size()) { + Player::SelectPlayer(roster.at(index)); + } + + ShowPlayer(); + + apply->SetEnabled(roster.size() > 0); +} + +void +PlayerDlg::OnAdd(AWEvent* event) +{ + Player::Create("Pilot"); + ShowPlayer(); + + if (!lst_roster) return; + lst_roster->ClearItems(); + + List& roster = Player::GetRoster(); + for (int i = 0; i < roster.size(); i++) { + Player* p = roster[i]; + lst_roster->AddItem(p->Name()); + lst_roster->SetSelected(i, (p == Player::GetCurrentPlayer())); + } + + apply->SetEnabled(roster.size() > 0); +} + +void +PlayerDlg::OnDel(AWEvent* event) +{ + if (!Player::GetCurrentPlayer()) + return; + + ConfirmDlg* confirm = manager->GetConfirmDlg(); + if (confirm) { + char msg[256]; + sprintf(msg, Game::GetText("PlayerDlg.are-you-sure").data(), + Player::GetCurrentPlayer()->Name().data()); + confirm->SetMessage(msg); + confirm->SetTitle(Game::GetText("PlayerDlg.confirm-delete")); + confirm->SetParentControl(btn_del); + + manager->ShowConfirmDlg(); + } + + else { + OnDelConfirm(event); + } +} + +void +PlayerDlg::OnDelConfirm(AWEvent* event) +{ + Player::Destroy(Player::GetCurrentPlayer()); + ShowPlayer(); + + if (!lst_roster) return; + lst_roster->ClearItems(); + + List& roster = Player::GetRoster(); + for (int i = 0; i < roster.size(); i++) { + Player* p = roster[i]; + lst_roster->AddItem(p->Name()); + lst_roster->SetSelected(i, (p == Player::GetCurrentPlayer())); + } + + apply->SetEnabled(roster.size() > 0); +} + +// +--------------------------------------------------------------------+ + +void +PlayerDlg::OnRank(AWEvent* event) +{ + Player* p = Player::GetCurrentPlayer(); + AwardShowDlg* award_dlg = manager->GetAwardDlg(); + + if (p && award_dlg) { + award_dlg->SetRank(p->Rank()); + manager->ShowAwardDlg(); + } +} + +void +PlayerDlg::OnMedal(AWEvent* event) +{ + Player* p = Player::GetCurrentPlayer(); + AwardShowDlg* award_dlg = manager->GetAwardDlg(); + + if (p && award_dlg) { + int m = -1; + + for (int i = 0; i < 15; i++) { + if (event->window == img_medals[i]) { + m = i; + break; + } + } + + if (m >= 0) { + award_dlg->SetMedal(medals[i]); + manager->ShowAwardDlg(); + } + } +} + +// +--------------------------------------------------------------------+ + +void +PlayerDlg::OnApply(AWEvent* event) +{ + Player* player = Player::GetCurrentPlayer(); + if (player) { + UpdatePlayer(); + Player::Save(); + } + + FlushKeys(); + manager->ShowMenuDlg(); +} + +void +PlayerDlg::OnCancel(AWEvent* event) +{ + Player::Load(); + FlushKeys(); + manager->ShowMenuDlg(); +} diff --git a/Stars45/PlayerDlg.h b/Stars45/PlayerDlg.h new file mode 100644 index 0000000..ff0af40 --- /dev/null +++ b/Stars45/PlayerDlg.h @@ -0,0 +1,86 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: PlayerDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Main Menu Dialog Active Window class +*/ + +#ifndef PlayerDlg_h +#define PlayerDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "Bitmap.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Font.h" + +// +--------------------------------------------------------------------+ + +class MenuScreen; +class Player; + +// +--------------------------------------------------------------------+ + +class PlayerDlg : public FormWindow +{ +public: + PlayerDlg(Screen* s, FormDef& def, MenuScreen* mgr); + virtual ~PlayerDlg(); + + virtual void RegisterControls(); + virtual void Show(); + virtual void ExecFrame(); + + // Operations: + virtual void OnApply(AWEvent* event); + virtual void OnCancel(AWEvent* event); + virtual void OnSelectPlayer(AWEvent* event); + virtual void OnAdd(AWEvent* event); + virtual void OnDel(AWEvent* event); + virtual void OnDelConfirm(AWEvent* event); + virtual void OnRank(AWEvent* event); + virtual void OnMedal(AWEvent* event); + + virtual void UpdatePlayer(); + virtual void ShowPlayer(); + +protected: + MenuScreen* manager; + + ListBox* lst_roster; + Button* btn_add; + Button* btn_del; + + EditBox* txt_name; + EditBox* txt_password; + EditBox* txt_squadron; + EditBox* txt_signature; + + EditBox* txt_chat[10]; + + ActiveWindow* lbl_createdate; + ActiveWindow* lbl_rank; + ActiveWindow* lbl_flighttime; + ActiveWindow* lbl_missions; + ActiveWindow* lbl_kills; + ActiveWindow* lbl_losses; + ActiveWindow* lbl_points; + ImageBox* img_rank; + ImageBox* img_medals[15]; + int medals[15]; + + Button* apply; + Button* cancel; +}; + +#endif PlayerDlg_h + diff --git a/Stars45/Power.cpp b/Stars45/Power.cpp new file mode 100644 index 0000000..7090d2f --- /dev/null +++ b/Stars45/Power.cpp @@ -0,0 +1,300 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Power.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Power generation and usage classes +*/ + +#include "MemDebug.h" +#include "Power.h" +#include "Ship.h" +#include "NetUtil.h" + +#include "Game.h" + +// +----------------------------------------------------------------------+ + +static char* source_type[] = { + "sys.power.battery", "sys.power.auxilliary", "sys.power.fusion" +}; + +static int source_value[] = { + 1, 2, 4 +}; + +PowerSource::PowerSource(SUBTYPE s, double max_out, double f_ratio) + : System(POWER_SOURCE, (int) s, "Power", source_value[s], 0), + max_output((float) max_out), fuel_ratio((float) f_ratio), + route_changed(false), requested_power_level(1.0f) +{ + name = Game::GetText(source_type[s]); + abrv = Game::GetText(Text(source_type[s]) + ".abrv"); + + if (fuel_ratio < 1) { + switch (subtype) { // enough to last for [n] hours at full power + case BATTERY: fuel_ratio = max_output * 5 * 3600 / 100; break; + case AUX: fuel_ratio = max_output * 50 * 3600 / 100; break; + case FUSION: fuel_ratio = max_output * 100 * 3600 / 100; break; + } + } + + capacity = 100.0f; + + if (subtype != BATTERY) { + emcon_power[0] = 10; + emcon_power[1] = 50; + emcon_power[2] = 100; + } +} + +PowerSource::PowerSource(const PowerSource& p) + : System(p), + max_output(p.max_output), fuel_ratio(p.fuel_ratio), + route_changed(false), requested_power_level(1.0f) +{ + Mount(p); + SetAbbreviation(p.Abbreviation()); +} + +// +----------------------------------------------------------------------+ + +void +PowerSource::AddClient(System* client) +{ + if (client) { + int old_src_index = client->GetSourceIndex(); + + client->SetSourceIndex(GetID()); + clients.append(client); + route_changed = true; + + if (ship && old_src_index != GetID()) + NetUtil::SendSysStatus(ship, client); + } +} + +void +PowerSource::RemoveClient(System* client) +{ + if (client && clients.contains(client)) { + client->SetSourceIndex(-1); + clients.remove(client); + route_changed = true; + } +} + +// +----------------------------------------------------------------------+ + +int +PowerSource::Charge() const +{ + return (int) capacity; +} + +void +PowerSource::SetFuelRange(double hours) +{ + if (hours > 0) + fuel_ratio = (float) (max_output * hours * 3600 / 100); +} + +// +----------------------------------------------------------------------+ + +void +PowerSource::ExecFrame(double seconds) +{ + if (seconds < 0.001) + seconds = 0.001; + + if (capacity <= 0) + capacity = 0; + + System::ExecFrame(seconds); + + double energy_requested = 0; + double energy_avail = 0; + double total_distrib = 0; + + // fuel leak? + if (availability < 0.4 && capacity > 0) + capacity -= (float) (0.03 * seconds); + + if (IsPowerOn() && capacity > 0) { + energy_avail = max_output * seconds * power_level * availability; + + if (power_level < requested_power_level) { + power_level += (float) (seconds * 0.03); // thirty seconds to charge up + + if (power_level > requested_power_level) + power_level = (float) requested_power_level; + } + else if (power_level > requested_power_level) { + power_level -= (float) (seconds * 0.10); // ten seconds to power down + + if (power_level < requested_power_level) + power_level = (float) requested_power_level; + } + } + + ListIter iter = clients; + while (++iter) { + System* sink = iter.value(); + + if (sink->IsPowerOn()) { + double joules = sink->GetRequest(seconds); + + if (joules > 0) { + if (sink->IsPowerCritical()) { + + if (joules > energy_avail) + joules = energy_avail; + + energy_avail -= joules; + total_distrib += joules; + sink->Distribute(joules, seconds); + } + else { + energy_requested += joules; + } + } + } + else { + sink->Distribute(-0.2 * sink->GetCapacity() * seconds, seconds); + } + } + + if (energy_avail > 0) { + + // enough to go around: + if (energy_requested <= energy_avail) { + iter.reset(); + + while (++iter) { + System* sink = iter.value(); + + if (sink->IsPowerOn() && !sink->IsPowerCritical()) { + double joules = sink->GetRequest(seconds); + total_distrib += joules; + sink->Distribute(joules, seconds); + } + } + } + + // load balancing: + else { + iter.reset(); + while (++iter) { + System* sink = iter.value(); + + if (sink->IsPowerOn() && !sink->IsPowerCritical()) { + double request = sink->GetRequest(seconds); + double delivery = 0; + + if (request > 0) + delivery = energy_avail * (request/energy_requested); + + if (delivery > 0) { + total_distrib += delivery; + sink->Distribute(delivery, seconds); + } + } + } + } + } + + if (IsPowerOn() && capacity > 0 && !Game::Paused()) { + // reactors always burn at least 10% of max (to maintain operating temp): + if (subtype != BATTERY) { + double baseline = 0.1 * max_output * seconds; + + if (total_distrib < baseline) + total_distrib = baseline; + } + + // expend fuel: + if (total_distrib > 0) { + double effective_fuel_ratio = fuel_ratio; + + switch (Ship::GetFlightModel()) { + default: + case Ship::FM_STANDARD: + effective_fuel_ratio = 1 * fuel_ratio * (0.25 + 0.75 * availability); + break; + + case Ship::FM_RELAXED: + effective_fuel_ratio = 3 * fuel_ratio * (0.25 + 0.75 * availability); + break; + + case Ship::FM_ARCADE: + effective_fuel_ratio = 4 * fuel_ratio * (0.25 + 0.75 * availability); + break; + } + + capacity -= (float) (total_distrib / effective_fuel_ratio); + } + } + + else if (capacity <= 0) { + capacity = 0.0f; + PowerOff(); + } +} + +// +--------------------------------------------------------------------+ + +void +PowerSource::SetPowerLevel(double level) +{ + if (level > 100) + level = 100; + else if (level < 0) + level = 0; + + level /= 100; + + if (requested_power_level != level) { + // if the system is on emergency override power, + // do not let the EMCON system use this method + // to drop it back to normal power: + if (requested_power_level > 1 && level == 1) { + requested_power_level = 1.2f; + } + + else { + requested_power_level = (float) level; + } + } +} + +void +PowerSource::SetOverride(bool over) +{ + bool changed = false; + + if (over && requested_power_level != 1.2f) { + requested_power_level = 1.2f; + changed = true; + } + + else if (!over && requested_power_level > 1) { + requested_power_level = 1.0f; + changed = true; + } + + if (changed) + NetUtil::SendSysStatus(ship, this); +} + +void +PowerSource::DrainPower(double to_level) +{ + if (to_level >= 0 && to_level < power_level) + power_level = (float) to_level; +} diff --git a/Stars45/Power.h b/Stars45/Power.h new file mode 100644 index 0000000..893095a --- /dev/null +++ b/Stars45/Power.h @@ -0,0 +1,62 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Power.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Power generation and usage classes +*/ + +#ifndef Power_h +#define Power_h + +#include "Types.h" +#include "System.h" +#include "List.h" + +// +--------------------------------------------------------------------+ + +class PowerSource : public System +{ +public: + enum SUBTYPE { BATTERY, AUX, FUSION }; + + PowerSource(SUBTYPE s, double max_output, double fuel_ratio=0); + PowerSource(const PowerSource& rhs); + + virtual void ExecFrame(double seconds); + + void AddClient(System* client); + void RemoveClient(System* client); + + List& Clients() { return clients; } + + virtual int Charge() const; + + virtual void SetFuelRange(double hours); + + bool RouteChanged() const { return route_changed; } + void RouteScanned() { route_changed = false; } + + // override from System: + virtual void SetPowerLevel(double level); + virtual void SetOverride(bool over); + + // for power drain damage: + virtual void DrainPower(double to_level); + +protected: + float max_output; + float fuel_ratio; + List clients; + bool route_changed; + float requested_power_level; +}; + +#endif Power_h + diff --git a/Stars45/QuantumDrive.cpp b/Stars45/QuantumDrive.cpp new file mode 100644 index 0000000..57e2c94 --- /dev/null +++ b/Stars45/QuantumDrive.cpp @@ -0,0 +1,246 @@ +/* Project Starshatter 5.0 + Destroyer Studios LLC + Copyright © 1997-2007. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: QuantumDrive.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Quantum Drive class +*/ + +#include "MemDebug.h" +#include "QuantumDrive.h" +#include "Ship.h" +#include "Explosion.h" +#include "Drive.h" +#include "Sim.h" +#include "SimEvent.h" +#include "StarSystem.h" + +#include "Game.h" +#include "Random.h" + +// +----------------------------------------------------------------------+ + +static int drive_value = 3; + +// +----------------------------------------------------------------------+ + +QuantumDrive::QuantumDrive(SUBTYPE s, double cap, double rate) + : System(DRIVE, s, "Quantum", drive_value, (float) cap, (float) cap, (float) rate), + dst_rgn(0), active_state(ACTIVE_READY), warp_fov(1), jump_time(0), countdown(5) +{ + name = Game::GetText("sys.quantum"); + abrv = Game::GetText("sys.quantum.abrv"); + + emcon_power[0] = 0; + emcon_power[1] = 0; + emcon_power[2] = 100; +} + +// +----------------------------------------------------------------------+ + +QuantumDrive::QuantumDrive(const QuantumDrive& d) + : System(d), + dst_rgn(0), active_state(ACTIVE_READY), warp_fov(1), jump_time(0), + countdown(d.countdown) +{ + Mount(d); + SetAbbreviation(d.Abbreviation()); + + energy = capacity; +} + +// +--------------------------------------------------------------------+ + +QuantumDrive::~QuantumDrive() +{ } + +// +--------------------------------------------------------------------+ + +void +QuantumDrive::SetDestination(SimRegion* rgn, const Point& loc) +{ + dst_rgn = rgn; + dst_loc = loc; +} + +// +--------------------------------------------------------------------+ + +bool +QuantumDrive::Engage(bool immediate) +{ + if (active_state == ACTIVE_READY && ship != 0 && + IsPowerOn() && Status() == NOMINAL && energy == capacity) { + + active_state = ACTIVE_COUNTDOWN; + if (immediate) { + jump_time = 1; + return true; + } + + jump_time = countdown; + + SimRegion* rgn = ship->GetRegion(); + + ListIter s_iter = rgn->Ships(); + while (++s_iter) { + Ship* s = s_iter.value(); + + if (s != ship) { + double dist = Point(s->Location() - ship->Location()).length(); + + if (dist < 25e3) + jump_time += 5; + + else if (dist < 50e3) + jump_time += 2; + + else if (dist < 100e3) + jump_time += 1; + + else if (dist < 200e3) + jump_time += 0.5; + } + } + + return true; + } + + return false; +} + +void +QuantumDrive::PowerOff() +{ + System::PowerOff(); + AbortJump(); +} + +void +QuantumDrive::AbortJump() +{ + active_state = ACTIVE_READY; + jump_time = 0; + energy = 0; + warp_fov = 1; + + ship->SetWarp(warp_fov); + + SimRegion* r = ship->GetRegion(); + ListIter neighbor = r->Ships(); + + while (++neighbor) { + if (neighbor->IsDropship()) { + Ship* s = neighbor.value(); + Point delta = s->Location() - ship->Location(); + + if (delta.length() < 5e3) + s->SetWarp(warp_fov); + } + } +} + +// +--------------------------------------------------------------------+ + +void +QuantumDrive::ExecFrame(double seconds) +{ + System::ExecFrame(seconds); + + if (active_state == ACTIVE_READY) + return; + + if (ship) { + bool warping = false; + + if (active_state == ACTIVE_COUNTDOWN) { + if (jump_time > 0) { + jump_time -= seconds; + } + + else { + jump_time = 0; + active_state = ACTIVE_PREWARP; + } + } + + else if (active_state == ACTIVE_PREWARP) { + if (warp_fov < 5000) { + warp_fov *= 1.5; + } + else { + Jump(); + energy = 0.0f; + } + + warping = true; + } + + else if (active_state == ACTIVE_POSTWARP) { + if (warp_fov > 1) { + warp_fov *= 0.75; + } + else { + warp_fov = 1; + active_state = ACTIVE_READY; + } + + warping = true; + } + + if (warping) { + ship->SetWarp(warp_fov); + + SimRegion* r = ship->GetRegion(); + ListIter neighbor = r->Ships(); + + while (++neighbor) { + if (neighbor->IsDropship()) { + Ship* s = neighbor.value(); + Point delta = s->Location() - ship->Location(); + + if (delta.length() < 5e3) + s->SetWarp(warp_fov); + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +QuantumDrive::Jump() +{ + Sim* sim = Sim::GetSim(); + + if (ship && sim) { + double dist = 150e3 + Random(0, 60e3); + Point esc_vec = dst_rgn->GetOrbitalRegion()->Location() - + dst_rgn->GetOrbitalRegion()->Primary()->Location(); + + esc_vec.Normalize(); + esc_vec *= dist; + esc_vec += RandomDirection() * Random(15e3, 22e3); + + if (subtype == HYPER) + sim->CreateExplosion(ship->Location(), Point(0,0,0), Explosion::HYPER_FLASH, 1, 1, ship->GetRegion()); + else + sim->CreateExplosion(ship->Location(), Point(0,0,0), Explosion::QUANTUM_FLASH, 1, 0, ship->GetRegion()); + + sim->RequestHyperJump(ship, dst_rgn, esc_vec); + + ShipStats* stats = ShipStats::Find(ship->Name()); + stats->AddEvent(SimEvent::QUANTUM_JUMP, dst_rgn->Name()); + } + + dst_rgn = 0; + dst_loc = Point(); + + active_state = ACTIVE_POSTWARP; +} diff --git a/Stars45/QuantumDrive.h b/Stars45/QuantumDrive.h new file mode 100644 index 0000000..e2c41b7 --- /dev/null +++ b/Stars45/QuantumDrive.h @@ -0,0 +1,73 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: QuantumDrive.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Quantum (JUMP) Drive (system) class +*/ + +#ifndef QuantumDrive_h +#define QuantumDrive_h + +#include "Types.h" +#include "System.h" +#include "Geometry.h" + +// +--------------------------------------------------------------------+ + +class Ship; +class SimRegion; + +// +--------------------------------------------------------------------+ + +class QuantumDrive : public System +{ +public: + enum SUBTYPE { QUANTUM, HYPER }; + + QuantumDrive(SUBTYPE s, double capacity, double sink_rate); + QuantumDrive(const QuantumDrive& rhs); + virtual ~QuantumDrive(); + + enum ACTIVE_STATES { + ACTIVE_READY, ACTIVE_COUNTDOWN, ACTIVE_PREWARP, ACTIVE_POSTWARP + }; + + void SetDestination(SimRegion* rgn, const Point& loc); + bool Engage(bool immediate=false); + int ActiveState() const { return active_state; } + double WarpFactor() const { return warp_fov; } + double JumpTime() const { return jump_time; } + virtual void PowerOff(); + + virtual void ExecFrame(double seconds); + + void SetShip(Ship* s) { ship = s; } + Ship* GetShip() const { return ship; } + + double GetCountdown() const { return countdown; } + void SetCountdown(double d) { countdown = d; } + +protected: + void Jump(); + void AbortJump(); + + int active_state; + + Ship* ship; + double warp_fov; + double jump_time; + double countdown; + + SimRegion* dst_rgn; + Point dst_loc; +}; + +#endif // QuantumDrive_h + diff --git a/Stars45/QuantumFlash.cpp b/Stars45/QuantumFlash.cpp new file mode 100644 index 0000000..f47b3b5 --- /dev/null +++ b/Stars45/QuantumFlash.cpp @@ -0,0 +1,175 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: QuantumFlash.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Quantum Warp Out special effect class +*/ + +#include "MemDebug.h" +#include "QuantumFlash.h" + +#include "Light.h" +#include "Bitmap.h" +#include "DataLoader.h" +#include "Game.h" +#include "Random.h" +#include "Scene.h" + +// +--------------------------------------------------------------------+ + +static Bitmap* quantum_flash_texture = 0; + +// +--------------------------------------------------------------------+ + +QuantumFlash::QuantumFlash() + : nverts(64), npolys(16), mtl(0), verts(0), polys(0), beams(0), + texture(quantum_flash_texture), length(8000), width(0), + shade(1.0) +{ + trans = true; + luminous = true; + + if (!texture || texture->Width() < 1) { + DataLoader* loader = DataLoader::GetLoader(); + loader->SetDataPath("Explosions/"); + loader->LoadTexture("quantum.pcx", quantum_flash_texture, Bitmap::BMP_TRANSLUCENT); + loader->SetDataPath(0); + texture = quantum_flash_texture; + } + + loc = Vec3(0.0f, 0.0f, 1000.0f); + + mtl = new(__FILE__,__LINE__) Material; + verts = new(__FILE__,__LINE__) VertexSet(nverts); + polys = new(__FILE__,__LINE__) Poly[npolys]; + beams = new(__FILE__,__LINE__) Matrix[npolys]; + + mtl->Kd = Color::White; + mtl->tex_diffuse = texture; + mtl->tex_emissive = texture; + mtl->blend = Video::BLEND_ADDITIVE; + mtl->luminous = true; + + verts->nverts = nverts; + + for (int i = 0; i < npolys; i++) { + verts->loc[4*i+0] = Point( width, 0, 1000); + verts->loc[4*i+1] = Point( width, -length, 1000); + verts->loc[4*i+2] = Point(-width, -length, 1000); + verts->loc[4*i+3] = Point(-width, 0, 1000); + + for (int n = 0; n < 4; n++) { + verts->diffuse[4*i+n] = D3DCOLOR_RGBA(255,255,255,255); + verts->specular[4*i+n] = D3DCOLOR_RGBA( 0, 0, 0,255); + verts->tu[4*i+n] = (n < 2) ? 0.0f : 1.0f; + verts->tv[4*i+n] = (n > 0 && n < 3) ? 1.0f : 0.0f; + } + + beams[i].Roll( Random(-2*PI, 2*PI)); + beams[i].Pitch(Random(-2*PI, 2*PI)); + beams[i].Yaw( Random(-2*PI, 2*PI)); + + polys[i].nverts = 4; + polys[i].visible = 1; + polys[i].sortval = 0; + polys[i].vertex_set = verts; + polys[i].material = mtl; + + polys[i].verts[0] = 4*i+0; + polys[i].verts[1] = 4*i+1; + polys[i].verts[2] = 4*i+2; + polys[i].verts[3] = 4*i+3; + } + + radius = (float) length; + length = 0; + strcpy(name, "QuantumFlash"); +} + +// +--------------------------------------------------------------------+ + +QuantumFlash::~QuantumFlash() +{ + delete mtl; + delete verts; + delete [] polys; + delete [] beams; +} + +// +--------------------------------------------------------------------+ + +void +QuantumFlash::Render(Video* video, DWORD flags) +{ + if (hidden || !visible || !video || ((flags & RENDER_ADDITIVE) == 0)) + return; + + const Camera* cam = video->GetCamera(); + + if (cam) { + UpdateVerts(cam->Pos()); + video->DrawPolys(npolys, polys); + } +} + +// +--------------------------------------------------------------------+ + +void +QuantumFlash::UpdateVerts(const Point& cam_pos) +{ + if (length < radius) { + length += radius/80; + width += 1; + } + + for (int i = 0; i < npolys; i++) { + Matrix& m = beams[i]; + + m.Yaw(0.05); + Point vpn = Point(m(2,0), m(2,1), m(2,2)); + + Point head = loc; + Point tail = loc - vpn * length; + Point vtail = tail - head; + Point vcam = cam_pos - loc; + Point vtmp = vcam.cross(vtail); + vtmp.Normalize(); + Point vlat = vtmp * -width; + + verts->loc[4*i+0] = head + vlat; + verts->loc[4*i+1] = tail + vlat * 8; + verts->loc[4*i+2] = tail - vlat * 8; + verts->loc[4*i+3] = head - vlat; + + DWORD color = D3DCOLOR_RGBA((BYTE) (255*shade), (BYTE) (255*shade), (BYTE) (255*shade), 255); + + for (int n = 0; n < 4; n++) { + verts->diffuse[4*i+n] = color; + } + } +} + +// +--------------------------------------------------------------------+ + +void +QuantumFlash::SetOrientation(const Matrix& o) +{ + orientation = o; +} + +void +QuantumFlash::SetShade(double s) +{ + if (s < 0) s = 0; + else if (s > 1) s = 1; + + shade = s; +} + diff --git a/Stars45/QuantumFlash.h b/Stars45/QuantumFlash.h new file mode 100644 index 0000000..0e07306 --- /dev/null +++ b/Stars45/QuantumFlash.h @@ -0,0 +1,60 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: QuantumFlash.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Quantum Warp Out special effect class +*/ + +#ifndef QuantumFlash_h +#define QuantumFlash_h + +#include "Types.h" +#include "Geometry.h" +#include "Graphic.h" +#include "Polygon.h" +#include "SimObject.h" + +// +--------------------------------------------------------------------+ + +class QuantumFlash : public Graphic +{ +public: + QuantumFlash(); + virtual ~QuantumFlash(); + + // operations + virtual void Render(Video* video, DWORD flags); + + // accessors / mutators + virtual void SetOrientation(const Matrix& o); + void SetDirection(const Point& v); + void SetEndPoints(const Point& from, const Point& to); + + double Shade() const { return shade; } + void SetShade(double s); + +protected: + void UpdateVerts(const Point& cam_pos); + + double length; + double width; + double shade; + + int npolys, nverts; + Material* mtl; + VertexSet* verts; + Poly* polys; + Matrix* beams; + Bitmap* texture; + + Matrix orientation; +}; + +#endif QuantumFlash_h diff --git a/Stars45/QuantumView.cpp b/Stars45/QuantumView.cpp new file mode 100644 index 0000000..07f5154 --- /dev/null +++ b/Stars45/QuantumView.cpp @@ -0,0 +1,318 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: QuantumView.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + View class for Quantum Destination HUD Overlay +*/ + +#include "MemDebug.h" +#include "QuantumView.h" +#include "QuantumDrive.h" +#include "HUDView.h" +#include "Ship.h" +#include "Element.h" +#include "Sim.h" +#include "StarSystem.h" +#include "FormatUtil.h" + +#include "Color.h" +#include "Window.h" +#include "Video.h" +#include "Screen.h" +#include "DataLoader.h" +#include "Scene.h" +#include "Font.h" +#include "FontMgr.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "Game.h" +#include "Menu.h" + +static Color hud_color = Color::Black; + +// +====================================================================+ +// +// QUANTUM DRIVE DESTINATION MENU: +// + +static Menu* quantum_menu = 0; +static bool show_menu = false; + +void +QuantumView::Initialize() +{ + static int initialized = 0; + if (initialized) return; + + quantum_menu = new(__FILE__,__LINE__) Menu(Game::GetText("QuantumView.menu")); + + initialized = 1; +} + +void +QuantumView::Close() +{ + delete quantum_menu; +} + +// +====================================================================+ + +QuantumView* QuantumView::quantum_view = 0; + +QuantumView::QuantumView(Window* c) + : View(c), sim(0), ship(0), font(0) +{ + quantum_view = this; + sim = Sim::GetSim(); + + width = window->Width(); + height = window->Height(); + xcenter = (width / 2.0) - 0.5; + ycenter = (height / 2.0) + 0.5; + font = FontMgr::Find("HUD"); + + HUDView* hud = HUDView::GetInstance(); + if (hud) + SetColor(hud->GetTextColor()); +} + +QuantumView::~QuantumView() +{ + quantum_view = 0; +} + +void +QuantumView::OnWindowMove() +{ + width = window->Width(); + height = window->Height(); + xcenter = (width / 2.0) - 0.5; + ycenter = (height / 2.0) + 0.5; +} + +// +--------------------------------------------------------------------+ + +bool +QuantumView::Update(SimObject* obj) +{ + if (obj == ship) + ship = 0; + + return SimObserver::Update(obj); +} + +const char* +QuantumView::GetObserverName() const +{ + return "QuantumView"; +} + +// +--------------------------------------------------------------------+ + +void +QuantumView::Refresh() +{ + sim = Sim::GetSim(); + + if (sim && ship != sim->GetPlayerShip()) { + ship = sim->GetPlayerShip(); + + if (ship) { + if (ship->Life() == 0 || ship->IsDying() || ship->IsDead()) { + ship = 0; + } + else { + Observe(ship); + } + } + } + + if (IsMenuShown()) { + Rect menu_rect(width-115, 10, 115, 12); + + font->SetColor(hud_color); + font->SetAlpha(1); + font->DrawText(quantum_menu->GetTitle(), 0, menu_rect, DT_CENTER); + + menu_rect.y += 15; + + ListIter item = quantum_menu->GetItems(); + while (++item) { + item->SetEnabled(true); + + font->DrawText(item->GetText(), 0, menu_rect, DT_LEFT); + menu_rect.y += 10; + } + } +} + +// +--------------------------------------------------------------------+ + +void +QuantumView::ExecFrame() +{ + HUDView* hud = HUDView::GetInstance(); + if (hud) { + if (hud_color != hud->GetTextColor()) { + hud_color = hud->GetTextColor(); + SetColor(hud_color); + } + } + + static double time_til_change = 0; + + if (time_til_change > 0) + time_til_change -= Game::GUITime(); + + if (time_til_change <= 0) { + time_til_change = 0; + + if (show_menu) { + QuantumDrive* quantum_drive = 0; + + if (ship) + quantum_drive = ship->GetQuantumDrive(); + + if (quantum_drive && quantum_drive->ActiveState() != QuantumDrive::ACTIVE_READY) { + show_menu = false; + return; + } + + int max_items = quantum_menu->NumItems(); + + for (int i = 0; i < max_items; i++) { + if (Keyboard::KeyDown('1' + i)) { + MenuItem* item = quantum_menu->GetItem(i); + if (item && item->GetEnabled()) { + + SimRegion* rgn = (SimRegion*) item->GetData(); + + if (rgn) { + quantum_drive->SetDestination(rgn, Point(0,0,0)); + quantum_drive->Engage(); + } + + show_menu = false; + time_til_change = 0.3; + break; + } + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +QuantumView::SetColor(Color c) +{ + hud_color = c; +} + +// +--------------------------------------------------------------------+ + +bool +QuantumView::IsMenuShown() +{ + return show_menu; +} + +void +QuantumView::ShowMenu() +{ + if (!ship) return; + + if (!show_menu) { + if (ship->IsStarship() && ship->GetQuantumDrive()) { + GetQuantumMenu(ship); + show_menu = true; + } + + for (int i = 0; i < 10; i++) { + if (Keyboard::KeyDown('1' + i)) { + // just need to clear the key down flag + // so we don't process old keystrokes + // as valid menu picks... + } + } + } +} + +void +QuantumView::CloseMenu() +{ + show_menu = false; +} + +// +--------------------------------------------------------------------+ + +Menu* +QuantumView::GetQuantumMenu(Ship* s) +{ + if (s && sim) { + if (s->IsStarship()) { + quantum_menu->ClearItems(); + + SimRegion* current_region = ship->GetRegion(); + + if (!current_region) return 0; + + StarSystem* current_system = current_region->System(); + + List rgn_list; + + ListIter iter = sim->GetRegions(); + while (++iter) { + SimRegion* rgn = iter.value(); + StarSystem* rgn_system = rgn->System(); + + if (rgn != ship->GetRegion() && !rgn->IsAirSpace() && + rgn_system == current_system) { + rgn_list.append(rgn); + } + } + + // sort local regions by distance from star: + rgn_list.sort(); + + // now add regions in other star systems: + iter.reset(); + while (++iter) { + SimRegion* rgn = iter.value(); + StarSystem* rgn_system = rgn->System(); + + if (rgn != ship->GetRegion() && rgn->Type() != SimRegion::AIR_SPACE && + rgn_system != current_system && current_region->Links().contains(rgn)) { + rgn_list.append(rgn); + } + } + + int n = 1; + iter.attach(rgn_list); + while (++iter) { + SimRegion* rgn = iter.value(); + StarSystem* rgn_system = rgn->System(); + char text[64]; + + if (rgn_system != current_system) + sprintf(text, "%d. %s/%s", n++, rgn_system->Name(), rgn->Name()); + else + sprintf(text, "%d. %s", n++, rgn->Name()); + + quantum_menu->AddItem(text, (DWORD) rgn); + } + + return quantum_menu; + } + } + + return 0; +} diff --git a/Stars45/QuantumView.h b/Stars45/QuantumView.h new file mode 100644 index 0000000..07d074c --- /dev/null +++ b/Stars45/QuantumView.h @@ -0,0 +1,73 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: QuantumView.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + View class for Radio Communications HUD Overlay +*/ + +#ifndef QuantumView_h +#define QuantumView_h + +#include "Types.h" +#include "View.h" +#include "SimObject.h" +#include "Color.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class Ship; +class RadioMessage; +class HUDView; +class Menu; +class Font; + +// +--------------------------------------------------------------------+ + +class QuantumView : public View, + public SimObserver +{ +public: + QuantumView(Window* c); + virtual ~QuantumView(); + + // Operations: + virtual void Refresh(); + virtual void OnWindowMove(); + virtual void ExecFrame(); + + virtual Menu* GetQuantumMenu(Ship* ship); + virtual bool IsMenuShown(); + virtual void ShowMenu(); + virtual void CloseMenu(); + + virtual bool Update(SimObject* obj); + virtual const char* GetObserverName() const; + + static void SetColor(Color c); + + static void Initialize(); + static void Close(); + + static QuantumView* GetInstance() { return quantum_view; } + +protected: + int width, height; + double xcenter, ycenter; + + Font* font; + Sim* sim; + Ship* ship; + + static QuantumView* quantum_view; +}; + +#endif QuantumView_h + diff --git a/Stars45/QuitView.cpp b/Stars45/QuitView.cpp new file mode 100644 index 0000000..35670ac --- /dev/null +++ b/Stars45/QuitView.cpp @@ -0,0 +1,302 @@ +/* Project Starshatter 5.0 + Destroyer Studios LLC + Copyright © 1997-2007. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: QuitView.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + View class for End Mission menu +*/ + +#include "MemDebug.h" +#include "QuitView.h" +#include "HUDView.h" +#include "RadioView.h" +#include "Ship.h" +#include "Contact.h" +#include "Sim.h" +#include "SimEvent.h" +#include "Campaign.h" +#include "Starshatter.h" +#include "GameScreen.h" + +#include "CameraView.h" +#include "Color.h" +#include "Window.h" +#include "Video.h" +#include "Screen.h" +#include "DataLoader.h" +#include "Scene.h" +#include "FontMgr.h" +#include "Button.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "MouseController.h" +#include "Game.h" +#include "Menu.h" + +// +====================================================================+ + +static bool show_menu = false; +static Bitmap* menu_bmp = 0; +static MouseController* mouse_con = 0; +static bool mouse_active = false; +static const int w2 = 200; +static const int h2 = 128; + +void +QuitView::Initialize() +{ + if (!menu_bmp) { + menu_bmp = new(__FILE__,__LINE__) Bitmap; + + DataLoader* loader = DataLoader::GetLoader(); + loader->SetDataPath("Screens/"); + loader->LoadBitmap("QuitWin.pcx", *menu_bmp, Bitmap::BMP_TRANSPARENT); + loader->SetDataPath(0); + } +} + +void +QuitView::Close() +{ + delete menu_bmp; +} + +// +====================================================================+ + +QuitView* QuitView::quit_view = 0; + +QuitView::QuitView(Window* c) + : View(c), mouse_latch(false) +{ + quit_view = this; + sim = Sim::GetSim(); + + width = window->Width(); + height = window->Height(); + xcenter = width / 2; + ycenter = height / 2; + + mouse_con = MouseController::GetInstance(); +} + +QuitView::~QuitView() +{ + quit_view = 0; +} + +void +QuitView::OnWindowMove() +{ + width = window->Width(); + height = window->Height(); + xcenter = width / 2; + ycenter = height / 2; +} + +// +--------------------------------------------------------------------+ + +void +QuitView::Refresh() +{ + if (show_menu && menu_bmp) { + Rect clip_rect; + + clip_rect.x = xcenter - w2; + clip_rect.y = ycenter - h2; + clip_rect.w = w2 * 2; + clip_rect.h = h2 * 2; + + window->ClipBitmap(xcenter - w2, + ycenter - h2, + xcenter - w2 + menu_bmp->Width(), + ycenter - h2 + menu_bmp->Height(), + menu_bmp, + Color::White, + Video::BLEND_SOLID, + clip_rect); + } +} + +// +--------------------------------------------------------------------+ + +void +QuitView::ExecFrame() +{ + sim = Sim::GetSim(); + + if (show_menu) { + Color::SetFade(1, Color::Black, 0); + int action = 0; + + if (Mouse::LButton()) { + mouse_latch = true; + } + else if (mouse_latch) { + mouse_latch = false; + + if (Mouse::X() > xcenter - w2 && Mouse::X() < xcenter + w2) { + int y0 = ycenter - h2; + + for (int i = 0; i < 4; i++) + if (Mouse::Y() >= y0 + 75 + i * 30 && Mouse::Y() <= y0 + 105 + i * 30) + action = i+1; + } + } + + for (int i = 1; i <= 4; i++) { + if (Keyboard::KeyDown('0' + i)) + action = i; + } + + // was mission long enough to accept? + if (action == 1 && !CanAccept()) { + Button::PlaySound(Button::SND_REJECT); + action = 3; + } + + // exit and accept: + if (action == 1) { + CloseMenu(); + Game::SetTimeCompression(1); + + Starshatter* stars = Starshatter::GetInstance(); + stars->SetGameMode(Starshatter::PLAN_MODE); + } + + // quit and discard results: + else if (action == 2) { + CloseMenu(); + Game::SetTimeCompression(1); + + Starshatter* stars = Starshatter::GetInstance(); + Campaign* campaign = Campaign::GetCampaign(); + + // discard mission and events: + if (sim) sim->UnloadMission(); + else ShipStats::Initialize(); + + if (campaign && campaign->GetCampaignId() < Campaign::SINGLE_MISSIONS) { + campaign->RollbackMission(); + stars->SetGameMode(Starshatter::CMPN_MODE); + } + + else { + stars->SetGameMode(Starshatter::MENU_MODE); + } + } + + // resume: + else if (action == 3) { + CloseMenu(); + } + + // controls: + else if (action == 4) { + GameScreen* game_screen = GameScreen::GetInstance(); + + if (game_screen) + game_screen->ShowCtlDlg(); + else + CloseMenu(); + } + } +} + +// +--------------------------------------------------------------------+ + +bool +QuitView::IsMenuShown() +{ + return show_menu; +} + +// +--------------------------------------------------------------------+ + +bool +QuitView::CanAccept() +{ + sim = Sim::GetSim(); + + if (!sim || sim->IsNetGame()) + return true; + + Ship* player_ship = sim->GetPlayerShip(); + + if (player_ship->MissionClock() < 60000) { + RadioView::Message(Game::GetText("QuitView.too-soon")); + RadioView::Message(Game::GetText("QuitView.abort")); + return false; + } + + ListIter iter = player_ship->ContactList(); + while (++iter) { + Contact* c = iter.value(); + Ship* cship = c->GetShip(); + int ciff = c->GetIFF(player_ship); + + if (c->Threat(player_ship)) { + RadioView::Message(Game::GetText("QuitView.threats-present")); + RadioView::Message(Game::GetText("QuitView.abort")); + return false; + } + + else if (cship && ciff > 0 && ciff != player_ship->GetIFF()) { + Point delta = c->Location() - player_ship->Location(); + double dist = delta.length(); + + if (cship->IsDropship() && dist < 50e3) { + RadioView::Message(Game::GetText("QuitView.threats-present")); + RadioView::Message(Game::GetText("QuitView.abort")); + return false; + } + + else if (cship->IsStarship() && dist < 100e3) { + RadioView::Message(Game::GetText("QuitView.threats-present")); + RadioView::Message(Game::GetText("QuitView.abort")); + return false; + } + } + } + + return true; +} + +void +QuitView::ShowMenu() +{ + if (!show_menu) { + show_menu = true; + + for (int i = 0; i < 10; i++) { + if (Keyboard::KeyDown('1' + i)) { + // just need to clear the key down flag + // so we don't process old keystrokes + // as valid menu picks... + } + } + + Button::PlaySound(Button::SND_CONFIRM); + Starshatter::GetInstance()->Pause(true); + + if (mouse_con) { + mouse_active = mouse_con->Active(); + mouse_con->SetActive(false); + } + } +} + +void +QuitView::CloseMenu() +{ + show_menu = false; + Starshatter::GetInstance()->Pause(false); + + if (mouse_con) + mouse_con->SetActive(mouse_active); +} diff --git a/Stars45/QuitView.h b/Stars45/QuitView.h new file mode 100644 index 0000000..c9a75da --- /dev/null +++ b/Stars45/QuitView.h @@ -0,0 +1,64 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: QuitView.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + View class for End Mission menu +*/ + +#ifndef QuitView_h +#define QuitView_h + +#include "Types.h" +#include "View.h" +#include "Bitmap.h" +#include "Font.h" +#include "SimObject.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class HUDView; +class Menu; + +// +--------------------------------------------------------------------+ + +class QuitView : public View +{ +public: + QuitView(Window* c); + virtual ~QuitView(); + + // Operations: + virtual void Refresh(); + virtual void OnWindowMove(); + virtual void ExecFrame(); + + virtual bool CanAccept(); + virtual bool IsMenuShown(); + virtual void ShowMenu(); + virtual void CloseMenu(); + + static void Initialize(); + static void Close(); + + static QuitView* GetInstance() { return quit_view; } + +protected: + int width, height; + int xcenter, ycenter; + bool mouse_latch; + + Sim* sim; + + static QuitView* quit_view; +}; + +#endif QuitView_h + diff --git a/Stars45/RLoc.cpp b/Stars45/RLoc.cpp new file mode 100644 index 0000000..4388844 --- /dev/null +++ b/Stars45/RLoc.cpp @@ -0,0 +1,88 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: RLoc.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Navigation Point class implementation +*/ + +#include "MemDebug.h" +#include "RLoc.h" +#include "Random.h" + +// +----------------------------------------------------------------------+ + +RLoc::RLoc() + : rloc(0), dex(0), dex_var(5.0e3f), az(0), az_var(3.1415f), el(0), el_var(0.1f) +{ } + +RLoc::RLoc(const Point& l, double d, double dv) + : loc(l), base_loc(l), rloc(0), dex((float) d), dex_var((float) dv), + az(0), az_var(3.1415f), el(0), el_var(0.1f) +{ } + +RLoc::RLoc(RLoc* l, double d, double dv) + : rloc(l), dex((float) d), dex_var((float) dv), + az(0), az_var(3.1415f), el(0), el_var(0.1f) +{ } + +RLoc::RLoc(const RLoc& r) + : loc(r.loc), base_loc(r.base_loc), rloc(r.rloc), + dex(r.dex), dex_var(r.dex_var), + az(r.az), az_var(r.az_var), + el(r.el), el_var(r.el_var) +{ } + +RLoc::~RLoc() +{ } + +// +----------------------------------------------------------------------+ + +const Point& +RLoc::Location() +{ + if (rloc || dex > 0) Resolve(); + return loc; +} + +// +----------------------------------------------------------------------+ + +void +RLoc::Resolve() +{ + if (rloc) { + base_loc = rloc->Location(); + rloc = 0; + } + + if (dex > 0) { + double d = dex + Random(-dex_var, dex_var); + double a = az + Random(-az_var, az_var); + double e = el + Random(-el_var, el_var); + + Point p = Point(d * sin(a), + d * -cos(a), + d * sin(e)); + + loc = base_loc + p; + dex = 0; + } + else { + loc = base_loc; + } +} + +// +----------------------------------------------------------------------+ + +void +RLoc::SetBaseLocation(const Point& l) +{ + base_loc = l; + loc = l; +} diff --git a/Stars45/RLoc.h b/Stars45/RLoc.h new file mode 100644 index 0000000..39cded4 --- /dev/null +++ b/Stars45/RLoc.h @@ -0,0 +1,71 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: RLoc.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Relative Location (RLoc) class declaration +*/ + +#ifndef RLoc_h +#define RLoc_h + +#include "Types.h" +#include "Geometry.h" + +// +--------------------------------------------------------------------+ + +class RLoc +{ +public: + RLoc(); + RLoc(const Point& loc, double d, double dv=5e3); + RLoc(RLoc* rloc, double d, double dv=5e3); + RLoc(const RLoc& r); + ~RLoc(); + + // accessors: + const Point& Location(); + const Point& BaseLocation() const { return base_loc; } + RLoc* ReferenceLoc() const { return rloc; } + double Distance() const { return dex; } + double DistanceVar() const { return dex_var; } + double Azimuth() const { return az; } + double AzimuthVar() const { return az_var; } + double Elevation() const { return el; } + double ElevationVar() const { return el_var; } + + void Resolve(); + + // mutators: + void SetBaseLocation(const Point& l); + void SetReferenceLoc(RLoc* r) { rloc = r; } + void SetDistance(double d) { dex = (float) d; } + void SetDistanceVar(double dv) { dex_var = (float) dv; } + void SetAzimuth(double a) { az = (float) a; } + void SetAzimuthVar(double av) { az_var = (float) av; } + void SetElevation(double e) { el = (float) e; } + void SetElevationVar(double ev) { el_var = (float) ev; } + +private: + Point loc; + Point base_loc; + RLoc* rloc; + + float dex; + float dex_var; + float az; + float az_var; + float el; + float el_var; +}; + +// +--------------------------------------------------------------------+ + +#endif RLoc_h + diff --git a/Stars45/RadioHandler.cpp b/Stars45/RadioHandler.cpp new file mode 100644 index 0000000..e56c850 --- /dev/null +++ b/Stars45/RadioHandler.cpp @@ -0,0 +1,561 @@ +/* Project Starshatter 5.0 + Destroyer Studios LLC + Copyright © 1997-2007. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: RadioHandler.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Radio message handler class implementation +*/ + +#include "MemDebug.h" +#include "RadioHandler.h" +#include "RadioMessage.h" +#include "RadioTraffic.h" +#include "Instruction.h" + +#include "Contact.h" +#include "Element.h" +#include "Mission.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "Sim.h" +#include "StarSystem.h" +#include "Power.h" +#include "Drive.h" +#include "Shield.h" +#include "Hangar.h" +#include "FlightDeck.h" +#include "WeaponGroup.h" +#include "SteerAI.h" + +#include "Text.h" +#include "Game.h" + +// +----------------------------------------------------------------------+ + +RadioHandler::RadioHandler() +{ } + +RadioHandler::~RadioHandler() +{ } + +// +----------------------------------------------------------------------+ + +bool +RadioHandler::ProcessMessage(RadioMessage* msg, Ship* s) +{ + if (!s || !msg || !msg->Sender()) + return false; + + if (s->Class() >= Ship::FARCASTER && s->Class() <= Ship::C3I) + return false; + + if (msg->Sender()->IsRogue()) { + Ship* sender = (Ship*) msg->Sender(); // cast-away const + RadioMessage* nak = new(__FILE__,__LINE__) RadioMessage(sender, s, RadioMessage::NACK); + RadioTraffic::Transmit(nak); + return false; + } + + bool respond = (s != msg->Sender()); + + // SPECIAL CASE: + // skip navpoint must be processed by elem leader, + // even if the elem leader sent the message: + + if (msg->Action() == RadioMessage::SKIP_NAVPOINT && !respond) + ProcessMessageAction(msg, s); + + if (ProcessMessageOrders(msg, s)) + respond = respond && true; + + else + respond = respond && ProcessMessageAction(msg, s); + + return respond; +} + +// +----------------------------------------------------------------------+ + +bool +RadioHandler::IsOrder(int action) +{ + bool result = false; + + switch (action) { + default: + case RadioMessage::NONE: + case RadioMessage::ACK: + case RadioMessage::NACK: result = false; break; + +// target mgt: + case RadioMessage::ATTACK: + case RadioMessage::ESCORT: + case RadioMessage::BRACKET: + case RadioMessage::IDENTIFY: result = true; break; + +// combat mgt: + case RadioMessage::COVER_ME: + case RadioMessage::WEP_HOLD: + case RadioMessage::FORM_UP: result = true; break; + + case RadioMessage::WEP_FREE: + case RadioMessage::SAY_POSITION: + case RadioMessage::LAUNCH_PROBE: result = false; break; + +// formation mgt: + case RadioMessage::GO_DIAMOND: + case RadioMessage::GO_SPREAD: + case RadioMessage::GO_BOX: + case RadioMessage::GO_TRAIL: result = true; break; + +// mission mgt: + case RadioMessage::MOVE_PATROL: result = true; break; + case RadioMessage::SKIP_NAVPOINT: result = false; break; + case RadioMessage::RESUME_MISSION: result = true; break; + + case RadioMessage::RTB: + case RadioMessage::DOCK_WITH: + case RadioMessage::QUANTUM_TO: + case RadioMessage::FARCAST_TO: result = true; break; + +// sensor mgt: + case RadioMessage::GO_EMCON1: + case RadioMessage::GO_EMCON2: + case RadioMessage::GO_EMCON3: result = true; break; + +// support: + case RadioMessage::REQUEST_PICTURE: + case RadioMessage::REQUEST_SUPPORT: + case RadioMessage::PICTURE: result = false; break; + +// traffic control: + case RadioMessage::CALL_INBOUND: + case RadioMessage::CALL_APPROACH: + case RadioMessage::CALL_CLEARANCE: + case RadioMessage::CALL_FINALS: + case RadioMessage::CALL_WAVE_OFF: result = false; break; + } + + return result; +} + +// +----------------------------------------------------------------------+ + +bool +RadioHandler::ProcessMessageOrders(RadioMessage* msg, Ship* ship) +{ + Instruction* instruction = ship->GetRadioOrders(); + int action = 0; + + if (msg && msg->Action() == RadioMessage::RESUME_MISSION) { + instruction->SetAction(RadioMessage::NONE); + instruction->SetFormation(-1); + instruction->SetWeaponsFree(true); + if (instruction->GetTarget()) { + instruction->ClearTarget(); + ship->DropTarget(); + } + return true; + } + + if (msg && IsOrder(msg->Action())) { + int posture_only = false; + + action = msg->Action(); + + if (action == RadioMessage::FORM_UP) + action = RadioMessage::WEP_HOLD; + + // target orders => drop current target: + if (action >= RadioMessage::ATTACK && + action <= RadioMessage::COVER_ME || + action == RadioMessage::WEP_HOLD || + action >= RadioMessage::DOCK_WITH && + action <= RadioMessage::FARCAST_TO) { + + if (ship != msg->Sender()) + ship->DropTarget(); + + Director* dir = ship->GetDirector(); + if (dir && dir->Type() >= SteerAI::SEEKER && dir->Type() <= SteerAI::GROUND) { + SteerAI* ai = (SteerAI*) dir; + ai->SetTarget(0); + } + + // farcast and quantum jump radio messages: + if (action >= RadioMessage::QUANTUM_TO) { + Sim* sim = Sim::GetSim(); + + if (sim) { + SimRegion* rgn = sim->FindRegion(msg->Info()); + + if (rgn) { + instruction->SetAction(action); + instruction->SetLocation(Point(0,0,0)); + instruction->SetRegion(rgn); + instruction->SetFarcast(action == RadioMessage::FARCAST_TO); + instruction->SetWeaponsFree(false); + return true; + } + } + } + } + + // formation orders => set formation: + if (action >= RadioMessage::GO_DIAMOND && + action <= RadioMessage::GO_TRAIL) { + + switch (action) { + case RadioMessage::GO_DIAMOND: instruction->SetFormation(Instruction::DIAMOND); break; + case RadioMessage::GO_SPREAD: instruction->SetFormation(Instruction::SPREAD); break; + case RadioMessage::GO_BOX: instruction->SetFormation(Instruction::BOX); break; + case RadioMessage::GO_TRAIL: instruction->SetFormation(Instruction::TRAIL); break; + } + + posture_only = true; + } + + // emcon orders => set emcon: + if (action >= RadioMessage::GO_EMCON1 && + action <= RadioMessage::GO_EMCON3) { + + switch (msg->Action()) { + case RadioMessage::GO_EMCON1: instruction->SetEMCON(1); break; + case RadioMessage::GO_EMCON2: instruction->SetEMCON(2); break; + case RadioMessage::GO_EMCON3: instruction->SetEMCON(3); break; + } + + posture_only = true; + } + + if (!posture_only) { + instruction->SetAction(action); + instruction->ClearTarget(); + + if (msg->TargetList().size() > 0) { + SimObject* msg_tgt = msg->TargetList().at(0); + instruction->SetTarget(msg_tgt); + instruction->SetLocation(msg_tgt->Location()); + } + + else if (action == RadioMessage::COVER_ME) { + instruction->SetTarget((Ship*) msg->Sender()); + instruction->SetLocation(msg->Sender()->Location()); + } + + else if (action == RadioMessage::MOVE_PATROL) { + instruction->SetLocation(msg->Location()); + } + + // handle element engagement: + if (action == RadioMessage::ATTACK && msg->TargetList().size() > 0) { + Element* elem = msg->DestinationElem(); + + if (!elem && msg->DestinationShip()) + elem = msg->DestinationShip()->GetElement(); + + if (elem) { + SimObject* msg_tgt = msg->TargetList().at(0); + if (msg_tgt && msg_tgt->Type() == SimObject::SIM_SHIP) { + Element* tgt = ((Ship*) msg_tgt)->GetElement(); + elem->SetAssignment(tgt); + + if (msg->TargetList().size() > 1) + instruction->SetTarget(tgt->Name().data()); + else + instruction->SetTarget(msg_tgt); + } + else { + elem->ResumeAssignment(); + } + } + } + + else if (action == RadioMessage::RESUME_MISSION) { + Element* elem = msg->DestinationElem(); + + if (!elem && msg->DestinationShip()) + elem = msg->DestinationShip()->GetElement(); + + if (elem) { + elem->ResumeAssignment(); + } + } + } + + instruction->SetWeaponsFree(action <= RadioMessage::WEP_FREE); + return true; + } + + return false; +} + +// +----------------------------------------------------------------------+ + +bool +RadioHandler::ProcessMessageAction(RadioMessage* msg, Ship* ship) +{ + if (!msg) return false; + + if (msg->Action() == RadioMessage::CALL_INBOUND) + return Inbound(msg, ship); + + if (msg->Action() == RadioMessage::CALL_FINALS) + return true; // acknowledge + + if (msg->Action() == RadioMessage::REQUEST_PICTURE) + return Picture(msg, ship); + + if (msg->Action() == RadioMessage::REQUEST_SUPPORT) + return Support(msg, ship); + + if (msg->Action() == RadioMessage::SKIP_NAVPOINT) + return SkipNavpoint(msg, ship); + + if (msg->Action() == RadioMessage::LAUNCH_PROBE) + return LaunchProbe(msg, ship); + + return false; +} + +bool +RadioHandler::SkipNavpoint(RadioMessage* msg, Ship* ship) +{ + // Find next Instruction: + Instruction* navpt = ship->GetNextNavPoint(); + int elem_index = ship->GetElementIndex(); + + if (navpt && elem_index < 2) { + ship->SetNavptStatus(navpt, Instruction::SKIPPED); + } + + return true; +} + +bool +RadioHandler::LaunchProbe(RadioMessage* msg, Ship* ship) +{ + if (ship && ship->GetProbeLauncher()) { + ship->LaunchProbe(); + return ship->GetProbe() != 0; + } + + return false; +} + +bool +RadioHandler::Inbound(RadioMessage* msg, Ship* ship) +{ + Ship* inbound = (Ship*) msg->Sender(); + Hangar* hangar = ship->GetHangar(); + FlightDeck* deck = 0; + int squadron = -1; + int slot = -1; + bool same_rgn = false; + + if (inbound && inbound->GetRegion() == ship->GetRegion()) + same_rgn = true; + + // is the sender already inbound to us? + if (inbound->GetInbound() && + inbound->GetInbound()->GetDeck() && + inbound->GetInbound()->GetDeck()->GetCarrier() == ship) { + InboundSlot* islot = inbound->GetInbound(); + deck = islot->GetDeck(); + squadron = islot->Squadron(); + slot = islot->Index(); + } + + // otherwise, find space for sender: + else { + if (hangar && same_rgn) { + if (hangar->FindSlot(inbound, squadron, slot)) { + int shortest_queue = 1000; + + for (int i = 0; i < ship->NumFlightDecks(); i++) { + FlightDeck* d = ship->GetFlightDeck(i); + if (d->IsRecoveryDeck()) { + int nwaiting = d->GetRecoveryQueue().size(); + + if (nwaiting < shortest_queue) { + deck = d; + shortest_queue = nwaiting; + } + } + } + } + } + } + + // if no space (or not a carrier!) wave sender off: + if (!deck || !same_rgn || squadron < 0 || slot < 0) { + RadioMessage* wave_off = new(__FILE__,__LINE__) RadioMessage(inbound, ship, RadioMessage::NACK); + if (!hangar) + wave_off->SetInfo(Game::GetText("RadioHandler.no-hangar")); + + else if (!same_rgn) { + char info[256]; + sprintf(info, Game::GetText("RadioHandler.too-far-away").data(), ship->GetRegion()->Name()); + wave_off->SetInfo(info); + } + + else + wave_off->SetInfo(Game::GetText("RadioHandler.all-full")); + + RadioTraffic::Transmit(wave_off); + return false; + } + + // put sender in recovery queue, if not already there: + InboundSlot* inbound_slot = inbound->GetInbound(); + int sequence = 0; + + if (!inbound_slot) { + inbound_slot = new(__FILE__,__LINE__) InboundSlot(inbound, deck, squadron, slot); + sequence = deck->Inbound(inbound_slot); + } + else { + sequence = inbound_slot->Index(); + } + + // inform sender of status: + RadioMessage* approach = new(__FILE__,__LINE__) RadioMessage(inbound, ship, RadioMessage::CALL_APPROACH); + + if (inbound_slot->Cleared()) { + char info[256]; + sprintf(info, Game::GetText("RadioHandler.cleared").data(), deck->Name()); + approach->SetInfo(info); + } + else if (sequence) { + char info[256]; + sprintf(info, Game::GetText("RadioHandler.sequenced").data(), sequence, deck->Name()); + approach->SetInfo(info); + } + + RadioTraffic::Transmit(approach); + + return false; +} + +bool +RadioHandler::Picture(RadioMessage* msg, Ship* ship) +{ + if (!ship) return false; + + // try to find some enemy fighters in the area: + Ship* tgt = 0; + double range = 1e9; + + ListIter iter = ship->ContactList(); + while (++iter) { + Contact* c = iter.value(); + int iff = c->GetIFF(ship); + Ship* s = c->GetShip(); + + if (s && s->IsDropship() && s->IsHostileTo(ship)) { + double s_range = Point(msg->Sender()->Location() - s->Location()).length(); + if (!tgt || s_range < range) { + tgt = s; + range = s_range; + } + } + } + + // found some: + if (tgt) { + Element* sender = msg->Sender()->GetElement(); + Element* tgt_elem = tgt->GetElement(); + RadioMessage* response = new(__FILE__,__LINE__) RadioMessage(sender, ship, RadioMessage::ATTACK); + + if (tgt_elem) { + for (int i = 1; i <= tgt_elem->NumShips(); i++) + response->AddTarget(tgt_elem->GetShip(i)); + } + else { + response->AddTarget(tgt); + } + + RadioTraffic::Transmit(response); + } + + // nobody worth killin': + else { + Ship* sender = (Ship*) msg->Sender(); // cast-away const + RadioMessage* response = new(__FILE__,__LINE__) RadioMessage(sender, ship, RadioMessage::PICTURE); + RadioTraffic::Transmit(response); + } + + return false; +} + +bool +RadioHandler::Support(RadioMessage* msg, Ship* ship) +{ + if (!ship) return false; + + // try to find some fighters with time on their hands... + Element* help = 0; + Element* cmdr = ship->GetElement(); + Element* baby = msg->Sender()->GetElement(); + SimRegion* rgn = msg->Sender()->GetRegion(); + + for (int i = 0; i < rgn->Ships().size(); i++) { + Ship* s = rgn->Ships().at(i); + Element* e = s->GetElement(); + + if (e && s->IsDropship() && + e->Type() == Mission::PATROL && + e != baby && + cmdr->CanCommand(e) && + s->GetRadioOrders()->Action() == RadioMessage::NONE) { + help = e; + break; + } + } + + // found some: + if (help) { + RadioMessage* escort = new(__FILE__,__LINE__) RadioMessage(help, ship, RadioMessage::ESCORT); + escort->TargetList().append(msg->Sender()); + RadioTraffic::Transmit(escort); + + Text ok = Game::GetText("RadioHandler.help-enroute"); + Ship* sender = (Ship*) msg->Sender(); // cast-away const + RadioMessage* response = new(__FILE__,__LINE__) RadioMessage(sender, ship, RadioMessage::ACK); + response->SetInfo(ok); + RadioTraffic::Transmit(response); + } + + // no help in sight: + else { + Text nope = Game::GetText("RadioHandler.no-help-for-you"); + Ship* sender = (Ship*) msg->Sender(); // cast-away const + RadioMessage* response = new(__FILE__,__LINE__) RadioMessage(sender, ship, RadioMessage::NACK); + response->SetInfo(nope); + RadioTraffic::Transmit(response); + } + + return false; +} + +// +----------------------------------------------------------------------+ + +void +RadioHandler::AcknowledgeMessage(RadioMessage* msg, Ship* s) +{ + if (s && msg && msg->Sender() && msg->Action()) { + if (msg->Action() >= RadioMessage::ACK && msg->Action() <= RadioMessage::NACK) + return; // nothing to say here + + Ship* sender = (Ship*) msg->Sender(); // cast-away const + RadioMessage* ack = new(__FILE__,__LINE__) RadioMessage(sender, s, RadioMessage::ACK); + RadioTraffic::Transmit(ack); + } +} + diff --git a/Stars45/RadioHandler.h b/Stars45/RadioHandler.h new file mode 100644 index 0000000..151514e --- /dev/null +++ b/Stars45/RadioHandler.h @@ -0,0 +1,53 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: RadioHandler.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + RadioHandler (radio comms) class declaration +*/ + +#ifndef RadioHandler_h +#define RadioHandler_h + +#include "Types.h" +#include "Geometry.h" +#include "SimObject.h" + +// +--------------------------------------------------------------------+ + +class RadioMessage; +class Ship; + +// +--------------------------------------------------------------------+ + +class RadioHandler +{ +public: + RadioHandler(); + virtual ~RadioHandler(); + + virtual bool ProcessMessage(RadioMessage* msg, Ship* s); + virtual void AcknowledgeMessage(RadioMessage* msg, Ship* s); + +protected: + virtual bool IsOrder(int action); + virtual bool ProcessMessageOrders(RadioMessage* msg, Ship* s); + virtual bool ProcessMessageAction(RadioMessage* msg, Ship* s); + + virtual bool Inbound(RadioMessage* msg, Ship* s); + virtual bool Picture(RadioMessage* msg, Ship* s); + virtual bool Support(RadioMessage* msg, Ship* s); + virtual bool SkipNavpoint(RadioMessage* msg, Ship* s); + virtual bool LaunchProbe(RadioMessage* msg, Ship* s); +}; + +// +--------------------------------------------------------------------+ + +#endif RadioHandler_h + diff --git a/Stars45/RadioMessage.cpp b/Stars45/RadioMessage.cpp new file mode 100644 index 0000000..4072897 --- /dev/null +++ b/Stars45/RadioMessage.cpp @@ -0,0 +1,165 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: RadioMessage.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Radio communication message class implementation +*/ + +#include "MemDebug.h" +#include "RadioMessage.h" +#include "Ship.h" +#include "Text.h" + +// +----------------------------------------------------------------------+ + +RadioMessage::RadioMessage(Ship* dst, const Ship* s, int a) + : dst_ship(dst), dst_elem(0), sender(s), action(a), channel(0) +{ + if (s) + channel = s->GetIFF(); +} + +RadioMessage::RadioMessage(Element* dst, const Ship* s, int a) + : dst_ship(0), dst_elem(dst), sender(s), action(a), channel(0) +{ + if (s) + channel = s->GetIFF(); +} + +RadioMessage::RadioMessage(const RadioMessage& rm) + : dst_ship(rm.dst_ship), dst_elem(rm.dst_elem), + sender(rm.sender), action(rm.action), channel(rm.channel), + info(rm.info), location(rm.location) +{ + if (rm.target_list.size() > 0) { + for (int i = 0; i < rm.target_list.size(); i++) { + SimObject* obj = rm.target_list.at(i); + target_list.append(obj); + } + } +} + +RadioMessage::~RadioMessage() +{ } + +// +----------------------------------------------------------------------+ + +const char* +RadioMessage::ActionName(int a) +{ + if (a == ACK) { + int coin = rand(); + if (coin < 10000) return "Acknowledged"; + if (coin < 17000) return "Roger that"; + if (coin < 20000) return "Understood"; + if (coin < 22000) return "Copy that"; + return "Affirmative"; + } + + if (a == DISTRESS) { + int coin = rand(); + if (coin < 15000) return "Mayday! Mayday!"; + if (coin < 18000) return "She's breaking up!"; + if (coin < 21000) return "Checking out!"; + return "We're going down!"; + } + + if (a == WARN_ACCIDENT) { + int coin = rand(); + if (coin < 15000) return "Check your fire!"; + if (coin < 18000) return "Watch it!"; + if (coin < 21000) return "Hey! We're on your side!"; + return "Confirm your targets!"; + } + + if (a == WARN_TARGETED) { + int coin = rand(); + if (coin < 15000) return "Break off immediately!"; + if (coin < 20000) return "Buddy spike!"; + return "Abort! Abort!"; + } + + switch (a) { + case NONE: return ""; + + case NACK: return "Negative, Unable"; + + case ATTACK: return "Engage"; + case ESCORT: return "Escort"; + case BRACKET: return "Bracket"; + case IDENTIFY: return "Identify"; + + case COVER_ME: return "Cover Me"; + case MOVE_PATROL: return "Vector"; + case SKIP_NAVPOINT: return "Skip Navpoint"; + case RESUME_MISSION: return "Resume Mission"; + case RTB: return "Return to Base"; + case DOCK_WITH: return "Dock With"; + case QUANTUM_TO: return "Jump to"; + case FARCAST_TO: return "Farcast to"; + + case GO_DIAMOND: return "Goto Diamond Formation"; + case GO_SPREAD: return "Goto Spread Formation"; + case GO_BOX: return "Goto Box Formation"; + case GO_TRAIL: return "Goto Trail Formation"; + + case WEP_FREE: return "Break and Attack"; + case WEP_HOLD: return "Hold All Weapons"; + case FORM_UP: return "Return to Formation"; + case SAY_POSITION: return "Say Your Position"; + + case LAUNCH_PROBE: return "Launch Probe"; + case GO_EMCON1: return "Goto EMCON 1"; + case GO_EMCON2: return "Goto EMCON 2"; + case GO_EMCON3: return "Goto EMCON 3"; + + case REQUEST_PICTURE: return "Request Picture"; + case REQUEST_SUPPORT: return "Request Support"; + case PICTURE: return "Picture is clear"; + + case CALL_INBOUND: return "Calling Inbound"; + case CALL_APPROACH: return "Roger your approach"; + case CALL_CLEARANCE: return "You have clearance"; + case CALL_FINALS: return "On final approach"; + case CALL_WAVE_OFF: return "Wave off - Runway is closed"; + + case DECLARE_ROGUE: return "Prepare to be destroyed!"; + + case CALL_ENGAGING: return "Engaging"; + case FOX_1: return "Fox One!"; + case FOX_2: return "Fox Two!"; + case FOX_3: return "Fox Three!"; + case SPLASH_1: return "Splash One!"; + case SPLASH_2: return "Splash Two!"; + case SPLASH_3: return "Splash Three!"; + case SPLASH_4: return "Splash Four!"; + case SPLASH_5: return "Target Destroyed!"; + case SPLASH_6: return "Enemy Destroyed!"; + case SPLASH_7: return "Confirmed Kill!"; + case BREAK_ORBIT: return "Breaking Orbit"; + case MAKE_ORBIT: return "Heading for Orbit"; + case QUANTUM_JUMP: return "Going Quantum"; + + default: return "Unknown"; + } +} + +// +----------------------------------------------------------------------+ + +void +RadioMessage::AddTarget(SimObject* obj) +{ + if (obj && !target_list.contains(obj)) { + target_list.append(obj); + } +} + + + diff --git a/Stars45/RadioMessage.h b/Stars45/RadioMessage.h new file mode 100644 index 0000000..93d010e --- /dev/null +++ b/Stars45/RadioMessage.h @@ -0,0 +1,155 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: RadioMessage.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + RadioMessage (radio comms) class declaration +*/ + +#ifndef RadioMessage_h +#define RadioMessage_h + +#include "Types.h" +#include "Geometry.h" +#include "SimObject.h" +#include "Instruction.h" +#include "List.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class Element; +class Ship; +class SimObject; + +// +--------------------------------------------------------------------+ + +class RadioMessage +{ +public: + enum ACTION + { + NONE = 0, + + DOCK_WITH = Instruction::DOCK, + RTB = Instruction::RTB, + QUANTUM_TO = Instruction::NUM_ACTIONS, + FARCAST_TO, + + // protocol: + ACK, + NACK, + + // target mgt: + ATTACK, + ESCORT, + BRACKET, + IDENTIFY, + + // combat mgt: + COVER_ME, + WEP_FREE, + WEP_HOLD, + FORM_UP, // alias for wep_hold + SAY_POSITION, + + // sensor mgt: + LAUNCH_PROBE, + GO_EMCON1, + GO_EMCON2, + GO_EMCON3, + + // formation mgt: + GO_DIAMOND, + GO_SPREAD, + GO_BOX, + GO_TRAIL, + + // mission mgt: + MOVE_PATROL, + SKIP_NAVPOINT, + RESUME_MISSION, + + // misc announcements: + CALL_ENGAGING, + FOX_1, + FOX_2, + FOX_3, + SPLASH_1, + SPLASH_2, + SPLASH_3, + SPLASH_4, + SPLASH_5, // target destroyed + SPLASH_6, // enemy destroyed + SPLASH_7, // confirmed kill + DISTRESS, + BREAK_ORBIT, + MAKE_ORBIT, + QUANTUM_JUMP, + + // friendly fire: + WARN_ACCIDENT, + WARN_TARGETED, + DECLARE_ROGUE, + + // support: + PICTURE, + REQUEST_PICTURE, + REQUEST_SUPPORT, + + // traffic control: + CALL_INBOUND, + CALL_APPROACH, + CALL_CLEARANCE, + CALL_FINALS, + CALL_WAVE_OFF, + + NUM_ACTIONS + }; + + RadioMessage(Ship* dst, const Ship* sender, int action); + RadioMessage(Element* dst, const Ship* sender, int action); + RadioMessage(const RadioMessage& rm); + virtual ~RadioMessage(); + + // accessors: + static const char* ActionName(int a); + + const Ship* Sender() const { return sender; } + Ship* DestinationShip() const { return dst_ship; } + Element* DestinationElem() const { return dst_elem; } + int Action() const { return action; } + List& TargetList() { return target_list; } + const Point& Location() const { return location; } + const Text& Info() const { return info; } + int Channel() const { return channel; } + + // mutators: + void SetDestinationShip(Ship* s) { dst_ship = s; } + void SetDestinationElem(Element* e) { dst_elem = e; } + void AddTarget(SimObject* s); + void SetLocation(const Point& l) { location = l; } + void SetInfo(Text msg) { info = msg; } + void SetChannel(int c) { channel = c; } + +protected: + const Ship* sender; + Ship* dst_ship; + Element* dst_elem; + int action; + List target_list; + Point location; + Text info; + int channel; +}; + +// +--------------------------------------------------------------------+ + +#endif RadioMessage_h + diff --git a/Stars45/RadioTraffic.cpp b/Stars45/RadioTraffic.cpp new file mode 100644 index 0000000..b0c558e --- /dev/null +++ b/Stars45/RadioTraffic.cpp @@ -0,0 +1,391 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: RadioTraffic.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Radio message handler class implementation +*/ + +#include "MemDebug.h" +#include "RadioTraffic.h" +#include "RadioMessage.h" +#include "RadioView.h" +#include "RadioVox.h" +#include "Instruction.h" +#include "Ship.h" +#include "Contact.h" +#include "Element.h" +#include "Sim.h" +#include "NetGame.h" +#include "NetData.h" + +#include "Game.h" +#include "Text.h" + +// +----------------------------------------------------------------------+ + +RadioTraffic* RadioTraffic::radio_traffic = 0; + +// +----------------------------------------------------------------------+ + +RadioTraffic::RadioTraffic() +{ + radio_traffic = this; +} + +RadioTraffic::~RadioTraffic() +{ + traffic.destroy(); +} + +// +----------------------------------------------------------------------+ + +void +RadioTraffic::Initialize() +{ + if (!radio_traffic) + radio_traffic = new(__FILE__,__LINE__) RadioTraffic; +} + +void +RadioTraffic::Close() +{ + delete radio_traffic; + radio_traffic = 0; +} + +// +--------------------------------------------------------------------+ + +void +RadioTraffic::SendQuickMessage(Ship* ship, int action) +{ + if (!ship) return; + + Element* elem = ship->GetElement(); + + if (elem) { + if (action >= RadioMessage::REQUEST_PICTURE) { + Ship* controller = ship->GetController(); + + if (controller && !ship->IsStarship()) { + RadioMessage* msg = new(__FILE__,__LINE__) RadioMessage(controller, ship, action); + Transmit(msg); + } + } + else if (action >= RadioMessage::SPLASH_1 && action <= RadioMessage::DISTRESS) { + RadioMessage* msg = new(__FILE__,__LINE__) RadioMessage((Element*) 0, ship, action); + Transmit(msg); + } + else { + RadioMessage* msg = new(__FILE__,__LINE__) RadioMessage(elem, ship, action); + + if (action == RadioMessage::ATTACK || action == RadioMessage::ESCORT) + msg->AddTarget(ship->GetTarget()); + + Transmit(msg); + } + } +} + +// +----------------------------------------------------------------------+ + +void +RadioTraffic::Transmit(RadioMessage* msg) +{ + if (msg && radio_traffic) { + NetGame* net_game = NetGame::GetInstance(); + if (net_game) { + NetCommMsg net_msg; + net_msg.SetRadioMessage(msg); + net_game->SendData(&net_msg); + } + + radio_traffic->SendMessage(msg); + } +} + +// +----------------------------------------------------------------------+ + +void +RadioTraffic::SendMessage(RadioMessage* msg) +{ + if (!msg) return; + + Sim* sim = Sim::GetSim(); + Ship* player = sim->GetPlayerShip(); + int iff = 0; + + if (player) + iff = player->GetIFF(); + + if (msg->DestinationShip()) { + traffic.append(msg); + + if (msg->Channel() == 0 || msg->Channel() == iff) + DisplayMessage(msg); + + if (!NetGame::IsNetGameClient()) + msg->DestinationShip()->HandleRadioMessage(msg); + } + + else if (msg->DestinationElem()) { + traffic.append(msg); + + if (msg->Channel() == 0 || msg->Channel() == iff) + DisplayMessage(msg); + + if (!NetGame::IsNetGameClient()) + msg->DestinationElem()->HandleRadioMessage(msg); + } + + else { + if (msg->Channel() == 0 || msg->Channel() == iff) + DisplayMessage(msg); + + delete msg; + } +} + +// +----------------------------------------------------------------------+ + +Text +RadioTraffic::TranslateVox(const char* phrase) +{ + Text vox = "vox."; + vox += phrase; + vox.toLower(); + vox = Game::GetText(vox); + + if (vox.contains("vox.")) // failed to translate + return Text(phrase); // return the original text + + return vox; +} + +void +RadioTraffic::DisplayMessage(RadioMessage* msg) +{ + if (!msg) return; + + char txt_buf[256]; txt_buf[0] = 0; + char msg_buf[128]; msg_buf[0] = 0; + char src_buf[64]; src_buf[0] = 0; + char dst_buf[64]; dst_buf[0] = 0; + char act_buf[64]; act_buf[0] = 0; + int vox_channel = 0; + + Ship* dst_ship = msg->DestinationShip(); + Element* dst_elem = msg->DestinationElem(); + + // BUILD SRC AND DST BUFFERS ------------------- + + if (msg->Sender()) { + const Ship* sender = msg->Sender(); + + // orders to self? + if (dst_elem && dst_elem->NumShips() == 1 && dst_elem->GetShip(1) == sender) { + if (msg->Action() >= RadioMessage::CALL_ENGAGING) { + sprintf(src_buf, "%s", sender->Name()); + + if (sender->IsStarship()) + vox_channel = (sender->Identity()%3) + 5; + } + } + + // orders to other ships: + else { + if (sender->IsStarship()) { + vox_channel = (sender->Identity()%3) + 5; + } + else { + vox_channel = sender->GetElementIndex(); + } + + if (msg->Action() >= RadioMessage::CALL_ENGAGING) { + sprintf(src_buf, "%s", sender->Name()); + } + else { + sprintf(src_buf, "This is %s", sender->Name()); + + if (dst_ship) { + // internal announcement + if (dst_ship->GetElement() == sender->GetElement()) { + dst_elem = sender->GetElement(); + int index = sender->GetElementIndex(); + + if (index > 1 && dst_elem) { + sprintf(dst_buf, "%s Leader", (const char*) dst_elem->Name()); + sprintf(src_buf, "this is %s %d", (const char*) dst_elem->Name(), index); + } + else { + sprintf(src_buf, "this is %s leader", (const char*) dst_elem->Name()); + } + } + + else { + strcpy(dst_buf, (const char*) dst_ship->Name()); + src_buf[0] = tolower(src_buf[0]); + } + } + + else if (dst_elem) { + // flight + if (dst_elem->NumShips() > 1) { + sprintf(dst_buf, "%s Flight", (const char*) dst_elem->Name()); + + // internal announcement + if (sender->GetElement() == dst_elem) { + int index = sender->GetElementIndex(); + + if (index > 1) { + sprintf(dst_buf, "%s Leader", (const char*) dst_elem->Name()); + sprintf(src_buf, "this is %s %d", (const char*) dst_elem->Name(), index); + } + else { + sprintf(src_buf, "this is %s leader", (const char*) dst_elem->Name()); + } + } + } + + // solo + else { + strcpy(dst_buf, (const char*) dst_elem->Name()); + src_buf[0] = tolower(src_buf[0]); + } + } + } + } + } + + // BUILD ACTION AND TARGET BUFFERS ------------------- + + SimObject* target = 0; + + strcpy(act_buf, RadioMessage::ActionName(msg->Action())); + + if (msg->TargetList().size() > 0) + target = msg->TargetList()[0]; + + if (msg->Action() == RadioMessage::ACK || + msg->Action() == RadioMessage::NACK) { + + if (dst_ship == msg->Sender()) { + src_buf[0] = 0; + dst_buf[0] = 0; + + if (msg->Action() == RadioMessage::ACK) + sprintf(msg_buf, "%s.", TranslateVox("Acknowledged").data()); + else + sprintf(msg_buf, "%s.", TranslateVox("Unable").data()); + } + else if (msg->Sender()) { + dst_buf[0] = 0; + + if (msg->Info().length()) { + sprintf(msg_buf, "%s. %s", + TranslateVox(act_buf).data(), + (const char*) msg->Info()); + } + else { + sprintf(msg_buf, "%s.", TranslateVox(act_buf).data()); + } + } + else { + if (msg->Info().length()) { + sprintf(msg_buf, "%s. %s", + TranslateVox(act_buf).data(), + (const char*) msg->Info()); + } + else { + sprintf(msg_buf, "%s.", TranslateVox(act_buf).data()); + } + } + } + + else if (msg->Action() == RadioMessage::MOVE_PATROL) { + sprintf(msg_buf, TranslateVox("Move patrol.").data()); + } + + else if (target && dst_ship && msg->Sender()) { + Contact* c = msg->Sender()->FindContact(target); + + if (c && c->GetIFF(msg->Sender()) > 10) { + sprintf(msg_buf, "%s %s.", TranslateVox(act_buf).data(), TranslateVox("unknown contact").data()); + } + + else { + sprintf(msg_buf, "%s %s.", + TranslateVox(act_buf).data(), + target->Name()); + } + } + + else if (target) { + sprintf(msg_buf, "%s %s.", + TranslateVox(act_buf).data(), + target->Name()); + } + + else if (msg->Info().length()) { + sprintf(msg_buf, "%s %s", + TranslateVox(act_buf).data(), + (const char*) msg->Info()); + } + + else { + strcpy(msg_buf, TranslateVox(act_buf).data()); + } + + char last_char = msg_buf[strlen(msg_buf)-1]; + if (last_char != '!' && last_char != '.' && last_char != '?') + strcat(msg_buf, "."); + + // final format: + if (dst_buf[0] && src_buf[0]) { + sprintf(txt_buf, "%s %s. %s", TranslateVox(dst_buf).data(), TranslateVox(src_buf).data(), msg_buf); + txt_buf[0] = toupper(txt_buf[0]); + } + + else if (src_buf[0]) { + sprintf(txt_buf, "%s. %s", TranslateVox(src_buf).data(), msg_buf); + txt_buf[0] = toupper(txt_buf[0]); + } + + else if (dst_buf[0]) { + sprintf(txt_buf, "%s %s", TranslateVox(dst_buf).data(), msg_buf); + txt_buf[0] = toupper(txt_buf[0]); + } + + else { + strcpy(txt_buf, msg_buf); + } + + // vox: + const char* path[8] = { "1", "1", "2", "3", "4", "5", "6", "7" }; + + RadioVox* vox = new(__FILE__,__LINE__) RadioVox(vox_channel, path[vox_channel], txt_buf); + + vox->AddPhrase(dst_buf); + vox->AddPhrase(src_buf); + vox->AddPhrase(act_buf); + + if (vox && !vox->Start()) { + RadioView::Message(txt_buf); + delete vox; + } +} + +// +----------------------------------------------------------------------+ + +void +RadioTraffic::DiscardMessages() +{ + if (radio_traffic) + radio_traffic->traffic.destroy(); +} diff --git a/Stars45/RadioTraffic.h b/Stars45/RadioTraffic.h new file mode 100644 index 0000000..93a925c --- /dev/null +++ b/Stars45/RadioTraffic.h @@ -0,0 +1,65 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: RadioTraffic.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + RadioTraffic maintains a history of all messages sent between ships + in the simulation. This class also handles displaying relevant + traffic to the player. +*/ + +#ifndef RadioTraffic_h +#define RadioTraffic_h + +#include "Types.h" +#include "Geometry.h" +#include "SimObject.h" +#include "List.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class Element; +class RadioMessage; +class Ship; +class SimObject; + +// +--------------------------------------------------------------------+ + +class RadioTraffic +{ +public: + RadioTraffic(); + ~RadioTraffic(); + + // accessors: + static void Initialize(); + static void Close(); + + static RadioTraffic* GetInstance() { return radio_traffic; } + + static void SendQuickMessage(Ship* ship, int msg); + static void Transmit(RadioMessage* msg); + static void DiscardMessages(); + static Text TranslateVox(const char* phrase); + + void SendMessage(RadioMessage* msg); + void DisplayMessage(RadioMessage* msg); + + +protected: + List traffic; + + static RadioTraffic* radio_traffic; +}; + +// +--------------------------------------------------------------------+ + +#endif RadioTraffic_h + diff --git a/Stars45/RadioView.cpp b/Stars45/RadioView.cpp new file mode 100644 index 0000000..cdb5f84 --- /dev/null +++ b/Stars45/RadioView.cpp @@ -0,0 +1,640 @@ +/* Project Starshatter 5.0 + Destroyer Studios LLC + Copyright © 1997-2007. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: RadioView.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + View class for Radio Communications HUD Overlay +*/ + +#include "MemDebug.h" +#include "RadioView.h" +#include "RadioMessage.h" +#include "RadioTraffic.h" +#include "QuantumView.h" +#include "HUDView.h" +#include "Ship.h" +#include "Element.h" +#include "Sim.h" +#include "Starshatter.h" + +#include "CameraView.h" +#include "Color.h" +#include "Window.h" +#include "Video.h" +#include "Screen.h" +#include "DataLoader.h" +#include "Scene.h" +#include "FontMgr.h" +#include "FormatUtil.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "Game.h" +#include "Menu.h" + +static Color hud_color = Color::Black; +static Color txt_color = Color::White; + +// +====================================================================+ +// +// RADIO COMMUNICATIONS MENU: +// + +static Menu* fighter_menu = 0; +static Menu* starship_menu = 0; +static Menu* target_menu = 0; +static Menu* combat_menu = 0; +static Menu* formation_menu = 0; +static Menu* sensors_menu = 0; +static Menu* mission_menu = 0; +static Menu* wing_menu = 0; +static Menu* elem_menu = 0; +static Menu* control_menu = 0; + +static int starship_page = 0; +static int num_pages = 0; +const int PAGE_SIZE = 9; + +static MenuHistory history; + +void +RadioView::Initialize() +{ + static int initialized = 0; + if (initialized) return; + + target_menu = new(__FILE__,__LINE__) Menu(Game::GetText("RadioView.menu.TARGET")); + target_menu->AddItem(Game::GetText("RadioView.item.attack"), RadioMessage::ATTACK); + target_menu->AddItem(Game::GetText("RadioView.item.bracket"), RadioMessage::BRACKET); + target_menu->AddItem(Game::GetText("RadioView.item.escort"), RadioMessage::ESCORT); + + combat_menu = new(__FILE__,__LINE__) Menu(Game::GetText("RadioView.menu.COMBAT")); + combat_menu->AddItem(Game::GetText("RadioView.item.cover"), RadioMessage::COVER_ME); + combat_menu->AddItem(Game::GetText("RadioView.item.break-attack"), RadioMessage::WEP_FREE); + combat_menu->AddItem(Game::GetText("RadioView.item.form-up"), RadioMessage::FORM_UP); + + formation_menu = new(__FILE__,__LINE__) Menu(Game::GetText("RadioView.menu.FORMATION")); + formation_menu->AddItem(Game::GetText("RadioView.item.diamond"), RadioMessage::GO_DIAMOND); + formation_menu->AddItem(Game::GetText("RadioView.item.spread"), RadioMessage::GO_SPREAD); + formation_menu->AddItem(Game::GetText("RadioView.item.box"), RadioMessage::GO_BOX); + formation_menu->AddItem(Game::GetText("RadioView.item.trail"), RadioMessage::GO_TRAIL); + + sensors_menu = new(__FILE__,__LINE__) Menu(Game::GetText("RadioView.menu.SENSORS")); + sensors_menu->AddItem(Game::GetText("RadioView.item.emcon-1"), RadioMessage::GO_EMCON1); + sensors_menu->AddItem(Game::GetText("RadioView.item.emcon-2"), RadioMessage::GO_EMCON2); + sensors_menu->AddItem(Game::GetText("RadioView.item.emcon-3"), RadioMessage::GO_EMCON3); + sensors_menu->AddItem(Game::GetText("RadioView.item.probe"), RadioMessage::LAUNCH_PROBE); + + mission_menu = new(__FILE__,__LINE__) Menu(Game::GetText("RadioView.menu.MISSION")); + mission_menu->AddItem(Game::GetText("RadioView.item.skip-navpt"), RadioMessage::SKIP_NAVPOINT); + mission_menu->AddItem(Game::GetText("RadioView.item.resume"), RadioMessage::RESUME_MISSION); + mission_menu->AddItem(Game::GetText("RadioView.item.rtb"), RadioMessage::RTB); + + wing_menu = new(__FILE__,__LINE__) Menu(Game::GetText("RadioView.menu.WINGMAN")); + wing_menu->AddMenu(Game::GetText("RadioView.item.target"), target_menu); + wing_menu->AddMenu(Game::GetText("RadioView.item.combat"), combat_menu); + wing_menu->AddMenu(Game::GetText("RadioView.item.formation"), formation_menu); + wing_menu->AddMenu(Game::GetText("RadioView.item.mission"), mission_menu); + wing_menu->AddMenu(Game::GetText("RadioView.item.sensors"), sensors_menu); + + elem_menu = new(__FILE__,__LINE__) Menu(Game::GetText("RadioView.menu.ELEMENT")); + elem_menu->AddMenu(Game::GetText("RadioView.item.target"), target_menu); + elem_menu->AddMenu(Game::GetText("RadioView.item.combat"), combat_menu); + elem_menu->AddMenu(Game::GetText("RadioView.item.formation"), formation_menu); + elem_menu->AddMenu(Game::GetText("RadioView.item.mission"), mission_menu); + elem_menu->AddMenu(Game::GetText("RadioView.item.sensors"), sensors_menu); + + control_menu = new(__FILE__,__LINE__) Menu(Game::GetText("RadioView.menu.CONTROL")); + control_menu->AddItem(Game::GetText("RadioView.item.picture"), RadioMessage::REQUEST_PICTURE); + control_menu->AddItem(Game::GetText("RadioView.item.backup"), RadioMessage::REQUEST_SUPPORT); + control_menu->AddItem(Game::GetText("RadioView.item.call-inbound"), RadioMessage::CALL_INBOUND); + control_menu->AddItem(Game::GetText("RadioView.item.call-finals"), RadioMessage::CALL_FINALS); + + fighter_menu = new(__FILE__,__LINE__) Menu(Game::GetText("RadioView.menu.RADIO")); + fighter_menu->AddMenu(Game::GetText("RadioView.item.wingman"), wing_menu); + fighter_menu->AddMenu(Game::GetText("RadioView.item.element"), elem_menu); + fighter_menu->AddMenu(Game::GetText("RadioView.item.control"), control_menu); + + starship_menu = new(__FILE__,__LINE__) Menu(Game::GetText("RadioView.menu.RADIO")); + + initialized = 1; +} + +void +RadioView::Close() +{ + history.Clear(); + + delete fighter_menu; + delete starship_menu; + delete target_menu; + delete combat_menu; + delete formation_menu; + delete sensors_menu; + delete mission_menu; + delete wing_menu; + delete elem_menu; + delete control_menu; +} + +static bool TargetRequired(const MenuItem* item) +{ + if (item) { + switch (item->GetData()) { + case RadioMessage::ATTACK: + case RadioMessage::BRACKET: + case RadioMessage::ESCORT: + return true; + + default: + if (item->GetData() == (DWORD) target_menu) + return true; + } + } + + return false; +} + +// +====================================================================+ + +RadioView* RadioView::radio_view = 0; +ThreadSync RadioView::sync; + +RadioView::RadioView(Window* c) + : View(c), sim(0), ship(0), font(0), dst_elem(0) +{ + radio_view = this; + sim = Sim::GetSim(); + + width = window->Width(); + height = window->Height(); + xcenter = (width / 2.0) - 0.5; + ycenter = (height / 2.0) + 0.5; + font = FontMgr::Find("HUD"); + + HUDView* hud = HUDView::GetInstance(); + if (hud) + SetColor(hud->GetTextColor()); + + for (int i = 0; i < MAX_MSG; i++) + msg_time[i] = 0; +} + +RadioView::~RadioView() +{ + radio_view = 0; +} + +void +RadioView::OnWindowMove() +{ + width = window->Width(); + height = window->Height(); + xcenter = (width / 2.0) - 0.5; + ycenter = (height / 2.0) + 0.5; +} + +// +--------------------------------------------------------------------+ + +bool +RadioView::Update(SimObject* obj) +{ + if (obj == ship) { + ship = 0; + history.Clear(); + } + + return SimObserver::Update(obj); +} + +const char* +RadioView::GetObserverName() const +{ + return "RadioView"; +} + +// +--------------------------------------------------------------------+ + +void +RadioView::Refresh() +{ + sim = Sim::GetSim(); + + if (!font) + return; + + font->SetColor(txt_color); + font->SetAlpha(1); + + if (sim && ship != sim->GetPlayerShip()) { + ship = sim->GetPlayerShip(); + history.Clear(); + + if (ship) { + if (ship->Life() == 0 || ship->IsDying() || ship->IsDead()) { + ship = 0; + } + else { + Observe(ship); + } + } + } + + QuantumView* qv = QuantumView::GetInstance(); + + if (!qv || !qv->IsMenuShown()) { + Menu* menu = history.GetCurrent(); + + if (menu) { + Rect menu_rect(width-115, 10, 115, 12); + + font->DrawText(menu->GetTitle(), 0, menu_rect, DT_CENTER); + menu_rect.y += 15; + + ListIter item = menu->GetItems(); + while (++item) { + if (ship->GetEMCON() < 2 || + (TargetRequired(item.value()) && !ship->GetTarget()) || + item->GetText().contains("KIA")) { + item->SetEnabled(false); + font->SetAlpha(0.35); + } + else { + item->SetEnabled(true); + font->SetAlpha(1); + } + + font->DrawText(item->GetText(), 0, menu_rect, DT_LEFT); + menu_rect.y += 10; + } + } + } + + int message_queue_empty = true; + + // age messages: + for (int i = 0; i < MAX_MSG; i++) { + if (msg_time[i] > 0) { + msg_time[i] -= Game::GUITime(); + + if (msg_time[i] <= 0) { + msg_time[i] = 0; + msg_text[i] = ""; + } + + message_queue_empty = false; + } + } + + if (!message_queue_empty) { + // advance message pipeline: + for (i = 0; i < MAX_MSG; i++) { + if (msg_time[0] == 0) { + for (int j = 0; j < MAX_MSG-1; j++) { + msg_time[j] = msg_time[j+1]; + msg_text[j] = msg_text[j+1]; + } + + msg_time[MAX_MSG-1] = 0; + msg_text[MAX_MSG-1] = ""; + } + } + + bool hud_off = false; + + if (HUDView::GetInstance()) + hud_off = (HUDView::GetInstance()->GetHUDMode() == HUDView::HUD_MODE_OFF); + + // draw messages: + if (!hud_off) { + for (i = 0; i < MAX_MSG; i++) { + if (msg_time[i] > 0) { + Rect msg_rect(0, 95 + i*10, width, 12); + + if (msg_time[i] > 1) + font->SetAlpha(1); + else + font->SetAlpha(0.5 + 0.5*msg_time[i]); + + font->DrawText(msg_text[i].data(), msg_text[i].length(), msg_rect, DT_CENTER); + } + } + + font->SetAlpha(1); + } + } + + Starshatter* stars = Starshatter::GetInstance(); + if (stars && stars->GetChatMode()) { + Text chat; + + switch (stars->GetChatMode()) { + case 1: chat = "ALL: "; break; + case 2: chat = "TEAM: "; break; + case 3: chat = "WING: "; break; + case 4: chat = "UNIT: "; break; + } + + chat += stars->GetChatText(); + + Rect chat_rect(width/2 - 250, height-150, 500, 12); + font->DrawText(chat, 0, chat_rect, DT_LEFT); + + chat_rect.Inflate(2,2); + window->DrawRect(chat_rect, hud_color); + } +} + +// +--------------------------------------------------------------------+ + +void +RadioView::SendRadioMessage(Ship* ship, MenuItem* item) +{ + if (!ship || !item) return; + Element* elem = ship->GetElement(); + if (!elem) return; + + // check destination: + if (dst_elem) { + RadioMessage* msg = new(__FILE__,__LINE__) RadioMessage(dst_elem, ship, item->GetData()); + + if (TargetRequired(item)) + msg->AddTarget(ship->GetTarget()); + + RadioTraffic::Transmit(msg); + dst_elem = 0; + } + + else if (history.Find(Game::GetText("RadioView.menu.WINGMAN"))) { // wingman menu + int index = ship->GetElementIndex(); + int wing = 0; + + switch (index) { + case 1: wing = 2; break; + case 2: wing = 1; break; + case 3: wing = 4; break; + case 4: wing = 3; break; + } + + if (wing) { + Ship* dst = elem->GetShip(wing); + if (dst) { + RadioMessage* msg = new(__FILE__,__LINE__) RadioMessage(dst, ship, item->GetData()); + + if (TargetRequired(item)) + msg->AddTarget(ship->GetTarget()); + + RadioTraffic::Transmit(msg); + } + } + } + + else if (history.Find(Game::GetText("RadioView.menu.ELEMENT"))) { // element menu + RadioMessage* msg = new(__FILE__,__LINE__) RadioMessage(elem, ship, item->GetData()); + + if (TargetRequired(item)) + msg->AddTarget(ship->GetTarget()); + + RadioTraffic::Transmit(msg); + } + + else if (history.Find(Game::GetText("RadioView.menu.CONTROL"))) { // control menu + RadioMessage* msg = 0; + Ship* controller = ship->GetController(); + + if (controller) { + msg = new(__FILE__,__LINE__) RadioMessage(controller, ship, item->GetData()); + RadioTraffic::Transmit(msg); + } + } +} + +// +--------------------------------------------------------------------+ + +void +RadioView::ExecFrame() +{ + HUDView* hud = HUDView::GetInstance(); + if (hud) { + if (txt_color != hud->GetTextColor()) { + txt_color = hud->GetTextColor(); + SetColor(txt_color); + } + + hud_color = hud->GetHUDColor(); + } + + static int current_key = 0; + + if (current_key > 0 && Keyboard::KeyDown(current_key)) + return; + + current_key = 0; + + Menu* menu = history.GetCurrent(); + if (menu) { + int max_items = menu->NumItems(); + + if (menu == starship_menu && Keyboard::KeyDown('0')) { + current_key = '0'; + if (++starship_page >= num_pages) + starship_page = 0; + + history.Pop(); + history.Push(GetRadioMenu(ship)); + } + else { + for (int i = 0; i < max_items; i++) { + if (Keyboard::KeyDown('1' + i)) { + current_key = '1' + i; + MenuItem* item = menu->GetItem(i); + if (item && item->GetEnabled()) { + if (item->GetSubmenu()) { + if (history.GetCurrent() == starship_menu) + dst_elem = (Element*) item->GetData(); + + history.Push(item->GetSubmenu()); + } + else { + // execute radio message: + SendRadioMessage(ship, item); + + // clear radio menu: + history.Clear(); + } + + break; + } + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +RadioView::SetColor(Color c) +{ + HUDView* hud = HUDView::GetInstance(); + + if (hud) { + hud_color = hud->GetHUDColor(); + txt_color = hud->GetTextColor(); + } + else { + hud_color = c; + txt_color = c; + } +} + +// +--------------------------------------------------------------------+ + +bool +RadioView::IsMenuShown() +{ + return history.GetCurrent() != 0; +} + +void +RadioView::ShowMenu() +{ + if (!ship) return; + + if (!history.GetCurrent()) { + history.Push(GetRadioMenu(ship)); + + for (int i = 0; i < 10; i++) { + if (Keyboard::KeyDown('1' + i)) { + // just need to clear the key down flag + // so we don't process old keystrokes + // as valid menu picks... + } + } + } +} + +void +RadioView::CloseMenu() +{ + history.Clear(); + dst_elem = 0; + starship_page = 0; + num_pages = 0; +} + +// +--------------------------------------------------------------------+ + +Menu* +RadioView::GetRadioMenu(Ship* s) +{ + dst_elem = 0; + + if (s && sim) { + if (s->IsStarship()) { + starship_menu->ClearItems(); + + int n = 0; + int page_offset = starship_page*PAGE_SIZE; + + ListIter elem = sim->GetElements(); + + if (num_pages == 0) { + while (++elem) { + if (elem->IsFinished() || elem->IsSquadron() || elem->IsStatic()) + continue; + + if (ship->GetIFF() == elem->GetIFF() && ship->GetElement() != elem.value()) + n++; + } + + num_pages = (n/PAGE_SIZE) + (n%PAGE_SIZE > 0); + n = 0; + elem.reset(); + } + + while (++elem) { + if (elem->IsFinished() || elem->IsSquadron() || elem->IsStatic()) + continue; + + if (ship->GetIFF() == elem->GetIFF() && ship->GetElement() != elem.value()) { + if (n >= page_offset && n < page_offset+PAGE_SIZE) { + char text[64]; + sprintf(text, "%d. %s", n+1 - page_offset, (const char*) elem->Name()); + + if (elem->IsActive()) { + starship_menu->AddMenu(text, elem_menu, (DWORD) elem.value()); + } + else { + strcat(text, " "); + strcat(text, Game::GetText("RadioView.item.not-avail").data()); + starship_menu->AddItem(text, 0, false); + } + } + n++; + } + } + + if (num_pages > 1) { + char text[64]; + sprintf(text, Game::GetText("RadioView.item.next-page").data(), starship_page + 1, num_pages); + starship_menu->AddItem(text); + } + + return starship_menu; + } + else if (s->IsDropship()) { + return fighter_menu; + } + } + + return 0; +} + +// +--------------------------------------------------------------------+ + +void +RadioView::Message(const char* msg) +{ + AutoThreadSync a(sync); + + if (radio_view) { + int index = -1; + + for (int i = 0; i < MAX_MSG; i++) { + if (radio_view->msg_time[i] <= 0) { + index = i; + break; + } + } + + // no space; advance pipeline: + if (index < 0) { + for (i = 0; i < MAX_MSG-1; i++) { + radio_view->msg_text[i] = radio_view->msg_text[i+1]; + radio_view->msg_time[i] = radio_view->msg_time[i+1]; + } + + index = MAX_MSG-1; + } + + radio_view->msg_text[index] = msg; + radio_view->msg_time[index] = 10; + } +} + +void +RadioView::ClearMessages() +{ + if (radio_view) { + for (int i = 0; i < MAX_MSG-1; i++) { + radio_view->msg_text[i] = Text(); + radio_view->msg_time[i] = 0; + } + } +} diff --git a/Stars45/RadioView.h b/Stars45/RadioView.h new file mode 100644 index 0000000..8bcdf03 --- /dev/null +++ b/Stars45/RadioView.h @@ -0,0 +1,87 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: RadioView.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + View class for Radio Communications HUD Overlay +*/ + +#ifndef RadioView_h +#define RadioView_h + +#include "Types.h" +#include "View.h" +#include "Color.h" +#include "SimObject.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class Font; +class Element; +class Ship; +class RadioMessage; +class CameraView; +class HUDView; +class Menu; +class MenuItem; + +// +--------------------------------------------------------------------+ + +class RadioView : public View, + public SimObserver +{ +public: + RadioView(Window* c); + virtual ~RadioView(); + + // Operations: + virtual void Refresh(); + virtual void OnWindowMove(); + virtual void ExecFrame(); + + virtual Menu* GetRadioMenu(Ship* ship); + virtual bool IsMenuShown(); + virtual void ShowMenu(); + virtual void CloseMenu(); + + static void Message(const char* msg); + static void ClearMessages(); + + virtual bool Update(SimObject* obj); + virtual const char* GetObserverName() const; + + static void SetColor(Color c); + + static void Initialize(); + static void Close(); + + static RadioView* GetInstance() { return radio_view; } + +protected: + void SendRadioMessage(Ship* ship, MenuItem* item); + + int width, height; + double xcenter, ycenter; + + Font* font; + Sim* sim; + Ship* ship; + Element* dst_elem; + + enum { MAX_MSG=6 }; + Text msg_text[MAX_MSG]; + double msg_time[MAX_MSG]; + + static RadioView* radio_view; + static ThreadSync sync; +}; + +#endif RadioView_h + diff --git a/Stars45/RadioVox.cpp b/Stars45/RadioVox.cpp new file mode 100644 index 0000000..6974dc0 --- /dev/null +++ b/Stars45/RadioVox.cpp @@ -0,0 +1,249 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: RadioVox.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + View class for Radio Communications HUD Overlay +*/ + +#include "MemDebug.h" +#include "RadioVox.h" +#include "RadioView.h" +#include "AudioConfig.h" + +#include "DataLoader.h" +#include "Game.h" +#include "Sound.h" +#include "ThreadSync.h" + +// +====================================================================+ +// +// RADIO VOX CONTROLLER: +// + +DWORD WINAPI VoxUpdateProc(LPVOID link); + +class RadioVoxController +{ +public: + enum { MAX_QUEUE = 5 }; + + RadioVoxController(); + ~RadioVoxController(); + + bool Add(RadioVox* vox); + void Update(); + DWORD UpdateThread(); + + bool shutdown; + HANDLE hthread; + List queue; + ThreadSync sync; +}; + +static RadioVoxController* controller = 0; + +// +--------------------------------------------------------------------+ + +RadioVoxController::RadioVoxController() + : hthread(0), shutdown(false) +{ + DWORD thread_id = 0; + hthread = CreateThread(0, 4096, VoxUpdateProc, + (LPVOID) this, 0, &thread_id); +} + +// +--------------------------------------------------------------------+ + +RadioVoxController::~RadioVoxController() +{ + shutdown = true; + + WaitForSingleObject(hthread, 500); + CloseHandle(hthread); + hthread = 0; + + queue.destroy(); +} + +// +--------------------------------------------------------------------+ + +DWORD WINAPI VoxUpdateProc(LPVOID link) +{ + RadioVoxController* controller = (RadioVoxController*) link; + + if (controller) + return controller->UpdateThread(); + + return (DWORD) E_POINTER; +} + +// +--------------------------------------------------------------------+ + +DWORD +RadioVoxController::UpdateThread() +{ + while (!shutdown) { + Update(); + Sleep(50); + } + + return 0; +} + +// +--------------------------------------------------------------------+ + +void +RadioVoxController::Update() +{ + AutoThreadSync a(sync); + + if (queue.size()) { + RadioVox* vox = queue.first(); + + if (!vox->Update()) + delete queue.removeIndex(0); + } +} + +bool +RadioVoxController::Add(RadioVox* vox) +{ + if (!vox || vox->sounds.isEmpty()) + return false; + + AutoThreadSync a(sync); + + if (queue.size() < MAX_QUEUE) { + queue.append(vox); + return true; + } + + return false; +} + +// +====================================================================+ +// +// RADIO VOX MESSAGE: +// + +void +RadioVox::Initialize() +{ + if (!controller) { + controller = new(__FILE__,__LINE__) RadioVoxController; + } +} + +void +RadioVox::Close() +{ + delete controller; + controller = 0; +} + +// +--------------------------------------------------------------------+ + +RadioVox::RadioVox(int n, const char* p, const char* m) + : path(p), message(m), index(0), channel(n) +{ +} + +RadioVox::~RadioVox() +{ + sounds.destroy(); +} + +// +--------------------------------------------------------------------+ + +bool +RadioVox::AddPhrase(const char* key) +{ + if (AudioConfig::VoxVolume() <= AudioConfig::Silence()) + return false; + + DataLoader* loader = DataLoader::GetLoader(); + if (!loader) + return false; + + if (key && *key) { + char datapath[256]; + char filename[256]; + + sprintf(datapath, "Vox/%s/", path.data()); + sprintf(filename, "%s.wav", key); + + bool use_fs = loader->IsFileSystemEnabled(); + Sound* sound = 0; + + loader->UseFileSystem(true); + loader->SetDataPath(datapath); + loader->LoadSound(filename, sound, Sound::LOCALIZED, true); // optional sound + loader->SetDataPath(0); + loader->UseFileSystem(use_fs); + + if (sound) { + sound->SetVolume(AudioConfig::VoxVolume()); + sound->SetFlags(Sound::LOCALIZED | Sound::LOCKED); + sound->SetFilename(filename); + sounds.append(sound); + + return true; + } + } + + return false; +} + +// +--------------------------------------------------------------------+ + +bool +RadioVox::Start() +{ + if (controller) + return controller->Add(this); + + return false; +} + +bool +RadioVox::Update() +{ + if (message.length()) { + RadioView::Message(message); + message = ""; + } + + bool active = false; + + while (!active && index < sounds.size()) { + Sound* s = sounds[index]; + + if (s->IsReady()) { + if (channel & 1) + s->SetPan(channel * -3000); + else + s->SetPan(channel * 3000); + + s->Play(); + active = true; + } + + else if (s->IsPlaying()) { + s->Update(); + active = true; + } + + else { + index++; + } + } + + return active; +} diff --git a/Stars45/RadioVox.h b/Stars45/RadioVox.h new file mode 100644 index 0000000..b68430a --- /dev/null +++ b/Stars45/RadioVox.h @@ -0,0 +1,59 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: RadioVox.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + View class for Radio Communications HUD Overlay +*/ + +#ifndef RadioVox_h +#define RadioVox_h + +#include "Types.h" +#include "List.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class Element; +class Ship; +class RadioMessage; +class Sound; + +// +--------------------------------------------------------------------+ + +class RadioVox +{ + friend class RadioVoxController; + +public: + static const char* TYPENAME() { return "RadioVox"; } + + RadioVox(int channel, const char* path, const char* message=0); + virtual ~RadioVox(); + + // Operations: + virtual bool AddPhrase(const char* key); + virtual bool Start(); + + static void Initialize(); + static void Close(); + +protected: + virtual bool Update(); + + Text path; + Text message; + List sounds; + int index; + int channel; +}; + +#endif RadioVox_h + diff --git a/Stars45/SeekerAI.cpp b/Stars45/SeekerAI.cpp new file mode 100644 index 0000000..c57219c --- /dev/null +++ b/Stars45/SeekerAI.cpp @@ -0,0 +1,257 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: SeekerAI.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Seeker Missile (low-level) Artificial Intelligence class +*/ + +#include "MemDebug.h" +#include "SeekerAI.h" +#include "Ship.h" +#include "Shot.h" +#include "System.h" +#include "WeaponDesign.h" + +#include "Game.h" + +// +----------------------------------------------------------------------+ + +SeekerAI::SeekerAI(SimObject* s) + : SteerAI(s), shot((Shot*) s), orig_target(0), + pursuit(1), delay(0), overshot(false) +{ + ai_type = SEEKER; + + seek_gain = 25; + seek_damp = 0.55; +} + + +// +--------------------------------------------------------------------+ + +SeekerAI::~SeekerAI() +{ + if (shot) { + if (shot->Owner()) + ((Ship*) shot->Owner())->SetMissileEta(shot->Identity(), 0); + } +} + +// +--------------------------------------------------------------------+ + +void +SeekerAI::ExecFrame(double seconds) +{ + // setup: + FindObjective(); + + // adaptive behavior: + Navigator(); +} + +// +--------------------------------------------------------------------+ + +void +SeekerAI::Navigator() +{ + if (delay > 0) { + delay -= Game::FrameTime(); + } + else { + Steer s = SeekTarget(); + self->ApplyYaw((float) s.yaw); + self->ApplyPitch((float) s.pitch); + } +} + +void +SeekerAI::SetTarget(SimObject* targ, System* sub) +{ + if (!orig_target && targ && targ->Type() == SimObject::SIM_SHIP) { + orig_target = (Ship*) targ; + Observe(orig_target); + } + + SteerAI::SetTarget(targ, sub); + + if (!target) { + shot->SetEta(0); + + if (shot->Owner()) + ((Ship*) shot->Owner())->SetMissileEta(shot->Identity(), 0); + } +} + +void +SeekerAI::FindObjective() +{ + if (!shot || !target) return; + + if (target->Life() == 0) { + if (target != orig_target) + SetTarget(orig_target,0); + else + SetTarget(0,0); + + return; + } + + Point tloc = target->Location(); + tloc = Transform(tloc); + + // seeker head limit of 45 degrees: + if (tloc.z < 0 || tloc.z < fabs(tloc.x) || tloc.z < fabs(tloc.y)) { + overshot = true; + SetTarget(0,0); + return; + } + + // distance from self to target: + distance = Point(target->Location() - self->Location()).length(); + + // are we being spoofed? + CheckDecoys(distance); + + Point cv = ClosingVelocity(); + + // time to reach target: + double time = distance / cv.length(); + double predict = time; + if (predict > 15) + predict = 15; + + // pure pursuit: + if (pursuit == 1 || time < 0.1) { + obj_w = target->Location(); + } + + // lead pursuit: + else { + // where the target will be when we reach it: + Point run_vec = target->Velocity(); + obj_w = target->Location() + (run_vec * predict); + } + + // subsystem offset: + if (subtarget) { + Point offset = target->Location() - subtarget->MountLocation(); + obj_w -= offset; + } + else if (target->Type() == SimObject::SIM_SHIP) { + Ship* tgt_ship = (Ship*) target; + + if (tgt_ship->IsGroundUnit()) + obj_w += Point(0,150,0); + } + + + distance = Point(obj_w - self->Location()).length(); + time = distance / cv.length(); + + // where we will be when the target gets there: + if (predict > 0.1 && predict < 15) { + Point self_dest = self->Location() + cv * predict; + Point err = obj_w - self_dest; + + obj_w += err; + } + + // transform into camera coords: + objective = Transform(obj_w); + objective.Normalize(); + + shot->SetEta((int) time); + + if (shot->Owner()) + ((Ship*) shot->Owner())->SetMissileEta(shot->Identity(), (int) time); +} + +// +--------------------------------------------------------------------+ + +void +SeekerAI::CheckDecoys(double target_distance) +{ + // if the assigned target has the burner lit, + // ignore the decoys: + if (orig_target && orig_target->Augmenter()) { + SetTarget(orig_target); + return; + } + + if (target && + target == orig_target && + orig_target->GetActiveDecoys().size()) { + + ListIter decoy = orig_target->GetActiveDecoys(); + + while (++decoy) { + double decoy_distance = Point(decoy->Location() - self->Location()).length(); + + if (decoy_distance < target_distance) { + if (rand() < 1600) { + SetTarget(decoy.value(), 0); + return; + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +bool +SeekerAI::Overshot() +{ + return overshot; +} + +// +--------------------------------------------------------------------+ + +Steer +SeekerAI::AvoidCollision() +{ + return Steer(); +} + +// +--------------------------------------------------------------------+ + +Steer +SeekerAI::SeekTarget() +{ + if (!self || !target || Overshot()) + return Steer(); + + return Seek(objective); +} + +// +--------------------------------------------------------------------+ + +bool +SeekerAI::Update(SimObject* obj) +{ + if (obj == target) { + if (obj->Type() == SimObject::SIM_SHOT && orig_target != 0) + target = orig_target; + } + + if (obj == orig_target) + orig_target = 0; + + return SteerAI::Update(obj); +} + +const char* +SeekerAI::GetObserverName() const +{ + static char name[64]; + sprintf(name, "SeekerAI(%s)", self->Name()); + return name; +} + diff --git a/Stars45/SeekerAI.h b/Stars45/SeekerAI.h new file mode 100644 index 0000000..c72e54f --- /dev/null +++ b/Stars45/SeekerAI.h @@ -0,0 +1,73 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: SeekerAI.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Seeker Missile (low-level) Artifical Intelligence class +*/ + +#ifndef SeekerAI_h +#define SeekerAI_h + +#include "Types.h" +#include "SteerAI.h" +#include "SimObject.h" + +// +--------------------------------------------------------------------+ + +class Ship; +class Shot; + +class SeekerAI : public SteerAI +{ +public: + SeekerAI(SimObject* s); + virtual ~SeekerAI(); + + virtual int Type() const { return 1001; } + virtual int Subframe() const { return true; } + + virtual void ExecFrame(double seconds); + virtual void FindObjective(); + virtual void SetTarget(SimObject* targ, System* sub=0); + virtual bool Overshot(); + + virtual void SetPursuit(int p) { pursuit = p; } + virtual int GetPursuit() const { return pursuit; } + + virtual void SetDelay(double d) { delay = d; } + virtual double GetDelay() const { return delay; } + + virtual bool Update(SimObject* obj); + virtual const char* GetObserverName() const; + +protected: + // behaviors: + virtual Steer AvoidCollision(); + virtual Steer SeekTarget(); + + // accumulate behaviors: + virtual void Navigator(); + + virtual void CheckDecoys(double distance); + + Ship* orig_target; + Shot* shot; + int pursuit; // type of pursuit curve + // 1: pure pursuit + // 2: lead pursuit + + double delay; // don't start seeking until then + bool overshot; +}; + +// +--------------------------------------------------------------------+ + +#endif SeekerAI_h + diff --git a/Stars45/Sensor.cpp b/Stars45/Sensor.cpp new file mode 100644 index 0000000..315d214 --- /dev/null +++ b/Stars45/Sensor.cpp @@ -0,0 +1,850 @@ +/* Project Starshatter 5.0 + Destroyer Studios LLC + Copyright © 1997-2007. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Sensor.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Integrated (Passive and Active) Sensor Package class implementation +*/ + +#include "MemDebug.h" +#include "Sensor.h" +#include "Contact.h" +#include "Element.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "Shot.h" +#include "Drone.h" +#include "WeaponDesign.h" +#include "Sim.h" +#include "CombatGroup.h" +#include "CombatUnit.h" + +#include "Game.h" + +// +----------------------------------------------------------------------+ + +const double SENSOR_THRESHOLD = 0.25; + +// +----------------------------------------------------------------------+ + +Sensor::Sensor() + : System(SENSOR, 1, "Dual Sensor Pkg", 1, 10, 10, 10), + mode(STD), target(0), + nsettings(0), range_index(0) +{ + name = Game::GetText("sys.sensor"); + abrv = Game::GetText("sys.sensor.abrv"); + + SetMode(mode); + power_flags = POWER_WATTS; + + ZeroMemory(range_settings, sizeof(range_settings)); +} + +// +----------------------------------------------------------------------+ + +Sensor::Sensor(const Sensor& s) + : System(s), mode(STD), target(0), + nsettings(s.nsettings), range_index(0) +{ + Mount(s); + + SetMode(mode); + power_flags = POWER_WATTS; + + CopyMemory(range_settings, s.range_settings, sizeof(range_settings)); + + if (nsettings) + range_index = nsettings-1; +} + +// +--------------------------------------------------------------------+ + +Sensor::~Sensor() +{ + ClearAllContacts(); +} + +// +--------------------------------------------------------------------+ + +void +Sensor::ClearAllContacts() +{ + contacts.destroy(); +} + +// +--------------------------------------------------------------------+ + +double +Sensor::GetBeamLimit() const +{ + if (mode == ACM) + return 15*DEGREES; + + if (mode <= GM) + return 45*DEGREES; + + return 175*DEGREES; +} + +double +Sensor::GetBeamRange() const +{ + return (double) range_settings[range_index]; +} + +void +Sensor::IncreaseRange() +{ + if (range_index < nsettings-1) + range_index++; + else + range_index = nsettings-1; +} + +void +Sensor::DecreaseRange() +{ + if (range_index > 0) + range_index--; + else + range_index = 0; +} + +void +Sensor::AddRange(double r) +{ + if (nsettings < 8) + range_settings[nsettings++] = (float) r; + + range_index = nsettings-1; +} + +void +Sensor::SetMode(Mode m) +{ + if (mode != m) { + // dump the contact list when changing in/out of GM: + if (mode == GM || m == GM) + ClearAllContacts(); + + // dump the current target on mode changes: + if (m <= GM) { + if (ship) + ship->DropTarget(); + + Ignore(target); + target = 0; + } + } + + mode = m; +} + +// +----------------------------------------------------------------------+ + +bool +Sensor::Update(SimObject* obj) +{ + if (obj == target) { + target = 0; + } + + return SimObserver::Update(obj); +} + +const char* +Sensor::GetObserverName() const +{ + return "Sensor"; +} + +// +--------------------------------------------------------------------+ + +void +Sensor::ExecFrame(double seconds) +{ + if (Game::Paused()) + return; + + System::ExecFrame(seconds); + + if (!IsPowerOn() || energy <= 0) { + ClearAllContacts(); + return; + } + + if (ship && ship->GetAIMode() < 2) { + // just pretend to work this frame: + energy = 0.0f; + return; + } + + if (ship && ship->GetRegion()) { + const Camera* cam = &ship->Cam(); + double az1 = -45*DEGREES; + double az2 = 45*DEGREES; + + if (mode > GM) { + az1 = -175*DEGREES; + az2 = 175*DEGREES; + } + + ListIter ship_iter(ship->GetRegion()->Ships()); + while (++ship_iter) { + Ship* c_ship = ship_iter.value(); + + if (c_ship != ship) { + ProcessContact(c_ship, az1, az2); + } + else { + Contact* c = FindContact(c_ship); + + if (!c) { + c = new(__FILE__,__LINE__) Contact(c_ship, 0.0f, 0.0f); + contacts.append(c); + } + + // update track: + if (c) { + c->loc = c_ship->Location(); + c->d_pas = 2.0f; + c->d_act = 2.0f; + + c->UpdateTrack(); + } + } + } + + ListIter threat_iter(ship->GetThreatList()); + while (++threat_iter) { + ProcessContact(threat_iter.value(), az1, az2); + } + + ListIter drone_iter(ship->GetRegion()->Drones()); + while (++drone_iter) { + ProcessContact(drone_iter.value(), az1, az2); + } + + List& track_list = ship->GetRegion()->TrackList(ship->GetIFF()); + ListIter c_iter(contacts); + + while (++c_iter) { + Contact* contact = c_iter.value(); + Ship* c_ship = contact->GetShip(); + Shot* c_shot = contact->GetShot(); + double c_life = -1; + + if (c_ship) { + c_life = c_ship->Life(); + + // look for quantum jumps and orbit transitions: + if (c_ship->GetRegion() != ship->GetRegion()) + c_life = 0; + } + + else if (c_shot) { + c_life = c_shot->Life(); + } + + else { + c_life = 0; + } + + if (contact->Age() < 0 || c_life == 0) { + delete c_iter.removeItem(); + } + + else if (ship && ship->GetIFF() >= 0 && ship->GetIFF() < 5) { + // update shared track database: + Contact* t = track_list.find(contact); + + if (c_ship) { + if (!t) { + Contact* track = new(__FILE__,__LINE__) Contact(c_ship, contact->d_pas, contact->d_act); + track->loc = c_ship->Location(); + track_list.append(track); + } + + else { + t->loc = c_ship->Location(); + t->Merge(contact); + t->UpdateTrack(); + } + } + + else if (c_shot) { + if (!t) { + Contact* track = new(__FILE__,__LINE__) Contact(c_shot, contact->d_pas, contact->d_act); + track->loc = c_shot->Location(); + track_list.append(track); + } + + else { + t->loc = c_shot->Location(); + t->Merge(contact); + t->UpdateTrack(); + } + } + } + } + + + if (mode == ACM) { + if (!ship->GetTarget()) + ship->LockTarget(SimObject::SIM_SHIP, true, true); + } + } + + energy = 0.0f; +} + +// +--------------------------------------------------------------------+ + +void +Sensor::ProcessContact(Ship* c_ship, double az1, double az2) +{ + if (c_ship->IsNetObserver()) + return; + + double sensor_range = GetBeamRange(); + + // translate: + const Camera* cam = &ship->Cam(); + Point targ_pt = c_ship->Location() - ship->Location(); + + // rotate: + double tx = targ_pt * cam->vrt(); + double ty = targ_pt * cam->vup(); + double tz = targ_pt * cam->vpn(); + + // convert to spherical coords: + double rng = targ_pt.length(); + double az = asin(fabs(tx) / rng); + double el = asin(fabs(ty) / rng); + if (tx < 0) az = -az; + if (ty < 0) el = -el; + + double min_range = rng; + Drone* probe = ship->GetProbe(); + bool probescan = false; + + if (ship->GetIFF() == c_ship->GetIFF()) { + min_range = 1; + } + + else if (probe) { + Point probe_pt = c_ship->Location() - probe->Location(); + double prng = probe_pt.length(); + + if (prng < probe->Design()->lethal_radius && prng < rng) { + min_range = prng; + probescan = true; + } + } + + bool vis = tz > 1 && (c_ship->Radius()/rng > 0.001); + bool threat = (c_ship->Life() != 0 && + c_ship->GetIFF() && + c_ship->GetIFF() != ship->GetIFF() && + c_ship->GetEMCON() > 2 && + c_ship->IsTracking(ship)); + + if (!threat) { + if (mode == GM && !c_ship->IsGroundUnit()) + return; + + if (mode != GM && c_ship->IsGroundUnit()) + return; + + if (min_range > sensor_range || min_range > c_ship->Design()->detet) { + if (c_ship == target) { + ship->DropTarget(); + Ignore(target); + target = 0; + } + + return; + } + } + + // clip: + if (threat || vis || mode >= PST || tz > 1) { + + // correct az/el for back hemisphere: + if (tz < 0) { + if (az < 0) az = -PI - az; + else az = PI - az; + } + + double d_pas = 0; + double d_act = 0; + double effectivity = energy/capacity * availability; + + // did this contact get scanned this frame? + if (effectivity > SENSOR_THRESHOLD) { + if (az >= az1 && az <= az2 && (mode >= PST || fabs(el) < 45*DEGREES)) { + double passive_range_limit = 500e3; + if (c_ship->Design()->detet > passive_range_limit) + passive_range_limit = c_ship->Design()->detet; + + d_pas = c_ship->PCS() * effectivity * (1 - min_range/passive_range_limit); + + if (d_pas < 0) + d_pas = 0; + + if (probescan) { + double max_range = probe->Design()->lethal_radius; + d_act = c_ship->ACS() * (1 - min_range/max_range); + } + + else if (mode != PAS && mode != PST) { + double max_range = sensor_range; + d_act = c_ship->ACS() * effectivity * (1 - min_range/max_range); + } + + if (d_act < 0) + d_act = 0; + } + } + + // yes, update or add new contact: + if (threat || vis || d_pas > SENSOR_THRESHOLD || d_act > SENSOR_THRESHOLD) { + Element* elem = c_ship->GetElement(); + CombatUnit* unit = c_ship->GetCombatUnit(); + + if (elem && ship && elem->GetIFF() != ship->GetIFF() && elem->IntelLevel() < Intel::LOCATED) { + elem->SetIntelLevel(Intel::LOCATED); + } + + if (unit && ship && unit->GetIFF() != ship->GetIFF()) { + CombatGroup* group = unit->GetCombatGroup(); + + if (group && group->IntelLevel() < Intel::LOCATED && + group->IntelLevel() > Intel::RESERVE) { + group->SetIntelLevel(Intel::LOCATED); + } + } + + Contact* c = FindContact(c_ship); + + if (!c) { + c = new(__FILE__,__LINE__) Contact(c_ship, 0.0f, 0.0f); + contacts.append(c); + } + + // update track: + if (c) { + c->loc = c_ship->Location(); + c->d_pas = (float) d_pas; + c->d_act = (float) d_act; + c->probe = probescan; + + c->UpdateTrack(); + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +Sensor::ProcessContact(Shot* c_shot, double az1, double az2) +{ + double sensor_range = GetBeamRange(); + + if (c_shot->IsPrimary() || c_shot->IsDecoy()) + return; + + // translate: + const Camera* cam = &ship->Cam(); + Point targ_pt = c_shot->Location() - ship->Location(); + + // rotate: + double tx = targ_pt * cam->vrt(); + double ty = targ_pt * cam->vup(); + double tz = targ_pt * cam->vpn(); + + // convert to spherical coords: + double rng = targ_pt.length(); + double az = asin(fabs(tx) / rng); + double el = asin(fabs(ty) / rng); + if (tx < 0) az = -az; + if (ty < 0) el = -el; + + bool vis = tz > 1 && (c_shot->Radius()/rng > 0.001); + bool threat = (c_shot->IsTracking(ship)); + + // clip: + if (threat || vis || ((mode >= PST || tz > 1) && rng <= sensor_range)) { + + // correct az/el for back hemisphere: + if (tz < 0) { + if (az < 0) az = -PI - az; + else az = PI - az; + } + + double d_pas = 0; + double d_act = 0; + double effectivity = energy/capacity * availability; + + // did this contact get scanned this frame? + if (effectivity > SENSOR_THRESHOLD) { + if (az >= az1 && az <= az2 && (mode >= PST || fabs(el) < 45*DEGREES)) { + if (rng < sensor_range/2) + d_pas = 1.5; + else + d_pas = 0.5; + + if (mode != PAS && mode != PST) + d_act = effectivity * (1 - rng/sensor_range); + + if (d_act < 0) + d_act = 0; + } + } + + // yes, update or add new contact: + if (threat || vis || d_pas > SENSOR_THRESHOLD || d_act > SENSOR_THRESHOLD) { + Contact* c = FindContact(c_shot); + + if (!c) { + c = new(__FILE__,__LINE__) Contact(c_shot, 0.0f, 0.0f); + contacts.append(c); + } + + // update track: + if (c) { + c->loc = c_shot->Location(); + c->d_pas = (float) d_pas; + c->d_act = (float) d_act; + + c->UpdateTrack(); + } + } + } +} + +Contact* +Sensor::FindContact(Ship* s) +{ + ListIter iter(contacts); + while (++iter) { + Contact* c = iter.value(); + if (c->GetShip() == s) + return c; + } + + return 0; +} + +Contact* +Sensor::FindContact(Shot* s) +{ + ListIter iter(contacts); + while (++iter) { + Contact* c = iter.value(); + if (c->GetShot() == s) + return c; + } + + return 0; +} + +// +--------------------------------------------------------------------+ + +bool +Sensor::IsTracking(SimObject* tgt) +{ + if (tgt && mode != GM && mode != PAS && mode != PST && IsPowerOn()) { + if (tgt == target) + return true; + + Contact* c = 0; + + if (tgt->Type() == SimObject::SIM_SHIP) { + c = FindContact((Ship*) tgt); + } + else { + c = FindContact((Shot*) tgt); + } + + return (c != 0 && c->ActReturn() > SENSOR_THRESHOLD && !c->IsProbed()); + } + + return false; +} + +// +--------------------------------------------------------------------+ + +const double sensor_lock_threshold = 0.5; + +struct TargetOffset { + static const char* TYPENAME() { return "TargetOffset"; } + + SimObject* target; + double offset; + + TargetOffset() : target(0), offset(10) { } + TargetOffset(SimObject* t, double o) : target(t), offset(o) { } + int operator< (const TargetOffset& o) const { return offset < o.offset; } + int operator<=(const TargetOffset& o) const { return offset <= o.offset; } + int operator==(const TargetOffset& o) const { return offset == o.offset; } +}; + +SimObject* +Sensor::LockTarget(int type, bool closest, bool hostile) +{ + if (!ship || ship->GetEMCON() < 3) { + Ignore(target); + target = 0; + return target; + } + + SimObject* test = 0; + ListIter contact(ship->ContactList()); + + List targets; + + while (++contact) { + if (type == SimObject::SIM_SHIP) + test = contact->GetShip(); + else + test = contact->GetShot(); + + if (!test) + continue; + + // do not target own missiles: + if (contact->GetShot() && contact->GetShot()->Owner() == ship) + continue; + + double tgt_range = contact->Range(ship); + + // do not target ships outside of detet range: + if (contact->GetShip() && contact->GetShip()->Design()->detet < tgt_range) + continue; + + double d_pas = contact->PasReturn(); + double d_act = contact->ActReturn(); + bool vis = contact->Visible(ship) || contact->Threat(ship); + + if (!vis && d_pas < sensor_lock_threshold && d_act < sensor_lock_threshold) + continue; + + if (closest) { + if (hostile && (contact->GetIFF(ship) == 0 || contact->GetIFF(ship) == ship->GetIFF())) + continue; + + targets.append(new(__FILE__,__LINE__) TargetOffset(test, tgt_range)); + } + + // clip: + else if (contact->InFront(ship)) { + double az, el, rng; + + contact->GetBearing(ship, az, el, rng); + az = fabs(az / PI); + el = fabs(el / PI); + + if (az <= 0.2 && el <= 0.2) + targets.append(new(__FILE__,__LINE__) TargetOffset(test, az+el)); + } + } + + targets.sort(); + + if (target) { + int index = 100000; + int i = 0; + + if (targets.size() > 0) { + ListIter iter(targets); + while (++iter) { + if (iter->target == target) { + index = i; + break; + } + + i++; + } + + if (index < targets.size()-1) + index++; + else + index = 0; + + target = targets[index]->target; + Observe(target); + } + } + else if (targets.size() > 0) { + target = targets[0]->target; + Observe(target); + } + else { + target = 0; + } + + targets.destroy(); + + if (target && mode < STD) + mode = STD; + + return target; +} + +// +--------------------------------------------------------------------+ + +SimObject* +Sensor::LockTarget(SimObject* candidate) +{ + Ignore(target); + target = 0; + + if (ship->GetEMCON() < 3) + return target; + + if (!candidate) + return target; + + int type = candidate->Type(); + SimObject* test = 0; + ListIter contact(ship->ContactList()); + + while (++contact) { + if (type == SimObject::SIM_SHIP) + test = contact->GetShip(); + else + test = contact->GetShot(); + + if (test == candidate) { + double d_pas = contact->PasReturn(); + double d_act = contact->ActReturn(); + bool vis = contact->Visible(ship) || contact->Threat(ship); + + if (vis || d_pas > sensor_lock_threshold || d_act > sensor_lock_threshold) { + target = test; + Observe(target); + } + + break; + } + } + + if (target && mode < STD) + mode = STD; + + return target; +} + +// +--------------------------------------------------------------------+ + +SimObject* +Sensor::AcquirePassiveTargetForMissile() +{ + SimObject* pick = 0; + double min_off = 2; + + ListIter contact(ship->ContactList()); + + while (++contact) { + SimObject* test = contact->GetShip(); + double d = contact->PasReturn(); + + if (d < 1) continue; + + // clip: + if (contact->InFront(ship)) { + double az, el, rng; + + contact->GetBearing(ship, az, el, rng); + az = fabs(az / PI); + el = fabs(el / PI); + + if (az + el < min_off) { + min_off = az + el; + pick = test; + } + } + } + + return pick; +} + +// +--------------------------------------------------------------------+ + +SimObject* +Sensor::AcquireActiveTargetForMissile() +{ + SimObject* pick = 0; + double min_off = 2; + + ListIter contact(ship->ContactList()); + + while (++contact) { + SimObject* test = contact->GetShip(); + double d = contact->ActReturn(); + + if (d < 1) continue; + + if (contact->InFront(ship)) { + double az, el, rng; + + contact->GetBearing(ship, az, el, rng); + az = fabs(az / PI); + el = fabs(el / PI); + + if (az + el < min_off) { + min_off = az + el; + pick = test; + } + } + } + + return pick; +} + +// +--------------------------------------------------------------------+ + +void +Sensor::DoEMCON(int index) +{ + int e = GetEMCONPower(index); + + if (power_level * 100 > e || emcon != index) { + if (e == 0) { + PowerOff(); + } + else if (emcon != index) { + PowerOn(); + + if (power_level * 100 > e) { + SetPowerLevel(e); + } + + if (emcon == 3) { + if (GetMode() < PST) + SetMode(STD); + else + SetMode(CST); + } + else { + int m = GetMode(); + if (m < PST && m > PAS) + SetMode(Sensor::PAS); + else if (m == CST) + SetMode(PST); + } + } + } + + emcon = index; +} + diff --git a/Stars45/Sensor.h b/Stars45/Sensor.h new file mode 100644 index 0000000..5bb4dc0 --- /dev/null +++ b/Stars45/Sensor.h @@ -0,0 +1,86 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Sensor.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Integrated (Passive and Active) Sensor Package class +*/ + +#ifndef Sensor_h +#define Sensor_h + +#include "Types.h" +#include "SimObject.h" +#include "System.h" +#include "Geometry.h" +#include "List.h" + +// +--------------------------------------------------------------------+ + +class Shot; +class Contact; + +// +--------------------------------------------------------------------+ + +class Sensor : public System, public SimObserver +{ +public: + enum Mode { + PAS, STD, ACM, GM, // fighter modes + PST, CST // starship modes + }; + + Sensor(); + Sensor(const Sensor& rhs); + virtual ~Sensor(); + + virtual void ExecFrame(double seconds); + virtual SimObject* LockTarget(int type=SimObject::SIM_SHIP, + bool closest=false, + bool hostile=false); + virtual SimObject* LockTarget(SimObject* candidate); + virtual bool IsTracking(SimObject* tgt); + virtual void DoEMCON(int emcon); + + virtual void ClearAllContacts(); + + virtual Mode GetMode() const { return mode; } + virtual void SetMode(Mode m); + virtual double GetBeamLimit() const; + virtual double GetBeamRange() const; + virtual void IncreaseRange(); + virtual void DecreaseRange(); + virtual void AddRange(double r); + + Contact* FindContact(Ship* s); + Contact* FindContact(Shot* s); + + // borrow this sensor for missile seeker + SimObject* AcquirePassiveTargetForMissile(); + SimObject* AcquireActiveTargetForMissile(); + + // SimObserver: + virtual bool Update(SimObject* obj); + virtual const char* GetObserverName() const; + +protected: + void ProcessContact(Ship* contact, double az1, double az2); + void ProcessContact(Shot* contact, double az1, double az2); + + Mode mode; + int nsettings; + int range_index; + float range_settings[8]; + SimObject* target; + + List contacts; +}; + +#endif Sensor_h + diff --git a/Stars45/Shield.cpp b/Stars45/Shield.cpp new file mode 100644 index 0000000..58d3776 --- /dev/null +++ b/Stars45/Shield.cpp @@ -0,0 +1,259 @@ +/* Project Starshatter 5.0 + Destroyer Studios LLC + Copyright © 1997-2007. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Shield.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Weapon class +*/ + +#include "MemDebug.h" +#include "Shield.h" +#include "Shot.h" +#include "WeaponDesign.h" + +#include "Game.h" + +// +----------------------------------------------------------------------+ + +static char* shield_name[] = { + "sys.shield.none", + "sys.shield.deflector", + "sys.shield.grav", + "sys.shield.hyper" +}; + +static int shield_value[] = { + 0, 2, 2, 3 +}; + +// +----------------------------------------------------------------------+ + +Shield::Shield(SUBTYPE shield_type) + : System(SHIELD, shield_type, "shield", shield_value[shield_type], 100, 0), + shield_cutoff(0.0f), shield_capacitor(false), shield_bubble(false), + deflection_cost(1.0f), shield_curve(0.05f) +{ + name = Game::GetText(shield_name[shield_type]); + abrv = Game::GetText("sys.shield.abrv"); + + power_flags = POWER_WATTS | POWER_CRITICAL; + energy = 0.0f; + power_level = 0.0f; + shield_level = 0.0f; + + switch (shield_type) { + default: + case DEFLECTOR: + capacity = sink_rate = 2.0e3f; + shield_factor = 0.05f; + break; + + case GRAV_SHIELD: + capacity = sink_rate = 7.0e3f; + shield_factor = 0.01f; + break; + + case HYPER_SHIELD: + capacity = sink_rate = 10.0e3f; + shield_factor = 0.003f; + break; + } + + emcon_power[0] = 0; + emcon_power[1] = 0; + emcon_power[2] = 100; +} + +// +----------------------------------------------------------------------+ + +Shield::Shield(const Shield& s) + : System(s), shield_factor(s.shield_factor), requested_power_level(0.0f), + shield_cutoff(s.shield_cutoff), shield_capacitor(s.shield_capacitor), + shield_bubble(s.shield_bubble), deflection_cost(s.deflection_cost), + shield_curve(s.shield_curve) +{ + power_flags = s.power_flags; + energy = 0.0f; + power_level = 0.0f; + shield_level = 0.0f; + + Mount(s); +} + +// +--------------------------------------------------------------------+ + +Shield::~Shield() +{ } + +void +Shield::SetShieldCapacitor(bool c) +{ + shield_capacitor = c; + + if (shield_capacitor) { + power_flags = POWER_CRITICAL; + shield_curve = 0.05f; + } + else { + power_flags = POWER_WATTS | POWER_CRITICAL; + shield_curve = 0.25f; + } +} + +// +--------------------------------------------------------------------+ + +void +Shield::ExecFrame(double seconds) +{ + System::ExecFrame(seconds); + + if (power_level < requested_power_level) { + power_level += (float) (seconds * 0.10); // ten seconds to charge up + + if (power_level > requested_power_level) + power_level = (float) requested_power_level; + } + else if (power_level > requested_power_level) { + power_level -= (float) (seconds * 0.20); // five seconds to power down + + if (power_level < requested_power_level) + power_level = (float) requested_power_level; + } + + if (power_level < 0.01 && !shield_capacitor) { + shield_level = 0.0f; + energy = 0.0f; + } +} + +// +----------------------------------------------------------------------+ + +void +Shield::Distribute(double delivered_energy, double seconds) +{ + System::Distribute(delivered_energy, seconds); + + if (shield_capacitor) { + if (shield_cutoff > 0 && shield_cutoff < 0.999) { + float cutoff = shield_cutoff * capacity; + + if (energy > cutoff) + shield_level = (energy-cutoff)/(capacity-cutoff); + else + shield_level = 0.0f; + } + + else { + shield_level = energy/capacity; + } + } + else { + shield_level = energy/sink_rate; + energy = 0.0f; + } + + if (shield_level < 0) + shield_level = 0; +} + +// +--------------------------------------------------------------------+ + +double +Shield::DeflectDamage(Shot* shot, double damage) +{ + double filter = 1; + double penetration = 5; + double leak = 0; + + if (shot) + penetration = shot->Design()->penetration; + + filter = 1 - shield_factor * penetration; + + if (filter < 0) + filter = 0; + + else if (filter > 1) + filter = 1; + + if (shield_capacitor) { + if (shield_cutoff > 0 && shield_level < 1e-6) { + leak = damage; + energy -= (float) (damage * deflection_cost); + } + + else { + leak = damage * (1 - pow(shield_level, shield_curve) * filter * availability); + + double deflected = damage - leak; + energy -= (float) deflected * deflection_cost; + } + + } + else { + leak = damage * (1 - pow(shield_level, shield_curve) * filter * availability); + } + + return leak; +} + +// +--------------------------------------------------------------------+ + +void +Shield::SetPowerLevel(double level) +{ + if (level > 100) + level = 100; + else if (level < 0) + level = 0; + + level /= 100; + + if (requested_power_level != level) { + // if the system is on emergency override power, + // do not let the EMCON system use this method + // to drop it back to normal power: + if (power_level > 1 && level == 1) { + requested_power_level = (float) power_level; + return; + } + + requested_power_level = (float) level; + } +} + +void +Shield::SetNetShieldLevel(int level) +{ + if (level > 100) level = 100; + else if (level < 0) level = 0; + + requested_power_level = (float) (level/100.0); + power_level = requested_power_level; +} + +void +Shield::DoEMCON(int index) +{ + int e = GetEMCONPower(index); + + if (power_level * 100 > e || emcon != index) { + if (e == 0) { + PowerOff(); + } + else if (emcon != index) { + PowerOn(); + + if (power_level * 100 > e) + SetPowerLevel(e); + } + } + + emcon = index; +} diff --git a/Stars45/Shield.h b/Stars45/Shield.h new file mode 100644 index 0000000..0fa16c3 --- /dev/null +++ b/Stars45/Shield.h @@ -0,0 +1,77 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Shield.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Conventional Shield (system) class +*/ + +#ifndef Shield_h +#define Shield_h + +#include "Types.h" +#include "System.h" +#include "Geometry.h" + +// +--------------------------------------------------------------------+ + +class Shot; +class Sound; + +// +--------------------------------------------------------------------+ + +class Shield : public System +{ +public: + enum SUBTYPE { DEFLECTOR = 1, GRAV_SHIELD, HYPER_SHIELD }; + + Shield(SUBTYPE s); + Shield(const Shield& rhs); + virtual ~Shield(); + + virtual void ExecFrame(double seconds); + double DeflectDamage(Shot* shot, double shot_damage); + + double ShieldLevel() const { return shield_level * 100; } + double ShieldFactor() const { return shield_factor; } + double ShieldCurve() const { return shield_curve; } + void SetShieldFactor(double f) { shield_factor = (float) f; } + void SetShieldCurve(double c) { shield_curve = (float) c; } + double ShieldCutoff() const { return shield_cutoff; } + void SetShieldCutoff(double f) { shield_cutoff = (float) f; } + double Capacity() const { return capacity; } + double Consumption() const { return sink_rate; } + void SetConsumption(double r) { sink_rate = (float)r; } + bool ShieldCapacitor() const { return shield_capacitor; } + void SetShieldCapacitor(bool c); + bool ShieldBubble() const { return shield_bubble; } + void SetShieldBubble(bool b) { shield_bubble = b; } + double DeflectionCost() const { return deflection_cost; } + void SetDeflectionCost(double c) { deflection_cost = (float) c; } + + // override from System: + virtual void SetPowerLevel(double level); + virtual void SetNetShieldLevel(int level); + + virtual void Distribute(double delivered_energy, double seconds); + virtual void DoEMCON(int emcon); + +protected: + bool shield_capacitor; + bool shield_bubble; + float shield_factor; + float shield_level; + float shield_curve; + float shield_cutoff; + float requested_power_level; + float deflection_cost; +}; + +#endif Shield_h + diff --git a/Stars45/ShieldRep.cpp b/Stars45/ShieldRep.cpp new file mode 100644 index 0000000..31ef1f4 --- /dev/null +++ b/Stars45/ShieldRep.cpp @@ -0,0 +1,250 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: ShieldRep.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + ShieldRep Solid class +*/ + +#include "MemDebug.h" +#include "ShieldRep.h" +#include "Random.h" + +#include "Game.h" +#include "Light.h" +#include "Solid.h" +#include "Bitmap.h" +#include "Color.h" +#include "DataLoader.h" + +// +--------------------------------------------------------------------+ + +const int MAX_SHIELD_HITS = 16; + +// +--------------------------------------------------------------------+ + +struct ShieldHit +{ + Vec3 hitloc; + double damage; + double age; + Shot* shot; + + ShieldHit() : damage(0), age(0), shot(0) { } +}; + +// +--------------------------------------------------------------------+ + +ShieldRep::ShieldRep() +{ + bubble = false; + luminous = true; + trans = true; + nhits = 0; + + hits = new(__FILE__,__LINE__) ShieldHit[MAX_SHIELD_HITS]; +} + +ShieldRep::~ShieldRep() +{ + delete [] hits; +} + +// +--------------------------------------------------------------------+ + +void +ShieldRep::Hit(Vec3 impact, Shot* shot, double damage) +{ + if (!model || model->GetSurfaces().size() < 1) + return; + + // transform impact into object space: + Matrix xform(Orientation()); + + Vec3 tmp = impact - loc; + + impact.x = tmp * Vec3(xform(0,0), xform(0,1), xform(0,2)); + impact.y = tmp * Vec3(xform(1,0), xform(1,1), xform(1,2)); + impact.z = tmp * Vec3(xform(2,0), xform(2,1), xform(2,2)); + + // find slot to store the hit: + int i; + int slot = -1; + double age = -1; + + for (i = 0; i < MAX_SHIELD_HITS; i++) { + if (hits[i].shot == shot) { + slot = i; + break; + } + } + + if (slot < 0) { + for (i = 0; i < MAX_SHIELD_HITS; i++) { + if (hits[i].damage <= 0) { + slot = i; + break; + } + + if (hits[i].age > age) { + slot = i; + age = hits[i].age; + } + } + } + + if (slot >= 0 && slot < MAX_SHIELD_HITS) { + // record the hit in the slot: + hits[slot].hitloc = impact; + hits[slot].damage = damage; + hits[slot].age = 1; + hits[slot].shot = shot; + + if (nhits < MAX_SHIELD_HITS) + nhits++; + } +} + +// +--------------------------------------------------------------------+ + +void +ShieldRep::Energize(double seconds, bool b) +{ + bubble = b; + + if (nhits < 1) return; + + nhits = 0; + + for (int i = 0; i < MAX_SHIELD_HITS; i++) { + if (hits[i].damage > 0) { + // age the hit: + hits[i].age += seconds; + hits[i].damage -= (hits[i].damage * 4 * seconds); + + // collect garbage: + if (hits[i].damage < 10) { + hits[i].age = 0; + hits[i].damage = 0; + hits[i].shot = 0; + } + else { + nhits++; + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +ShieldRep::TranslateBy(const Point& ref) +{ + true_eye_point = ref; + Solid::TranslateBy(ref); +} + +// +--------------------------------------------------------------------+ + +void +ShieldRep::Illuminate() +{ + if (!model) return; + + Surface* surf = model->GetSurfaces().first(); + VertexSet* vset = surf->GetVertexSet(); + int nverts = vset->nverts; + + for (int i = 0; i < nverts; i++) { + vset->diffuse[i] = 0; + vset->specular[i] = 0; + } + + double all_damage = 0; + + if (nhits < 1) return; + + for (i = 0; i < MAX_SHIELD_HITS; i++) { + if (hits[i].damage > 0) { + // add the hit's contribution to the shield verts: + Vec3 hitloc = hits[i].hitloc; + double hitdam = hits[i].damage * 2000; + + all_damage += hits[i].damage; + + if (!bubble) { + + double limit = radius * radius; + if (hitdam > limit) + hitdam = limit; + + for (int v = 0; v < nverts; v++) { + double dist = (vset->loc[v] - hitloc).length(); + + if (dist < 1) + dist = 1; // can't divide by zero! + + else + dist = pow(dist, 2.7); + + double pert = Random(0.1, 1.5); + double intensity = pert*hitdam/dist; + + if (intensity > 0.003) + vset->diffuse[v] = ((Color::White * intensity) + vset->diffuse[v]).Value(); + } + + } + } + } + + if (bubble) { + double shield_gain = 1; + + if (all_damage < 1000) { + shield_gain = all_damage / 1000; + } + + for (int i = 0; i < nverts; i++) { + Vec3 vloc = (vset->loc[i] * orientation) + loc; + Vec3 vnrm = (vset->nrm[i] * orientation); + + Vec3 V = vloc * -1.0f; + V.Normalize(); + + double intensity = 1 - V*vnrm; + + if (intensity > 0) { + intensity *= intensity; + + if (intensity > 1) intensity = 1; + + intensity *= (shield_gain * Random(0.75, 1.0)); + + Color vs = Color::White * intensity; + vset->diffuse[i] = vs.Value(); + } + } + } + + InvalidateSurfaceData(); +} + +void +ShieldRep::Render(Video* video, DWORD flags) +{ + if ((flags & RENDER_ADDITIVE) == 0) + return; + + if (nhits > 0) { + Illuminate(); + Solid::Render(video, RENDER_ALPHA); // have to lie about the render flag + // or the engine will reject the solid + } +} \ No newline at end of file diff --git a/Stars45/ShieldRep.h b/Stars45/ShieldRep.h new file mode 100644 index 0000000..07c2bbd --- /dev/null +++ b/Stars45/ShieldRep.h @@ -0,0 +1,48 @@ +/* Project STARSHATTER + John DiCamillo + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: ShieldRep.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + ShieldRep Solid class +*/ + +#ifndef ShieldRep_h +#define ShieldRep_h + +#include "Types.h" +#include "Solid.h" + +// +--------------------------------------------------------------------+ + +struct ShieldHit; +class Shot; + +class ShieldRep : public Solid +{ +public: + ShieldRep(); + virtual ~ShieldRep(); + + // operations + virtual void Render(Video* video, DWORD flags); + virtual void Energize(double seconds, bool bubble=false); + int ActiveHits() const { return nhits; } + virtual void Hit(Vec3 impact, Shot* shot, double damage=0); + virtual void TranslateBy(const Point& ref); + virtual void Illuminate(); + +protected: + int nhits; + ShieldHit* hits; + Point true_eye_point; + bool bubble; +}; + +#endif ShieldRep_h + diff --git a/Stars45/Ship.cpp b/Stars45/Ship.cpp new file mode 100644 index 0000000..5753b00 --- /dev/null +++ b/Stars45/Ship.cpp @@ -0,0 +1,5305 @@ +/* Project Starshatter 5.0 + Destroyer Studios LLC + Copyright © 1997-2007. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Ship.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Starship class +*/ + +#include "MemDebug.h" +#include "Ship.h" +#include "ShipAI.h" +#include "ShipCtrl.h" +#include "ShipDesign.h" +#include "ShipKiller.h" +#include "Shot.h" +#include "Drone.h" +#include "SeekerAI.h" +#include "HardPoint.h" +#include "Weapon.h" +#include "WeaponGroup.h" +#include "Shield.h" +#include "ShieldRep.h" +#include "Computer.h" +#include "FlightComp.h" +#include "Drive.h" +#include "QuantumDrive.h" +#include "Farcaster.h" +#include "Thruster.h" +#include "Power.h" +#include "FlightDeck.h" +#include "LandingGear.h" +#include "Hangar.h." +#include "Sensor.h" +#include "Contact.h" +#include "CombatUnit.h" +#include "Element.h" +#include "Instruction.h" +#include "RadioMessage.h" +#include "RadioHandler.h" +#include "RadioTraffic.h" +#include "NavLight.h" +#include "NavSystem.h" +#include "NavAI.h" +#include "DropShipAI.h" +#include "Explosion.h" +#include "MissionEvent.h" +#include "ShipSolid.h" +#include "Sim.h" +#include "SimEvent.h" +#include "StarSystem.h" +#include "TerrainRegion.h" +#include "Terrain.h" +#include "System.h" +#include "Component.h" +#include "KeyMap.h" +#include "RadioView.h" +#include "AudioConfig.h" +#include "CameraDirector.h" +#include "HUDView.h" +#include "Random.h" +#include "RadioVox.h" + +#include "NetGame.h" +#include "NetUtil.h" + +#include "MotionController.h" +#include "Keyboard.h" +#include "Joystick.h" +#include "Bolt.h" +#include "Game.h" +#include "Solid.h" +#include "Shadow.h" +#include "Skin.h" +#include "Sprite.h" +#include "Light.h" +#include "Bitmap.h" +#include "Button.h" +#include "Sound.h" +#include "DataLoader.h" + +#include "Parser.h" +#include "Reader.h" + +// +----------------------------------------------------------------------+ + +static int base_contact_id = 0; +static double range_min = 0; +static double range_max = 250e3; + +int Ship::control_model = 0; // standard +int Ship::flight_model = 0; // standard +int Ship::landing_model = 0; // standard +double Ship::friendly_fire_level = 1; // 100% + +const int HIT_NOTHING = 0; +const int HIT_HULL = 1; +const int HIT_SHIELD = 2; +const int HIT_BOTH = 3; +const int HIT_TURRET = 4; + +// +----------------------------------------------------------------------+ + +Ship::Ship(const char* ship_name, const char* reg_num, ShipDesign* ship_dsn, int IFF, int cmd_ai, const int* load) + : IFF_code(IFF), killer(0), throttle(0), augmenter(false), throttle_request(0), + shield(0), shieldRep(0), main_drive(0), quantum_drive(0), farcaster(0), + check_fire(false), probe(0), sensor_drone(0), primary(0), secondary(1), + cmd_chain_index(0), target(0), subtarget(0), radio_orders(0), launch_point(0), + g_force(0.0f), sensor(0), navsys(0), flcs(0), hangar(0), respawns(0), invulnerable(false), + thruster(0), decoy(0), ai_mode(2), command_ai_level(cmd_ai), flcs_mode(FLCS_AUTO), loadout(0), + emcon(3), old_emcon(3), master_caution(false), cockpit(0), gear(0), skin(0), + auto_repair(true), last_repair_time(0), last_eval_time(0), last_beam_time(0), last_bolt_time(0), + warp_fov(1), flight_phase(LAUNCH), launch_time(0), carrier(0), dock(0), ff_count(0), + inbound(0), element(0), director_info("Init"), combat_unit(0), net_control(0), + track(0), ntrack(0), track_time(0), helm_heading(0.0f), helm_pitch(0.0f), + altitude_agl(-1.0e6f), transition_time(0.0f), transition_type(TRANSITION_NONE), + friendly_fire_time(0), ward(0), net_observer_mode(false), orig_elem_index(-1) +{ + sim = Sim::GetSim(); + + strcpy(name, ship_name); + if (reg_num && *reg_num) + strcpy(regnum, reg_num); + else regnum[0] = 0; + + design = ship_dsn; + + if (!design) { + char msg[256]; + sprintf(msg, "No ship design found for '%s'\n", ship_name); + Game::Panic(msg); + } + + obj_type = SimObject::SIM_SHIP; + + radius = design->radius; + mass = design->mass; + integrity = design->integrity; + vlimit = design->vlimit; + + agility = design->agility; + wep_mass = 0.0f; + wep_resist = 0.0f; + + CL = design->CL; + CD = design->CD; + stall = design->stall; + + chase_vec = design->chase_vec; + bridge_vec = design->bridge_vec; + + acs = design->acs; + pcs = design->acs; + + auto_repair = design->repair_auto; + + while (!base_contact_id) + base_contact_id = rand() % 1000; + + contact_id = base_contact_id++; + int sys_id = 0; + + for (int i = 0; i < design->reactors.size(); i++) { + PowerSource* reactor = new(__FILE__,__LINE__) PowerSource(*design->reactors[i]); + reactor->SetShip(this); + reactor->SetID(sys_id++); + reactors.append(reactor); + systems.append(reactor); + } + + for (i = 0; i < design->drives.size(); i++) { + Drive* drive = new(__FILE__,__LINE__) Drive(*design->drives[i]); + drive->SetShip(this); + drive->SetID(sys_id++); + + int src_index = drive->GetSourceIndex(); + if (src_index >= 0 && src_index < reactors.size()) + reactors[src_index]->AddClient(drive); + + drives.append(drive); + systems.append(drive); + } + + if (design->quantum_drive) { + quantum_drive = new(__FILE__,__LINE__) QuantumDrive(*design->quantum_drive); + quantum_drive->SetShip(this); + quantum_drive->SetID(sys_id++); + + int src_index = quantum_drive->GetSourceIndex(); + if (src_index >= 0 && src_index < reactors.size()) + reactors[src_index]->AddClient(quantum_drive); + + quantum_drive->SetShip(this); + systems.append(quantum_drive); + } + + if (design->farcaster) { + farcaster = new(__FILE__,__LINE__) Farcaster(*design->farcaster); + farcaster->SetShip(this); + farcaster->SetID(sys_id++); + + int src_index = farcaster->GetSourceIndex(); + if (src_index >= 0 && src_index < reactors.size()) + reactors[src_index]->AddClient(farcaster); + + farcaster->SetShip(this); + systems.append(farcaster); + } + + if (design->thruster) { + thruster = new(__FILE__,__LINE__) Thruster(*design->thruster); + thruster->SetShip(this); + thruster->SetID(sys_id++); + + int src_index = thruster->GetSourceIndex(); + if (src_index >= 0 && src_index < reactors.size()) + reactors[src_index]->AddClient(thruster); + + thruster->SetShip(this); + systems.append(thruster); + } + + if (design->shield) { + shield = new(__FILE__,__LINE__) Shield(*design->shield); + shield->SetShip(this); + shield->SetID(sys_id++); + + int src_index = shield->GetSourceIndex(); + if (src_index >= 0 && src_index < reactors.size()) + reactors[src_index]->AddClient(shield); + + if (design->shield_model) { + shieldRep = new(__FILE__,__LINE__) ShieldRep; + shieldRep->UseModel(design->shield_model); + } + + systems.append(shield); + } + + for (i = 0; i < design->flight_decks.size(); i++) { + FlightDeck* deck = new(__FILE__,__LINE__) FlightDeck(*design->flight_decks[i]); + deck->SetShip(this); + deck->SetCarrier(this); + deck->SetID(sys_id++); + deck->SetIndex(i); + + int src_index = deck->GetSourceIndex(); + if (src_index >= 0 && src_index < reactors.size()) + reactors[src_index]->AddClient(deck); + + flight_decks.append(deck); + systems.append(deck); + } + + if (design->flight_decks.size() > 0) { + if (!hangar) { + hangar = new(__FILE__,__LINE__) Hangar; + hangar->SetShip(this); + } + } + + if (design->squadrons.size() > 0) { + if (!hangar) { + hangar = new(__FILE__,__LINE__) Hangar; + hangar->SetShip(this); + } + + for (i = 0; i < design->squadrons.size(); i++) { + ShipSquadron* s = design->squadrons[i]; + hangar->CreateSquadron(s->name, 0, s->design, s->count, GetIFF(), 0, 0, s->avail); + } + } + + if (design->gear) { + gear = new(__FILE__,__LINE__) LandingGear(*design->gear); + gear->SetShip(this); + gear->SetID(sys_id++); + + int src_index = gear->GetSourceIndex(); + if (src_index >= 0 && src_index < reactors.size()) + reactors[src_index]->AddClient(gear); + + systems.append(gear); + } + + if (design->sensor) { + sensor = new(__FILE__,__LINE__) Sensor(*design->sensor); + sensor->SetShip(this); + sensor->SetID(sys_id++); + + int src_index = sensor->GetSourceIndex(); + if (src_index >= 0 && src_index < reactors.size()) + reactors[src_index]->AddClient(sensor); + + if (IsStarship() || IsStatic() || !strncmp(design->name, "Camera", 6)) + sensor->SetMode(Sensor::CST); + + systems.append(sensor); + } + + int wep_index = 1; + + for (i = 0; i < design->weapons.size(); i++) { + Weapon* gun = new(__FILE__,__LINE__) Weapon(*design->weapons[i]); + gun->SetID(sys_id++); + gun->SetOwner(this); + gun->SetIndex(wep_index++); + + int src_index = gun->GetSourceIndex(); + if (src_index >= 0 && src_index < reactors.size()) + reactors[src_index]->AddClient(gun); + + WeaponGroup* group = FindWeaponGroup(gun->Group()); + group->AddWeapon(gun); + group->SetAbbreviation(gun->Abbreviation()); + + systems.append(gun); + + if (IsDropship() && gun->GetTurret()) + gun->SetFiringOrders(Weapon::POINT_DEFENSE); + else + gun->SetFiringOrders(Weapon::MANUAL); + } + + int loadout_size = design->hard_points.size(); + + if (load && loadout_size > 0) { + loadout = new(__FILE__,__LINE__) int[loadout_size]; + + for (i = 0; i < loadout_size; i++) { + int mounted_weapon = loadout[i] = load[i]; + + if (mounted_weapon < 0) + continue; + + Weapon* missile = design->hard_points[i]->CreateWeapon(mounted_weapon); + + if (missile) { + missile->SetID(sys_id++); + missile->SetOwner(this); + missile->SetIndex(wep_index++); + + WeaponGroup* group = FindWeaponGroup(missile->Group()); + group->AddWeapon(missile); + group->SetAbbreviation(missile->Abbreviation()); + + systems.append(missile); + } + } + } + + if (weapons.size() > 1) { + primary = -1; + secondary = -1; + + for (i = 0; i < weapons.size(); i++) { + WeaponGroup* group = weapons[i]; + if (group->IsPrimary() && primary < 0) { + primary = i; + + // turrets on fighters are set to point defense by default, + // this forces the primary turret back to manual control + group->SetFiringOrders(Weapon::MANUAL); + } + + else if (group->IsMissile() && secondary < 0) { + secondary = i; + } + } + + if (primary < 0) primary = 0; + if (secondary < 0) secondary = 1; + + if (weapons.size() > 4) { + ::Print("WARNING: Ship '%s' type '%s' has %d wep groups (max=4)\n", + Name(), DesignName(), weapons.size()); + } + } + + if (design->decoy) { + decoy = new(__FILE__,__LINE__) Weapon(*design->decoy); + decoy->SetOwner(this); + decoy->SetID(sys_id++); + decoy->SetIndex(wep_index++); + + int src_index = decoy->GetSourceIndex(); + if (src_index >= 0 && src_index < reactors.size()) + reactors[src_index]->AddClient(decoy); + + systems.append(decoy); + } + + for (i = 0; i < design->navlights.size(); i++) { + NavLight* navlight = new(__FILE__,__LINE__) NavLight(*design->navlights[i]); + navlight->SetShip(this); + navlight->SetID(sys_id++); + navlight->SetOffset(((DWORD) this) << 2); + navlights.append(navlight); + systems.append(navlight); + } + + if (design->navsys) { + navsys = new(__FILE__,__LINE__) NavSystem(*design->navsys); + navsys->SetShip(this); + navsys->SetID(sys_id++); + + int src_index = navsys->GetSourceIndex(); + if (src_index >= 0 && src_index < reactors.size()) + reactors[src_index]->AddClient(navsys); + + systems.append(navsys); + } + + if (design->probe) { + probe = new(__FILE__,__LINE__) Weapon(*design->probe); + probe->SetOwner(this); + probe->SetID(sys_id++); + probe->SetIndex(wep_index++); + + int src_index = probe->GetSourceIndex(); + if (src_index >= 0 && src_index < reactors.size()) + reactors[src_index]->AddClient(probe); + + systems.append(probe); + } + + for (i = 0; i < design->computers.size(); i++) { + Computer* comp = 0; + + if (design->computers[i]->Subtype() == Computer::FLIGHT) { + flcs = new(__FILE__,__LINE__) FlightComp(*design->computers[i]); + + flcs->SetShip(this); + flcs->SetMode(flcs_mode); + flcs->SetVelocityLimit(vlimit); + + if (thruster) + flcs->SetTransLimit(thruster->TransXLimit(), + thruster->TransYLimit(), + thruster->TransZLimit()); + else + flcs->SetTransLimit(design->trans_x, + design->trans_y, + design->trans_z); + + comp = flcs; + } + else { + comp = new(__FILE__,__LINE__) Computer(*design->computers[i]); + } + + comp->SetShip(this); + comp->SetID(sys_id++); + int src_index = comp->GetSourceIndex(); + if (src_index >= 0 && src_index < reactors.size()) + reactors[src_index]->AddClient(comp); + + computers.append(comp); + systems.append(comp); + } + + radio_orders = new(__FILE__,__LINE__) Instruction("", Point(0,0,0)); + + // Load Detail Set: + for (i = 0; i < DetailSet::MAX_DETAIL; i++) { + if (design->models[i].size() > 0) { + Solid* solid = new(__FILE__,__LINE__) ShipSolid(this); + solid->UseModel(design->models[i].at(0)); + solid->CreateShadows(1); + + Point* offset = 0; + Point* spin = 0; + + if (design->offsets[i].size() > 0) + offset = new(__FILE__,__LINE__) Point(*design->offsets[i].at(0)); + + if (design->spin_rates.size() > 0) + spin = new(__FILE__,__LINE__) Point(*design->spin_rates.at(0)); + + detail_level = detail.DefineLevel(design->feature_size[i], solid, offset, spin); + } + + if (design->models[i].size() > 1) { + for (int n = 1; n < design->models[i].size(); n++) { + Solid* solid = new(__FILE__,__LINE__) ShipSolid(this); //Solid; + solid->UseModel(design->models[i].at(n)); + solid->CreateShadows(1); + + Point* offset = 0; + Point* spin = 0; + + if (design->offsets[i].size() > n) + offset = new(__FILE__,__LINE__) Point(*design->offsets[i].at(n)); + + if (design->spin_rates.size() > n) + spin = new(__FILE__,__LINE__) Point(*design->spin_rates.at(n)); + + detail.AddToLevel(detail_level, solid, offset, spin); + } + } + } + + // start with lowest available detail: + detail_level = 0; // this is highest -> detail.NumLevels()-1); + rep = detail.GetRep(detail_level); + + if (design->cockpit_model) { + cockpit = new(__FILE__,__LINE__) Solid; + cockpit->UseModel(design->cockpit_model); + cockpit->SetForeground(true); + } + + if (design->main_drive >= 0 && design->main_drive < drives.size()) + main_drive = drives[design->main_drive]; + + // only use light from drives: + light = 0; + + // setup starship helm stuff: + if (IsStarship()) { + flcs_mode = FLCS_HELM; + } + + // initialize the AI: + dir = 0; + SetControls(0); + + for (i = 0; i < 4; i++) { + missile_id[i] = 0; + missile_eta[i] = 0; + trigger[i] = false; + } +} + +// +--------------------------------------------------------------------+ + +Ship::~Ship() +{ + // the loadout can not be cleared during Destroy, because it + // is needed after Destroy to create the re-spawned ship + + delete [] loadout; + loadout = 0; + + Destroy(); +} + +// +--------------------------------------------------------------------+ + +void +Ship::Destroy() +{ + // destroy fighters on deck: + ListIter deck = flight_decks; + while (++deck) { + for (int i = 0; i < deck->NumSlots(); i++) { + Ship* s = deck->GetShip(i); + + if (s && !s->IsDying() && !s->IsDead()) { + if (sim && sim->IsActive()) { + s->DeathSpiral(); + } + else { + s->transition_type = TRANSITION_DEAD; + s->Destroy(); + } + } + } + } + + if (element) { + // mission ending for this ship, evaluate objectives one last time: + for (int i = 0; i < element->NumObjectives(); i++) { + Instruction* obj = element->GetObjective(i); + + if (obj->Status() <= Instruction::ACTIVE) { + obj->Evaluate(this); + } + } + + combat_unit = element->GetCombatUnit(); + SetElement(0); + } + + delete [] track; + track = 0; + + delete shield; + shield = 0; + delete sensor; + sensor = 0; + delete navsys; + navsys = 0; + delete thruster; + thruster = 0; + delete farcaster; + farcaster = 0; + delete quantum_drive; + quantum_drive = 0; + delete decoy; + decoy = 0; + delete probe; + probe = 0; + delete gear; + gear = 0; + + main_drive = 0; + flcs = 0; + + // repair queue does not own the systems under repair: + repair_queue.clear(); + + navlights.destroy(); + flight_decks.destroy(); + computers.destroy(); + weapons.destroy(); + drives.destroy(); + reactors.destroy(); + + // this is now a list of dangling pointers: + systems.clear(); + + delete hangar; + hangar = 0; + + // this also destroys the rep: + detail.Destroy(); + rep = 0; + + GRAPHIC_DESTROY(cockpit); + GRAPHIC_DESTROY(shieldRep); + LIGHT_DESTROY(light); + + delete launch_point; + launch_point = 0; + + delete radio_orders; + radio_orders = 0; + + delete dir; + dir = 0; + + delete killer; + killer = 0; + + // inbound slot is deleted by flight deck: + inbound = 0; + + life = 0; + Notify(); +} + +// +--------------------------------------------------------------------+ + +void +Ship::Initialize() +{ + ShipDesign::Initialize(); + Thruster::Initialize(); +} + +// +--------------------------------------------------------------------+ + +void +Ship::Close() +{ + ShipDesign::Close(); + Thruster::Close(); +} + +void +Ship::SetupAgility() +{ + const float ROLL_SPEED = (float)(PI * 0.1500); + const float PITCH_SPEED = (float)(PI * 0.0250); + const float YAW_SPEED = (float)(PI * 0.0250); + + drag = design->drag; + dr_drg = design->roll_drag; + dp_drg = design->pitch_drag; + dy_drg = design->yaw_drag; + + if (IsDying()) { + drag = 0.0f; + dr_drg *= 0.25f; + dp_drg *= 0.25f; + dy_drg *= 0.25f; + } + + if (flight_model > 0) { + drag = design->arcade_drag; + thrust *= 10.0f; + } + + float yaw_air_factor = 1.0f; + + if (IsAirborne()) { + bool grounded = AltitudeAGL() < Radius()/2; + + if (flight_model > 0) { + drag *= 2.0f; + + if (gear && gear->GetState() != LandingGear::GEAR_UP) + drag *= 2.0f; + + if (grounded) + drag *= 3.0f; + } + + else { + if (Class() != LCA) + yaw_air_factor = 0.3f; + + double rho = GetDensity(); + double speed = Velocity().length(); + + agility = design->air_factor * rho * speed - wep_resist; + + if (grounded && agility < 0) + agility = 0; + + else if (!grounded && agility < 0.5 * design->agility) + agility = 0.5 * design->agility; + + else if (agility > 2 * design->agility) + agility = 2 * design->agility; + + // undercarriage aerodynamic drag + if (gear && gear->GetState() != LandingGear::GEAR_UP) + drag *= 5.0f; + + // wheel rolling friction + if (grounded) + drag *= 10.0f; + + // dead engine drag ;-) + if (thrust < 10) + drag *= 5.0f; + } + } + + else { + agility = design->agility - wep_resist; + + if (agility < 0.5 * design->agility) + agility = 0.5 * design->agility; + + if (flight_model == 0) + drag = 0.0f; + } + + float rr = (float) (design->roll_rate * PI / 180); + float pr = (float) (design->pitch_rate * PI / 180); + float yr = (float) (design->yaw_rate * PI / 180); + + if (rr == 0) rr = (float) agility * ROLL_SPEED; + if (pr == 0) pr = (float) agility * PITCH_SPEED; + if (yr == 0) yr = (float) agility * YAW_SPEED * yaw_air_factor; + + SetAngularRates(rr, pr, yr); +} + +// +--------------------------------------------------------------------+ + +void +Ship::SetRegion(SimRegion* rgn) +{ + SimObject::SetRegion(rgn); + + const double GRAV = 6.673e-11; + + if (IsGroundUnit()) { + // glue buildings to the terrain: + Point loc = Location(); + Terrain* terrain = region->GetTerrain(); + + if (terrain) { + loc.y = terrain->Height(loc.x, loc.z); + MoveTo(loc); + } + } + + else if (IsAirborne()) { + Orbital* primary = GetRegion()->GetOrbitalRegion()->Primary(); + + double m0 = primary->Mass(); + double r = primary->Radius(); + + SetGravity((float) (GRAV * m0 / (r*r))); + SetBaseDensity(1.0f); + } + + else { + SetGravity(0.0f); + SetBaseDensity(0.0f); + + if (IsStarship()) + flcs_mode = FLCS_HELM; + else + flcs_mode = FLCS_AUTO; + } +} + +// +--------------------------------------------------------------------+ + +int +Ship::GetTextureList(List& textures) +{ + textures.clear(); + + for (int d = 0; d < detail.NumLevels(); d++) { + for (int i = 0; i < detail.NumModels(d); i++) { + Graphic* g = detail.GetRep(d, i); + + if (g->IsSolid()) { + Solid* solid = (Solid*) g; + Model* model = solid->GetModel(); + + if (model) { + for (int n = 0; n < model->NumMaterials(); n++) { + //textures.append(model->textures[n]); + } + } + } + } + } + + return textures.size(); +} + +// +--------------------------------------------------------------------+ + +void +Ship::Activate(Scene& scene) +{ + int i = 0; + SimObject::Activate(scene); + + for (i = 0; i < detail.NumModels(detail_level); i++) { + Graphic* g = detail.GetRep(detail_level, i); + scene.AddGraphic(g); + } + + for (i = 0; i < flight_decks.size(); i++) + scene.AddLight(flight_decks[i]->GetLight()); + + if (shieldRep) + scene.AddGraphic(shieldRep); + + if (cockpit) { + scene.AddForeground(cockpit); + cockpit->Hide(); + } + + Drive* drive = GetDrive(); + if (drive) { + for (i = 0; i < drive->NumEngines(); i++) { + Graphic* flare = drive->GetFlare(i); + if (flare) { + scene.AddGraphic(flare); + } + + Graphic* trail = drive->GetTrail(i); + if (trail) { + scene.AddGraphic(trail); + } + } + } + + Thruster* thruster = GetThruster(); + if (thruster) { + for (i = 0; i < thruster->NumThrusters(); i++) { + Graphic* flare = thruster->Flare(i); + if (flare) { + scene.AddGraphic(flare); + } + + Graphic* trail = thruster->Trail(i); + if (trail) { + scene.AddGraphic(trail); + } + } + } + + for (int n = 0; n < navlights.size(); n++) { + NavLight* navlight = navlights[n]; + for (i = 0; i < navlight->NumBeacons(); i++) { + Graphic* beacon = navlight->Beacon(i); + if (beacon) + scene.AddGraphic(beacon); + } + } + + ListIter g = weapons; + while (++g) { + ListIter w = g->GetWeapons(); + while (++w) { + Solid* turret = w->GetTurret(); + if (turret) { + scene.AddGraphic(turret); + + Solid* turret_base = w->GetTurretBase(); + if (turret_base) + scene.AddGraphic(turret_base); + } + if (w->IsMissile()) { + for (i = 0; i < w->Ammo(); i++) { + Solid* store = w->GetVisibleStore(i); + if (store) + scene.AddGraphic(store); + } + } + } + } + + if (gear && gear->GetState() != LandingGear::GEAR_UP) { + for (int i = 0; i < gear->NumGear(); i++) { + scene.AddGraphic(gear->GetGear(i)); + } + } +} + +void +Ship::Deactivate(Scene& scene) +{ + int i = 0; + SimObject::Deactivate(scene); + + for (i = 0; i < detail.NumModels(detail_level); i++) { + Graphic* g = detail.GetRep(detail_level, i); + scene.DelGraphic(g); + } + + for (i = 0; i < flight_decks.size(); i++) + scene.DelLight(flight_decks[i]->GetLight()); + + if (shieldRep) + scene.DelGraphic(shieldRep); + + if (cockpit) + scene.DelForeground(cockpit); + + Drive* drive = GetDrive(); + if (drive) { + for (i = 0; i < drive->NumEngines(); i++) { + Graphic* flare = drive->GetFlare(i); + if (flare) { + scene.DelGraphic(flare); + } + + Graphic* trail = drive->GetTrail(i); + if (trail) { + scene.DelGraphic(trail); + } + } + } + + Thruster* thruster = GetThruster(); + if (thruster) { + for (i = 0; i < thruster->NumThrusters(); i++) { + Graphic* flare = thruster->Flare(i); + if (flare) { + scene.DelGraphic(flare); + } + + Graphic* trail = thruster->Trail(i); + if (trail) { + scene.DelGraphic(trail); + } + } + } + + for (int n = 0; n < navlights.size(); n++) { + NavLight* navlight = navlights[n]; + for (i = 0; i < navlight->NumBeacons(); i++) { + Graphic* beacon = navlight->Beacon(i); + if (beacon) + scene.DelGraphic(beacon); + } + } + + ListIter g = weapons; + while (++g) { + ListIter w = g->GetWeapons(); + while (++w) { + Solid* turret = w->GetTurret(); + if (turret) { + scene.DelGraphic(turret); + + Solid* turret_base = w->GetTurretBase(); + if (turret_base) + scene.DelGraphic(turret_base); + } + if (w->IsMissile()) { + for (i = 0; i < w->Ammo(); i++) { + Solid* store = w->GetVisibleStore(i); + if (store) + scene.DelGraphic(store); + } + } + } + } + + if (gear) { + for (int i = 0; i < gear->NumGear(); i++) { + scene.DelGraphic(gear->GetGear(i)); + } + } +} + +// +--------------------------------------------------------------------+ + +void +Ship::MatchOrientation(const Ship& s) +{ + Point pos = cam.Pos(); + cam.Clone(s.cam); + cam.MoveTo(pos); + + if (rep) + rep->SetOrientation(cam.Orientation()); + + if (cockpit) + cockpit->SetOrientation(cam.Orientation()); +} + +// +--------------------------------------------------------------------+ + +void +Ship::ClearTrack() +{ + const int DEFAULT_TRACK_LENGTH = 20; // 10 seconds + + if (!track) { + track = new(__FILE__,__LINE__) Point[DEFAULT_TRACK_LENGTH]; + } + + track[0] = Location(); + ntrack = 1; + track_time = Game::GameTime(); +} + +void +Ship::UpdateTrack() +{ + const int DEFAULT_TRACK_UPDATE = 500; // milliseconds + const int DEFAULT_TRACK_LENGTH = 20; // 10 seconds + + DWORD time = Game::GameTime(); + + if (!track) { + track = new(__FILE__,__LINE__) Point[DEFAULT_TRACK_LENGTH]; + track[0] = Location(); + ntrack = 1; + track_time = time; + } + + else if (time - track_time > DEFAULT_TRACK_UPDATE) { + if (Location() != track[0]) { + for (int i = DEFAULT_TRACK_LENGTH-2; i >= 0; i--) + track[i+1] = track[i]; + + track[0] = Location(); + if (ntrack < DEFAULT_TRACK_LENGTH) ntrack++; + } + + track_time = time; + } +} + +Point +Ship::TrackPoint(int i) const +{ + if (track && i < ntrack) + return track[i]; + + return Point(); +} + +// +--------------------------------------------------------------------+ + +const char* +Ship::Abbreviation() const +{ + return design->abrv; +} + +const char* +Ship::DesignName() const +{ + return design->DisplayName(); +} + +const char* +Ship::DesignFileName() const +{ + return design->filename; +} + +const char* +Ship::ClassName() const +{ + return ShipDesign::ClassName(design->type); +} + +const char* +Ship::ClassName(int c) +{ + return ShipDesign::ClassName(c); +} + +int +Ship::ClassForName(const char* name) +{ + return ShipDesign::ClassForName(name); +} + +Ship::CLASSIFICATION +Ship::Class() const +{ + return (CLASSIFICATION) design->type; +} + +bool +Ship::IsGroundUnit() const +{ + return (design->type & GROUND_UNITS) ? true : false; +} + +bool +Ship::IsStarship() const +{ + return (design->type & STARSHIPS) ? true : false; +} + +bool +Ship::IsDropship() const +{ + return (design->type & DROPSHIPS) ? true : false; +} + +bool +Ship::IsStatic() const +{ + return design->type >= STATION; +} + +bool +Ship::IsRogue() const +{ + return ff_count >= 50; +} + +// +--------------------------------------------------------------------+ + +bool +Ship::IsHostileTo(const SimObject* o) const +{ + if (o) { + if (IsRogue()) + return true; + + if (o->Type() == SIM_SHIP) { + Ship* s = (Ship*) o; + + if (s->IsRogue()) + return true; + + if (GetIFF() == 0) { + if (s->GetIFF() > 1) + return true; + } + else { + if (s->GetIFF() > 0 && s->GetIFF() != GetIFF()) + return true; + } + } + + else if (o->Type() == SIM_SHOT || o->Type() == SIM_DRONE) { + Shot* s = (Shot*) o; + + if (GetIFF() == 0) { + if (s->GetIFF() > 1) + return true; + } + else { + if (s->GetIFF() > 0 && s->GetIFF() != GetIFF()) + return true; + } + } + } + + return false; +} + +// +--------------------------------------------------------------------+ + +double +Ship::RepairSpeed() const +{ + return design->repair_speed; +} + +int +Ship::RepairTeams() const +{ + return design->repair_teams; +} + +// +--------------------------------------------------------------------+ + +int +Ship::NumContacts() const +{ + // cast-away const: + return ((Ship*)this)->ContactList().size(); +} + +List& +Ship::ContactList() +{ + if (region) + return region->TrackList(GetIFF()); + + static List empty_contact_list; + return empty_contact_list; +} + +Contact* +Ship::FindContact(SimObject* s) const +{ + if (!s) return 0; + + ListIter c_iter = ((Ship*) this)->ContactList(); + while (++c_iter) { + Contact* c = c_iter.value(); + + if (c->GetShip() == s) + return c; + + if (c->GetShot() == s) + return c; + } + + return 0; +} + +// +--------------------------------------------------------------------+ + +Ship* +Ship::GetController() const +{ + Ship* controller = 0; + + if (carrier) { + // are we in same region as carrier? + if (carrier->GetRegion() == GetRegion()) { + return carrier; + } + + // if not, figure out who our control unit is: + else { + double distance = 10e6; + + ListIter iter = GetRegion()->Carriers(); + while (++iter) { + Ship* test = iter.value(); + if (test->GetIFF() == GetIFF()) { + double d = Point(Location() - test->Location()).length(); + if (d < distance) { + controller = test; + distance = d; + } + } + } + } + } + + if (!controller) { + if (element && element->GetCommander()) + controller = element->GetCommander()->GetShip(1); + } + + return controller; +} + +int +Ship::NumInbound() const +{ + int result = 0; + + for (int i = 0; i < flight_decks.size(); i++) { + result += flight_decks[i]->GetRecoveryQueue().size(); + } + + return result; +} + +int +Ship::NumFlightDecks() const +{ + return flight_decks.size(); +} + +FlightDeck* +Ship::GetFlightDeck(int i) const +{ + if (i >= 0 && i < flight_decks.size()) + return flight_decks[i]; + + return 0; +} + +// +--------------------------------------------------------------------+ + +void +Ship::SetFlightPhase(OP_MODE phase) +{ + if (phase == ACTIVE && !launch_time) { + launch_time = Game::GameTime() + 1; + dock = 0; + + if (element) + element->SetLaunchTime(launch_time); + } + + flight_phase = phase; + + if (flight_phase == ACTIVE) + dock = 0; +} + +void +Ship::SetCarrier(Ship* c, FlightDeck* d) +{ + carrier = c; + dock = d; + + if (carrier) + Observe(carrier); +} + +void +Ship::SetInbound(InboundSlot* s) +{ + inbound = s; + + if (inbound && flight_phase == ACTIVE) { + flight_phase = APPROACH; + + SetCarrier((Ship*) inbound->GetDeck()->GetCarrier(), inbound->GetDeck()); + + HUDView* hud = HUDView::GetInstance(); + + if (hud && hud->GetShip() == this) + hud->SetHUDMode(HUDView::HUD_MODE_ILS); + } +} + +void +Ship::Stow() +{ + if (carrier && carrier->GetHangar()) + carrier->GetHangar()->Stow(this); +} + +bool +Ship::IsGearDown() +{ + if (gear && gear->GetState() == LandingGear::GEAR_DOWN) + return true; + + return false; +} + +void +Ship::LowerGear() +{ + if (gear && gear->GetState() != LandingGear::GEAR_DOWN) { + gear->SetState(LandingGear::GEAR_LOWER); + Scene* scene = 0; + + if (rep) + scene = rep->GetScene(); + + if (scene) { + for (int i = 0; i < gear->NumGear(); i++) { + Solid* g = gear->GetGear(i); + if (g) { + if (detail_level == 0) + scene->DelGraphic(g); + else + scene->AddGraphic(g); + } + } + } + } +} + +void +Ship::RaiseGear() +{ + if (gear && gear->GetState() != LandingGear::GEAR_UP) + gear->SetState(LandingGear::GEAR_RAISE); +} + +void +Ship::ToggleGear() +{ + if (gear) { + if (gear->GetState() == LandingGear::GEAR_UP || + gear->GetState() == LandingGear::GEAR_RAISE) { + LowerGear(); + } + else { + RaiseGear(); + } + } +} + +void +Ship::ToggleNavlights() +{ + bool enable = false; + + for (int i = 0; i < navlights.size(); i++) { + if (i == 0) + enable = !navlights[0]->IsEnabled(); + + if (enable) + navlights[i]->Enable(); + else + navlights[i]->Disable(); + } +} + +// +--------------------------------------------------------------------+ + +int +Ship::CollidesWith(Physical& o) +{ + // bounding spheres test: + Point delta_loc = Location() - o.Location(); + if (delta_loc.length() > radius + o.Radius()) + return 0; + + if (!o.Rep()) + return 1; + + for (int i = 0; i < detail.NumModels(detail_level); i++) { + Graphic* g = detail.GetRep(detail_level, i); + + if (o.Type() == SimObject::SIM_SHIP) { + Ship* o_ship = (Ship*) &o; + int o_det = o_ship->detail_level; + + for (int j = 0; j < o_ship->detail.NumModels(o_det); j++) { + Graphic* o_g = o_ship->detail.GetRep(o_det, j); + + if (g->CollidesWith(*o_g)) + return 1; + } + } + else { + // representation collision test (will do bounding spheres first): + if (g->CollidesWith(*o.Rep())) + return 1; + } + } + + return 0; +} + +// +--------------------------------------------------------------------+ + +static DWORD ff_warn_time = 0; + +int +Ship::HitBy(Shot* shot, Point& impact) +{ + if (shot->Owner() == this || IsNetObserver()) + return HIT_NOTHING; + + if (shot->IsFlak()) + return HIT_NOTHING; + + if (InTransition()) + return HIT_NOTHING; + + Point shot_loc = shot->Location(); + Point delta = shot_loc - Location(); + double dlen = delta.length(); + + Point hull_impact; + int hit_type = HIT_NOTHING; + double dscale = 1; + float scale = design->explosion_scale; + Weapon* wep = 0; + + if (!shot->IsMissile() && !shot->IsBeam()) { + if (dlen > Radius() * 2) + return HIT_NOTHING; + } + + if (scale <= 0) + scale = design->scale; + + if (shot->Owner()) { + const ShipDesign* owner_design = shot->Owner()->Design(); + if (owner_design && owner_design->scale < scale) + scale = (float) owner_design->scale; + } + + + // MISSILE PROCESSING ------------------------------------------------ + + if (shot->IsMissile() && rep) { + if (dlen < rep->Radius()) { + hull_impact = impact = shot_loc; + + hit_type = CheckShotIntersection(shot, impact, hull_impact, &wep); + + if (hit_type) { + if (shot->Damage() > 0) { + DWORD flash = Explosion::HULL_FLASH; + + if ((hit_type & HIT_SHIELD) != 0) + flash = Explosion::SHIELD_FLASH; + + sim->CreateExplosion(impact, Velocity(), flash, 0.3f * scale, scale, region); + sim->CreateExplosion(impact, Point(), Explosion::SHOT_BLAST, 2.0f, scale, region); + } + } + } + + if (hit_type == HIT_NOTHING && shot->IsArmed()) { + SeekerAI* seeker = (SeekerAI*) shot->GetDirector(); + + // if the missile overshot us, take damage proportional to distance + double damage_radius = shot->Design()->lethal_radius; + if (dlen < (damage_radius + Radius())) { + if (seeker && seeker->Overshot()) { + dscale = 1.0 - (dlen / (damage_radius + Radius())); + + if (dscale > 1) + dscale = 1; + + if (ShieldStrength() > 5) { + hull_impact = impact = shot_loc; + + if (shot->Damage() > 0) { + if (shieldRep) + shieldRep->Hit(impact, shot, shot->Damage()*dscale); + sim->CreateExplosion(impact, Velocity(), Explosion::SHIELD_FLASH, 0.20f * scale, scale, region); + sim->CreateExplosion(impact, Point(), Explosion::SHOT_BLAST, 20.0f * scale, scale, region); + } + + hit_type = HIT_BOTH; + } + else { + hull_impact = impact = shot_loc; + + if (shot->Damage() > 0) { + sim->CreateExplosion(impact, Velocity(), Explosion::HULL_FLASH, 0.30f * scale, scale, region); + sim->CreateExplosion(impact, Point(), Explosion::SHOT_BLAST, 20.0f * scale, scale, region); + } + + hit_type = HIT_HULL; + } + } + } + } + } + + // ENERGY WEP PROCESSING --------------------------------------------- + + else { + hit_type = CheckShotIntersection(shot, impact, hull_impact, &wep); + + // impact: + if (hit_type) { + + if (hit_type & HIT_SHIELD) { + if (shieldRep) + shieldRep->Hit(impact, shot, shot->Damage()); + sim->CreateExplosion(impact, Velocity(), Explosion::SHIELD_FLASH, 0.20f * scale, scale, region); + } + + else { + if (shot->IsBeam()) + sim->CreateExplosion(impact, Velocity(), Explosion::BEAM_FLASH, 0.30f * scale, scale, region); + else + sim->CreateExplosion(impact, Velocity(), Explosion::HULL_FLASH, 0.30f * scale, scale, region); + + if (IsStarship()) { + Point burst_vel = hull_impact - Location(); + burst_vel.Normalize(); + burst_vel *= Radius() * 0.5; + burst_vel += Velocity(); + + sim->CreateExplosion(hull_impact, burst_vel, Explosion::HULL_BURST, 0.50f * scale, scale, region, this); + } + } + } + } + + // DAMAGE RESOLUTION ------------------------------------------------- + + if (hit_type != HIT_NOTHING && shot->IsArmed()) { + double effective_damage = shot->Damage() * dscale; + + // FRIENDLY FIRE -------------------------------------------------- + + if (shot->Owner()) { + Ship* s = (Ship*) shot->Owner(); + + if (!IsRogue() && s->GetIFF() == GetIFF() && + s->GetDirector() && s->GetDirector()->Type() < 1000) { + bool was_rogue = s->IsRogue(); + + // only count beam hits once + if (shot->Damage() && !shot->HitTarget() && GetFriendlyFireLevel() > 0) { + int penalty = 1; + + if (shot->IsBeam()) penalty = 5; + else if (shot->IsDrone()) penalty = 7; + + if (s->GetTarget() == this) penalty *= 3; + + s->IncFriendlyFire(penalty); + } + + effective_damage *= GetFriendlyFireLevel(); + + if (Class() > DRONE && s->Class() > DRONE) { + if (s->IsRogue() && !was_rogue) { + RadioMessage* warn = new(__FILE__,__LINE__) RadioMessage(s, this, RadioMessage::DECLARE_ROGUE); + RadioTraffic::Transmit(warn); + } + else if (!s->IsRogue() && (Game::GameTime() - ff_warn_time) > 5000) { + ff_warn_time = Game::GameTime(); + + RadioMessage* warn = 0; + if (s->GetTarget() == this) + warn = new(__FILE__,__LINE__) RadioMessage(s, this, RadioMessage::WARN_TARGETED); + else + warn = new(__FILE__,__LINE__) RadioMessage(s, this, RadioMessage::WARN_ACCIDENT); + + RadioTraffic::Transmit(warn); + } + } + } + } + + if (effective_damage > 0) { + if (!shot->IsBeam() && shot->Design()->damage_type == WeaponDesign::DMG_NORMAL) + ApplyTorque(shot->Velocity() * (float) effective_damage * 1e-6f); + + if (!NetGame::IsNetGameClient()) { + InflictDamage(effective_damage, shot, hit_type, hull_impact); + } + } + } + + return hit_type; +} + +static bool CheckRaySphereIntersection(Point loc, double radius, Point Q, Point w, double len) +{ + Point d0 = loc - Q; + Point d1 = d0.cross(w); + double dlen = d1.length(); // distance of point from line + + if (dlen > radius) // clean miss + return false; // (no impact) + + // possible collision course... + // find the point on the ray that is closest + // to the sphere's location: + Point closest = Q + w * (d0 * w); + + // find the leading edge, and it's distance from the location: + Point leading_edge = Q + w*len; + Point leading_delta = leading_edge - loc; + double leading_dist = leading_delta.length(); + + // if the leading edge is not within the sphere, + if (leading_dist > radius) { + // check to see if the closest point is between the + // ray's endpoints: + Point delta1 = closest - Q; + Point delta2 = leading_edge - Q; // this is w*len + + // if the closest point is not between the leading edge + // and the origin, this ray does not intersect: + if (delta1 * delta2 < 0 || delta1.length() > len) { + return false; + } + } + + return true; +} + +int +Ship::CheckShotIntersection(Shot* shot, Point& ipt, Point& hpt, Weapon** wep) +{ + int hit_type = HIT_NOTHING; + Point shot_loc = shot->Location(); + Point shot_org = shot->Origin(); + Point shot_vpn = shot_loc - shot_org; + double shot_len = shot_vpn.Normalize(); + double blow_len = shot_len; + bool hit_hull = false; + bool easy = false; + + if (shot_len <= 0) + return hit_type; + + if (shot_len < 1000) + shot_len = 1000; + + Point hull_impact; + Point shield_impact; + Point turret_impact; + Point closest; + double d0 = 1e9; + double d1 = 1e9; + double ds = 1e9; + + if (dir && dir->Type() == SteerAI::FIGHTER) { + ShipAI* shipAI = (ShipAI*) dir; + easy = shipAI->GetAILevel() < 2; + } + + if (shieldRep && ShieldStrength() > 5) { + if (shieldRep->CheckRayIntersection(shot_org, shot_vpn, shot_len, shield_impact)) { + hit_type = HIT_SHIELD; + closest = shield_impact; + d0 = Point(closest - shot_org).length(); + ds = d0; + + ipt = shield_impact; + } + } + + if (shieldRep && hit_type == HIT_SHIELD && !shot->IsBeam()) + blow_len = shieldRep->Radius() * 2; + + for (int i = 0; i < detail.NumModels(detail_level) && !hit_hull; i++) { + Solid* s = (Solid*) detail.GetRep(detail_level, i); + if (s) { + if (easy) { + hit_hull = CheckRaySphereIntersection(s->Location(), s->Radius(), shot_org, shot_vpn, shot_len); + } + else { + hit_hull = s->CheckRayIntersection(shot_org, shot_vpn, blow_len, hull_impact)?true:false; + } + } + } + + if (hit_hull) { + if (ShieldStrength() > 5 && !shieldRep) + hit_type = HIT_SHIELD; + + hit_type = hit_type | HIT_HULL; + hpt = hull_impact; + + d1 = Point(hull_impact - shot_org).length(); + + if (d1 < d0) { + closest = hull_impact; + d0 = d1; + } + } + + if (IsStarship() || IsStatic()) { + ListIter g_iter = Weapons(); + while (++g_iter) { + WeaponGroup* g = g_iter.value(); + + if (g->GetDesign() && g->GetDesign()->turret_model) { + double tsize = g->GetDesign()->turret_model->Radius(); + + ListIter w_iter = g->GetWeapons(); + while (++w_iter) { + Weapon* w = w_iter.value(); + + Point tloc = w->GetTurret()->Location(); + + if (CheckRaySphereIntersection(tloc, tsize, shot_org, shot_vpn, shot_len)) { + Point delta = tloc - shot_org; + d1 = delta.length(); + + if (d1 < d0) { + if (wep) *wep = w; + hit_type = hit_type | HIT_TURRET; + turret_impact = tloc; + + d0 = d1; + + closest = turret_impact; + hull_impact = turret_impact; + hpt = turret_impact; + + if (d1 < ds) + ipt = turret_impact; + } + } + } + } + } + } + + // trim beam shots to closest impact point: + if (hit_type && shot->IsBeam()) { + shot->SetBeamPoints(shot_org, closest); + } + + return hit_type; +} + +// +--------------------------------------------------------------------+ + +void +Ship::InflictNetDamage(double damage, Shot* shot) +{ + if (damage > 0 && !IsNetObserver()) { + Physical::InflictDamage(damage, 0); + + // shake by percentage of maximum damage + double newshake = 50 * damage/design->integrity; + const double MAX_SHAKE = 7; + + if (shake < MAX_SHAKE) shake += (float) newshake; + if (shake > MAX_SHAKE) shake = (float) MAX_SHAKE; + } +} + +void +Ship::InflictNetSystemDamage(System* system, double damage, BYTE dmg_type) +{ + if (system && damage > 0 && !IsNetObserver()) { + bool dmg_normal = dmg_type == WeaponDesign::DMG_NORMAL; + bool dmg_power = dmg_type == WeaponDesign::DMG_POWER; + bool dmg_emp = dmg_type == WeaponDesign::DMG_EMP; + + double sys_damage = damage; + double avail = system->Availability(); + + if (dmg_normal || system->IsPowerCritical() && dmg_emp) { + system->ApplyDamage(sys_damage); + master_caution = true; + + if (system->GetExplosionType() && (avail - system->Availability()) >= 50) { + float scale = design->explosion_scale; + if (scale <= 0) + scale = design->scale; + + sim->CreateExplosion(system->MountLocation(), Velocity() * 0.7f, system->GetExplosionType(), 0.2f * scale, scale, region, this, system); + } + } + } +} + +void +Ship::SetNetSystemStatus(System* system, int status, int power, int reactor, double avail) +{ + if (system && !IsNetObserver()) { + if (system->GetPowerLevel() != power) + system->SetPowerLevel(power); + + if (system->GetSourceIndex() != reactor) { + System* s = GetSystem(reactor); + + if (s && s->Type() == System::POWER_SOURCE) { + PowerSource* reac = (PowerSource*) s; + reac->AddClient(system); + } + } + + if (system->Status() != status) { + if (status == System::MAINT) { + ListIter comp = system->GetComponents(); + while (++comp) { + Component* c = comp.value(); + + if (c->Status() < Component::NOMINAL && c->Availability() < 75) { + if (c->SpareCount() && + c->ReplaceTime() <= 300 && + (c->Availability() < 50 || + c->ReplaceTime() < c->RepairTime())) { + + c->Replace(); + } + + else if (c->Availability() >= 50 || c->NumJerried() < 5) { + c->Repair(); + } + } + } + + RepairSystem(system); + } + } + + if (system->Availability() < avail) { + system->SetNetAvail(avail); + } + else { + system->SetNetAvail(-1); + } + } +} + +// +----------------------------------------------------------------------+ + +bool IsWeaponBlockedFriendly(Weapon* w, const SimObject* test) +{ + if (w->GetTarget()) { + Point tgt = w->GetTarget()->Location(); + Point obj = test->Location(); + Point wep = w->MountLocation(); + + Point dir = tgt - wep; + double d = dir.Normalize(); + Point rho = obj - wep; + double r = rho.Normalize(); + + // if target is much closer than obstacle, + // don't worry about friendly fire... + if (d < 1.5 * r) + return false; + + Point dst = dir * r + wep; + double err = (obj - dst).length(); + + if (err < test->Radius() * 1.5) + return true; + } + + return false; +} + +void +Ship::CheckFriendlyFire() +{ + // if no weapons, there is no worry about friendly fire... + if (weapons.size() < 1) + return; + + // only check once each second + if (Game::GameTime() - friendly_fire_time < 1000) + return; + + List w_list; + int i, j; + + // clear the FF blocked flag on all weapons + for (i = 0; i < weapons.size(); i++) { + WeaponGroup* g = weapons[i]; + + for (j = 0; j < g->NumWeapons(); j++) { + Weapon* w = g->GetWeapon(j); + w_list.append(w); + w->SetBlockedFriendly(false); + } + } + + // for each friendly ship within some kind of weapons range, + ListIter c_iter = ContactList(); + while (++c_iter) { + Contact* c = c_iter.value(); + Ship* cship = c->GetShip(); + Shot* cshot = c->GetShot(); + + if (cship && cship != this && (cship->GetIFF() == 0 || cship->GetIFF() == GetIFF())) { + double range = (cship->Location() - Location()).length(); + + if (range > 100e3) + continue; + + // check each unblocked weapon to see if it is blocked by that ship + ListIter iter = w_list; + while (++iter) { + Weapon* w = iter.value(); + + if (!w->IsBlockedFriendly()) + w->SetBlockedFriendly(IsWeaponBlockedFriendly(w, cship)); + } + } + + else if (cshot && cshot->GetIFF() == GetIFF()) { + double range = (cshot->Location() - Location()).length(); + + if (range > 30e3) + continue; + + // check each unblocked weapon to see if it is blocked by that shot + ListIter iter = w_list; + while (++iter) { + Weapon* w = iter.value(); + + if (!w->IsBlockedFriendly()) + w->SetBlockedFriendly(IsWeaponBlockedFriendly(w, cshot)); + } + } + } + + friendly_fire_time = Game::GameTime() + (DWORD) Random(0, 500); +} + +// +----------------------------------------------------------------------+ + +Ship* +Ship::GetLeader() const +{ + if (element) + return element->GetShip(1); + + return (Ship*) this; +} + +int +Ship::GetElementIndex() const +{ + if (element) + return element->FindIndex(this); + + return 0; +} + +int +Ship::GetOrigElementIndex() const +{ + return orig_elem_index; +} + +void +Ship::SetElement(Element* e) +{ + element = e; + + if (element) { + combat_unit = element->GetCombatUnit(); + + if (combat_unit) { + integrity = (float) (design->integrity - combat_unit->GetSustainedDamage()); + } + + orig_elem_index = element->FindIndex(this); + } +} + +void +Ship::SetLaunchPoint(Instruction* pt) +{ + if (pt && !launch_point) + launch_point = pt; +} + +void +Ship::AddNavPoint(Instruction* pt, Instruction* after) +{ + if (GetElementIndex() == 1) + element->AddNavPoint(pt, after); +} + +void +Ship::DelNavPoint(Instruction* pt) +{ + if (GetElementIndex() == 1) + element->DelNavPoint(pt); +} + +void +Ship::ClearFlightPlan() +{ + if (GetElementIndex() == 1) + element->ClearFlightPlan(); +} + +// +----------------------------------------------------------------------+ + +bool +Ship::IsAutoNavEngaged() +{ + if (navsys && navsys->AutoNavEngaged()) + return true; + + return false; +} + +void +Ship::SetAutoNav(bool engage) +{ + if (navsys) { + if (navsys->AutoNavEngaged()) { + if (!engage) + navsys->DisengageAutoNav(); + } + else { + if (engage) + navsys->EngageAutoNav(); + } + + if (sim) + SetControls(sim->GetControls()); + } +} + +void +Ship::CommandMode() +{ + if (!dir || dir->Type() != ShipCtrl::DIR_TYPE) { + const char* msg = "Captain on the bridge"; + RadioVox* vox = new(__FILE__,__LINE__) RadioVox(0, "1", msg); + vox->AddPhrase(msg); + + if (vox && !vox->Start()) { + RadioView::Message( RadioTraffic::TranslateVox(msg) ); + delete vox; + } + + SetControls(sim->GetControls()); + } + + else { + const char* msg = "Exec, you have the conn"; + RadioVox* vox = new(__FILE__,__LINE__) RadioVox(0, "1", msg); + vox->AddPhrase(msg); + + if (vox && !vox->Start()) { + RadioView::Message( RadioTraffic::TranslateVox(msg) ); + delete vox; + } + + SetControls(0); + } +} + +// +----------------------------------------------------------------------+ + +Instruction* +Ship::GetNextNavPoint() +{ + if (launch_point && launch_point->Status() <= Instruction::ACTIVE) + return launch_point; + + if (element) + return element->GetNextNavPoint(); + + return 0; +} + +int +Ship::GetNavIndex(const Instruction* n) +{ + if (element) + return element->GetNavIndex(n); + + return 0; +} + +double +Ship::RangeToNavPoint(const Instruction* n) +{ + double distance = 0; + + if (n && n->Region()) { + Point npt = n->Region()->Location() + n->Location(); + npt -= GetRegion()->Location(); + npt = npt.OtherHand(); // convert from map to sim coords + + distance = Point(npt - Location()).length(); + } + + return distance; +} + +void +Ship::SetNavptStatus(Instruction* navpt, int status) +{ + if (navpt && navpt->Status() != status) { + if (status == Instruction::COMPLETE) { + if (navpt->Action() == Instruction::ASSAULT) + ::Print("Completed Assault\n"); + + else if (navpt->Action() == Instruction::STRIKE) + ::Print("Completed Strike\n"); + } + + navpt->SetStatus(status); + + if (status == Instruction::COMPLETE) + sim->ProcessEventTrigger(MissionEvent::TRIGGER_NAVPT, 0, Name(), GetNavIndex(navpt)); + + if (element) { + int index = element->GetNavIndex(navpt); + + if (index >= 0) + NetUtil::SendNavData(false, element, index-1, navpt); + } + } +} + +List& +Ship::GetFlightPlan() +{ + if (element) + return element->GetFlightPlan(); + + static List dummy_flight_plan; + return dummy_flight_plan; +} + +int +Ship::FlightPlanLength() +{ + if (element) + return element->FlightPlanLength(); + + return 0; +} + +// +--------------------------------------------------------------------+ + +void +Ship::SetWard(Ship* s) +{ + if (ward == s) + return; + + ward = s; + + if (ward) + Observe(ward); +} + +// +--------------------------------------------------------------------+ + +void +Ship::SetTarget(SimObject* targ, System* sub, bool from_net) +{ + if (targ && targ->Type() == SimObject::SIM_SHIP) { + Ship* targ_ship = (Ship*) targ; + + if (targ_ship && targ_ship->IsNetObserver()) + return; + } + + if (target != targ) { + // DON'T IGNORE TARGET, BECAUSE IT MAY BE IN THREAT LIST + target = targ; + if (target) Observe(target); + + if (sim && target) + sim->ProcessEventTrigger(MissionEvent::TRIGGER_TARGET, 0, target->Name()); + } + + subtarget = sub; + + ListIter weapon = weapons; + while (++weapon) { + if (weapon->GetFiringOrders() != Weapon::POINT_DEFENSE) { + weapon->SetTarget(target, subtarget); + + if (sub || !IsStarship()) + weapon->SetSweep(Weapon::SWEEP_NONE); + else + weapon->SetSweep(Weapon::SWEEP_TIGHT); + } + } + + if (!from_net && NetGame::GetInstance()) + NetUtil::SendObjTarget(this); + + // track engagement: + if (target && target->Type() == SimObject::SIM_SHIP) { + Element* elem = GetElement(); + Element* tgt_elem = ((Ship*) target)->GetElement(); + + if (elem) + elem->SetAssignment(tgt_elem); + } +} + +void +Ship::DropTarget() +{ + target = 0; + subtarget = 0; + + SetTarget(target, subtarget); +} + +// +--------------------------------------------------------------------+ + +void +Ship::CycleSubTarget(int dir) +{ + if (!target || target->Type() != SimObject::SIM_SHIP) + return; + + Ship* tgt = (Ship*) target; + + if (tgt->IsDropship()) + return; + + System* subtgt = 0; + + ListIter sys = tgt->Systems(); + + if (dir > 0) { + int latch = (subtarget == 0); + while (++sys) { + if (sys->Type() == System::COMPUTER || // computers are not targetable + sys->Type() == System::SENSOR) // sensors are not targetable + continue; + + if (sys.value() == subtarget) { + latch = 1; + } + + else if (latch) { + subtgt = sys.value(); + break; + } + } + } + + else { + System* prev = 0; + + while (++sys) { + if (sys->Type() == System::COMPUTER || // computers are not targetable + sys->Type() == System::SENSOR) // sensors are not targetable + continue; + + if (sys.value() == subtarget) { + subtgt = prev; + break; + } + + prev = sys.value(); + } + + if (!subtarget) + subtgt = prev; + } + + SetTarget(tgt, subtgt); +} + +// +--------------------------------------------------------------------+ + +void +Ship::ExecFrame(double seconds) +{ + ZeroMemory(trigger, sizeof(trigger)); + altitude_agl = -1.0e6f; + + if (flight_phase < LAUNCH) { + DockFrame(seconds); + return; + } + + if (flight_phase == LAUNCH || + (flight_phase == TAKEOFF && AltitudeAGL() > Radius())) { + SetFlightPhase(ACTIVE); + } + + if (transition_time > 0) { + transition_time -= (float) seconds; + + if (transition_time <= 0) { + CompleteTransition(); + return; + } + + if (rep && IsDying() && killer) { + killer->ExecFrame(seconds); + } + } + + // observers do not run out of power: + if (IsNetObserver()) { + for (int i = 0; i < reactors.size(); i++) + reactors[i]->SetFuelRange(1e6); + } + + if (IsStatic()) { + StatFrame(seconds); + return; + } + + CheckFriendlyFire(); + ExecNavFrame(seconds); + ExecEvalFrame(seconds); + + if (IsAirborne()) { + // are we trying to make orbit? + if (Location().y >= TERRAIN_ALTITUDE_LIMIT) + MakeOrbit(); + } + + if (!InTransition()) { + ExecSensors(seconds); + ExecThrottle(seconds); + } + + else if (IsDropping() || IsAttaining() || IsSkipping()) { + throttle = 100; + } + + if (target && target->Life() == 0) { + DropTarget(); + } + + ExecPhysics(seconds); + + if (!InTransition()) { + UpdateTrack(); + } + + // are we docking? + if (IsDropship()) { + ListIter iter = GetRegion()->Carriers(); + + while (++iter) { + Ship* carrier_target = iter.value(); + + double range = (Location() - carrier_target->Location()).length(); + if (range > carrier_target->Radius() * 1.5) + continue; + + if (carrier_target->GetIFF() == GetIFF() || carrier_target->GetIFF() == 0) { + for (int i = 0; i < carrier_target->NumFlightDecks(); i++) { + if (carrier_target->GetFlightDeck(i)->Recover(this)) + break; + } + } + } + } + + ExecSystems(seconds); + ExecMaintFrame(seconds); + + if (flight_decks.size() > 0) { + Camera* global_cam = CameraDirector::GetInstance()->GetCamera(); + Point global_cam_loc = global_cam->Pos(); + bool disable_shadows = false; + + for (int i = 0; i < flight_decks.size(); i++) { + if (flight_decks[i]->ContainsPoint(global_cam_loc)) + disable_shadows = true; + } + + EnableShadows(!disable_shadows); + } + + if (!_finite(Location().x)) { + DropTarget(); + } + + if (!IsStatic() && !IsGroundUnit() && GetFlightModel() < 2) + CalcFlightPath(); +} + +// +--------------------------------------------------------------------+ + +void +Ship::LaunchProbe() +{ + if (net_observer_mode) + return; + + if (sensor_drone) { + sensor_drone = 0; + } + + if (probe) { + sensor_drone = (Drone*) probe->Fire(); + + if (sensor_drone) + Observe(sensor_drone); + + else if (sim->GetPlayerShip() == this) + Button::PlaySound(Button::SND_REJECT); + } +} + +void +Ship::SetProbe(Drone* d) +{ + if (sensor_drone != d) { + sensor_drone = d; + + if (sensor_drone) + Observe(sensor_drone); + } +} + +void +Ship::ExecSensors(double seconds) +{ + // how visible are we? + DoEMCON(); + + // what can we see? + if (sensor) + sensor->ExecFrame(seconds); + + // can we still see our target? + if (target) { + int target_found = 0; + ListIter c_iter = ContactList(); + while (++c_iter) { + Contact* c = c_iter.value(); + + if (target == c->GetShip() || target == c->GetShot()) { + target_found = 1; + + bool vis = c->Visible(this) || c->Threat(this); + + if (!vis && !c->PasLock() && !c->ActLock()) + DropTarget(); + } + } + + if (!target_found) + DropTarget(); + } +} + +// +--------------------------------------------------------------------+ + +void +Ship::ExecNavFrame(double seconds) +{ + bool auto_pilot = false; + + // update director info string: + SetFLCSMode(flcs_mode); + + if (navsys) { + navsys->ExecFrame(seconds); + + if (navsys->AutoNavEngaged()) { + if (dir && dir->Type() == NavAI::DIR_TYPE) { + NavAI* navai = (NavAI*) dir; + + if (navai->Complete()) { + navsys->DisengageAutoNav(); + SetControls(sim->GetControls()); + } + else { + auto_pilot = true; + } + } + } + } + + // even if we are not on auto pilot, + // have we completed the next navpoint? + + Instruction* navpt = GetNextNavPoint(); + if (navpt && !auto_pilot) { + if (navpt->Region() == GetRegion()) { + double distance = 0; + + Point npt = navpt->Location(); + + if (navpt->Region()) + npt += navpt->Region()->Location(); + + Sim* sim = Sim::GetSim(); + if (sim->GetActiveRegion()) + npt -= sim->GetActiveRegion()->Location(); + + npt = npt.OtherHand(); + + // distance from self to navpt: + distance = Point(npt - Location()).length(); + + if (distance < 10 * Radius()) + SetNavptStatus(navpt, Instruction::COMPLETE); + } + } +} + +// +--------------------------------------------------------------------+ + +void +Ship::ExecEvalFrame(double seconds) +{ + // is it already too late? + if (life == 0 || integrity < 1) return; + + const DWORD EVAL_FREQUENCY = 1000; // once every second + static DWORD last_eval_frame = 0; // one ship per game frame + + if (element && element->NumObjectives() > 0 && + Game::GameTime() - last_eval_time > EVAL_FREQUENCY && + last_eval_frame != Game::Frame()) { + + last_eval_time = Game::GameTime(); + last_eval_frame = Game::Frame(); + + for (int i = 0; i < element->NumObjectives(); i++) { + Instruction* obj = element->GetObjective(i); + + if (obj->Status() <= Instruction::ACTIVE) { + obj->Evaluate(this); + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +Ship::ExecPhysics(double seconds) +{ + if (net_control) { + net_control->ExecFrame(seconds); + Thrust(seconds); // drive flare + } + else { + thrust = (float) Thrust(seconds); + SetupAgility(); + + if (seconds > 0) { + g_force = 0.0f; + } + + if (IsAirborne()) { + Point v1 = velocity; + AeroFrame(seconds); + Point v2 = velocity; + Point dv = v2 - v1 + Point(0, g_accel*seconds, 0); + + if (seconds > 0) { + g_force = (float) (dv * cam.vup() / seconds) / 9.8f; + } + } + + else if (IsDying() || flight_model < 2) { // standard and relaxed modes + Physical::ExecFrame(seconds); + } + + else { // arcade mode + Physical::ArcadeFrame(seconds); + } + } +} + +// +--------------------------------------------------------------------+ + +void +Ship::ExecThrottle(double seconds) +{ + double spool = 75 * seconds; + + if (throttle < throttle_request) + if (throttle_request-throttle < spool) + throttle = throttle_request; + else + throttle += spool; + + else if (throttle > throttle_request) + if (throttle - throttle_request < spool) + throttle = throttle_request; + else + throttle -= spool; +} + +// +--------------------------------------------------------------------+ + +void +Ship::ExecSystems(double seconds) +{ + if (!rep) + return; + + int i; + + ListIter iter = systems; + while (++iter) { + System* sys = iter.value(); + + sys->Orient(this); + + // sensors have already been executed, + // they can not be run twice in a frame! + if (sys->Type() != System::SENSOR) + sys->ExecFrame(seconds); + } + + // hangars and weapon groups are not systems + // they must be executed separately from above + if (hangar) + hangar->ExecFrame(seconds); + + wep_mass = 0.0f; + wep_resist = 0.0f; + + bool winchester_cycle = false; + + for (i = 0; i < weapons.size(); i++) { + WeaponGroup* w_group = weapons[i]; + w_group->ExecFrame(seconds); + + if (w_group->GetTrigger() && w_group->GetFiringOrders() == Weapon::MANUAL) { + + Weapon* gun = w_group->GetSelected(); + + SimObject* gun_tgt = gun->GetTarget(); + + // if no target has been designated for this + // weapon, let it guide on the contact closest + // to its boresight. this must be done before + // firing the weapon. + + if (sensor && gun->Guided() && !gun->Design()->beam && !gun_tgt) { + gun->SetTarget(sensor->AcquirePassiveTargetForMissile(), 0); + } + + gun->Fire(); + + w_group->SetTrigger(false); + w_group->CycleWeapon(); + w_group->CheckAmmo(); + + // was that the last shot from this missile group? + if (w_group->IsMissile() && w_group->Ammo() < 1) { + + // is this the current secondary weapon group? + if (weapons[secondary] == w_group) { + winchester_cycle = true; + } + } + } + + wep_mass += w_group->Mass(); + wep_resist += w_group->Resistance(); + } + + // if we just fired the last shot in the current secondary + // weapon group, auto cycle to another secondary weapon: + if (winchester_cycle) { + int old_secondary = secondary; + + CycleSecondary(); + + // do not winchester-cycle to an A2G missile type, + // or a missile that is also out of ammo, + // keep going! + + while (secondary != old_secondary) { + Weapon* missile = GetSecondary(); + if (missile && missile->CanTarget(Ship::GROUND_UNITS)) + CycleSecondary(); + + else if (weapons[secondary]->Ammo() < 1) + CycleSecondary(); + + else + break; + } + } + + mass = (float) design->mass + wep_mass; + + if (IsDropship()) + agility = (float) design->agility - wep_resist; + + if (shieldRep) { + Solid* solid = (Solid*) rep; + shieldRep->MoveTo(solid->Location()); + shieldRep->SetOrientation(solid->Orientation()); + + bool bubble = false; + if (shield) + bubble = shield->ShieldBubble(); + + if (shieldRep->ActiveHits()) { + shieldRep->Energize(seconds, bubble); + shieldRep->Show(); + } + else { + shieldRep->Hide(); + } + } + + if (cockpit) { + Solid* solid = (Solid*) rep; + + Point cpos = cam.Pos() + + cam.vrt() * bridge_vec.x + + cam.vpn() * bridge_vec.y + + cam.vup() * bridge_vec.z; + + cockpit->MoveTo(cpos); + cockpit->SetOrientation(solid->Orientation()); + } +} + +// +--------------------------------------------------------------------+ + +void +Ship::AeroFrame(double seconds) +{ + float g_save = g_accel; + + if (Class() == LCA) { + lat_thrust = true; + SetGravity(0.0f); + } + + if (AltitudeAGL() < Radius()) { + SetGravity(0.0f); + + // on the ground/runway? + double bottom = 1e9; + double tlevel = Location().y - AltitudeAGL(); + + // taking off or landing? + if (flight_phase < ACTIVE || flight_phase > APPROACH) { + if (dock) + tlevel = dock->MountLocation().y; + } + + if (tlevel < 0) + tlevel = 0; + + if (gear) + bottom = gear->GetTouchDown()-1; + else + bottom = Location().y-6; + + if (bottom < tlevel) + TranslateBy(Point(0, bottom-tlevel, 0)); + } + + // MODEL 2: ARCADE + if (flight_model >= 2) { + Physical::ArcadeFrame(seconds); + } + + // MODEL 1: RELAXED + else if (flight_model == 1) { + Physical::ExecFrame(seconds); + } + + // MODEL 0: STANDARD + else { + // apply drag-torque (i.e. turn ship into + // velocity vector to minimize drag): + + Point vnrm = velocity; + double v = vnrm.Normalize(); + double pitch_deflection = vnrm * cam.vup(); + double yaw_deflection = vnrm * cam.vrt(); + + if (lat_thrust && v < 250) { + } + + else { + if (v < 250) { + double factor = 1.2 + (250 - v) / 100; + + ApplyPitch(pitch_deflection * -factor); + ApplyYaw(yaw_deflection * factor); + + dp += (float) (dp_acc * seconds); + dy += (float) (dy_acc * seconds); + } + + else { + if (fabs(pitch_deflection) > stall) { + ApplyPitch(pitch_deflection * -1.2); + dp += (float) (dp_acc * seconds); + } + + ApplyYaw(yaw_deflection * 2); + dy += (float) (dy_acc * seconds); + } + } + + // compute rest of physics: + Physical::AeroFrame(seconds); + } + + SetGravity(g_save); +} + +// +--------------------------------------------------------------------+ + +void +Ship::LinearFrame(double seconds) +{ + Physical::LinearFrame(seconds); + + if (!IsAirborne() || Class() != LCA) + return; + + // damp lateral movement in atmosphere: + + // side-to-side + if (!trans_x) { + Point transvec = cam.vrt(); + transvec *= (transvec * velocity) * seconds * 0.5; + velocity -= transvec; + } + + // fore-and-aft + if (!trans_y && fabs(thrust < 1)) { + Point transvec = cam.vpn(); + transvec *= (transvec * velocity) * seconds * 0.25; + velocity -= transvec; + } + + // up-and-down + if (!trans_z) { + Point transvec = cam.vup(); + transvec *= (transvec * velocity) * seconds * 0.5; + velocity -= transvec; + } +} + +// +--------------------------------------------------------------------+ + +void +Ship::DockFrame(double seconds) +{ + SelectDetail(seconds); + + if (sim->GetPlayerShip() == this) { + // Make sure the thruster sound is diabled + // when the player is on the runway or catapult + if (thruster) { + thruster->ExecTrans(0,0,0); + } + } + + if (rep) { + // Update the graphic rep and light sources: + // (This is usually done by the physics class, + // but when the ship is in dock, we skip the + // standard physics processing): + rep->MoveTo(cam.Pos()); + rep->SetOrientation(cam.Orientation()); + + if (light) + light->MoveTo(cam.Pos()); + + ListIter iter = systems; + while (++iter) + iter->Orient(this); + + double spool = 75 * seconds; + + if (flight_phase == DOCKING) { + throttle_request = 0; + throttle = 0; + } + + else if (throttle < throttle_request) + if (throttle_request-throttle < spool) + throttle = throttle_request; + else + throttle += spool; + + else if (throttle > throttle_request) + if (throttle - throttle_request < spool) + throttle = throttle_request; + else + throttle -= spool; + + // make sure there is power to run the drive: + for (int i = 0; i < reactors.size(); i++) + reactors[i]->ExecFrame(seconds); + + // count up weapon ammo for status mfd: + for (i = 0; i < weapons.size(); i++) + weapons[i]->ExecFrame(seconds); + + // show drive flare while on catapult: + if (main_drive) { + main_drive->SetThrottle(throttle); + + if (throttle > 0) + main_drive->Thrust(seconds); // show drive flare + } + } + + if (cockpit && !cockpit->Hidden()) { + Solid* solid = (Solid*) rep; + + Point cpos = cam.Pos() + + cam.vrt() * bridge_vec.x + + cam.vpn() * bridge_vec.y + + cam.vup() * bridge_vec.z; + + cockpit->MoveTo(cpos); + cockpit->SetOrientation(solid->Orientation()); + } +} + +// +--------------------------------------------------------------------+ + +void +Ship::StatFrame(double seconds) +{ + if (flight_phase != ACTIVE) { + flight_phase = ACTIVE; + launch_time = Game::GameTime() + 1; + + if (element) + element->SetLaunchTime(launch_time); + } + + if (IsGroundUnit()) { + // glue buildings to the terrain: + Point loc = Location(); + Terrain* terrain = region->GetTerrain(); + + if (terrain) { + loc.y = terrain->Height(loc.x, loc.z); + MoveTo(loc); + } + } + + if (rep) { + rep->MoveTo(cam.Pos()); + rep->SetOrientation(cam.Orientation()); + } + + if (light) { + light->MoveTo(cam.Pos()); + } + + ExecSensors(seconds); + + if (target && target->Life() == 0) { + DropTarget(); + } + + if (dir) dir->ExecFrame(seconds); + + SelectDetail(seconds); + + int i = 0; + + if (rep) { + ListIter iter = systems; + while (++iter) + iter->Orient(this); + + for (i = 0; i < reactors.size(); i++) + reactors[i]->ExecFrame(seconds); + + for (i = 0; i < navlights.size(); i++) + navlights[i]->ExecFrame(seconds); + + for (i = 0; i < weapons.size(); i++) + weapons[i]->ExecFrame(seconds); + + if (farcaster) { + farcaster->ExecFrame(seconds); + + if (navlights.size() == 2) { + if (farcaster->Charge() > 99) { + navlights[0]->Enable(); + navlights[1]->Disable(); + } + else { + navlights[0]->Disable(); + navlights[1]->Enable(); + } + } + } + + if (shield) + shield->ExecFrame(seconds); + + if (hangar) + hangar->ExecFrame(seconds); + + if (flight_decks.size() > 0) { + Camera* global_cam = CameraDirector::GetInstance()->GetCamera(); + Point global_cam_loc = global_cam->Pos(); + bool disable_shadows = false; + + for (i = 0; i < flight_decks.size(); i++) { + flight_decks[i]->ExecFrame(seconds); + + if (flight_decks[i]->ContainsPoint(global_cam_loc)) + disable_shadows = true; + } + + EnableShadows(!disable_shadows); + } + } + + if (shieldRep) { + Solid* solid = (Solid*) rep; + shieldRep->MoveTo(solid->Location()); + shieldRep->SetOrientation(solid->Orientation()); + + bool bubble = false; + if (shield) + bubble = shield->ShieldBubble(); + + if (shieldRep->ActiveHits()) { + shieldRep->Energize(seconds, bubble); + shieldRep->Show(); + } + else { + shieldRep->Hide(); + } + } + + if (!_finite(Location().x)) { + DropTarget(); + } +} + +// +--------------------------------------------------------------------+ + +Graphic* +Ship::Cockpit() const +{ + return cockpit; +} + +void +Ship::ShowCockpit() +{ + if (cockpit) { + cockpit->Show(); + + ListIter g = weapons; + while (++g) { + ListIter w = g->GetWeapons(); + while (++w) { + Solid* turret = w->GetTurret(); + if (turret) { + turret->Show(); + + Solid* turret_base = w->GetTurretBase(); + if (turret_base) + turret_base->Show(); + } + + if (w->IsMissile()) { + for (int i = 0; i < w->Ammo(); i++) { + Solid* store = w->GetVisibleStore(i); + if (store) { + store->Show(); + } + } + } + } + } + } +} + +void +Ship::HideCockpit() +{ + if (cockpit) + cockpit->Hide(); +} + +// +--------------------------------------------------------------------+ + +void +Ship::SelectDetail(double seconds) +{ + detail.ExecFrame(seconds); + detail.SetLocation(GetRegion(), Location()); + + int new_level = detail.GetDetailLevel(); + + if (detail_level != new_level) { + Scene* scene = 0; + + // remove current rep from scene (if necessary): + for (int i = 0; i < detail.NumModels(detail_level); i++) { + Graphic* g = detail.GetRep(detail_level, i); + if (g) { + scene = g->GetScene(); + if (scene) + scene->DelGraphic(g); + } + } + + // switch to new rep: + detail_level = new_level; + rep = detail.GetRep(detail_level); + + // add new rep to scene (if necessary): + if (scene) { + for (int i = 0; i < detail.NumModels(detail_level); i++) { + Graphic* g = detail.GetRep(detail_level, i); + Point s = detail.GetSpin(detail_level, i); + Matrix m = cam.Orientation(); + + m.Pitch(s.x); + m.Yaw(s.z); + m.Roll(s.y); + + scene->AddGraphic(g); + g->MoveTo(cam.Pos() + detail.GetOffset(detail_level, i)); + g->SetOrientation(m); + } + + // show/hide external stores and landing gear... + if (detail.NumLevels() > 0) { + if (gear && (gear->GetState() != LandingGear::GEAR_UP)) { + for (int i = 0; i < gear->NumGear(); i++) { + Solid* g = gear->GetGear(i); + + if (g) { + if (detail_level == 0) + scene->DelGraphic(g); + else + scene->AddGraphic(g); + } + } + } + + ListIter g = weapons; + while (++g) { + ListIter w = g->GetWeapons(); + while (++w) { + Solid* turret = w->GetTurret(); + if (turret) { + if (detail_level == 0) + scene->DelGraphic(turret); + else + scene->AddGraphic(turret); + + Solid* turret_base = w->GetTurretBase(); + if (turret_base) { + if (detail_level == 0) + scene->DelGraphic(turret_base); + else + scene->AddGraphic(turret_base); + } + } + if (w->IsMissile()) { + for (i = 0; i < w->Ammo(); i++) { + Solid* store = w->GetVisibleStore(i); + if (store) { + if (detail_level == 0) + scene->DelGraphic(store); + else + scene->AddGraphic(store); + } + } + } + } + } + } + } + } + + else { + int nmodels = detail.NumModels(detail_level); + + if (nmodels > 1) { + for (int i = 0; i < nmodels; i++) { + Graphic* g = detail.GetRep(detail_level, i); + Point s = detail.GetSpin(detail_level, i); + Matrix m = cam.Orientation(); + + m.Pitch(s.x); + m.Yaw(s.z); + m.Roll(s.y); + + g->MoveTo(cam.Pos() + detail.GetOffset(detail_level, i)); + g->SetOrientation(m); + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +Ship::ShowRep() +{ + for (int i = 0; i < detail.NumModels(detail_level); i++) { + Graphic* g = detail.GetRep(detail_level, i); + g->Show(); + } + + if (gear && (gear->GetState() != LandingGear::GEAR_UP)) { + for (int i = 0; i < gear->NumGear(); i++) { + Solid* g = gear->GetGear(i); + if (g) g->Show(); + } + } + + ListIter g = weapons; + while (++g) { + ListIter w = g->GetWeapons(); + while (++w) { + Solid* turret = w->GetTurret(); + if (turret) { + turret->Show(); + + Solid* turret_base = w->GetTurretBase(); + if (turret_base) + turret_base->Show(); + } + + if (w->IsMissile()) { + for (i = 0; i < w->Ammo(); i++) { + Solid* store = w->GetVisibleStore(i); + if (store) { + store->Show(); + } + } + } + } + } +} + +void +Ship::HideRep() +{ + for (int i = 0; i < detail.NumModels(detail_level); i++) { + Graphic* g = detail.GetRep(detail_level, i); + g->Hide(); + } + + if (gear && (gear->GetState() != LandingGear::GEAR_UP)) { + for (int i = 0; i < gear->NumGear(); i++) { + Solid* g = gear->GetGear(i); + if (g) g->Hide(); + } + } + + ListIter g = weapons; + while (++g) { + ListIter w = g->GetWeapons(); + while (++w) { + Solid* turret = w->GetTurret(); + if (turret) { + turret->Hide(); + + Solid* turret_base = w->GetTurretBase(); + if (turret_base) + turret_base->Hide(); + } + + if (w->IsMissile()) { + for (i = 0; i < w->Ammo(); i++) { + Solid* store = w->GetVisibleStore(i); + if (store) { + store->Hide(); + } + } + } + } + } +} + +void +Ship::EnableShadows(bool enable) +{ + for (int i = 0; i < detail.NumModels(detail_level); i++) { + Graphic* g = detail.GetRep(detail_level, i); + + if (g->IsSolid()) { + Solid* s = (Solid*) g; + + ListIter iter = s->GetShadows(); + while (++iter) { + Shadow* shadow = iter.value(); + shadow->SetEnabled(enable); + } + } + } +} + +// +--------------------------------------------------------------------+ + +bool +Ship::Update(SimObject* obj) +{ + if (obj == ward) + ward = 0; + + if (obj == target) { + target = 0; + subtarget = 0; + } + + if (obj == carrier) { + carrier = 0; + dock = 0; + inbound = 0; + } + + if (obj->Type() == SimObject::SIM_SHOT || + obj->Type() == SimObject::SIM_DRONE) { + Shot* s = (Shot*) obj; + + if (sensor_drone == s) + sensor_drone = 0; + + if (decoy_list.contains(s)) + decoy_list.remove(s); + + if (threat_list.contains(s)) + threat_list.remove(s); + } + + return SimObserver::Update(obj); +} + +// +--------------------------------------------------------------------+ + +int +Ship::GetFuelLevel() const +{ + if (reactors.size() > 0) { + PowerSource* reactor = reactors[0]; + if (reactor) + return reactor->Charge(); + } + + return 0; +} + +void +Ship::SetThrottle(double percent) +{ + throttle_request = percent; + + if (throttle_request < 0) throttle_request = 0; + else if (throttle_request > 100) throttle_request = 100; + + if (throttle_request < 50) + augmenter = false; +} + +void +Ship::SetAugmenter(bool enable) +{ + if (throttle <= 50) + enable = false; + + if (main_drive && main_drive->MaxAugmenter() <= 0) + enable = false; + + augmenter = enable; +} + +// +--------------------------------------------------------------------+ + +void +Ship::SetTransition(double trans_time, int trans_type, const Point& trans_loc) +{ + transition_time = (float) trans_time; + transition_type = trans_type; + transition_loc = trans_loc; +} + +void +Ship::DropOrbit() +{ + if (IsDropship() && transition_type == TRANSITION_NONE && !IsAirborne()) { + SimRegion* dst_rgn = sim->FindNearestTerrainRegion(this); + + if (dst_rgn && + dst_rgn->GetOrbitalRegion()->Primary() == + GetRegion()->GetOrbitalRegion()->Primary()) { + + transition_time = 10.0f; + transition_type = TRANSITION_DROP_ORBIT; + transition_loc = Location() + Heading() * (-2*Radius()); + + RadioTraffic::SendQuickMessage(this, RadioMessage::BREAK_ORBIT); + SetControls(0); + } + } +} + +void +Ship::MakeOrbit() +{ + if (IsDropship() && transition_type == TRANSITION_NONE && IsAirborne()) { + transition_time = 5.0f; + transition_type = TRANSITION_MAKE_ORBIT; + transition_loc = Location() + Heading() * (-2*Radius()); + + RadioTraffic::SendQuickMessage(this, RadioMessage::MAKE_ORBIT); + SetControls(0); + } +} + +// +--------------------------------------------------------------------+ + +bool +Ship::IsInCombat() +{ + if (IsRogue()) + return true; + + bool combat = false; + + ListIter c_iter = ContactList(); + while (++c_iter) { + Contact* c = c_iter.value(); + Ship* cship = c->GetShip(); + int ciff = c->GetIFF(this); + Point delta = c->Location() - Location(); + double dist = delta.length(); + + if (c->Threat(this) && !cship) { + if (IsStarship()) + combat = dist < 120e3; + else + combat = dist < 60e3; + } + + else if (cship && ciff > 0 && ciff != GetIFF()) { + if (IsStarship() && cship->IsStarship()) + combat = dist < 120e3; + else + combat = dist < 60e3; + } + } + + return combat; +} + +// +--------------------------------------------------------------------+ + +bool +Ship::CanTimeSkip() +{ + bool go = false; + Instruction* navpt = GetNextNavPoint(); + + if (MissionClock() < 10000 || NetGame::IsNetGame()) + return go; + + if (navpt) { + go = true; + + if (navpt->Region() != GetRegion()) + go = false; + + else if (Point(navpt->Location().OtherHand() - Location()).length() < 30e3) + go = false; + } + + if (go) + go = !IsInCombat(); + + return go; +} + +void +Ship::TimeSkip() +{ + if (CanTimeSkip()) { + // go back to regular time before performing the skip: + Game::SetTimeCompression(1); + + transition_time = 7.5f; + transition_type = TRANSITION_TIME_SKIP; + transition_loc = Location() + Heading() * (Velocity().length() * 4); + // 2500; //(8*Radius()); + + if (rand() < 16000) + transition_loc += BeamLine() * (2.5*Radius()); + else + transition_loc += BeamLine() * (-2 *Radius()); + + if (rand() < 8000) + transition_loc += LiftLine() * (-1*Radius()); + else + transition_loc += LiftLine() * (1.8*Radius()); + + SetControls(0); + } + + else if (sim->GetPlayerShip() == this) { + SetAutoNav(true); + } +} + +// +--------------------------------------------------------------------+ + +void +Ship::DropCam(double time, double range) +{ + transition_type = TRANSITION_DROP_CAM; + + if (time > 0) + transition_time = (float) time; + else + transition_time = 10.0f; + + Point offset = Heading() * (Velocity().length() * 5); + double lateral_offset = 2 * Radius(); + double vertical_offset = Radius(); + + if (vertical_offset > 300) + vertical_offset = 300; + + if (rand() < 16000) + lateral_offset *= -1; + + if (rand() < 8000) + vertical_offset *= -1; + + offset += BeamLine() * lateral_offset; + offset += LiftLine() * vertical_offset; + + if (range > 0) + offset *= range; + + transition_loc = Location() + offset; +} + +// +--------------------------------------------------------------------+ + +void +Ship::DeathSpiral() +{ + if (!killer) + killer = new(__FILE__,__LINE__) ShipKiller(this); + + ListIter iter = systems; + while (++iter) + iter->PowerOff(); + + // transfer arcade velocity to newtonian velocity: + if (flight_model >= 2) { + velocity += arcade_velocity; + } + + if (GetIFF() < 100 && !IsGroundUnit()) { + RadioTraffic::SendQuickMessage(this, RadioMessage::DISTRESS); + } + + transition_type = TRANSITION_DEATH_SPIRAL; + + killer->BeginDeathSpiral(); + + transition_time = killer->TransitionTime(); + transition_loc = killer->TransitionLoc(); +} + +// +--------------------------------------------------------------------+ + +void +Ship::CompleteTransition() +{ + int old_type = transition_type; + transition_time = 0.0f; + transition_type = TRANSITION_NONE; + + switch (old_type) { + case TRANSITION_NONE: + case TRANSITION_DROP_CAM: + default: + return; + + case TRANSITION_DROP_ORBIT: { + SetControls(0); + SimRegion* dst_rgn = sim->FindNearestTerrainRegion(this); + Point dst_loc = Location().OtherHand() * 0.20; + dst_loc.x += 6000 * GetElementIndex(); + dst_loc.z = TERRAIN_ALTITUDE_LIMIT * 0.95; + dst_loc += RandomDirection() * 2e3; + + sim->RequestHyperJump(this, dst_rgn, dst_loc, TRANSITION_DROP_ORBIT); + + ShipStats* stats = ShipStats::Find(Name()); + stats->AddEvent(SimEvent::BREAK_ORBIT, dst_rgn->Name()); + } + break; + + case TRANSITION_MAKE_ORBIT: { + SetControls(0); + SimRegion* dst_rgn = sim->FindNearestSpaceRegion(this); + double dist = 200.0e3 + 10.0e3 * GetElementIndex(); + Point esc_vec = dst_rgn->GetOrbitalRegion()->Location() - + dst_rgn->GetOrbitalRegion()->Primary()->Location(); + + esc_vec.z = -100 * GetElementIndex(); + esc_vec.Normalize(); + esc_vec *= -dist; + esc_vec += RandomDirection() * 2e3; + + sim->RequestHyperJump(this, dst_rgn, esc_vec, TRANSITION_MAKE_ORBIT); + + ShipStats* stats = ShipStats::Find(Name()); + stats->AddEvent(SimEvent::MAKE_ORBIT, dst_rgn->Name()); + } + break; + + case TRANSITION_TIME_SKIP: { + Instruction* navpt = GetNextNavPoint(); + + if (navpt) { + Point delta = navpt->Location().OtherHand() - Location(); + Point unit = delta; unit.Normalize(); + Point trans = delta + unit * -20e3; + double dist = trans.length(); + double speed = navpt->Speed(); + + if (speed < 50) speed = 500; + + double etr = dist / speed; + + sim->ResolveTimeSkip(etr); + } + } + break; + + case TRANSITION_DEATH_SPIRAL: + SetControls(0); + transition_type = TRANSITION_DEAD; + break; + } + +} + +bool +Ship::IsAirborne() const +{ + if (region) + return region->Type() == SimRegion::AIR_SPACE; + + return false; +} + +double +Ship::CompassHeading() const +{ + Point heading = Heading(); + double compass_heading = atan2(fabs(heading.x), heading.z); + + if (heading.x < 0) + compass_heading *= -1; + + double result = compass_heading + PI; + + if (result >= 2*PI) + result -= 2*PI; + + return result; +} + +double +Ship::CompassPitch() const +{ + Point heading = Heading(); + return asin(heading.y); +} + +double +Ship::AltitudeMSL() const +{ + return Location().y; +} + +double +Ship::AltitudeAGL() const +{ + if (altitude_agl < -1000) { + Ship* pThis = (Ship*) this; // cast-away const + Point loc = Location(); + + Terrain* terrain = region->GetTerrain(); + + if (terrain) + pThis->altitude_agl = (float) (loc.y - terrain->Height(loc.x, loc.z)); + + else + pThis->altitude_agl = (float) loc.y; + + if (!_finite(altitude_agl)) { + pThis->altitude_agl = 0.0f; + } + } + + return altitude_agl; +} + +double +Ship::GForce() const +{ + return g_force; +} + +// +--------------------------------------------------------------------+ + +WeaponGroup* +Ship::FindWeaponGroup(const char* name) +{ + WeaponGroup* group = 0; + + ListIter iter = weapons; + while (!group && ++iter) + if (!stricmp(iter->Name(), name)) + group = iter.value(); + + if (!group) { + group = new(__FILE__,__LINE__) WeaponGroup(name); + weapons.append(group); + } + + return group; +} + +void +Ship::SelectWeapon(int n, int w) +{ + if (n < weapons.size()) + weapons[n]->SelectWeapon(w); +} + +// +--------------------------------------------------------------------+ + +void +Ship::CyclePrimary() +{ + if (weapons.isEmpty()) + return; + + if (IsDropship() && primary < weapons.size()) { + WeaponGroup* p = weapons[primary]; + Weapon* w = p->GetSelected(); + + if (w && w->GetTurret()) { + p->SetFiringOrders(Weapon::POINT_DEFENSE); + } + } + + int n = primary + 1; + while (n != primary) { + if (n >= weapons.size()) + n = 0; + + if (weapons[n]->IsPrimary()) { + weapons[n]->SetFiringOrders(Weapon::MANUAL); + break; + } + + n++; + } + + primary = n; +} + +// +--------------------------------------------------------------------+ + +void +Ship::CycleSecondary() +{ + if (weapons.isEmpty()) + return; + + int n = secondary + 1; + while (n != secondary) { + if (n >= weapons.size()) + n = 0; + + if (weapons[n]->IsMissile()) + break; + + n++; + } + + secondary = n; + + // automatically switch sensors to appropriate mode: + if (IsAirborne()) { + Weapon* missile = GetSecondary(); + if (missile && missile->CanTarget(Ship::GROUND_UNITS)) + SetSensorMode(Sensor::GM); + else if (sensor && sensor->GetMode() == Sensor::GM) + SetSensorMode(Sensor::STD); + } +} + +int +Ship::GetMissileEta(int index) const +{ + if (index >= 0 && index < 4) + return missile_eta[index]; + + return 0; +} + +void +Ship::SetMissileEta(int id, int eta) +{ + int index = -1; + + // are we tracking this missile's eta? + for (int i = 0; i < 4; i++) + if (id == missile_id[i]) + index = i; + + // if not, can we find an open slot to track it in? + if (index < 0) { + for (int i = 0; i < 4 && index < 0; i++) { + if (missile_eta[i] == 0) { + index = i; + missile_id[i] = id; + } + } + } + + // track the eta: + if (index >= 0 && index < 4) { + if (eta > 3599) + eta = 3599; + + missile_eta[index] = (BYTE) eta; + } +} + +// +--------------------------------------------------------------------+ + +void +Ship::DoEMCON() +{ + ListIter iter = systems; + while (++iter) { + System* s = iter.value(); + s->DoEMCON(emcon); + } + + old_emcon = emcon; +} + +// +--------------------------------------------------------------------+ + +double +Ship::Thrust(double seconds) const +{ + double total_thrust = 0; + + if (main_drive) { + // velocity limiter: + Point H = Heading(); + Point V = Velocity(); + double vmag = V.Normalize(); + double eff_throttle = throttle; + double thrust_factor = 1; + double vfwd = H * V; + bool aug_on = main_drive->IsAugmenterOn(); + + if (vmag > vlimit && vfwd > 0) { + double vmax = vlimit; + if (aug_on) + vmax *= 1.5; + + vfwd = 0.5 * vfwd + 0.5; + + // reduce drive efficiency at high fwd speed: + thrust_factor = (vfwd * pow(vmax,3) / pow(vmag,3)) + (1-vfwd); + } + + if (flcs) + eff_throttle = flcs->Throttle(); + + // square-law throttle curve to increase sensitivity + // at lower throttle settings: + if (flight_model > 1) { + eff_throttle /= 100; + eff_throttle *= eff_throttle; + eff_throttle *= 100; + } + + main_drive->SetThrottle(eff_throttle, augmenter); + total_thrust += thrust_factor * main_drive->Thrust(seconds); + + if (aug_on && shake < 1.5) + ((Ship*) this)->shake = 1.5f; + } + + return total_thrust; +} + +// +--------------------------------------------------------------------+ + +void +Ship::CycleFLCSMode() +{ + switch (flcs_mode) { + case FLCS_MANUAL: SetFLCSMode(FLCS_HELM); break; + case FLCS_AUTO: SetFLCSMode(FLCS_MANUAL); break; + case FLCS_HELM: SetFLCSMode(FLCS_AUTO); break; + + default: + if (IsStarship()) + flcs_mode = (BYTE) FLCS_HELM; + else + flcs_mode = (BYTE) FLCS_AUTO; + break; + } + + // reset helm heading to compass heading when switching + // back to helm mode from manual mode: + + if (flcs_mode == FLCS_HELM) { + if (IsStarship()) { + SetHelmHeading(CompassHeading()); + SetHelmPitch(CompassPitch()); + } + else { + flcs_mode = (BYTE) FLCS_AUTO; + } + } +} + +void +Ship::SetFLCSMode(int mode) +{ + flcs_mode = (BYTE) mode; + + if (IsAirborne()) + flcs_mode = (BYTE) FLCS_MANUAL; + + if (dir && dir->Type() < SteerAI::SEEKER) { + switch (flcs_mode) { + case FLCS_MANUAL: director_info = Game::GetText("flcs.manual"); break; + case FLCS_AUTO: director_info = Game::GetText("flcs.auto"); break; + case FLCS_HELM: director_info = Game::GetText("flcs.helm"); break; + default: director_info = Game::GetText("flcs.fault"); break; + } + + if (!flcs || !flcs->IsPowerOn()) + director_info = Game::GetText("flcs.offline"); + + else if (IsAirborne()) + director_info = Game::GetText("flcs.atmospheric"); + } + + if (flcs) + flcs->SetMode(mode); +} + +int +Ship::GetFLCSMode() const +{ + return (int) flcs_mode; +} + +void +Ship::SetTransX(double t) +{ + float limit = design->trans_x; + + if (thruster) + limit = (float) thruster->TransXLimit(); + + trans_x = (float) t; + + if (trans_x) { + if (trans_x > limit) + trans_x = limit; + else if (trans_x < -limit) + trans_x = -limit; + + // reduce thruster efficiency at high fwd speed: + double vfwd = cam.vrt() * Velocity(); + double vmag = fabs(vfwd); + if (vmag > vlimit) { + if (trans_x > 0 && vfwd > 0 || trans_x < 0 && vfwd < 0) + trans_x *= (float) (pow(vlimit,4) / pow(vmag,4)); + } + } +} + +void +Ship::SetTransY(double t) +{ + float limit = design->trans_y; + + if (thruster) + limit = (float) thruster->TransYLimit(); + + trans_y = (float) t; + + if (trans_y) { + double vmag = Velocity().length(); + + if (trans_y > limit) + trans_y = limit; + else if (trans_y < -limit) + trans_y = -limit; + + // reduce thruster efficiency at high fwd speed: + if (vmag > vlimit) { + double vfwd = cam.vpn() * Velocity(); + + if (trans_y > 0 && vfwd > 0 || trans_y < 0 && vfwd < 0) + trans_y *= (float) (pow(vlimit,4) / pow(vmag,4)); + } + } +} + +void +Ship::SetTransZ(double t) +{ + float limit = design->trans_z; + + if (thruster) + limit = (float) thruster->TransZLimit(); + + trans_z = (float) t; + + if (trans_z) { + + if (trans_z > limit) + trans_z = limit; + else if (trans_z < -limit) + trans_z = -limit; + + // reduce thruster efficiency at high fwd speed: + double vfwd = cam.vup() * Velocity(); + double vmag = fabs(vfwd); + if (vmag > vlimit) { + if (trans_z > 0 && vfwd > 0 || trans_z < 0 && vfwd < 0) + trans_z *= (float) (pow(vlimit,4) / pow(vmag,4)); + } + } +} + +void +Ship::ExecFLCSFrame() +{ + if (flcs) + flcs->ExecSubFrame(); +} + +// +--------------------------------------------------------------------+ + +void +Ship::SetHelmHeading(double h) +{ + while (h < 0) + h += 2*PI; + + while (h >= 2*PI) + h -= 2*PI; + + helm_heading = (float) h; +} + +void +Ship::SetHelmPitch(double p) +{ + const double PITCH_LIMIT = 80 * DEGREES; + + if (p < -PITCH_LIMIT) + p = -PITCH_LIMIT; + + else if (p > PITCH_LIMIT) + p = PITCH_LIMIT; + + helm_pitch = (float) p; +} + +void +Ship::ApplyHelmYaw(double y) +{ + // rotate compass into helm-relative orientation: + double compass = CompassHeading() - helm_heading; + double turn = y * PI/4; + + if (compass > PI) + compass -= 2*PI; + else if (compass < -PI) + compass += 2*PI; + + // if requested turn is more than 170, reject it: + if (fabs(compass + turn) > 170*DEGREES) + return; + + SetHelmHeading(helm_heading + turn); +} + +void +Ship::ApplyHelmPitch(double p) +{ + SetHelmPitch(helm_pitch - p * PI/4); +} + +void +Ship::ApplyPitch(double p) +{ + if (flight_model == 0) { // standard flight model + if (IsAirborne()) + p *= 0.5; + + // command for pitch up is negative + if (p < 0) { + if (alpha > PI/6) { + p *= 0.05; + } + else if (g_force > 12.0) { + double limit = 0.5 - (g_force - 12.0)/10.0; + + if (limit < 0) + p = 0; + else + p *= limit; + } + } + + // command for pitch down is positive + else if (p > 0) { + if (alpha < -PI/8) { + p *= 0.05; + } + else if (g_force < -3) { + p *= 0.1; + } + } + } + + Physical::ApplyPitch(p); +} + +// +--------------------------------------------------------------------+ + +bool +Ship::FireWeapon(int n) +{ + bool fired = false; + + if (n >= 0 && !CheckFire()) { + if (n < 4) + trigger[n] = true; + + if (n < weapons.size()) { + weapons[n]->SetTrigger(true); + fired = weapons[n]->GetTrigger(); + } + } + + if (!fired && sim->GetPlayerShip() == this) + Button::PlaySound(Button::SND_REJECT); + + return fired; +} + +bool +Ship::FireDecoy() +{ + Shot* drone = 0; + + if (decoy && !CheckFire()) { + drone = decoy->Fire(); + + if (drone) { + Observe(drone); + decoy_list.append(drone); + } + } + + if (sim->GetPlayerShip() == this) { + if (NetGame::IsNetGame()) { + if (decoy && decoy->Ammo() < 1) + Button::PlaySound(Button::SND_REJECT); + } + + else if (!drone) { + Button::PlaySound(Button::SND_REJECT); + } + } + + return drone != 0; +} + +void +Ship::AddActiveDecoy(Drone* drone) +{ + if (drone) { + Observe(drone); + decoy_list.append(drone); + } +} + +Weapon* +Ship::GetPrimary() const +{ + if (weapons.size() > primary) + return weapons[primary]->GetSelected(); + return 0; +} + +Weapon* +Ship::GetSecondary() const +{ + if (weapons.size() > secondary) + return weapons[secondary]->GetSelected(); + return 0; +} + +Weapon* +Ship::GetWeaponByIndex(int n) +{ + for (int i = 0; i < weapons.size(); i++) { + WeaponGroup* g = weapons[i]; + + List& wlist = g->GetWeapons(); + for (int j = 0; j < wlist.size(); j++) { + Weapon* w = wlist[j]; + + if (w->GetIndex() == n) { + return w; + } + } + } + + return 0; +} + +WeaponGroup* +Ship::GetPrimaryGroup() const +{ + if (weapons.size() > primary) + return weapons[primary]; + return 0; +} + +WeaponGroup* +Ship::GetSecondaryGroup() const +{ + if (weapons.size() > secondary) + return weapons[secondary]; + return 0; +} + +WeaponDesign* +Ship::GetPrimaryDesign() const +{ + if (weapons.size() > primary) + return (WeaponDesign*) weapons[primary]->GetSelected()->Design(); + return 0; +} + +WeaponDesign* +Ship::GetSecondaryDesign() const +{ + if (weapons.size() > secondary) + return (WeaponDesign*) weapons[secondary]->GetSelected()->Design(); + return 0; +} + +Weapon* +Ship::GetDecoy() const +{ + return decoy; +} + +List& +Ship::GetActiveDecoys() +{ + return decoy_list; +} + +List& +Ship::GetThreatList() +{ + return threat_list; +} + +void +Ship::AddThreat(Shot* s) +{ + if (!threat_list.contains(s)) { + Observe(s); + threat_list.append(s); + } +} + +void +Ship::DropThreat(Shot* s) +{ + if (threat_list.contains(s)) { + threat_list.remove(s); + } +} + +bool +Ship::GetTrigger(int i) const +{ + if (i >= 0) { + if (i < 4) + return trigger[i]; + + else if (i < weapons.size()) + return weapons[i]->GetTrigger(); + } + + return false; +} + +void +Ship::SetTrigger(int i) +{ + if (i >= 0 && !CheckFire()) { + if (i < 4) + trigger[i] = true; + + if (i < weapons.size()) + weapons[i]->SetTrigger(); + } +} + +// +--------------------------------------------------------------------+ + +void +Ship::SetSensorMode(int mode) +{ + if (sensor) + sensor->SetMode((Sensor::Mode) mode); +} + +int +Ship::GetSensorMode() const +{ + if (sensor) + return (int) sensor->GetMode(); + + return 0; +} + +// +--------------------------------------------------------------------+ + +bool +Ship::IsTracking(SimObject* tgt) +{ + if (tgt && sensor) + return sensor->IsTracking(tgt); + + return false; +} + +// +--------------------------------------------------------------------+ + +void +Ship::LockTarget(int type, bool closest, bool hostile) +{ + if (sensor) + SetTarget(sensor->LockTarget(type, closest, hostile)); +} + +// +--------------------------------------------------------------------+ + +void +Ship::LockTarget(SimObject* candidate) +{ + if (sensor) + SetTarget(sensor->LockTarget(candidate)); + else + SetTarget(candidate); +} + +// +--------------------------------------------------------------------+ + +double +Ship::InflictDamage(double damage, Shot* shot, int hit_type, Point impact) +{ + double damage_applied = 0; + + if (Game::Paused() || IsNetObserver() || IsInvulnerable()) + return damage_applied; + + if (Integrity() == 0) // already dead? + return damage_applied; + + const double MAX_SHAKE = 7; + double hull_damage = damage; + bool hit_shield = (hit_type & HIT_SHIELD) != 0; + bool hit_hull = (hit_type & HIT_HULL) != 0; + bool hit_turret = (hit_type & HIT_TURRET) != 0; + + if (impact == Point(0,0,0)) + impact = Location(); + + if (hit_shield && ShieldStrength() > 0) { + hull_damage = shield->DeflectDamage(shot, damage); + + if (shot) { + if (shot->IsBeam()) { + if (design->beam_hit_sound_resource) { + if (Game::RealTime() - last_beam_time > 400) { + Sound* s = design->beam_hit_sound_resource->Duplicate(); + s->SetLocation(impact); + s->SetVolume(AudioConfig::EfxVolume()); + s->Play(); + + last_beam_time = Game::RealTime(); + } + } + } + + else { + if (design->bolt_hit_sound_resource) { + if (Game::RealTime() - last_bolt_time > 400) { + Sound* s = design->bolt_hit_sound_resource->Duplicate(); + s->SetLocation(impact); + s->SetVolume(AudioConfig::EfxVolume()); + s->Play(); + + last_bolt_time = Game::RealTime(); + } + } + } + } + } + + if (hit_hull) { + hull_damage = InflictSystemDamage(hull_damage, shot, impact); + + int damage_type = WeaponDesign::DMG_NORMAL; + + if (shot && shot->Design()) + damage_type = shot->Design()->damage_type; + + if (damage_type == WeaponDesign::DMG_NORMAL) { + damage_applied = hull_damage; + Physical::InflictDamage(damage_applied, 0); + NetUtil::SendObjDamage(this, damage_applied, shot); + } + } + + else if (hit_turret) { + hull_damage = InflictSystemDamage(hull_damage, shot, impact) * 0.3; + + int damage_type = WeaponDesign::DMG_NORMAL; + + if (shot && shot->Design()) + damage_type = shot->Design()->damage_type; + + if (damage_type == WeaponDesign::DMG_NORMAL) { + damage_applied = hull_damage; + Physical::InflictDamage(damage_applied, 0); + NetUtil::SendObjDamage(this, damage_applied, shot); + } + } + + // shake by percentage of maximum damage + double newshake = 50 * damage/design->integrity; + + if (shake < MAX_SHAKE) shake += (float) newshake; + if (shake > MAX_SHAKE) shake = (float) MAX_SHAKE; + + // start fires as needed: + if ((IsStarship() || IsGroundUnit() || RandomChance(1,3)) && hit_hull && damage_applied > 0) { + int old_integrity = (int) ((integrity + damage_applied)/design->integrity * 10); + int new_integrity = (int) ((integrity )/design->integrity * 10); + + if (new_integrity < 5 && new_integrity < old_integrity) { + // need accurate hull impact for starships, + if (rep) { + Point detonation = impact*2 - Location(); + Point direction = Location() - detonation; + double distance = direction.Normalize() * 3; + rep->CheckRayIntersection(detonation, direction, distance, impact); + + // pull fire back into hull a bit: + direction = Location() - impact; + impact += direction * 0.2; + + float scale = (float) design->scale; + + if (IsDropship()) + sim->CreateExplosion(impact, Velocity(), Explosion::SMOKE_TRAIL, 0.01f * scale, 0.5f * scale, region, this); + else + sim->CreateExplosion(impact, Velocity(), Explosion::HULL_FIRE, 0.10f * scale, scale, region, this); + } + } + } + + return damage_applied; +} + +double +Ship::InflictSystemDamage(double damage, Shot* shot, Point impact) +{ + if (IsNetObserver()) + return 0; + + // find the system that is closest to the impact point: + System* system = 0; + double distance = 1e6; + double blast_radius = 0; + int dmg_type = 0; + + if (shot) + dmg_type = shot->Design()->damage_type; + + bool dmg_normal = dmg_type == WeaponDesign::DMG_NORMAL; + bool dmg_power = dmg_type == WeaponDesign::DMG_POWER; + bool dmg_emp = dmg_type == WeaponDesign::DMG_EMP; + double to_level = 0; + + if (dmg_power) { + to_level = 1 - damage / 1e4; + + if (to_level < 0) + to_level = 0; + } + + // damage caused by weapons applies to closest system: + if (shot) { + if (shot->IsMissile()) + blast_radius = 300; + + ListIter iter = systems; + while (++iter) { + System* candidate = iter.value(); + double sysrad = candidate->Radius(); + + if (dmg_power) + candidate->DrainPower(to_level); + + if (sysrad > 0 || dmg_emp && candidate->IsPowerCritical()) { + double test_distance = (impact - candidate->MountLocation()).length(); + + if ((test_distance-blast_radius) < sysrad || dmg_emp && candidate->IsPowerCritical()) { + if (test_distance < distance) { + system = candidate; + distance = test_distance; + } + } + } + } + + // if a system was in range of the blast, assess the damage: + if (system) { + double hull_damage = damage * system->HullProtection(); + double sys_damage = damage - hull_damage; + double avail = system->Availability(); + + if (dmg_normal || system->IsPowerCritical() && dmg_emp) { + system->ApplyDamage(sys_damage); + NetUtil::SendSysDamage(this, system, sys_damage); + + master_caution = true; + + if (dmg_normal) { + if (sys_damage < 100) + damage -= sys_damage; + else + damage -= 100; + } + + if (system->GetExplosionType() && (avail - system->Availability()) >= 50) { + float scale = design->explosion_scale; + if (scale <= 0) + scale = design->scale; + + sim->CreateExplosion(system->MountLocation(), Velocity() * 0.7f, system->GetExplosionType(), 0.2f * scale, scale, region, this, system); + } + } + } + } + + // damage caused by collision applies to all systems: + else { + // ignore incidental bumps: + if (damage < 100) + return damage; + + ListIter iter = systems; + while (++iter) { + System* sys = iter.value(); + + if (rand() > 24000) { + double base_damage = 33.0 + rand()/1000.0; + double sys_damage = base_damage * (1.0 - sys->HullProtection()); + sys->ApplyDamage(sys_damage); + NetUtil::SendSysDamage(this, system, sys_damage); + damage -= sys_damage; + + master_caution = true; + } + } + + // just in case this ship has lots of systems... + if (damage < 0) + damage = 0; + } + + // return damage remaining + return damage; +} + +// +--------------------------------------------------------------------+ + +int +Ship::ShieldStrength() const +{ + if (!shield) return 0; + + return (int) shield->ShieldLevel(); +} + +int +Ship::HullStrength() const +{ + if (design) + return (int) (Integrity() / design->integrity * 100); + + return 10; +} + +// +--------------------------------------------------------------------+ + +System* +Ship::GetSystem(int sys_id) +{ + System* s = 0; + + if (sys_id >= 0) { + if (sys_id < systems.size()) { + s = systems[sys_id]; + if (s->GetID() == sys_id) + return s; + } + + ListIter iter = systems; + while (++iter) { + s = iter.value(); + + if (s->GetID() == sys_id) + return s; + } + } + + return 0; +} + +// +--------------------------------------------------------------------+ + +void +Ship::RepairSystem(System* sys) +{ + if (!repair_queue.contains(sys)) { + repair_queue.append(sys); + sys->Repair(); + } +} + +// +--------------------------------------------------------------------+ + +void +Ship::IncreaseRepairPriority(int task_index) +{ + if (task_index > 0 && task_index < repair_queue.size()) { + System* task1 = repair_queue.at(task_index-1); + System* task2 = repair_queue.at(task_index); + + repair_queue.at(task_index-1) = task2; + repair_queue.at(task_index) = task1; + } +} + +void +Ship::DecreaseRepairPriority(int task_index) +{ + if (task_index >= 0 && task_index < repair_queue.size()-1) { + System* task1 = repair_queue.at(task_index); + System* task2 = repair_queue.at(task_index+1); + + repair_queue.at(task_index) = task2; + repair_queue.at(task_index+1) = task1; + } +} + +// +--------------------------------------------------------------------+ + +void +Ship::ExecMaintFrame(double seconds) +{ + // is it already too late? + if (life == 0 || integrity < 1) return; + + const DWORD REPAIR_FREQUENCY = 5000; // once every five seconds + static DWORD last_repair_frame = 0; // one ship per game frame + + if (auto_repair && + Game::GameTime() - last_repair_time > REPAIR_FREQUENCY && + last_repair_frame != Game::Frame()) { + + last_repair_time = Game::GameTime(); + last_repair_frame = Game::Frame(); + + ListIter iter = systems; + while (++iter) { + System* sys = iter.value(); + + if (sys->Status() != System::NOMINAL) { + bool started_repairs = false; + + // emergency power routing: + if (sys->Type() == System::POWER_SOURCE && sys->Availability() < 33) { + PowerSource* src = (PowerSource*) sys; + PowerSource* dst = 0; + + for (int i = 0; i < reactors.size(); i++) { + PowerSource* pwr = reactors[i]; + + if (pwr != src && pwr->Availability() > src->Availability()) { + if (!dst || + (pwr->Availability() > dst->Availability() && + pwr->Charge() > dst->Charge())) + dst = pwr; + } + } + + if (dst) { + while (src->Clients().size() > 0) { + System* s = src->Clients().at(0); + src->RemoveClient(s); + dst->AddClient(s); + } + } + } + + ListIter comp = sys->GetComponents(); + while (++comp) { + Component* c = comp.value(); + + if (c->Status() < Component::NOMINAL && c->Availability() < 75) { + if (c->SpareCount() && + c->ReplaceTime() <= 300 && + (c->Availability() < 50 || + c->ReplaceTime() < c->RepairTime())) { + + c->Replace(); + started_repairs = true; + } + + else if (c->Availability() >= 50 || c->NumJerried() < 5) { + c->Repair(); + started_repairs = true; + } + } + } + + if (started_repairs) + RepairSystem(sys); + } + } + } + + if (repair_queue.size() > 0 && RepairTeams() > 0) { + int team = 0; + ListIter iter = repair_queue; + while (++iter && team < RepairTeams()) { + System* sys = iter.value(); + + sys->ExecMaintFrame(seconds * RepairSpeed()); + team++; + + if (sys->Status() != System::MAINT) { + iter.removeItem(); + + // emergency power routing (restore): + if (sys->Type() == System::POWER_SOURCE && + sys->Status() == System::NOMINAL) { + PowerSource* src = (PowerSource*) sys; + int isrc = reactors.index(src); + + for (int i = 0; i < reactors.size(); i++) { + PowerSource* pwr = reactors[i]; + + if (pwr != src) { + List xfer; + + for (int i = 0; i < pwr->Clients().size(); i++) { + System* s = pwr->Clients().at(i); + + if (s->GetSourceIndex() == isrc) { + xfer.append(s); + } + } + + for (i = 0; i < xfer.size(); i++) { + System* s = xfer.at(i); + pwr->RemoveClient(s); + src->AddClient(s); + } + } + } + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +Ship::SetNetworkControl(Director* net) +{ + net_control = net; + + delete dir; + dir = 0; + + if (!net_control && GetIFF() < 100) { + if (IsStatic()) + dir = 0; + else if (IsStarship()) + dir = SteerAI::Create(this, SteerAI::STARSHIP); + else + dir = SteerAI::Create(this, SteerAI::FIGHTER); + } +} + +void +Ship::SetControls(MotionController* m) +{ + if (IsDropping() || IsAttaining()) { + if (dir && dir->Type() != DropShipAI::DIR_TYPE) { + delete dir; + dir = new(__FILE__,__LINE__) DropShipAI(this); + } + + return; + } + + else if (IsSkipping()) { + if (navsys && sim->GetPlayerShip() == this) + navsys->EngageAutoNav(); + } + + else if (IsDying() || IsDead()) { + if (dir) { + delete dir; + dir = 0; + } + + if (navsys && navsys->AutoNavEngaged()) { + navsys->DisengageAutoNav(); + } + + return; + } + + else if (life == 0) { + if (dir || navsys) { + ::Print("Warning: dying ship '%' still has not been destroyed!\n", name); + delete dir; + dir = 0; + + if (navsys && navsys->AutoNavEngaged()) + navsys->DisengageAutoNav(); + } + + return; + } + + if (navsys && navsys->AutoNavEngaged()) { + NavAI* nav = 0; + + if (dir) { + if (dir->Type() != NavAI::DIR_TYPE) { + delete dir; + dir = 0; + } + else { + nav = (NavAI*) dir; + } + } + + if (!nav) { + nav = new(__FILE__,__LINE__) NavAI(this); + dir = nav; + return; + } + + else if (!nav->Complete()) { + return; + } + } + + if (dir) { + delete dir; + dir = 0; + } + + if (m) { + Keyboard::FlushKeys(); + m->Acquire(); + dir = new(__FILE__,__LINE__) ShipCtrl(this, m); + director_info = Game::GetText("flcs.auto"); + } + else if (GetIFF() < 100) { + if (IsStatic()) + dir = SteerAI::Create(this, SteerAI::GROUND); + + else if (IsStarship() && !IsAirborne()) + dir = SteerAI::Create(this, SteerAI::STARSHIP); + + else + dir = SteerAI::Create(this, SteerAI::FIGHTER); + } +} + +// +--------------------------------------------------------------------+ + +Color +Ship::IFFColor(int iff) +{ + Color c; + + switch (iff) { + case 0: // NEUTRAL, NON-COMBAT + c = Color(192,192,192); + break; + + case 1: // TERELLIAN ALLIANCE + c = Color(70,70,220); + break; + + case 2: // MARAKAN HEGEMONY + c = Color(220,20,20); + break; + + case 3: // BROTHERHOOD OF IRON + c = Color(200,180,20); + break; + + case 4: // ZOLON EMPIRE + c = Color(20,200,20); + break; + + case 5: + c = Color(128, 0, 128); + break; + + case 6: + c = Color(40,192,192); + break; + + default: + c = Color(128,128,128); + break; + } + + return c; +} + +Color +Ship::MarkerColor() const +{ + return IFFColor(IFF_code); +} + +// +--------------------------------------------------------------------+ + +void +Ship::SetIFF(int iff) +{ + IFF_code = iff; + + if (hangar) + hangar->SetAllIFF(iff); + + DropTarget(); + + if (dir && dir->Type() >= 1000) { + SteerAI* ai = (SteerAI*) dir; + ai->DropTarget(); + } +} + +// +--------------------------------------------------------------------+ + +void +Ship::SetRogue(bool r) +{ + bool rogue = IsRogue(); + + ff_count = r ? 1000 : 0; + + if (!rogue && IsRogue()) { + Print("Ship '%s' has been made rogue\n", Name()); + } + else if (rogue && !IsRogue()) { + Print("Ship '%s' is no longer rogue\n", Name()); + } +} + +void +Ship::SetFriendlyFire(int f) +{ + bool rogue = IsRogue(); + + ff_count = f; + + if (!rogue && IsRogue()) { + Print("Ship '%s' has been made rogue with ff_count = %d\n", Name(), ff_count); + } + else if (rogue && !IsRogue()) { + Print("Ship '%s' is no longer rogue\n", Name()); + } +} + +void +Ship::IncFriendlyFire(int f) +{ + if (f > 0) { + bool rogue = IsRogue(); + + ff_count += f; + + if (!rogue && IsRogue()) { + Print("Ship '%s' has been made rogue with ff_count = %d\n", Name(), ff_count); + } + } +} + +// +--------------------------------------------------------------------+ + +void +Ship::SetEMCON(int e, bool from_net) +{ + if (e < 1) emcon = 1; + else if (e > 3) emcon = 3; + else emcon = (BYTE) e; + + if (emcon != old_emcon && !from_net && NetGame::GetInstance()) + NetUtil::SendObjEmcon(this); +} + +double +Ship::PCS() const +{ + double e_factor = design->e_factor[emcon-1]; + + if (IsAirborne() && !IsGroundUnit()) { + if (AltitudeAGL() < 40) + return 0; + + if (AltitudeAGL() < 200) { + double clutter = AltitudeAGL() / 200; + return clutter * e_factor; + } + } + + return e_factor * pcs; +} + +double +Ship::ACS() const +{ + if (IsAirborne() && !IsGroundUnit()) { + if (AltitudeAGL() < 40) + return 0; + + if (AltitudeAGL() < 200) { + double clutter = AltitudeAGL() / 200; + return clutter * acs; + } + } + + return acs; +} + +DWORD +Ship::MissionClock() const +{ + if (launch_time > 0) + return Game::GameTime() + 1 - launch_time; + + return 0; +} + +// +----------------------------------------------------------------------+ + +Instruction* +Ship::GetRadioOrders() const +{ + return radio_orders; +} + +void +Ship::ClearRadioOrders() +{ + if (radio_orders) { + radio_orders->SetAction(0); + radio_orders->ClearTarget(); + radio_orders->SetLocation(Point()); + } +} + +void +Ship::HandleRadioMessage(RadioMessage* msg) +{ + if (!msg) return; + + static RadioHandler rh; + + if (rh.ProcessMessage(msg, this)) + rh.AcknowledgeMessage(msg, this); +} + +// +----------------------------------------------------------------------+ + +int +Ship::Value() const +{ + return Value(design->type); +} + +// +----------------------------------------------------------------------+ + +int +Ship::Value(int type) +{ + int value = 0; + + switch (type) { + case DRONE: value = 10; break; + case FIGHTER: value = 20; break; + case ATTACK: value = 40; break; + case LCA: value = 50; break; + + case COURIER: value = 100; break; + case CARGO: value = 100; break; + case CORVETTE: value = 100; break; + case FREIGHTER: value = 250; break; + case FRIGATE: value = 200; break; + case DESTROYER: value = 500; break; + case CRUISER: value = 800; break; + case BATTLESHIP: value = 1000; break; + case CARRIER: value = 1500; break; + case DREADNAUGHT: value = 1500; break; + + case STATION: value = 2500; break; + case FARCASTER: value = 5000; break; + + case MINE: value = 20; break; + case COMSAT: value = 200; break; + case DEFSAT: value = 300; break; + case SWACS: value = 500; break; + + case BUILDING: value = 100; break; + case FACTORY: value = 250; break; + case SAM: value = 100; break; + case EWR: value = 200; break; + case C3I: value = 500; break; + case STARBASE: value = 2000; break; + + default: value = 100; break; + } + + return value; +} + +// +----------------------------------------------------------------------+ + +double +Ship::AIValue() const +{ + int i = 0; + double value = 0; + + for (i = 0; i < reactors.size(); i++) { + const PowerSource* r = reactors[i]; + value += r->Value(); + } + + for (i = 0; i < drives.size(); i++) { + const Drive* d = drives[i]; + value += d->Value(); + } + + for (i = 0; i < weapons.size(); i++) { + const WeaponGroup* w = weapons[i]; + value += w->Value(); + } + + return value; +} diff --git a/Stars45/Ship.h b/Stars45/Ship.h new file mode 100644 index 0000000..a1057d8 --- /dev/null +++ b/Stars45/Ship.h @@ -0,0 +1,583 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Ship.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Starship (or space/ground station) class +*/ + +#ifndef Ship_h +#define Ship_h + +#include "Types.h" +#include "SimObject.h" +#include "DetailSet.h" +#include "Director.h" +#include "Geometry.h" +#include "List.h" + +// +--------------------------------------------------------------------+ + +class Ship; +class Shot; +class Drone; + +class Bitmap; +class CombatUnit; +class Computer; +class Contact; +class Drive; +class Element; +class Farcaster; +class FlightComp; +class FlightDeck; +class Hangar; +class InboundSlot; +class Instruction; +class LandingGear; +class MotionController; +class NavLight; +class NavSystem; +class PowerSource; +class QuantumDrive; +class RadioMessage; +class Shield; +class ShieldRep; +class ShipDesign; +class ShipKiller; +class Solid; +class Skin; +class Sound; +class Sensor; +class System; +class Thruster; +class Weapon; +class WeaponDesign; +class WeaponGroup; + +// +--------------------------------------------------------------------+ + +class Ship : public SimObject, + public SimObserver +{ +public: + static const char* TYPENAME() { return "Ship"; } + + enum CLASSIFICATION { + DRONE = 0x0001, + FIGHTER = 0x0002, + ATTACK = 0x0004, + LCA = 0x0008, + COURIER = 0x0010, + CARGO = 0x0020, + CORVETTE = 0x0040, + FREIGHTER = 0x0080, + FRIGATE = 0x0100, + DESTROYER = 0x0200, + CRUISER = 0x0400, + BATTLESHIP = 0x0800, + CARRIER = 0x1000, + DREADNAUGHT = 0x2000, + + STATION = 0x4000, + FARCASTER = 0x8000, + + MINE = 0x00010000, + COMSAT = 0x00020000, + DEFSAT = 0x00040000, + SWACS = 0x00080000, + + BUILDING = 0x00100000, + FACTORY = 0x00200000, + SAM = 0x00400000, + EWR = 0x00800000, + C3I = 0x01000000, + STARBASE = 0x02000000, + + DROPSHIPS = 0x0000000f, + STARSHIPS = 0x0000fff0, + SPACE_UNITS = 0x000f0000, + GROUND_UNITS = 0xfff00000 + }; + + enum OP_MODE { DOCKED, ALERT, LOCKED, LAUNCH, TAKEOFF, ACTIVE, APPROACH, RECOVERY, DOCKING }; + enum FLCS_MODE { FLCS_MANUAL, FLCS_AUTO, FLCS_HELM }; + enum TRAN_TYPE { TRANSITION_NONE, + TRANSITION_DROP_CAM, + TRANSITION_DROP_ORBIT, + TRANSITION_MAKE_ORBIT, + TRANSITION_TIME_SKIP, + TRANSITION_DEATH_SPIRAL, + TRANSITION_DEAD }; + + enum FLIGHT_MODEL { FM_STANDARD, FM_RELAXED, FM_ARCADE }; + enum LANDING_MODEL { LM_STANDARD, LM_EASIER }; + + // CONSTRUCTORS: + Ship(const char* ship_name, const char* reg_num, ShipDesign* design, int IFF=0, int cmd_ai=0, const int* loadout=0); + virtual ~Ship(); + + int operator == (const Ship& s) const { return id == s.id; } + + static void Initialize(); + static void Close(); + + virtual void ExecFrame(double seconds); + virtual void AeroFrame(double seconds); + virtual void StatFrame(double seconds); + virtual void DockFrame(double seconds); + virtual void LinearFrame(double seconds); + virtual void ExecSensors(double seconds); + + void ExecNavFrame(double seconds); + void ExecPhysics(double seconds); + void ExecThrottle(double seconds); + void ExecSystems(double seconds); + + virtual void Activate(Scene& scene); + virtual void Deactivate(Scene& scene); + virtual void SelectDetail(double seconds); + virtual void SetRegion(SimRegion* rgn); + virtual int GetTextureList(List& textures); + + // DIRECTION: + virtual void SetControls(MotionController* m); + virtual void SetNetworkControl(Director* net_ctrl=0); + void SetDirectorInfo(const char* msg) { director_info = msg; } + const char* GetDirectorInfo() const { return director_info; } + void SetAIMode(int n) { ai_mode = (BYTE) n; } + int GetAIMode() const { return (int) ai_mode; } + void SetCommandAILevel(int n) { command_ai_level = (BYTE) n; } + int GetCommandAILevel() const { return command_ai_level; } + virtual int GetFlightPhase() const { return flight_phase; } + virtual void SetFlightPhase(OP_MODE phase); + bool IsNetObserver() const { return net_observer_mode; } + void SetNetObserver(bool n) { net_observer_mode = n; } + + bool IsInvulnerable() const { return invulnerable; } + void SetInvulnerable(bool n) { invulnerable = n; } + + double GetHelmHeading() const { return helm_heading; } + double GetHelmPitch() const { return helm_pitch; } + void SetHelmHeading(double h); + void SetHelmPitch(double p); + virtual void ApplyHelmYaw(double y); + virtual void ApplyHelmPitch(double p); + virtual void ApplyPitch(double pitch_acc); // override for G limiter + + void ArcadeStop() { arcade_velocity *= 0; } + + // CAMERA: + Point BridgeLocation() const { return bridge_vec; } + Point ChaseLocation() const { return chase_vec; } + Point TransitionLocation() const { return transition_loc; } + + // FLIGHT DECK: + Ship* GetController() const; + int NumInbound() const; + int NumFlightDecks() const; + FlightDeck* GetFlightDeck(int i=0) const; + Ship* GetCarrier() const { return carrier; } + FlightDeck* GetDock() const { return dock; } + void SetCarrier(Ship* c, FlightDeck* d); + void Stow(); + InboundSlot* GetInbound() const { return inbound; } + void SetInbound(InboundSlot* s); + + // DRIVE SYSTEMS: + int GetFuelLevel() const; // (0-100) percent of full tank + void SetThrottle(double percent); + void SetAugmenter(bool enable); + double Thrust(double seconds) const; + double VelocityLimit() const { return vlimit; } + Drive* GetDrive() const { return main_drive; } + double Throttle() const { return throttle; } + bool Augmenter() const { return augmenter; } + QuantumDrive* GetQuantumDrive() const { return quantum_drive; } + Farcaster* GetFarcaster() const { return farcaster; } + + bool IsAirborne() const; + bool IsDropCam() const { return transition_type == TRANSITION_DROP_CAM; } + bool IsDropping() const { return transition_type == TRANSITION_DROP_ORBIT; } + bool IsAttaining() const { return transition_type == TRANSITION_MAKE_ORBIT; } + bool IsSkipping() const { return transition_type == TRANSITION_TIME_SKIP; } + bool IsDying() const { return transition_type == TRANSITION_DEATH_SPIRAL; } + bool IsDead() const { return transition_type == TRANSITION_DEAD; } + bool InTransition() const { return transition_type != TRANSITION_NONE; } + void DropOrbit(); + void MakeOrbit(); + bool CanTimeSkip(); + bool IsInCombat(); + void TimeSkip(); + void DropCam(double time=10, double range=0); + void DeathSpiral(); + void CompleteTransition(); + void SetTransition(double trans_time, int trans_type, const Point& trans_loc); + + double CompassHeading() const; + double CompassPitch() const; + double AltitudeMSL() const; + double AltitudeAGL() const; + double GForce() const; + + virtual void SetupAgility(); + + // FLIGHT CONTROL SYSTEM (FLCS): + void ExecFLCSFrame(); + void CycleFLCSMode(); + void SetFLCSMode(int mode); + int GetFLCSMode() const; + void SetTransX(double t); + void SetTransY(double t); + void SetTransZ(double t); + + bool IsGearDown(); + void LowerGear(); + void RaiseGear(); + void ToggleGear(); + void ToggleNavlights(); + + // WEAPON SYSTEMS: + virtual void CheckFriendlyFire(); + virtual void CheckFire(bool c) { check_fire = c; } + virtual bool CheckFire() const { return (check_fire||net_observer_mode)?true:false; } + virtual void SelectWeapon(int n, int w); + virtual bool FireWeapon(int n); + virtual bool FirePrimary() { return FireWeapon(primary); } + virtual bool FireSecondary() { return FireWeapon(secondary); } + virtual bool FireDecoy(); + virtual void CyclePrimary(); + virtual void CycleSecondary(); + virtual Weapon* GetPrimary() const; + virtual Weapon* GetSecondary() const; + virtual Weapon* GetWeaponByIndex(int n); + virtual WeaponGroup* GetPrimaryGroup() const; + virtual WeaponGroup* GetSecondaryGroup() const; + virtual Weapon* GetDecoy() const; + virtual List& GetActiveDecoys(); + virtual void AddActiveDecoy(Drone* d); + virtual int* GetLoadout() { return loadout; } + + List& GetThreatList(); + void AddThreat(Shot* s); + void DropThreat(Shot* s); + + virtual bool Update(SimObject* obj); + virtual const char* GetObserverName() const { return name; } + + virtual int GetMissileEta(int index) const; + virtual void SetMissileEta(int id, int eta); + + virtual WeaponDesign* GetPrimaryDesign() const; + virtual WeaponDesign* GetSecondaryDesign() const; + + virtual void SetTarget(SimObject* t, System* sub=0, bool from_net=false); + virtual SimObject* GetTarget() const { return target; } + virtual System* GetSubTarget() const { return subtarget; } + virtual void CycleSubTarget(int dir=1); + virtual void DropTarget(); + virtual void LockTarget(int type=SimObject::SIM_SHIP, + bool closest=false, + bool hostile=false); + virtual void LockTarget(SimObject* candidate); + virtual bool IsTracking(SimObject* tgt); + virtual bool GetTrigger(int i) const; + virtual void SetTrigger(int i); + + Ship* GetWard() const { return ward; } + void SetWard(Ship* s); + + // SHIELD SYSTEMS: + virtual double InflictDamage(double damage, + Shot* shot = 0, + int hit_type = 3, + Point hull_impact = Point(0,0,0)); + + virtual double InflictSystemDamage(double damage, Shot* shot, Point impact); + + virtual void InflictNetDamage(double damage, Shot* shot=0); + virtual void InflictNetSystemDamage(System* system, double damage, BYTE type); + virtual void SetNetSystemStatus(System* system, int status, int power, int reactor, double avail); + virtual void SetIntegrity(float n) { integrity = n; } + + virtual void Destroy(); + virtual int ShieldStrength() const; + virtual int HullStrength() const; + virtual int HitBy(Shot* shot, Point& impact); + virtual int CollidesWith(Physical& o); + + // SENSORS AND VISIBILITY: + virtual int GetContactID() const { return contact_id; } + virtual int GetIFF() const { return IFF_code; } + virtual void SetIFF(int iff); + virtual Color MarkerColor() const; + static Color IFFColor(int iff); + virtual void DoEMCON(); + virtual double PCS() const; + virtual double ACS() const; + int NumContacts() const; // actual sensor contacts + List& ContactList(); + virtual int GetSensorMode() const; + virtual void SetSensorMode(int mode); + virtual void LaunchProbe(); + virtual Weapon* GetProbeLauncher() const { return probe; } + virtual Drone* GetProbe() const { return sensor_drone; } + virtual void SetProbe(Drone* d); + virtual int GetEMCON() const { return emcon; } + virtual void SetEMCON(int e, bool from_net=false); + virtual Contact* FindContact(SimObject* s) const; + virtual bool IsHostileTo(const SimObject* o) const; + + // GENERAL ACCESSORS: + const char* Registry() const { return regnum; } + void SetName(const char* ident) { strcpy(name, ident); } + const ShipDesign* Design() const { return design; } + const char* Abbreviation() const; + const char* DesignName() const; + const char* DesignFileName() const; + static const char* ClassName(int c); + static int ClassForName(const char* name); + const char* ClassName() const; + CLASSIFICATION Class() const; + bool IsGroundUnit() const; + bool IsStarship() const; + bool IsDropship() const; + bool IsStatic() const; + bool IsRogue() const; + void SetRogue(bool r=true); + int GetFriendlyFire() const { return ff_count; } + void SetFriendlyFire(int f); + void IncFriendlyFire(int f=1); + double Agility() const { return agility; } + DWORD MissionClock() const; + Graphic* Cockpit() const; + void ShowCockpit(); + void HideCockpit(); + int Value() const; + double AIValue() const; + static int Value(int type); + + const Skin* GetSkin() const { return skin; } + void UseSkin(const Skin* s) { skin = s; } + void ShowRep(); + void HideRep(); + void EnableShadows(bool enable); + + int RespawnCount() const { return respawns; } + void SetRespawnCount(int r) { respawns = r; } + const Point& RespawnLoc() const { return respawn_loc; } + void SetRespawnLoc(const Point& rl) + { respawn_loc = rl; } + + double WarpFactor() const { return warp_fov; } + void SetWarp(double w) { warp_fov = (float) w; } + + void MatchOrientation(const Ship& s); + + // ORDERS AND NAVIGATION: + void ExecEvalFrame(double seconds); + void SetLaunchPoint(Instruction* pt); + void AddNavPoint(Instruction* pt, Instruction* afterPoint=0); + void DelNavPoint(Instruction* pt); + void ClearFlightPlan(); + Instruction* GetNextNavPoint(); + int GetNavIndex(const Instruction* n); + double RangeToNavPoint(const Instruction* n); + void SetNavptStatus(Instruction* n, int status); + List& GetFlightPlan(); + int FlightPlanLength(); + CombatUnit* GetCombatUnit() const { return combat_unit; } + Element* GetElement() const { return element; } + Ship* GetLeader() const; + int GetElementIndex() const; + int GetOrigElementIndex() const; + void SetElement(Element* e); + + Instruction* GetRadioOrders() const; + void ClearRadioOrders(); + void HandleRadioMessage(RadioMessage* msg); + bool IsAutoNavEngaged(); + void SetAutoNav(bool engage=true); + void CommandMode(); + + void ClearTrack(); + void UpdateTrack(); + int TrackLength() const { return ntrack; } + Point TrackPoint(int i) const; + + // DAMAGE CONTROL AND ENGINEERING: + List& RepairQueue() { return repair_queue; } + double RepairSpeed() const; + int RepairTeams() const; + void RepairSystem(System* sys); + void IncreaseRepairPriority(int task_index); + void DecreaseRepairPriority(int task_index); + void ExecMaintFrame(double seconds); + bool AutoRepair() const { return auto_repair; } + void EnableRepair(bool e) { auto_repair = e; } + bool MasterCaution() const { return master_caution; } + void ClearCaution() { master_caution = 0; } + + // SYSTEM ACCESSORS: + List& Systems() { return systems; } + List& Weapons() { return weapons; } + List& Drives() { return drives; } + List& Computers() { return computers; } + List& FlightDecks() { return flight_decks; } + List& Reactors() { return reactors; } + List& NavLights() { return navlights; } + Shield* GetShield() { return shield; } + Solid* GetShieldRep() { return (Solid*) shieldRep; } + Sensor* GetSensor() { return sensor; } + NavSystem* GetNavSystem() { return navsys; } + FlightComp* GetFLCS() { return flcs; } + Thruster* GetThruster() { return thruster; } + Hangar* GetHangar() { return hangar; } + LandingGear* GetGear() { return gear; } + + System* GetSystem(int sys_id); + + static int GetControlModel() { return control_model; } + static void SetControlModel(int n) { control_model = n; } + static int GetFlightModel() { return flight_model; } + static void SetFlightModel(int f) { flight_model = f; } + static int GetLandingModel() { return landing_model; } + static void SetLandingModel(int f) { landing_model = f; } + static double GetFriendlyFireLevel() { return friendly_fire_level; } + static void SetFriendlyFireLevel(double f) + { friendly_fire_level = f; } + +protected: + int CheckShotIntersection(Shot* shot, Point& ipt, Point& hpt, Weapon** wep=0); + WeaponGroup* FindWeaponGroup(const char* name); + + char regnum[16]; + ShipDesign* design; + ShipKiller* killer; + DetailSet detail; + int detail_level; + Sim* sim; + double vlimit; + double agility; + double throttle; + double throttle_request; + bool augmenter; + float wep_mass; + float wep_resist; + + int IFF_code; + int cmd_chain_index; + int ff_count; + OP_MODE flight_phase; + + SimObject* target; + System* subtarget; + Ship* ward; + int check_fire; + int primary; + int secondary; + + const Skin* skin; + Solid* cockpit; + Drive* main_drive; + QuantumDrive* quantum_drive; + Farcaster* farcaster; + Shield* shield; + ShieldRep* shieldRep; + NavSystem* navsys; + FlightComp* flcs; + Sensor* sensor; + LandingGear* gear; + Thruster* thruster; + Weapon* decoy; + Weapon* probe; + Drone* sensor_drone; + Hangar* hangar; + List decoy_list; + List threat_list; + + List systems; + List reactors; + List weapons; + List drives; + List computers; + List flight_decks; + List navlights; + List repair_queue; + + CombatUnit* combat_unit; + Element* element; + int orig_elem_index; + Instruction* radio_orders; + Instruction* launch_point; + + Vec3 chase_vec; + Vec3 bridge_vec; + + const char* director_info; + BYTE ai_mode; + BYTE command_ai_level; + BYTE flcs_mode; + bool net_observer_mode; + + float pcs; // passive sensor cross section + float acs; // active sensor cross section + BYTE emcon; + BYTE old_emcon; + bool invulnerable; + + DWORD launch_time; + DWORD friendly_fire_time; + + Ship* carrier; + FlightDeck* dock; + InboundSlot* inbound; + + Director* net_control; + + Point* track; + int ntrack; + DWORD track_time; + + float helm_heading; + float helm_pitch; + + float altitude_agl; + float g_force; + + float warp_fov; + + float transition_time; + int transition_type; + Point transition_loc; + Point respawn_loc; + int respawns; + + bool master_caution; + bool auto_repair; + DWORD last_repair_time; + DWORD last_eval_time; + DWORD last_beam_time; + DWORD last_bolt_time; + + int missile_id[4]; + BYTE missile_eta[4]; + bool trigger[4]; + int* loadout; + + int contact_id; + + static int control_model; + static int flight_model; + static int landing_model; + static double friendly_fire_level; +}; + +#endif Ship_h + diff --git a/Stars45/ShipAI.cpp b/Stars45/ShipAI.cpp new file mode 100644 index 0000000..1118a8a --- /dev/null +++ b/Stars45/ShipAI.cpp @@ -0,0 +1,1358 @@ +/* Project Starshatter 5.0 + Destroyer Studios LLC + Copyright © 1997-2007. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: ShipAI.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Starship (low-level) Artificial Intelligence class +*/ + +#include "MemDebug.h" +#include "ShipAI.h" +#include "TacticalAI.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "Shot.h" +#include "Element.h" +#include "NavLight.h" +#include "Instruction.h" +#include "RadioMessage.h" +#include "RadioTraffic.h" +#include "Contact.h" +#include "WeaponGroup.h" +#include "Drive.h" +#include "Shield.h" +#include "Sim.h" +#include "Player.h" +#include "StarSystem.h" +#include "FlightComp.h" +#include "Farcaster.h" +#include "QuantumDrive.h" +#include "Debris.h" +#include "Asteroid.h" + +#include "Game.h" +#include "Random.h" + +// +----------------------------------------------------------------------+ + +ShipAI::ShipAI(SimObject* s) + : SteerAI(s), + support(0), rumor(0), threat(0), threat_missile(0), drop_time(0), + too_close(0), navpt(0), patrol(0), engaged_ship_id(0), + bracket(false), identify(false), hold(false), takeoff(false), + throttle(0), old_throttle(0), element_index(1), splash_count(0), + tactical(0), farcaster(0), ai_level(2), last_avoid_time(0), + last_call_time(0) +{ + ship = (Ship*) self; + + Sim* sim = Sim::GetSim(); + Ship* pship = sim->GetPlayerShip(); + int player_team = 1; + + if (pship) + player_team = pship->GetIFF(); + + Player* player = Player::GetCurrentPlayer(); + if (player) { + if (ship && ship->GetIFF() && ship->GetIFF() != player_team) { + ai_level = player->AILevel(); + } + else if (player->AILevel() == 0) { + ai_level = 1; + } + } + + // evil alien ships are *always* smart: + if (ship && ship->GetIFF() > 1 && ship->Design()->auto_roll > 1) { + ai_level = 2; + } +} + + +// +--------------------------------------------------------------------+ + +ShipAI::~ShipAI() +{ + delete tactical; +} + +void +ShipAI::ClearTactical() +{ + delete tactical; + tactical = 0; +} + +// +--------------------------------------------------------------------+ + +Ship* +ShipAI::GetWard() const +{ + return ship->GetWard(); +} + +void +ShipAI::SetWard(Ship* s) +{ + if (s == ship->GetWard()) + return; + + ship->SetWard(s); + + Point form = RandomDirection(); + form.SwapYZ(); + + if (fabs(form.x) < 0.5) { + if (form.x < 0) + form.x = -0.5; + else + form.x = 0.5; + } + + if (ship && ship->IsStarship()) { + form *= 30e3; + } + else { + form *= 15e3; + form.y = 500; + } + + SetFormationDelta(form); +} + +void +ShipAI::SetSupport(Ship* s) +{ + if (support == s) + return; + + support = s; + + if (support) + Observe(support); +} + +void +ShipAI::SetRumor(Ship* s) +{ + if (!s || rumor == s) + return; + + rumor = s; + + if (rumor) + Observe(rumor); +} + +void +ShipAI::ClearRumor() +{ + rumor = 0; +} + +void +ShipAI::SetThreat(Ship* s) +{ + if (threat == s) + return; + + threat = s; + + if (threat) + Observe(threat); +} + +void +ShipAI::SetThreatMissile(Shot* s) +{ + if (threat_missile == s) + return; + + threat_missile = s; + + if (threat_missile) + Observe(threat_missile); +} + +bool +ShipAI::Update(SimObject* obj) +{ + if (obj == support) + support = 0; + + if (obj == threat) + threat = 0; + + if (obj == threat_missile) + threat_missile = 0; + + if (obj == rumor) + rumor = 0; + + return SteerAI::Update(obj); +} + +const char* +ShipAI::GetObserverName() const +{ + static char name[64]; + sprintf(name, "ShipAI(%s)", self->Name()); + return name; +} + +// +--------------------------------------------------------------------+ + +Point +ShipAI::GetPatrol() const +{ + return patrol_loc; +} + +void +ShipAI::SetPatrol(const Point& p) +{ + patrol = 1; + patrol_loc = p; +} + +void +ShipAI::ClearPatrol() +{ + patrol = 0; +} + +// +--------------------------------------------------------------------+ + +void +ShipAI::ExecFrame(double secs) +{ + seconds = secs; + + if (drop_time > 0) drop_time -= seconds; + if (!ship) return; + + ship->SetDirectorInfo(" "); + + // check to make sure current navpt is still valid: + if (navpt) + navpt = ship->GetNextNavPoint(); + + if (ship->GetFlightPhase() == Ship::TAKEOFF || ship->GetFlightPhase() == Ship::LAUNCH) + takeoff = true; + + if (takeoff) { + FindObjective(); + Navigator(); + + if (ship->MissionClock() > 10000) + takeoff = false; + + return; + } + + // initial assessment: + if (ship->MissionClock() < 5000) + return; + + element_index = ship->GetElementIndex(); + + NavlightControl(); + CheckTarget(); + + if (tactical) + tactical->ExecFrame(seconds); + + if (target && target != ship->GetTarget()) { + ship->LockTarget(target); + + // if able to lock target, and target is a ship (not a shot)... + if (target == ship->GetTarget() && target->Type() == SimObject::SIM_SHIP) { + + // if this isn't the same ship we last called out: + if (target->Identity() != engaged_ship_id && Game::GameTime() - last_call_time > 10000) { + // call engaging: + RadioMessage* msg = new(__FILE__,__LINE__) RadioMessage(ship->GetElement(), ship, RadioMessage::CALL_ENGAGING); + msg->AddTarget(target); + RadioTraffic::Transmit(msg); + last_call_time = Game::GameTime(); + + engaged_ship_id = target->Identity(); + } + } + } + + else if (!target) { + target = ship->GetTarget(); + + if (engaged_ship_id && !target) { + engaged_ship_id = 0; + + /*** + *** XXX + *** Not the right place to make this decision. + *** + *** There is a brief wait between killing a target and + *** selecting a new one, so this message is sent after + *** every kill. + *** + *** Need to track when the entire element has been + *** put down. + + if (element_index == 1) { + RadioMessage* msg = new(__FILE__,__LINE__) RadioMessage(ship->GetElement(), ship, RadioMessage::RESUME_MISSION); + RadioTraffic::Transmit(msg); + } + + *** + ***/ + } + } + + FindObjective(); + Navigator(); +} + +// +--------------------------------------------------------------------+ + +Point +ShipAI::ClosingVelocity() +{ + if (ship && target) { + if (ship->GetPrimaryDesign()) { + WeaponDesign* guns = ship->GetPrimaryDesign(); + Point delta = target->Location() - ship->Location(); + + // fighters need to aim the ship so that the guns will hit the target + if (guns->firing_cone < 10*DEGREES && guns->max_range <= delta.length()) { + Point aim_vec = ship->Heading(); + aim_vec.Normalize(); + + Point shot_vel = ship->Velocity() + aim_vec * guns->speed; + return shot_vel - target->Velocity(); + } + + // ships with turreted weapons just need to worry about actual closing speed + else { + return ship->Velocity() - target->Velocity(); + } + } + + else { + return ship->Velocity(); + } + } + + return Point(1,0,0); +} + +// +--------------------------------------------------------------------+ + +void +ShipAI::FindObjective() +{ + distance = 0; + + int order = ship->GetRadioOrders()->Action(); + + if (order == RadioMessage::QUANTUM_TO || + order == RadioMessage::FARCAST_TO) { + + FindObjectiveQuantum(); + objective = Transform(obj_w); + return; + } + + bool form = (order == RadioMessage::WEP_HOLD) || + (order == RadioMessage::FORM_UP) || + (order == RadioMessage::MOVE_PATROL) || + (order == RadioMessage::RTB) || + (order == RadioMessage::DOCK_WITH) || + (!order && !target) || + (farcaster); + + Ship* ward = ship->GetWard(); + + // if not the element leader, stay in formation: + if (form && element_index > 1) { + ship->SetDirectorInfo(Game::GetText("ai.formation")); + + if (navpt && navpt->Action() == Instruction::LAUNCH) { + FindObjectiveNavPoint(); + } + else { + navpt = 0; + FindObjectiveFormation(); + } + + // transform into camera coords: + objective = Transform(obj_w); + return; + } + + // under orders? + bool directed = false; + if (tactical) + directed = (tactical->RulesOfEngagement() == TacticalAI::DIRECTED); + + // threat processing: + if (threat && !directed) { + double d_threat = Point(threat->Location() - ship->Location()).length(); + + // seek support: + if (support) { + double d_support = Point(support->Location() - ship->Location()).length(); + if (d_support > 35e3) { + ship->SetDirectorInfo(Game::GetText("ai.regroup")); + FindObjectiveTarget(support); + objective = Transform(obj_w); + return; + } + } + + // run away: + else if (threat != target) { + ship->SetDirectorInfo(Game::GetText("ai.retreat")); + obj_w = ship->Location() + Point(ship->Location() - threat->Location()) * 100; + objective = Transform(obj_w); + return; + } + } + + // normal processing: + if (target) { + ship->SetDirectorInfo(Game::GetText("ai.seek-target")); + FindObjectiveTarget(target); + objective = AimTransform(obj_w); + } + + else if (patrol) { + ship->SetDirectorInfo(Game::GetText("ai.patrol")); + FindObjectivePatrol(); + objective = Transform(obj_w); + } + + else if (ward) { + ship->SetDirectorInfo(Game::GetText("ai.seek-ward")); + FindObjectiveFormation(); + objective = Transform(obj_w); + } + + else if (navpt && form) { + ship->SetDirectorInfo(Game::GetText("ai.seek-navpt")); + FindObjectiveNavPoint(); + objective = Transform(obj_w); + } + + else if (rumor) { + ship->SetDirectorInfo(Game::GetText("ai.search")); + FindObjectiveTarget(rumor); + objective = Transform(obj_w); + } + + else { + obj_w = Point(); + objective = Point(); + } +} + +// +--------------------------------------------------------------------+ + +void +ShipAI::FindObjectiveTarget(SimObject* tgt) +{ + if (!tgt) { + obj_w = Point(); + return; + } + + navpt = 0; // this tells fire control that we are chasing a target, + // instead of a navpoint! + + Point cv = ClosingVelocity(); + double cvl = cv.length(); + double time = 0; + + if (cvl > 50) { + // distance from self to target: + distance = Point(tgt->Location() - self->Location()).length(); + + // time to reach target: + time = distance / cvl; + + // where the target will be when we reach it: + if (time < 15) { + Point run_vec = tgt->Velocity(); + obj_w = tgt->Location() + (run_vec * time); + + + if (time < 10) + obj_w += (tgt->Acceleration() * 0.33 * time * time); + } + else { + obj_w = tgt->Location(); + } + } + + else { + obj_w = tgt->Location(); + } + + distance = Point(obj_w - self->Location()).length(); + + if (cvl > 50) { + time = distance / cvl; + + // where we will be when the target gets there: + if (time < 15) { + Point self_dest = self->Location() + cv * time; + Point err = obj_w - self_dest; + + obj_w += err; + } + } + + Point approach = obj_w - self->Location(); + distance = approach.length(); + + if (bracket && distance > 25e3) { + Point offset = approach.cross(Point(0,1,0)); + offset.Normalize(); + offset *= 15e3; + + Ship* s = (Ship*) self; + if (s->GetElementIndex() & 1) + obj_w -= offset; + else + obj_w += offset; + } +} + +// +--------------------------------------------------------------------+ + +void +ShipAI::FindObjectivePatrol() +{ + navpt = 0; + + Point npt = patrol_loc; + obj_w = npt; + + // distance from self to navpt: + distance = Point(obj_w - self->Location()).length(); + + if (distance < 1000) { + ship->ClearRadioOrders(); + ClearPatrol(); + } +} + +// +--------------------------------------------------------------------+ + +void +ShipAI::FindObjectiveNavPoint() +{ + SimRegion* self_rgn = ship->GetRegion(); + SimRegion* nav_rgn = navpt->Region(); + QuantumDrive* qdrive = ship->GetQuantumDrive(); + + if (!self_rgn) + return; + + if (!nav_rgn) { + nav_rgn = self_rgn; + navpt->SetRegion(nav_rgn); + } + + bool use_farcaster = self_rgn != nav_rgn && + (navpt->Farcast() || + !qdrive || + !qdrive->IsPowerOn() || + qdrive->Status() < System::DEGRADED + ); + + if (use_farcaster) { + FindObjectiveFarcaster(self_rgn, nav_rgn); + } + + else { + if (farcaster) { + if (farcaster->GetShip()->GetRegion() != self_rgn) + farcaster = farcaster->GetDest()->GetFarcaster(); + + obj_w = farcaster->EndPoint(); + } + + else { + // transform from starsystem to world coordinates: + Point npt = navpt->Region()->Location() + navpt->Location(); + + SimRegion* active_region = ship->GetRegion(); + + if (active_region) + npt -= active_region->Location(); + + npt = npt.OtherHand(); + + obj_w = npt; + } + + // distance from self to navpt: + distance = Point(obj_w - ship->Location()).length(); + + if (farcaster && distance < 1000) + farcaster = 0; + + if (distance < 1000 || (navpt->Action() == Instruction::LAUNCH && distance > 25000)) + ship->SetNavptStatus(navpt, Instruction::COMPLETE); + } +} + +// +--------------------------------------------------------------------+ + +void +ShipAI::FindObjectiveQuantum() +{ + Instruction* orders = ship->GetRadioOrders(); + SimRegion* self_rgn = ship->GetRegion(); + SimRegion* nav_rgn = orders->Region(); + QuantumDrive* qdrive = ship->GetQuantumDrive(); + + if (!self_rgn || !nav_rgn) + return; + + bool use_farcaster = self_rgn != nav_rgn && + (orders->Farcast() || + !qdrive || + !qdrive->IsPowerOn() || + qdrive->Status() < System::DEGRADED + ); + + if (use_farcaster) { + FindObjectiveFarcaster(self_rgn, nav_rgn); + } + + else { + if (farcaster) { + if (farcaster->GetShip()->GetRegion() != self_rgn) + farcaster = farcaster->GetDest()->GetFarcaster(); + + obj_w = farcaster->EndPoint(); + } + + else { + // transform from starsystem to world coordinates: + Point npt = orders->Region()->Location() + orders->Location(); + + SimRegion* active_region = ship->GetRegion(); + + if (active_region) + npt -= active_region->Location(); + + npt = npt.OtherHand(); + + obj_w = npt; + + if (qdrive && qdrive->ActiveState() == QuantumDrive::ACTIVE_READY) { + qdrive->SetDestination(nav_rgn, orders->Location()); + qdrive->Engage(); + return; + } + } + + // distance from self to navpt: + distance = Point(obj_w - ship->Location()).length(); + + if (farcaster) { + if (distance < 1000) { + farcaster = 0; + ship->ClearRadioOrders(); + } + } + else if (self_rgn == nav_rgn) { + ship->ClearRadioOrders(); + } + } +} + +void +ShipAI::FindObjectiveFarcaster(SimRegion* src_rgn, SimRegion* dst_rgn) +{ + if (!farcaster) { + ListIter s = src_rgn->Ships(); + while (++s && !farcaster) { + if (s->GetFarcaster()) { + const Ship* dest = s->GetFarcaster()->GetDest(); + if (dest && dest->GetRegion() == dst_rgn) { + farcaster = s->GetFarcaster(); + } + } + } + } + + if (farcaster) { + Point apt = farcaster->ApproachPoint(0); + Point npt = farcaster->StartPoint(); + double r1 = (ship->Location() - npt).length(); + + if (r1 > 50e3) { + obj_w = apt; + distance = r1; + } + + else { + double r2 = (ship->Location() - apt).length(); + double r3 = (npt - apt).length(); + + if (r1+r2 < 1.2*r3) { + obj_w = npt; + distance = r1; + } + else { + obj_w = apt; + distance = r2; + } + } + + objective = Transform(obj_w); + } +} + +// +--------------------------------------------------------------------+ + +void +ShipAI::SetFormationDelta(const Point& point) +{ + formation_delta = point; +} + +void +ShipAI::FindObjectiveFormation() +{ + const double prediction = 5; + + // find the base position: + Element* elem = ship->GetElement(); + Ship* lead = elem->GetShip(1); + Ship* ward = ship->GetWard(); + + if (!lead || lead == ship) { + lead = ward; + + distance = (lead->Location() - self->Location()).length(); + if (distance < 30e3 && lead->Velocity().length() < 50) { + obj_w = self->Location() + lead->Heading() * 1e6; + distance = -1; + return; + } + } + + obj_w = lead->Location() + lead->Velocity() * prediction; + Matrix m; m.Rotate(0, 0, lead->CompassHeading() - PI); + Point fd = formation_delta * m; + obj_w += fd; + + // try to avoid smacking into the ground... + if (ship->IsAirborne()) { + if (ship->AltitudeAGL() < 3000 || lead->AltitudeAGL() < 3000) { + obj_w.y += 500; + } + } + + Point dst_w = self->Location() + self->Velocity() * prediction; + Point dlt_w = obj_w - dst_w; + + distance = dlt_w.length(); + + // get slot z distance: + dlt_w += ship->Location(); + slot_dist = Transform(dlt_w).z; + + Director* lead_dir = lead->GetDirector(); + if (lead_dir && (lead_dir->Type() == FIGHTER || lead_dir->Type() == STARSHIP)) { + ShipAI* lead_ai = (ShipAI*) lead_dir; + farcaster = lead_ai->GetFarcaster(); + } + else { + Instruction* navpt = elem->GetNextNavPoint(); + if (!navpt) { + farcaster = 0; + return; + } + + SimRegion* self_rgn = ship->GetRegion(); + SimRegion* nav_rgn = navpt->Region(); + QuantumDrive* qdrive = ship->GetQuantumDrive(); + + if (self_rgn && !nav_rgn) { + nav_rgn = self_rgn; + navpt->SetRegion(nav_rgn); + } + + bool use_farcaster = self_rgn != nav_rgn && + (navpt->Farcast() || + !qdrive || + !qdrive->IsPowerOn() || + qdrive->Status() < System::DEGRADED + ); + + if (use_farcaster) { + ListIter s = self_rgn->Ships(); + while (++s && !farcaster) { + if (s->GetFarcaster()) { + const Ship* dest = s->GetFarcaster()->GetDest(); + if (dest && dest->GetRegion() == nav_rgn) { + farcaster = s->GetFarcaster(); + } + } + } + } + else if (farcaster) { + if (farcaster->GetShip()->GetRegion() != self_rgn) + farcaster = farcaster->GetDest()->GetFarcaster(); + + obj_w = farcaster->EndPoint(); + distance = Point(obj_w - ship->Location()).length(); + + if (distance < 1000) + farcaster = 0; + } + } +} + +// +--------------------------------------------------------------------+ + +void +ShipAI::Splash(const Ship* targ) +{ + if (splash_count > 6) + splash_count = 4; + + // call splash: + RadioTraffic::SendQuickMessage(ship, RadioMessage::SPLASH_1 + splash_count); + splash_count++; +} + +// +--------------------------------------------------------------------+ + +void +ShipAI::SetTarget(SimObject* targ, System* sub) +{ + if (targ != target) { + bracket = false; + } + + SteerAI::SetTarget(targ, sub); +} + +void +ShipAI::DropTarget(double dtime) +{ + SetTarget(0); + drop_time = dtime; // seconds until we can re-acquire + + ship->DropTarget(); +} + +void +ShipAI::SetBracket(bool b) +{ + bracket = b; + identify = false; +} + +void +ShipAI::SetIdentify(bool i) +{ + identify = i; + bracket = false; +} + +// +--------------------------------------------------------------------+ + +void +ShipAI::Navigator() +{ + accumulator.Clear(); + magnitude = 0; + + hold = false; + if ((ship->GetElement() && ship->GetElement()->GetHoldTime() > 0) || + (navpt && navpt->Status() == Instruction::COMPLETE && navpt->HoldTime() > 0)) + hold = true; + + ship->SetFLCSMode(Ship::FLCS_HELM); + + if (target) + ship->SetDirectorInfo(Game::GetText("ai.seek-target")); + else if (rumor) + ship->SetDirectorInfo(Game::GetText("ai.seek-rumor")); + else + ship->SetDirectorInfo(Game::GetText("ai.none")); + + Accumulate(AvoidCollision()); + Accumulate(AvoidTerrain()); + + if (!hold) + Accumulate(SeekTarget()); + + HelmControl(); + ThrottleControl(); + FireControl(); + AdjustDefenses(); +} + +// +--------------------------------------------------------------------+ + +void +ShipAI::HelmControl() +{ + double trans_x = 0; + double trans_y = 0; + double trans_z = 0; + + ship->SetHelmHeading(accumulator.yaw); + + if (fabs(accumulator.pitch) < 5*DEGREES || fabs(accumulator.pitch) > 45*DEGREES) { + trans_z = objective.y; + ship->SetHelmPitch(0); + } + + else { + ship->SetHelmPitch(accumulator.pitch); + } + + ship->SetTransX(trans_x); + ship->SetTransY(trans_y); + ship->SetTransZ(trans_z); + + ship->ExecFLCSFrame(); +} + +/***************************************** + ** + ** NOTE: + ** No one is really using this method. + ** It is overridden by both StarshipAI + ** and FighterAI. + ** + *****************************************/ + +void +ShipAI::ThrottleControl() +{ + if (navpt && !threat && !target) { // lead only, get speed from navpt + double speed = navpt->Speed(); + + if (speed > 0) + throttle = speed / ship->VelocityLimit() * 100; + else + throttle = 50; + } + + else if (patrol && !threat && !target) { // lead only, get speed from navpt + double speed = 200; + + if (distance > 5000) + speed = 500; + + if (ship->Velocity().length() > speed) + throttle = 0; + else + throttle = 50; + } + + else { + if (threat || target || element_index < 2) { // element lead + throttle = 100; + + if (!threat && !target) + throttle = 50; + + if (accumulator.brake > 0) { + throttle *= (1 - accumulator.brake); + } + } + + else { // wingman + Ship* lead = ship->GetElement()->GetShip(1); + double lv = lead->Velocity().length(); + double sv = ship->Velocity().length(); + double dv = lv-sv; + double dt = 0; + + if (dv > 0) dt = dv * 1e-2 * seconds; + else if (dv < 0) dt = dv * 1e-2 * seconds; + + throttle = old_throttle + dt; + } + } + + old_throttle = throttle; + ship->SetThrottle((int) throttle); +} + +// +--------------------------------------------------------------------+ + +void +ShipAI::NavlightControl() +{ + Ship* leader = ship->GetLeader(); + + if (leader && leader != ship) { + bool navlight_enabled = false; + + if (leader->NavLights().size() > 0) + navlight_enabled = leader->NavLights().at(0)->IsEnabled(); + + for (int i = 0; i < ship->NavLights().size(); i++) { + if (navlight_enabled) + ship->NavLights().at(i)->Enable(); + else + ship->NavLights().at(i)->Disable(); + } + } +} + +// +--------------------------------------------------------------------+ + +Steer +ShipAI::AvoidTerrain() +{ + Steer avoid; + return avoid; +} + +// +--------------------------------------------------------------------+ + +Steer +ShipAI::AvoidCollision() +{ + Steer avoid; + + if (!ship || !ship->GetRegion() || !ship->GetRegion()->IsActive()) + return avoid; + + if (other && (other->Life() == 0 || other->Integrity() < 1)) { + other = 0; + last_avoid_time = 0; // check for a new obstacle immediately + } + + if (!other && Game::GameTime() - last_avoid_time < 500) + return avoid; + + brake = 0; + + // don't get closer than this: + double avoid_dist = 5 * self->Radius(); + + if (avoid_dist < 1e3) avoid_dist = 1e3; + else if (avoid_dist > 12e3) avoid_dist = 12e3; + + // find the soonest potential collision, + // ignore any that occur after this: + double avoid_time = 15; + + if (ship->Design()->avoid_time > 0) + avoid_time = ship->Design()->avoid_time; + else if (ship->IsStarship()) + avoid_time *= 1.5; + + Point bearing = self->Velocity(); + bearing.Normalize(); + + bool found = false; + int num_contacts = ship->NumContacts(); + ListIter contact = ship->ContactList(); + + // check current obstacle first: + if (other) { + found = AvoidTestSingleObject(other, bearing, avoid_dist, avoid_time, avoid); + } + + if (!found) { + // avoid ships: + while (++contact && !found) { + Ship* c_ship = contact->GetShip(); + + if (c_ship && c_ship != ship && c_ship->IsStarship()) { + found = AvoidTestSingleObject(c_ship, bearing, avoid_dist, avoid_time, avoid); + } + } + + // also avoid large pieces of debris: + if (!found) { + ListIter iter = ship->GetRegion()->Rocks(); + while (++iter && !found) { + Debris* debris = iter.value(); + + if (debris->Mass() > ship->Mass()) + found = AvoidTestSingleObject(debris, bearing, avoid_dist, avoid_time, avoid); + } + } + + // and asteroids: + if (!found) { + // give asteroids a wider berth - + avoid_dist *= 8; + + ListIter iter = ship->GetRegion()->Roids(); + while (++iter && !found) { + Asteroid* roid = iter.value(); + found = AvoidTestSingleObject(roid, bearing, avoid_dist, avoid_time, avoid); + } + + if (!found) + avoid_dist /= 8; + } + + // if found, steer to avoid: + if (other) { + avoid = Avoid(obstacle, (float) (ship->Radius() + other->Radius() + avoid_dist * 0.9)); + avoid.brake = brake; + + ship->SetDirectorInfo(Game::GetText("ai.avoid-collision")); + } + } + + last_avoid_time = Game::GameTime(); + return avoid; +} + +bool +ShipAI::AvoidTestSingleObject(SimObject* obj, + const Point& bearing, + double avoid_dist, + double& avoid_time, + Steer& avoid) +{ + if (too_close == obj->Identity()) { + double dist = (ship->Location() - obj->Location()).length(); + double closure = (ship->Velocity() - obj->Velocity()) * bearing; + + if (closure > 1 && dist < avoid_dist) { + avoid = AvoidCloseObject(obj); + return true; + } + else { + too_close = 0; + } + } + + // will we get close? + double time = ClosestApproachTime(ship->Location(), ship->Velocity(), + obj->Location(), obj->Velocity()); + + // already past the obstacle: + if (time <= 0) { + if (other == obj) other = 0; + return false; + } + + // how quickly could we collide? + Point current_relation = ship->Location() - obj->Location(); + double current_distance = current_relation.length() - ship->Radius() - obj->Radius(); + + // are we really far away? + if (current_distance > 25e3) { + if (other == obj) other = 0; + return false; + } + + // is the obstacle a farcaster? + if (obj->Type() == SimObject::SIM_SHIP) { + Ship* c_ship = (Ship*) obj; + + if (c_ship->GetFarcaster()) { + // are we on a safe vector? + Point dir = ship->Velocity(); + dir.Normalize(); + + double angle_off = fabs(acos(dir * obj->Cam().vpn())); + + if (angle_off > 90*DEGREES) + angle_off = 180*DEGREES - angle_off; + + if (angle_off < 35*DEGREES) { + // will we pass through the center? + Point d = ship->Location() + dir * (current_distance + ship->Radius() + obj->Radius()); + double err = (obj->Location() - d).length(); + + if (err < 0.667 * obj->Radius()) { + return false; + } + } + } + } + + // rate of closure: + double closing_velocity = (ship->Velocity() - obj->Velocity()) * bearing; + + // are we too close already? + if (current_distance < (avoid_dist * 0.35)) { + if (closing_velocity > 1 || current_distance < ship->Radius()) { + avoid = AvoidCloseObject(obj); + return true; + } + } + + // too far away to worry about: + double separation = (avoid_dist + obj->Radius()); + if ((current_distance-separation) / closing_velocity > avoid_time) { + if (other == obj) other = 0; + return false; + } + + // where will we be? + Point selfpt = ship->Location() + ship->Velocity() * time; + Point testpt = obj->Location() + obj->Velocity() * time; + + // how close will we get? + double dist = (selfpt - testpt).length() + - ship->Radius() + - obj->Radius(); + + // that's too close: + if (dist < avoid_dist) { + if (dist < avoid_dist * 0.25 && time < avoid_time * 0.5) { + avoid = AvoidCloseObject(obj); + return true; + } + + obstacle = Transform(testpt); + + if (obstacle.z > 0) { + other = obj; + avoid_time = time; + brake = 0.5; + + Observe(other); + } + } + + // hysteresis: + else if (other == obj && dist > avoid_dist * 1.25) { + other = 0; + } + + return false; +} + +// +--------------------------------------------------------------------+ + +Steer +ShipAI::AvoidCloseObject(SimObject* obj) +{ + too_close = obj->Identity(); + obstacle = Transform(obj->Location()); + other = obj; + + Observe(other); + + Steer avoid = Flee(obstacle); + avoid.brake = 0.3; + + ship->SetDirectorInfo(Game::GetText("ai.avoid-collision")); + return avoid; +} + +// +--------------------------------------------------------------------+ + +Steer +ShipAI::SeekTarget() +{ + Ship* ward = ship->GetWard(); + + if (!target && !ward && !navpt && !patrol) { + if (element_index > 1) { + // wingmen keep in formation: + return Seek(objective); + } + + if (farcaster) { + return Seek(objective); + } + + if (rumor) { + return Seek(objective); + } + + return Steer(); + } + + if (patrol) { + Steer result = Seek(objective); + + if (distance < 2000) { + result.brake = 1; + } + + return result; + } + + if (target && too_close == target->Identity()) { + drop_time = 4; + return Avoid(objective, 0.0f); + } + else if (drop_time > 0) { + return Steer(); + } + + return Seek(objective); +} + +// +--------------------------------------------------------------------+ + +Steer +ShipAI::EvadeThreat() +{ + return Steer(); +} + +// +--------------------------------------------------------------------+ + +void +ShipAI::FireControl() +{ +} + +// +--------------------------------------------------------------------+ + +void +ShipAI::AdjustDefenses() +{ + Shield* shield = ship->GetShield(); + + if (shield) { + double desire = 50; + + if (threat_missile || threat) + desire = 100; + + shield->SetPowerLevel(desire); + } +} + +// +--------------------------------------------------------------------+ + +void +ShipAI::CheckTarget() +{ + if (target) { + if (target->Life() == 0) + target = 0; + + else if (target->Type() == SimObject::SIM_SHIP) { + Ship* tgt_ship = (Ship*) target; + + if (tgt_ship->GetIFF() == ship->GetIFF() && !tgt_ship->IsRogue()) + target = 0; + } + } +} diff --git a/Stars45/ShipAI.h b/Stars45/ShipAI.h new file mode 100644 index 0000000..05f0555 --- /dev/null +++ b/Stars45/ShipAI.h @@ -0,0 +1,153 @@ +/* Project STARSHATTER + John DiCamillo + Copyright © 1997-2002. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: ShipAI.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Common base class and interface for low-level ship AI +*/ + +#ifndef ShipAI_h +#define ShipAI_h + +#include "Types.h" +#include "SteerAI.h" + +// +--------------------------------------------------------------------+ + +class Ship; +class Shot; +class Instruction; +class TacticalAI; +class Farcaster; + +// +--------------------------------------------------------------------+ + +class ShipAI : public SteerAI +{ +public: + ShipAI(SimObject* s); + virtual ~ShipAI(); + + virtual void ExecFrame(double seconds); + virtual int Subframe() const { return true; } + + virtual Ship* GetShip() const { return ship; } + + virtual Ship* GetWard() const; + virtual void SetWard(Ship* s); + virtual Ship* GetThreat() const { return threat; } + virtual void SetThreat(Ship* s); + virtual Ship* GetSupport() const { return support; } + virtual void SetSupport(Ship* s); + virtual Ship* GetRumor() const { return rumor; } + virtual void SetRumor(Ship* s); + virtual Shot* GetThreatMissile() const { return threat_missile; } + virtual void SetThreatMissile(Shot* s); + virtual Instruction* GetNavPoint() const { return navpt; } + virtual void SetNavPoint(Instruction* n) { navpt = n; } + virtual Point GetPatrol() const; + virtual void SetPatrol(const Point& p); + virtual void ClearPatrol(); + virtual void ClearRumor(); + virtual void ClearTactical(); + + virtual Farcaster* GetFarcaster() { return farcaster; } + + // convert the goal point from world to local coords: + virtual void FindObjective(); + + virtual void Splash(const Ship* targ); + virtual void SetTarget(SimObject* targ, System* sub=0); + virtual void DropTarget(double drop_time=1.5); + virtual double DropTime() const { return drop_time; } + virtual void SetBracket(bool bracket); + virtual void SetIdentify(bool identify); + + virtual void SetFormationDelta(const Point& point); + + virtual bool Update(SimObject* obj); + virtual const char* GetObserverName() const; + + virtual int GetAILevel() const { return ai_level; } + +protected: + // accumulate behaviors: + virtual void Navigator(); + + // behaviors: + virtual bool AvoidTestSingleObject(SimObject* obj, + const Point& bearing, + double avoid_dist, + double& avoid_time, + Steer& steer); + + virtual Steer AvoidCloseObject(SimObject* obj); + virtual Steer AvoidCollision(); + virtual Steer AvoidTerrain(); + virtual Steer SeekTarget(); + virtual Steer EvadeThreat(); + + virtual Point ClosingVelocity(); + + // compute the goal point in world coords based on current ai state: + virtual void FindObjectiveTarget(SimObject* tgt); + virtual void FindObjectiveNavPoint(); + virtual void FindObjectiveFormation(); + virtual void FindObjectivePatrol(); + virtual void FindObjectiveQuantum(); + virtual void FindObjectiveFarcaster(SimRegion* src, SimRegion* dst); + + // fire on target if appropriate: + virtual void AdjustDefenses(); + virtual void FireControl(); + virtual void HelmControl(); + virtual void ThrottleControl(); + virtual void NavlightControl(); + + virtual void CheckTarget(); + + Ship* ship; + Ship* support; + Ship* rumor; + Ship* threat; + Shot* threat_missile; + Instruction* navpt; + Point obstacle; + TacticalAI* tactical; + Farcaster* farcaster; + int engaged_ship_id; + int splash_count; + + Point formation_delta; + double slot_dist; + + double throttle; + double old_throttle; + double seconds; + double drop_time; + double brake; + DWORD last_avoid_time; + DWORD last_call_time; + + int element_index; + int too_close; + bool bracket; + bool identify; + bool hold; + bool takeoff; + + int patrol; + Point patrol_loc; + int ai_level; +}; + +// +--------------------------------------------------------------------+ + +#endif ShipAI_h + diff --git a/Stars45/ShipCtrl.cpp b/Stars45/ShipCtrl.cpp new file mode 100644 index 0000000..0da5012 --- /dev/null +++ b/Stars45/ShipCtrl.cpp @@ -0,0 +1,341 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: ShipCtrl.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Ship Controller class +*/ + +#include "MemDebug.h" +#include "ShipCtrl.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "Shield.h" +#include "Sensor.h" +#include "NavSystem.h" +#include "FlightComp.h" +#include "LandingGear.h" +#include "Sim.h" +#include "StarSystem.h" +#include "Starshatter.h" +#include "HUDView.h" +#include "HUDSounds.h" +#include "KeyMap.h" +#include "RadioMessage.h" +#include "RadioTraffic.h" + +#include "MouseController.h" +#include "Keyboard.h" +#include "Joystick.h" +#include "Game.h" +#include "DataLoader.h" + +// +--------------------------------------------------------------------+ + +ShipCtrl::ShipCtrl(Ship* s, MotionController* ctrl) + : ship(s), controller(ctrl), throttle_active(false), launch_latch(false), + pickle_latch(false), target_latch(false) +{ + for (int i = 0; i < 10; i++) + GetAsyncKeyState('0'+i); +} + +// +--------------------------------------------------------------------+ + +int +ShipCtrl::KeyDown(int action) +{ + int k = Joystick::KeyDownMap(action) || + Keyboard::KeyDownMap(action); + + return k; +} + +int +ShipCtrl::Toggled(int action) +{ + static double last_toggle_time = 0; + + if (KeyDown(action)) { + if ((Game::RealTime() - last_toggle_time) > 250) { + last_toggle_time = Game::RealTime(); + return 1; + } + } + + return 0; +} + +// +--------------------------------------------------------------------+ + +void +ShipCtrl::Launch() +{ + if (controller) { + ship->SetThrottle(100); + throttle_active = false; + launch_latch = true; + } +} + +// +--------------------------------------------------------------------+ + +void +ShipCtrl::ExecFrame(double seconds) +{ + Starshatter* stars = Starshatter::GetInstance(); + if (stars && stars->GetChatMode()) return; + if (!ship) return; + + const int DELTA_THROTTLE = 5; + + controller->Acquire(); + + if (ship->IsStarship() && ship->GetFLCSMode() == Ship::FLCS_HELM) { + ship->ApplyHelmPitch(controller->Pitch() * seconds); + ship->ApplyHelmYaw(controller->Yaw() * seconds); + } + else { + ship->ApplyRoll(controller->Roll()); + ship->ApplyPitch(controller->Pitch()); + ship->ApplyYaw(controller->Yaw()); + } + + ship->SetTransX(controller->X() * ship->Design()->trans_x); + ship->SetTransY(controller->Y() * ship->Design()->trans_y); + ship->SetTransZ(controller->Z() * ship->Design()->trans_z); + + bool augmenter = false; + + if (controller->Throttle() > 0.05) { + if (throttle_active) { + ship->SetThrottle(controller->Throttle() * 100); + + if (controller->Throttle() >= 0.99) + augmenter = true; + } + else if (!launch_latch || controller->Throttle() > 0.5) { + throttle_active = true; + launch_latch = false; + } + } + else if (throttle_active) { + ship->SetThrottle(0); + throttle_active = false; + } + + ship->ExecFLCSFrame(); + + static double time_til_change = 0.0; + + if (time_til_change < 0.001) { + if (KeyDown(KEY_THROTTLE_UP)) { + ship->SetThrottle(ship->Throttle() + DELTA_THROTTLE); + time_til_change = 0.05; + } + + else if (KeyDown(KEY_THROTTLE_DOWN)) { + ship->SetThrottle(ship->Throttle() - DELTA_THROTTLE); + time_til_change = 0.05; + } + + else if (KeyDown(KEY_THROTTLE_ZERO)) { + ship->SetThrottle(0); + if (ship->GetFLCS()) + ship->GetFLCS()->FullStop(); + time_til_change = 0.05; + } + + else if (KeyDown(KEY_THROTTLE_FULL)) { + ship->SetThrottle(100); + time_til_change = 0.05; + } + + else if (KeyDown(KEY_FLCS_MODE_AUTO)) { + ship->CycleFLCSMode(); + time_til_change = 0.5f; + } + + else if (KeyDown(KEY_CYCLE_PRIMARY)) { + HUDSounds::PlaySound(HUDSounds::SND_WEP_MODE); + ship->CyclePrimary(); + time_til_change = 0.5f; + } + + else if (KeyDown(KEY_CYCLE_SECONDARY)) { + HUDSounds::PlaySound(HUDSounds::SND_WEP_MODE); + ship->CycleSecondary(); + time_til_change = 0.5f; + } + + if (ship->GetShield()) { + Shield* shield = ship->GetShield(); + double level = shield->GetPowerLevel(); + + if (KeyDown(KEY_SHIELDS_FULL)) { + HUDSounds::PlaySound(HUDSounds::SND_SHIELD_LEVEL); + shield->SetPowerLevel(100); + time_til_change = 0.5f; + } + + else if (KeyDown(KEY_SHIELDS_ZERO)) { + HUDSounds::PlaySound(HUDSounds::SND_SHIELD_LEVEL); + shield->SetPowerLevel(0); + time_til_change = 0.5f; + } + + else if (KeyDown(KEY_SHIELDS_UP)) { + if (level < 25) level = 25; + else if (level < 50) level = 50; + else if (level < 75) level = 75; + else level = 100; + + HUDSounds::PlaySound(HUDSounds::SND_SHIELD_LEVEL); + shield->SetPowerLevel(level); + time_til_change = 0.5f; + } + + else if (KeyDown(KEY_SHIELDS_DOWN)) { + if (level > 75) level = 75; + else if (level > 50) level = 50; + else if (level > 25) level = 25; + else level = 0; + + HUDSounds::PlaySound(HUDSounds::SND_SHIELD_LEVEL); + shield->SetPowerLevel(level); + time_til_change = 0.5f; + } + + } + + if (ship->GetSensor()) { + Sensor* sensor = ship->GetSensor(); + + if (sensor->GetMode() < Sensor::PST) { + if (KeyDown(KEY_SENSOR_MODE)) { + int sensor_mode = sensor->GetMode() + 1; + if (sensor_mode > Sensor::GM) + sensor_mode = Sensor::PAS; + + sensor->SetMode((Sensor::Mode) sensor_mode); + time_til_change = 0.5f; + } + + else if (KeyDown(KEY_SENSOR_GROUND_MODE)) { + if (ship->IsAirborne()) { + sensor->SetMode(Sensor::GM); + time_til_change = 0.5f; + } + } + } + else { + // manual "return to search" command for starships: + if (KeyDown(KEY_SENSOR_MODE)) { + ship->DropTarget(); + } + } + + if (KeyDown(KEY_SENSOR_RANGE_PLUS)) { + sensor->IncreaseRange(); + time_til_change = 0.5f; + } + + else if (KeyDown(KEY_SENSOR_RANGE_MINUS)) { + sensor->DecreaseRange(); + time_til_change = 0.5f; + } + } + + if (KeyDown(KEY_EMCON_PLUS)) { + ship->SetEMCON(ship->GetEMCON()+1); + time_til_change = 0.5f; + } + + else if (KeyDown(KEY_EMCON_MINUS)) { + ship->SetEMCON(ship->GetEMCON()-1); + time_til_change = 0.5f; + } + } + else + time_til_change -= seconds; + + if (controller->ActionMap(KEY_ACTION_0)) + ship->FirePrimary(); + + if (controller->ActionMap(KEY_ACTION_1)) { + if (!pickle_latch) + ship->FireSecondary(); + + pickle_latch = true; + } + else { + pickle_latch = false; + } + + if (controller->ActionMap(KEY_ACTION_3)) { + if (!target_latch) + ship->LockTarget(SimObject::SIM_SHIP); + + target_latch = true; + } + else { + target_latch = false; + } + + ship->SetAugmenter(augmenter || (KeyDown(KEY_AUGMENTER) ? true : false)); + + if (Toggled(KEY_DECOY)) + ship->FireDecoy(); + + if (Toggled(KEY_LAUNCH_PROBE)) + ship->LaunchProbe(); + + if (Toggled(KEY_GEAR_TOGGLE)) + ship->ToggleGear(); + + if (Toggled(KEY_NAVLIGHT_TOGGLE)) + ship->ToggleNavlights(); + + if (Toggled(KEY_LOCK_TARGET)) + ship->LockTarget(SimObject::SIM_SHIP, false, true); + + else if (Toggled(KEY_LOCK_THREAT)) + ship->LockTarget(SimObject::SIM_DRONE); + + else if (Toggled(KEY_LOCK_CLOSEST_SHIP)) + ship->LockTarget(SimObject::SIM_SHIP, true, false); + + else if (Toggled(KEY_LOCK_CLOSEST_THREAT)) + ship->LockTarget(SimObject::SIM_DRONE, true, false); + + else if (Toggled(KEY_LOCK_HOSTILE_SHIP)) + ship->LockTarget(SimObject::SIM_SHIP, true, true); + + else if (Toggled(KEY_LOCK_HOSTILE_THREAT)) + ship->LockTarget(SimObject::SIM_DRONE, true, true); + + else if (Toggled(KEY_CYCLE_SUBTARGET)) + ship->CycleSubTarget(1); + + else if (Toggled(KEY_PREV_SUBTARGET)) + ship->CycleSubTarget(-1); + + if (Toggled(KEY_AUTO_NAV)) { + ship->SetAutoNav(true); + // careful: this object has just been deleted! + return; + } + + if (Toggled(KEY_DROP_ORBIT)) { + ship->DropOrbit(); + // careful: this object has just been deleted! + return; + } +} + diff --git a/Stars45/ShipCtrl.h b/Stars45/ShipCtrl.h new file mode 100644 index 0000000..f694832 --- /dev/null +++ b/Stars45/ShipCtrl.h @@ -0,0 +1,59 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: ShipCtrl.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Starship (or space/ground station) class +*/ + +#ifndef ShipCtrl_h +#define ShipCtrl_h + +#include "Types.h" +#include "SimObject.h" +#include "MotionController.h" +#include "Director.h" +#include "Geometry.h" + +// +--------------------------------------------------------------------+ + +class Ship; +class ShipDesign; +class KeyMap; + +// +--------------------------------------------------------------------+ + +class ShipCtrl : public Director +{ +public: + enum TYPE { DIR_TYPE = 1 }; + + ShipCtrl(Ship* s, MotionController* m); + + virtual void ExecFrame(double seconds); + virtual int Subframe() const { return true; } + virtual void Launch(); + + static int KeyDown(int action); + static int Toggled(int action); + + virtual int Type() const { return DIR_TYPE; } + +protected: + Ship* ship; + MotionController* controller; + + bool throttle_active; + bool launch_latch; + bool pickle_latch; + bool target_latch; +}; + +#endif ShipCtrl_h + diff --git a/Stars45/ShipDesign.cpp b/Stars45/ShipDesign.cpp new file mode 100644 index 0000000..8c1da09 --- /dev/null +++ b/Stars45/ShipDesign.cpp @@ -0,0 +1,3700 @@ +/* Project Starshatter 5.0 + Destroyer Studios LLC + Copyright © 1997-2007. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: ShipDesign.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Starship Design parameters class +*/ + +#include "MemDebug.h" +#include "ShipDesign.h" +#include "Ship.h" +#include "Shot.h" +#include "Power.h" +#include "HardPoint.h" +#include "Weapon.h" +#include "WeaponDesign.h" +#include "Shield.h" +#include "Sensor.h" +#include "NavLight.h" +#include "NavSystem.h" +#include "Drive.h" +#include "QuantumDrive.h" +#include "Farcaster.h" +#include "Thruster.h" +#include "FlightDeck.h" +#include "LandingGear.h" +#include "Computer.h" +#include "SystemDesign.h" +#include "Component.h" + +#include "Game.h" +#include "Solid.h" +#include "Skin.h" +#include "Sprite.h" +#include "Light.h" +#include "Bitmap.h" +#include "Sound.h" +#include "DataLoader.h" +#include "ParseUtil.h" + +// +--------------------------------------------------------------------+ + +const char* ship_design_class_name[32] = { + "Drone", "Fighter", + "Attack", "LCA", + "Courier", "Cargo", + "Corvette", "Freighter", + + "Frigate", "Destroyer", + "Cruiser", "Battleship", + "Carrier", "Dreadnaught", + + "Station", "Farcaster", + + "Mine", "DEFSAT", + "COMSAT", "SWACS", + + "Building", "Factory", + "SAM", "EWR", + "C3I", "Starbase", + + "0x04000000", "0x08000000", + "0x10000000", "0x20000000", + "0x40000000", "0x80000000" +}; + +// +--------------------------------------------------------------------+ + +static const int NAMELEN = 64; +static bool degrees = false; + +struct ShipCatalogEntry { + static const char* TYPENAME() { return "ShipCatalogEntry"; } + + ShipCatalogEntry() : hide(false), design(0) {} + + ShipCatalogEntry(const char* n, const char* t, const char* p, const char* f, bool h=false) : + name(n), type(t), path(p), file(f), hide(h), design(0) {} + + ~ShipCatalogEntry() { delete design; } + + Text name; + Text type; + Text path; + Text file; + bool hide; + + ShipDesign* design; +}; + +static List catalog; +static List mod_catalog; + +// +--------------------------------------------------------------------+ + +#define GET_DEF_BOOL(n) if (defname==(#n)) GetDefBool((n), def, filename) +#define GET_DEF_TEXT(n) if (defname==(#n)) GetDefText((n), def, filename) +#define GET_DEF_NUM(n) if (defname==(#n)) GetDefNumber((n), def, filename) +#define GET_DEF_VEC(n) if (defname==(#n)) GetDefVec((n), def, filename) + +static char cockpit_name[80]; +static List detail[4]; +static List offset[4]; + +static char errmsg[256]; + +// +--------------------------------------------------------------------+ + +ShipLoad::ShipLoad() +{ + ZeroMemory(name, sizeof(name)); + ZeroMemory(load, sizeof(load)); + mass = 0; +} + +ShipSquadron::ShipSquadron() +{ + name[0] = 0; + design = 0; + count = 4; + avail = 4; +} + +static void PrepareModel(Model& model) +{ + bool uses_bumps = false; + + ListIter iter = model.GetMaterials(); + while (++iter && !uses_bumps) { + Material* mtl = iter.value(); + if (mtl->tex_bumpmap != 0 && mtl->bump != 0) + uses_bumps = true; + } + + if (uses_bumps) + model.ComputeTangents(); +} + +// +--------------------------------------------------------------------+ + +ShipDesign::ShipDesign() + : sensor(0), navsys(0), shield(0), type(0), decoy(0), + probe(0), gear(0), valid(false), secret(false), auto_roll(1), cockpit_model(0), + bolt_hit_sound_resource(0), beam_hit_sound_resource(0), lod_levels(0) +{ + ZeroMemory(filename, sizeof(filename)); + ZeroMemory(path_name, sizeof(path_name)); + ZeroMemory(name, sizeof(name)); + ZeroMemory(display_name, sizeof(display_name)); + ZeroMemory(abrv, sizeof(abrv)); + + for (int i = 0; i < 4; i++) + feature_size[i] = 0.0f; +} + +// +--------------------------------------------------------------------+ + +ShipDesign::ShipDesign(const char* n, const char* p, const char* fname, bool s) + : sensor(0), navsys(0), shield(0), type(0), + quantum_drive(0), farcaster(0), thruster(0), shield_model(0), decoy(0), + probe(0), gear(0), valid(false), secret(s), auto_roll(1), cockpit_model(0), + bolt_hit_sound_resource(0), beam_hit_sound_resource(0), lod_levels(0) +{ + ZeroMemory(filename, sizeof(filename)); + ZeroMemory(path_name, sizeof(path_name)); + ZeroMemory(name, sizeof(name)); + ZeroMemory(display_name, sizeof(display_name)); + ZeroMemory(abrv, sizeof(abrv)); + + strcpy(name, n); + + if (!strstr(fname, ".def")) + sprintf(filename, "%s.def", fname); + else + strcpy(filename, fname); + + for (int i = 0; i < 4; i++) + feature_size[i] = 0.0f; + + scale = 1.0f; + + agility = 2e2f; + air_factor = 0.1f; + vlimit = 8e3f; + drag = 2.5e-5f; + arcade_drag = 1.0f; + roll_drag = 5.0f; + pitch_drag = 5.0f; + yaw_drag = 5.0f; + + roll_rate = 0.0f; + pitch_rate = 0.0f; + yaw_rate = 0.0f; + + trans_x = 0.0f; + trans_y = 0.0f; + trans_z = 0.0f; + + turn_bank = (float) (PI/8); + + CL = 0.0f; + CD = 0.0f; + stall = 0.0f; + + prep_time = 30.0f; + avoid_time = 0.0f; + avoid_fighter = 0.0f; + avoid_strike = 0.0f; + avoid_target = 0.0f; + commit_range = 0.0f; + + splash_radius = -1.0f; + scuttle = 5e3f; + repair_speed = 1.0f; + repair_teams = 2; + repair_auto = true; + repair_screen = true; + wep_screen = true; + + chase_vec = Vec3(0, -100, 20); + bridge_vec = Vec3(0, 0, 0); + beauty_cam = Vec3(0, 0, 0); + cockpit_scale = 1.0f; + + radius = 1.0f; + integrity = 500.0f; + + primary = 0; + secondary = 1; + main_drive = -1; + + pcs = 3.0f; + acs = 1.0f; + detet = 250.0e3f; + e_factor[0] = 0.1f; + e_factor[1] = 0.3f; + e_factor[2] = 1.0f; + + explosion_scale = 0.0f; + death_spiral_time = 3.0f; + + if (!secret) + Print("Loading ShipDesign '%s'\n", name); + + strcpy(path_name, p); + if (path_name[strlen(path_name)-1] != '/') + strcat(path_name, "/"); + + // Load Design File: + DataLoader* loader = DataLoader::GetLoader(); + loader->SetDataPath(path_name); + + BYTE* block; + int blocklen = loader->LoadBuffer(filename, block, true); + + // file not found: + if (blocklen <= 4) { + valid = false; + return; + } + + Parser parser(new(__FILE__,__LINE__) BlockReader((const char*) block, blocklen)); + Term* term = parser.ParseTerm(); + + if (!term) { + Print("ERROR: could not parse '%s'\n", filename); + valid = false; + return; + } + else { + TermText* file_type = term->isText(); + if (!file_type || file_type->value() != "SHIP") { + Print("ERROR: invalid ship design file '%s'\n", filename); + valid = false; + return; + } + } + + cockpit_name[0] = 0; + valid = true; + degrees = false; + + do { + delete term; + + term = parser.ParseTerm(); + + if (term) { + TermDef* def = term->isDef(); + if (def) { + ParseShip(def); + } + else { + Print("WARNING: term ignored in '%s'\n", filename); + term->print(); + } + } + } + while (term); + + for (i = 0; i < 4; i++) { + int n = 0; + ListIter iter = detail[i]; + while (++iter) { + const char* model_name = iter.value()->data(); + + Model* model = new(__FILE__,__LINE__) Model; + if (!model->Load(model_name, scale)) { + Print("ERROR: Could not load detail %d, model '%s'\n", i, model_name); + delete model; + model = 0; + valid = false; + } + + else { + lod_levels = i+1; + + if (model->Radius() > radius) + radius = (float) model->Radius(); + + models[i].append(model); + PrepareModel(*model); + + if (offset[i].size()) { + *offset[i].at(n) *= scale; + offsets[i].append(offset[i].at(n)); // transfer ownership + } + else + offsets[i].append(new(__FILE__,__LINE__) Point); + + n++; + } + } + + detail[i].destroy(); + } + + if (!secret) + Print(" Ship Design Radius = %f\n", radius); + + if (cockpit_name[0]) { + const char* model_name = cockpit_name; + + cockpit_model = new(__FILE__,__LINE__) Model; + if (!cockpit_model->Load(model_name, cockpit_scale)) { + Print("ERROR: Could not load cockpit model '%s'\n", model_name); + delete cockpit_model; + cockpit_model = 0; + } + else { + if (!secret) + Print(" Loaded cockpit model '%s', preparing tangents\n", model_name); + PrepareModel(*cockpit_model); + } + } + + if (beauty.Width() < 1 && loader->FindFile("beauty.pcx")) + loader->LoadBitmap("beauty.pcx", beauty); + + if (hud_icon.Width() < 1 && loader->FindFile("hud_icon.pcx")) + loader->LoadBitmap("hud_icon.pcx", hud_icon); + + loader->ReleaseBuffer(block); + loader->SetDataPath(0); + + if (abrv[0] == 0) { + switch (type) { + case Ship::DRONE: strcpy(abrv, "DR"); break; + case Ship::FIGHTER: strcpy(abrv, "F"); break; + case Ship::ATTACK: strcpy(abrv, "F/A"); break; + case Ship::LCA: strcpy(abrv, "LCA"); break; + case Ship::CORVETTE: strcpy(abrv, "FC"); break; + case Ship::COURIER: + case Ship::CARGO: + case Ship::FREIGHTER: strcpy(abrv, "MV"); break; + case Ship::FRIGATE: strcpy(abrv, "FF"); break; + case Ship::DESTROYER: strcpy(abrv, "DD"); break; + case Ship::CRUISER: strcpy(abrv, "CA"); break; + case Ship::BATTLESHIP: strcpy(abrv, "BB"); break; + case Ship::CARRIER: strcpy(abrv, "CV"); break; + case Ship::DREADNAUGHT: strcpy(abrv, "DN"); break; + case Ship::MINE: strcpy(abrv, "MINE"); break; + case Ship::COMSAT: strcpy(abrv, "COMS"); break; + case Ship::DEFSAT: strcpy(abrv, "DEFS"); break; + case Ship::SWACS: strcpy(abrv, "SWAC"); break; + default: break; + } + } + + if (scuttle < 1) + scuttle = 1; + + if (splash_radius < 0) + splash_radius = radius * 12.0f; + + if (repair_speed <= 1e-6) + repair_speed = 1.0e-6f; + + if (commit_range <= 0) { + if (type <= Ship::LCA) + commit_range = 80.0e3f; + else + commit_range = 200.0e3f; + } + + // calc standard loadout weights: + ListIter sl = loadouts; + while (++sl) { + for (int i = 0; i < hard_points.size(); i++) { + HardPoint* hp = hard_points[i]; + sl->mass += hp->GetCarryMass(sl->load[i]); + } + } +} + +// +--------------------------------------------------------------------+ + +ShipDesign::~ShipDesign() +{ + delete bolt_hit_sound_resource; + delete beam_hit_sound_resource; + delete cockpit_model; + delete navsys; + delete sensor; + delete shield; + delete thruster; + delete farcaster; + delete quantum_drive; + delete decoy; + delete probe; + delete gear; + + navlights.destroy(); + flight_decks.destroy(); + hard_points.destroy(); + computers.destroy(); + weapons.destroy(); + drives.destroy(); + reactors.destroy(); + loadouts.destroy(); + map_sprites.destroy(); + + delete shield_model; + for (int i = 0; i < 4; i++) { + models[i].destroy(); + offsets[i].destroy(); + } + + spin_rates.destroy(); + + for (i = 0; i < 10; i++) { + delete debris[i].model; + } +} + +const char* +ShipDesign::DisplayName() const +{ + if (display_name[0]) + return display_name; + + return name; +} + +// +--------------------------------------------------------------------+ + +void AddModCatalogEntry(const char* design_name, const char* design_path) +{ + if (!design_name || !*design_name) + return; + + ShipCatalogEntry* entry = 0; + + for (int i = 0; i < catalog.size(); i++) { + ShipCatalogEntry* e = catalog[i]; + if (e->name == design_name) { + if (design_path && *design_path && e->path != design_path) + continue; + entry = e; + return; + } + } + + for (i = 0; i < mod_catalog.size(); i++) { + ShipCatalogEntry* e = mod_catalog[i]; + if (e->name == design_name) { + if (design_path && *design_path) { + Text full_path = "Mods/Ships/"; + full_path += design_path; + + if (e->path != full_path) + continue; + } + + entry = e; + return; + } + } + + // still here? not found yet: + Text file = Text(design_name) + ".def"; + Text path = Text("Mods/Ships/"); + Text name; + Text type; + bool valid = false; + + if (design_path && *design_path) + path += design_path; + else + path += design_name; + + path += "/"; + + DataLoader* loader = DataLoader::GetLoader(); + loader->SetDataPath(path); + + BYTE* block; + int blocklen = loader->LoadBuffer(file, block, true); + + // file not found: + if (blocklen <= 4) { + return; + } + + Parser parser(new(__FILE__,__LINE__) BlockReader((const char*) block, blocklen)); + Term* term = parser.ParseTerm(); + + if (!term) { + Print("ERROR: could not parse '%s'\n", file); + delete block; + return; + } + else { + TermText* file_type = term->isText(); + if (!file_type || file_type->value() != "SHIP") { + Print("ERROR: invalid ship design file '%s'\n", file); + delete block; + return; + } + } + + valid = true; + + do { + delete term; + + term = parser.ParseTerm(); + + if (term) { + TermDef* def = term->isDef(); + if (def) { + Text defname = def->name()->value(); + defname.setSensitive(false); + + if (defname == "class") { + if (!GetDefText(type, def, file)) { + Print("WARNING: invalid or missing ship class in '%s'\n", file); + valid = false; + } + } + + else if (defname == "name") { + if (!GetDefText(name, def, file)) { + Print("WARNING: invalid or missing ship name in '%s'\n", file); + valid = false; + } + } + } + else { + Print("WARNING: term ignored in '%s'\n", file); + term->print(); + } + } + } + while (term && valid && (name.length() < 1 || type.length() < 1)); + + delete block; + + if (valid && name.length() && type.length()) { + Print("Add Mod Catalog Entry '%s' Class '%s'\n", name.data(), type.data()); + + ShipCatalogEntry* entry = new(__FILE__,__LINE__) ShipCatalogEntry(name, type, path, file); + mod_catalog.append(entry); + } +} + +void +ShipDesign::Initialize() +{ + if (catalog.size()) return; + + LoadCatalog("Ships/", "catalog.def"); + LoadSkins("Mods/Skins/"); + + List mod_designs; + DataLoader* loader = DataLoader::GetLoader(); + loader->SetDataPath("Mods/Ships/"); + loader->ListFiles("*.def", mod_designs, true); + + for (int i = 0; i < mod_designs.size(); i++) { + Text full_name = *mod_designs[i]; + full_name.setSensitive(false); + + if (full_name.contains('/') && !full_name.contains("catalog")) { + char path[1024]; + strcpy(path, full_name.data()); + + char* name = path + full_name.length(); + while (*name != '/') + name--; + + *name++ = 0; + + char* p = strrchr(name, '.'); + if (p && strlen(p) > 3) { + if ((p[1] == 'd' || p[1] == 'D') && + (p[2] == 'e' || p[2] == 'E') && + (p[3] == 'f' || p[3] == 'F')) { + + *p = 0; + } + } + + // Just do a quick parse of the def file and add the + // info to the catalog. DON'T preload all of the models, + // textures, and weapons at this time. That takes way + // too long with some of the larger user mods. + + AddModCatalogEntry(name, path); + } + } + + mod_designs.destroy(); + loader->SetDataPath(0); +} + +void +ShipDesign::Close() +{ + mod_catalog.destroy(); + catalog.destroy(); +} + +// +--------------------------------------------------------------------+ + +int +ShipDesign::LoadCatalog(const char* path, const char* fname, bool mod) +{ + int result = 0; + + // Load Design Catalog File: + DataLoader* loader = DataLoader::GetLoader(); + loader->SetDataPath(path); + + char filename[NAMELEN]; + ZeroMemory(filename, NAMELEN); + strncpy(filename, fname, NAMELEN-1); + + Print("Loading ship design catalog: %s%s\n", path, filename); + + BYTE* block; + int blocklen = loader->LoadBuffer(filename, block, true); + Parser parser(new(__FILE__,__LINE__) BlockReader((const char*) block, blocklen)); + Term* term = parser.ParseTerm(); + + if (!term) { + Print("ERROR: could not parse '%s'\n", filename); + loader->ReleaseBuffer(block); + loader->SetDataPath(0); + return result; + } + else { + TermText* file_type = term->isText(); + if (!file_type || file_type->value() != "SHIPCATALOG") { + Print("ERROR: invalid ship catalog file '%s'\n", filename); + loader->ReleaseBuffer(block); + loader->SetDataPath(0); + return result; + } + } + + do { + delete term; + + term = parser.ParseTerm(); + + Text name, type, fname, path; + bool hide = false; + + if (term) { + TermDef* def = term->isDef(); + if (def && def->term() && def->term()->isStruct()) { + TermStruct* val = def->term()->isStruct(); + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + Text defname = pdef->name()->value(); + defname.setSensitive(false); + + if (defname == "name") { + if (!GetDefText(name, pdef, filename)) + Print("WARNING: invalid or missing ship name in '%s'\n", filename); + } + else if (defname == "type") { + if (!GetDefText(type, pdef, filename)) + Print("WARNING: invalid or missing ship type in '%s'\n", filename); + } + else if (defname == "path") { + if (!GetDefText(path, pdef, filename)) + Print("WARNING: invalid or missing ship path in '%s'\n", filename); + } + else if (defname == "file") { + if (!GetDefText(fname, pdef, filename)) + Print("WARNING: invalid or missing ship file in '%s'\n", filename); + } + else if (defname == "hide" || defname == "secret") { + GetDefBool(hide, pdef, filename); + } + } + } + + ShipCatalogEntry* entry = new(__FILE__,__LINE__) ShipCatalogEntry(name, type, path, fname, hide); + + if (mod) mod_catalog.append(entry); + else catalog.append(entry); + + result++; + } + else { + Print("WARNING: term ignored in '%s'\n", filename); + term->print(); + } + } + } + while (term); + + loader->ReleaseBuffer(block); + loader->SetDataPath(0); + + return result; +} + +// +--------------------------------------------------------------------+ + +void +ShipDesign::LoadSkins(const char* path, const char* archive) +{ + // Load MOD Skin Files: + List list; + DataLoader* loader = DataLoader::GetLoader(); + bool oldfs = loader->IsFileSystemEnabled(); + + loader->UseFileSystem(true); + loader->SetDataPath(path); + loader->ListArchiveFiles(archive, "*.def", list); + + ListIter iter = list; + while (++iter) { + Text filename = *iter.value(); + BYTE* block; + int blocklen = loader->LoadBuffer(filename, block, true); + + // file not found: + if (blocklen <= 4) { + continue; + } + + Parser parser(new(__FILE__,__LINE__) BlockReader((const char*) block, blocklen)); + Term* term = parser.ParseTerm(); + ShipDesign* design = 0; + + if (!term) { + Print("ERROR: could not parse '%s'\n", filename); + return; + } + else { + TermText* file_type = term->isText(); + if (!file_type || file_type->value() != "SKIN") { + Print("ERROR: invalid skin file '%s'\n", filename); + return; + } + } + + do { + delete term; + + term = parser.ParseTerm(); + + if (term) { + TermDef* def = term->isDef(); + if (def) { + Text defname = def->name()->value(); + defname.setSensitive(false); + + if (defname == "name") { + Text name; + GetDefText(name, def, filename); + design = Get(name); + } + + else if (defname == "skin" && design != 0) { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: skin struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + Skin* skin = design->ParseSkin(val); + + if (skin) + skin->SetPath(archive); + } + } + } + } + } + while (term); + } + + loader->UseFileSystem(oldfs); +} + +// +--------------------------------------------------------------------+ + +int +ShipDesign::StandardCatalogSize() +{ + return catalog.size(); +} + +void +ShipDesign::PreloadCatalog(int index) +{ + if (index >= 0 && index < catalog.size()) { + ShipCatalogEntry* entry = catalog[index]; + + if (entry->hide) + return; + + int ship_class = ClassForName(entry->type); + if (ship_class > Ship::STARSHIPS) + return; + + if (!entry->path.contains("Alliance_")) + return; + + if (!entry->design) { + entry->design = new(__FILE__,__LINE__) ShipDesign(entry->name, + entry->path, + entry->file, + entry->hide); + } + } + + else { + ListIter iter = catalog; + while (++iter) { + ShipCatalogEntry* entry = iter.value(); + + if (!entry->design) { + entry->design = new(__FILE__,__LINE__) ShipDesign(entry->name, + entry->path, + entry->file, + entry->hide); + } + } + } +} + +// +--------------------------------------------------------------------+ + +bool +ShipDesign::CheckName(const char* design_name) +{ + ShipCatalogEntry* entry = 0; + + for (int i = 0; i < catalog.size(); i++) { + if (catalog.at(i)->name == design_name) { + entry = catalog.at(i); + break; + } + } + + if (!entry) { + for (int i = 0; i < mod_catalog.size(); i++) { + if (mod_catalog.at(i)->name == design_name) { + entry = mod_catalog.at(i); + break; + } + } + } + + return entry != 0; +} + +// +--------------------------------------------------------------------+ + +ShipDesign* +ShipDesign::Get(const char* design_name, const char* design_path) +{ + if (!design_name || !*design_name) + return 0; + + ShipCatalogEntry* entry = 0; + + for (int i = 0; i < catalog.size(); i++) { + ShipCatalogEntry* e = catalog[i]; + if (e->name == design_name) { + if (design_path && *design_path && e->path != design_path) + continue; + entry = e; + break; + } + } + + if (!entry) { + for (int i = 0; i < mod_catalog.size(); i++) { + ShipCatalogEntry* e = mod_catalog[i]; + if (e->name == design_name) { + if (design_path && *design_path) { + Text full_path = "Mods/Ships/"; + full_path += design_path; + + if (e->path != full_path) + continue; + } + + entry = e; + break; + } + } + } + + if (entry) { + if (!entry->design) { + entry->design = new(__FILE__,__LINE__) ShipDesign(entry->name, + entry->path, + entry->file, + entry->hide); + } + return entry->design; + } + else { + Print("ShipDesign: no catalog entry for design '%s', checking mods...\n", design_name); + return ShipDesign::FindModDesign(design_name, design_path); + } +} + +ShipDesign* +ShipDesign::FindModDesign(const char* design_name, const char* design_path) +{ + Text file = Text(design_name) + ".def"; + Text path = Text("Mods/Ships/"); + + if (design_path && *design_path) + path += design_path; + else + path += design_name; + + DataLoader* loader = DataLoader::GetLoader(); + loader->SetDataPath(path); + + ShipDesign* design = new(__FILE__,__LINE__) ShipDesign(design_name, path, file); + + if (design->valid) { + Print("ShipDesign: found mod design '%s'\n", design->name); + + ShipCatalogEntry* entry = new(__FILE__,__LINE__) ShipCatalogEntry(design->name, + ClassName(design->type), + path, + file); + mod_catalog.append(entry); + entry->design = design; + return entry->design; + } + else { + delete design; + } + + return 0; +} + +void +ShipDesign::ClearModCatalog() +{ + mod_catalog.destroy(); + + for (int i = 0; i < catalog.size(); i++) { + ShipCatalogEntry* e = catalog[i]; + + if (e && e->design) { + ListIter iter = e->design->skins; + + while (++iter) { + Skin* skin = iter.value(); + if (*skin->Path()) + iter.removeItem(); + } + } + } +} + +// +--------------------------------------------------------------------+ + +int +ShipDesign::GetDesignList(int type, List& designs) +{ + designs.clear(); + + for (int i = 0; i < catalog.size(); i++) { + ShipCatalogEntry* e = catalog[i]; + + int etype = ClassForName(e->type); + if (etype & type) { + if (!e->design) + e->design = new(__FILE__,__LINE__) ShipDesign(e->name, + e->path, + e->file, + e->hide); + + if (e->hide || !e->design || !e->design->valid || e->design->secret) + continue; + + designs.append(&e->name); + } + } + + for (i = 0; i < mod_catalog.size(); i++) { + ShipCatalogEntry* e = mod_catalog[i]; + + int etype = ClassForName(e->type); + if (etype & type) { + designs.append(&e->name); + } + } + + return designs.size(); +} + +// +--------------------------------------------------------------------+ + +int +ShipDesign::ClassForName(const char* name) +{ + if (!name || !name[0]) + return 0; + + for (int i = 0; i < 32; i++) { + if (!stricmp(name, ship_design_class_name[i])) { + return 1 << i; + } + } + + return 0; +} + +const char* +ShipDesign::ClassName(int type) +{ + if (type != 0) { + int index = 0; + + while (!(type & 1)) { + type >>= 1; + index++; + } + + if (index >= 0 && index < 32) + return ship_design_class_name[index]; + } + + return "Unknown"; +} + +// +--------------------------------------------------------------------+ + +void +ShipDesign::ParseShip(TermDef* def) +{ + char detail_name[NAMELEN]; + Vec3 off_loc; + Vec3 spin; + Text defname = def->name()->value(); + + defname.setSensitive(false); + + if (defname == "cockpit_model") { + if (!GetDefText(cockpit_name, def, filename)) + Print("WARNING: invalid or missing cockpit_model in '%s'\n", filename); + } + + else if (defname == "model" || defname == "detail_0") { + if (!GetDefText(detail_name, def, filename)) + Print("WARNING: invalid or missing model in '%s'\n", filename); + + detail[0].append(new(__FILE__,__LINE__) Text(detail_name)); + } + + else if (defname == "detail_1") { + if (!GetDefText(detail_name, def, filename)) + Print("WARNING: invalid or missing detail_1 in '%s'\n", filename); + + detail[1].append(new(__FILE__,__LINE__) Text(detail_name)); + } + + else if (defname == "detail_2") { + if (!GetDefText(detail_name, def, filename)) + Print("WARNING: invalid or missing detail_2 in '%s'\n", filename); + + detail[2].append(new(__FILE__,__LINE__) Text(detail_name)); + } + + else if (defname == "detail_3") { + if (!GetDefText(detail_name, def, filename)) + Print("WARNING: invalid or missing detail_3 in '%s'\n", filename); + + detail[3].append(new(__FILE__,__LINE__) Text(detail_name)); + } + + else if (defname == "spin") { + if (!GetDefVec(spin, def, filename)) + Print("WARNING: invalid or missing spin in '%s'\n", filename); + + spin_rates.append(new(__FILE__,__LINE__) Point(spin)); + } + + else if (defname == "offset_0") { + if (!GetDefVec(off_loc, def, filename)) + Print("WARNING: invalid or missing offset_0 in '%s'\n", filename); + + offset[0].append(new(__FILE__,__LINE__) Point(off_loc)); + } + + else if (defname == "offset_1") { + if (!GetDefVec(off_loc, def, filename)) + Print("WARNING: invalid or missing offset_1 in '%s'\n", filename); + + offset[1].append(new(__FILE__,__LINE__) Point(off_loc)); + } + + else if (defname == "offset_2") { + if (!GetDefVec(off_loc, def, filename)) + Print("WARNING: invalid or missing offset_2 in '%s'\n", filename); + + offset[2].append(new(__FILE__,__LINE__) Point(off_loc)); + } + + else if (defname == "offset_3") { + if (!GetDefVec(off_loc, def, filename)) + Print("WARNING: invalid or missing offset_3 in '%s'\n", filename); + + offset[3].append(new(__FILE__,__LINE__) Point(off_loc)); + } + + else if (defname == "beauty") { + if (def->term() && def->term()->isArray()) { + GetDefVec(beauty_cam, def, filename); + + if (degrees) { + beauty_cam.x *= (float) DEGREES; + beauty_cam.y *= (float) DEGREES; + } + } + + else { + char beauty_name[64]; + if (!GetDefText(beauty_name, def, filename)) + Print("WARNING: invalid or missing beauty in '%s'\n", filename); + + DataLoader* loader = DataLoader::GetLoader(); + loader->LoadBitmap(beauty_name, beauty); + } + } + + else if (defname == "hud_icon") { + char hud_icon_name[64]; + if (!GetDefText(hud_icon_name, def, filename)) + Print("WARNING: invalid or missing hud_icon in '%s'\n", filename); + + DataLoader* loader = DataLoader::GetLoader(); + loader->LoadBitmap(hud_icon_name, hud_icon); + } + + else if (defname == "feature_0") { + if (!GetDefNumber(feature_size[0], def, filename)) + Print("WARNING: invalid or missing feature_0 in '%s'\n", filename); + } + + else if (defname == "feature_1") { + if (!GetDefNumber(feature_size[1], def, filename)) + Print("WARNING: invalid or missing feature_1 in '%s'\n", filename); + } + + else if (defname == "feature_2") { + if (!GetDefNumber(feature_size[2], def, filename)) + Print("WARNING: invalid or missing feature_2 in '%s'\n", filename); + } + + else if (defname == "feature_3") { + if (!GetDefNumber(feature_size[3], def, filename)) + Print("WARNING: invalid or missing feature_3 in '%s'\n", filename); + } + + + else if (defname == "class") { + char typestr[64]; + if (!GetDefText(typestr, def, filename)) + Print("WARNING: invalid or missing ship class in '%s'\n", filename); + + type = ClassForName(typestr); + + if (type <= Ship::LCA) { + repair_auto = false; + repair_screen = false; + wep_screen = false; + } + } + + else GET_DEF_TEXT(name); + else GET_DEF_TEXT(description); + else GET_DEF_TEXT(display_name); + else GET_DEF_TEXT(abrv); + else GET_DEF_NUM(pcs); + else GET_DEF_NUM(acs); + else GET_DEF_NUM(detet); + else GET_DEF_NUM(scale); + else GET_DEF_NUM(explosion_scale); + else GET_DEF_NUM(mass); + else GET_DEF_NUM(vlimit); + else GET_DEF_NUM(agility); + else GET_DEF_NUM(air_factor); + else GET_DEF_NUM(roll_rate); + else GET_DEF_NUM(pitch_rate); + else GET_DEF_NUM(yaw_rate); + else GET_DEF_NUM(integrity); + else GET_DEF_NUM(drag); + else GET_DEF_NUM(arcade_drag); + else GET_DEF_NUM(roll_drag); + else GET_DEF_NUM(pitch_drag); + else GET_DEF_NUM(yaw_drag); + else GET_DEF_NUM(trans_x); + else GET_DEF_NUM(trans_y); + else GET_DEF_NUM(trans_z); + else GET_DEF_NUM(turn_bank); + else GET_DEF_NUM(cockpit_scale); + else GET_DEF_NUM(auto_roll); + + else GET_DEF_NUM(CL); + else GET_DEF_NUM(CD); + else GET_DEF_NUM(stall); + + else GET_DEF_NUM(prep_time); + else GET_DEF_NUM(avoid_time); + else GET_DEF_NUM(avoid_fighter); + else GET_DEF_NUM(avoid_strike); + else GET_DEF_NUM(avoid_target); + else GET_DEF_NUM(commit_range); + + else GET_DEF_NUM(splash_radius); + else GET_DEF_NUM(scuttle); + else GET_DEF_NUM(repair_speed); + else GET_DEF_NUM(repair_teams); + else GET_DEF_BOOL(secret); + else GET_DEF_BOOL(repair_auto); + else GET_DEF_BOOL(repair_screen); + else GET_DEF_BOOL(wep_screen); + else GET_DEF_BOOL(degrees); + + else if (defname == "emcon_1") { + GetDefNumber(e_factor[0], def, filename); + } + + else if (defname == "emcon_2") { + GetDefNumber(e_factor[1], def, filename); + } + + else if (defname == "emcon_3") { + GetDefNumber(e_factor[2], def, filename); + } + + else if (defname == "chase") { + if (!GetDefVec(chase_vec, def, filename)) + Print("WARNING: invalid or missing chase cam loc in '%s'\n", filename); + + chase_vec *= (float) scale; + } + + else if (defname == "bridge") { + if (!GetDefVec(bridge_vec, def, filename)) + Print("WARNING: invalid or missing bridge cam loc in '%s'\n", filename); + + bridge_vec *= (float) scale; + } + + else if (defname == "power") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: power source struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + ParsePower(val); + } + } + + else if (defname == "main_drive" || defname == "drive") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: main drive struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + ParseDrive(val); + } + } + + else if (defname == "quantum" || defname == "quantum_drive") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: quantum_drive struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + ParseQuantumDrive(val); + } + } + + else if (defname == "sender" || defname == "farcaster") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: farcaster struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + ParseFarcaster(val); + } + } + + else if (defname == "thruster") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: thruster struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + ParseThruster(val); + } + } + + else if (defname == "navlight") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: navlight struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + ParseNavlight(val); + } + } + + else if (defname == "flightdeck") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: flightdeck struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + ParseFlightDeck(val); + } + } + + else if (defname == "gear") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: gear struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + ParseLandingGear(val); + } + } + + else if (defname == "weapon") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: weapon struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + ParseWeapon(val); + } + } + + else if (defname == "hardpoint") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: hardpoint struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + ParseHardPoint(val); + } + } + + else if (defname == "loadout") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: loadout struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + ParseLoadout(val); + } + } + + else if (defname == "decoy") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: decoy struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + ParseWeapon(val); + } + } + + else if (defname == "probe") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: probe struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + ParseWeapon(val); + } + } + + else if (defname == "sensor") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: sensor struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + ParseSensor(val); + } + } + + else if (defname == "nav") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: nav struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + ParseNavsys(val); + } + } + + else if (defname == "computer") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: computer struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + ParseComputer(val); + } + } + + else if (defname == "shield") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: shield struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + ParseShield(val); + } + } + + else if (defname == "death_spiral") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: death spiral struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + ParseDeathSpiral(val); + } + } + + else if (defname == "map") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: map struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + ParseMap(val); + } + } + + else if (defname == "squadron") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: squadron struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + ParseSquadron(val); + } + } + + else if (defname == "skin") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: skin struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + ParseSkin(val); + } + } + + else { + Print("WARNING: unknown parameter '%s' in '%s'\n", + defname.data(), filename); + } + + if (description.length()) + description = Game::GetText(description); +} + +// +--------------------------------------------------------------------+ + +void +ShipDesign::ParsePower(TermStruct* val) +{ + int stype = 0; + float output = 1000.0f; + float fuel = 0.0f; + Vec3 loc(0.0f, 0.0f, 0.0f); + float size = 0.0f; + float hull = 0.5f; + Text design_name; + Text pname; + Text pabrv; + int etype = 0; + int emcon_1 = -1; + int emcon_2 = -1; + int emcon_3 = -1; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + Text defname = pdef->name()->value(); + defname.setSensitive(false); + + if (defname == "type") { + TermText* tname = pdef->term()->isText(); + + if (tname) { + if (tname->value()[0] == 'B') stype = PowerSource::BATTERY; + else if (tname->value()[0] == 'A') stype = PowerSource::AUX; + else if (tname->value()[0] == 'F') stype = PowerSource::FUSION; + else Print("WARNING: unknown power source type '%s' in '%s'\n", tname->value().data(), filename); + } + } + + else if (defname == "name") { + GetDefText(pname, pdef, filename); + } + + else if (defname == "abrv") { + GetDefText(pabrv, pdef, filename); + } + + else if (defname == "design") { + GetDefText(design_name, pdef, filename); + } + + else if (defname == "max_output") { + GetDefNumber(output, pdef, filename); + } + else if (defname == "fuel_range") { + GetDefNumber(fuel, pdef, filename); + } + + else if (defname == "loc") { + GetDefVec(loc, pdef, filename); + loc *= (float) scale; + } + else if (defname == "size") { + GetDefNumber(size, pdef, filename); + size *= (float) scale; + } + else if (defname == "hull_factor") { + GetDefNumber(hull, pdef, filename); + } + + else if (defname == "explosion") { + GetDefNumber(etype, pdef, filename); + } + + else if (defname == "emcon_1") { + GetDefNumber(emcon_1, pdef, filename); + } + + else if (defname == "emcon_2") { + GetDefNumber(emcon_2, pdef, filename); + } + + else if (defname == "emcon_3") { + GetDefNumber(emcon_3, pdef, filename); + } + } + } + + PowerSource* source = new(__FILE__,__LINE__) PowerSource((PowerSource::SUBTYPE) stype, output); + if (pname.length()) source->SetName(pname); + if (pabrv.length()) source->SetName(pabrv); + source->SetFuelRange(fuel); + source->Mount(loc, size, hull); + source->SetExplosionType(etype); + + if (design_name.length()) { + SystemDesign* sd = SystemDesign::Find(design_name); + if (sd) + source->SetDesign(sd); + } + + if (emcon_1 >= 0 && emcon_1 <= 100) + source->SetEMCONPower(1, emcon_1); + + if (emcon_2 >= 0 && emcon_2 <= 100) + source->SetEMCONPower(1, emcon_2); + + if (emcon_3 >= 0 && emcon_3 <= 100) + source->SetEMCONPower(1, emcon_3); + + reactors.append(source); +} + +// +--------------------------------------------------------------------+ + +void +ShipDesign::ParseDrive(TermStruct* val) +{ + Text dname; + Text dabrv; + int dtype = 0; + int etype = 0; + float dthrust = 1.0f; + float daug = 0.0f; + float dscale = 1.0f; + Vec3 loc(0.0f, 0.0f, 0.0f); + float size = 0.0f; + float hull = 0.5f; + Text design_name; + int emcon_1 = -1; + int emcon_2 = -1; + int emcon_3 = -1; + bool trail = true; + Drive* drive = 0; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + Text defname = pdef->name()->value(); + defname.setSensitive(false); + + if (defname == "type") { + TermText* tname = pdef->term()->isText(); + + if (tname) { + Text tval = tname->value(); + tval.setSensitive(false); + + if (tval == "Plasma") dtype = Drive::PLASMA; + else if (tval == "Fusion") dtype = Drive::FUSION; + else if (tval == "Alien") dtype = Drive::GREEN; + else if (tval == "Green") dtype = Drive::GREEN; + else if (tval == "Red") dtype = Drive::RED; + else if (tval == "Blue") dtype = Drive::BLUE; + else if (tval == "Yellow") dtype = Drive::YELLOW; + else if (tval == "Stealth") dtype = Drive::STEALTH; + + else Print("WARNING: unknown drive type '%s' in '%s'\n", tname->value().data(), filename); + } + } + else if (defname == "name") { + if (!GetDefText(dname, pdef, filename)) + Print("WARNING: invalid or missing name for drive in '%s'\n", filename); + } + + else if (defname == "abrv") { + if (!GetDefText(dabrv, pdef, filename)) + Print("WARNING: invalid or missing abrv for drive in '%s'\n", filename); + } + + else if (defname == "design") { + if (!GetDefText(design_name, pdef, filename)) + Print("WARNING: invalid or missing design for drive in '%s'\n", filename); + } + + else if (defname == "thrust") { + if (!GetDefNumber(dthrust, pdef, filename)) + Print("WARNING: invalid or missing thrust for drive in '%s'\n", filename); + } + + else if (defname == "augmenter") { + if (!GetDefNumber(daug, pdef, filename)) + Print("WARNING: invalid or missing augmenter for drive in '%s'\n", filename); + } + + else if (defname == "scale") { + if (!GetDefNumber(dscale, pdef, filename)) + Print("WARNING: invalid or missing scale for drive in '%s'\n", filename); + } + + else if (defname == "port") { + Vec3 port; + float flare_scale = 0; + + if (pdef->term()->isArray()) { + GetDefVec(port, pdef, filename); + port *= scale; + flare_scale = dscale; + } + + else if (pdef->term()->isStruct()) { + TermStruct* val = pdef->term()->isStruct(); + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef2 = val->elements()->at(i)->isDef(); + if (pdef2) { + if (pdef2->name()->value() == "loc") { + GetDefVec(port, pdef2, filename); + port *= scale; + } + + else if (pdef2->name()->value() == "scale") { + GetDefNumber(flare_scale, pdef2, filename); + } + } + } + + if (flare_scale <= 0) + flare_scale = dscale; + } + + if (!drive) + drive = new(__FILE__,__LINE__) Drive((Drive::SUBTYPE) dtype, dthrust, daug, trail); + + drive->AddPort(port, flare_scale); + } + + else if (defname == "loc") { + if (!GetDefVec(loc, pdef, filename)) + Print("WARNING: invalid or missing loc for drive in '%s'\n", filename); + loc *= (float) scale; + } + + else if (defname == "size") { + if (!GetDefNumber(size, pdef, filename)) + Print("WARNING: invalid or missing size for drive in '%s'\n", filename); + size *= (float) scale; + } + + else if (defname == "hull_factor") { + if (!GetDefNumber(hull, pdef, filename)) + Print("WARNING: invalid or missing hull_factor for drive in '%s'\n", filename); + } + + else if (defname == "explosion") { + if (!GetDefNumber(etype, pdef, filename)) + Print("WARNING: invalid or missing explosion for drive in '%s'\n", filename); + } + + else if (defname == "emcon_1") { + GetDefNumber(emcon_1, pdef, filename); + } + + else if (defname == "emcon_2") { + GetDefNumber(emcon_2, pdef, filename); + } + + else if (defname == "emcon_3") { + GetDefNumber(emcon_3, pdef, filename); + } + + else if (defname == "trail" || defname == "show_trail") { + GetDefBool(trail, pdef, filename); + } + } + } + + if (!drive) + drive = new(__FILE__,__LINE__) Drive((Drive::SUBTYPE) dtype, dthrust, daug, trail); + + drive->SetSourceIndex(reactors.size()-1); + drive->Mount(loc, size, hull); + if (dname.length()) drive->SetName(dname); + if (dabrv.length()) drive->SetAbbreviation(dabrv); + drive->SetExplosionType(etype); + + if (design_name.length()) { + SystemDesign* sd = SystemDesign::Find(design_name); + if (sd) + drive->SetDesign(sd); + } + + if (emcon_1 >= 0 && emcon_1 <= 100) + drive->SetEMCONPower(1, emcon_1); + + if (emcon_2 >= 0 && emcon_2 <= 100) + drive->SetEMCONPower(1, emcon_2); + + if (emcon_3 >= 0 && emcon_3 <= 100) + drive->SetEMCONPower(1, emcon_3); + + main_drive = drives.size(); + drives.append(drive); +} + +// +--------------------------------------------------------------------+ + +void +ShipDesign::ParseQuantumDrive(TermStruct* val) +{ + double capacity = 250e3; + double consumption = 1e3; + Vec3 loc(0.0f, 0.0f, 0.0f); + float size = 0.0f; + float hull = 0.5f; + float countdown = 5.0f; + Text design_name; + Text type_name; + Text abrv; + int subtype = QuantumDrive::QUANTUM; + int emcon_1 = -1; + int emcon_2 = -1; + int emcon_3 = -1; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + Text defname = pdef->name()->value(); + defname.setSensitive(false); + + if (defname == "design") { + GetDefText(design_name, pdef, filename); + } + else if (defname == "abrv") { + GetDefText(abrv, pdef, filename); + } + else if (defname == "type") { + GetDefText(type_name, pdef, filename); + type_name.setSensitive(false); + + if (type_name.contains("hyper")) { + subtype = QuantumDrive::HYPER; + } + } + else if (defname == "capacity") { + GetDefNumber(capacity, pdef, filename); + } + else if (defname == "consumption") { + GetDefNumber(consumption, pdef, filename); + } + else if (defname == "loc") { + GetDefVec(loc, pdef, filename); + loc *= (float) scale; + } + else if (defname == "size") { + GetDefNumber(size, pdef, filename); + size *= (float) scale; + } + else if (defname == "hull_factor") { + GetDefNumber(hull, pdef, filename); + } + else if (defname == "jump_time") { + GetDefNumber(countdown, pdef, filename); + } + else if (defname == "countdown") { + GetDefNumber(countdown, pdef, filename); + } + + else if (defname == "emcon_1") { + GetDefNumber(emcon_1, pdef, filename); + } + + else if (defname == "emcon_2") { + GetDefNumber(emcon_2, pdef, filename); + } + + else if (defname == "emcon_3") { + GetDefNumber(emcon_3, pdef, filename); + } + } + } + + QuantumDrive* drive = new(__FILE__,__LINE__) QuantumDrive((QuantumDrive::SUBTYPE) subtype, capacity, consumption); + drive->SetSourceIndex(reactors.size()-1); + drive->Mount(loc, size, hull); + drive->SetCountdown(countdown); + + if (design_name.length()) { + SystemDesign* sd = SystemDesign::Find(design_name); + if (sd) + drive->SetDesign(sd); + } + + if (abrv.length()) + drive->SetAbbreviation(abrv); + + if (emcon_1 >= 0 && emcon_1 <= 100) + drive->SetEMCONPower(1, emcon_1); + + if (emcon_2 >= 0 && emcon_2 <= 100) + drive->SetEMCONPower(1, emcon_2); + + if (emcon_3 >= 0 && emcon_3 <= 100) + drive->SetEMCONPower(1, emcon_3); + + quantum_drive = drive; +} + +// +--------------------------------------------------------------------+ + +void +ShipDesign::ParseFarcaster(TermStruct* val) +{ + Text design_name; + double capacity = 300e3; + double consumption = 15e3; // twenty second recharge + int napproach = 0; + Vec3 approach[Farcaster::NUM_APPROACH_PTS]; + Vec3 loc(0.0f, 0.0f, 0.0f); + Vec3 start(0.0f, 0.0f, 0.0f); + Vec3 end(0.0f, 0.0f, 0.0f); + float size = 0.0f; + float hull = 0.5f; + int emcon_1 = -1; + int emcon_2 = -1; + int emcon_3 = -1; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + Text defname = pdef->name()->value(); + defname.setSensitive(false); + + if (defname == "design") { + GetDefText(design_name, pdef, filename); + } + else if (defname == "capacity") { + GetDefNumber(capacity, pdef, filename); + } + else if (defname == "consumption") { + GetDefNumber(consumption, pdef, filename); + } + else if (defname == "loc") { + GetDefVec(loc, pdef, filename); + loc *= (float) scale; + } + else if (defname == "size") { + GetDefNumber(size, pdef, filename); + size *= (float) scale; + } + else if (defname == "hull_factor") { + GetDefNumber(hull, pdef, filename); + } + + else if (defname == "start") { + GetDefVec(start, pdef, filename); + start *= (float) scale; + } + else if (defname == "end") { + GetDefVec(end, pdef, filename); + end *= (float) scale; + } + else if (defname == "approach") { + if (napproach < Farcaster::NUM_APPROACH_PTS) { + GetDefVec(approach[napproach], pdef, filename); + approach[napproach++] *= (float) scale; + } + else { + Print("WARNING: farcaster approach point ignored in '%s' (max=%d)\n", + filename, Farcaster::NUM_APPROACH_PTS); + } + } + + else if (defname == "emcon_1") { + GetDefNumber(emcon_1, pdef, filename); + } + + else if (defname == "emcon_2") { + GetDefNumber(emcon_2, pdef, filename); + } + + else if (defname == "emcon_3") { + GetDefNumber(emcon_3, pdef, filename); + } + } + } + + Farcaster* caster = new(__FILE__,__LINE__) Farcaster(capacity, consumption); + caster->SetSourceIndex(reactors.size()-1); + caster->Mount(loc, size, hull); + + if (design_name.length()) { + SystemDesign* sd = SystemDesign::Find(design_name); + if (sd) + caster->SetDesign(sd); + } + + caster->SetStartPoint(start); + caster->SetEndPoint(end); + + for (i = 0; i < napproach; i++) + caster->SetApproachPoint(i, approach[i]); + + if (emcon_1 >= 0 && emcon_1 <= 100) + caster->SetEMCONPower(1, emcon_1); + + if (emcon_2 >= 0 && emcon_2 <= 100) + caster->SetEMCONPower(1, emcon_2); + + if (emcon_3 >= 0 && emcon_3 <= 100) + caster->SetEMCONPower(1, emcon_3); + + farcaster = caster; +} + +// +--------------------------------------------------------------------+ + +void +ShipDesign::ParseThruster(TermStruct* val) +{ + if (thruster) { + Print("WARNING: additional thruster ignored in '%s'\n", filename); + return; + } + + double thrust = 100; + + Vec3 loc(0.0f, 0.0f, 0.0f); + float size = 0.0f; + float hull = 0.5f; + Text design_name; + float tscale = 1.0f; + int emcon_1 = -1; + int emcon_2 = -1; + int emcon_3 = -1; + int dtype = 0; + + Thruster* drive = 0; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + Text defname = pdef->name()->value(); + defname.setSensitive(false); + + + if (defname == "type") { + TermText* tname = pdef->term()->isText(); + + if (tname) { + Text tval = tname->value(); + tval.setSensitive(false); + + if (tval == "Plasma") dtype = Drive::PLASMA; + else if (tval == "Fusion") dtype = Drive::FUSION; + else if (tval == "Alien") dtype = Drive::GREEN; + else if (tval == "Green") dtype = Drive::GREEN; + else if (tval == "Red") dtype = Drive::RED; + else if (tval == "Blue") dtype = Drive::BLUE; + else if (tval == "Yellow") dtype = Drive::YELLOW; + else if (tval == "Stealth") dtype = Drive::STEALTH; + + else Print("WARNING: unknown thruster type '%s' in '%s'\n", tname->value().data(), filename); + } + } + + else if (defname == "thrust") { + GetDefNumber(thrust, pdef, filename); + } + + else if (defname == "design") { + GetDefText(design_name, pdef, filename); + } + + else if (defname == "loc") { + GetDefVec(loc, pdef, filename); + loc *= (float) scale; + } + else if (defname == "size") { + GetDefNumber(size, pdef, filename); + size *= (float) scale; + } + else if (defname == "hull_factor") { + GetDefNumber(hull, pdef, filename); + } + else if (defname == "scale") { + GetDefNumber(tscale, pdef, filename); + } + else if (defname.contains("port") && pdef->term()) { + Vec3 port; + float port_scale = 0; + DWORD fire = 0; + + if (pdef->term()->isArray()) { + GetDefVec(port, pdef, filename); + port *= scale; + port_scale = tscale; + } + + else if (pdef->term()->isStruct()) { + TermStruct* val = pdef->term()->isStruct(); + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef2 = val->elements()->at(i)->isDef(); + if (pdef2) { + if (pdef2->name()->value() == "loc") { + GetDefVec(port, pdef2, filename); + port *= scale; + } + + else if (pdef2->name()->value() == "fire") { + GetDefNumber(fire, pdef2, filename); + } + + else if (pdef2->name()->value() == "scale") { + GetDefNumber(port_scale, pdef2, filename); + } + } + } + + if (port_scale <= 0) + port_scale = tscale; + } + + if (!drive) + drive = new(__FILE__,__LINE__) Thruster(dtype, thrust, tscale); + + if (defname == "port" || defname == "port_bottom") + drive->AddPort(Thruster::BOTTOM, port, fire, port_scale); + + else if (defname == "port_top") + drive->AddPort(Thruster::TOP, port, fire, port_scale); + + else if (defname == "port_left") + drive->AddPort(Thruster::LEFT, port, fire, port_scale); + + else if (defname == "port_right") + drive->AddPort(Thruster::RIGHT, port, fire, port_scale); + + else if (defname == "port_fore") + drive->AddPort(Thruster::FORE, port, fire, port_scale); + + else if (defname == "port_aft") + drive->AddPort(Thruster::AFT, port, fire, port_scale); + } + + else if (defname == "emcon_1") { + GetDefNumber(emcon_1, pdef, filename); + } + + else if (defname == "emcon_2") { + GetDefNumber(emcon_2, pdef, filename); + } + + else if (defname == "emcon_3") { + GetDefNumber(emcon_3, pdef, filename); + } + } + } + + if (!drive) + drive = new(__FILE__,__LINE__) Thruster(dtype, thrust, tscale); + drive->SetSourceIndex(reactors.size()-1); + drive->Mount(loc, size, hull); + + if (design_name.length()) { + SystemDesign* sd = SystemDesign::Find(design_name); + if (sd) + drive->SetDesign(sd); + } + + if (emcon_1 >= 0 && emcon_1 <= 100) + drive->SetEMCONPower(1, emcon_1); + + if (emcon_2 >= 0 && emcon_2 <= 100) + drive->SetEMCONPower(1, emcon_2); + + if (emcon_3 >= 0 && emcon_3 <= 100) + drive->SetEMCONPower(1, emcon_3); + + thruster = drive; +} + +// +--------------------------------------------------------------------+ + +void +ShipDesign::ParseNavlight(TermStruct* val) +{ + Text dname; + Text dabrv; + Text design_name; + int nlights = 0; + float dscale = 1.0f; + float period = 10.0f; + Vec3 bloc[NavLight::MAX_LIGHTS]; + int btype[NavLight::MAX_LIGHTS]; + DWORD pattern[NavLight::MAX_LIGHTS]; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + Text defname = pdef->name()->value(); + defname.setSensitive(false); + + if (defname == "name") + GetDefText(dname, pdef, filename); + else if (defname == "abrv") + GetDefText(dabrv, pdef, filename); + + else if (defname == "design") { + GetDefText(design_name, pdef, filename); + } + + else if (defname == "scale") { + GetDefNumber(dscale, pdef, filename); + } + else if (defname == "period") { + GetDefNumber(period, pdef, filename); + } + else if (defname == "light") { + if (!pdef->term() || !pdef->term()->isStruct()) { + Print("WARNING: light struct missing for ship '%s' in '%s'\n", name, filename); + } + else { + TermStruct* val = pdef->term()->isStruct(); + + Vec3 loc; + int t = 0; + DWORD ptn = 0; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + Text defname = pdef->name()->value(); + defname.setSensitive(false); + + if (defname == "type") { + GetDefNumber(t, pdef, filename); + } + else if (defname == "loc") { + GetDefVec(loc, pdef, filename); + } + else if (defname == "pattern") { + GetDefNumber(ptn, pdef, filename); + } + } + } + + if (t < 1 || t > 4) + t = 1; + + if (nlights < NavLight::MAX_LIGHTS) { + bloc[nlights] = loc * scale; + btype[nlights] = t-1; + pattern[nlights] = ptn; + nlights++; + } + else { + Print("WARNING: Too many lights ship '%s' in '%s'\n", name, filename); + } + } + } + } + } + + NavLight* nav = new(__FILE__,__LINE__) NavLight(period, dscale); + if (dname.length()) nav->SetName(dname); + if (dabrv.length()) nav->SetAbbreviation(dabrv); + + if (design_name.length()) { + SystemDesign* sd = SystemDesign::Find(design_name); + if (sd) + nav->SetDesign(sd); + } + + for (i = 0; i < nlights; i++) + nav->AddBeacon(bloc[i], pattern[i], btype[i]); + + navlights.append(nav); +} + +// +--------------------------------------------------------------------+ + +void +ShipDesign::ParseFlightDeck(TermStruct* val) +{ + Text dname; + Text dabrv; + Text design_name; + float dscale = 1.0f; + float az = 0.0f; + int etype = 0; + + bool launch = false; + bool recovery = false; + int nslots = 0; + int napproach = 0; + int nrunway = 0; + DWORD filters[10]; + Vec3 spots[10]; + Vec3 approach[FlightDeck::NUM_APPROACH_PTS]; + Vec3 runway[2]; + Vec3 loc(0,0,0); + Vec3 start(0,0,0); + Vec3 end(0,0,0); + Vec3 cam(0,0,0); + Vec3 box(0,0,0); + float cycle_time = 0.0f; + float size = 0.0f; + float hull = 0.5f; + + float light = 0.0f; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + Text defname = pdef->name()->value(); + defname.setSensitive(false); + + if (defname == "name") + GetDefText(dname, pdef, filename); + else if (defname == "abrv") + GetDefText(dabrv, pdef, filename); + else if (defname == "design") + GetDefText(design_name, pdef, filename); + + else if (defname == "start") { + GetDefVec(start, pdef, filename); + start *= (float) scale; + } + else if (defname == "end") { + GetDefVec(end, pdef, filename); + end *= (float) scale; + } + else if (defname == "cam") { + GetDefVec(cam, pdef, filename); + cam *= (float) scale; + } + else if (defname == "box" || defname == "bounding_box") { + GetDefVec(box, pdef, filename); + box *= (float) scale; + } + else if (defname == "approach") { + if (napproach < FlightDeck::NUM_APPROACH_PTS) { + GetDefVec(approach[napproach], pdef, filename); + approach[napproach++] *= (float) scale; + } + else { + Print("WARNING: flight deck approach point ignored in '%s' (max=%d)\n", + filename, FlightDeck::NUM_APPROACH_PTS); + } + } + else if (defname == "runway") { + GetDefVec(runway[nrunway], pdef, filename); + runway[nrunway++] *= (float) scale; + } + else if (defname == "spot") { + if (pdef->term()->isStruct()) { + TermStruct* s = pdef->term()->isStruct(); + for (int i = 0; i < s->elements()->size(); i++) { + TermDef* d = s->elements()->at(i)->isDef(); + if (d) { + if (d->name()->value() == "loc") { + GetDefVec(spots[nslots], d, filename); + spots[nslots] *= (float) scale; + } + else if (d->name()->value() == "filter") { + GetDefNumber(filters[nslots], d, filename); + } + } + } + + nslots++; + } + + else if (pdef->term()->isArray()) { + GetDefVec(spots[nslots], pdef, filename); + spots[nslots] *= (float) scale; + filters[nslots++] = 0xf; + } + } + + else if (defname == "light") { + GetDefNumber(light, pdef, filename); + } + + else if (defname == "cycle_time") { + GetDefNumber(cycle_time, pdef, filename); + } + + else if (defname == "launch") { + GetDefBool(launch, pdef, filename); + } + + else if (defname == "recovery") { + GetDefBool(recovery, pdef, filename); + } + + else if (defname == "azimuth") { + GetDefNumber(az, pdef, filename); + if (degrees) az *= (float) DEGREES; + } + + else if (defname == "loc") { + GetDefVec(loc, pdef, filename); + loc *= (float) scale; + } + else if (defname == "size") { + GetDefNumber(size, pdef, filename); + size *= (float) scale; + } + else if (defname == "hull_factor") { + GetDefNumber(hull, pdef, filename); + } + else if (defname == "explosion") { + GetDefNumber(etype, pdef, filename); + } + } + } + + FlightDeck* deck = new(__FILE__,__LINE__) FlightDeck(); + deck->Mount(loc, size, hull); + if (dname.length()) deck->SetName(dname); + if (dabrv.length()) deck->SetAbbreviation(dabrv); + + if (design_name.length()) { + SystemDesign* sd = SystemDesign::Find(design_name); + if (sd) + deck->SetDesign(sd); + } + + if (launch) + deck->SetLaunchDeck(); + else if (recovery) + deck->SetRecoveryDeck(); + + deck->SetAzimuth(az); + deck->SetBoundingBox(box); + deck->SetStartPoint(start); + deck->SetEndPoint(end); + deck->SetCamLoc(cam); + deck->SetExplosionType(etype); + + if (light > 0) + deck->SetLight(light); + + for (i = 0; i < napproach; i++) + deck->SetApproachPoint(i, approach[i]); + + for (i = 0; i < nrunway; i++) + deck->SetRunwayPoint(i, runway[i]); + + for (i = 0; i < nslots; i++) + deck->AddSlot(spots[i], filters[i]); + + if (cycle_time > 0) + deck->SetCycleTime(cycle_time); + + flight_decks.append(deck); +} + +// +--------------------------------------------------------------------+ + +void +ShipDesign::ParseLandingGear(TermStruct* val) +{ + Text dname; + Text dabrv; + Text design_name; + int ngear = 0; + Vec3 start[LandingGear::MAX_GEAR]; + Vec3 end[LandingGear::MAX_GEAR]; + Model* model[LandingGear::MAX_GEAR]; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + Text defname = pdef->name()->value(); + defname.setSensitive(false); + + if (defname == "name") + GetDefText(dname, pdef, filename); + else if (defname == "abrv") + GetDefText(dabrv, pdef, filename); + + else if (defname == "design") { + GetDefText(design_name, pdef, filename); + } + + else if (defname == "gear") { + if (!pdef->term() || !pdef->term()->isStruct()) { + Print("WARNING: gear struct missing for ship '%s' in '%s'\n", name, filename); + } + else { + TermStruct* val = pdef->term()->isStruct(); + + Vec3 v1, v2; + char mod_name[256]; + + ZeroMemory(mod_name, sizeof(mod_name)); + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + defname = pdef->name()->value(); + defname.setSensitive(false); + + if (defname == "model") { + GetDefText(mod_name, pdef, filename); + } + else if (defname == "start") { + GetDefVec(v1, pdef, filename); + } + else if (defname == "end") { + GetDefVec(v2, pdef, filename); + } + } + } + + if (ngear < LandingGear::MAX_GEAR) { + Model* m = new(__FILE__,__LINE__) Model; + if (!m->Load(mod_name, scale)) { + Print("WARNING: Could not load landing gear model '%s'\n", mod_name); + delete m; + m = 0; + } + else { + model[ngear] = m; + start[ngear] = v1 * scale; + end[ngear] = v2 * scale; + ngear++; + } + } + else { + Print("WARNING: Too many landing gear ship '%s' in '%s'\n", name, filename); + } + } + } + } + } + + gear = new(__FILE__,__LINE__) LandingGear(); + if (dname.length()) gear->SetName(dname); + if (dabrv.length()) gear->SetAbbreviation(dabrv); + + if (design_name.length()) { + SystemDesign* sd = SystemDesign::Find(design_name); + if (sd) + gear->SetDesign(sd); + } + + for (i = 0; i < ngear; i++) + gear->AddGear(model[i], start[i], end[i]); +} + +// +--------------------------------------------------------------------+ + +void +ShipDesign::ParseWeapon(TermStruct* val) +{ + Text wtype; + Text wname; + Text wabrv; + Text design_name; + Text group_name; + int nmuz = 0; + Vec3 muzzles[Weapon::MAX_BARRELS]; + Vec3 loc(0.0f, 0.0f, 0.0f); + float size = 0.0f; + float hull = 0.5f; + float az = 0.0f; + float el = 0.0f; + float az_max = 1e6f; + float az_min = 1e6f; + float el_max = 1e6f; + float el_min = 1e6f; + float az_rest = 1e6f; + float el_rest = 1e6f; + int etype = 0; + int emcon_1 = -1; + int emcon_2 = -1; + int emcon_3 = -1; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + Text defname = pdef->name()->value(); + defname.setSensitive(false); + + if (defname == "type") + GetDefText(wtype, pdef, filename); + else if (defname == "name") + GetDefText(wname, pdef, filename); + else if (defname == "abrv") + GetDefText(wabrv, pdef, filename); + else if (defname == "design") + GetDefText(design_name, pdef, filename); + else if (defname == "group") + GetDefText(group_name, pdef, filename); + + else if (defname == "muzzle") { + if (nmuz < Weapon::MAX_BARRELS) { + GetDefVec(muzzles[nmuz], pdef, filename); + nmuz++; + } + else { + Print("WARNING: too many muzzles (max=%d) for weapon in '%s'\n", filename, Weapon::MAX_BARRELS); + } + } + else if (defname == "loc") { + GetDefVec(loc, pdef, filename); + loc *= (float) scale; + } + else if (defname == "size") { + GetDefNumber(size, pdef, filename); + size *= (float) scale; + } + else if (defname == "hull_factor") { + GetDefNumber(hull, pdef, filename); + } + else if (defname == "azimuth") { + GetDefNumber(az, pdef, filename); + if (degrees) az *= (float) DEGREES; + } + else if (defname == "elevation") { + GetDefNumber(el, pdef, filename); + if (degrees) el *= (float) DEGREES; + } + + else if (defname==("aim_az_max")) { + GetDefNumber(az_max,pdef,filename); + if (degrees) az_max *= (float) DEGREES; + az_min = 0.0f - az_max; + } + + else if (defname==("aim_el_max")) { + GetDefNumber(el_max,pdef,filename); + if (degrees) el_max *= (float) DEGREES; + el_min = 0.0f - el_max; + } + + else if (defname==("aim_az_min")) { + GetDefNumber(az_min,pdef,filename); + if (degrees) az_min *= (float) DEGREES; + } + + else if (defname==("aim_el_min")) { + GetDefNumber(el_min,pdef,filename); + if (degrees) el_min *= (float) DEGREES; + } + + else if (defname==("aim_az_rest")) { + GetDefNumber(az_rest,pdef,filename); + if (degrees) az_rest *= (float) DEGREES; + } + + else if (defname==("aim_el_rest")) { + GetDefNumber(el_rest,pdef,filename); + if (degrees) el_rest *= (float) DEGREES; + } + + else if (defname == "rest_azimuth") { + GetDefNumber(az_rest, pdef, filename); + if (degrees) az_rest *= (float) DEGREES; + } + else if (defname == "rest_elevation") { + GetDefNumber(el_rest, pdef, filename); + if (degrees) el_rest *= (float) DEGREES; + } + else if (defname == "explosion") { + GetDefNumber(etype, pdef, filename); + } + + else if (defname == "emcon_1") { + GetDefNumber(emcon_1, pdef, filename); + } + + else if (defname == "emcon_2") { + GetDefNumber(emcon_2, pdef, filename); + } + + else if (defname == "emcon_3") { + GetDefNumber(emcon_3, pdef, filename); + } + else { + Print("WARNING: unknown weapon parameter '%s' in '%s'\n", + defname.data(), filename); + } + } + } + + WeaponDesign* meta = WeaponDesign::Find(wtype); + if (!meta) { + Print("WARNING: unusual weapon name '%s' in '%s'\n", (const char*) wtype, filename); + } + else { + // non-turret weapon muzzles are relative to ship scale: + if (meta->turret_model == 0) { + for (int i = 0; i < nmuz; i++) + muzzles[i] *= (float) scale; + } + + // turret weapon muzzles are relative to weapon scale: + else { + for (int i = 0; i < nmuz; i++) + muzzles[i] *= (float) meta->scale; + } + + Weapon* gun = new(__FILE__,__LINE__) Weapon(meta, nmuz, muzzles, az, el); + gun->SetSourceIndex(reactors.size()-1); + gun->Mount(loc, size, hull); + + if (az_max < 1e6) gun->SetAzimuthMax(az_max); + if (az_min < 1e6) gun->SetAzimuthMin(az_min); + if (az_rest < 1e6) gun->SetRestAzimuth(az_rest); + + if (el_max < 1e6) gun->SetElevationMax(el_max); + if (el_min < 1e6) gun->SetElevationMin(el_min); + if (el_rest < 1e6) gun->SetRestElevation(el_rest); + + if (emcon_1 >= 0 && emcon_1 <= 100) + gun->SetEMCONPower(1, emcon_1); + + if (emcon_2 >= 0 && emcon_2 <= 100) + gun->SetEMCONPower(1, emcon_2); + + if (emcon_3 >= 0 && emcon_3 <= 100) + gun->SetEMCONPower(1, emcon_3); + + if (wname.length()) gun->SetName(wname); + if (wabrv.length()) gun->SetAbbreviation(wabrv); + + if (design_name.length()) { + SystemDesign* sd = SystemDesign::Find(design_name); + if (sd) + gun->SetDesign(sd); + } + + if (group_name.length()) { + gun->SetGroup(group_name); + } + + gun->SetExplosionType(etype); + + if (meta->decoy_type && !decoy) + decoy = gun; + else if (meta->probe && !probe) + probe = gun; + else + weapons.append(gun); + } + + DataLoader* loader = DataLoader::GetLoader(); + loader->SetDataPath(path_name); +} + +// +--------------------------------------------------------------------+ + +void +ShipDesign::ParseHardPoint(TermStruct* val) +{ + Text wtypes[8]; + Text wname; + Text wabrv; + Text design; + Vec3 muzzle; + Vec3 loc(0.0f, 0.0f, 0.0f); + float size = 0.0f; + float hull = 0.5f; + float az = 0.0f; + float el = 0.0f; + int ntypes = 0; + int emcon_1 = -1; + int emcon_2 = -1; + int emcon_3 = -1; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + Text defname = pdef->name()->value(); + defname.setSensitive(false); + + if (defname == "type") + GetDefText(wtypes[ntypes++], pdef, filename); + else if (defname == "name") + GetDefText(wname, pdef, filename); + else if (defname == "abrv") + GetDefText(wabrv, pdef, filename); + else if (defname == "design") + GetDefText(design, pdef, filename); + + else if (defname == "muzzle") { + GetDefVec(muzzle, pdef, filename); + muzzle *= (float) scale; + } + else if (defname == "loc") { + GetDefVec(loc, pdef, filename); + loc *= (float) scale; + } + else if (defname == "size") { + GetDefNumber(size, pdef, filename); + size *= (float) scale; + } + else if (defname == "hull_factor") { + GetDefNumber(hull, pdef, filename); + } + else if (defname == "azimuth") { + GetDefNumber(az, pdef, filename); + if (degrees) az *= (float) DEGREES; + } + else if (defname == "elevation") { + GetDefNumber(el, pdef, filename); + if (degrees) el *= (float) DEGREES; + } + + else if (defname == "emcon_1") { + GetDefNumber(emcon_1, pdef, filename); + } + + else if (defname == "emcon_2") { + GetDefNumber(emcon_2, pdef, filename); + } + + else if (defname == "emcon_3") { + GetDefNumber(emcon_3, pdef, filename); + } + else { + Print("WARNING: unknown weapon parameter '%s' in '%s'\n", + defname.data(), filename); + } + } + } + + HardPoint* hp = new(__FILE__,__LINE__) HardPoint(muzzle, az, el); + if (hp) { + for (int i = 0; i < ntypes; i++) { + WeaponDesign* meta = WeaponDesign::Find(wtypes[i]); + if (!meta) { + Print("WARNING: unusual weapon name '%s' in '%s'\n", (const char*) wtypes[i], filename); + } + else { + hp->AddDesign(meta); + } + } + + hp->Mount(loc, size, hull); + if (wname.length()) hp->SetName(wname); + if (wabrv.length()) hp->SetAbbreviation(wabrv); + if (design.length()) hp->SetDesign(design); + + hard_points.append(hp); + } + + DataLoader* loader = DataLoader::GetLoader(); + loader->SetDataPath(path_name); +} + +// +--------------------------------------------------------------------+ + +void +ShipDesign::ParseLoadout(TermStruct* val) +{ + ShipLoad* load = new(__FILE__,__LINE__) ShipLoad; + + if (!load) return; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + Text defname = pdef->name()->value(); + defname.setSensitive(false); + + if (defname == "name") + GetDefText(load->name, pdef, filename); + + else if (defname == "stations") + GetDefArray(load->load, 16, pdef, filename); + + else + Print("WARNING: unknown loadout parameter '%s' in '%s'\n", + defname.data(), filename); + } + } + + loadouts.append(load); +} + +// +--------------------------------------------------------------------+ + +void +ShipDesign::ParseSensor(TermStruct* val) +{ + Text design_name; + Vec3 loc(0.0f, 0.0f, 0.0f); + float size = 0.0f; + float hull = 0.5f; + int nranges = 0; + float ranges[8]; + int emcon_1 = -1; + int emcon_2 = -1; + int emcon_3 = -1; + + ZeroMemory(ranges, sizeof(ranges)); + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + Text defname = pdef->name()->value(); + defname.setSensitive(false); + + if (defname == "range") { + GetDefNumber(ranges[nranges++], pdef, filename); + } + else if (defname == "loc") { + GetDefVec(loc, pdef, filename); + loc *= (float) scale; + } + else if (defname == "size") { + size *= (float) scale; + GetDefNumber(size, pdef, filename); + } + else if (defname == "hull_factor") { + GetDefNumber(hull, pdef, filename); + } + else if (defname == "design") { + GetDefText(design_name, pdef, filename); + } + else if (defname == "emcon_1") { + GetDefNumber(emcon_1, pdef, filename); + } + + else if (defname == "emcon_2") { + GetDefNumber(emcon_2, pdef, filename); + } + + else if (defname == "emcon_3") { + GetDefNumber(emcon_3, pdef, filename); + } + } + } + + if (!sensor) { + sensor = new(__FILE__,__LINE__) Sensor(); + + if (design_name.length()) { + SystemDesign* sd = SystemDesign::Find(design_name); + if (sd) + sensor->SetDesign(sd); + } + + for (int i = 0; i < nranges; i++) + sensor->AddRange(ranges[i]); + + if (emcon_1 >= 0 && emcon_1 <= 100) + sensor->SetEMCONPower(1, emcon_1); + + if (emcon_2 >= 0 && emcon_2 <= 100) + sensor->SetEMCONPower(1, emcon_2); + + if (emcon_3 >= 0 && emcon_3 <= 100) + sensor->SetEMCONPower(1, emcon_3); + + sensor->Mount(loc, size, hull); + sensor->SetSourceIndex(reactors.size()-1); + } + else { + Print("WARNING: additional sensor ignored in '%s'\n", filename); + } +} + +// +--------------------------------------------------------------------+ + +void +ShipDesign::ParseNavsys(TermStruct* val) +{ + Text design_name; + Vec3 loc(0.0f, 0.0f, 0.0f); + float size = 0.0f; + float hull = 0.5f; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + Text defname = pdef->name()->value(); + defname.setSensitive(false); + + if (defname == "loc") { + GetDefVec(loc, pdef, filename); + loc *= (float) scale; + } + else if (defname == "size") { + size *= (float) scale; + GetDefNumber(size, pdef, filename); + } + else if (defname == "hull_factor") { + GetDefNumber(hull, pdef, filename); + } + else if (defname == "design") + GetDefText(design_name, pdef, filename); + } + } + + if (!navsys) { + navsys = new(__FILE__,__LINE__) NavSystem; + + if (design_name.length()) { + SystemDesign* sd = SystemDesign::Find(design_name); + if (sd) + navsys->SetDesign(sd); + } + + navsys->Mount(loc, size, hull); + navsys->SetSourceIndex(reactors.size()-1); + } + else { + Print("WARNING: additional nav system ignored in '%s'\n", filename); + } +} + +// +--------------------------------------------------------------------+ + +void +ShipDesign::ParseComputer(TermStruct* val) +{ + Text comp_name("Computer"); + Text comp_abrv("Comp"); + Text design_name; + int comp_type = 1; + Vec3 loc(0.0f, 0.0f, 0.0f); + float size = 0.0f; + float hull = 0.5f; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + Text defname = pdef->name()->value(); + defname.setSensitive(false); + + if (defname == "name") { + GetDefText(comp_name, pdef, filename); + } + else if (defname == "abrv") { + GetDefText(comp_abrv, pdef, filename); + } + else if (defname == "design") { + GetDefText(design_name, pdef, filename); + } + else if (defname == "type") { + GetDefNumber(comp_type, pdef, filename); + } + else if (defname == "loc") { + GetDefVec(loc, pdef, filename); + loc *= (float) scale; + } + else if (defname == "size") { + size *= (float) scale; + GetDefNumber(size, pdef, filename); + } + else if (defname == "hull_factor") { + GetDefNumber(hull, pdef, filename); + } + } + } + + Computer* comp = new(__FILE__,__LINE__) Computer(comp_type, comp_name); + comp->Mount(loc, size, hull); + comp->SetAbbreviation(comp_abrv); + comp->SetSourceIndex(reactors.size()-1); + + if (design_name.length()) { + SystemDesign* sd = SystemDesign::Find(design_name); + if (sd) + comp->SetDesign(sd); + } + + computers.append(comp); +} + +// +--------------------------------------------------------------------+ + +void +ShipDesign::ParseShield(TermStruct* val) +{ + Text dname; + Text dabrv; + Text design_name; + Text model_name; + double factor = 0; + double capacity = 0; + double consumption = 0; + double cutoff = 0; + double curve = 0; + double def_cost = 1; + int shield_type = 0; + Vec3 loc(0.0f, 0.0f, 0.0f); + float size = 0.0f; + float hull = 0.5f; + int etype = 0; + bool shield_capacitor = false; + bool shield_bubble = false; + int emcon_1 = -1; + int emcon_2 = -1; + int emcon_3 = -1; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + Text defname = pdef->name()->value(); + defname.setSensitive(false); + + if (defname == "type") { + GetDefNumber(shield_type, pdef, filename); + } + else if (defname == "name") + GetDefText(dname, pdef, filename); + else if (defname == "abrv") + GetDefText(dabrv, pdef, filename); + else if (defname == "design") + GetDefText(design_name, pdef, filename); + else if (defname == "model") + GetDefText(model_name, pdef, filename); + + else if (defname == "loc") { + GetDefVec(loc, pdef, filename); + loc *= (float) scale; + } + else if (defname == "size") { + GetDefNumber(size, pdef, filename); + size *= (float) scale; + } + else if (defname == "hull_factor") + GetDefNumber(hull, pdef, filename); + + else if (defname.contains("factor")) + GetDefNumber(factor, pdef, filename); + else if (defname.contains("cutoff")) + GetDefNumber(cutoff, pdef, filename); + else if (defname.contains("curve")) + GetDefNumber(curve, pdef, filename); + else if (defname.contains("capacitor")) + GetDefBool(shield_capacitor, pdef, filename); + else if (defname.contains("bubble")) + GetDefBool(shield_bubble, pdef, filename); + else if (defname == "capacity") + GetDefNumber(capacity, pdef, filename); + else if (defname == "consumption") + GetDefNumber(consumption, pdef, filename); + else if (defname == "deflection_cost") + GetDefNumber(def_cost, pdef, filename); + else if (defname == "explosion") + GetDefNumber(etype, pdef, filename); + + else if (defname == "emcon_1") { + GetDefNumber(emcon_1, pdef, filename); + } + + else if (defname == "emcon_2") { + GetDefNumber(emcon_2, pdef, filename); + } + + else if (defname == "emcon_3") { + GetDefNumber(emcon_3, pdef, filename); + } + + else if (defname == "bolt_hit_sound") { + GetDefText(bolt_hit_sound, pdef, filename); + } + + else if (defname == "beam_hit_sound") { + GetDefText(beam_hit_sound, pdef, filename); + } + } + } + + if (!shield) { + if (shield_type) { + shield = new(__FILE__,__LINE__) Shield((Shield::SUBTYPE) shield_type); + shield->SetSourceIndex(reactors.size()-1); + shield->Mount(loc, size, hull); + if (dname.length()) shield->SetName(dname); + if (dabrv.length()) shield->SetAbbreviation(dabrv); + + if (design_name.length()) { + SystemDesign* sd = SystemDesign::Find(design_name); + if (sd) + shield->SetDesign(sd); + } + + shield->SetExplosionType(etype); + shield->SetShieldCapacitor(shield_capacitor); + shield->SetShieldBubble(shield_bubble); + + if (factor > 0) shield->SetShieldFactor(factor); + if (capacity > 0) shield->SetCapacity(capacity); + if (cutoff > 0) shield->SetShieldCutoff(cutoff); + if (consumption > 0) shield->SetConsumption(consumption); + if (def_cost > 0) shield->SetDeflectionCost(def_cost); + if (curve > 0) shield->SetShieldCurve(curve); + + if (emcon_1 >= 0 && emcon_1 <= 100) + shield->SetEMCONPower(1, emcon_1); + + if (emcon_2 >= 0 && emcon_2 <= 100) + shield->SetEMCONPower(1, emcon_2); + + if (emcon_3 >= 0 && emcon_3 <= 100) + shield->SetEMCONPower(1, emcon_3); + + if (model_name.length()) { + shield_model = new(__FILE__,__LINE__) Model; + if (!shield_model->Load(model_name, scale)) { + Print("ERROR: Could not load shield model '%s'\n", model_name); + delete shield_model; + shield_model = 0; + valid = false; + } + else { + shield_model->SetDynamic(true); + shield_model->SetLuminous(true); + } + } + + DataLoader* loader = DataLoader::GetLoader(); + DWORD SOUND_FLAGS = Sound::LOCALIZED | Sound::LOC_3D; + + if (bolt_hit_sound.length()) { + if (!loader->LoadSound(bolt_hit_sound, bolt_hit_sound_resource, SOUND_FLAGS, true)) { + loader->SetDataPath("Sounds/"); + loader->LoadSound(bolt_hit_sound, bolt_hit_sound_resource, SOUND_FLAGS); + loader->SetDataPath(path_name); + } + } + + if (beam_hit_sound.length()) { + if (!loader->LoadSound(beam_hit_sound, beam_hit_sound_resource, SOUND_FLAGS, true)) { + loader->SetDataPath("Sounds/"); + loader->LoadSound(beam_hit_sound, beam_hit_sound_resource, SOUND_FLAGS); + loader->SetDataPath(path_name); + } + } + } + else { + Print("WARNING: invalid shield type in '%s'\n", filename); + } + } + else { + Print("WARNING: additional shield ignored in '%s'\n", filename); + } +} + +// +--------------------------------------------------------------------+ + +void +ShipDesign::ParseDeathSpiral(TermStruct* val) +{ + int exp_index = -1; + int debris_index = -1; + int fire_index = -1; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* def = val->elements()->at(i)->isDef(); + if (def) { + Text defname = def->name()->value(); + defname.setSensitive(false); + + if (defname == "time") { + GetDefNumber(death_spiral_time, def, filename); + } + + else if (defname == "explosion") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: explosion struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + ParseExplosion(val, ++exp_index); + } + } + + // BACKWARD COMPATIBILITY: + else if (defname == "explosion_type") { + GetDefNumber(explosion[++exp_index].type, def, filename); + } + + else if (defname == "explosion_time") { + GetDefNumber(explosion[exp_index].time, def, filename); + } + + else if (defname == "explosion_loc") { + GetDefVec(explosion[exp_index].loc, def, filename); + explosion[exp_index].loc *= (float) scale; + } + + else if (defname == "final_type") { + GetDefNumber(explosion[++exp_index].type, def, filename); + explosion[exp_index].final = true; + } + + else if (defname == "final_loc") { + GetDefVec(explosion[exp_index].loc, def, filename); + explosion[exp_index].loc *= (float) scale; + } + + + else if (defname == "debris") { + if (def->term() && def->term()->isText()) { + Text model_name; + GetDefText(model_name, def, filename); + Model* model = new(__FILE__,__LINE__) Model; + if (!model->Load(model_name, scale)) { + Print("Could not load debris model '%s'\n", model_name); + delete model; + return; + } + + PrepareModel(*model); + debris[++debris_index].model = model; + fire_index = -1; + } + else if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: debris struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + ParseDebris(val, ++debris_index); + } + } + + else if (defname == "debris_mass") { + GetDefNumber(debris[debris_index].mass, def, filename); + } + + else if (defname == "debris_speed") { + GetDefNumber(debris[debris_index].speed, def, filename); + } + + else if (defname == "debris_drag") { + GetDefNumber(debris[debris_index].drag, def, filename); + } + + else if (defname == "debris_loc") { + GetDefVec(debris[debris_index].loc, def, filename); + debris[debris_index].loc *= (float) scale; + } + + else if (defname == "debris_count") { + GetDefNumber(debris[debris_index].count, def, filename); + } + + else if (defname == "debris_life") { + GetDefNumber(debris[debris_index].life, def, filename); + } + + else if (defname == "debris_fire") { + if (++fire_index < 5) { + GetDefVec(debris[debris_index].fire_loc[fire_index], def, filename); + debris[debris_index].fire_loc[fire_index] *= (float) scale; + } + } + + else if (defname == "debris_fire_type") { + GetDefNumber(debris[debris_index].fire_type, def, filename); + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +ShipDesign::ParseExplosion(TermStruct* val, int index) +{ + ShipExplosion* exp = &explosion[index]; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* def = val->elements()->at(i)->isDef(); + if (def) { + Text defname = def->name()->value(); + defname.setSensitive(false); + + if (defname == "time") { + GetDefNumber(exp->time, def, filename); + } + + else if (defname == "type") { + GetDefNumber(exp->type, def, filename); + } + + else if (defname == "loc") { + GetDefVec(exp->loc, def, filename); + exp->loc *= (float) scale; + } + + else if (defname == "final") { + GetDefBool(exp->final, def, filename); + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +ShipDesign::ParseDebris(TermStruct* val, int index) +{ + char model_name[NAMELEN]; + int fire_index = 0; + ShipDebris* deb = &debris[index]; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* def = val->elements()->at(i)->isDef(); + if (def) { + Text defname = def->name()->value(); + + if (defname == "model") { + GetDefText(model_name, def, filename); + Model* model = new(__FILE__,__LINE__) Model; + if (!model->Load(model_name, scale)) { + Print("Could not load debris model '%s'\n", model_name); + delete model; + return; + } + + PrepareModel(*model); + deb->model = model; + } + + else if (defname == "mass") { + GetDefNumber(deb->mass, def, filename); + } + + else if (defname == "speed") { + GetDefNumber(deb->speed, def, filename); + } + + else if (defname == "drag") { + GetDefNumber(deb->drag, def, filename); + } + + else if (defname == "loc") { + GetDefVec(deb->loc, def, filename); + deb->loc *= (float) scale; + } + + else if (defname == "count") { + GetDefNumber(deb->count, def, filename); + } + + else if (defname == "life") { + GetDefNumber(deb->life, def, filename); + } + + else if (defname == "fire") { + if (fire_index < 5) { + GetDefVec(deb->fire_loc[fire_index], def, filename); + deb->fire_loc[fire_index] *= (float) scale; + fire_index++; + } + } + + else if (defname == "fire_type") { + GetDefNumber(deb->fire_type, def, filename); + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +ShipDesign::ParseMap(TermStruct* val) +{ + char sprite_name[NAMELEN]; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + Text defname = pdef->name()->value(); + defname.setSensitive(false); + + if (defname == "sprite") { + GetDefText(sprite_name, pdef, filename); + + Bitmap* sprite = new(__FILE__,__LINE__) Bitmap(); + DataLoader* loader = DataLoader::GetLoader(); + loader->LoadBitmap(sprite_name, *sprite, Bitmap::BMP_TRANSLUCENT); + + map_sprites.append(sprite); + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +ShipDesign::ParseSquadron(TermStruct* val) +{ + char name[NAMELEN]; + char design[NAMELEN]; + int count = 4; + int avail = 4; + + name[0] = 0; + design[0] = 0; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + Text defname = pdef->name()->value(); + defname.setSensitive(false); + + if (defname == "name") { + GetDefText(name, pdef, filename); + } + else if (defname == "design") { + GetDefText(design, pdef, filename); + } + else if (defname == "count") { + GetDefNumber(count, pdef, filename); + } + else if (defname == "avail") { + GetDefNumber(avail, pdef, filename); + } + } + } + + ShipSquadron* s = new(__FILE__,__LINE__) ShipSquadron; + strcpy(s->name, name); + + s->design = Get(design); + s->count = count; + s->avail = avail; + + squadrons.append(s); +} + +// +--------------------------------------------------------------------+ + +Skin* +ShipDesign::ParseSkin(TermStruct* val) +{ + Skin* skin = 0; + char name[NAMELEN]; + + name[0] = 0; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* def = val->elements()->at(i)->isDef(); + if (def) { + Text defname = def->name()->value(); + defname.setSensitive(false); + + if (defname == "name") { + GetDefText(name, def, filename); + + skin = new(__FILE__,__LINE__) Skin(name); + } + else if (defname == "material" || defname == "mtl") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: skin struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + ParseSkinMtl(val, skin); + } + } + } + } + + if (skin && skin->NumCells()) { + skins.append(skin); + } + + else if (skin) { + delete skin; + skin = 0; + } + + return skin; +} + +void +ShipDesign::ParseSkinMtl(TermStruct* val, Skin* skin) +{ + Material* mtl = new(__FILE__,__LINE__) Material; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* def = val->elements()->at(i)->isDef(); + if (def) { + Text defname = def->name()->value(); + defname.setSensitive(false); + + if (defname == "name") { + GetDefText(mtl->name, def, filename); + } + else if (defname == "Ka") { + GetDefColor(mtl->Ka, def, filename); + } + else if (defname == "Kd") { + GetDefColor(mtl->Kd, def, filename); + } + else if (defname == "Ks") { + GetDefColor(mtl->Ks, def, filename); + } + else if (defname == "Ke") { + GetDefColor(mtl->Ke, def, filename); + } + else if (defname == "Ns" || defname == "power") { + GetDefNumber(mtl->power, def, filename); + } + else if (defname == "bump") { + GetDefNumber(mtl->bump, def, filename); + } + else if (defname == "luminous") { + GetDefBool(mtl->luminous, def, filename); + } + + else if (defname == "blend") { + if (def->term() && def->term()->isNumber()) + GetDefNumber(mtl->blend, def, filename); + + else if (def->term() && def->term()->isText()) { + Text val; + GetDefText(val, def, filename); + val.setSensitive(false); + + if (val == "alpha" || val == "translucent") + mtl->blend = Material::MTL_TRANSLUCENT; + + else if (val == "additive") + mtl->blend = Material::MTL_ADDITIVE; + + else + mtl->blend = Material::MTL_SOLID; + } + } + + else if (defname.indexOf("tex_d") == 0) { + char tex_name[64]; + if (!GetDefText(tex_name, def, filename)) + Print("WARNING: invalid or missing tex_diffuse in '%s'\n", filename); + + DataLoader* loader = DataLoader::GetLoader(); + loader->LoadTexture(tex_name, mtl->tex_diffuse); + } + + else if (defname.indexOf("tex_s") == 0) { + char tex_name[64]; + if (!GetDefText(tex_name, def, filename)) + Print("WARNING: invalid or missing tex_specular in '%s'\n", filename); + + DataLoader* loader = DataLoader::GetLoader(); + loader->LoadTexture(tex_name, mtl->tex_specular); + } + + else if (defname.indexOf("tex_b") == 0) { + char tex_name[64]; + if (!GetDefText(tex_name, def, filename)) + Print("WARNING: invalid or missing tex_bumpmap in '%s'\n", filename); + + DataLoader* loader = DataLoader::GetLoader(); + loader->LoadTexture(tex_name, mtl->tex_bumpmap); + } + + else if (defname.indexOf("tex_e") == 0) { + char tex_name[64]; + if (!GetDefText(tex_name, def, filename)) + Print("WARNING: invalid or missing tex_emissive in '%s'\n", filename); + + DataLoader* loader = DataLoader::GetLoader(); + loader->LoadTexture(tex_name, mtl->tex_emissive); + } + } + } + + if (skin && mtl) + skin->AddMaterial(mtl); +} + +const Skin* +ShipDesign::FindSkin(const char* skin_name) const +{ + int n = skins.size(); + + for (int i = 0; i < n; i++) { + Skin* s = skins[n-1-i]; + + if (!strcmp(s->Name(), skin_name)) + return s; + } + + return 0; +} diff --git a/Stars45/ShipDesign.h b/Stars45/ShipDesign.h new file mode 100644 index 0000000..6ae85dc --- /dev/null +++ b/Stars45/ShipDesign.h @@ -0,0 +1,292 @@ +/* Project Starshatter 4.6 + Destroyer Studios LLC + Copyright © 1997-2006. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: ShipDesign.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Starship Design parameters class +*/ + +#ifndef ShipDesign_h +#define ShipDesign_h + +#include "Types.h" +#include "Bitmap.h" +#include "Geometry.h" +#include "term.h" +#include "List.h" + +// +----------------------------------------------------------------------+ + +class ShipDesign; +class Model; +class Skin; +class PowerSource; +class Weapon; +class HardPoint; +class Computer; +class Drive; +class QuantumDrive; +class Farcaster; +class Thruster; +class Sensor; +class NavLight; +class NavSystem; +class Shield; +class FlightDeck; +class LandingGear; +class System; +class Sound; + +// +====================================================================+ + +class ShipLoad +{ +public: + static const char* TYPENAME() { return "ShipLoad"; } + + ShipLoad(); + + char name[64]; + int load[16]; + double mass; +}; + +class ShipSquadron +{ +public: + static const char* TYPENAME() { return "ShipSquadron"; } + + ShipSquadron(); + + char name[64]; + ShipDesign* design; + int count; + int avail; +}; + +class ShipExplosion +{ +public: + static const char* TYPENAME() { return "ShipExplosion"; } + + ShipExplosion() { ZeroMemory(this, sizeof(ShipExplosion)); } + + int type; + float time; + Vec3 loc; + bool final; +}; + +class ShipDebris +{ +public: + static const char* TYPENAME() { return "ShipDebris"; } + + ShipDebris() { ZeroMemory(this, sizeof(ShipDebris)); } + + Model* model; + int count; + int life; + Vec3 loc; + float mass; + float speed; + float drag; + int fire_type; + Vec3 fire_loc[5]; +}; + +// +====================================================================+ +// Used to share common information about ships of a single type. +// ShipDesign objects are loaded from a text file and stored in a +// static list (catalog) member for use by the Ship. + +class ShipDesign +{ +public: + static const char* TYPENAME() { return "ShipDesign"; } + + enum CONSTANTS { + MAX_DEBRIS = 10, + MAX_EXPLOSIONS = 10 + }; + + ShipDesign(); + ShipDesign(const char* name, const char* path, const char* filename, bool secret=false); + ~ShipDesign(); + + // public interface: + static void Initialize(); + static void Close(); + static bool CheckName(const char* name); + static ShipDesign* Get(const char* design_name, const char* design_path=0); + static ShipDesign* FindModDesign(const char* design_name, const char* design_path=0); + static void ClearModCatalog(); + static int GetDesignList(int type, List& designs); // never destroy the design list! + + static int ClassForName(const char* name); + static const char* ClassName(int type); + + static int LoadCatalog(const char* path, const char* file, bool mod=false); + static void LoadSkins(const char* path, const char* archive=0); + static void PreloadCatalog(int index=-1); + static int StandardCatalogSize(); + + int operator == (const ShipDesign& s) const { return !strncmp(name, s.name, 31); } + + // Parser: + void ParseShip(TermDef* def); + + void ParsePower(TermStruct* val); + void ParseDrive(TermStruct* val); + void ParseQuantumDrive(TermStruct* val); + void ParseFarcaster(TermStruct* val); + void ParseThruster(TermStruct* val); + void ParseNavlight(TermStruct* val); + void ParseFlightDeck(TermStruct* val); + void ParseLandingGear(TermStruct* val); + void ParseWeapon(TermStruct* val); + void ParseHardPoint(TermStruct* val); + void ParseSensor(TermStruct* val); + void ParseNavsys(TermStruct* val); + void ParseComputer(TermStruct* val); + void ParseShield(TermStruct* val); + void ParseDeathSpiral(TermStruct* val); + void ParseExplosion(TermStruct* val, int index); + void ParseDebris(TermStruct* val, int index); + void ParseLoadout(TermStruct* val); + void ParseMap(TermStruct* val); + void ParseSquadron(TermStruct* val); + Skin* ParseSkin(TermStruct* val); + void ParseSkinMtl(TermStruct* val, Skin* skin); + + // general information: + const char* DisplayName() const; + + char filename[64]; + char path_name[64]; + char name[64]; + char display_name[64]; + char abrv[16]; + int type; + float scale; + int auto_roll; + bool valid; + bool secret; // don't display in editor + Text description; // background info for tactical reference + + // LOD representation: + int lod_levels; + List models[4]; + List offsets[4]; + float feature_size[4]; + List spin_rates; + + // player selectable skins: + List skins; + const Skin* FindSkin(const char* skin_name) const; + + // virtual cockpit: + Model* cockpit_model; + float cockpit_scale; + + // performance: + float vlimit; + float agility; + float air_factor; + float roll_rate; + float pitch_rate; + float yaw_rate; + float trans_x; + float trans_y; + float trans_z; + float turn_bank; + Vec3 chase_vec; + Vec3 bridge_vec; + Vec3 beauty_cam; + + float prep_time; + + // physical data: + float drag, roll_drag, pitch_drag, yaw_drag; + float arcade_drag; + float mass, integrity, radius; + + // aero data: + float CL, CD, stall; + + // weapons: + int primary; + int secondary; + + // drives: + int main_drive; + + // visibility: + float pcs; // passive sensor cross section + float acs; // active sensor cross section + float detet; // maximum detection range + float e_factor[3]; // pcs scaling by emcon setting + + // ai settings: + float avoid_time; + float avoid_fighter; + float avoid_strike; + float avoid_target; + float commit_range; + + // death spriral sequence: + float death_spiral_time; + float explosion_scale; + ShipExplosion explosion[MAX_EXPLOSIONS]; + ShipDebris debris[MAX_DEBRIS]; + + List reactors; + List weapons; + List hard_points; + List drives; + List computers; + List flight_decks; + List navlights; + QuantumDrive* quantum_drive; + Farcaster* farcaster; + Thruster* thruster; + Sensor* sensor; + NavSystem* navsys; + Shield* shield; + Model* shield_model; + Weapon* decoy; + Weapon* probe; + LandingGear* gear; + + float splash_radius; + float scuttle; + float repair_speed; + int repair_teams; + bool repair_auto; + bool repair_screen; + bool wep_screen; + + Text bolt_hit_sound; + Text beam_hit_sound; + + Sound* bolt_hit_sound_resource; + Sound* beam_hit_sound_resource; + + List loadouts; + List map_sprites; + List squadrons; + + Bitmap beauty; + Bitmap hud_icon; +}; + +// +--------------------------------------------------------------------+ + +#endif ShipDesign_h + diff --git a/Stars45/ShipKiller.cpp b/Stars45/ShipKiller.cpp new file mode 100644 index 0000000..47f3ad8 --- /dev/null +++ b/Stars45/ShipKiller.cpp @@ -0,0 +1,263 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars + FILE: ShipKiller.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + ShipKiller class implementation +*/ + +#include "MemDebug.h" +#include "ShipKiller.h" +#include "Sim.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "System.h" +#include "Weapon.h" +#include "Shot.h" +#include "Explosion.h" +#include "Debris.h" +#include "HUDSounds.h" + +#include "Solid.h" +#include "Random.h" + +// +----------------------------------------------------------------------+ + +ShipKiller::ShipKiller(Ship* s) + : ship(s), DEATH_CAM_LINGER(5.0f), time(0.0f), exp_time(0.0f), exp_index(0) +{ +} + +ShipKiller::~ShipKiller() +{ +} + +// +----------------------------------------------------------------------+ + +inline int random_index() +{ + return (int) (rand()/3277); +} + +// +----------------------------------------------------------------------+ + +void +ShipKiller::BeginDeathSpiral() +{ + if (!ship) return; + + // shut down all ship systems: + ListIter iter = ship->Systems(); + while (++iter) { + iter->PowerOff(); + iter->SetPowerLevel(0); + + if (iter->Type() == System::WEAPON) { + Weapon* gun = (Weapon*) iter.value(); + + for (int i = 0; i < Weapon::MAX_BARRELS; i++) { + Shot* beam = gun->GetBeam(i); + if (beam) + beam->Destroy(); + } + } + } + + if (ship->GetShieldRep()) + ship->GetShieldRep()->Hide(); + + Sim* sim = Sim::GetSim(); + const ShipDesign* design = ship->Design(); + + float time_to_go = design->death_spiral_time; + time = DEATH_CAM_LINGER + time_to_go; + loc = ship->Location() + ship->Velocity() * (time_to_go-1.0f); + + if (rand() < 16000) + loc += ship->BeamLine() * -3 * ship->Radius(); + else + loc += ship->BeamLine() * 3 * ship->Radius(); + + if (rand() < 8000) + loc += ship->LiftLine() * -1 * ship->Radius(); + else + loc += ship->LiftLine() * 2 * ship->Radius(); + + // stop on crash: + if (ship->IsGroundUnit() || (ship->IsAirborne() && ship->AltitudeAGL() < ship->Radius()*2)) { + time = DEATH_CAM_LINGER; + loc = ship->Location() + Point(6*ship->Radius(), 7*ship->Radius(), 8*ship->Radius()); + ship->SetVelocity(Point(0,0,0)); + } + // else, slow tumble: + else { + Point torque = RandomVector(ship->Mass()/7); + ship->ApplyTorque(torque); + + for (int i = 0; i < 5; i++) { + exp_index = random_index() % ShipDesign::MAX_EXPLOSIONS; + if (design->explosion[exp_index].type > 0 && !design->explosion[exp_index].final) + break; + } + + float exp_scale = design->explosion_scale; + if (exp_scale <= 0) + exp_scale = design->scale; + + exp_time = design->explosion[exp_index].time; + + if (design->explosion[exp_index].type > 0) { + Point exp_loc = ship->Location() + (design->explosion[exp_index].loc * ship->Cam().Orientation()); + sim->CreateExplosion(exp_loc, + ship->Velocity(), + design->explosion[exp_index].type, + (float) ship->Radius(), + exp_scale, + ship->GetRegion(), + ship); + } + } + + ship->SetControls(0); + ship->SetupAgility(); +} + +// +----------------------------------------------------------------------+ + +void +ShipKiller::ExecFrame(double seconds) +{ + Sim* sim = Sim::GetSim(); + const ShipDesign* design = ship->Design(); + + time -= (float) seconds; + exp_time -= (float) seconds; + + float exp_scale = design->explosion_scale; + if (exp_scale <= 0) + exp_scale = design->scale; + + if (exp_time < 0) { + exp_index++; + if (exp_index >= ShipDesign::MAX_EXPLOSIONS || design->explosion[exp_index].final) + exp_index = 0; + + exp_time = design->explosion[exp_index].time; + + if (design->explosion[exp_index].type > 0) { + Point exp_loc = ship->Location() + (design->explosion[exp_index].loc * ship->Cam().Orientation()); + sim->CreateExplosion(exp_loc, + ship->Velocity(), + design->explosion[exp_index].type, + (float) ship->Radius(), + exp_scale, + ship->GetRegion(), + ship); + } + } + + if (time < DEATH_CAM_LINGER) { + for (int i = 0; i < ShipDesign::MAX_EXPLOSIONS; i++) { + if (design->explosion[i].final) { + Point exp_loc = ship->Location() + (design->explosion[i].loc * ship->Cam().Orientation()); + sim->CreateExplosion(exp_loc, + ship->Velocity(), + design->explosion[i].type, + (float) ship->Radius(), + exp_scale, + ship->GetRegion()); + } + } + + for (i = 0; i < ShipDesign::MAX_DEBRIS; i++) { + if (design->debris[i].model) { + Point debris_loc = ship->Location() + (design->debris[i].loc * ship->Cam().Orientation()); + Point debris_vel = debris_loc - ship->Location(); + debris_vel.Normalize(); + + if (design->debris[i].speed > 0) + debris_vel *= design->debris[i].speed; + else + debris_vel *= 200; + + if (ship->IsGroundUnit()) { + debris_vel *= 2; + + if (debris_vel.y < 0) + debris_vel.y *= -1; + } + + for (int n = 0; n < design->debris[i].count; n++) { + Debris* debris = sim->CreateDebris(debris_loc, + debris_vel + ship->Velocity(), + design->debris[i].model, + design->debris[i].mass, + ship->GetRegion()); + + debris->SetLife(design->debris[i].life); + debris->SetDrag(design->debris[i].drag); + + if (n == 0) { + debris->CloneCam(ship->Cam()); + debris->MoveTo(debris_loc); + } + + for (int fire = 0; fire < 5; fire++) { + if (design->debris[i].fire_loc[fire] == Vec3(0,0,0)) + continue; + + Point fire_loc = debris->Location() + (design->debris[i].fire_loc[fire] * debris->Cam().Orientation()); + + if (design->debris[i].fire_type > 0) { + sim->CreateExplosion(fire_loc, + ship->Velocity(), + design->debris[i].fire_type, + exp_scale, + exp_scale, + ship->GetRegion(), + debris); + } + else { + sim->CreateExplosion(fire_loc, + ship->Velocity(), + Explosion::SMALL_FIRE, + exp_scale, + exp_scale, + ship->GetRegion(), + debris); + + sim->CreateExplosion(fire_loc, + ship->Velocity(), + Explosion::SMOKE_TRAIL, + exp_scale * 0.25f, + exp_scale * 0.25f, + ship->GetRegion(), + debris); + } + } + + if (n+1 < design->debris[i].count) { + debris_vel = RandomVector(1); + + if (design->debris[i].speed > 0) + debris_vel *= design->debris[i].speed * Random(0.8, 1.2); + else + debris_vel *= 300 + rand()/50; + } + } + } + } + + if (ship == sim->GetPlayerShip()) + HUDSounds::StopSound(HUDSounds::SND_RED_ALERT); + + sim->CreateSplashDamage(ship); + ship->Destroy(); // CAREFUL!!! This will also delete this object! + } +} diff --git a/Stars45/ShipKiller.h b/Stars45/ShipKiller.h new file mode 100644 index 0000000..3cb9b35 --- /dev/null +++ b/Stars45/ShipKiller.h @@ -0,0 +1,55 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: ShipKiller.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + ShipKiller (i.e. death spiral) class +*/ + +#ifndef ShipKiller_h +#define ShipKiller_h + +#include "Types.h" +#include "Geometry.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class Ship; + +// +--------------------------------------------------------------------+ + +class ShipKiller +{ +public: + const float DEATH_CAM_LINGER; + + // CONSTRUCTORS: + ShipKiller(Ship* ship); + virtual ~ShipKiller(); + + virtual void BeginDeathSpiral(); + virtual void ExecFrame(double seconds); + + // GENERAL ACCESSORS: + virtual float TransitionTime() const { return time; } + virtual Point TransitionLoc() const { return loc; } + +protected: + Ship* ship; + + float time; + Point loc; + + float exp_time; + int exp_index; +}; + +#endif ShipKiller_h + diff --git a/Stars45/ShipSolid.cpp b/Stars45/ShipSolid.cpp new file mode 100644 index 0000000..8691d21 --- /dev/null +++ b/Stars45/ShipSolid.cpp @@ -0,0 +1,96 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: ShipSolid.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== +*/ + +#include "MemDebug.h" +#include "ShipSolid.h" +#include "Ship.h" +#include "Sim.h" +#include "StarSystem.h" +#include "TerrainRegion.h" + +#include "Game.h" +#include "Skin.h" + +// +--------------------------------------------------------------------+ + +ShipSolid::ShipSolid(Ship* s) + : ship(s), skin(0), in_soup(false) +{ +} + +// +--------------------------------------------------------------------+ + +ShipSolid::~ShipSolid() +{ +} + +// +--------------------------------------------------------------------+ + +void +ShipSolid::TranslateBy(const Point& ref) +{ + true_eye_point = ref; + Solid::TranslateBy(ref); +} + +// +--------------------------------------------------------------------+ + +void +ShipSolid::Render(Video* video, DWORD flags) +{ + if (hidden || !visible || !video || Depth() > 5e6) + return; + + const Skin* s = 0; + + if (ship) + s = ship->GetSkin(); + else + s = skin; + + if (s) + s->ApplyTo(model); + + bool fog = false; + + if (ship && ship->IsAirborne()) { + fog = true; + + TerrainRegion* rgn = (TerrainRegion*) ship->GetRegion()->GetOrbitalRegion(); + double visibility = rgn->GetWeather().Visibility(); + FLOAT fog_density = (FLOAT) (rgn->FogDensity() * 2.5e-5 * 1/visibility); + Color fog_color = rgn->FogColor(); + + // Use BLACK fog on secondary lighting pass + // This will effectively "filter out" the highlights + // with distance... + + if (flags & Graphic::RENDER_ADD_LIGHT) + fog_color = Color::Black; + + video->SetRenderState(Video::FOG_ENABLE, true); + video->SetRenderState(Video::FOG_COLOR, fog_color.Value()); + video->SetRenderState(Video::FOG_DENSITY, *((DWORD*) &fog_density)); + } + + if (!fog) video->SetRenderState(Video::FOG_ENABLE, false); + + Solid::Render(video, flags); + + if (fog) video->SetRenderState(Video::FOG_ENABLE, false); + + if (s) + s->Restore(model); +} + + diff --git a/Stars45/ShipSolid.h b/Stars45/ShipSolid.h new file mode 100644 index 0000000..48a1bae --- /dev/null +++ b/Stars45/ShipSolid.h @@ -0,0 +1,52 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: ShipSolid.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + 3D Solid (Polygon) Object +*/ + +#ifndef ShipSolid_h +#define ShipSolid_h + +#include "Solid.h" + +// +--------------------------------------------------------------------+ + +class Ship; +class Skin; + +// +--------------------------------------------------------------------+ + +class ShipSolid : public Solid +{ +public: + static const char* TYPENAME() { return "ShipSolid"; } + + ShipSolid(Ship* s); + virtual ~ShipSolid(); + + virtual void Render(Video* video, DWORD flags); + virtual void TranslateBy(const Point& ref); + + const Skin* GetSkin() const { return skin; } + void SetSkin(const Skin* s) { skin = s; } + +protected: + Ship* ship; + const Skin* skin; + Point true_eye_point; + Point fog_loc; + bool in_soup; +}; + +// +--------------------------------------------------------------------+ + +#endif ShipSolid_h + diff --git a/Stars45/Shot.cpp b/Stars45/Shot.cpp new file mode 100644 index 0000000..21980a8 --- /dev/null +++ b/Stars45/Shot.cpp @@ -0,0 +1,626 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Shot.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Laser and Missile class +*/ + +#include "MemDebug.h" +#include "Shot.h" +#include "Weapon.h" +#include "DriveSprite.h" +#include "SeekerAI.h" +#include "Sim.h" +#include "Ship.h" +#include "Trail.h" +#include "Random.h" +#include "AudioConfig.h" +#include "TerrainRegion.h" +#include "Terrain.h" + +#include "Game.h" +#include "Bolt.h" +#include "Sprite.h" +#include "Solid.h" +#include "Light.h" +#include "Bitmap.h" +#include "DataLoader.h" +#include "Sound.h" + +// +--------------------------------------------------------------------+ + +Shot::Shot(const Point& pos, const Camera& shot_cam, WeaponDesign* dsn, const Ship* ship) + : first_frame(true), owner(ship), flash(0), flare(0), trail(0), sound(0), eta(0), + charge(1.0f), design(dsn), offset(1.0e5f), altitude_agl(-1.0e6f), hit_target(false) +{ + obj_type = SimObject::SIM_SHOT; + type = design->type; + primary = design->primary; + beam = design->beam; + base_damage = design->damage; + armed = false; + + radius = 10.0f; + + if (primary || design->decoy_type || !design->guided) { + straight = true; + armed = true; + } + + cam.Clone(shot_cam); + + life = design->life; + velocity = cam.vpn() * (double) design->speed; + + MoveTo(pos); + + if (beam) + origin = pos + (shot_cam.vpn() * -design->length); + + switch (design->graphic_type) { + case Graphic::BOLT: { + Bolt* s = new(__FILE__,__LINE__) Bolt(design->length, design->width, design->shot_img, 1); + s->SetDirection(cam.vpn()); + rep = s; + } + break; + + case Graphic::SPRITE: { + Sprite* s = 0; + + if (design->animation) + s = new(__FILE__,__LINE__) DriveSprite(design->animation, design->anim_length); + else + s = new(__FILE__,__LINE__) DriveSprite(design->shot_img); + + s->Scale((double) design->scale); + rep = s; + } + break; + + case Graphic::SOLID: { + Solid* s = new(__FILE__,__LINE__) Solid; + s->UseModel(design->shot_model); + rep = s; + + radius = rep->Radius(); + } + break; + } + + if (rep) + rep->MoveTo(pos); + + light = 0; + + if (design->light > 0) { + light = new(__FILE__,__LINE__) Light(design->light); + light->SetColor(design->light_color); + } + + mass = design->mass; + drag = design->drag; + thrust = 0.0f; + + dr_drg = design->roll_drag; + dp_drg = design->pitch_drag; + dy_drg = design->yaw_drag; + + SetAngularRates((float) design->roll_rate, (float) design->pitch_rate, (float) design->yaw_rate); + + if (design->flash_img != 0) { + flash = new(__FILE__,__LINE__) Sprite(design->flash_img); + flash->Scale((double) design->flash_scale); + flash->MoveTo(pos - cam.vpn() * design->length); + flash->SetLuminous(true); + } + + if (design->flare_img != 0) { + flare = new(__FILE__,__LINE__) DriveSprite(design->flare_img); + flare->Scale((double) design->flare_scale); + flare->MoveTo(pos); + } + + if (owner) { + iff_code = (BYTE) owner->GetIFF(); + Observe((SimObject*) owner); + } + + sprintf(name, "Shot(%s)", design->name); +} + +// +--------------------------------------------------------------------+ + +Shot::~Shot() +{ + GRAPHIC_DESTROY(flash); + GRAPHIC_DESTROY(flare); + GRAPHIC_DESTROY(trail); + + if (sound) { + sound->Stop(); + sound->Release(); + } +} + +// +--------------------------------------------------------------------+ + +const char* +Shot::DesignName() const +{ + return design->name; +} + +// +--------------------------------------------------------------------+ + +void +Shot::SetCharge(float c) +{ + charge = c; + + // trim beam life to amount of energy available: + if (beam) + life = design->life * charge / design->charge; +} + +void +Shot::SetFuse(double seconds) +{ + if (seconds > 0 && !beam) + life = seconds; +} + +// +--------------------------------------------------------------------+ + +void +Shot::SeekTarget(SimObject* target, System* sub) +{ + if (dir && !primary) { + SeekerAI* seeker = (SeekerAI*) dir; + SimObject* old_target = seeker->GetTarget(); + + if (old_target->Type()==SimObject::SIM_SHIP) { + Ship* tgt_ship = (Ship*) old_target; + tgt_ship->DropThreat(this); + } + } + + delete dir; + dir = 0; + + if (target) { + SeekerAI* seeker = new(__FILE__,__LINE__) SeekerAI(this); + seeker->SetTarget(target, sub); + seeker->SetPursuit(design->guided); + seeker->SetDelay(1); + + dir = seeker; + + if (!primary && target->Type()==SimObject::SIM_SHIP) { + Ship* tgt_ship = (Ship*) target; + tgt_ship->AddThreat(this); + } + } +} + +bool +Shot::IsTracking(Ship* tgt) const +{ + return tgt && (GetTarget() == tgt); +} + +SimObject* +Shot::GetTarget() const +{ + if (dir) { + SeekerAI* seeker = (SeekerAI*) dir; + + if (seeker->GetDelay() <= 0) + return seeker->GetTarget(); + } + + return 0; +} + +bool +Shot::IsFlak() const +{ + return design && design->flak; +} + +// +--------------------------------------------------------------------+ + +bool +Shot::IsHostileTo(const SimObject* o) const +{ + if (o) { + if (o->Type() == SIM_SHIP) { + Ship* s = (Ship*) o; + + if (s->IsRogue()) + return true; + + if (s->GetIFF() > 0 && s->GetIFF() != GetIFF()) + return true; + } + + else if (o->Type() == SIM_SHOT || o->Type() == SIM_DRONE) { + Shot* s = (Shot*) o; + + if (s->GetIFF() > 0 && s->GetIFF() != GetIFF()) + return true; + } + } + + return false; +} + +// +--------------------------------------------------------------------+ + +void +Shot::ExecFrame(double seconds) +{ + altitude_agl = -1.0e6f; + + // add random flickering effect: + double flicker = 0.75 + (double) rand() / 8e4; + if (flicker > 1) flicker = 1; + + if (flare) { + flare->SetShade(flicker); + } + else if (beam) { + Bolt* blob = (Bolt*) rep; + blob->SetShade(flicker); + offset -= (float) (seconds * 10); + } + + if (Game::Paused()) + return; + + if (beam) { + if (!first_frame) { + if (life > 0) { + life -= seconds; + + if (life < 0) + life = 0; + } + } + } + else { + origin = Location(); + + if (!first_frame) + Physical::ExecFrame(seconds); + else + Physical::ExecFrame(0); + + double len = design->length; + if (len < 50) len = 50; + + if (!trail && life > 0 && design->life - life > 0.2) { + if (design->trail.length()) { + trail = new(__FILE__,__LINE__) Trail(design->trail_img, design->trail_length); + + if (design->trail_width > 0) + trail->SetWidth(design->trail_width); + + if (design->trail_dim > 0) + trail->SetDim(design->trail_dim); + + trail->AddPoint(Location() + Heading() * -100); + + Scene* scene = 0; + + if (rep) + scene = rep->GetScene(); + + if (scene) + scene->AddGraphic(trail); + } + } + + if (trail) + trail->AddPoint(Location()); + + if (!armed) { + SeekerAI* seeker = (SeekerAI*) dir; + + if (seeker && seeker->GetDelay() <= 0) + armed = true; + } + + // handle submunitions: + else if (design->det_range > 0 && design->det_count > 0) { + if (dir && !primary) { + SeekerAI* seeker = (SeekerAI*) dir; + SimObject* target = seeker->GetTarget(); + + if (target) { + double range = Point(Location() - target->Location()).length(); + + if (range < design->det_range) { + life = 0; + + Sim* sim = Sim::GetSim(); + WeaponDesign* child_design = WeaponDesign::Find(design->det_child); + + if (sim && child_design) { + double spread = design->det_spread; + + Camera aim_cam; + aim_cam.Clone(Cam()); + aim_cam.LookAt(target->Location()); + + for (int i = 0; i < design->det_count; i++) { + Shot* child = sim->CreateShot(Location(), aim_cam, child_design, + owner, owner->GetRegion()); + + child->SetCharge(child_design->charge); + + if (child_design->guided) + child->SeekTarget(target, seeker->GetSubTarget()); + + if (child_design->beam) + child->SetBeamPoints(Location(), target->Location()); + + if (i) aim_cam.LookAt(target->Location()); + aim_cam.Pitch(Random(-spread, spread)); + aim_cam.Yaw(Random(-spread, spread)); + } + } + } + } + } + } + + if (flash && !first_frame) + GRAPHIC_DESTROY(flash); + + if (thrust < design->thrust) + thrust += (float) (seconds * 5.0e3); + else + thrust = design->thrust; + } + + first_frame = 0; + + if (flare) + flare->MoveTo(Location()); +} + +// +--------------------------------------------------------------------+ + +void +Shot::Disarm() +{ + if (armed && !primary) { + armed = false; + delete dir; + dir = 0; + } +} + +void +Shot::Destroy() +{ + life = 0; +} + +// +--------------------------------------------------------------------+ + +void +Shot::SetBeamPoints(const Point& from, const Point& to) +{ + if (beam) { + MoveTo(to); + origin = from; + + if (sound) { + sound->SetLocation(from); + } + + if (rep) { + Bolt* s = (Bolt*) rep; + s->SetEndPoints(from, to); + + double len = Point(to - from).length() / 500; + s->SetTextureOffset(offset, offset + len); + } + } + + if (flash) { + flash->MoveTo(origin); + } +} + +// +--------------------------------------------------------------------+ + +double +Shot::AltitudeMSL() const +{ + return Location().y; +} + +double +Shot::AltitudeAGL() const +{ + if (altitude_agl < -1000) { + Shot* pThis = (Shot*) this; // cast-away const + Point loc = Location(); + Terrain* terrain = region->GetTerrain(); + + if (terrain) + pThis->altitude_agl = (float) (loc.y - terrain->Height(loc.x, loc.z)); + + else + pThis->altitude_agl = (float) loc.y; + + if (!_finite(altitude_agl)) { + pThis->altitude_agl = 0.0f; + } + } + + return altitude_agl; +} + +// +--------------------------------------------------------------------+ + +void +Shot::Initialize() +{ +} + +// +--------------------------------------------------------------------+ + +void +Shot::Close() +{ +} + +// +--------------------------------------------------------------------+ + +double +Shot::Damage() const +{ + double damage = 0; + + // beam damage based on length: + if (beam) { + double fade = 1; + + if (design) { + // linear fade with distance: + double len = Point(origin - Location()).length(); + + if (len > design->min_range) + fade = (design->length - len) / (design->length - design->min_range); + } + + damage = base_damage * charge * fade * Game::FrameTime(); + } + + // energy wep damage based on time: + else if (primary) { + damage = base_damage * charge * life; + } + + // missile damage is constant: + else { + damage = base_damage * charge; + } + + return damage; +} + +double +Shot::Length() const +{ + if (design) + return design->length; + + return 500; +} + +// +--------------------------------------------------------------------+ + +void +Shot::Activate(Scene& scene) +{ + SimObject::Activate(scene); + + if (trail) + scene.AddGraphic(trail); + + if (flash) + scene.AddGraphic(flash); + + if (flare) + scene.AddGraphic(flare); + + if (first_frame) { + if (design->sound_resource) { + sound = design->sound_resource->Duplicate(); + + if (sound) { + long max_vol = AudioConfig::EfxVolume(); + long volume = -1000; + + if (volume > max_vol) + volume = max_vol; + + if (beam) { + sound->SetLocation(origin); + sound->SetVolume(volume); + sound->Play(); + } + else { + sound->SetLocation(Location()); + sound->SetVolume(volume); + sound->Play(); + sound = 0; // fire and forget: + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +Shot::Deactivate(Scene& scene) +{ + SimObject::Deactivate(scene); + + if (trail) + scene.DelGraphic(trail); + + if (flash) + scene.DelGraphic(flash); + + if (flare) + scene.DelGraphic(flare); +} + +// +--------------------------------------------------------------------+ + +int +Shot::GetIFF() const +{ + return iff_code; +} + +// +--------------------------------------------------------------------+ + +Color +Shot::MarkerColor() const +{ + return Ship::IFFColor(GetIFF()); +} + +// +--------------------------------------------------------------------+ + +const char* +Shot::GetObserverName() const +{ + return name; +} + +// +--------------------------------------------------------------------+ + +bool +Shot::Update(SimObject* obj) +{ + if (obj == (SimObject*) owner) + owner = 0; + + return SimObserver::Update(obj); +} diff --git a/Stars45/Shot.h b/Stars45/Shot.h new file mode 100644 index 0000000..52f5a6c --- /dev/null +++ b/Stars45/Shot.h @@ -0,0 +1,127 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Shot.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Laser and Missile class +*/ + +#ifndef Shot_h +#define Shot_h + +#include "Types.h" +#include "Geometry.h" +#include "SimObject.h" +#include "Color.h" + +// +--------------------------------------------------------------------+ + +class Camera; +class Ship; +class Trail; +class System; +class WeaponDesign; +class Sprite; +class Sound; + +// +--------------------------------------------------------------------+ + +class Shot : public SimObject, + public SimObserver +{ +public: + static const char* TYPENAME() { return "Shot"; } + + Shot(const Point& pos, const Camera& cam, WeaponDesign* design, const Ship* ship=0); + virtual ~Shot(); + + virtual void SeekTarget(SimObject* target, System* sub=0); + virtual void ExecFrame(double factor); + static void Initialize(); + static void Close(); + + virtual void Activate(Scene& scene); + virtual void Deactivate(Scene& scene); + + const Ship* Owner() const { return owner; } + double Damage() const; + int ShotType() const { return type; } + virtual SimObject* GetTarget() const; + + virtual bool IsPrimary() const { return primary; } + virtual bool IsDrone() const { return false; } + virtual bool IsDecoy() const { return false; } + virtual bool IsProbe() const { return false; } + virtual bool IsMissile() const { return !primary; } + virtual bool IsArmed() const { return armed; } + virtual bool IsBeam() const { return beam; } + virtual bool IsFlak() const; + virtual bool IsHostileTo(const SimObject* o) const; + + bool HitTarget() const { return hit_target; } + void SetHitTarget(bool h) { hit_target = h; } + + virtual bool IsTracking(Ship* tgt) const; + virtual double PCS() const { return 0; } + virtual double ACS() const { return 0; } + virtual int GetIFF() const; + virtual Color MarkerColor() const; + + const Point& Origin() const { return origin; } + float Charge() const { return charge; } + void SetCharge(float c); + double Length() const; + Graphic* GetTrail() const { return (Graphic*) trail; } + void SetFuse(double seconds); + + void SetBeamPoints(const Point& from, const Point& to); + virtual void Disarm(); + virtual void Destroy(); + + const WeaponDesign* Design() const { return design; } + const char* DesignName() const; + int GetEta() const { return eta; } + void SetEta(int t) { eta = t; } + + double AltitudeMSL() const; + double AltitudeAGL() const; + + // SimObserver interface: + virtual bool Update(SimObject* obj); + virtual const char* GetObserverName() const; + +protected: + const Ship* owner; + + int type; + float base_damage; + float charge; + float offset; + float altitude_agl; + short eta; + BYTE iff_code; + bool first_frame; + bool primary; + bool beam; + bool armed; + bool hit_target; + + Sprite* flash; // muzzle flash + Sprite* flare; // drive flare + Trail* trail; // exhaust trail + + Sound* sound; + WeaponDesign* design; + + // for beam weapons: + Point origin; +}; + +#endif Shot_h + diff --git a/Stars45/Sim.cpp b/Stars45/Sim.cpp new file mode 100644 index 0000000..90986a4 --- /dev/null +++ b/Stars45/Sim.cpp @@ -0,0 +1,3810 @@ +/* Project Starshatter 5.0 + Destroyer Studios LLC + Copyright © 1997-2007. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Sim.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Simulation Universe and Region classes +*/ + +#include "MemDebug.h" +#include "Sim.h" +#include "SimEvent.h" +#include "SimObject.h" +#include "Starshatter.h" +#include "StarSystem.h" +#include "Contact.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "Element.h" +#include "Instruction.h" +#include "RadioTraffic.h" +#include "Shot.h" +#include "Drone.h" +#include "Explosion.h" +#include "Debris.h" +#include "Asteroid.h" +#include "Drive.h" +#include "QuantumDrive.h" +#include "Sensor.h" +#include "NavLight.h" +#include "Shield.h" +#include "Weapon.h" +#include "WeaponGroup.h" +#include "Hangar.h" +#include "FlightDeck.h" +#include "Sky.h" +#include "Grid.h" +#include "MFD.h" +#include "AudioConfig.h" +#include "Mission.h" +#include "MissionEvent.h" +#include "CameraDirector.h" +#include "MusicDirector.h" +#include "Combatant.h" +#include "CombatGroup.h" +#include "CombatUnit.h" +#include "HUDView.h" +#include "SeekerAI.h" +#include "ShipAI.h" +#include "Power.h" +#include "Callsign.h" +#include "GameScreen.h" +#include "Terrain.h" +#include "TerrainPatch.h" + +#include "NetGame.h" +#include "NetClientConfig.h" +#include "NetServerConfig.h" +#include "NetPlayer.h" +#include "NetUtil.h" +#include "NetData.h" + +#include "Game.h" +#include "Sound.h" +#include "Bolt.h" +#include "Solid.h" +#include "Sprite.h" +#include "Light.h" +#include "Bitmap.h" +#include "DataLoader.h" +#include "ParseUtil.h" +#include "MouseController.h" +#include "Player.h" +#include "Random.h" +#include "Video.h" + +const char* FormatGameTime(); + +// +--------------------------------------------------------------------+ + +class SimHyper +{ +public: + SimHyper(Ship* o, SimRegion* r, const Point& l, int t, bool h, Ship* fc1, Ship* fc2) + : ship(o), rgn(r), loc(l), type(t), hyperdrive(h), fc_src(fc1), fc_dst(fc2) { } + + Ship* ship; + SimRegion* rgn; + Point loc; + int type; + bool hyperdrive; + Ship* fc_src; + Ship* fc_dst; +}; + +// +--------------------------------------------------------------------+ + +class SimSplash +{ +public: + SimSplash(SimRegion* r, const Point& l, double d, double n) + : rgn(r), loc(l), damage(d), range(n), + owner_name("Collateral Damage"), missile(false) { } + + SimRegion* rgn; + Point loc; + double damage; + double range; + Text owner_name; + bool missile; +}; + +// +--------------------------------------------------------------------+ + +static bool first_frame = true; +Sim* Sim::sim = 0; + +Sim::Sim(MotionController* c) + : ctrl(c), test_mode(false), grid_shown(false), dust(0), + star_system(0), active_region(0), mission(0), netgame(0), + start_time(0) +{ + Drive::Initialize(); + Explosion::Initialize(); + FlightDeck::Initialize(); + NavLight::Initialize(); + Shot::Initialize(); + MFD::Initialize(); + Asteroid::Initialize(); + + if (!sim) + sim = this; + + cam_dir = CameraDirector::GetInstance(); +} + +Sim::~Sim() +{ + UnloadMission(); + + Shot::Close(); + FlightDeck::Close(); + NavLight::Close(); + Token::close(); + Asteroid::Close(); + + if (sim == this) + sim = 0; +} + +// +--------------------------------------------------------------------+ + +void +Sim::CommitMission() +{ + for (int i = 0; i < regions.size(); i++) + regions[i]->CommitMission(); + + if (ShipStats::NumStats() > 0) { + Print("\n\nFINAL SCORE '%s'\n", (const char*) mission->Name()); + Print("Name Kill1 Kill2 Died Colls Points Cmd Pts\n"); + Print("---------------- ----- ----- ----- ----- ------ ------\n"); + + int tk1 = 0; + int tk2 = 0; + int td = 0; + int tc = 0; + + for (int i = 0; i < ShipStats::NumStats(); i++) { + ShipStats* s = ShipStats::GetStats(i); + s->Summarize(); + + Print("%-16s %5d %5d %5d %5d %6d %6d\n", + s->GetName(), + s->GetGunKills(), + s->GetMissileKills(), + s->GetDeaths(), + s->GetColls(), + s->GetPoints(), + s->GetCommandPoints()); + + tk1 += s->GetGunKills(); + tk2 += s->GetMissileKills(); + td += s->GetDeaths(); + tc += s->GetColls(); + + CombatGroup* group = s->GetCombatGroup(); + + if (group) { + Combatant* c = group->GetCombatant(); + + if (c) + c->AddScore(s->GetPoints()); + + if (s->GetElementIndex() == 1) + group->SetSorties(group->Sorties() + 1); + + group->SetKills(group->Kills() + s->GetGunKills() + s->GetMissileKills()); + group->SetPoints(group->Points() + s->GetPoints()); + } + + if (s->IsPlayer()) { + Player* p = Player::GetCurrentPlayer(); + p->ProcessStats(s, start_time); + + if (mission && mission->Type() == Mission::TRAINING && + s->GetDeaths() == 0 && s->GetColls() == 0) + p->SetTrained(mission->Identity()); + + Player::Save(); // save training state right now before we forget! + } + } + + Print("--------------------------------------------\n"); + Print("TOTAL %5d %5d %5d %5d\n\n", tk1, tk2, td, tc); + + ShipStats::Initialize(); + } +} + +// +--------------------------------------------------------------------+ + +void +Sim::UnloadMission() +{ + if (netgame) { + delete netgame; + netgame = 0; + } + + HUDView* hud = HUDView::GetInstance(); + if (hud) + hud->HideAll(); + + ShipStats::Initialize(); + + events.destroy(); + mission_elements.destroy(); + elements.destroy(); + finished.destroy(); + + if (active_region) + active_region->Deactivate(); + + if (star_system) + star_system->Deactivate(); + + if (mission) { + mission->SetActive(false); + mission->SetComplete(true); + } + + regions.destroy(); + scene.Collect(); + + GRAPHIC_DESTROY(dust); + + star_system = 0; + active_region = 0; + mission = 0; + + // reclaim memory used by radio traffic: + RadioTraffic::DiscardMessages(); + + // release texture memory for 2D screens: + Starshatter* stars = Starshatter::GetInstance(); + if (stars) + stars->InvalidateTextureCache(); + + cam_dir = CameraDirector::GetInstance(); + if (cam_dir) + cam_dir->SetShip(0); + + AudioConfig::SetTraining(false); +} + +bool +Sim::IsActive() const +{ + return mission && mission->IsActive(); +} + +bool +Sim::IsComplete() const +{ + return mission && mission->IsComplete(); +} + +// +--------------------------------------------------------------------+ + +void +Sim::LoadMission(Mission* m, bool preload_textures) +{ + cam_dir = CameraDirector::GetInstance(); + + if (!mission) { + mission = m; + mission->SetActive(true); + + if (preload_textures) { + Video* video = Game::GetVideo(); + List all_models; + //List all_textures; + + ListIter elem_iter = mission->GetElements(); + while (++elem_iter) { + MissionElement* elem = elem_iter.value(); + const ShipDesign* design = elem->GetDesign(); + + if (design) { + for (int i = 0; i < 4; i++) { + List& models = (List&) design->models[i]; // cast-away const + + ListIter model_iter = models; + while (++model_iter) { + Model* model = model_iter.value(); + if (!all_models.contains(model)) { + all_models.append(model); + //model->GetAllTextures(all_textures); + + ListIter surf_iter = model->GetSurfaces(); + while (++surf_iter) { + Surface* surface = surf_iter.value(); + video->PreloadSurface(surface); + } + } + } + } + } + } + + /* + if (video && all_textures.size() > 0) { + ::Print("Preloading %d textures into video texture cache\n", all_textures.size()); + ListIter bmp_iter = all_textures; + while (++bmp_iter) { + Bitmap* bmp = bmp_iter.value(); + video->PreloadTexture(bmp); + } + } + */ + } + } +} + +void +Sim::ExecMission() +{ + cam_dir = CameraDirector::GetInstance(); + + if (!mission) { + Print("Sim::ExecMission() - No mission to execute.\n"); + return; + } + + if (elements.size() || finished.size()) { + Print("Sim::ExecMission(%s) mission is already executing.\n", mission->Name()); + return; + } + + Print("\nExec Mission: '%s'\n", (const char*) mission->Name()); + + if (cam_dir) + cam_dir->Reset(); + + if (mission->Stardate() > 0) + StarSystem::SetBaseTime(mission->Stardate(), true); + + star_system = mission->GetStarSystem(); + star_system->Activate(scene); + + int dust_factor = 0; + + if (Starshatter::GetInstance()) + dust_factor = Starshatter::GetInstance()->Dust(); + + if (star_system->NumDust() * dust_factor) { + dust = new(__FILE__,__LINE__) Dust(star_system->NumDust() * 2*(dust_factor+1), dust_factor > 1); + scene.AddGraphic(dust); + } + + CreateRegions(); + BuildLinks(); + CreateElements(); + CopyEvents(); + + if (netgame) { + delete netgame; + netgame = 0; + } + + first_frame = true; + start_time = Game::GameTime(); + + AudioConfig::SetTraining(mission->Type() == Mission::TRAINING); +} + +// +--------------------------------------------------------------------+ + +void +Sim::CreateRegions() +{ + const char* active_region_name = 0; + + if (mission) + active_region_name = mission->GetRegion(); + + ListIter iter = mission->GetSystemList(); + while (++iter) { + StarSystem* sys = iter.value(); + + // insert objects from star system: + ListIter star = sys->Bodies(); + while (++star) { + ListIter planet = star->Satellites(); + while (++planet) { + ListIter moon = planet->Satellites(); + while (++moon) { + ListIter rgn = moon->Regions(); + while (++rgn) { + SimRegion* sim_region = new(__FILE__,__LINE__) SimRegion(this, rgn.value()); + regions.append(sim_region); + if (!strcmp(active_region_name, sim_region->Name())) { + ActivateRegion(sim_region); + } + } + } + + ListIter rgn = planet->Regions(); + while (++rgn) { + SimRegion* sim_region = new(__FILE__,__LINE__) SimRegion(this, rgn.value()); + regions.append(sim_region); + if (!strcmp(active_region_name, sim_region->Name())) { + ActivateRegion(sim_region); + } + } + } + + ListIter rgn = star->Regions(); + while (++rgn) { + SimRegion* sim_region = new(__FILE__,__LINE__) SimRegion(this, rgn.value()); + regions.append(sim_region); + if (!strcmp(active_region_name, sim_region->Name())) { + ActivateRegion(sim_region); + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +Sim::BuildLinks() +{ + ListIter iter = regions; + while (++iter) { + SimRegion* rgn = iter.value(); + OrbitalRegion* orb = rgn->GetOrbitalRegion(); + + if (orb) { + ListIter lnk_iter = orb->Links(); + while (++lnk_iter) { + Text* t = lnk_iter.value(); + + SimRegion* tgt = FindRegion(*t); + + if (tgt && !rgn->Links().contains(tgt)) + rgn->Links().append(tgt); + } + } + } +} + +void +Sim::CreateElements() +{ + ListIter e_iter = mission->GetElements(); + while (++e_iter) { + MissionElement* msn_elem = e_iter.value(); + + // add element to a carrier? + if (msn_elem->IsSquadron()) { + Ship* carrier = FindShip(msn_elem->Carrier()); + if (carrier) { + Hangar* hangar = carrier->GetHangar(); + + if (hangar) { + int* def_load = 0; + + if (msn_elem->Loadouts().size()) { + MissionLoad* m = msn_elem->Loadouts().at(0); + + if (m->GetName().length()) { + ShipDesign* dsn = (ShipDesign*) msn_elem->GetDesign(); + ListIter sl_iter = dsn->loadouts; + while (++sl_iter) { + ShipLoad* sl = sl_iter.value(); + + if (m->GetName() == sl->name) + def_load = sl->load; + } + } + + if (!def_load) { + def_load = m->GetStations(); + } + } + + hangar->CreateSquadron(msn_elem->Name(), msn_elem->GetCombatGroup(), + msn_elem->GetDesign(), msn_elem->Count(), + msn_elem->GetIFF(), + def_load, msn_elem->MaintCount(), msn_elem->DeadCount()); + + Element* element = CreateElement(msn_elem->Name(), + msn_elem->GetIFF(), + msn_elem->MissionRole()); + + element->SetCarrier(carrier); + element->SetCombatGroup(msn_elem->GetCombatGroup()); + element->SetCombatUnit(msn_elem->GetCombatUnit()); + element->SetCount(msn_elem->Count()); + element->SetRogue(false); + element->SetPlayable(false); + element->SetLoadout(def_load); + } + } + } + + // create the element in space: + else { + Ship* carrier = 0; + Hangar* hangar = 0; + int squadron = -1; + int slot = 0; + + // first create the package element: + Element* element = CreateElement(msn_elem->Name(), + msn_elem->GetIFF(), + msn_elem->MissionRole()); + + element->SetPlayer(msn_elem->Player()); + element->SetCombatGroup(msn_elem->GetCombatGroup()); + element->SetCombatUnit(msn_elem->GetCombatUnit()); + element->SetCommandAILevel(msn_elem->CommandAI()); + element->SetHoldTime(msn_elem->HoldTime()); + element->SetZoneLock(msn_elem->ZoneLock() ? true : false); + element->SetRogue(msn_elem->IsRogue()); + element->SetPlayable(msn_elem->IsPlayable()); + element->SetIntelLevel(msn_elem->IntelLevel()); + + // if this is the player's element, make sure to activate the region: + if (msn_elem->Player()) { + SimRegion* rgn = FindRegion(msn_elem->Region()); + + if (rgn && rgn != active_region) + ActivateRegion(rgn); + } + + // if element belongs to a squadron, + // find the carrier, squadron, flight deck, etc.: + if (msn_elem->Squadron().length() > 0) { + MissionElement* squadron_elem = mission->FindElement(msn_elem->Squadron()); + + if (squadron_elem) { + element->SetSquadron(msn_elem->Squadron()); + + Element* cmdr = FindElement(squadron_elem->Carrier()); + + if (cmdr) { + element->SetCommander(cmdr); + carrier = cmdr->GetShip(1); + + if (carrier) { + element->SetCarrier(carrier); + hangar = carrier->GetHangar(); + + for (int s = 0; s < hangar->NumSquadrons(); s++) { + if (hangar->SquadronName(s) == msn_elem->Squadron()) { + squadron = s; + break; + } + } + } + } + } + } + + else if (msn_elem->Commander().length() > 0) { + Element* cmdr = FindElement(msn_elem->Commander()); + + if (cmdr) { + element->SetCommander(cmdr); + } + } + + ListIter obj = msn_elem->Objectives(); + while (++obj) { + Instruction* o = obj.value(); + Instruction* instr = 0; + + instr = new(__FILE__,__LINE__) Instruction(*o); + + element->AddObjective(instr); + } + + if (msn_elem->Instructions().size() > 0) { + ListIter instr = msn_elem->Instructions(); + while (++instr) { + element->AddInstruction(*instr); + } + } + + ListIter nav = msn_elem->NavList(); + while (++nav) { + SimRegion* rgn = FindRegion(nav->RegionName()); + + if (!rgn) + rgn = FindRegion(msn_elem->Region()); + + if (rgn) { + Instruction* npt = new(__FILE__,__LINE__) + Instruction(rgn, nav->Location(), nav->Action()); + + npt->SetStatus(nav->Status()); + npt->SetEMCON(nav->EMCON()); + npt->SetFormation(nav->Formation()); + npt->SetSpeed(nav->Speed()); + npt->SetTarget(nav->TargetName()); + npt->SetHoldTime(nav->HoldTime()); + npt->SetFarcast(nav->Farcast()); + + element->AddNavPoint(npt); + } + } + + bool alertPrep = false; + int* loadout = 0; + int respawns = msn_elem->RespawnCount(); + + // if ships are to start on alert, + // spot them onto the appropriate launch deck: + if (hangar && element && msn_elem->Count() > 0 && msn_elem->IsAlert()) { + FlightDeck* deck = 0; + int queue = 1000; + const ShipDesign* dsn = msn_elem->GetDesign(); + + if (dsn) { + for (int i = 0; i < carrier->NumFlightDecks(); i++) { + FlightDeck* d = carrier->GetFlightDeck(i); + int dq = hangar->PreflightQueue(d); + + if (d && d->IsLaunchDeck() && d->SpaceLeft(dsn->type) && dq < queue) { + queue = dq; + deck = d; + } + } + } + + if (deck) { + alertPrep = true; + + // choose best loadout: + if (msn_elem->Loadouts().size()) { + MissionLoad* l = msn_elem->Loadouts().at(0); + if (l->GetName().length()) { + ListIter sl = ((ShipDesign*) dsn)->loadouts; + while (++sl) { + if (!stricmp(sl->name, l->GetName())) + loadout = sl->load; + } + } + + else { + loadout = l->GetStations(); + } + } + + element->SetLoadout(loadout); + + for (int i = 0; i < msn_elem->Count(); i++) { + int squadron = -1; + int slot = -1; + + if (hangar->FindAvailSlot(msn_elem->GetDesign(), squadron, slot)) { + alertPrep = alertPrep && + hangar->GotoAlert(squadron, + slot, + deck, + element, + loadout, + true, // package for launch + true); // expedite + + HangarSlot* s = (HangarSlot*) hangar->GetSlot(squadron, slot); + Ship* alertShip = hangar->GetShip(s); + + if (alertShip) { + alertShip->SetRespawnCount(respawns); + + if (msn_elem->Player() == i+1) { + if (alertShip->GetRegion()) { + alertShip->GetRegion()->SetPlayerShip(alertShip); + } + else { + ::Print("WARNING: alert ship '%s' region is null\n", alertShip->Name()); + } + } + } + } + } + } + } + + if (!alertPrep) { + // then, create the ships: + for (int i = 0; i < msn_elem->Count(); i++) { + MissionShip* msn_ship = 0; + Text sname = msn_elem->GetShipName(i); + Text rnum = msn_elem->GetRegistry(i); + Text rgn_name = msn_elem->Region(); + + if (msn_elem->Ships().size() > i) { + msn_ship = msn_elem->Ships()[i]; + sname = msn_ship->Name(); + rnum = msn_ship->RegNum(); + rgn_name = msn_ship->Region(); + } + + Point l2 = msn_elem->Location(); + + if (msn_ship && fabs(msn_ship->Location().x) < 1e9) { + l2 = msn_ship->Location(); + } + else if (i) { + Point offset = RandomPoint(); + offset.z = Random(-1e3, 1e3); + + if (msn_elem->Count() < 5) + offset *= 0.3; + + l2 += offset; + } + + // choose best loadout: + ListIter l = msn_elem->Loadouts(); + while (++l) { + if ((l->GetShip() == i) || (l->GetShip() < 0 && loadout == 0)) { + if (l->GetName().length()) { + ListIter sl = ((ShipDesign*) msn_elem->GetDesign())->loadouts; + while (++sl) { + if (!stricmp(sl->name, l->GetName())) + loadout = sl->load; + } + } + + else { + loadout = l->GetStations(); + } + } + } + + element->SetLoadout(loadout); + + Ship* ship = CreateShip(sname, rnum, + (ShipDesign*) msn_elem->GetDesign(), + rgn_name, l2, + msn_elem->GetIFF(), + msn_elem->CommandAI(), + loadout); + + if (ship) { + double heading = msn_elem->Heading(); + const Skin* skin = msn_elem->GetSkin(); + + if (msn_ship) { + heading = msn_ship->Heading(); + + if (msn_ship->GetSkin()) + skin = msn_ship->GetSkin(); + } + + ship->SetRogue(msn_elem->IsRogue()); + ship->SetInvulnerable(msn_elem->IsInvulnerable()); + ship->SetHeading(0, 0, heading + PI); + ship->SetRespawnCount(respawns); + ship->UseSkin(skin); + + if (!netgame) + ship->SetRespawnLoc(RandomPoint() * 2); + + if (ship->IsStarship()) + ship->SetHelmHeading(heading); + + else if (ship->IsAirborne() && ship->AltitudeAGL() > 25) + ship->SetVelocity(ship->Heading() * 250); + + if (element) + element->AddShip(ship); + + if (hangar) + hangar->FindSlot(ship, squadron, slot, Hangar::ACTIVE); + + if (ship->GetRegion() && msn_elem->Player() == i+1) + ship->GetRegion()->SetPlayerShip(ship); + + if (ship->NumFlightDecks()) { + for (int i = 0; i < ship->NumFlightDecks(); i++) { + FlightDeck* deck = ship->GetFlightDeck(i); + if (deck) + deck->Orient(ship); + } + } + + if (msn_ship) { + ship->SetVelocity(msn_ship->Velocity().OtherHand()); + ship->SetIntegrity((float) msn_ship->Integrity()); + ship->SetRespawnCount(msn_ship->Respawns()); + + if (msn_ship->Ammo()[0] > -10) { + for (int i = 0; i < 64; i++) { + Weapon* w = ship->GetWeaponByIndex(i+1); + if (w) + w->SetAmmo(msn_ship->Ammo()[i]); + else + break; + } + } + + if (msn_ship->Fuel()[0] > -10) { + for (int i = 0; i < 4; i++) { + if (ship->Reactors().size() > i) { + PowerSource* p = ship->Reactors()[i]; + p->SetCapacity(msn_ship->Fuel()[i]); + } + } + } + + if (msn_ship->Decoys() > -10) { + Weapon* w = ship->GetDecoy(); + if (w) + w->SetAmmo(msn_ship->Decoys()); + } + + if (msn_ship->Probes() > -10) { + Weapon* w = ship->GetProbeLauncher(); + if (w) + w->SetAmmo(msn_ship->Probes()); + } + } + + Shield* shield = ship->GetShield(); + + if (shield) { + shield->SetPowerLevel(50); + } + + if (ship->Class() > Ship::FRIGATE) { + ListIter iter = ship->Weapons(); + while (++iter) { + WeaponGroup* weapon = iter.value(); + + // anti-air weapon? + if (weapon->GetDesign()->target_type & Ship::DRONE) { + weapon->SetFiringOrders(Weapon::POINT_DEFENSE); + } + else { + weapon->SetFiringOrders(Weapon::MANUAL); + } + } + } + + if (ship->Class() > Ship::DRONE && ship->Class() < Ship::STATION) { + ShipStats* stats = ShipStats::Find(sname); + if (stats) { + char design[64]; + sprintf(design, "%s %s", ship->Abbreviation(), ship->Design()->display_name); + stats->SetType(design); + stats->SetShipClass(ship->Class()); + stats->SetRole(Mission::RoleName(msn_elem->MissionRole())); + stats->SetIFF(ship->GetIFF()); + stats->SetRegion(msn_elem->Region()); + stats->SetCombatGroup(msn_elem->GetCombatGroup()); + stats->SetCombatUnit(msn_elem->GetCombatUnit()); + stats->SetPlayer(msn_elem->Player() == i+1); + stats->SetElementIndex(ship->GetElementIndex()); + } + } + } // ship + } // count + } + } + } +} + +void +Sim::CopyEvents() +{ + events.destroy(); + + if (mission) { + ListIter iter = mission->GetEvents(); + while (++iter) { + MissionEvent* orig = iter.value(); + MissionEvent* event = new(__FILE__,__LINE__) MissionEvent(*orig); + events.append(event); + } + } +} + +// +--------------------------------------------------------------------+ + +const char* +Sim::FindAvailCallsign(int IFF) +{ + const char* call = "Unidentified"; + + for (int i = 0; i < 32; i++) { + call = Callsign::GetCallsign(IFF); + + if (!FindElement(call)) + break; + } + + return call; +} + +Element* +Sim::CreateElement(const char* callsign, int IFF, int type) +{ + Element* elem = new(__FILE__,__LINE__) Element(callsign, IFF, type); + elements.append(elem); + return elem; +} + +void +Sim::DestroyElement(Element* elem) +{ + if (elements.contains(elem)) + elements.remove(elem); + + delete elem; +} + +Element* +Sim::FindElement(const char* name) +{ + ListIter iter = elements; + + while (++iter) { + Element* elem = iter.value(); + Text ename = elem->Name(); + + if (ename == name) + return elem; + } + + return 0; +} + +// +--------------------------------------------------------------------+ + +int +Sim::GetAssignedElements(Element* elem, List& assigned) +{ + assigned.clear(); + + if (elem) { + for (int i = 0; i < elements.size(); i++) { + Element* e = elements.at(i); + if (!e->IsSquadron() && e->GetAssignment() == elem) + assigned.append(e); + } + } + + return assigned.size(); +} + +// +--------------------------------------------------------------------+ + +Ship* +Sim::CreateShip(const char* name, const char* reg_num, ShipDesign* design, const char* rgn_name, const Point& loc, int IFF, int cmd_ai, const int* loadout) +{ + if (!design) { + Print("WARNING: CreateShip(%s): invalid design\n", name); + return 0; + } + + SimRegion* rgn = FindRegion(rgn_name); + + if (!rgn) { + return 0; + } + + Ship* ship = new(__FILE__,__LINE__) Ship(name, reg_num, design, IFF, cmd_ai, loadout); + ship->MoveTo(loc.OtherHand()); + + if (rgn) { + Print("Inserting Ship(%s) into Region(%s) (%s)\n", ship->Name(), rgn->Name(), FormatGameTime()); + rgn->InsertObject(ship); + + if (ship->IsAirborne() && ship->AltitudeAGL() > 25) + ship->SetVelocity(ship->Heading() * 250); + } + + return ship; +} + +Ship* +Sim::FindShip(const char* name, const char* rgn_name) +{ + Ship* ship = 0; + + if (rgn_name) { + SimRegion* rgn = FindRegion(rgn_name); + if (rgn) + ship = rgn->FindShip(name); + } + + if (!ship) { + ListIter rgn = regions; + while (++rgn && !ship) + ship = rgn->FindShip(name); + } + + return ship; +} + +void +Sim::DestroyShip(Ship* ship) +{ + SimRegion* rgn = ship->GetRegion(); + if (rgn) + rgn->DestroyShip(ship); +} + +void +Sim::NetDockShip(Ship* ship, Ship* carrier, FlightDeck* deck) +{ + SimRegion* rgn = ship->GetRegion(); + if (rgn) + rgn->NetDockShip(ship, carrier, deck); +} + +Ship* +Sim::FindShipByObjID(DWORD objid) +{ + Ship* ship = 0; + + ListIter rgn = regions; + while (++rgn && !ship) + ship = rgn->FindShipByObjID(objid); + + return ship; +} + +Shot* +Sim::FindShotByObjID(DWORD objid) +{ + Shot* shot = 0; + + ListIter rgn = regions; + while (++rgn && !shot) + shot = rgn->FindShotByObjID(objid); + + return shot; +} + +// +--------------------------------------------------------------------+ + +Orbital* +Sim::FindOrbitalBody(const char* name) +{ + Orbital* body = 0; + + if (mission) { + ListIter iter = mission->GetSystemList(); + while (++iter && !body) { + StarSystem* sys = iter.value(); + body = sys->FindOrbital(name); + } + } + + return body; +} + + +// +--------------------------------------------------------------------+ + +Shot* +Sim::CreateShot(const Point& pos, const Camera& shot_cam, WeaponDesign* design, const Ship* ship, SimRegion* rgn) +{ + Shot* shot = 0; + + if (design->drone) + shot = new(__FILE__,__LINE__) Drone(pos, shot_cam, design, ship); + else + shot = new(__FILE__,__LINE__) Shot( pos, shot_cam, design, ship); + + if (rgn) + rgn->InsertObject(shot); + + else if (active_region) + active_region->InsertObject(shot); + + return shot; +} + +// +--------------------------------------------------------------------+ + +Explosion* +Sim::CreateExplosion(const Point& pos, const Point& vel, int type, float exp_scale, float part_scale, SimRegion* rgn, SimObject* source, System* sys) +{ + // don't bother creating explosions that can't be seen: + if (!rgn || !active_region || rgn != active_region) + return 0; + + Explosion* exp = new(__FILE__,__LINE__) Explosion(type, pos, vel, exp_scale, part_scale, rgn, source); + + if (rgn) + rgn->InsertObject(exp); + + else if (active_region) + active_region->InsertObject(exp); + + return exp; +} + +// +--------------------------------------------------------------------+ + +Debris* +Sim::CreateDebris(const Point& pos, const Point& vel, Model* model, double mass, SimRegion* rgn) +{ + Debris* debris = new(__FILE__,__LINE__) Debris(model, pos, vel, mass); + + if (rgn) + rgn->InsertObject(debris); + + else if (active_region) + active_region->InsertObject(debris); + + return debris; +} + +// +--------------------------------------------------------------------+ + +Asteroid* +Sim::CreateAsteroid(const Point& pos, int t, double mass, SimRegion* rgn) +{ + Asteroid* asteroid = new(__FILE__,__LINE__) Asteroid(t, pos, mass); + + if (rgn) + rgn->InsertObject(asteroid); + + else if (active_region) + active_region->InsertObject(asteroid); + + return asteroid; +} + +// +--------------------------------------------------------------------+ + +void +Sim::CreateSplashDamage(Ship* ship) +{ + if (ship && ship->GetRegion() && ship->Design()->splash_radius > 1) { + SimSplash* splash = new(__FILE__,__LINE__) + SimSplash(ship->GetRegion(), + ship->Location(), + ship->Design()->integrity / 4, + ship->Design()->splash_radius); + + splash->owner_name = ship->Name(); + splashlist.append(splash); + } +} + +// +--------------------------------------------------------------------+ + +void +Sim::CreateSplashDamage(Shot* shot) +{ + if (shot && shot->GetRegion()) { + double damage = shot->Damage(); + if (damage < shot->Design()->damage) + damage = shot->Design()->damage; + + SimSplash* splash = new(__FILE__,__LINE__) + SimSplash(shot->GetRegion(), + shot->Location(), + damage, + shot->Design()->lethal_radius); + + if (shot->Owner()) + splash->owner_name = shot->Owner()->Name(); + + splash->missile = shot->IsMissile(); + + splashlist.append(splash); + CreateExplosion(shot->Location(), Point(), Explosion::SHOT_BLAST, 20.0f, 1.0f, shot->GetRegion()); + } +} + +// +--------------------------------------------------------------------+ + +void +Sim::ShowGrid(int show) +{ + Player* player = Player::GetCurrentPlayer(); + + if (player && player->GridMode() == 0) { + show = 0; + grid_shown = false; + } + + ListIter rgn = regions; + while (++rgn) { + rgn->ShowGrid(show); + } + + grid_shown = show?true:false; +} + +bool +Sim::GridShown() const +{ + return grid_shown; +} + +// +--------------------------------------------------------------------+ + +List& +Sim::GetSystemList() +{ + if (mission) + return mission->GetSystemList(); + + static List dummy_system_list; + return dummy_system_list; +} + +// +--------------------------------------------------------------------+ + +void +Sim::NextView() +{ + if (active_region) + active_region->NextView(); +} + +Ship* +Sim::GetPlayerShip() +{ + if (active_region) + return active_region->GetPlayerShip(); + + Starshatter* stars = Starshatter::GetInstance(); + if (stars && stars->InCutscene()) { + Ship* player = 0; + + ListIter rgn = regions; + while (++rgn && !player) { + player = rgn->GetPlayerShip(); + } + + return player; + } + + return 0; +} + +Element* +Sim::GetPlayerElement() +{ + Element* elem = 0; + + for (int i = 0; i < elements.size(); i++) { + Element* e = elements[i]; + + if (e->Player() > 0) + elem = e; + } + + return elem; +} + +bool +Sim::IsSelected(Ship* s) +{ + if (active_region) + return active_region->IsSelected(s); + + return false; +} + +ListIter +Sim::GetSelection() +{ + if (active_region) + return active_region->GetSelection(); + + static List empty; + return empty; +} + +void +Sim::ClearSelection() +{ + if (active_region) + active_region->ClearSelection(); +} + +void +Sim::AddSelection(Ship* s) +{ + if (active_region) + active_region->AddSelection(s); +} + +void +Sim::SetSelection(Ship* newsel) +{ + if (active_region) + active_region->SetSelection(newsel); +} + +// +--------------------------------------------------------------------+ + +void +Sim::SetTestMode(bool t) +{ + test_mode = t; + Ship* pship = GetPlayerShip(); + + if (pship) + if (IsTestMode()) + pship->SetControls(0); + else + pship->SetControls(ctrl); +} + +// +--------------------------------------------------------------------+ + +SimRegion* +Sim::FindRegion(const char* name) +{ + ListIter rgn = regions; + while (++rgn) + if (rgn->name == name) + return rgn.value(); + + return 0; +} + +SimRegion* +Sim::FindRegion(OrbitalRegion* orgn) +{ + ListIter rgn = regions; + while (++rgn) + if (rgn->orbital_region == orgn) + return rgn.value(); + + return 0; +} + +// +--------------------------------------------------------------------+ + +SimRegion* +Sim::FindNearestSpaceRegion(SimObject* object) +{ + return FindNearestRegion(object, REAL_SPACE); +} + +SimRegion* +Sim::FindNearestTerrainRegion(SimObject* object) +{ + return FindNearestRegion(object, AIR_SPACE); +} + +SimRegion* +Sim::FindNearestRegion(SimObject* object, int type) +{ + if (!object) return 0; + + SimRegion* result = 0; + double distance = 1.0e40; + Point objloc = object->Location(); + + objloc = objloc.OtherHand(); + + if (object->GetRegion()) + objloc += object->GetRegion()->Location(); + + ListIter rgn = regions; + while (++rgn) { + if (rgn->Type() == type) { + OrbitalRegion* orgn = rgn->GetOrbitalRegion(); + if (orgn) { + double test = fabs((orgn->Location() - objloc).length()); + if (test < distance) { + result = rgn.value(); + distance = test; + } + } + } + } + + return result; +} + +SimRegion* +Sim::FindNearestSpaceRegion(Orbital* body) +{ + SimRegion* result = 0; + + if (!body) + return result; + + ListIter rgn = regions; + while (++rgn && !result) { + if (rgn->IsOrbital()) { + OrbitalRegion* orgn = rgn->GetOrbitalRegion(); + if (orgn) { + ListIter iter = body->Regions(); + while (++iter) { + if (iter.value() == orgn) + result = rgn.value(); + } + } + } + } + + return result; +} + +// +--------------------------------------------------------------------+ + +bool +Sim::ActivateRegion(SimRegion* rgn) +{ + if (rgn && active_region != rgn && regions.contains(rgn)) { + if (active_region) + active_region->Deactivate(); + + if (!active_region || active_region->System() != rgn->System()) { + if (active_region) + active_region->System()->Deactivate(); + rgn->System()->Activate(scene); + } + + active_region = rgn; + star_system = active_region->System(); + + if (star_system) { + star_system->SetActiveRegion(active_region->orbital_region); + } + else { + ::Print("WARNING: Sim::ActivateRegion() No star system found for rgn '%s'", rgn->Name()); + } + + active_region->Activate(); + return true; + } + + return false; +} + +// +--------------------------------------------------------------------+ + +void +Sim::RequestHyperJump(Ship* obj, SimRegion* rgn, const Point& loc, + int type, Ship* fc1, Ship* fc2) +{ + bool hyperdrive = false; + + if (obj->GetQuantumDrive() && obj->GetQuantumDrive()->Subtype() == QuantumDrive::HYPER) + hyperdrive = true; + + jumplist.append(new(__FILE__,__LINE__) SimHyper(obj, rgn, loc, type, hyperdrive, fc1, fc2)); +} + +// +--------------------------------------------------------------------+ + +void +Sim::ExecFrame(double seconds) +{ + if (first_frame) { + first_frame = false; + netgame = NetGame::Create(); + } + + if (netgame) + netgame->ExecFrame(); + + if (regions.isEmpty()) { + active_region = 0; + rgn_queue.clear(); + jumplist.destroy(); + scene.Collect(); + return; + } + + ListIter elem = elements; + while (++elem) + if (!elem->IsSquadron()) + elem->ExecFrame(seconds); + + ListIter rgn = regions; + while (++rgn) + if (rgn.value() != active_region && rgn->NumShips() && !rgn_queue.contains(rgn.value())) + rgn_queue.append(rgn.value()); + + // execframe for one inactive sim region: + if (rgn_queue.size()) { + SimRegion* exec_rgn = rgn_queue.removeIndex(0); + + while (exec_rgn && (exec_rgn->NumShips() == 0 || exec_rgn == active_region)) + if (rgn_queue.size()) + exec_rgn = rgn_queue.removeIndex(0); + else + exec_rgn = 0; + + if (exec_rgn) + exec_rgn->ExecFrame(seconds); + } + + if (active_region) + active_region->ExecFrame(seconds); + + ExecEvents(seconds); + ResolveHyperList(); + ResolveSplashList(); + + // GC all the dead objects: + scene.Collect(); + + if (!IsTestMode()) { + ListIter e_iter = elements; + while (++e_iter) { + Element* elem = e_iter.value(); + if (!elem->IsSquadron() && elem->IsFinished()) { + finished.append(e_iter.removeItem()); + } + } + } + + // setup music + if (!MusicDirector::IsNoMusic()) { + Starshatter* stars = Starshatter::GetInstance(); + if (stars && stars->GetGameMode() == Starshatter::PLAY_MODE) { + Ship* player_ship = GetPlayerShip(); + if (player_ship) { + int phase = player_ship->GetFlightPhase(); + + if (phase < Ship::ACTIVE) { + MusicDirector::SetMode(MusicDirector::LAUNCH); + } + + else if (phase > Ship::ACTIVE) { + MusicDirector::SetMode(MusicDirector::RECOVERY); + } + + else { + if (player_ship->IsInCombat()) { + MusicDirector::SetMode(MusicDirector::COMBAT); + } + + else { + MusicDirector::SetMode(MusicDirector::FLIGHT); + } + } + } + } + } +} + +void +Sim::ExecEvents(double seconds) +{ + ListIter iter = events; + while (++iter) { + MissionEvent* event = iter.value(); + event->ExecFrame(seconds); + } +} + +void +Sim::ResolveHyperList() +{ + // resolve the hyper space transitions: + if (jumplist.size()) { + Ship* pship = GetPlayerShip(); + + ListIter j_iter = jumplist; + while (++j_iter) { + SimHyper* jump = j_iter.value(); + Ship* jumpship = jump->ship; + + if (jumpship) { + SimRegion* dest = jump->rgn; + + if (!dest) + dest = FindNearestSpaceRegion(jumpship); + + if (dest) { + // bring along fighters on deck: + ListIter deck = jumpship->FlightDecks(); + while (++deck) { + for (int i = 0; i < deck->NumSlots(); i++) { + Ship* s = deck->GetShip(i); + + if (s) { + dest->InsertObject(s); + s->ClearTrack(); + } + } + } + + if (jump->type == 0 && !jump->hyperdrive) { + // bring along nearby ships: + // have to do it in two parts, because inserting the ships + // into the destination corrupts the iter over the current + // region's list of ships... + + // part one: gather the ships that will be jumping: + List riders; + ListIter neighbor = jumpship->GetRegion()->Ships(); + while (++neighbor) { + if (neighbor->IsDropship()) { + Ship* s = neighbor.value(); + if (s == jumpship) continue; + + Point delta = s->Location() - jumpship->Location(); + + if (delta.length() < 5e3) { + riders.append(s); + } + } + } + + // part two: now transfer the list to the destination: + for (int i = 0; i < riders.size(); i++) { + Ship* s = riders[i]; + Point delta = s->Location() - jumpship->Location(); + dest->InsertObject(s); + s->MoveTo(jump->loc.OtherHand() + delta); + s->ClearTrack(); + + if (jump->fc_dst) { + double r = jump->fc_dst->Roll(); + double p = jump->fc_dst->Pitch(); + double w = jump->fc_dst->Yaw(); + + s->SetAbsoluteOrientation(r, p, w); + s->SetVelocity(jump->fc_dst->Heading() * 500); + } + + ProcessEventTrigger(MissionEvent::TRIGGER_JUMP, 0, s->Name()); + } + } + + // now it is safe to move the main jump ship: + dest->InsertObject(jumpship); + jumpship->MoveTo(jump->loc.OtherHand()); + jumpship->ClearTrack(); + + ProcessEventTrigger(MissionEvent::TRIGGER_JUMP, 0, jumpship->Name()); + NetUtil::SendObjHyper(jumpship, dest->Name(), jump->loc, jump->fc_src, jump->fc_dst, jump->type); + + // if using farcaster: + if (jump->fc_src) { + ::Print("Ship '%s' farcast to '%s'\n", jumpship->Name(), dest->Name()); + CreateExplosion(jumpship->Location(), Point(0,0,0), Explosion::QUANTUM_FLASH, 1.0f, 0, dest); + + if (jump->fc_dst) { + double r = jump->fc_dst->Roll(); + double p = jump->fc_dst->Pitch(); + double w = jump->fc_dst->Yaw(); + + jumpship->SetAbsoluteOrientation(r, p, w); + jumpship->SetVelocity(jump->fc_dst->Heading() * 500); + } + + jumpship->SetHelmHeading(jumpship->CompassHeading()); + jumpship->SetHelmPitch(0); + } + + // break orbit: + else if (jump->type == Ship::TRANSITION_DROP_ORBIT) { + ::Print("Ship '%s' broke orbit to '%s'\n", jumpship->Name(), dest->Name()); + jumpship->SetAbsoluteOrientation(0,PI/4,0); + jumpship->SetVelocity(jumpship->Heading() * 1.0e3); + } + + // make orbit: + else if (jump->type == Ship::TRANSITION_MAKE_ORBIT) { + ::Print("Ship '%s' achieved orbit '%s'\n", jumpship->Name(), dest->Name()); + jumpship->LookAt(Point(0,0,0)); + jumpship->SetVelocity(jumpship->Heading() * 500.0); + } + + // hyper jump: + else { + ::Print("Ship '%s' quantum to '%s'\n", jumpship->Name(), dest->Name()); + + if (jump->hyperdrive) + CreateExplosion(jumpship->Location(), Point(0,0,0), Explosion::HYPER_FLASH, 1, 1, dest); + else + CreateExplosion(jumpship->Location(), Point(0,0,0), Explosion::QUANTUM_FLASH, 1, 0, dest); + + jumpship->LookAt(Point(0,0,0)); + jumpship->SetVelocity(jumpship->Heading() * 500.0); + jumpship->SetHelmHeading(jumpship->CompassHeading()); + jumpship->SetHelmPitch(0); + } + } + + else if (regions.size() > 1) { + ::Print("Warning: Unusual jump request for ship '%s'\n", jumpship->Name()); + regions[1]->InsertObject(jumpship); + } + + Sensor* sensor = jumpship->GetSensor(); + if (sensor) + sensor->ClearAllContacts(); + } + } + + jumplist.destroy(); + + if (pship && pship->GetRegion()) { + if (active_region != pship->GetRegion()) { + pship->GetRegion()->SetPlayerShip(pship); + } + } + } +} + +void +Sim::ResolveSplashList() +{ + if (splashlist.size()) { + ListIter iter = splashlist; + while (++iter) { + SimSplash* splash = iter.value(); + + if (!splash->rgn) + continue; + + // damage ships: + ListIter s_iter = splash->rgn->Ships(); + while (++s_iter) { + Ship* ship = s_iter.value(); + + double distance = (ship->Location() - splash->loc).length(); + + if (distance > 1 && distance < splash->range) { + double damage = splash->damage * (1 - distance/splash->range); + if (!NetGame::IsNetGameClient()) { + ship->InflictDamage(damage); + } + + int ship_destroyed = (!ship->InTransition() && ship->Integrity() < 1.0f); + + // then delete the ship: + if (ship_destroyed) { + NetUtil::SendObjKill(ship, 0, NetObjKill::KILL_MISC); + Print(" %s Killed %s (%s)\n", (const char*) splash->owner_name, ship->Name(), FormatGameTime()); + + // record the kill + ShipStats* killer = ShipStats::Find(splash->owner_name); + if (killer) { + if (splash->missile) + killer->AddEvent(SimEvent::MISSILE_KILL, ship->Name()); + else + killer->AddEvent(SimEvent::GUNS_KILL, ship->Name()); + } + + Ship* owner = FindShip(splash->owner_name, splash->rgn->Name()); + if (owner && owner->GetIFF() != ship->GetIFF()) { + if (ship->GetIFF() > 0 || owner->GetIFF() > 1) { + killer->AddPoints(ship->Value()); + + Element* elem = owner->GetElement(); + if (elem) { + if (owner->GetElementIndex() > 1) { + Ship* s = elem->GetShip(1); + + if (s) { + ShipStats* cmdr_stats = ShipStats::Find(s->Name()); + if (cmdr_stats) { + cmdr_stats->AddCommandPoints(ship->Value()/2); + } + } + } + + Element* cmdr = elem->GetCommander(); + if (cmdr) { + Ship* s = cmdr->GetShip(1); + + if (s) { + ShipStats* cmdr_stats = ShipStats::Find(s->Name()); + if (cmdr_stats) { + cmdr_stats->AddCommandPoints(ship->Value()/2); + } + } + } + } + } + } + + ShipStats* killee = ShipStats::Find(ship->Name()); + if (killee) + killee->AddEvent(SimEvent::DESTROYED, splash->owner_name); + + ship->DeathSpiral(); + } + } + } + + // damage drones: + ListIter drone_iter = splash->rgn->Drones(); + while (++drone_iter) { + Drone* drone = drone_iter.value(); + + double distance = (drone->Location() - splash->loc).length(); + + if (distance > 1 && distance < splash->range) { + double damage = splash->damage * (1 - distance/splash->range); + drone->InflictDamage(damage); + + int destroyed = (drone->Integrity() < 1.0f); + + // then mark the drone for deletion: + if (destroyed) { + NetUtil::SendWepDestroy(drone); + sim->CreateExplosion(drone->Location(), drone->Velocity(), 21 /* was LARGE_EXP */, 1.0f, 1.0f, splash->rgn); + drone->SetLife(0); + } + } + } + } + + splashlist.destroy(); + } +} + +// +--------------------------------------------------------------------+ + +void +Sim::ProcessEventTrigger(int type, int event_id, const char* ship, int param) +{ + Text ship_name = ship; + + ListIter iter = events; + while (++iter) { + MissionEvent* event = iter.value(); + + if (event->IsPending() && event->Trigger() == type) { + switch (type) { + case MissionEvent::TRIGGER_DAMAGE: + case MissionEvent::TRIGGER_DESTROYED: + case MissionEvent::TRIGGER_JUMP: + case MissionEvent::TRIGGER_LAUNCH: + case MissionEvent::TRIGGER_DOCK: + case MissionEvent::TRIGGER_TARGET: + if (event->TriggerParam() <= param) { + if (ship_name.indexOf(event->TriggerShip()) == 0) + event->Activate(); + } + break; + + case MissionEvent::TRIGGER_NAVPT: + if (event->TriggerParam() == param) { + if (ship_name.indexOf(event->TriggerShip()) == 0) + event->Activate(); + } + break; + + case MissionEvent::TRIGGER_EVENT: + case MissionEvent::TRIGGER_SKIPPED: + if (event->TriggerParam() == event_id) + event->Activate(); + break; + } + } + } +} + +double +Sim::MissionClock() const +{ + return (Game::GameTime() - start_time) / 1000.0; +} + +// +--------------------------------------------------------------------+ + +void +Sim::SkipCutscene() +{ + Starshatter* stars = Starshatter::GetInstance(); + if (stars && stars->InCutscene()) { + ListIter iter = events; + bool end = false; + double end_time = 0; + + while (++iter && !end) { + MissionEvent* event = iter.value(); + + if (event->IsPending() || event->IsActive()) { + if (event->Event() == MissionEvent::END_SCENE || + event->Event() == MissionEvent::END_MISSION) { + end = true; + end_time = event->Time(); + } + + if (event->Event() == MissionEvent::FIRE_WEAPON) { + event->Skip(); + } + + else { + event->Activate(); + event->Execute(true); + } + } + } + + double skip_time = end_time - MissionClock(); + if (skip_time > 0) { + Game::SkipGameTime(skip_time); + } + } +} + +// +--------------------------------------------------------------------+ + +void +Sim::ResolveTimeSkip(double seconds) +{ + double skipped = 0; + + // allow elements to process hold time, and release as needed: + ListIter elem = elements; + while (++elem) + elem->ExecFrame(seconds); + + // step through the skip, ten seconds at a time: + if (active_region) { + double total_skip = seconds; + double frame_skip = 10; + Ship* player = GetPlayerShip(); + + while (total_skip > frame_skip) { + if (active_region->CanTimeSkip()) { + active_region->ResolveTimeSkip(frame_skip); + total_skip -= frame_skip; + skipped += frame_skip; + } + // break out early if player runs into bad guys... + else { + total_skip = 0; + } + } + + if (total_skip > 0) + active_region->ResolveTimeSkip(total_skip); + skipped += total_skip; + } + + // give player control after time skip: + Ship* player_ship = GetPlayerShip(); + if (player_ship) { + player_ship->SetAutoNav(false); + player_ship->SetThrottle(75); + + HUDView* hud = HUDView::GetInstance(); + if (hud) + hud->SetHUDMode(HUDView::HUD_MODE_TAC); + + if (IsTestMode()) + player_ship->SetControls(0); + } + + Game::SkipGameTime(skipped); + CameraDirector::SetCameraMode(CameraDirector::MODE_COCKPIT); +} + +// +--------------------------------------------------------------------+ + +ListIter +Sim::GetMissionElements() +{ + mission_elements.destroy(); + + ListIter iter = elements; + while (++iter) { + Element* elem = iter.value(); + + int num_live_ships = 0; + + for (int i = 0; i < elem->NumShips(); i++) { + Ship* s = elem->GetShip(i+1); + + if (s && !s->IsDying() && !s->IsDead()) + num_live_ships++; + } + + if (elem->IsSquadron() || num_live_ships > 0) { + MissionElement* msn_elem = CreateMissionElement(elem); + + if (msn_elem) + mission_elements.append(msn_elem); + } + } + + return mission_elements; +} + +MissionElement* +Sim::CreateMissionElement(Element* elem) +{ + MissionElement* msn_elem = 0; + + if (elem->IsSquadron()) { + if (!elem->GetCarrier() || elem->GetCarrier()->Integrity() < 1) + return msn_elem; + } + + if (elem && !elem->IsNetObserver()) { + msn_elem = new(__FILE__,__LINE__) MissionElement; + + msn_elem->SetName(elem->Name()); + msn_elem->SetIFF(elem->GetIFF()); + msn_elem->SetMissionRole(elem->Type()); + + if (elem->IsSquadron() && elem->GetCarrier()) { + Ship* carrier = elem->GetCarrier(); + + msn_elem->SetCarrier(carrier->Name()); + msn_elem->SetCount(elem->GetCount()); + msn_elem->SetLocation(carrier->Location().OtherHand()); + + if (carrier->GetRegion()) + msn_elem->SetRegion(carrier->GetRegion()->Name()); + + int squadron_index = 0; + Hangar* hangar = FindSquadron(elem->Name(), squadron_index); + + if (hangar) { + msn_elem->SetDeadCount(hangar->NumShipsDead(squadron_index)); + msn_elem->SetMaintCount(hangar->NumShipsMaint(squadron_index)); + + const ShipDesign* design = hangar->SquadronDesign(squadron_index); + msn_elem->SetDesign(design); + + Text design_path = design->path_name; + design_path.setSensitive(false); + + if (design_path.indexOf("/Mods/Ships") == 0) { + design_path = design_path.substring(11, 1000); + msn_elem->SetPath(design_path); + } + } + } + + else { + msn_elem->SetSquadron(elem->GetSquadron()); + msn_elem->SetCount(elem->NumShips()); + } + + if (elem->GetCommander()) + msn_elem->SetCommander(elem->GetCommander()->Name()); + + msn_elem->SetCombatGroup(elem->GetCombatGroup()); + msn_elem->SetCombatUnit(elem->GetCombatUnit()); + + Ship* ship = elem->GetShip(1); + if (ship) { + if (ship->GetRegion()) + msn_elem->SetRegion(ship->GetRegion()->Name()); + + msn_elem->SetLocation(ship->Location().OtherHand()); + msn_elem->SetDesign(ship->Design()); + + msn_elem->SetPlayer(elem->Player()); + msn_elem->SetCommandAI(elem->GetCommandAILevel()); + msn_elem->SetHoldTime((int) elem->GetHoldTime()); + msn_elem->SetZoneLock(elem->GetZoneLock()); + msn_elem->SetHeading(ship->CompassHeading()); + + msn_elem->SetPlayable(elem->IsPlayable()); + msn_elem->SetRogue(elem->IsRogue()); + msn_elem->SetIntelLevel(elem->IntelLevel()); + + Text design_path = ship->Design()->path_name; + design_path.setSensitive(false); + + if (design_path.indexOf("/Mods/Ships") == 0) { + design_path = design_path.substring(11, 1000); + msn_elem->SetPath(design_path); + } + + msn_elem->SetRespawnCount(ship->RespawnCount()); + } + + MissionLoad* loadout = new(__FILE__,__LINE__) MissionLoad; + CopyMemory(loadout->GetStations(), elem->Loadout(), 16 * sizeof(int)); + + msn_elem->Loadouts().append(loadout); + + int num_obj = elem->NumObjectives(); + for (int i = 0; i < num_obj; i++) { + Instruction* o = elem->GetObjective(i); + Instruction* instr = 0; + + instr = new(__FILE__,__LINE__) Instruction(*o); + + msn_elem->AddObjective(instr); + } + + int num_inst = elem->NumInstructions(); + for (i = 0; i < num_inst; i++) { + Text instr = elem->GetInstruction(i); + msn_elem->AddInstruction(instr); + } + + ListIter nav_iter = elem->GetFlightPlan(); + while (++nav_iter) { + Instruction* nav = nav_iter.value(); + Instruction* npt = new(__FILE__,__LINE__) + Instruction(nav->RegionName(), nav->Location(), nav->Action()); + + npt->SetFormation(nav->Formation()); + npt->SetSpeed(nav->Speed()); + npt->SetTarget(nav->TargetName()); + npt->SetHoldTime(nav->HoldTime()); + npt->SetFarcast(nav->Farcast()); + npt->SetStatus(nav->Status()); + + msn_elem->AddNavPoint(npt); + } + + for (i = 0; i < elem->NumShips(); i++) { + ship = elem->GetShip(i+1); + + if (ship) { + MissionShip* s = new(__FILE__,__LINE__) MissionShip; + + s->SetName(ship->Name()); + s->SetRegNum(ship->Registry()); + s->SetRegion(ship->GetRegion()->Name()); + s->SetLocation(ship->Location().OtherHand()); + s->SetVelocity(ship->Velocity().OtherHand()); + + s->SetRespawns(ship->RespawnCount()); + s->SetHeading(ship->CompassHeading()); + s->SetIntegrity(ship->Integrity()); + + if (ship->GetDecoy()) + s->SetDecoys(ship->GetDecoy()->Ammo()); + + if (ship->GetProbeLauncher()) + s->SetProbes(ship->GetProbeLauncher()->Ammo()); + + int n; + int ammo[16]; + int fuel[4]; + + for (n = 0; n < 16; n++) { + Weapon* w = ship->GetWeaponByIndex(n+1); + + if (w) + ammo[n] = w->Ammo(); + else + ammo[n] = -10; + } + + for (n = 0; n < 4; n++) { + if (ship->Reactors().size() > n) + fuel[n] = ship->Reactors()[n]->Charge(); + else + fuel[n] = -10; + } + + s->SetAmmo(ammo); + s->SetFuel(fuel); + + msn_elem->Ships().append(s); + } + } + } + + return msn_elem; +} + +Hangar* +Sim::FindSquadron(const char* name, int& index) +{ + Hangar* hangar = 0; + + ListIter iter = regions; + while (++iter && !hangar) { + SimRegion* rgn = iter.value(); + + ListIter s_iter = rgn->Carriers(); + while (++s_iter && !hangar) { + Ship* carrier = s_iter.value(); + Hangar* h = carrier->GetHangar(); + + for (int i = 0; i < h->NumSquadrons() && !hangar; i++) { + if (h->SquadronName(i) == name) { + hangar = h; + index = i; + } + } + } + } + + return hangar; +} + +// +===================================================================-+ + +SimRegion::SimRegion(Sim* s, const char* n, int t) + : sim(s), name(n), type(t), orbital_region(0), star_system(0) + , player_ship(0), grid(0), active(false), current_view(0), sim_time(0) + , ai_index(0), terrain(0) +{ + if (sim) { + star_system = sim->GetStarSystem(); + } +} + +SimRegion::SimRegion(Sim* s, OrbitalRegion* r) + : sim(s), orbital_region(r), type(REAL_SPACE), star_system(0) + , player_ship(0), grid(0), active(false), current_view(0), sim_time(0) + , ai_index(0), terrain(0) +{ + if (r) { + star_system = r->System(); + } + + if (orbital_region) { + name = orbital_region->Name(); + grid = new(__FILE__,__LINE__) Grid((int) orbital_region->Radius(), + (int) orbital_region->GridSpace()); + + + if (orbital_region->Type() == Orbital::TERRAIN) { + TerrainRegion* trgn = (TerrainRegion*) orbital_region; + terrain = new(__FILE__,__LINE__) Terrain(trgn); + + type = AIR_SPACE; + } + + else if (orbital_region->Asteroids() > 0) { + int asteroids = orbital_region->Asteroids(); + + for (int i = 0; i < asteroids; i++) { + Point init_loc((rand()-16384.0f) * 30, + (rand()-16384.0f) * 3, + (rand()-16384.0f) * 30); + sim->CreateAsteroid(init_loc, i, Random(1e7, 1e8), this); + } + } + } + else { + name = Game::GetText("Unknown"); + } +} + +SimRegion::~SimRegion() +{ + GRAPHIC_DESTROY(grid); + delete terrain; + explosions.destroy(); + shots.destroy(); + ships.destroy(); + debris.destroy(); + asteroids.destroy(); + dead_ships.destroy(); + + for (int i = 0; i < 5; i++) + track_database[i].destroy(); +} + +int +SimRegion::operator < (const SimRegion& r) const +{ + return (orbital_region && r.orbital_region && *orbital_region < *r.orbital_region); +} + +int +SimRegion::operator <= (const SimRegion& r) const +{ + return (orbital_region && r.orbital_region && *orbital_region <= *r.orbital_region); +} + +// +--------------------------------------------------------------------+ + +void +SimRegion::SetPlayerShip(Ship* ship) +{ + // there can only be a player ship when playing the game locally + if (Starshatter::GetInstance()) { + int player_index = ships.index(ship); + + if (player_index >= 0) { + if (sim->GetActiveRegion() != this) + sim->ActivateRegion(this); + + AttachPlayerShip(player_index); + } + + else { + Print("SimRegion %s could not set player ship '%s' - not in region\n", + name.data(), ship ? ship->Name() : "(null)"); + } + } + + // if this is a stand-alone server, set player ship to null + else { + if (player_ship) + player_ship->SetControls(0); + + current_view = -1; + player_ship = 0; + } +} + +void +SimRegion::AttachPlayerShip(int index) +{ + if (player_ship) + player_ship->SetControls(0); + + current_view = index; + player_ship = ships[current_view]; + + CameraDirector* cam_dir = CameraDirector::GetInstance(); + if (cam_dir) + cam_dir->SetShip(player_ship); + + if (sim->dust) + sim->dust->Reset(player_ship->Location()); + + if (!sim->IsTestMode()) + player_ship->SetControls(sim->ctrl); + + MouseController* mouse_con = MouseController::GetInstance(); + if (mouse_con) + mouse_con->SetActive(false); +} + +void +SimRegion::NextView() +{ + if (ships.size()) { + int original_view = current_view; + + do { + current_view++; + if (current_view >= ships.size()) { + current_view = 0; + } + } + while (ships[current_view]->Life() == 0 && current_view != original_view); + + if (current_view != original_view) { + ClearSelection(); + + if (!sim->IsTestMode()) + player_ship->SetControls(0); + + if (player_ship->Rep()) + player_ship->Rep()->Show(); + + AttachPlayerShip(current_view); + } + } +} + +bool +SimRegion::IsSelected(Ship* s) +{ + return selection.contains(s); +} + +ListIter +SimRegion::GetSelection() +{ + return selection; +} + +void +SimRegion::SetSelection(Ship* newsel) +{ + selection.clear(); + selection.append(newsel); +} + +void +SimRegion::ClearSelection() +{ + selection.clear(); +} + +void +SimRegion::AddSelection(Ship* newsel) +{ + if (!newsel || + newsel->GetFlightPhase() < Ship::ACTIVE || + newsel->GetFlightPhase() >= Ship::RECOVERY) + return; + + if (!selection.contains(newsel)) + selection.append(newsel); +} + +// +--------------------------------------------------------------------+ + +void +SimRegion::Activate() +{ + if (!sim) return; + + ListIter ship = ships; + while (++ship) + ship->Activate(sim->scene); + + ListIter shot = shots; + while (++shot) + shot->Activate(sim->scene); + + ListIter exp = explosions; + while (++exp) + exp->Activate(sim->scene); + + ListIter deb = debris; + while (++deb) + deb->Activate(sim->scene); + + ListIter a = asteroids; + while (++a) + a->Activate(sim->scene); + + if (grid) + sim->scene.AddGraphic(grid); + + if (terrain) + terrain->Activate(sim->scene); + + player_ship = 0; + active = true; +} + +// +--------------------------------------------------------------------+ + +void +SimRegion::Deactivate() +{ + if (!sim) return; + + ListIter ship = ships; + while (++ship) + ship->Deactivate(sim->scene); + + ListIter shot = shots; + while (++shot) + shot->Deactivate(sim->scene); + + ListIter exp = explosions; + while (++exp) + exp->Deactivate(sim->scene); + + ListIter deb = debris; + while (++deb) + deb->Deactivate(sim->scene); + + ListIter a = asteroids; + while (++a) + a->Deactivate(sim->scene); + + if (grid) + sim->scene.DelGraphic(grid); + + if (terrain) + terrain->Deactivate(sim->scene); + + player_ship = 0; + active = false; + + for (int i = 0; i < 5; i++) + track_database[i].destroy(); +} + +// +--------------------------------------------------------------------+ + +void +SimRegion::ExecFrame(double secs) +{ + if (!sim) return; + + double seconds = secs; + + // DON'T REALLY KNOW WHAT PURPOSE THIS SERVES.... + if (!active) { + double max_frame = 3 * Game::GetMaxFrameLength(); + long new_time = Game::GameTime(); + double delta = new_time - sim_time; + seconds = delta / 1000.0; + + if (seconds > max_frame) + seconds = max_frame; + } + + sim_time = Game::GameTime(); + + if (orbital_region) + location = orbital_region->Location(); + + CameraDirector* cam_dir = CameraDirector::GetInstance(); + + Point ref; + + if (active && cam_dir) { + ref = cam_dir->GetCamera()->Pos(); + UpdateSky(seconds, ref); + } + + if (terrain) + terrain->ExecFrame(seconds); + + UpdateTracks(seconds); + UpdateShips(seconds); + UpdateShots(seconds); + UpdateExplosions(seconds); + + if (!Game::Paused()) { + DamageShips(); + DockShips(); + + if (active) { + CollideShips(); + CrashShips(); + } + + DestroyShips(); + } + + if (active && cam_dir && player_ship) { + Sound::SetListener(*(cam_dir->GetCamera()), player_ship->Velocity()); + } +} + +// +--------------------------------------------------------------------+ + +void +SimRegion::ShowGrid(int show) +{ + if (grid) { + if (show) + grid->Show(); + else + grid->Hide(); + } +} + +// +--------------------------------------------------------------------+ + +void +SimRegion::UpdateSky(double seconds, const Point& ref) +{ + Dust* dust = sim->dust; + + if (dust) { + if (orbital_region && orbital_region->Type() == Orbital::TERRAIN) { + dust->Hide(); + } + else { + dust->Show(); + + dust->ExecFrame(seconds, ref); + + if (player_ship && dust->Hidden()) { + dust->Reset(player_ship->Location()); + dust->Show(); + } + } + } + + ListIter a = asteroids; + while (++a) { + a->ExecFrame(seconds); + } +} + +// +--------------------------------------------------------------------+ + +void +SimRegion::UpdateShips(double seconds) +{ + int ship_index = 0; + if (ai_index > ships.size()) + ai_index = 0; + + ListIter ship_iter = ships; + Ship* ship = 0; + + while (++ship_iter) { + ship = ship_iter.value(); + + if (ship_index == ai_index || ship == player_ship) + ship->SetAIMode(2); + else + ship->SetAIMode(1); + + ship->ExecFrame(seconds); + ship_index++; + } + + ai_index++; +} + +// +--------------------------------------------------------------------+ + +void +SimRegion::UpdateShots(double seconds) +{ + ListIter shot_iter = shots; + while (++shot_iter) { + Shot* shot = shot_iter.value(); + shot->ExecFrame(seconds); + + if (shot->Design()->flak) { + SeekerAI* seeker = (SeekerAI*) shot->GetDirector(); + + if (shot->Life() < 0.02 || seeker && seeker->Overshot()) { + shot->SetFuse(0.001); // set lifetime to ~zero + sim->CreateSplashDamage(shot); + } + } + + if (shot->Life() < 0.01) { // died of old age + NetUtil::SendWepDestroy(shot); + + if (shot->IsDrone()) + drones.remove((Drone*) shot); + + shot_iter.removeItem(); + delete shot; + shot = 0; + } + } +} + +// +--------------------------------------------------------------------+ + +void +SimRegion::UpdateExplosions(double seconds) +{ + ListIter exp_iter = explosions; + while (++exp_iter) { + Explosion* exp = exp_iter.value(); + exp->ExecFrame(seconds); + + if (exp->Life() < 0.01) { // died of old age + exp_iter.removeItem(); + delete exp; + } + } + + ListIter debris_iter = debris; + while (++debris_iter) { + Debris* d = debris_iter.value(); + d->ExecFrame(seconds); + + if (d->Life() < 0.01) { // died of old age + debris_iter.removeItem(); + delete d; + } + } +} + +// +--------------------------------------------------------------------+ +// Check for collisions between ships and shots, and apply damage. +// Also look for damage to drones and debris. + +void +SimRegion::DamageShips() +{ + if (ships.size() == 0 || shots.size() == 0) + return; + + Point impact; + + // FOR EACH SHOT IN THE REGION: + ListIter shot_iter = shots; + while (++shot_iter) { + Shot* shot = shot_iter.value(); + const Ship* owner = shot->Owner(); + const char* owner_name; + + if (owner) + owner_name = owner->Name(); + else + owner_name = "[KIA]"; + + // CHECK FOR COLLISION WITH A SHIP: + ListIter ship_iter = ships; + while (shot && ++ship_iter) { + Ship* ship = ship_iter.value(); + int hit = ship->HitBy(shot, impact); + + if (hit) { + // recon imager: + if (shot->Damage() < 0) { + ShipStats* shooter = ShipStats::Find(owner_name); + if (shooter) { + shooter->AddEvent(SimEvent::SCAN_TARGET, ship->Name()); + } + } + + // live round: + else if (shot->Damage() > 0) { + int ship_destroyed = (!ship->InTransition() && ship->Integrity() < 1.0f); + + // then delete the ship: + if (ship_destroyed) { + NetUtil::SendObjKill(ship, owner, shot->IsMissile() ? NetObjKill::KILL_SECONDARY : NetObjKill::KILL_PRIMARY); + + Print(" %s Killed %s (%s)\n", owner_name, ship->Name(), FormatGameTime()); + + // alert the killer + Director* director = owner->GetDirector(); + if (director && director->Type() > SteerAI::SEEKER && director->Type() < SteerAI::GROUND) { + ShipAI* shipAI = (ShipAI*) director; + shipAI->Splash(ship); + } + + // record the kill + ShipStats* killer = ShipStats::Find(owner_name); + if (killer) { + if (shot->IsMissile()) + killer->AddEvent(SimEvent::MISSILE_KILL, ship->Name()); + else + killer->AddEvent(SimEvent::GUNS_KILL, ship->Name()); + } + + if (owner && owner->GetIFF() != ship->GetIFF()) { + if (ship->GetIFF() > 0 || owner->GetIFF() > 1) { + killer->AddPoints(ship->Value()); + + Element* elem = owner->GetElement(); + if (elem) { + if (owner->GetElementIndex() > 1) { + Ship* s = elem->GetShip(1); + + if (s) { + ShipStats* cmdr_stats = ShipStats::Find(s->Name()); + if (cmdr_stats) { + cmdr_stats->AddCommandPoints(ship->Value()/2); + } + } + } + + Element* cmdr = elem->GetCommander(); + if (cmdr) { + Ship* s = cmdr->GetShip(1); + + if (s) { + ShipStats* cmdr_stats = ShipStats::Find(s->Name()); + if (cmdr_stats) { + cmdr_stats->AddCommandPoints(ship->Value()/2); + } + } + } + } + } + } + + ShipStats* killee = ShipStats::Find(ship->Name()); + if (killee) + killee->AddEvent(SimEvent::DESTROYED, owner_name); + + ship->DeathSpiral(); + } + } + + // finally, consume the shot: + if (!shot->IsBeam()) { + if (owner) { + ShipStats* stats = ShipStats::Find(owner_name); + if (shot->Design()->primary) + stats->AddGunHit(); + else if (shot->Damage() > 0) + stats->AddMissileHit(); + } + + NetUtil::SendWepDestroy(shot); + + if (shot->IsDrone()) + drones.remove((Drone*) shot); + + shot_iter.removeItem(); + delete shot; + shot = 0; + } + else if (!shot->HitTarget()) { + shot->SetHitTarget(true); + + if (owner) { + ShipStats* stats = ShipStats::Find(owner_name); + if (shot->Design()->primary) + stats->AddGunHit(); + } + } + } + } + + // CHECK FOR COLLISION WITH A DRONE: + if (shot && shot->Design()->target_type & Ship::DRONE) { + ListIter drone_iter = drones; + while (shot && ++drone_iter) { + Drone* d = drone_iter.value(); + + if (d == shot || d->Owner() == owner) + continue; + + int hit = d->HitBy(shot, impact); + if (hit) { + int destroyed = (d->Integrity() < 1.0f); + + // then mark the drone for deletion: + if (destroyed) { + NetUtil::SendWepDestroy(d); + sim->CreateExplosion(d->Location(), d->Velocity(), 21, 1.0f, 1.0f, this); + d->SetLife(0); + } + + // finally, consume the shot: + if (!shot->IsBeam()) { + if (owner) { + ShipStats* stats = ShipStats::Find(owner_name); + if (shot->Design()->primary) + stats->AddGunHit(); + else + stats->AddMissileHit(); + } + + NetUtil::SendWepDestroy(shot); + + if (shot->IsDrone()) + drones.remove((Drone*) shot); + + shot_iter.removeItem(); + delete shot; + shot = 0; + } + } + } + } + + // CHECK FOR COLLISION WITH DEBRIS: + ListIter debris_iter = debris; + while (shot && ++debris_iter) { + Debris* d = debris_iter.value(); + + if (d->Radius() < 50) + continue; + + int hit = d->HitBy(shot, impact); + if (hit) { + int destroyed = (d->Integrity() < 1.0f); + + // then delete the debris: + if (destroyed) { + sim->CreateExplosion(d->Location(), d->Velocity(), Explosion::LARGE_EXPLOSION, 1.0f, 1.0f, this); + debris_iter.removeItem(); + delete d; + } + + // finally, consume the shot: + if (!shot->IsBeam()) { + NetUtil::SendWepDestroy(shot); + if (shot->IsDrone()) + drones.remove((Drone*) shot); + + shot_iter.removeItem(); + delete shot; + shot = 0; + } + } + } + + // CHECK FOR COLLISION WITH ASTEROIDS: + ListIter a_iter = asteroids; + while (shot && ++a_iter) { + Asteroid* a = a_iter.value(); + + int hit = a->HitBy(shot, impact); + if (hit) { + if (!shot->IsBeam()) { + if (shot->IsDrone()) + drones.remove((Drone*) shot); + + shot_iter.removeItem(); + delete shot; + shot = 0; + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +SimRegion::CollideShips() +{ + if (ships.size() < 2 && debris.size() < 1) + return; + + List kill_list; + + int s_index = 0; + + ListIter ship_iter = ships; + while (++ship_iter) { + Ship* ship = ship_iter.value(); + + if (ship->InTransition() || + ship->GetFlightPhase() < Ship::ACTIVE || + ship->MissionClock() < 10000 || + ship->IsNetObserver()) + continue; + + int t_index = 0; + ListIter targ_iter = ships; + while (++targ_iter) { + Ship* targ = targ_iter.value(); + + if (t_index++ <= s_index) continue; + + if (targ == ship) continue; + + if (targ->InTransition() || + targ->GetFlightPhase() < Ship::ACTIVE || + targ->MissionClock() < 10000 || + targ->IsNetObserver()) + continue; + + // ignore AI fighter collisions: + if (ship->IsDropship() && + ship != player_ship && + targ->IsDropship() && + targ != player_ship) + continue; + + // don't collide with own runway! + if (ship->IsAirborne() && ship->GetCarrier() == targ) + continue; + if (targ->IsAirborne() && targ->GetCarrier() == ship) + continue; + + // impact: + if (ship->CollidesWith(*targ)) { + Vec3 tv1 = targ->Velocity(); + Vec3 sv1 = ship->Velocity(); + + Physical::SemiElasticCollision(*ship, *targ); + + Vec3 tv2 = targ->Velocity(); + Vec3 sv2 = ship->Velocity(); + + double dvs = (sv2-sv1).length(); + double dvt = (tv2-tv1).length(); + + if (dvs > 20) dvs *= dvs; + if (dvt > 20) dvt *= dvt; + + if (!NetGame::IsNetGameClient()) { + double old_integrity = ship->Integrity(); + ship->InflictDamage(dvs); + double hull_damage = old_integrity - ship->Integrity(); + NetUtil::SendObjDamage(ship, hull_damage); + + old_integrity = targ->Integrity(); + targ->InflictDamage(dvt); + hull_damage = old_integrity - targ->Integrity(); + NetUtil::SendObjDamage(targ, hull_damage); + } + + // then delete the ship: + if (targ->Integrity() < 1.0f) { + NetUtil::SendObjKill(targ, ship, NetObjKill::KILL_COLLISION); + Print(" ship %s died in collision with %s (%s)\n", targ->Name(), ship->Name(), FormatGameTime()); + if (!kill_list.contains(targ)) { + ShipStats* r = ShipStats::Find(targ->Name()); + if (r) r->AddEvent(SimEvent::COLLIDE, ship->Name()); + + if (targ->GetIFF() > 0 && ship->GetIFF() != targ->GetIFF()) { + r = ShipStats::Find(ship->Name()); + if (r) r->AddPoints(targ->Value()); + } + + kill_list.insert(targ); + } + } + + if (ship->Integrity() < 1.0f) { + NetUtil::SendObjKill(ship, targ, NetObjKill::KILL_COLLISION); + Print(" ship %s died in collision with %s (%s)\n", ship->Name(), targ->Name(), FormatGameTime()); + if (!kill_list.contains(ship)) { + ShipStats* r = ShipStats::Find(ship->Name()); + if (r) r->AddEvent(SimEvent::COLLIDE, targ->Name()); + + if (ship->GetIFF() > 0 && ship->GetIFF() != targ->GetIFF()) { + r = ShipStats::Find(targ->Name()); + if (r) r->AddPoints(ship->Value()); + } + + kill_list.insert(ship); + } + } + } + } + + ListIter debris_iter = debris; + while (++debris_iter) { + Debris* d = debris_iter.value(); + + if (d->Radius() < 50) + continue; + + if (ship->CollidesWith(*d)) { + Vec3 tv1 = d->Velocity(); + Vec3 sv1 = ship->Velocity(); + + Physical::SemiElasticCollision(*ship, *d); + + Vec3 tv2 = d->Velocity(); + Vec3 sv2 = ship->Velocity(); + + if (!NetGame::IsNetGameClient()) { + ship->InflictDamage((sv2-sv1).length()); + } + + d->InflictDamage((tv2-tv1).length()); + + // then delete the debris: + if (d->Integrity() < 1.0f) { + sim->CreateExplosion(d->Location(), d->Velocity(), Explosion::LARGE_EXPLOSION, 1.0f, 1.0f, this); + debris_iter.removeItem(); + delete d; + } + + if (ship->Integrity() < 1.0f) { + if (!kill_list.contains(ship)) { + ShipStats* r = ShipStats::Find(ship->Name()); + if (r) r->AddEvent(SimEvent::COLLIDE, Game::GetText("DEBRIS")); + + kill_list.insert(ship); + } + } + } + } + + ListIter a_iter = asteroids; + while (++a_iter) { + Asteroid* a = a_iter.value(); + + if (ship->CollidesWith(*a)) { + Vec3 sv1 = ship->Velocity(); + Physical::SemiElasticCollision(*ship, *a); + Vec3 sv2 = ship->Velocity(); + + if (!NetGame::IsNetGameClient()) { + ship->InflictDamage((sv2-sv1).length() * 10); + } + + if (ship->Integrity() < 1.0f) { + if (!kill_list.contains(ship)) { + ShipStats* r = ShipStats::Find(ship->Name()); + if (r) r->AddEvent(SimEvent::COLLIDE, Game::GetText("ASTEROID")); + + kill_list.insert(ship); + } + } + } + } + + s_index++; + } + + ListIter killed(kill_list); + while (++killed) { + Ship* kill = killed.value(); + kill->DeathSpiral(); + } +} + +// +--------------------------------------------------------------------+ + +void +SimRegion::CrashShips() +{ + if (type != AIR_SPACE || NetGame::IsNetGameClient()) + return; + + ListIter ship_iter = ships; + while (++ship_iter) { + Ship* ship = ship_iter.value(); + + if (!ship->IsGroundUnit() && + !ship->InTransition() && + ship->Class() != Ship::LCA && + ship->AltitudeAGL() < ship->Radius()/2) { + if (ship->GetFlightPhase() == Ship::ACTIVE || ship->GetFlightPhase() == Ship::APPROACH) { + ship->InflictDamage(1e6); + + if (ship->Integrity() < 1.0f) { + Print(" ship destroyed by crash: %s (%s)\n", ship->Name(), FormatGameTime()); + ShipStats* r = ShipStats::Find(ship->Name()); + if (r) r->AddEvent(SimEvent::CRASH); + + ship->DeathSpiral(); + } + } + } + } + + ListIter shot_iter = shots; + while (++shot_iter) { + Shot* shot = shot_iter.value(); + + if (shot->IsBeam() || shot->IsDecoy()) + continue; + + if (shot->AltitudeMSL() < 5e3 && + shot->AltitudeAGL() < 5) { + + // shot hit the ground, destroy it: + NetUtil::SendWepDestroy(shot); + + if (shot->IsDrone()) + drones.remove((Drone*) shot); + + shot_iter.removeItem(); + delete shot; + } + } +} + +// +--------------------------------------------------------------------+ + +void +SimRegion::DestroyShips() +{ + ListIter ship_iter = ships; + while (++ship_iter) { + Ship* ship = ship_iter.value(); + + if (ship->IsDead()) { + // must use the iterator to remove the current + // item from the container: + ship_iter.removeItem(); + DestroyShip(ship); + } + } +} + +// +--------------------------------------------------------------------+ + +void +SimRegion::DestroyShip(Ship* ship) +{ + if (!ship) return; + + Ship* spawn = 0; + + ships.remove(ship); + carriers.remove(ship); + selection.remove(ship); + + Text rgn_name; + if (ship->GetRegion()) + rgn_name = ship->GetRegion()->Name(); + + bool player_destroyed = (player_ship == ship); + + char ship_name[64]; + char ship_reg[64]; + strcpy(ship_name, ship->Name()); + strcpy(ship_reg, ship->Registry()); + + ShipDesign* ship_design = (ShipDesign*) ship->Design(); + int ship_iff = ship->GetIFF(); + int cmd_ai = ship->GetCommandAILevel(); + bool respawn = sim->IsTestMode() && !ship->IsGroundUnit(); + bool observe = false; + + if (!respawn) + respawn = ship->RespawnCount() > 0; + + if (sim->netgame) { + if (!respawn) + observe = player_destroyed; + } + + if (respawn || observe) { + if (!sim->netgame || !respawn) + ship->SetRespawnLoc(RandomPoint() * 2); + + Point spawn_loc = ship->RespawnLoc(); + + if (ship->IsAirborne() && spawn_loc.z < 5e3) + spawn_loc.z = Random(8e3, 10e3); + + spawn = sim->CreateShip(ship_name, ship_reg, ship_design, rgn_name, spawn_loc, ship_iff, cmd_ai, observe ? 0 : ship->GetLoadout()); + spawn->SetRespawnCount(ship->RespawnCount() - 1); + spawn->SetNetObserver(observe); + + if (sim->netgame && respawn) + sim->netgame->Respawn(ship->GetObjID(), spawn); + + int n = strlen(ship_name); + if (ship_name[n-2] == ' ' && isdigit(ship_name[n-1])) + ship_name[n-2] = 0; + + Element* elem = sim->FindElement(ship_name); + if (elem) + elem->AddShip(spawn, ship->GetOrigElementIndex()); + else + Print("Warning: No Element found for '%s' on respawn.\n", ship_name); + + if (player_destroyed) + SetPlayerShip(spawn); + } + else { + // close mission, return to menu: + if (player_destroyed) { + Starshatter* stars = Starshatter::GetInstance(); + if (stars) + stars->SetGameMode(Starshatter::PLAN_MODE); + } + } + + sim->ProcessEventTrigger(MissionEvent::TRIGGER_DESTROYED, 0, ship->Name()); + + dead_ships.insert(ship); + ship->Destroy(); +} + +// +--------------------------------------------------------------------+ + +void +SimRegion::NetDockShip(Ship* ship, Ship* carrier, FlightDeck* deck) +{ + if (!ship || !carrier || !deck) return; + + deck->Dock(ship); +} + +// +--------------------------------------------------------------------+ + +Ship* +SimRegion::FindShip(const char* ship_name) +{ + Ship* ship = 0; + + if (ship_name && *ship_name) { + int name_len = strlen(ship_name); + + ListIter ship_iter = ships; + while (++ship_iter && !ship) { + Ship* test = ship_iter.value(); + if (!strncmp(test->Name(), ship_name, name_len)) { + int test_len = strlen(test->Name()); + + // The only fuzzy match is for element indices. + // The desired name "Alpha" matches "Alpha 1" and "Alpha 2" + // but not "Alpha-Centauri" + + if (test_len > name_len && test->Name()[name_len] != ' ') + continue; + + ship = test; + } + } + } + + return ship; +} + +Ship* +SimRegion::FindShipByObjID(DWORD objid) +{ + Ship* ship = 0; + + ListIter ship_iter = ships; + while (++ship_iter && !ship) { + Ship* test = ship_iter.value(); + if (test->GetObjID() == objid) + ship = test; + } + + return ship; +} + +Shot* +SimRegion::FindShotByObjID(DWORD objid) +{ + Shot* shot = 0; + + ListIter shot_iter = shots; + while (++shot_iter && !shot) { + Shot* test = shot_iter.value(); + if (test->GetObjID() == objid) + shot = test; + } + + if (!shot) { + ListIter drone_iter = drones; + while (++drone_iter && !shot) { + Drone* test = drone_iter.value(); + if (test->GetObjID() == objid) + shot = test; + } + } + + return shot; +} + +// +--------------------------------------------------------------------+ + +void +SimRegion::DockShips() +{ + if (ships.size() == 0) + return; + + ListIter ship_iter = ships; + while (++ship_iter) { + Ship* ship = ship_iter.value(); + int docked = (ship->GetFlightPhase() == Ship::DOCKED); + + if (docked) { + sim->ProcessEventTrigger(MissionEvent::TRIGGER_DOCK, 0, ship->Name()); + + // who did this ship dock with? + Ship* carrier = ship->GetCarrier(); + + if (carrier) { + ShipStats* s = ShipStats::Find(ship->Name()); + if (s) { + if (ship->IsAirborne()) + s->AddEvent(SimEvent::LAND, carrier->Name()); + else + s->AddEvent(SimEvent::DOCK, carrier->Name()); + } + + ShipStats* c = ShipStats::Find(carrier->Name()); + if (c) c->AddEvent(SimEvent::RECOVER_SHIP, ship->Name()); + } + + // then delete the ship: + int player_docked = (player_ship == ship); + char ship_name[33]; + strcpy(ship_name, ship->Name()); + + selection.remove(ship); + dead_ships.insert(ship_iter.removeItem()); + ship->Destroy(); + + if (player_docked) { + // close mission, return to menu: + Starshatter* stars = Starshatter::GetInstance(); + if (stars) + stars->SetGameMode(Starshatter::PLAN_MODE); + } + + if (carrier) + Print(" %s Docked with %s\n", ship_name, carrier->Name()); + } + } +} + +// +--------------------------------------------------------------------+ + +void +SimRegion::InsertObject(Ship* ship) +{ + if (!ship) return; + + SimRegion* orig = ship->GetRegion(); + + if (orig != this) { + if (orig != 0) { + if (orig->active) + ship->Deactivate(sim->scene); + + orig->ships.remove(ship); + orig->carriers.remove(ship); + orig->selection.remove(ship); + } + + ships.append(ship); + + if (ship->NumFlightDecks()) + carriers.append(ship); + + TranslateObject(ship); + ship->SetRegion(this); + + if (active) + ship->Activate(sim->scene); + } +} + +void +SimRegion::InsertObject(Shot* shot) +{ + if (!shot) return; + + SimRegion* orig = shot->GetRegion(); + + if (orig != this) { + if (orig != 0) + orig->shots.remove(shot); + + shots.append(shot); + if (shot->IsDrone()) + drones.append((Drone*) shot); + + TranslateObject(shot); + shot->SetRegion(this); + + if (active) + shot->Activate(sim->scene); + } +} + +void +SimRegion::InsertObject(Explosion* exp) +{ + if (!exp) return; + + SimRegion* orig = exp->GetRegion(); + + if (orig != this) { + if (orig != 0) + orig->explosions.remove(exp); + + explosions.append(exp); + TranslateObject(exp); + exp->SetRegion(this); + + if (active) + exp->Activate(sim->scene); + } +} + +void +SimRegion::InsertObject(Debris* d) +{ + if (!d) return; + + SimRegion* orig = d->GetRegion(); + + if (orig != this) { + if (orig != 0) + orig->debris.remove(d); + + debris.append(d); + TranslateObject(d); + d->SetRegion(this); + + if (active) + d->Activate(sim->scene); + } +} + +void +SimRegion::InsertObject(Asteroid* a) +{ + if (!a) return; + + SimRegion* orig = a->GetRegion(); + + if (orig != this) { + if (orig != 0) + orig->asteroids.remove(a); + + asteroids.append(a); + TranslateObject(a); + a->SetRegion(this); + + if (active) + a->Activate(sim->scene); + } +} + +// +--------------------------------------------------------------------+ + +void +SimRegion::TranslateObject(SimObject* object) +{ + if (orbital_region) + location = orbital_region->Location(); + + if (object) { + SimRegion* orig = object->GetRegion(); + if (orig) { + Point delta = Location() - orig->Location(); + delta = delta.OtherHand(); + object->TranslateBy(delta); + } + } +} + +// +--------------------------------------------------------------------+ + +List& +SimRegion::TrackList(int iff) +{ + if (iff >= 0 && iff < 5) + return track_database[iff]; + + static List empty; + return empty; +} + +void +SimRegion::UpdateTracks(double seconds) +{ + for (int i = 0; i < 5; i++) { + ListIter track_iter = track_database[i]; + + while (++track_iter) { + Contact* t = track_iter.value(); + Ship* c_ship = t->GetShip(); + Shot* c_shot = t->GetShot(); + double c_life = 0; + + if (c_ship) { + c_life = c_ship->Life(); + + // look for quantum jumps and orbit transitions: + if (c_ship->GetRegion() != this || c_ship->IsNetObserver()) + c_life = 0; + } + + else if (c_shot) + c_life = c_shot->Life(); + + if (t->Age() < 0 || c_life == 0) { + track_iter.removeItem(); + delete t; + } + + else { + t->Reset(); + } + } + } +} + +// +--------------------------------------------------------------------+ + +bool +SimRegion::CanTimeSkip() const +{ + bool ok = false; + + if (player_ship) { + ok = true; + + for (int i = 0; ok && i < ships.size(); i++) { + Ship* s = ships[i]; + + if (s != player_ship && s->GetIFF() && s->GetIFF() != player_ship->GetIFF()) { + double dist = Point(s->Location() - player_ship->Location()).length(); + + if (s->IsStarship()) + ok = dist > 60e3; + else + ok = dist > 30e3; + } + } + } + + return ok; +} + +// +--------------------------------------------------------------------+ + +void +SimRegion::ResolveTimeSkip(double seconds) +{ + for (int i = 0; i < ships.size(); i++) { + Ship* ship = ships[i]; + Ship* ward = ship->GetWard(); + + // remember to burn fuel and fix stuff... + ship->ExecSystems(seconds); + ship->ExecMaintFrame(seconds); + + ship->ClearTrack(); + ListIter contact = ship->ContactList(); + while (++contact) + contact->ClearTrack(); + + if (ship->IsStatic()) + continue; + + // if ship is cleared inbound, land him: + InboundSlot* inbound = ship->GetInbound(); + if (inbound) { + if (inbound->Cleared()) { + FlightDeck* deck = inbound->GetDeck(); + + if (deck) { + ship->SetCarrier((Ship*) deck->GetCarrier(), deck); + ship->SetFlightPhase(Ship::DOCKED); + ship->Stow(); + deck->Clear(inbound->Index()); + } + } + + // cleared or not, once you're inbound, don't seek navpoints: + continue; + } + + if (ship->GetHangar()) { + ship->GetHangar()->ExecFrame(seconds); + + List& flight_decks = ship->FlightDecks(); + for (int n = 0; n < flight_decks.size(); n++) + flight_decks[n]->ExecFrame(seconds); + } + + Instruction* navpt = ship->GetNextNavPoint(); + Point dest = ship->Location(); + double speed = 500; + double space = 2.0e3 * (ship->GetElementIndex() - 1); + + if (ship->IsStarship()) + space *= 5; + + if (navpt && navpt->Action() == Instruction::LAUNCH) { + ship->SetNavptStatus(navpt, Instruction::COMPLETE); + navpt = ship->GetNextNavPoint(); + } + + if (navpt) { + dest = navpt->Location().OtherHand(); + speed = navpt->Speed(); + } + + else if (ward) { + Point delta = ship->Location() - ward->Location(); + delta.y = 0; + + if (delta.length() > 25e3) { + delta.Normalize(); + dest = ward->Location() + delta * 25e3; + } + } + + Point delta = dest - ship->Location(); + Point unit = delta; + double dist = unit.Normalize() - space; + + if (dist > 1e3) { + if (speed < 50) + speed = 500; + + double etr = dist / speed; + + if (etr > seconds) + etr = seconds; + + Point trans = unit * (speed * etr); + + if (ship->GetFuelLevel() > 1) { + ship->MoveTo(ship->Location() + trans); + ship->SetVelocity(unit * speed); + } + ship->LookAt(dest); + + if (ship->IsStarship()) { + ship->SetFLCSMode(Ship::FLCS_HELM); + ship->SetHelmHeading(ship->CompassHeading()); + ship->SetHelmPitch(ship->CompassPitch()); + } + } + + else if (navpt && navpt->Status() <= Instruction::ACTIVE) { + ship->SetNavptStatus(navpt, Instruction::COMPLETE); + } + + if (ward) { + Point ward_heading = ward->Heading(); + ward_heading.y = 0; + ward_heading.Normalize(); + + if (ship->GetFuelLevel() > 1) { + ship->SetVelocity(ward->Velocity()); + } + ship->LookAt(ship->Location() + ward_heading * 1e6); + + if (ship->IsStarship()) { + ship->SetFLCSMode(Ship::FLCS_HELM); + ship->SetHelmHeading(ship->CompassHeading()); + ship->SetHelmPitch(ship->CompassPitch()); + } + } + + if (dist > 1 || ward) { + for (int j = 0; j < ships.size(); j++) { + Ship* test = ships[j]; + + if (ship != test && test->Mass() >= ship->Mass()) { + Point delta = ship->Location() - test->Location(); + + if (delta.length() < ship->Radius() * 2 + test->Radius() * 2) { + ship->MoveTo(test->Location() + RandomPoint().OtherHand()); + } + } + } + } + } + + DockShips(); +} + +// +--------------------------------------------------------------------+ + +void +SimRegion::CommitMission() +{ + for (int i = 0; i < dead_ships.size(); i++) { + Ship* s = dead_ships[i]; + + if (s->GetCombatUnit() && s->GetFlightPhase() != Ship::DOCKED) + s->GetCombatUnit()->Kill(1); + } + + for (i = 0; i < ships.size(); i++) { + Ship* s = ships[i]; + CombatUnit* u = s->GetCombatUnit(); + + if (u) { + Point u_loc = s->Location().OtherHand(); + if (u_loc.z > 20e3) + u_loc.z = 20e3; + else if (u_loc.z < -20e3) + u_loc.z = -20e3; + + if (u->IsStarship()) { + u->SetRegion(s->GetRegion()->Name()); + u->MoveTo(u_loc); + } + + if (!u->IsDropship()) { + if (s->Integrity() < 1) + u->Kill(1); + else + u->SetSustainedDamage(s->Design()->integrity - s->Integrity()); + } + + CombatGroup* g = u->GetCombatGroup(); + if (g && g->Type() > CombatGroup::FLEET && g->GetFirstUnit() == u) { + if (!g->IsZoneLocked()) + g->SetRegion(s->GetRegion()->Name()); + else + u->SetRegion(g->GetRegion()); + + g->MoveTo(u_loc); + } + } + } +} + +// +--------------------------------------------------------------------+ + +const char* FormatGameTime() +{ + static char txt[64]; + + int t = Game::GameTime(); + + int h = ( t / 3600000); + int m = ((t - h*3600000) / 60000); + int s = ((t - h*3600000 - m*60000) / 1000); + int e = ( t - h*3600000 - m*60000 - s*1000); + + if (h > 0) + sprintf(txt, "%02d:%02d:%02d.%03d", h,m,s,e); + else + sprintf(txt, "%02d:%02d.%03d", m,s,e); + + return txt; +} diff --git a/Stars45/Sim.h b/Stars45/Sim.h new file mode 100644 index 0000000..dd2be8f --- /dev/null +++ b/Stars45/Sim.h @@ -0,0 +1,330 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Sim.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Simulation Universe and Region classes +*/ + +#ifndef Sim_h +#define Sim_h + +#include "Types.h" +#include "Universe.h" +#include "Scene.h" +#include "Physical.h" +#include "Geometry.h" +#include "List.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class Sim; +class SimRegion; +class SimObject; +class SimObserver; +class SimHyper; +class SimSplash; + +class StarSystem; +class Orbital; +class OrbitalRegion; +class Asteroid; + +class NetGame; + +class CameraDirector; +class Contact; +class Ship; +class ShipDesign; +class System; +class Element; +class Shot; +class Drone; +class Explosion; +class Debris; +class WeaponDesign; +class MotionController; +class Dust; +class Grid; +class Mission; +class MissionElement; +class MissionEvent; +class Hangar; +class FlightDeck; + +class Terrain; +class TerrainPatch; + +class Model; + +// +--------------------------------------------------------------------+ + +class Sim : public Universe +{ + friend class SimRegion; + +public: + enum { REAL_SPACE, AIR_SPACE }; + + Sim(MotionController* ctrl); + virtual ~Sim(); + + static Sim* GetSim() { return sim; } + + virtual void ExecFrame(double seconds); + + void LoadMission(Mission* msn, bool preload_textures=false); + void ExecMission(); + void CommitMission(); + void UnloadMission(); + + void NextView(); + void ShowGrid(int show = true); + bool GridShown() const; + + const char* FindAvailCallsign(int IFF); + Element* CreateElement(const char* callsign, int IFF, int type=0/*PATROL*/); + void DestroyElement(Element* elem); + Ship* CreateShip(const char* name, + const char* reg_num, + ShipDesign* design, + const char* rgn_name, + const Point& loc, + int IFF=0, + int cmd_ai=0, + const int* loadout=0); + Ship* FindShip(const char* name, const char* rgn_name=0); + Shot* CreateShot(const Point& pos, const Camera& shot_cam, WeaponDesign* d, const Ship* ship=0, SimRegion* rgn=0); + Explosion* CreateExplosion(const Point& pos, const Point& vel, int type, float exp_scale, float part_scale, SimRegion* rgn=0, SimObject* source=0, System* sys=0); + Debris* CreateDebris(const Point& pos, const Point& vel, Model* model, double mass, SimRegion* rgn=0); + Asteroid* CreateAsteroid(const Point& pos, int type, double mass, SimRegion* rgn=0); + void CreateSplashDamage(Ship* ship); + void CreateSplashDamage(Shot* shot); + void DestroyShip(Ship* ship); + void NetDockShip(Ship* ship, Ship* carrier, FlightDeck* deck); + + virtual Ship* FindShipByObjID(DWORD objid); + virtual Shot* FindShotByObjID(DWORD objid); + + Mission* GetMission() { return mission; } + List& GetEvents() { return events; } + List& GetRegions() { return regions; } + SimRegion* FindRegion(const char* name); + SimRegion* FindRegion(OrbitalRegion* rgn); + SimRegion* FindNearestSpaceRegion(SimObject* object); + SimRegion* FindNearestSpaceRegion(Orbital* body); + SimRegion* FindNearestTerrainRegion(SimObject* object); + SimRegion* FindNearestRegion(SimObject* object, int type); + bool ActivateRegion(SimRegion* rgn); + + void RequestHyperJump(Ship* obj, + SimRegion* rgn, + const Point& loc, + int type=0, + Ship* fc_src=0, + Ship* fc_dst=0); + + SimRegion* GetActiveRegion() { return active_region; } + StarSystem* GetStarSystem() { return star_system; } + List& GetSystemList(); + Scene* GetScene() { return &scene; } + Ship* GetPlayerShip(); + Element* GetPlayerElement(); + Orbital* FindOrbitalBody(const char* name); + + void SetSelection(Ship* s); + bool IsSelected(Ship* s); + ListIter GetSelection(); + void ClearSelection(); + void AddSelection(Ship* s); + + void SetTestMode(bool t=true); + + bool IsTestMode() const { return test_mode; } + bool IsNetGame() const { return netgame != 0; } + bool IsActive() const; + bool IsComplete() const; + + MotionController* GetControls() const { return ctrl; } + + Element* FindElement(const char* name); + List& GetElements() { return elements; } + + int GetAssignedElements(Element* elem, List& assigned); + + void SkipCutscene(); + void ResolveTimeSkip(double seconds); + void ResolveHyperList(); + void ResolveSplashList(); + + void ExecEvents(double seconds); + void ProcessEventTrigger(int type, int event_id=0, const char* ship=0, int param=0); + double MissionClock() const; + DWORD StartTime() const { return start_time; } + + // Create a list of mission elements based on the current + // state of the simulation. Used for multiplayer join in progress. + ListIter GetMissionElements(); + +protected: + void CreateRegions(); + void CreateElements(); + void CopyEvents(); + void BuildLinks(); + + // Convert a single live element into a mission element + // that can be serialized over the net. + MissionElement* CreateMissionElement(Element* elem); + Hangar* FindSquadron(const char* name, int& index); + + static Sim* sim; + SimRegion* active_region; + StarSystem* star_system; + Scene scene; + Dust* dust; + CameraDirector* cam_dir; + + List regions; + List rgn_queue; + List jumplist; + List splashlist; + List elements; + List finished; + List events; + List mission_elements; + + MotionController* ctrl; + + bool test_mode; + bool grid_shown; + Mission* mission; + + NetGame* netgame; + DWORD start_time; +}; + +// +--------------------------------------------------------------------+ + +class SimRegion +{ + friend class Sim; + +public: + static const char* TYPENAME() { return "SimRegion"; } + + enum { REAL_SPACE, AIR_SPACE }; + + SimRegion(Sim* sim, const char* name, int type); + SimRegion(Sim* sim, OrbitalRegion* rgn); + virtual ~SimRegion(); + + int operator == (const SimRegion& r) const { return (sim==r.sim) && (name==r.name); } + int operator < (const SimRegion& r) const; + int operator <= (const SimRegion& r) const; + + virtual void Activate(); + virtual void Deactivate(); + virtual void ExecFrame(double seconds); + void ShowGrid(int show = true); + void NextView(); + Ship* FindShip(const char* name); + Ship* GetPlayerShip() { return player_ship; } + void SetPlayerShip(Ship* ship); + OrbitalRegion* GetOrbitalRegion() { return orbital_region; } + Terrain* GetTerrain() { return terrain; } + bool IsActive() const { return active; } + bool IsAirSpace() const { return type == AIR_SPACE; } + bool IsOrbital() const { return type == REAL_SPACE; } + bool CanTimeSkip()const; + + virtual Ship* FindShipByObjID(DWORD objid); + virtual Shot* FindShotByObjID(DWORD objid); + + virtual void InsertObject(Ship* ship); + virtual void InsertObject(Shot* shot); + virtual void InsertObject(Explosion* explosion); + virtual void InsertObject(Debris* debris); + virtual void InsertObject(Asteroid* asteroid); + + const char* Name() const { return name; } + int Type() const { return type; } + int NumShips() { return ships.size(); } + List& Ships() { return ships; } + List& Carriers() { return carriers; } + List& Shots() { return shots; } + List& Drones() { return drones; } + List& Rocks() { return debris; } + List& Roids() { return asteroids; } + List& Explosions() { return explosions; } + List& Links() { return links; } + StarSystem* System() { return star_system; } + + Point Location() const { return location; } + + void SetSelection(Ship* s); + bool IsSelected(Ship* s); + ListIter GetSelection(); + void ClearSelection(); + void AddSelection(Ship* s); + + List& TrackList(int iff); + + void ResolveTimeSkip(double seconds); + +protected: + void CommitMission(); + void TranslateObject(SimObject* object); + + void AttachPlayerShip(int index); + void DestroyShips(); + void DestroyShip(Ship* ship); + void NetDockShip(Ship* ship, Ship* carrier, FlightDeck* deck); + + void UpdateSky(double seconds, const Point& ref); + void UpdateShips(double seconds); + void UpdateShots(double seconds); + void UpdateExplosions(double seconds); + void UpdateTracks(double seconds); + + void DamageShips(); + void CollideShips(); + void CrashShips(); + void DockShips(); + + Sim* sim; + Text name; + int type; + StarSystem* star_system; + OrbitalRegion* orbital_region; + Point location; + Grid* grid; + Terrain* terrain; + bool active; + + Ship* player_ship; + int current_view; + List ships; + List carriers; + List selection; + List dead_ships; + List shots; + List drones; + List explosions; + List debris; + List asteroids; + List track_database[5]; + List links; + + DWORD sim_time; + int ai_index; +}; + +#endif Sim_h + diff --git a/Stars45/SimEvent.cpp b/Stars45/SimEvent.cpp new file mode 100644 index 0000000..1cec64e --- /dev/null +++ b/Stars45/SimEvent.cpp @@ -0,0 +1,262 @@ +/* Project Starshatter 5.0 + Destroyer Studios LLC + Copyright © 1997-2007. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: SimEvent.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Simulation Events for mission summary +*/ + +#include "MemDebug.h" +#include "SimEvent.h" +#include "Sim.h" +#include "Game.h" + +// +====================================================================+ + +List records; + +// +====================================================================+ + +SimEvent::SimEvent(int e, const char* t, const char* i) + : event(e), count(0) +{ + Sim* sim = Sim::GetSim(); + if (sim) { + time = (int) sim->MissionClock(); + } + else { + time = (int) (Game::GameTime()/1000); + } + + SetTarget(t); + SetInfo(i); +} + +SimEvent::~SimEvent() +{ +} + +// +--------------------------------------------------------------------+ + +void +SimEvent::SetTime(int t) +{ + time = t; +} + +void +SimEvent::SetTarget(const char* t) +{ + if (t && t[0]) + target = t; +} + +void +SimEvent::SetInfo(const char* i) +{ + if (i && i[0]) + info = i; +} + +void +SimEvent::SetCount(int c) +{ + count = c; +} + +Text +SimEvent::GetEventDesc() const +{ + switch (event) { + case LAUNCH: return Game::GetText("sim.event.Launch"); + case DOCK: return Game::GetText("sim.event.Dock"); + case LAND: return Game::GetText("sim.event.Land"); + case EJECT: return Game::GetText("sim.event.Eject"); + case CRASH: return Game::GetText("sim.event.Crash"); + case COLLIDE: return Game::GetText("sim.event.Collision With"); + case DESTROYED: return Game::GetText("sim.event.Destroyed By"); + case MAKE_ORBIT: return Game::GetText("sim.event.Make Orbit"); + case BREAK_ORBIT: return Game::GetText("sim.event.Break Orbit"); + case QUANTUM_JUMP: return Game::GetText("sim.event.Quantum Jump"); + case LAUNCH_SHIP: return Game::GetText("sim.event.Launch Ship"); + case RECOVER_SHIP: return Game::GetText("sim.event.Recover Ship"); + case FIRE_GUNS: return Game::GetText("sim.event.Fire Guns"); + case FIRE_MISSILE: return Game::GetText("sim.event.Fire Missile"); + case DROP_DECOY: return Game::GetText("sim.event.Drop Decoy"); + case GUNS_KILL: return Game::GetText("sim.event.Guns Kill"); + case MISSILE_KILL: return Game::GetText("sim.event.Missile Kill"); + case LAUNCH_PROBE: return Game::GetText("sim.event.Launch Probe"); + case SCAN_TARGET: return Game::GetText("sim.event.Scan Target"); + default: return Game::GetText("sim.event.no event"); + } +} + +// +====================================================================+ + +ShipStats::ShipStats(const char* n, int i) + : name(n), iff(i), kill1(0), kill2(0), lost(0), coll(0), points(0), + cmd_points(0), gun_shots(0), gun_hits(0), missile_shots(0), missile_hits(0), + combat_group(0), combat_unit(0), player(false), ship_class(0), elem_index(-1) +{ + if (!n || !n[0]) + name = Game::GetText("[unknown]"); +} + +ShipStats::~ShipStats() +{ + events.destroy(); +} + +// +--------------------------------------------------------------------+ + +void +ShipStats::SetType(const char* t) +{ + if (t && t[0]) + type = t; +} + +void +ShipStats::SetRole(const char* r) +{ + if (r && r[0]) + role = r; +} + +void +ShipStats::SetRegion(const char* r) +{ + if (r && r[0]) + region = r; +} + +void +ShipStats::SetCombatGroup(CombatGroup* g) +{ + combat_group = g; +} + +void +ShipStats::SetCombatUnit(CombatUnit* u) +{ + combat_unit = u; +} + +void +ShipStats::SetElementIndex(int n) +{ + elem_index = n; +} + +void +ShipStats::SetPlayer(bool p) +{ + player = p; +} + +// +--------------------------------------------------------------------+ + +void +ShipStats::Summarize() +{ + kill1 = 0; + kill2 = 0; + lost = 0; + coll = 0; + + ListIter iter = events; + while (++iter) { + SimEvent* event = iter.value(); + int code = event->GetEvent(); + + if (code == SimEvent::GUNS_KILL) + kill1++; + + else if (code == SimEvent::MISSILE_KILL) + kill2++; + + else if (code == SimEvent::DESTROYED) + lost++; + + else if (code == SimEvent::CRASH) + coll++; + + else if (code == SimEvent::COLLIDE) + coll++; + } +} + +// +--------------------------------------------------------------------+ + +SimEvent* +ShipStats::AddEvent(SimEvent* e) +{ + events.append(e); + return e; +} + +SimEvent* +ShipStats::AddEvent(int event, const char* tgt, const char* info) +{ + SimEvent* e = new(__FILE__,__LINE__) SimEvent(event, tgt, info); + events.append(e); + return e; +} + +bool +ShipStats::HasEvent(int event) +{ + for (int i = 0; i < events.size(); i++) + if (events[i]->GetEvent() == event) + return true; + + return false; +} + +// +--------------------------------------------------------------------+ + +void ShipStats::Initialize() { records.destroy(); } +void ShipStats::Close() { records.destroy(); } + +// +--------------------------------------------------------------------+ + +int +ShipStats::NumStats() +{ + return records.size(); +} + +ShipStats* +ShipStats::GetStats(int i) +{ + if (i >= 0 && i < records.size()) + return records.at(i); + + return 0; +} + +ShipStats* +ShipStats::Find(const char* name) +{ + if (name && name[0]) { + ListIter iter = records; + while (++iter) { + ShipStats* stats = iter.value(); + if (!strcmp(stats->GetName(), name)) + return stats; + } + + ShipStats* stats = new(__FILE__,__LINE__) ShipStats(name); + records.append(stats); + return stats; + } + + return 0; +} + diff --git a/Stars45/SimEvent.h b/Stars45/SimEvent.h new file mode 100644 index 0000000..779fe00 --- /dev/null +++ b/Stars45/SimEvent.h @@ -0,0 +1,163 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: SimEvent.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Simulation Universe and Region classes +*/ + +#ifndef SimEvent_h +#define SimEvent_h + +#include "Types.h" +#include "List.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class Sim; +class SimRegion; +class SimObject; +class SimObserver; +class SimHyper; +class CombatGroup; +class CombatUnit; + +// +--------------------------------------------------------------------+ + +class SimEvent +{ +public: + static const char* TYPENAME() { return "SimEvent"; } + + enum EVENT { LAUNCH=1, DOCK, LAND, EJECT, CRASH, COLLIDE, DESTROYED, + MAKE_ORBIT, BREAK_ORBIT, QUANTUM_JUMP, + LAUNCH_SHIP, RECOVER_SHIP, + FIRE_GUNS, FIRE_MISSILE, DROP_DECOY, + GUNS_KILL, MISSILE_KILL, + LAUNCH_PROBE, SCAN_TARGET + }; + + SimEvent(int event, const char* tgt=0, const char* info=0); + ~SimEvent(); + + int GetEvent() const { return event; } + int GetTime() const { return time; } + Text GetEventDesc() const; + const char* GetTarget() const { return target; } + const char* GetInfo() const { return info; } + int GetCount() const { return count; } + + void SetTarget(const char* tgt); + void SetInfo(const char* info); + void SetCount(int count); + void SetTime(int time); + +private: + int event; + int time; + Text target; + Text info; + int count; +}; + +// +--------------------------------------------------------------------+ + +class ShipStats +{ +public: + static const char* TYPENAME() { return "ShipStats"; } + + ShipStats(const char* name, int iff=0); + ~ShipStats(); + + static void Initialize(); + static void Close(); + static ShipStats* Find(const char* name); + static int NumStats(); + static ShipStats* GetStats(int i); + + void Summarize(); + + const char* GetName() const { return name; } + const char* GetType() const { return type; } + const char* GetRole() const { return role; } + const char* GetRegion() const { return region; } + CombatGroup* GetCombatGroup() const { return combat_group; } + CombatUnit* GetCombatUnit() const { return combat_unit; } + int GetElementIndex() const { return elem_index; } + int GetShipClass() const { return ship_class; } + int GetIFF() const { return iff; } + int GetGunKills() const { return kill1; } + int GetMissileKills() const { return kill2; } + int GetDeaths() const { return lost; } + int GetColls() const { return coll; } + int GetPoints() const { return points; } + int GetCommandPoints()const { return cmd_points; } + + int GetGunShots() const { return gun_shots; } + int GetGunHits() const { return gun_hits; } + int GetMissileShots() const { return missile_shots; } + int GetMissileHits() const { return missile_hits; } + + bool IsPlayer() const { return player; } + + List& + GetEvents() { return events; } + SimEvent* AddEvent(SimEvent* e); + SimEvent* AddEvent(int event, const char* tgt=0, const char* info=0); + bool HasEvent(int event); + + void SetShipClass(int c) { ship_class = c; } + void SetIFF(int i) { iff = i; } + void SetType(const char* t); + void SetRole(const char* r); + void SetRegion(const char* r); + void SetCombatGroup(CombatGroup* g); + void SetCombatUnit(CombatUnit* u); + void SetElementIndex(int n); + void SetPlayer(bool p); + + void AddGunShot() { gun_shots++; } + void AddGunHit() { gun_hits++; } + void AddMissileShot() { missile_shots++; } + void AddMissileHit() { missile_hits++; } + void AddPoints(int p) { points += p; } + void AddCommandPoints(int p) { cmd_points += p; } + +private: + Text name; + Text type; + Text role; + Text region; + CombatGroup* combat_group; + CombatUnit* combat_unit; + bool player; + int elem_index; + int ship_class; + int iff; + int kill1; + int kill2; + int lost; + int coll; + + int gun_shots; + int gun_hits; + + int missile_shots; + int missile_hits; + + int points; + int cmd_points; + + List events; +}; + +#endif SimEvent_h + diff --git a/Stars45/SimObject.cpp b/Stars45/SimObject.cpp new file mode 100644 index 0000000..01a69f6 --- /dev/null +++ b/Stars45/SimObject.cpp @@ -0,0 +1,150 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: SimObject.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Simulation Object and Observer classes +*/ + +#include "MemDebug.h" +#include "SimObject.h" + +#include "Graphic.h" +#include "Light.h" +#include "Scene.h" + +// +--------------------------------------------------------------------+ + +SimObserver::~SimObserver() +{ + ListIter observed = observe_list; + while (++observed) + observed->Unregister(this); +} + +void +SimObserver::Observe(SimObject* obj) +{ + if (obj) { + obj->Register(this); + + if (!observe_list.contains(obj)) + observe_list.append(obj); + } +} + +void +SimObserver::Ignore(SimObject* obj) +{ + if (obj) { + obj->Unregister(this); + observe_list.remove(obj); + } +} + +bool +SimObserver::Update(SimObject* obj) +{ + if (obj) + observe_list.remove(obj); + + return true; +} + +const char* +SimObserver::GetObserverName() const +{ + static char name[32]; + sprintf(name, "SimObserver 0x%08x", (DWORD) this); + return name; +} + +// +--------------------------------------------------------------------+ + +SimObject::~SimObject() +{ + Notify(); +} + +// +--------------------------------------------------------------------+ + +void +SimObject::Notify() +{ + if (!notifying) { + notifying = true; + + int nobservers = observers.size(); + int nupdate = 0; + + if (nobservers > 0) { + ListIter iter = observers; + while (++iter) { + SimObserver* observer = iter.value(); + observer->Update(this); + nupdate++; + } + + observers.clear(); + } + + if (nobservers != nupdate) { + ::Print("WARNING: incomplete notify sim object '%s' - %d of %d notified\n", + Name(), nupdate, nobservers); + } + + notifying = false; + } + else { + ::Print("WARNING: double notify on sim object '%s'\n", Name()); + } +} + +// +--------------------------------------------------------------------+ + +void +SimObject::Register(SimObserver* observer) +{ + if (!notifying && !observers.contains(observer)) + observers.append(observer); +} + +// +--------------------------------------------------------------------+ + +void +SimObject::Unregister(SimObserver* observer) +{ + if (!notifying) + observers.remove(observer); +} + +// +--------------------------------------------------------------------+ + +void +SimObject::Activate(Scene& scene) +{ + if (rep) + scene.AddGraphic(rep); + if (light) + scene.AddLight(light); + + active = true; +} + +void +SimObject::Deactivate(Scene& scene) +{ + if (rep) + scene.DelGraphic(rep); + if (light) + scene.DelLight(light); + + active = false; +} + diff --git a/Stars45/SimObject.h b/Stars45/SimObject.h new file mode 100644 index 0000000..1d3e645 --- /dev/null +++ b/Stars45/SimObject.h @@ -0,0 +1,99 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: SimObject.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Simulation Object and Observer classes +*/ + +#ifndef SimObject_h +#define SimObject_h + +#include "Types.h" +#include "Physical.h" +#include "List.h" + +// +--------------------------------------------------------------------+ + +class Sim; +class SimRegion; +class SimObject; +class SimObserver; +class Scene; + +// +--------------------------------------------------------------------+ + +class SimObject : public Physical +{ + friend class SimRegion; + +public: + static const char* TYPENAME() { return "SimObject"; } + + enum TYPES { + SIM_SHIP=100, + SIM_SHOT, + SIM_DRONE, + SIM_EXPLOSION, + SIM_DEBRIS, + SIM_ASTEROID + }; + + SimObject() : region(0), objid(0), active(0), notifying(0) { } + SimObject(const char* n, int t=0) : Physical(n,t), region(0), objid(0), active(0), notifying(0) { } + virtual ~SimObject(); + + virtual SimRegion* GetRegion() const { return region; } + virtual void SetRegion(SimRegion* rgn) { region = rgn; } + + virtual void Notify(); + virtual void Register(SimObserver* obs); + virtual void Unregister(SimObserver* obs); + + virtual void Activate(Scene& scene); + virtual void Deactivate(Scene& scene); + + virtual DWORD GetObjID() const { return objid; } + virtual void SetObjID(DWORD id) { objid = id; } + + virtual bool IsHostileTo(const SimObject* o) + const { return false; } + +protected: + SimRegion* region; + List observers; + DWORD objid; + bool active; + bool notifying; +}; + +// +--------------------------------------------------------------------+ + +class SimObserver +{ +public: + static const char* TYPENAME() { return "SimObserver"; } + + virtual ~SimObserver(); + + int operator == (const SimObserver& o) const { return this == &o; } + + virtual bool Update(SimObject* obj); + virtual const char* GetObserverName() const; + + virtual void Observe(SimObject* obj); + virtual void Ignore(SimObject* obj); + + +protected: + List observe_list; +}; + +#endif SimObject_h + diff --git a/Stars45/Sky.cpp b/Stars45/Sky.cpp new file mode 100644 index 0000000..0460924 --- /dev/null +++ b/Stars45/Sky.cpp @@ -0,0 +1,726 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Sky.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Celestial sphere, stars, planets, space dust... +*/ + +#include "MemDebug.h" +#include "Sky.h" +#include "StarSystem.h" + +#include "Game.h" +#include "Bitmap.h" +#include "DataLoader.h" +#include "Light.h" +#include "Random.h" + +void Print(const char* ftm, ...); + + +// +====================================================================+ + +Stars::Stars(int nstars) +{ + infinite = true; + luminous = true; + shadow = false; + + vset = new(__FILE__,__LINE__) VertexSet(nstars); + colors = new(__FILE__,__LINE__) Color[nstars]; + + for (int i = 0; i < nstars; i++) { + vset->loc[i] = RandomVector(1000); + + ColorValue val = ColorValue((float) Random(0.7, 0.8), + (float) Random(0.7, 0.8), + (float) Random(0.7, 0.8)); + Color c = val.ToColor(); + + colors[i] = c; + vset->diffuse[i] = c.Value(); + vset->specular[i] = 0; + } + + strcpy(name, "Stars"); +} + +Stars::~Stars() +{ + delete [] colors; + delete vset; +} + +// +--------------------------------------------------------------------+ + +void +Stars::Illuminate(double scale) +{ + if (!vset) + return; + + for (int i = 0; i < vset->nverts; i++) { + Color c = colors[i] * scale; + vset->diffuse[i] = c.Value(); + } +} + +// +--------------------------------------------------------------------+ + +void +Stars::Render(Video* video, DWORD flags) +{ + if (!vset || !video || (flags & Graphic::RENDER_ADDITIVE) == 0) + return; + + video->SetBlendType(Video::BLEND_ADDITIVE); + video->DrawPoints(vset); +} + +// +====================================================================+ + +static const double BOUNDARY = 3000; +static const double BOUNDARYx2 = BOUNDARY * 2; + +Dust::Dust(int ndust, bool b) + : really_hidden(false), bright(b) +{ + radius = (float) BOUNDARYx2; + luminous = true; + vset = new(__FILE__,__LINE__) VertexSet(ndust); + + Reset(Point(0, 0, 0)); + strcpy(name, "Dust"); +} + +// +--------------------------------------------------------------------+ + +Dust::~Dust() +{ + delete vset; +} + +// +--------------------------------------------------------------------+ + +void +Dust::Reset(const Point& ref) +{ + BYTE c = 0; + + for (int i = 0; i < vset->nverts; i++) { + vset->loc[i] = Vec3( Random(-BOUNDARY, BOUNDARY), + Random(-BOUNDARY, BOUNDARY), + Random(-BOUNDARY, BOUNDARY) ); + + if (bright) + c = (BYTE) Random(96,200); + else + c = (BYTE) Random(64,156); + + vset->diffuse[i] = Color(c,c,c).Value(); + vset->specular[i] = 0; + } +} + +// +--------------------------------------------------------------------+ + +void +Dust::ExecFrame(double factor, const Point& ref) +{ + if (Game::TimeCompression() > 4) { + Hide(); + return; + } + + Show(); + + Point delta = ref - loc; + double dlen = delta.length(); + + if (dlen < 0.0001) + return; + + if (dlen > BOUNDARY) { + Reset(ref); + } + else { + // wrap around if necessary to keep in view + for (int i = 0; i < vset->nverts; i++) { + Vec3 v = vset->loc[i]; + + v -= delta; + + if (v.x > BOUNDARY) v.x -= (float) BOUNDARYx2; + if (v.x < -BOUNDARY) v.x += (float) BOUNDARYx2; + if (v.y > BOUNDARY) v.y -= (float) BOUNDARYx2; + if (v.y < -BOUNDARY) v.y += (float) BOUNDARYx2; + if (v.z > BOUNDARY) v.z -= (float) BOUNDARYx2; + if (v.z < -BOUNDARY) v.z += (float) BOUNDARYx2; + + vset->loc[i] = v; + } + } + + MoveTo(ref); +} + +// +--------------------------------------------------------------------+ + +void +Dust::Render(Video* video, DWORD flags) +{ + if (hidden || really_hidden) + return; + + if (!vset || !video || (flags & Graphic::RENDER_SOLID) == 0 || (flags & Graphic::RENDER_ADD_LIGHT) != 0) + return; + + video->SetBlendType(Video::BLEND_SOLID); + video->SetRenderState(Video::Z_ENABLE, false); + video->SetRenderState(Video::Z_WRITE_ENABLE, false); + + video->DrawPoints(vset); + + video->SetRenderState(Video::Z_ENABLE, true); + video->SetRenderState(Video::Z_WRITE_ENABLE, true); +} + +// +--------------------------------------------------------------------+ + +void +Dust::Hide() +{ + hidden = true; + really_hidden = true; +} + +void +Dust::Show() +{ + hidden = false; + really_hidden = false; +} + +// +====================================================================+ + +PlanetRep::PlanetRep(const char* surface_name, const char* glow_name, + double rad, const Vec3& pos, double tscale, + const char* rngname, double minrad, double maxrad, + Color atmos, const char* gloss_name) + : mtl_surf(0), mtl_limb(0), mtl_ring(0), star_system(0) +{ + loc = pos; + + radius = (float) rad; + has_ring = 0; + ring_verts = -1; + ring_polys = -1; + ring_rad = 0; + body_rad = rad; + daytime = false; + atmosphere = atmos; + star_system = 0; + + if (!surface_name || !*surface_name) { + Print(" invalid Planet patch - no surface texture specified\n"); + return; + } + + Print(" constructing Planet patch %s\n", surface_name); + strncpy(name, surface_name, 31); + name[31] = 0; + + Bitmap* bmp_surf = 0; + Bitmap* bmp_spec = 0; + Bitmap* bmp_glow = 0; + Bitmap* bmp_ring = 0; + Bitmap* bmp_limb = 0; + + DataLoader* loader = DataLoader::GetLoader(); + loader->LoadTexture(surface_name, bmp_surf, Bitmap::BMP_SOLID, true); + + if (glow_name && *glow_name) { + Print(" loading glow texture %s\n", glow_name); + loader->LoadTexture(glow_name, bmp_glow, Bitmap::BMP_SOLID, true); + } + + if (gloss_name && *gloss_name) { + Print(" loading gloss texture %s\n", gloss_name); + loader->LoadTexture(gloss_name, bmp_spec, Bitmap::BMP_SOLID, true); + } + + mtl_surf = new(__FILE__,__LINE__) Material; + + mtl_surf->Ka = Color::LightGray; + mtl_surf->Kd = Color::White; + mtl_surf->Ke = bmp_glow ? Color::White : Color::Black; + mtl_surf->Ks = bmp_spec ? Color::LightGray : Color::Black; + mtl_surf->power = 25.0f; + mtl_surf->tex_diffuse = bmp_surf; + mtl_surf->tex_specular = bmp_spec; + mtl_surf->tex_emissive = bmp_glow; + mtl_surf->blend = Material::MTL_SOLID; + + if (bmp_spec && Video::GetInstance()->IsSpecMapEnabled()) { + if (glow_name && strstr(glow_name, "light")) + strcpy(mtl_surf->shader, "SimplePix/PlanetSurfNightLight"); + + else if (glow_name) + strcpy(mtl_surf->shader, "SimplePix/PlanetSurf"); + } + + if (atmosphere != Color::Black) { + mtl_limb = new(__FILE__,__LINE__) Material; + + mtl_limb->Ka = atmosphere; + + strcpy(mtl_limb->shader, "PlanetLimb"); + + Print(" loading atmospheric limb texture PlanetLimb.pcx\n"); + loader->LoadTexture("PlanetLimb.pcx", bmp_limb, Bitmap::BMP_TRANSLUCENT, true); + mtl_limb->tex_diffuse = bmp_limb; + mtl_limb->blend = Material::MTL_TRANSLUCENT; + } + + if (maxrad > 0 && minrad > 0) { + has_ring = 1; + radius = (float) maxrad; + ring_rad = (maxrad + minrad)/2; + loader->LoadTexture(rngname, bmp_ring, Bitmap::BMP_SOLID, true); + + mtl_ring = new(__FILE__,__LINE__) Material; + + mtl_ring->Ka = Color::LightGray; + mtl_ring->Kd = Color::White; + mtl_ring->Ks = Color::Gray; + mtl_ring->Ke = Color::Black; + mtl_ring->power = 30.0f; + mtl_ring->tex_diffuse = bmp_ring; + mtl_ring->blend = Material::MTL_TRANSLUCENT; + } + + if (rad > 2e6 && rad < 1e8) + CreateSphere(rad, 24, 32, minrad, maxrad, 48, tscale); + else + CreateSphere(rad, 16, 24, minrad, maxrad, 48, tscale); +} + +// +--------------------------------------------------------------------+ + +PlanetRep::~PlanetRep() +{ +} + +// +--------------------------------------------------------------------+ + +void +PlanetRep::CreateSphere(double radius, int nrings, int nsections, + double minrad, double maxrad, int rsections, + double tscale) +{ + const int sect_verts = nsections + 1; + + model = new(__FILE__,__LINE__) Model; + own_model = 1; + + Surface* surface = new(__FILE__,__LINE__) Surface; + + int i, j, m, n; + + int npolys = (nrings + 2) * nsections; + int nverts = (nrings + 3) * sect_verts; + + int ppolys = npolys; + int pverts = nverts; + + int apolys = 0; + int averts = 0; + + if (atmosphere != Color::Black) { + apolys = npolys; + averts = nverts; + + npolys *= 2; + nverts *= 2; + } + + if (has_ring) { + ring_verts = nverts; + ring_polys = npolys; + + npolys += rsections * 3; // top, bottom, edge + nverts += rsections * 6; + } + + surface->SetName(name); + surface->CreateVerts(nverts); + surface->CreatePolys(npolys); + + VertexSet* vset = surface->GetVertexSet(); + + if (!vset || vset->nverts < nverts) { + ::Print("WARNING: insufficient memory for planet '%s'\n", name); + return; + } + + Poly* polys = surface->GetPolys(); + + if (!polys) { + ::Print("WARNING: insufficient memory for planet '%s'\n", name); + return; + } + + ZeroMemory(polys, sizeof(Poly) * npolys); + + // Generate vertex points for planetary rings: + double dtheta = PI / (nrings + 2); + double dphi = 2 * PI / nsections; + double theta = 0; + n = 0; // vertex being generated + + for (i = 0; i < nrings+3; i++) { + double y = radius * cos(theta); // y is the same for entire ring + double v = theta / PI; // v is the same for entire ring + double rsintheta = radius * sin(theta); + double phi = 0; + + for (j = 0; j < sect_verts; j++) { + double x = rsintheta * sin(phi); + double z = rsintheta * cos(phi); + + vset->loc[n] = Vec3(x, y, z); + vset->nrm[n] = Vec3(x, y, z); + vset->tu[n] = (float) (tscale * (1 - (phi/(2.0*PI)))); + vset->tv[n] = (float) (tscale * v); + + vset->nrm[n].Normalize(); + + phi += dphi; + n++; + } + + theta += dtheta; + } + + // Generate vertex points for rings: + if (has_ring) { + n = ring_verts; + + double dphi = 2.0 * PI / rsections; + double y = 0; // y is the same for entire ring + + // top of ring: + double phi = 0; + for (j = 0; j < rsections; j++) { + double x = minrad * sin(phi); + double z = minrad * cos(phi); + + vset->loc[n] = Vec3(x, y, z); + vset->nrm[n] = Vec3(0, 1, 0); + vset->tu[n] = (j & 1) ? 1.0f : 0.0f; + vset->tv[n] = 0.0f; + n++; + + x = maxrad * sin(phi); + z = maxrad * cos(phi); + + vset->loc[n] = Vec3(x, y, z); + vset->nrm[n] = Vec3(0, 1, 0); + vset->tu[n] = (j & 1) ? 1.0f : 0.0f; + vset->tv[n] = 1.0f; + n++; + + phi += dphi; + } + + // bottom of ring: + phi = 0; + for (j = 0; j < rsections; j++) { + double x = minrad * sin(phi); + double z = minrad * cos(phi); + + vset->loc[n] = Vec3(x, y, z); + vset->nrm[n] = Vec3(0, -1, 0); + vset->tu[n] = (j & 1) ? 1.0f : 0.0f; + vset->tv[n] = 0.0f; + n++; + + x = maxrad * sin(phi); + z = maxrad * cos(phi); + + vset->loc[n] = Vec3(x, y, z); + vset->nrm[n] = Vec3(0, -1, 0); + vset->tu[n] = (j & 1) ? 1.0f : 0.0f; + vset->tv[n] = 1.0f; + n++; + + phi += dphi; + } + + // edge of ring: + phi = 0; + for (j = 0; j < rsections; j++) { + double x = maxrad * sin(phi); + double z = maxrad * cos(phi); + + Point normal = Point(x,0,z); + normal.Normalize(); + + double thickness = maxrad/333; + + vset->loc[n] = Vec3(x, y+thickness, z); + vset->nrm[n] = normal; + vset->tu[n] = (j & 1) ? 1.0f : 0.0f; + vset->tv[n] = 1.0f; + n++; + + vset->loc[n] = Vec3(x, y-thickness, z); + vset->nrm[n] = normal; + vset->tu[n] = (j & 1) ? 1.0f : 0.0f; + vset->tv[n] = 1.0f; + n++; + + phi += dphi; + } + } + + for (i = 0; i < npolys; i++) { + polys[i].nverts = 3; + polys[i].vertex_set = vset; + polys[i].material = mtl_surf; + } + + // Generate triangles for top and bottom caps. + for (i = 0; i < nsections; i++) { + Poly& p0 = polys[i]; + p0.verts[2] = i; + p0.verts[1] = sect_verts + i; + p0.verts[0] = sect_verts + ((i+1) % sect_verts); + + Poly& p1 = polys[ppolys - nsections + i]; + p1.verts[2] = pverts - 1 - i; + p1.verts[1] = pverts - 1 - sect_verts - i; + p1.verts[0] = pverts - 2 - sect_verts - i; + + surface->AddIndices(6); + } + + // Generate triangles for the planetary rings + m = sect_verts; // first vertex in current ring + n = nsections; // triangle being generated, skip the top cap + + for (i = 0; i < nrings; i++) { + for (j = 0; j < nsections; j++) { + Poly& p0 = polys[n]; + p0.nverts = 4; + p0.verts[3] = m + j; + p0.verts[2] = m + (sect_verts) + j; + p0.verts[1] = m + (sect_verts) + ((j + 1) % (sect_verts)); + p0.verts[0] = m + ((j + 1) % (sect_verts)); + n++; + + surface->AddIndices(6); + } + + m += sect_verts; + } + + if (averts && apolys && mtl_limb) { + for (i = 0; i < pverts; i++) { + vset->loc[averts + i] = vset->loc[i]; + vset->nrm[averts + i] = vset->nrm[i]; + } + + for (i = 0; i < ppolys; i++) { + Poly& p0 = polys[i]; + Poly& p1 = polys[apolys + i]; + + p1.vertex_set = vset; + p1.material = mtl_limb; + + p1.nverts = p0.nverts; + p1.verts[0] = p0.verts[0]; + p1.verts[1] = p0.verts[1]; + p1.verts[2] = p0.verts[2]; + p1.verts[3] = p0.verts[3]; + + surface->AddIndices(p1.nverts == 3 ? 3 : 6); + } + } + + if (has_ring) { + // Generate quads for the rings + m = ring_verts; // first vertex in top of ring, after planet verts + n = ring_polys; // quad being generated, after planet polys + + // top of ring: + for (j = 0; j < rsections; j++) { + Poly& p0 = polys[n]; + p0.nverts = 4; + p0.material = mtl_ring; + + p0.verts[3] = m + 2*j; + p0.verts[2] = m + 2*j + 1; + p0.verts[1] = m + ((2*j + 3) % (rsections*2)); + p0.verts[0] = m + ((2*j + 2) % (rsections*2)); + + surface->AddIndices(6); + + n++; + } + + // bottom of ring: + // first vertex in bottom of ring, after top ring verts + m = ring_verts + 2*rsections; + + for (j = 0; j < rsections; j++) { + Poly& p0 = polys[n]; + p0.nverts = 4; + p0.material = mtl_ring; + + p0.verts[0] = m + 2*j; + p0.verts[1] = m + 2*j + 1; + p0.verts[2] = m + ((2*j + 3) % (rsections*2)); + p0.verts[3] = m + ((2*j + 2) % (rsections*2)); + + surface->AddIndices(6); + + n++; + } + + // edge of ring: + // first vertex in edge of ring, after bottom ring verts + m = ring_verts + 4*rsections; + + for (j = 0; j < rsections; j++) { + Poly& p0 = polys[n]; + p0.nverts = 4; + p0.material = mtl_ring; + + p0.verts[3] = m + 2*j; + p0.verts[2] = m + 2*j + 1; + p0.verts[1] = m + ((2*j + 3) % (rsections*2)); + p0.verts[0] = m + ((2*j + 2) % (rsections*2)); + + surface->AddIndices(6); + + n++; + } + } + + // then assign them to cohesive segments: + Segment* segment = 0; + + for (n = 0; n < npolys; n++) { + Poly& poly = polys[n]; + poly.plane = Plane(vset->loc[poly.verts[0]], + vset->loc[poly.verts[2]], + vset->loc[poly.verts[1]]); + + if (segment && segment->material == polys[n].material) { + segment->npolys++; + } + else { + segment = new(__FILE__,__LINE__) Segment; + + segment->npolys = 1; + segment->polys = &polys[n]; + segment->material = segment->polys->material; + + surface->GetSegments().append(segment); + } + } + + model->AddSurface(surface); +} + + +int +PlanetRep::CheckRayIntersection(Point Q, Point w, double len, Point& ipt, + bool treat_translucent_polys_as_solid) +{ + // compute leading edge of ray: + Point dst = Q + w*len; + + // check right angle spherical distance: + Point d0 = loc - Q; + Point d1 = d0.cross(w); + double dlen = d1.length(); // distance of point from line + + if (dlen > body_rad) // clean miss + return 0; // (no impact) + + // possible collision course... + Point d2 = Q + w * (d0 * w); + + // so check the leading edge: + Point delta0 = dst - loc; + + if (delta0.length() > radius) { + // and the endpoints: + Point delta1 = d2 - Q; + Point delta2 = dst - Q; + + // if d2 is not between Q and dst, we missed: + if (delta1 * delta2 < 0 || + delta1.length() > delta2.length()) { + return 0; + } + } + + return 1; +} + +void +PlanetRep::SetDaytime(bool d) +{ + daytime = d; + + if (daytime) { + if (mtl_surf) mtl_surf->blend = Material::MTL_ADDITIVE; + if (mtl_ring) mtl_ring->blend = Material::MTL_ADDITIVE; + } + + else { + if (mtl_surf) mtl_surf->blend = Material::MTL_SOLID; + if (mtl_ring) mtl_ring->blend = Material::MTL_TRANSLUCENT; + } +} + +void +PlanetRep::SetStarSystem(StarSystem* system) +{ + star_system = system; +} + +// +--------------------------------------------------------------------+ + +void +PlanetRep::Render(Video* video, DWORD flags) +{ + Solid::Render(video, flags); + + /*** + *** DEBUG + *** + + Matrix orient = Orientation(); + orient.Transpose(); + + video->SetObjTransform(orient, Location()); + + Surface* surf = model->GetSurfaces().first(); + Poly* polys = surf->GetPolys(); + + for (int i = 0; i < 5; i++) + video->DrawPolyOutline(polys + i); + /***/ +} + diff --git a/Stars45/Sky.h b/Stars45/Sky.h new file mode 100644 index 0000000..92f731b --- /dev/null +++ b/Stars45/Sky.h @@ -0,0 +1,105 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Sky.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Celestial sphere, stars, planets, space dust... +*/ + +#ifndef Sky_h +#define Sky_h + +#include "Types.h" +#include "Solid.h" +#include "Bitmap.h" +#include "Geometry.h" + +// +--------------------------------------------------------------------+ + +class StarSystem; + +// +--------------------------------------------------------------------+ + +class Stars : public Graphic +{ +public: + Stars(int nstars); + virtual ~Stars(); + + virtual void Illuminate(double scale); + virtual void Render(Video* video, DWORD flags); + +protected: + VertexSet* vset; + Color* colors; +}; + +// +--------------------------------------------------------------------+ + +class Dust : public Graphic +{ +public: + Dust(int ndust, bool bright=false); + virtual ~Dust(); + + virtual void Render(Video* video, DWORD flags); + virtual void Reset(const Point& ref); + virtual void ExecFrame(double factor, const Point& ref); + + virtual void Hide(); + virtual void Show(); + +protected: + bool really_hidden; + bool bright; + VertexSet* vset; +}; + +// +--------------------------------------------------------------------+ + +class PlanetRep : public Solid +{ +public: + PlanetRep(const char* img_west, const char* img_glow, + double rad, const Vec3& pos, double tscale = 1, + const char* rngname=0, double minrad = 0, double maxrad = 0, + Color atmos = Color::Black, const char* img_gloss=0); + virtual ~PlanetRep(); + + virtual Color Atmosphere() const { return atmosphere; } + virtual void SetAtmosphere(Color a) { atmosphere = a; } + virtual void SetDaytime(bool d); + virtual void SetStarSystem(StarSystem* system); + + virtual void Render(Video* video, DWORD flags); + + virtual int CheckRayIntersection(Point pt, Point vpn, double len, Point& ipt, + bool treat_translucent_polys_as_solid=true); + +protected: + void CreateSphere(double radius, int nrings, int nsections, + double minrad, double maxrad, int rsections, + double tscale); + + Material* mtl_surf; + Material* mtl_limb; + Material* mtl_ring; + int has_ring; + int ring_verts; + int ring_polys; + double ring_rad; + double body_rad; + Color atmosphere; + bool daytime; + + StarSystem* star_system; +}; + +#endif Sky_h + diff --git a/Stars45/StarServer.cpp b/Stars45/StarServer.cpp new file mode 100644 index 0000000..672057a --- /dev/null +++ b/Stars45/StarServer.cpp @@ -0,0 +1,545 @@ +/* Project Starshatter 5.0 + Destroyer Studios LLC + Copyright © 1997-2007. All Rights Reserved. + + SUBSYSTEM: Stars + FILE: StarServer.cpp + AUTHOR: John DiCamillo + +*/ + +#include "MemDebug.h" + +#include "StarServer.h" +#include "Campaign.h" +#include "CombatRoster.h" +#include "Galaxy.h" +#include "Mission.h" +#include "Sim.h" +#include "SimEvent.h" +#include "Ship.h" +#include "Contact.h" +#include "QuantumDrive.h" +#include "Power.h" +#include "SystemDesign.h" +#include "WeaponDesign.h" +#include "Shot.h" +#include "Drive.h" +#include "Explosion.h" +#include "FlightDeck.h" +#include "RadioMessage.h" +#include "RadioTraffic.h" +#include "Random.h" +#include "ModConfig.h" + +#include "NetLayer.h" +#include "NetGame.h" +#include "NetHost.h" +#include "NetServer.h" +#include "HttpServer.h" +#include "HttpServletExec.h" +#include "NetAdminServer.h" +#include "NetLobbyServer.h" +#include "NetServerConfig.h" + +#include "Token.h" +#include "MachineInfo.h" +#include "Game.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "EventDispatch.h" +#include "MultiController.h" +#include "DataLoader.h" +#include "ParseUtil.h" +#include "Resource.h" + +// +--------------------------------------------------------------------+ + +StarServer* StarServer::instance = 0; + +static Mission* current_mission = 0; +static double time_til_change = 0; +static bool exit_latch = true; + +extern const char* versionInfo; + +// +--------------------------------------------------------------------+ + +StarServer::StarServer() + : loader(0), time_mark(0), minutes(0), game_mode(MENU_MODE), + admin_server(0), lobby_server(0) +{ + if (!instance) + instance = this; + + app_name = "Starserver 5.0"; + title_text = "Starserver"; + palette_name = "alpha"; + + Game::server = true; + Game::show_mouse = true; + + DataLoader::Initialize(); + loader = DataLoader::GetLoader(); + int loadstat = loader->EnableDatafile("shatter.dat"); + + if (loadstat != DataLoader::DATAFILE_OK) { + const char* err_msg = loadstat == DataLoader::DATAFILE_INVALID ? + "The file 'shatter.dat' appears to have been damaged. Please re-install Starshatter." : + "Starshatter cannot open the file 'shatter.dat'. Please re-install Starshatter."; + + ::MessageBox(hwnd, err_msg, "Starshatter - Error", MB_OK); + ::Print(err_msg); + ::Print("\n\nFATAL ERROR: EXIT."); + exit(-1); + } + +#ifndef STARSHATTER_DEMO_RELEASE + if (loader->FindFile("start.dat")) + loader->EnableDatafile("start.dat"); +#endif + + // no images or sounds in server mode: + loader->EnableMedia(false); +} + +StarServer::~StarServer() +{ + delete admin_server; + delete lobby_server; + + admin_server = 0; + lobby_server = 0; + + // delete all the ships and stuff + // BEFORE getting rid of the system + // and weapons catalogs! + delete world; + world = 0; // don't let base class double delete the world + + Drive::Close(); + Explosion::Close(); + FlightDeck::Close(); + Campaign::Close(); + CombatRoster::Close(); + Galaxy::Close(); + RadioTraffic::Close(); + Ship::Close(); + WeaponDesign::Close(); + SystemDesign::Close(); + DataLoader::Close(); + NetServerConfig::Close(); + ModConfig::Close(); + + instance = 0; + + Game::server = false; +} + +// +--------------------------------------------------------------------+ + +bool +StarServer::Init(HINSTANCE hi, HINSTANCE hpi, LPSTR cmdline, int nCmdShow) +{ + if (loader) + loader->UseFileSystem(false); + + return Game::Init(hi, hpi, cmdline, nCmdShow); +} + +// +--------------------------------------------------------------------+ + +bool +StarServer::InitGame() +{ + if (!Game::InitGame()) + return false; + + RandomInit(); + ModConfig::Initialize(); + NetServerConfig::Initialize(); + SystemDesign::Initialize("sys.def"); + WeaponDesign::Initialize("wep.def"); + Ship::Initialize(); + Galaxy::Initialize(); + CombatRoster::Initialize(); + Campaign::Initialize(); + + Drive::Initialize(); + Explosion::Initialize(); + FlightDeck::Initialize(); + Ship::Initialize(); + Shot::Initialize(); + RadioTraffic::Initialize(); + + time_mark = Game::GameTime(); + minutes = 0; + + NetServerConfig* server_config = NetServerConfig::GetInstance(); + if (!server_config) + return false; + + ::Print("\n\n\nStarshatter Server Init\n"); + ::Print("-----------------------\n"); + ::Print("Server Name: %s\n", (const char*) server_config->Name()); + ::Print("Server Type: %d\n", server_config->GetGameType()); + + if (server_config->GetMission().length() > 0) + ::Print("Server Mission: %s\n", (const char*) server_config->GetMission()); + + ::Print("Lobby Server Port: %d\n", server_config->GetLobbyPort()); + ::Print("Admin Server Port: %d\n", server_config->GetAdminPort()); + ::Print("-----------------------\n"); + + NetLobbyServer* nls = new(__FILE__,__LINE__) NetLobbyServer; + NetAdminServer* nas = NetAdminServer::GetInstance(server_config->GetAdminPort()); + nas->SetServerName(server_config->Name()); + + lobby_server = nls; + admin_server = nas; + + return true; +} + +// +--------------------------------------------------------------------+ + +void +StarServer::SetGameMode(int m) +{ + if (game_mode == m) + return; + + if (m == LOAD_MODE) { + Print(" game_mode = LOAD_MODE\n"); + paused = true; + } + + else if (m == PLAY_MODE) { + Print(" game_mode = PLAY_MODE\n"); + + if (!world) { + CreateWorld(); + InstantiateMission(); + } + + // stand alone server should wait for players to connect + // before unpausing the simulation... + SetTimeCompression(1); + Pause(true); + } + + else if (m == MENU_MODE) { + Print(" game_mode = MENU_MODE\n"); + paused = true; + + Sim* sim = (Sim*) world; + + if (sim) + sim->UnloadMission(); + } + + game_mode = m; +} + +// +--------------------------------------------------------------------+ + +void +StarServer::SetNextMission(const char* script) +{ + if (lobby_server) + lobby_server->SetServerMission(script); +} + +// +--------------------------------------------------------------------+ + +void +StarServer::CreateWorld() +{ + RadioTraffic::Initialize(); + + // create world + if (!world) { + Sim* sim = new(__FILE__,__LINE__) Sim(0); + world = sim; + Print(" World Created.\n"); + } +} + +void +StarServer::InstantiateMission() +{ + Memory::Check(); + + current_mission = 0; + + if (Campaign::GetCampaign()) { + current_mission = Campaign::GetCampaign()->GetMission(); + } + + Sim* sim = (Sim*) world; + + if (sim) { + sim->UnloadMission(); + + if (current_mission) { + sim->LoadMission(current_mission); + sim->ExecMission(); + sim->SetTestMode(false); + + Print(" Mission Instantiated.\n"); + } + + else { + Print(" *** WARNING: StarServer::InstantiateMission() - no mission selected ***\n"); + } + } + + Memory::Check(); +} + +// +--------------------------------------------------------------------+ + +bool +StarServer::GameLoop() +{ + if (active && paused) { + UpdateWorld(); + GameState(); + } + + else if (!active) { + UpdateWorld(); + GameState(); + Sleep(10); + } + + Game::GameLoop(); + return false; // must return false to keep processing + // true tells the outer loop to sleep until a + // windows event is available +} + +// +--------------------------------------------------------------------+ + +void +StarServer::UpdateWorld() +{ + long new_time = real_time; + double delta = new_time - frame_time; + seconds = max_frame_length; + gui_seconds = delta * 0.001; + + if (frame_time == 0) + gui_seconds = 0; + + time_comp = 1; + + if (delta < max_frame_length * 1000) + seconds = delta * 0.001; + + frame_time = new_time; + + Galaxy* galaxy = Galaxy::GetInstance(); + if (galaxy) galaxy->ExecFrame(); + + Campaign* campaign = Campaign::GetCampaign(); + if (campaign) campaign->ExecFrame(); + + if (paused) { + if (world) + world->ExecFrame(0); + } + + else { + game_time += (DWORD) (seconds * 1000); + + Drive::StartFrame(); + + if (world) + world->ExecFrame(seconds); + } + + static DWORD refresh_time = 0; + if (RealTime() - refresh_time > 1000) { + refresh_time = RealTime(); + RedrawWindow(hwnd, 0, 0, RDW_ERASE|RDW_INVALIDATE); + } +} + +// +--------------------------------------------------------------------+ + +void +StarServer::GameState() +{ + if (lobby_server) { + lobby_server->ExecFrame(); + + if (lobby_server->GetStatus() == NetServerInfo::PERSISTENT) + paused = NetGame::NumPlayers() < 1; + } + + if (game_mode == MENU_MODE) { + Sleep(30); + } + + else if (game_mode == LOAD_MODE) { + CreateWorld(); + InstantiateMission(); + + SetGameMode(PLAY_MODE); + } + + else if (game_mode == PLAY_MODE) { + if (Game::GameTime() - time_mark > 60000) { + time_mark = Game::GameTime(); + minutes++; + if (minutes > 60) + Print(" TIME %2d:%02d:00\n", minutes/60, minutes%60); + else + Print(" TIME %2d:00\n", minutes); + } + + Sleep(10); + } + + Memory::Check(); +} + +// +--------------------------------------------------------------------+ + +bool +StarServer::OnPaint() +{ + PAINTSTRUCT paintstruct; + HDC hdc = BeginPaint(hwnd, &paintstruct); + + Text txt_title = title_text; + Text txt_mode; + Text txt_users = Game::GetText("server.no-users"); + char buf[256]; + + txt_title += " "; + txt_title += versionInfo; + + switch (game_mode) { + case LOAD_MODE: + case MENU_MODE: + txt_mode = Game::GetText("server.mode.lobby"); + + if (lobby_server) { + sprintf(buf, Game::GetText("server.users").data(), lobby_server->NumUsers()); + txt_users = buf; + } + break; + + case PLAY_MODE: + txt_mode = Game::GetText("server.mode.active"); + if (lobby_server) { + sprintf(buf, Game::GetText("server.users-and-players").data(), lobby_server->NumUsers(), NetGame::NumPlayers()); + } + else { + sprintf(buf, Game::GetText("server.players").data(), NetGame::NumPlayers()); + } + txt_users = buf; + break; + + default: + txt_mode = Game::GetText("server.mode.other"); + break; + } + + if (lobby_server && lobby_server->GetStatus() == NetServerInfo::PERSISTENT) + txt_mode += " " + Game::GetText("server.alt.persistent"); + + if (paused) + txt_mode += " " + Game::GetText("server.alt.paused"); + + TextOut(hdc, 4, 4, txt_title, txt_title.length()); + TextOut(hdc, 4, 22, txt_mode, txt_mode.length()); + TextOut(hdc, 4, 40, txt_users, txt_users.length()); + + Sim* sim = Sim::GetSim(); + if (sim && sim->GetMission()) { + Mission* mission = sim->GetMission(); + Text txt_msn = Game::GetText("server.mission"); + txt_msn += mission->Name(); + TextOut(hdc, 4, 58, txt_msn, txt_msn.length()); + } + + EndPaint(hwnd, &paintstruct); + return true; +} + +// +--------------------------------------------------------------------+ + +DWORD WINAPI StarServerShutdownProc(LPVOID link) +{ + StarServer* stars = (StarServer*) link; + + Sleep(3000); + + if (stars) { + stars->Exit(); + return 0; + } + + return (DWORD) E_POINTER; +} + +DWORD WINAPI StarServerRestartProc(LPVOID link) +{ + StarServer* stars = (StarServer*) link; + + Sleep(3000); + +#ifdef STARSHATTER_DEMO_RELEASE + + if (stars) { + char cmdline[256]; + strcpy(cmdline, "StarDemo -server"); + + STARTUPINFO s; + ZeroMemory(&s, sizeof(s)); + s.cb = sizeof(s); + + PROCESS_INFORMATION pi; + ZeroMemory(&pi, sizeof(pi)); + + CreateProcess("StarDemo.exe", cmdline, 0, 0, 0, 0, 0, 0, &s, &pi); + stars->Exit(); + return 0; + } + +#else + + if (stars) { + char cmdline[256]; + strcpy(cmdline, "stars -server"); + + STARTUPINFO s; + ZeroMemory(&s, sizeof(s)); + s.cb = sizeof(s); + + PROCESS_INFORMATION pi; + ZeroMemory(&pi, sizeof(pi)); + + CreateProcess("stars.exe", cmdline, 0, 0, 0, 0, 0, 0, &s, &pi); + stars->Exit(); + return 0; + } + +#endif + + return (DWORD) E_POINTER; +} + +void +StarServer::Shutdown(bool restart) +{ + DWORD thread_id = 0; + + if (restart) + CreateThread(0, 4096, StarServerRestartProc, (LPVOID) this, 0, &thread_id); + else + CreateThread(0, 4096, StarServerShutdownProc, (LPVOID) this, 0, &thread_id); +} \ No newline at end of file diff --git a/Stars45/StarServer.h b/Stars45/StarServer.h new file mode 100644 index 0000000..62a8b0c --- /dev/null +++ b/Stars45/StarServer.h @@ -0,0 +1,76 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars + FILE: StarServer.h + AUTHOR: John DiCamillo + +*/ + +#ifndef StarServer_h +#define StarServer_h + +#include "Types.h" +#include "Game.h" +#include "Bitmap.h" +#include "KeyMap.h" + +// +--------------------------------------------------------------------+ + +class Campaign; +class Ship; +class Sim; +class FadeView; +class CameraDirector; +class MultiController; +class MouseController; +class DataLoader; + +class NetServer; +class NetLobbyServer; + +// +--------------------------------------------------------------------+ + +class StarServer : public Game +{ +public: + StarServer(); + virtual ~StarServer(); + + virtual bool Init(HINSTANCE hi, HINSTANCE hpi, LPSTR cmdline, int nCmdShow); + virtual bool InitGame(); + virtual void GameState(); + virtual bool OnPaint(); + + enum MODE { MENU_MODE, // main menu + LOAD_MODE, // loading mission into simulator + PLAY_MODE // active simulation + }; + + int GetGameMode() { return game_mode; } + void SetGameMode(int mode); + void SetNextMission(const char* script); + + void CreateWorld(); + void Shutdown(bool restart=false); + + static StarServer* GetInstance() { return instance; } + + +protected: + virtual bool GameLoop(); + virtual void UpdateWorld(); + virtual void InstantiateMission(); + + static StarServer* instance; + NetServer* admin_server; + NetLobbyServer* lobby_server; + DataLoader* loader; + + int game_mode; + DWORD time_mark; + DWORD minutes; +}; + +#endif StarServer_h diff --git a/Stars45/StarSystem.cpp b/Stars45/StarSystem.cpp new file mode 100644 index 0000000..b403cdb --- /dev/null +++ b/Stars45/StarSystem.cpp @@ -0,0 +1,1974 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: StarSystem.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Various Heavenly Bodies +*/ + +#include "MemDebug.h" +#include "StarSystem.h" +#include "Galaxy.h" +#include "Sky.h" +#include "Starshatter.h" +#include "TerrainRegion.h" +#include "TerrainHaze.h" +#include "Weather.h" + +#include "Game.h" +#include "Sound.h" +#include "Solid.h" +#include "Light.h" +#include "Bitmap.h" +#include "DataLoader.h" +#include "Scene.h" +#include "ParseUtil.h" + +const double epoch = 0.5e9; + double StarSystem::stardate = 0; + +// +====================================================================+ + +static double base_time = 0; +static WORD oldcw = 0; +static WORD fpcw = 0; + +void FPU2Extended() +{ + _asm { fstcw oldcw } + fpcw = oldcw | 0x0300; // set 80-bit precision mode + _asm { fldcw fpcw } +} + +void FPURestore() +{ + // well, I don't actually WANT single-precision mode + // so I think I'll just ignore this... + + //_asm { fldcw oldcw } +} + +void StarSystem::SetBaseTime(double t, bool absolute) +{ + FPU2Extended(); + + if (absolute) { + base_time = t; + CalcStardate(); + } + + else if (t > 0) { + if (t > epoch) t -= epoch; + base_time = t; + CalcStardate(); + } +} + +double StarSystem::GetBaseTime() +{ + return base_time; +} + +void StarSystem::CalcStardate() +{ + if (base_time < 1) { + time_t clock_seconds; + time(&clock_seconds); + + base_time = clock_seconds; + + while (base_time < 0) + base_time += epoch; + } + + FPU2Extended(); + + double gtime = (double) Game::GameTime() / 1000.0; + double sdate = gtime + base_time + epoch; + + stardate = sdate; + + FPURestore(); +} + +static const double GRAV = 6.673e-11; +static const int NAMELEN = 64; + +// +====================================================================+ + +StarSystem::StarSystem(const char* sys_name, Point l, int iff, int s) + : name(sys_name), affiliation(iff), sky_stars(0), sky_dust(0), loc(l), seq(s), + active_region(0), instantiated(false), ambient(0,0,0), + sun_color(255,255,255), sun_scale(1), point_stars(0), poly_stars(0), + nebula(0), haze(0) +{ + center = new(__FILE__,__LINE__) Orbital(this, "CG", Orbital::NOTHING, 1.0e35f, 0.0f, 0.0f, 0); + radius = 0.0f; +} + +// +--------------------------------------------------------------------+ + +StarSystem::~StarSystem() +{ + Print(" Destroying Star System %s\n", (const char*) name); + + if (instantiated) { + Deactivate(); + Destroy(); + } + + bodies.destroy(); + regions.destroy(); + all_regions.clear(); // do not destroy these! + + delete center; +} + +// +--------------------------------------------------------------------+ + +static OrbitalBody* primary_star = 0; +static OrbitalBody* primary_planet = 0; +static OrbitalBody* primary_moon = 0; + +void +StarSystem::Load() +{ + CalcStardate(); + active_region = 0; + + BYTE* block = 0; + DataLoader* loader = DataLoader::GetLoader(); + datapath = loader->GetDataPath(); + + sprintf(filename, "%s/%s.def", (const char*) name, (const char*) name); + + Print("Loading StarSystem: %s\n", filename); + loader->LoadBuffer(filename, block, true); + + if (!block) { + Print("ERROR: invalid star system file '%s'\n", filename); + exit(-2); + return; + } + + Parser parser(new(__FILE__,__LINE__) BlockReader((const char*) block)); + + Term* term = parser.ParseTerm(); + + if (!term) { + Print("ERROR: could not parse '%s'\n", filename); + exit(-3); + return; + } + else { + TermText* file_type = term->isText(); + if (!file_type || file_type->value() != "STARSYSTEM") { + Print("ERROR: invalid star system file '%s'\n", filename); + term->print(10); + exit(-4); + return; + } + } + + // parse the system: + do { + delete term; + term = parser.ParseTerm(); + + if (term) { + TermDef* def = term->isDef(); + if (def) { + if (def->name()->value() == "name") { + char namebuf[NAMELEN]; + namebuf[0] = 0; + GetDefText(namebuf, def, filename); + + if (namebuf[0]) + name = namebuf; + } + + else if (def->name()->value() == "sky") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: sky struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + + char imgname[NAMELEN]; + char magname[NAMELEN]; + char hazname[NAMELEN]; + + imgname[0] = 0; + magname[0] = 0; + hazname[0] = 0; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + if (pdef->name()->value() == "poly_stars") + GetDefText(imgname, pdef, filename); + + else if (pdef->name()->value() == "nebula") + GetDefText(magname, pdef, filename); + + else if (pdef->name()->value() == "haze") + GetDefText(hazname, pdef, filename); + } + } + + if (imgname[0]) + sky_poly_stars = imgname; + + if (magname[0]) + sky_nebula = magname; + + if (hazname[0]) + sky_haze = hazname; + } + } + + else if (def->name()->value() == "stars") { + GetDefNumber(sky_stars, def, filename); + } + + else if (def->name()->value() == "ambient") { + Vec3 a; + GetDefVec(a, def, filename); + + ambient = Color((BYTE) a.x, (BYTE) a.y, (BYTE) a.z) * 2.5; + } + + else if (def->name()->value() == "dust") { + GetDefNumber(sky_dust, def, filename); + } + + else if (def->name()->value() == "star") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: star struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + ParseStar(val); + } + } + + else if (def->name()->value() == "planet") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: planet struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + ParsePlanet(val); + } + } + + else if (def->name()->value() == "moon") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: moon struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + ParseMoon(val); + } + } + + else if (def->name()->value() == "region") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: region struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + ParseRegion(val); + } + } + + else if (def->name()->value() == "terrain") { + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: terrain struct missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + ParseTerrain(val); + } + } + + } + } + } + while (term); + + loader->ReleaseBuffer(block); +} + +// +--------------------------------------------------------------------+ + +void +StarSystem::ParseStar(TermStruct* val) +{ + char star_name[NAMELEN]; + char img_name[NAMELEN]; + char map_name[NAMELEN]; + double light = 0.0; + double radius = 0.0; + double rot = 0.0; + double mass = 0.0; + double orbit = 0.0; + double tscale = 1.0; + bool retro = false; + Color color; + Color back; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + if (pdef->name()->value() == "name") + GetDefText(star_name, pdef, filename); + + else if (pdef->name()->value() == "map" || pdef->name()->value() == "icon") + GetDefText(map_name, pdef, filename); + + else if (pdef->name()->value() == "image") + GetDefText(img_name, pdef, filename); + + else if (pdef->name()->value() == "mass") + GetDefNumber(mass, pdef, filename); + + else if (pdef->name()->value() == "orbit") + GetDefNumber(orbit, pdef, filename); + + else if (pdef->name()->value() == "radius") + GetDefNumber(radius, pdef, filename); + + else if (pdef->name()->value() == "rotation") + GetDefNumber(rot, pdef, filename); + + else if (pdef->name()->value() == "tscale") + GetDefNumber(tscale, pdef, filename); + + else if (pdef->name()->value() == "light") + GetDefNumber(light, pdef, filename); + + else if (pdef->name()->value() == "retro") + GetDefBool(retro, pdef, filename); + + else if (pdef->name()->value() == "color") { + Vec3 a; + GetDefVec(a, pdef, filename); + color = Color((BYTE) a.x, (BYTE) a.y, (BYTE) a.z); + } + + else if (pdef->name()->value() == "back" || pdef->name()->value() == "back_color") { + Vec3 a; + GetDefVec(a, pdef, filename); + back = Color((BYTE) a.x, (BYTE) a.y, (BYTE) a.z); + } + } + } + + OrbitalBody* star = new(__FILE__,__LINE__) OrbitalBody(this, star_name, Orbital::STAR, mass, radius, orbit, center); + star->map_name = map_name; + star->tex_name = img_name; + star->light = light; + star->tscale = tscale; + star->subtype = Star::G; + star->retro = retro; + star->rotation = rot * 3600; + star->color = color; + star->back = back; + + // map icon: + if (*map_name) { + DataLoader::GetLoader()->LoadBitmap(map_name, star->map_icon, Bitmap::BMP_TRANSLUCENT, true); + } + + bodies.append(star); + primary_star = star; + primary_planet = 0; + primary_moon = 0; + + if (orbit > StarSystem::radius) + StarSystem::radius = orbit; +} + +// +--------------------------------------------------------------------+ + +void +StarSystem::ParsePlanet(TermStruct* val) +{ + char pln_name[NAMELEN]; + char img_name[NAMELEN]; + char map_name[NAMELEN]; + char hi_name[NAMELEN]; + char img_ring[NAMELEN]; + char glo_name[NAMELEN]; + char glo_hi_name[NAMELEN]; + char gloss_name[NAMELEN]; + + double radius = 0.0; + double mass = 0.0; + double orbit = 0.0; + double rot = 0.0; + double minrad = 0.0; + double maxrad = 0.0; + double tscale = 1.0; + double tilt = 0.0; + bool retro = false; + bool lumin = false; + Color atmos = Color::Black; + + ZeroMemory(pln_name, NAMELEN); + ZeroMemory(hi_name, NAMELEN); + ZeroMemory(img_ring, NAMELEN); + ZeroMemory(glo_name, NAMELEN); + ZeroMemory(glo_hi_name, NAMELEN); + ZeroMemory(gloss_name, NAMELEN); + ZeroMemory(map_name, NAMELEN); + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + if (pdef->name()->value() == "name") + GetDefText(pln_name, pdef, filename); + + else if (pdef->name()->value() == "map" || pdef->name()->value() == "icon") + GetDefText(map_name, pdef, filename); + + else if (pdef->name()->value() == "image") + GetDefText(img_name, pdef, filename); + + else if (pdef->name()->value() == "image_west") + GetDefText(img_name, pdef, filename); + + else if (pdef->name()->value() == "image_east") + GetDefText(img_name, pdef, filename); + + else if (pdef->name()->value() == "glow") + GetDefText(glo_name, pdef, filename); + + else if (pdef->name()->value() == "gloss") + GetDefText(gloss_name, pdef, filename); + + else if (pdef->name()->value() == "high_res") + GetDefText(hi_name, pdef, filename); + + else if (pdef->name()->value() == "high_res_west") + GetDefText(hi_name, pdef, filename); + + else if (pdef->name()->value() == "high_res_east") + GetDefText(hi_name, pdef, filename); + + else if (pdef->name()->value() == "glow_high_res") + GetDefText(glo_hi_name, pdef, filename); + + else if (pdef->name()->value() == "mass") + GetDefNumber(mass, pdef, filename); + + else if (pdef->name()->value() == "orbit") + GetDefNumber(orbit, pdef, filename); + + else if (pdef->name()->value() == "retro") + GetDefBool(retro, pdef, filename); + + else if (pdef->name()->value() == "luminous") + GetDefBool(lumin, pdef, filename); + + else if (pdef->name()->value() == "rotation") + GetDefNumber(rot, pdef, filename); + + else if (pdef->name()->value() == "radius") + GetDefNumber(radius, pdef, filename); + + else if (pdef->name()->value() == "ring") + GetDefText(img_ring, pdef, filename); + + else if (pdef->name()->value() == "minrad") + GetDefNumber(minrad, pdef, filename); + + else if (pdef->name()->value() == "maxrad") + GetDefNumber(maxrad, pdef, filename); + + else if (pdef->name()->value() == "tscale") + GetDefNumber(tscale, pdef, filename); + + else if (pdef->name()->value() == "tilt") + GetDefNumber(tilt, pdef, filename); + + else if (pdef->name()->value() == "atmosphere") { + Vec3 a; + GetDefVec(a, pdef, filename); + atmos = Color((BYTE) a.x, (BYTE) a.y, (BYTE) a.z); + } + } + } + + OrbitalBody* planet = new(__FILE__,__LINE__) OrbitalBody(this, pln_name, Orbital::PLANET, mass, radius, orbit, primary_star); + planet->map_name = map_name; + planet->tex_name = img_name; + planet->tex_high_res = hi_name; + planet->tex_ring = img_ring; + planet->tex_glow = glo_name; + planet->tex_glow_high_res = glo_hi_name; + planet->tex_gloss = gloss_name; + planet->ring_min = minrad; + planet->ring_max = maxrad; + planet->tscale = tscale; + planet->tilt = tilt; + planet->retro = retro; + planet->luminous = lumin; + planet->rotation = rot * 3600; + planet->atmosphere = atmos; + + if (primary_star) + primary_star->satellites.append(planet); + else + bodies.append(planet); + + primary_planet = planet; + primary_moon = 0; + + if (orbit > StarSystem::radius) + StarSystem::radius = orbit; + + // map icon: + if (map_name[0]) { + DataLoader::GetLoader()->LoadBitmap(map_name, planet->map_icon, Bitmap::BMP_TRANSLUCENT, true); + } +} + +// +--------------------------------------------------------------------+ + +void +StarSystem::ParseMoon(TermStruct* val) +{ + char map_name[NAMELEN]; + char pln_name[NAMELEN]; + char img_name[NAMELEN]; + char hi_name[NAMELEN]; + char glo_name[NAMELEN]; + char glo_hi_name[NAMELEN]; + char gloss_name[NAMELEN]; + + double radius = 0.0; + double mass = 0.0; + double orbit = 0.0; + double rot = 0.0; + double tscale = 1.0; + double tilt = 0.0; + bool retro = false; + Color atmos = Color::Black; + + ZeroMemory(map_name, NAMELEN); + ZeroMemory(pln_name, NAMELEN); + ZeroMemory(hi_name, NAMELEN); + ZeroMemory(img_name, NAMELEN); + ZeroMemory(glo_name, NAMELEN); + ZeroMemory(glo_hi_name, NAMELEN); + ZeroMemory(gloss_name, NAMELEN); + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + if (pdef->name()->value() == "name") + GetDefText(pln_name, pdef, filename); + + else if (pdef->name()->value() == "map" || pdef->name()->value() == "icon") + GetDefText(map_name, pdef, filename); + + else if (pdef->name()->value() == "image") + GetDefText(img_name, pdef, filename); + + else if (pdef->name()->value() == "glow") + GetDefText(glo_name, pdef, filename); + + else if (pdef->name()->value() == "high_res") + GetDefText(hi_name, pdef, filename); + + else if (pdef->name()->value() == "glow_high_res") + GetDefText(glo_hi_name, pdef, filename); + + else if (pdef->name()->value() == "gloss") + GetDefText(gloss_name, pdef, filename); + + else if (pdef->name()->value() == "mass") + GetDefNumber(mass, pdef, filename); + + else if (pdef->name()->value() == "orbit") + GetDefNumber(orbit, pdef, filename); + + else if (pdef->name()->value() == "rotation") + GetDefNumber(rot, pdef, filename); + + else if (pdef->name()->value() == "retro") + GetDefBool(retro, pdef, filename); + + else if (pdef->name()->value() == "radius") + GetDefNumber(radius, pdef, filename); + + else if (pdef->name()->value() == "tscale") + GetDefNumber(tscale, pdef, filename); + + else if (pdef->name()->value() == "inclination") + GetDefNumber(tilt, pdef, filename); + + else if (pdef->name()->value() == "atmosphere") { + Vec3 a; + GetDefVec(a, pdef, filename); + atmos = Color((BYTE) a.x, (BYTE) a.y, (BYTE) a.z); + } + } + } + + OrbitalBody* moon = new(__FILE__,__LINE__) OrbitalBody(this, pln_name, Orbital::MOON, mass, radius, orbit, primary_planet); + moon->map_name = map_name; + moon->tex_name = img_name; + moon->tex_high_res = hi_name; + moon->tex_glow = glo_name; + moon->tex_glow_high_res = glo_hi_name; + moon->tex_gloss = gloss_name; + moon->tscale = tscale; + moon->retro = retro; + moon->rotation = rot * 3600; + moon->tilt = tilt; + moon->atmosphere = atmos; + + if (primary_planet) + primary_planet->satellites.append(moon); + else { + Print("WARNING: no planet for moon %s in '%s', deleted.\n", pln_name, filename); + delete moon; + moon = 0; + } + + primary_moon = moon; + + // map icon: + if (map_name[0]) { + DataLoader::GetLoader()->LoadBitmap(map_name, moon->map_icon, Bitmap::BMP_TRANSLUCENT, true); + } +} + +// +--------------------------------------------------------------------+ + +void +StarSystem::ParseRegion(TermStruct* val) +{ + char rgn_name[NAMELEN]; + char lnk_name[NAMELEN]; + double size = 1.0e6; + double orbit = 0.0; + double grid = 25000; + double inclination = 0.0; + int asteroids = 0; + + List links; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + if (pdef->name()->value() == "name") + GetDefText(rgn_name, pdef, filename); + + else if (pdef->name()->value() == "link") { + GetDefText(lnk_name, pdef, filename); + if (lnk_name[0]) { + links.append(new(__FILE__,__LINE__) Text(lnk_name)); + } + } + + else if (pdef->name()->value() == "orbit") + GetDefNumber(orbit, pdef, filename); + + else if (pdef->name()->value() == "size") + GetDefNumber(size, pdef, filename); + + else if (pdef->name()->value() == "radius") + GetDefNumber(size, pdef, filename); + + else if (pdef->name()->value() == "grid") + GetDefNumber(grid, pdef, filename); + + else if (pdef->name()->value() == "inclination") + GetDefNumber(inclination, pdef, filename); + + else if (pdef->name()->value() == "asteroids") + GetDefNumber(asteroids, pdef, filename); + } + } + + Orbital* primary = primary_moon; + if (!primary) primary = primary_planet; + if (!primary) primary = primary_star; + + OrbitalRegion* region = new(__FILE__,__LINE__) OrbitalRegion(this, rgn_name, 0, size, orbit, primary); + region->grid = grid; + region->inclination = inclination; + region->asteroids = asteroids; + region->links.append(links); + + if (primary) + primary->regions.append(region); + else + regions.append(region); + + all_regions.append(region); + + if (orbit > StarSystem::radius) + StarSystem::radius = orbit; +} + +// +--------------------------------------------------------------------+ + +void +StarSystem::ParseTerrain(TermStruct* val) +{ + Orbital* primary = primary_moon; + if (!primary) primary = primary_planet; + + if (!primary) { + Print("WARNING: Terrain region with no primary ignored in '%s'\n", filename); + return; + } + + TerrainRegion* region = 0; + + Text rgn_name; + Text patch_name; + Text patch_texture; + Text noise_tex0; + Text noise_tex1; + Text apron_name; + Text apron_texture; + Text water_texture; + Text env_texture_positive_x; + Text env_texture_negative_x; + Text env_texture_positive_y; + Text env_texture_negative_y; + Text env_texture_positive_z; + Text env_texture_negative_z; + Text haze_name; + Text sky_name; + Text clouds_high; + Text clouds_low; + Text shades_high; + Text shades_low; + + double size = 1.0e6; + double grid = 25000; + double inclination = 0.0; + double scale = 10e3; + double mtnscale = 1e3; + double fog_density = 0; + double fog_scale = 0; + double haze_fade = 0; + double clouds_alt_high= 0; + double clouds_alt_low= 0; + double w_period = 0; + double w_chances[Weather::NUM_STATES]; + + ZeroMemory(w_chances, sizeof(w_chances)); + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + if (pdef->name()->value() == "name") + GetDefText(rgn_name, pdef, filename); + + else if (pdef->name()->value() == "patch") + GetDefText(patch_name, pdef, filename); + + else if (pdef->name()->value() == "patch_texture") + GetDefText(patch_texture, pdef, filename); + + else if (pdef->name()->value() == "detail_texture_0") + GetDefText(noise_tex0, pdef, filename); + + else if (pdef->name()->value() == "detail_texture_1") + GetDefText(noise_tex1, pdef, filename); + + else if (pdef->name()->value() == "apron") + GetDefText(apron_name, pdef, filename); + + else if (pdef->name()->value() == "apron_texture") + GetDefText(apron_texture, pdef, filename); + + else if (pdef->name()->value() == "water_texture") + GetDefText(water_texture, pdef, filename); + + else if (pdef->name()->value() == "env_texture_positive_x") + GetDefText(env_texture_positive_x, pdef, filename); + + else if (pdef->name()->value() == "env_texture_negative_x") + GetDefText(env_texture_negative_x, pdef, filename); + + else if (pdef->name()->value() == "env_texture_positive_y") + GetDefText(env_texture_positive_y, pdef, filename); + + else if (pdef->name()->value() == "env_texture_negative_y") + GetDefText(env_texture_negative_y, pdef, filename); + + else if (pdef->name()->value() == "env_texture_positive_z") + GetDefText(env_texture_positive_z, pdef, filename); + + else if (pdef->name()->value() == "env_texture_negative_z") + GetDefText(env_texture_negative_z, pdef, filename); + + else if (pdef->name()->value() == "clouds_high") + GetDefText(clouds_high, pdef, filename); + + else if (pdef->name()->value() == "shades_high") + GetDefText(shades_high, pdef, filename); + + else if (pdef->name()->value() == "clouds_low") + GetDefText(clouds_low, pdef, filename); + + else if (pdef->name()->value() == "shades_low") + GetDefText(shades_low, pdef, filename); + + else if (pdef->name()->value() == "haze") + GetDefText(haze_name, pdef, filename); + + else if (pdef->name()->value() == "sky_color") + GetDefText(sky_name, pdef, filename); + + else if (pdef->name()->value() == "size" || + pdef->name()->value() == "radius") + GetDefNumber(size, pdef, filename); + + else if (pdef->name()->value() == "grid") + GetDefNumber(grid, pdef, filename); + + else if (pdef->name()->value() == "inclination") + GetDefNumber(inclination, pdef, filename); + + else if (pdef->name()->value() == "scale") + GetDefNumber(scale, pdef, filename); + + else if (pdef->name()->value() == "mtnscale" || + pdef->name()->value() == "mtn_scale") + GetDefNumber(mtnscale, pdef, filename); + + else if (pdef->name()->value() == "fog_density") + GetDefNumber(fog_density, pdef, filename); + + else if (pdef->name()->value() == "fog_scale") + GetDefNumber(fog_scale, pdef, filename); + + else if (pdef->name()->value() == "haze_fade") + GetDefNumber(haze_fade, pdef, filename); + + else if (pdef->name()->value() == "clouds_alt_high") + GetDefNumber(clouds_alt_high, pdef, filename); + + else if (pdef->name()->value() == "clouds_alt_low") + GetDefNumber(clouds_alt_low, pdef, filename); + + else if (pdef->name()->value() == "weather_period") + GetDefNumber(w_period, pdef, filename); + + else if (pdef->name()->value() == "weather_clear") + GetDefNumber(w_chances[0], pdef, filename); + else if (pdef->name()->value() == "weather_high_clouds") + GetDefNumber(w_chances[1], pdef, filename); + else if (pdef->name()->value() == "weather_moderate_clouds") + GetDefNumber(w_chances[2], pdef, filename); + else if (pdef->name()->value() == "weather_overcast") + GetDefNumber(w_chances[3], pdef, filename); + else if (pdef->name()->value() == "weather_fog") + GetDefNumber(w_chances[4], pdef, filename); + else if (pdef->name()->value() == "weather_storm") + GetDefNumber(w_chances[5], pdef, filename); + + else if (pdef->name()->value() == "layer") { + if (!pdef->term() || !pdef->term()->isStruct()) { + Print("WARNING: terrain layer struct missing in '%s'\n", filename); + } + else { + + if (!region) + region = new(__FILE__,__LINE__) TerrainRegion(this, rgn_name, size, primary); + + TermStruct* val = pdef->term()->isStruct(); + ParseLayer(region, val); + } + } + } + } + + if (!region) + region = new(__FILE__,__LINE__) TerrainRegion(this, rgn_name, size, primary); + + region->grid = grid; + region->inclination = inclination; + region->patch_name = patch_name; + region->patch_texture = patch_texture; + region->noise_tex0 = noise_tex0; + region->noise_tex1 = noise_tex1; + region->apron_name = apron_name; + region->apron_texture = apron_texture; + region->water_texture = water_texture; + region->haze_name = haze_name; + region->clouds_high = clouds_high; + region->shades_high = shades_high; + region->clouds_low = clouds_low; + region->shades_low = shades_low; + region->scale = scale; + region->mtnscale = mtnscale; + region->fog_density = fog_density; + region->fog_scale = fog_scale; + region->haze_fade = haze_fade; + region->clouds_alt_high = clouds_alt_high; + region->clouds_alt_low = clouds_alt_low; + + region->env_texture_positive_x = env_texture_positive_x; + region->env_texture_negative_x = env_texture_negative_x; + region->env_texture_positive_y = env_texture_positive_y; + region->env_texture_negative_y = env_texture_negative_y; + region->env_texture_positive_z = env_texture_positive_z; + region->env_texture_negative_z = env_texture_negative_z; + + Weather& weather = region->GetWeather(); + + weather.SetPeriod(w_period); + + for (i = 0; i < Weather::NUM_STATES; i++) + weather.SetChance(i, w_chances[i]); + + region->LoadSkyColors(sky_name); + + primary->regions.append(region); + all_regions.append(region); +} + +// +--------------------------------------------------------------------+ + +void +StarSystem::ParseLayer(TerrainRegion* rgn, TermStruct* val) +{ + Text tile_name; + Text detail_name; + double height = 0; + + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + if (pdef->name()->value().indexOf("tile") == 0) + GetDefText(tile_name, pdef, filename); + + else if (pdef->name()->value().indexOf("detail") == 0) + GetDefText(detail_name, pdef, filename); + + else if (pdef->name()->value().contains("height") || + pdef->name()->value().contains("alt")) + GetDefNumber(height, pdef, filename); + } + } + + if (rgn) + rgn->AddLayer(height, tile_name, detail_name); +} + +// +--------------------------------------------------------------------+ + +void +StarSystem::Create() +{ + if (Game::Server()) + return; + + if (!instantiated) { + Print("Creating Star System %s\n", (const char*) name); + + DataLoader* loader = DataLoader::GetLoader(); + loader->SetDataPath(datapath); + + // poly star shell: + if (sky_poly_stars.length()) { + poly_stars = new(__FILE__,__LINE__) Solid; + poly_stars->Load(sky_poly_stars, 120); + poly_stars->SetLuminous(true); + poly_stars->SetInfinite(true); + + Print("Celestial Sphere '%s' loaded\n", (const char*) sky_poly_stars); + Print(" radius: %f\n", poly_stars->Radius()); + } + + if (sky_stars) { + Print("Point Stars: %d\n", sky_stars); + point_stars = new(__FILE__,__LINE__) Stars(sky_stars); + } + + loader->SetDataPath(datapath); + + // nebula: + if (sky_nebula.length()) { + nebula = new(__FILE__,__LINE__) Solid; + nebula->Load(sky_nebula, 100); + nebula->SetLuminous(true); + nebula->SetInfinite(true); + + Print("Nebular Sky '%s' loaded\n", (const char*) sky_nebula); + Print(" radius: %f\n", nebula->Radius()); + } + + // atmospheric haze: + if (sky_haze.length()) { + loader->SetDataPath(datapath); + haze = new(__FILE__,__LINE__) TerrainHaze(); + haze->Load(sky_haze, 120); + + haze->SetInfinite(true); + + Print("Atmospheric Haze '%s' loaded\n", (const char*) sky_haze); + Print(" radius: %f\n", haze->Radius()); + + haze->Hide(); + } + + loader->SetDataPath(0); + + ListIter star = bodies; + while (++star) { + CreateBody(*star); + + ListIter planet = star->Satellites(); + while (++planet) { + CreateBody(*planet); + + ListIter moon = planet->Satellites(); + while (++moon) { + CreateBody(*moon); + } + } + } + } + + instantiated = true; +} + +// +--------------------------------------------------------------------+ + +void +StarSystem::CreateBody(OrbitalBody& body) +{ + DataLoader* loader = DataLoader::GetLoader(); + loader->SetDataPath(datapath); + + // stars: + if (body.type == Orbital::STAR) { + Point starloc = body.loc; + starloc = starloc.OtherHand(); + + PlanetRep* rep = new(__FILE__,__LINE__) PlanetRep(body.tex_name, + 0, + body.radius, + starloc, + body.tscale); + + rep->SetLuminous(true); + rep->MoveTo(loc); + body.rep = rep; + + sun_brightness = body.light; + sun_color = body.color; + + Light* sun_light = new(__FILE__,__LINE__) Light(1.1f); + sun_light->SetColor(sun_color); + sun_light->SetShadow(true); + sun_light->MoveTo(body.loc); + sun_light->SetType(Light::LIGHT_DIRECTIONAL); + + sun_lights.append(sun_light); + body.light_rep = sun_light; + + if (body.back != Color::Black) { + Light* back_light = new(__FILE__,__LINE__) Light(0.6f); + back_light->SetColor(body.back); + back_light->SetShadow(false); + back_light->MoveTo(body.loc * -1); + back_light->SetType(Light::LIGHT_DIRECTIONAL); + + back_lights.append(back_light); + body.back_light = back_light; + } + } + + // planets and moons: + else { + Point planetloc = body.loc; + planetloc = planetloc.OtherHand(); + + double rmax = 0; + double rmin = 0; + + if (body.ring_max > 0) { + rmax = body.ring_max*body.radius; + rmin = body.ring_min*body.radius; + } + + Text surface = body.tex_name; + Text glow = body.tex_glow; + + if (Game::MaxTexSize() >= 512) { + if (body.tex_high_res.length()) + surface = body.tex_high_res; + + if (body.tex_glow_high_res.length()) + glow = body.tex_glow_high_res; + } + + PlanetRep* rep = new(__FILE__,__LINE__) PlanetRep(surface, + glow, + body.radius, + planetloc, + body.tscale, + body.tex_ring, + rmin, + rmax, + body.atmosphere, + body.tex_gloss); + + rep->SetStarSystem(this); + + /*** + if (body.luminous) { + rep->SetLuminous(1); + } + ***/ + + if (body.tilt != 0) { + Matrix m; + m.Pitch(body.tilt); + + rep->SetOrientation(m); + } + + body.rep = rep; + } +} + +// +--------------------------------------------------------------------+ + +void +StarSystem::Destroy() +{ + if (instantiated) { + ListIter star_iter = bodies; + while (++star_iter) { + OrbitalBody* star = star_iter.value(); + + GRAPHIC_DESTROY(star->rep); + LIGHT_DESTROY(star->light_rep); + LIGHT_DESTROY(star->back_light); + + ListIter planet = star->Satellites(); + while (++planet) { + GRAPHIC_DESTROY(planet->rep); + + ListIter moon = planet->Satellites(); + while (++moon) { + GRAPHIC_DESTROY(moon->rep); + } + } + } + + GRAPHIC_DESTROY(point_stars); + GRAPHIC_DESTROY(poly_stars); + GRAPHIC_DESTROY(nebula); + GRAPHIC_DESTROY(haze); + + sun_lights.clear(); + back_lights.clear(); + } + + instantiated = false; + sun_scale = 1; +} + +// +--------------------------------------------------------------------+ + +void +StarSystem::Activate(Scene& scene) +{ + if (!instantiated) + Create(); + + Starshatter* stars = Starshatter::GetInstance(); + + if (!stars) + return; + + if (point_stars) { + scene.AddBackground(point_stars); + point_stars->Hide(); + } + + if (poly_stars) { + scene.AddBackground(poly_stars); + } + + if (stars->Nebula() && nebula) { + scene.AddBackground(nebula); + } + + if (haze) { + scene.AddBackground(haze); + haze->Hide(); + } + + ListIter star_iter = bodies; + while (++star_iter) { + OrbitalBody* star = star_iter.value(); + scene.AddGraphic(star->rep); + scene.AddLight(star->light_rep); + if (nebula && stars && stars->Nebula()) { + star->back_light->SetColor(star->back); + } + else { + Color c = Color(60,60,65); + star->back_light->SetColor(c); + } + scene.AddLight(star->back_light); + + if (nebula && stars && stars->Nebula()) { + scene.SetAmbient(ambient); + } + else { + Color c = ambient; + int n = (c.Red() + c.Green() + c.Blue()) / 3; + + c = Color(n,n,n); + scene.SetAmbient(c); + } + + ListIter planet = star->Satellites(); + while (++planet) { + scene.AddGraphic(planet->rep); + + ListIter moon = planet->Satellites(); + while (++moon) { + scene.AddGraphic(moon->rep); + } + } + } + + ExecFrame(); +} + +// +--------------------------------------------------------------------+ + +void +StarSystem::Deactivate() +{ + if (!instantiated) + return; + + active_region = 0; + + if (point_stars && point_stars->GetScene()) + point_stars->GetScene()->DelBackground(point_stars); + + if (poly_stars && poly_stars->GetScene()) + poly_stars->GetScene()->DelBackground(poly_stars); + + if (nebula && nebula->GetScene()) + nebula->GetScene()->DelBackground(nebula); + + if (haze && haze->GetScene()) { + haze->GetScene()->DelBackground(haze); + haze->Hide(); + } + + ListIter star = bodies; + while (++star) { + if (star->rep && star->rep->GetScene()) + star->rep->GetScene()->DelGraphic(star->rep); + + if (star->light_rep && star->light_rep->GetScene()) + star->light_rep->GetScene()->DelLight(star->light_rep); + + if (star->back_light && star->back_light->GetScene()) + star->back_light->GetScene()->DelLight(star->back_light); + + ListIter planet = star->Satellites(); + while (++planet) { + if (planet->rep && planet->rep->GetScene()) + planet->rep->GetScene()->DelGraphic(planet->rep); + + ListIter moon = planet->Satellites(); + while (++moon) { + if (moon->rep && moon->rep->GetScene()) + moon->rep->GetScene()->DelGraphic(moon->rep); + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +StarSystem::SetActiveRegion(OrbitalRegion* rgn) +{ + Scene* scene = 0; + + if (active_region != rgn) { + active_region = rgn; + + if (active_region) { + if (active_region->Type() != Orbital::TERRAIN) { + if (point_stars) point_stars->Hide(); + if (poly_stars) poly_stars->Show(); + if (nebula) nebula->Show(); + if (haze) haze->Hide(); + } + else { + if (point_stars) point_stars->Show(); + if (poly_stars) poly_stars->Hide(); + if (nebula) nebula->Hide(); + if (haze) haze->Show(); + } + + if (poly_stars) { + scene = poly_stars->GetScene(); + if (scene) + scene->SetAmbient(ambient); + } + else if (nebula) { + scene = nebula->GetScene(); + if (scene) + scene->SetAmbient(ambient); + } + + ListIter star = bodies; + while (++star) { + if (star->rep) + star->rep->Show(); + } + + ExecFrame(); + } + else { + if (point_stars) point_stars->Hide(); + if (poly_stars) poly_stars->Hide(); + if (nebula) nebula->Hide(); + if (haze) haze->Hide(); + + + ListIter star = bodies; + while (++star) { + if (star->rep) + star->rep->Hide(); + + if (star->light_rep) { + scene = star->light_rep->GetScene(); + if (scene) + scene->DelLight(star->light_rep); + } + + if (star->back_light) { + scene = star->back_light->GetScene(); + if (scene) + scene->DelLight(star->back_light); + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +static BYTE max3(BYTE a, BYTE b, BYTE c) +{ + if (a > b) + if (a > c) return a; + else return c; + else + if (b > c) return b; + else return c; +} + +static BYTE min3(BYTE a, BYTE b, BYTE c) +{ + if (a < b) + if (a < c) return a; + else return c; + else + if (b < c) return b; + else return c; +} + +void +StarSystem::ExecFrame() +{ + CalcStardate(); + + ListIter star = bodies; + while (++star) + star->Update(); + + ListIter region = regions; + while (++region) + region->Update(); + + // update the graphic reps, relative to the active region: + if (instantiated && active_region) { + Point active_loc = active_region->Location(); + active_loc = active_loc.OtherHand(); + + Scene* scene = 0; + TerrainRegion* trgn = 0; + bool terrain = (active_region->Type() == Orbital::TERRAIN); + Matrix terrain_orientation; + Matrix terrain_transformation; + + if (terrain) { + trgn = (TerrainRegion*) active_region; + Color tmp = trgn->SkyColor(); + Game::SetScreenColor(tmp); + + tvpn = (active_region->Location() - active_region->Primary()->Location()); + tvpn.Normalize(); + tvup = Point(0,0,-1); + tvrt = tvpn.cross(tvup); + tvrt.Normalize(); + + terrain_orientation.Rotate(0, PI/2, 0); + terrain_transformation = Matrix(tvrt, tvup, tvpn); + + if (point_stars) { + point_stars->SetOrientation(terrain_transformation); + + Color sky_color = trgn->SkyColor(); + BYTE sky_red = (BYTE) sky_color.Red(); + BYTE sky_green = (BYTE) sky_color.Green(); + BYTE sky_blue = (BYTE) sky_color.Blue(); + + BYTE Max = max3(sky_red, sky_green, sky_blue); + BYTE Min = min3(sky_red, sky_green, sky_blue); + + BYTE lum = (BYTE) (240.0 * (Max + Min) / 510.0); + + if (lum > 50) { + point_stars->Hide(); + } + else { + Stars* pstars = (Stars*) point_stars; + + pstars->Illuminate(1.0 - lum / 50.0); + pstars->Show(); + } + + scene = point_stars->GetScene(); + } + + if (haze) { + ((TerrainHaze*)haze)->UseTerrainRegion(trgn); + scene = haze->GetScene(); + } + + if (scene) { + scene->SetAmbient(ambient); + } + + } + else { + Game::SetScreenColor(Color::Black); + } + + double star_alt = 0; + + ListIter star_iter = bodies; + while (++star_iter) { + OrbitalBody* star = star_iter.value(); + + if (active_region->Inclination() != 0) { + double distance = (active_region->Location() - star->Location()).length(); + star_alt = sin(active_region->Inclination()) * distance; + } + + if (terrain) { + Point sloc = TerrainTransform(star->Location()); + + if (star->rep) { + star->rep->MoveTo(sloc); + + PlanetRep* pr = (PlanetRep*) star->rep; + pr->SetDaytime(true); + } + + if (star->light_rep) { + star->light_rep->MoveTo(sloc); + star->light_rep->SetActive(sloc.y > -100); + } + + if (star->back_light) { + star->back_light->MoveTo(sloc * -1); + star->back_light->SetActive(sloc.y > -100); + } + + if (trgn && star->rep) { + if (trgn->IsEclipsed()) + star->rep->Hide(); + else + star->rep->Show(); + } + } + + else { + Point sloc = Point(star->Location() - active_region->Location()).OtherHand(); + sloc.y = star_alt; + + if (star->rep) { + star->rep->MoveTo(sloc); + + PlanetRep* pr = (PlanetRep*) star->rep; + pr->SetDaytime(false); + } + + if (star->light_rep) { + star->light_rep->MoveTo(sloc); + star->light_rep->SetActive(true); + } + + if (star->back_light) { + star->back_light->MoveTo(sloc * -1); + star->back_light->SetActive(true); + } + } + + ListIter planet_iter = star->Satellites(); + while (++planet_iter) { + OrbitalBody* planet = planet_iter.value(); + + if (planet->rep) { + PlanetRep* pr = (PlanetRep*) planet->rep; + + if (terrain) { + pr->MoveTo(TerrainTransform(planet->Location())); + pr->SetOrientation(terrain_orientation); + + if (planet == active_region->Primary()) { + pr->Hide(); + } + + else { + pr->Show(); + pr->SetDaytime(true); + } + } + else { + pr->Show(); + pr->TranslateBy(active_loc); + pr->SetDaytime(false); + } + } + + ListIter moon_iter = planet->Satellites(); + while (++moon_iter) { + OrbitalBody* moon = moon_iter.value(); + + if (moon->rep) { + PlanetRep* pr = (PlanetRep*) moon->rep; + + if (terrain) { + pr->MoveTo(TerrainTransform(moon->Location())); + pr->SetOrientation(terrain_orientation); + + if (moon == active_region->Primary()) { + pr->Hide(); + } + + else { + pr->Show(); + pr->SetDaytime(true); + } + } + else { + pr->Show(); + pr->TranslateBy(active_loc); + pr->SetDaytime(false); + } + } + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +Orbital* +StarSystem::FindOrbital(const char* name) +{ + if (!name || !name[0]) + return 0; + + ListIter star = bodies; + while (++star) { + if (!stricmp(star->Name(), name)) + return star.value(); + + ListIter star_rgn = star->Regions(); + while (++star_rgn) { + if (!stricmp(star_rgn->Name(), name)) + return star_rgn.value(); + } + + ListIter planet = star->Satellites(); + while (++planet) { + if (!stricmp(planet->Name(), name)) + return planet.value(); + + ListIter planet_rgn = planet->Regions(); + while (++planet_rgn) { + if (!stricmp(planet_rgn->Name(), name)) + return planet_rgn.value(); + } + + ListIter moon = planet->Satellites(); + while (++moon) { + if (!stricmp(moon->Name(), name)) + return moon.value(); + + ListIter moon_rgn = moon->Regions(); + while (++moon_rgn) { + if (!stricmp(moon_rgn->Name(), name)) + return moon_rgn.value(); + } + } + } + } + + ListIter region = regions; + while (++region) { + if (!stricmp(region->Name(), name)) + return region.value(); + } + + return 0; +} + +// +--------------------------------------------------------------------+ + +OrbitalRegion* +StarSystem::FindRegion(const char* name) +{ + if (!name || !name[0]) + return 0; + + ListIter region = all_regions; + while (++region) { + if (!stricmp(region->Name(), name)) + return region.value(); + } + + return 0; +} + +// +--------------------------------------------------------------------+ + +bool +StarSystem::HasLinkTo(StarSystem* s) const +{ + ListIter iter = ((StarSystem*) this)->all_regions; + while (++iter) { + OrbitalRegion* rgn = iter.value(); + + ListIter lnk_iter = rgn->Links(); + while (++lnk_iter) { + Text* t = lnk_iter.value(); + + if (s->FindRegion(*t)) + return true; + } + } + + return false; +} + +// +--------------------------------------------------------------------+ + +Point +StarSystem::TerrainTransform(const Point& loc) +{ + Point result; + + Point tmp = loc - active_region->Location(); + + result.x = tmp * tvrt; + result.z = tmp * tvup; + result.y = tmp * tvpn; + + return result; +} + +Color +StarSystem::Ambient() const +{ + Color result = ambient; + bool terrain = (active_region && active_region->Type() == Orbital::TERRAIN); + + if (terrain) { + TerrainRegion* trgn = (TerrainRegion*) active_region; + result = trgn->Ambient(); + + if (trgn->IsEclipsed()) + result = result * 0.3; + } + + return result; +} + +void +StarSystem::SetSunlight(Color color, double brightness) +{ + sun_color = color; + sun_scale = brightness; + + ListIter sun_iter = sun_lights; + while (++sun_iter) { + Light* sun_light = sun_iter.value(); + sun_light->SetColor(color); + sun_light->SetIntensity((float) (1.1 * sun_scale)); + } + + ListIter back_iter = back_lights; + while (++back_iter) { + Light* back_light = back_iter.value(); + back_light->SetIntensity((float) (0.5 * sun_scale)); + } +} + +void +StarSystem::SetBacklight(Color color, double brightness) +{ + ListIter back_iter = back_lights; + while (++back_iter) { + Light* back_light = back_iter.value(); + back_light->SetColor(color); + back_light->SetIntensity((float) (0.5 * brightness)); + } +} + +void +StarSystem::RestoreTrueSunColor() +{ + ListIter iter = bodies; + while (++iter) { + OrbitalBody* star = iter.value(); + + if (star) { + if (star->light_rep) { + star->light_rep->SetColor(star->LightColor()); + star->light_rep->SetIntensity(1.1f); + } + + if (star->back_light) { + star->back_light->SetColor(star->back); + star->back_light->SetIntensity(0.5f); + } + } + } +} + +// +====================================================================+ + +Color +Star::GetColor() const +{ + return GetColor(seq); +} + +int +Star::GetSize() const +{ + return GetSize(seq); +} + +Color +Star::GetColor(int s) +{ + switch (s) { + case O: return Color(128,128,255); break; + case B: return Color(192,192,255); break; + case A: return Color(220,220,255); break; + case F: return Color(255,255,255); break; + case G: return Color(255,255,128); break; + case K: return Color(255,192,100); break; + case M: return Color(255,100,100); break; + + case RED_GIANT: return Color(255, 80, 80); break; + case WHITE_DWARF: return Color(255,255,255); break; + case BLACK_HOLE: return Color( 0, 0, 0); break; + } + + return Color::White; +} + +int +Star::GetSize(int s) +{ + switch (s) { + case O: return 4; break; + case B: return 4; break; + case A: return 3; break; + case F: return 3; break; + case G: return 2; break; + case K: return 2; break; + case M: return 1; break; + + case RED_GIANT: return 4; break; + case WHITE_DWARF: return 1; break; + case BLACK_HOLE: return 3; break; + } + + return 3; +} + +// +====================================================================+ + +Orbital::Orbital(StarSystem* s, const char* n, OrbitalType t, double m, double r, double o, Orbital* p) + : name(n), type(t), subtype(0), radius(r), mass(m), orbit(o), + phase(0), period(0), rotation(0), retro(false), + system(s), primary(p), loc(0, 0, 0), rep(0), velocity(0) +{ + if (system && primary && orbit > 0) { + velocity = sqrt(GRAV * primary->Mass() / orbit); + period = 2 * PI * orbit / velocity; + } + + Update(); +} + +// +--------------------------------------------------------------------+ + +Orbital::~Orbital() +{ + delete rep; + regions.destroy(); +} + +// +--------------------------------------------------------------------+ + +void +Orbital::Update() +{ + if (system && primary && orbit > 0) { + double grade = (retro) ? -1 : 1; + + // orbits are counter clockwise: + phase = -2 * PI * grade * StarSystem::Stardate() / period; + + loc = primary->Location() + Point((double) (orbit * cos(phase)), + (double) (orbit * sin(phase)), + 0); + } + + ListIter region = regions; + while (++region) + region->Update(); + + if (rep) + rep->MoveTo(loc.OtherHand()); +} + +// +--------------------------------------------------------------------+ + +Point +Orbital::PredictLocation(double delta_t) +{ + Point predicted_loc = Location(); + + if (system && primary && orbit > 0) { + predicted_loc = primary->PredictLocation(delta_t); + + double grade = (retro) ? -1 : 1; + + // orbits are(?) counter clockwise: + double predicted_phase = (double) (-2 * PI * grade * (StarSystem::Stardate()+delta_t) / period); + + predicted_loc += Point((double) (orbit * cos(predicted_phase)), + (double) (orbit * sin(predicted_phase)), + 0); + } + + return predicted_loc; +} + +void +Orbital::SetMapIcon(const Bitmap& img) +{ + if (img.Width() >=64 && img.Height() >= 64) { + map_icon.CopyBitmap(img); + map_icon.AutoMask(); + map_icon.MakeTexture(); + } +} + +// +====================================================================+ + +OrbitalBody::OrbitalBody(StarSystem* s, const char* n, OrbitalType t, double m, double r, double o, Orbital* p) + : Orbital(s, n, t, m, r, o, p), light(0), light_rep(0), back_light(0), + ring_min(0), ring_max(0), tilt(0), luminous(false) +{ + Update(); +} + +// +--------------------------------------------------------------------+ + +OrbitalBody::~OrbitalBody() +{ + satellites.destroy(); +} + +// +--------------------------------------------------------------------+ + +void +OrbitalBody::Update() +{ + Orbital::Update(); + + theta = 0; + + if (rotation > 0) + theta = -2 * PI * StarSystem::Stardate() / rotation; + + ListIter body = satellites; + while (++body) + body->Update(); + + if (rep && theta != 0) { + Matrix m; + m.Pitch(tilt); + m.Roll(tilt/2); + m.Yaw(theta); + rep->SetOrientation(m); + } + + if (light_rep) { + Point bodyloc = loc; + bodyloc = bodyloc.OtherHand(); + light_rep->MoveTo(bodyloc); + } +} + +// +====================================================================+ + +OrbitalRegion::OrbitalRegion(StarSystem* s, const char* n, double m, double r, double o, Orbital* p) + : Orbital(s, n, REGION, m, r, o, p), grid(25.0e3f), inclination(0) +{ +} + +// +--------------------------------------------------------------------+ + +OrbitalRegion::~OrbitalRegion() +{ + links.destroy(); +} + diff --git a/Stars45/StarSystem.h b/Stars45/StarSystem.h new file mode 100644 index 0000000..ab22c28 --- /dev/null +++ b/Stars45/StarSystem.h @@ -0,0 +1,322 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: StarSystem.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Various heavenly bodies +*/ + +#ifndef StarSystem_h +#define StarSystem_h + +#include "Types.h" +#include "Solid.h" +#include "Bitmap.h" +#include "Geometry.h" +#include "Text.h" +#include "term.h" +#include "List.h" + +// +--------------------------------------------------------------------+ + +class StarSystem; +class Orbital; +class OrbitalBody; +class OrbitalRegion; +class TerrainRegion; + +class Graphic; +class Light; +class Scene; + +// +--------------------------------------------------------------------+ + +class StarSystem +{ +public: + static const char* TYPENAME() { return "StarSystem"; } + + StarSystem(const char* name, Point loc, int iff=0, int s=4); + virtual ~StarSystem(); + + int operator == (const StarSystem& s) const { return name == s.name; } + + // operations: + virtual void Load(); + virtual void Create(); + virtual void Destroy(); + + virtual void Activate(Scene& scene); + virtual void Deactivate(); + + virtual void ExecFrame(); + + // accessors: + const char* Name() const { return name; } + const char* Govt() const { return govt; } + const char* Description() const { return description; } + int Affiliation() const { return affiliation; } + int Sequence() const { return seq; } + Point Location() const { return loc; } + int NumStars() const { return sky_stars; } + int NumDust() const { return sky_dust; } + Color Ambient() const; + + List& Bodies() { return bodies; } + List& Regions() { return regions; } + List& AllRegions() { return all_regions; } + OrbitalRegion* ActiveRegion() { return active_region; } + + Orbital* FindOrbital(const char* name); + OrbitalRegion* FindRegion(const char* name); + + void SetActiveRegion(OrbitalRegion* rgn); + + static void SetBaseTime(double t, bool absolute=false); + static double GetBaseTime(); + static double Stardate() { return stardate; } + static void CalcStardate(); + double Radius() const { return radius; } + + void SetSunlight(Color color, double brightness=1); + void SetBacklight(Color color, double brightness=1); + void RestoreTrueSunColor(); + bool HasLinkTo(StarSystem* s) const; + const Text& GetDataPath() const { return datapath; } + +protected: + void ParseStar(TermStruct* val); + void ParsePlanet(TermStruct* val); + void ParseMoon(TermStruct* val); + void ParseRegion(TermStruct* val); + void ParseTerrain(TermStruct* val); + void ParseLayer(TerrainRegion* rgn, TermStruct* val); + + void CreateBody(OrbitalBody& body); + Point TerrainTransform(const Point& loc); + + char filename[64]; + Text name; + Text govt; + Text description; + Text datapath; + int affiliation; + int seq; + Point loc; + static double stardate; + double radius; + bool instantiated; + + int sky_stars; + int sky_dust; + Text sky_poly_stars; + Text sky_nebula; + Text sky_haze; + double sky_uscale; + double sky_vscale; + Color ambient; + Color sun_color; + double sun_brightness; + double sun_scale; + List sun_lights; + List back_lights; + + Graphic* point_stars; + Solid* poly_stars; + Solid* nebula; + Solid* haze; + + List bodies; + List regions; + List all_regions; + + Orbital* center; + OrbitalRegion* active_region; + + Point tvpn, tvup, tvrt; +}; + +// +--------------------------------------------------------------------+ + +class Star +{ +public: + static const char* TYPENAME() { return "Star"; } + + Star(const char* n, const Point& l, int s) : name(n), loc(l), seq(s) { } + virtual ~Star() { } + + enum SPECTRAL_CLASS { BLACK_HOLE, WHITE_DWARF, RED_GIANT, + O, B, A, F, G, K, M }; + + int operator == (const Star& s) const { return name == s.name; } + + // accessors: + const char* Name() const { return name; } + const Point& Location() const { return loc; } + int Sequence() const { return seq; } + Color GetColor() const; + int GetSize() const; + + static Color GetColor(int spectral_class); + static int GetSize(int spectral_class); + +protected: + Text name; + Point loc; + int seq; +}; + +// +--------------------------------------------------------------------+ + +class Orbital +{ + friend class StarSystem; + +public: + static const char* TYPENAME() { return "Orbital"; } + + enum OrbitalType { NOTHING, STAR, PLANET, MOON, REGION, TERRAIN }; + + Orbital(StarSystem* sys, const char* n, OrbitalType t, double m, double r, double o, Orbital* p=0); + virtual ~Orbital(); + + int operator == (const Orbital& o) const { return type == o.type && name == o.name && system == o.system; } + int operator < (const Orbital& o) const { return loc.length() < o.loc.length(); } + int operator <= (const Orbital& o) const { return loc.length() <= o.loc.length(); } + + // operations: + virtual void Update(); + Point PredictLocation(double delta_t); + + // accessors: + const char* Name() const { return name; } + OrbitalType Type() const { return type; } + int SubType() const { return subtype; } + + const char* Description() const { return description; } + double Mass() const { return mass; } + double Radius() const { return radius; } + double Rotation() const { return rotation; } + double RotationPhase()const { return theta; } + double Orbit() const { return orbit; } + bool Retrograde() const { return retro; } + double Phase() const { return phase; } + double Period() const { return period; } + Point Location() const { return loc; } + Graphic* Rep() const { return rep; } + + const Bitmap& GetMapIcon() const { return map_icon; } + void SetMapIcon(const Bitmap& img); + + StarSystem* System() const { return system; } + Orbital* Primary() const { return primary; } + ListIter Regions() { return regions; } + +protected: + Text name; + OrbitalType type; + int subtype; + + Text description; + double mass; + double radius; + double rotation; + double theta; + double orbit; + double phase; + double period; + double velocity; + Point loc; + bool retro; + Graphic* rep; + Bitmap map_icon; + + StarSystem* system; + Orbital* primary; + + List regions; +}; + +// +--------------------------------------------------------------------+ + +class OrbitalBody : public Orbital +{ + friend class StarSystem; + +public: + static const char* TYPENAME() { return "OrbitalBody"; } + + OrbitalBody(StarSystem* sys, const char* n, OrbitalType t, double m, double r, double o, Orbital* prime=0); + virtual ~OrbitalBody(); + + // operations: + virtual void Update(); + + // accessors: + ListIter Satellites() { return satellites; } + + double Tilt() const { return tilt; } + double RingMin() const { return ring_min; } + double RingMax() const { return ring_max; } + + double LightIntensity() const { return light; } + Color LightColor() const { return color; } + bool Luminous() const { return luminous; } + +protected: + Text map_name; + Text tex_name; + Text tex_high_res; + Text tex_ring; + Text tex_glow; + Text tex_glow_high_res; + Text tex_gloss; + + double tscale; + double light; + double ring_min; + double ring_max; + double tilt; + Light* light_rep; + Light* back_light; + Color color; + Color back; + Color atmosphere; + bool luminous; + + List satellites; +}; + +// +--------------------------------------------------------------------+ + +class OrbitalRegion : public Orbital +{ + friend class StarSystem; + +public: + static const char* TYPENAME() { return "OrbitalRegion"; } + + OrbitalRegion(StarSystem* sys, const char* n, double m, double r, double o, Orbital* prime=0); + virtual ~OrbitalRegion(); + + double GridSpace() const { return grid; } + double Inclination() const { return inclination; } + int Asteroids() const { return asteroids; } + List& Links() { return links; } + +protected: + double grid; + double inclination; + int asteroids; + List links; +}; + +#endif StarSystem_h + diff --git a/Stars45/Stars.dsp b/Stars45/Stars.dsp new file mode 100644 index 0000000..8933134 --- /dev/null +++ b/Stars45/Stars.dsp @@ -0,0 +1,1585 @@ +# Microsoft Developer Studio Project File - Name="Stars" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Application" 0x0101 + +CFG=Stars - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "Stars.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "Stars.mak" CFG="Stars - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "Stars - Win32 Release" (based on "Win32 (x86) Application") +!MESSAGE "Stars - Win32 Debug" (based on "Win32 (x86) Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "Stars - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "../FoundationEx" /I "../nGenEx" /I "../NetEx" /I "../Parser" /I "../Opcode/OpcodeLib" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 +# ADD LINK32 ..\Opcode\OpcodeLib\Release\OpcodeLib.lib ..\ngenex\release\ngenex.lib ..\netex\release\netex.lib ..\zlib\release\zlib.lib ..\libpng\release\libpng.lib wsock32.lib dinput.lib dsound.lib d3d9.lib d3dx9.lib dxguid.lib winmm.lib version.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib ogg_static.lib vorbis_static.lib vorbisfile_static.lib Vfw32.lib /nologo /subsystem:windows /profile /map:"Game/Stars.map" /debug /machine:I386 /out:"Game/Stars.exe" + +!ELSEIF "$(CFG)" == "Stars - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../FoundationEx" /I "../nGenEx" /I "../NetEx" /I "../Parser" /I "../Opcode/OpcodeLib" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /FR /YX /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept +# ADD LINK32 ..\Opcode\OpcodeLib\Debug\OpcodeLib.lib ..\ngenex\debug\ngenex.lib ..\netex\debug\netex.lib ..\zlib\debug\zlib.lib ..\libpng\debug\libpng.lib wsock32.lib dinput.lib dsound.lib d3d9.lib d3dx9.lib dxguid.lib winmm.lib version.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib ogg_static.lib vorbis_static.lib vorbisfile_static.lib Vfw32.lib /nologo /subsystem:windows /map /debug /machine:I386 /out:"Game/Stars_d.exe" /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "Stars - Win32 Release" +# Name "Stars - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\Asteroid.cpp +# End Source File +# Begin Source File + +SOURCE=.\AudDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\AudioConfig.cpp +# End Source File +# Begin Source File + +SOURCE=.\Authorization.cpp +# End Source File +# Begin Source File + +SOURCE=.\AwardDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\AwardShowDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\Callsign.cpp +# End Source File +# Begin Source File + +SOURCE=.\CameraDirector.cpp +# End Source File +# Begin Source File + +SOURCE=..\nGenEx\CameraView.cpp + +!IF "$(CFG)" == "Stars - Win32 Release" + +# ADD CPP /FAcs + +!ELSEIF "$(CFG)" == "Stars - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\Campaign.cpp +# End Source File +# Begin Source File + +SOURCE=.\CampaignMissionFighter.cpp +# End Source File +# Begin Source File + +SOURCE=.\CampaignMissionRequest.cpp +# End Source File +# Begin Source File + +SOURCE=.\CampaignMissionStarship.cpp +# End Source File +# Begin Source File + +SOURCE=.\CampaignPlanAssignment.cpp +# End Source File +# Begin Source File + +SOURCE=.\CampaignPlanEvent.cpp +# End Source File +# Begin Source File + +SOURCE=.\CampaignPlanMission.cpp +# End Source File +# Begin Source File + +SOURCE=.\CampaignPlanMovement.cpp +# End Source File +# Begin Source File + +SOURCE=.\CampaignPlanStrategic.cpp +# End Source File +# Begin Source File + +SOURCE=.\CampaignSaveGame.cpp +# End Source File +# Begin Source File + +SOURCE=.\CampaignSituationReport.cpp +# End Source File +# Begin Source File + +SOURCE=.\CarrierAI.cpp +# End Source File +# Begin Source File + +SOURCE=.\CmdDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\CmdForceDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\CmdIntelDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\CmdMissionsDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\CmdMsgDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\CmdOrdersDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\CmdTheaterDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\CmdTitleDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\CmpCompleteDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\CmpFileDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\CmpLoadDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\CmpnScreen.cpp +# End Source File +# Begin Source File + +SOURCE=.\CmpSceneDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\CmpSelectDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\CombatAction.cpp +# End Source File +# Begin Source File + +SOURCE=.\Combatant.cpp +# End Source File +# Begin Source File + +SOURCE=.\CombatAssignment.cpp +# End Source File +# Begin Source File + +SOURCE=.\CombatEvent.cpp +# End Source File +# Begin Source File + +SOURCE=.\CombatGroup.cpp +# End Source File +# Begin Source File + +SOURCE=.\CombatRoster.cpp +# End Source File +# Begin Source File + +SOURCE=.\CombatUnit.cpp +# End Source File +# Begin Source File + +SOURCE=.\CombatZone.cpp +# End Source File +# Begin Source File + +SOURCE=.\Component.cpp +# End Source File +# Begin Source File + +SOURCE=.\Computer.cpp +# End Source File +# Begin Source File + +SOURCE=.\ConfirmDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\Contact.cpp +# End Source File +# Begin Source File + +SOURCE=.\CtlDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\DebriefDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\Debris.cpp +# End Source File +# Begin Source File + +SOURCE=.\DetailSet.cpp +# End Source File +# Begin Source File + +SOURCE=.\DisplayView.cpp +# End Source File +# Begin Source File + +SOURCE=.\Drive.cpp +# End Source File +# Begin Source File + +SOURCE=.\DriveSprite.cpp +# End Source File +# Begin Source File + +SOURCE=.\Drone.cpp +# End Source File +# Begin Source File + +SOURCE=.\DropShipAI.cpp +# End Source File +# Begin Source File + +SOURCE=.\Element.cpp +# End Source File +# Begin Source File + +SOURCE=.\EngDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\ExceptionHandler.cpp +# End Source File +# Begin Source File + +SOURCE=.\ExitDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\Explosion.cpp +# End Source File +# Begin Source File + +SOURCE=.\Farcaster.cpp +# End Source File +# Begin Source File + +SOURCE=.\FighterAI.cpp +# End Source File +# Begin Source File + +SOURCE=.\FighterTacticalAI.cpp +# End Source File +# Begin Source File + +SOURCE=.\FirstTimeDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\FlightComp.cpp +# End Source File +# Begin Source File + +SOURCE=.\FlightDeck.cpp +# End Source File +# Begin Source File + +SOURCE=.\FlightPlanner.cpp +# End Source File +# Begin Source File + +SOURCE=.\FltDlg.cpp +# End Source File +# Begin Source File + +SOURCE=..\nGenEx\Font.cpp +# End Source File +# Begin Source File + +SOURCE=..\nGenEx\FormDef.cpp +# End Source File +# Begin Source File + +SOURCE=.\Galaxy.cpp +# End Source File +# Begin Source File + +SOURCE=.\GameScreen.cpp +# End Source File +# Begin Source File + +SOURCE=.\Grid.cpp +# End Source File +# Begin Source File + +SOURCE=.\GroundAI.cpp +# End Source File +# Begin Source File + +SOURCE=.\Hangar.cpp +# End Source File +# Begin Source File + +SOURCE=.\HardPoint.cpp +# End Source File +# Begin Source File + +SOURCE=.\Hoop.cpp +# End Source File +# Begin Source File + +SOURCE=.\HUDSounds.cpp +# End Source File +# Begin Source File + +SOURCE=.\HUDView.cpp +# End Source File +# Begin Source File + +SOURCE=.\Instruction.cpp +# End Source File +# Begin Source File + +SOURCE=.\Intel.cpp +# End Source File +# Begin Source File + +SOURCE=.\JoyDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\KeyDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\KeyMap.cpp +# End Source File +# Begin Source File + +SOURCE=.\LandingGear.cpp +# End Source File +# Begin Source File + +SOURCE=.\LoadDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\LoadScreen.cpp +# End Source File +# Begin Source File + +SOURCE=.\Main.cpp +# End Source File +# Begin Source File + +SOURCE=.\MapView.cpp +# End Source File +# Begin Source File + +SOURCE=.\MenuDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\MenuScreen.cpp +# End Source File +# Begin Source File + +SOURCE=.\MenuView.cpp +# End Source File +# Begin Source File + +SOURCE=.\Mfd.cpp +# End Source File +# Begin Source File + +SOURCE=.\Mission.cpp +# End Source File +# Begin Source File + +SOURCE=.\MissionEvent.cpp +# End Source File +# Begin Source File + +SOURCE=.\MissionTemplate.cpp +# End Source File +# Begin Source File + +SOURCE=.\ModConfig.cpp +# End Source File +# Begin Source File + +SOURCE=.\ModDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\ModInfo.cpp +# End Source File +# Begin Source File + +SOURCE=.\ModInfoDlg.cpp +# End Source File +# Begin Source File + +SOURCE=..\nGenEx\Mouse.cpp +# End Source File +# Begin Source File + +SOURCE=.\MsnDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\MsnEditDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\MsnEditNavDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\MsnElemDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\MsnEventDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\MsnNavDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\MsnObjDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\MsnPkgDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\MsnSelectDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\MsnWepDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\MusicDirector.cpp +# End Source File +# Begin Source File + +SOURCE=.\MusicTrack.cpp +# End Source File +# Begin Source File + +SOURCE=.\NavAI.cpp +# End Source File +# Begin Source File + +SOURCE=.\NavDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\NavLight.cpp +# End Source File +# Begin Source File + +SOURCE=.\NavSystem.cpp +# End Source File +# Begin Source File + +SOURCE=.\NetAddrDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\NetAdminChat.cpp +# End Source File +# Begin Source File + +SOURCE=.\NetAdminServer.cpp +# End Source File +# Begin Source File + +SOURCE=.\NetAuth.cpp +# End Source File +# Begin Source File + +SOURCE=.\NetBrokerClient.cpp +# End Source File +# Begin Source File + +SOURCE=.\NetChat.cpp +# End Source File +# Begin Source File + +SOURCE=.\NetClientConfig.cpp +# End Source File +# Begin Source File + +SOURCE=.\NetClientDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\NetData.cpp +# End Source File +# Begin Source File + +SOURCE=.\NetFileServlet.cpp +# End Source File +# Begin Source File + +SOURCE=.\NetGame.cpp +# End Source File +# Begin Source File + +SOURCE=.\NetGameClient.cpp +# End Source File +# Begin Source File + +SOURCE=.\NetGameServer.cpp +# End Source File +# Begin Source File + +SOURCE=.\NetLobby.cpp +# End Source File +# Begin Source File + +SOURCE=.\NetLobbyClient.cpp +# End Source File +# Begin Source File + +SOURCE=.\NetLobbyDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\NetLobbyServer.cpp +# End Source File +# Begin Source File + +SOURCE=.\NetPacket.cpp +# End Source File +# Begin Source File + +SOURCE=.\NetPassDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\NetPlayer.cpp +# End Source File +# Begin Source File + +SOURCE=.\NetServerConfig.cpp +# End Source File +# Begin Source File + +SOURCE=.\NetServerDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\NetUnitDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\NetUser.cpp +# End Source File +# Begin Source File + +SOURCE=.\NetUtil.cpp +# End Source File +# Begin Source File + +SOURCE=.\NPClientWraps.cpp +# End Source File +# Begin Source File + +SOURCE=.\OptDlg.cpp +# End Source File +# Begin Source File + +SOURCE=..\nGenEx\Particles.cpp +# End Source File +# Begin Source File + +SOURCE=.\PlanScreen.cpp +# End Source File +# Begin Source File + +SOURCE=.\Player.cpp +# End Source File +# Begin Source File + +SOURCE=.\PlayerDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\Power.cpp +# End Source File +# Begin Source File + +SOURCE=.\QuantumDrive.cpp +# End Source File +# Begin Source File + +SOURCE=.\QuantumFlash.cpp +# End Source File +# Begin Source File + +SOURCE=.\QuantumView.cpp +# End Source File +# Begin Source File + +SOURCE=.\QuitView.cpp +# End Source File +# Begin Source File + +SOURCE=.\RadioHandler.cpp +# End Source File +# Begin Source File + +SOURCE=.\RadioMessage.cpp +# End Source File +# Begin Source File + +SOURCE=.\RadioTraffic.cpp +# End Source File +# Begin Source File + +SOURCE=.\RadioView.cpp +# End Source File +# Begin Source File + +SOURCE=.\RadioVox.cpp +# End Source File +# Begin Source File + +SOURCE=.\RLoc.cpp +# End Source File +# Begin Source File + +SOURCE=.\SeekerAI.cpp +# End Source File +# Begin Source File + +SOURCE=.\Sensor.cpp +# End Source File +# Begin Source File + +SOURCE=.\Shield.cpp +# End Source File +# Begin Source File + +SOURCE=.\ShieldRep.cpp +# End Source File +# Begin Source File + +SOURCE=.\Ship.cpp +# End Source File +# Begin Source File + +SOURCE=.\ShipAI.cpp +# End Source File +# Begin Source File + +SOURCE=.\ShipCtrl.cpp +# End Source File +# Begin Source File + +SOURCE=.\ShipDesign.cpp +# End Source File +# Begin Source File + +SOURCE=.\ShipKiller.cpp +# End Source File +# Begin Source File + +SOURCE=.\ShipSolid.cpp +# End Source File +# Begin Source File + +SOURCE=.\Shot.cpp +# End Source File +# Begin Source File + +SOURCE=.\Sim.cpp + +!IF "$(CFG)" == "Stars - Win32 Release" + +# ADD CPP /FAcs + +!ELSEIF "$(CFG)" == "Stars - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\SimEvent.cpp +# End Source File +# Begin Source File + +SOURCE=.\SimObject.cpp +# End Source File +# Begin Source File + +SOURCE=.\Sky.cpp +# End Source File +# Begin Source File + +SOURCE=.\Stars.rc +# End Source File +# Begin Source File + +SOURCE=.\StarServer.cpp +# End Source File +# Begin Source File + +SOURCE=.\Starshatter.cpp +# End Source File +# Begin Source File + +SOURCE=.\StarshipAI.cpp +# End Source File +# Begin Source File + +SOURCE=.\StarshipTacticalAI.cpp +# End Source File +# Begin Source File + +SOURCE=.\StarSystem.cpp +# End Source File +# Begin Source File + +SOURCE=.\SteerAI.cpp +# End Source File +# Begin Source File + +SOURCE=.\System.cpp +# End Source File +# Begin Source File + +SOURCE=.\SystemDesign.cpp +# End Source File +# Begin Source File + +SOURCE=.\TacRefDlg.cpp +# End Source File +# Begin Source File + +SOURCE=.\TacticalAI.cpp +# End Source File +# Begin Source File + +SOURCE=.\TacticalView.cpp +# End Source File +# Begin Source File + +SOURCE=.\Terrain.cpp +# End Source File +# Begin Source File + +SOURCE=.\TerrainApron.cpp +# End Source File +# Begin Source File + +SOURCE=.\TerrainClouds.cpp +# End Source File +# Begin Source File + +SOURCE=.\TerrainHaze.cpp +# End Source File +# Begin Source File + +SOURCE=.\TerrainPatch.cpp +# End Source File +# Begin Source File + +SOURCE=.\TerrainRegion.cpp +# End Source File +# Begin Source File + +SOURCE=.\Thruster.cpp +# End Source File +# Begin Source File + +SOURCE=.\TrackIR.cpp +# End Source File +# Begin Source File + +SOURCE=.\Trail.cpp +# End Source File +# Begin Source File + +SOURCE=.\VidDlg.cpp +# End Source File +# Begin Source File + +SOURCE=..\nGenEx\Water.cpp +# End Source File +# Begin Source File + +SOURCE=.\Weapon.cpp +# End Source File +# Begin Source File + +SOURCE=.\WeaponDesign.cpp +# End Source File +# Begin Source File + +SOURCE=.\WeaponGroup.cpp +# End Source File +# Begin Source File + +SOURCE=.\Weather.cpp +# End Source File +# Begin Source File + +SOURCE=.\WepView.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\Asteroid.h +# End Source File +# Begin Source File + +SOURCE=.\AudDlg.h +# End Source File +# Begin Source File + +SOURCE=.\AudioConfig.h +# End Source File +# Begin Source File + +SOURCE=.\Authorization.h +# End Source File +# Begin Source File + +SOURCE=.\AwardDlg.h +# End Source File +# Begin Source File + +SOURCE=.\AwardShowDlg.h +# End Source File +# Begin Source File + +SOURCE=.\BaseScreen.h +# End Source File +# Begin Source File + +SOURCE=.\Callsign.h +# End Source File +# Begin Source File + +SOURCE=.\CameraDirector.h +# End Source File +# Begin Source File + +SOURCE=.\Campaign.h +# End Source File +# Begin Source File + +SOURCE=.\CampaignMissionFighter.h +# End Source File +# Begin Source File + +SOURCE=.\CampaignMissionRequest.h +# End Source File +# Begin Source File + +SOURCE=.\CampaignMissionStarship.h +# End Source File +# Begin Source File + +SOURCE=.\CampaignPlan.h +# End Source File +# Begin Source File + +SOURCE=.\CampaignPlanAssignment.h +# End Source File +# Begin Source File + +SOURCE=.\CampaignPlanEvent.h +# End Source File +# Begin Source File + +SOURCE=.\CampaignPlanMission.h +# End Source File +# Begin Source File + +SOURCE=.\CampaignPlanMovement.h +# End Source File +# Begin Source File + +SOURCE=.\CampaignPlanStrategic.h +# End Source File +# Begin Source File + +SOURCE=.\CampaignSituationReport.h +# End Source File +# Begin Source File + +SOURCE=.\CmdDlg.h +# End Source File +# Begin Source File + +SOURCE=.\CmdForceDlg.h +# End Source File +# Begin Source File + +SOURCE=.\CmdIntelDlg.h +# End Source File +# Begin Source File + +SOURCE=.\CmdMissionsDlg.h +# End Source File +# Begin Source File + +SOURCE=.\CmdMsgDlg.h +# End Source File +# Begin Source File + +SOURCE=.\CmdOrdersDlg.h +# End Source File +# Begin Source File + +SOURCE=.\CmdTheaterDlg.h +# End Source File +# Begin Source File + +SOURCE=.\CmdTitleDlg.h +# End Source File +# Begin Source File + +SOURCE=.\CmpCompleteDlg.h +# End Source File +# Begin Source File + +SOURCE=.\CmpFileDlg.h +# End Source File +# Begin Source File + +SOURCE=.\CmpLoadDlg.h +# End Source File +# Begin Source File + +SOURCE=.\CmpnScreen.h +# End Source File +# Begin Source File + +SOURCE=.\CmpSceneDlg.h +# End Source File +# Begin Source File + +SOURCE=.\CmpSelectDlg.h +# End Source File +# Begin Source File + +SOURCE=.\CombatAction.h +# End Source File +# Begin Source File + +SOURCE=.\CombatEvent.h +# End Source File +# Begin Source File + +SOURCE=.\CombatGroup.h +# End Source File +# Begin Source File + +SOURCE=.\CombatRoster.h +# End Source File +# Begin Source File + +SOURCE=.\CombatUnit.h +# End Source File +# Begin Source File + +SOURCE=.\CombatZone.h +# End Source File +# Begin Source File + +SOURCE=.\Component.h +# End Source File +# Begin Source File + +SOURCE=.\ConfirmDlg.h +# End Source File +# Begin Source File + +SOURCE=.\CtlDlg.h +# End Source File +# Begin Source File + +SOURCE=..\nGenEx\DataLoader.h +# End Source File +# Begin Source File + +SOURCE=.\DebriefDlg.h +# End Source File +# Begin Source File + +SOURCE=.\Debris.h +# End Source File +# Begin Source File + +SOURCE=.\DetailSet.h +# End Source File +# Begin Source File + +SOURCE=.\DisplayView.h +# End Source File +# Begin Source File + +SOURCE=.\DriveSprite.h +# End Source File +# Begin Source File + +SOURCE=.\Drone.h +# End Source File +# Begin Source File + +SOURCE=.\DropShipAI.h +# End Source File +# Begin Source File + +SOURCE=.\Element.h +# End Source File +# Begin Source File + +SOURCE=.\EngDlg.h +# End Source File +# Begin Source File + +SOURCE=.\ExitDlg.h +# End Source File +# Begin Source File + +SOURCE=.\Explosion.h +# End Source File +# Begin Source File + +SOURCE=.\FighterAI.h +# End Source File +# Begin Source File + +SOURCE=.\FighterTacticalAI.h +# End Source File +# Begin Source File + +SOURCE=.\FlightPlanner.h +# End Source File +# Begin Source File + +SOURCE=.\FltDlg.h +# End Source File +# Begin Source File + +SOURCE=.\Galaxy.h +# End Source File +# Begin Source File + +SOURCE=.\GameScreen.h +# End Source File +# Begin Source File + +SOURCE=.\Grid.h +# End Source File +# Begin Source File + +SOURCE=.\GroundAI.h +# End Source File +# Begin Source File + +SOURCE=.\Hoop.h +# End Source File +# Begin Source File + +SOURCE=.\HUDSounds.h +# End Source File +# Begin Source File + +SOURCE=.\HUDView.h +# End Source File +# Begin Source File + +SOURCE=.\Instruction.h +# End Source File +# Begin Source File + +SOURCE=.\Intel.h +# End Source File +# Begin Source File + +SOURCE=.\JoyDlg.h +# End Source File +# Begin Source File + +SOURCE=.\KeyDlg.h +# End Source File +# Begin Source File + +SOURCE=.\KeyMap.h +# End Source File +# Begin Source File + +SOURCE=.\LoadDlg.h +# End Source File +# Begin Source File + +SOURCE=.\LoadScreen.h +# End Source File +# Begin Source File + +SOURCE=.\MapView.h +# End Source File +# Begin Source File + +SOURCE=.\MenuDlg.h +# End Source File +# Begin Source File + +SOURCE=.\MenuScreen.h +# End Source File +# Begin Source File + +SOURCE=.\MenuView.h +# End Source File +# Begin Source File + +SOURCE=.\Mfd.h +# End Source File +# Begin Source File + +SOURCE=.\Mission.h +# End Source File +# Begin Source File + +SOURCE=.\MissionEvent.h +# End Source File +# Begin Source File + +SOURCE=.\MissionTemplate.h +# End Source File +# Begin Source File + +SOURCE=.\ModConfig.h +# End Source File +# Begin Source File + +SOURCE=.\ModDlg.h +# End Source File +# Begin Source File + +SOURCE=.\ModInfo.h +# End Source File +# Begin Source File + +SOURCE=.\ModInfoDlg.h +# End Source File +# Begin Source File + +SOURCE=.\MsnDlg.h +# End Source File +# Begin Source File + +SOURCE=.\MsnEditDlg.h +# End Source File +# Begin Source File + +SOURCE=.\MsnElemDlg.h +# End Source File +# Begin Source File + +SOURCE=.\MsnEventDlg.h +# End Source File +# Begin Source File + +SOURCE=.\MsnNavDlg.h +# End Source File +# Begin Source File + +SOURCE=.\MsnObjDlg.h +# End Source File +# Begin Source File + +SOURCE=.\MsnPkgDlg.h +# End Source File +# Begin Source File + +SOURCE=.\MsnSelectDlg.h +# End Source File +# Begin Source File + +SOURCE=.\MsnWepDlg.h +# End Source File +# Begin Source File + +SOURCE=.\MusicDirector.h +# End Source File +# Begin Source File + +SOURCE=.\MusicTrack.h +# End Source File +# Begin Source File + +SOURCE=.\NavAI.h +# End Source File +# Begin Source File + +SOURCE=.\NavDlg.h +# End Source File +# Begin Source File + +SOURCE=.\NavLight.h +# End Source File +# Begin Source File + +SOURCE=.\NetAddrDlg.h +# End Source File +# Begin Source File + +SOURCE=.\NetAdminChat.h +# End Source File +# Begin Source File + +SOURCE=.\NetAdminServer.h +# End Source File +# Begin Source File + +SOURCE=.\NetAuth.h +# End Source File +# Begin Source File + +SOURCE=.\NetBrokerClient.h +# End Source File +# Begin Source File + +SOURCE=.\NetChat.h +# End Source File +# Begin Source File + +SOURCE=.\NetClientConfig.h +# End Source File +# Begin Source File + +SOURCE=.\NetClientDlg.h +# End Source File +# Begin Source File + +SOURCE=.\NetData.h +# End Source File +# Begin Source File + +SOURCE=.\NetFileServlet.h +# End Source File +# Begin Source File + +SOURCE=.\NetGame.h +# End Source File +# Begin Source File + +SOURCE=.\NetGameClient.h +# End Source File +# Begin Source File + +SOURCE=.\NetGameServer.h +# End Source File +# Begin Source File + +SOURCE=.\NetLobby.h +# End Source File +# Begin Source File + +SOURCE=.\NetLobbyClient.h +# End Source File +# Begin Source File + +SOURCE=.\NetLobbyDlg.h +# End Source File +# Begin Source File + +SOURCE=.\NetLobbyServer.h +# End Source File +# Begin Source File + +SOURCE=.\NetPacket.h +# End Source File +# Begin Source File + +SOURCE=.\NetPassDlg.h +# End Source File +# Begin Source File + +SOURCE=.\NetPlayer.h +# End Source File +# Begin Source File + +SOURCE=.\NetServerConfig.h +# End Source File +# Begin Source File + +SOURCE=.\NetServerDlg.h +# End Source File +# Begin Source File + +SOURCE=.\NetUnitDlg.h +# End Source File +# Begin Source File + +SOURCE=.\NetUtil.h +# End Source File +# Begin Source File + +SOURCE=.\NPClientWraps.h +# End Source File +# Begin Source File + +SOURCE=.\OptDlg.h +# End Source File +# Begin Source File + +SOURCE=.\PlanScreen.h +# End Source File +# Begin Source File + +SOURCE=.\Player.h +# End Source File +# Begin Source File + +SOURCE=.\PlayerDlg.h +# End Source File +# Begin Source File + +SOURCE=.\Power.h +# End Source File +# Begin Source File + +SOURCE=.\QuantumFlash.h +# End Source File +# Begin Source File + +SOURCE=.\QuantumView.h +# End Source File +# Begin Source File + +SOURCE=.\RadioHandler.h +# End Source File +# Begin Source File + +SOURCE=.\RadioMessage.h +# End Source File +# Begin Source File + +SOURCE=.\RadioView.h +# End Source File +# Begin Source File + +SOURCE=.\RadioVox.h +# End Source File +# Begin Source File + +SOURCE=.\RLoc.h +# End Source File +# Begin Source File + +SOURCE=.\SeekerAI.h +# End Source File +# Begin Source File + +SOURCE=.\ShieldRep.h +# End Source File +# Begin Source File + +SOURCE=.\ShipCtrl.h +# End Source File +# Begin Source File + +SOURCE=.\ShipKiller.h +# End Source File +# Begin Source File + +SOURCE=.\StarServer.h +# End Source File +# Begin Source File + +SOURCE=.\Starshatter.h +# End Source File +# Begin Source File + +SOURCE=.\StarshipAI.h +# End Source File +# Begin Source File + +SOURCE=.\StarshipTacticalAI.h +# End Source File +# Begin Source File + +SOURCE=.\StarSystem.h +# End Source File +# Begin Source File + +SOURCE=.\SteerAI.h +# End Source File +# Begin Source File + +SOURCE=.\SystemDesign.h +# End Source File +# Begin Source File + +SOURCE=.\TacRefDlg.h +# End Source File +# Begin Source File + +SOURCE=.\TacticalAI.h +# End Source File +# Begin Source File + +SOURCE=.\Terrain.h +# End Source File +# Begin Source File + +SOURCE=.\TerrainApron.h +# End Source File +# Begin Source File + +SOURCE=.\TerrainClouds.h +# End Source File +# Begin Source File + +SOURCE=.\TerrainHaze.h +# End Source File +# Begin Source File + +SOURCE=.\TerrainLayer.h +# End Source File +# Begin Source File + +SOURCE=.\TerrainPatch.h +# End Source File +# Begin Source File + +SOURCE=.\Thruster.h +# End Source File +# Begin Source File + +SOURCE=.\TrackIR.h +# End Source File +# Begin Source File + +SOURCE=.\Trail.h +# End Source File +# Begin Source File + +SOURCE=.\VidDlg.h +# End Source File +# Begin Source File + +SOURCE=.\WeaponDesign.h +# End Source File +# Begin Source File + +SOURCE=.\Weather.h +# End Source File +# Begin Source File + +SOURCE=.\WepView.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# Begin Source File + +SOURCE=.\Stars.ico +# End Source File +# End Group +# End Target +# End Project diff --git a/Stars45/Stars.dsw b/Stars45/Stars.dsw new file mode 100644 index 0000000..f7bf4fe --- /dev/null +++ b/Stars45/Stars.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "Stars"=.\Stars.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/Stars45/Stars.ico b/Stars45/Stars.ico new file mode 100644 index 0000000..69980ca Binary files /dev/null and b/Stars45/Stars.ico differ diff --git a/Stars45/Stars.rc b/Stars45/Stars.rc new file mode 100644 index 0000000..4eaa114 --- /dev/null +++ b/Stars45/Stars.rc @@ -0,0 +1,99 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +Stars ICON DISCARDABLE "Stars.ico" + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 4,5,0,0 + PRODUCTVERSION 4,5,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "Comments", "\0" + VALUE "CompanyName", "Destroyer Studios\0" + VALUE "FileDescription", "Stars\0" + VALUE "FileVersion", "5, 0, 0, 0\0" + VALUE "InternalName", "Stars\0" + VALUE "LegalCopyright", "Copyright © 2006\0" + VALUE "LegalTrademarks", "\0" + VALUE "OriginalFilename", "Stars.exe\0" + VALUE "PrivateBuild", "\0" + VALUE "ProductName", "Starshatter\0" + VALUE "ProductVersion", "5, 0, 0, 0\0" + VALUE "SpecialBuild", "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // !_MAC + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + + diff --git a/Stars45/Stars.vcxproj b/Stars45/Stars.vcxproj new file mode 100644 index 0000000..37a5cc1 --- /dev/null +++ b/Stars45/Stars.vcxproj @@ -0,0 +1,522 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Template + Win32 + + + + + + + + + Application + + + Application + false + MultiByte + + + Application + false + MultiByte + + + + + + + + + + + + + + + + + + .\Debug\ + .\Debug\ + true + + + .\Release\ + .\Release\ + false + + + + MultiThreadedDebug + Default + false + Disabled + true + Level3 + true + EditAndContinue + ../FoundationEx;../nGenEx;../NetEx;../Parser;../Opcode/OpcodeLib;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + .\Debug\ + true + .\Debug\Stars.pch + .\Debug\ + .\Debug\ + EnableFastChecks + + + true + _DEBUG;%(PreprocessorDefinitions) + .\Debug\Stars.tlb + true + Win32 + + + 0x0409 + _DEBUG;%(PreprocessorDefinitions) + + + true + .\Debug\Stars.bsc + + + true + true + Windows + Game/Stars_d.exe + ..\Opcode\OpcodeLib\Debug\OpcodeLib.lib;..\ngenex\debug\ngenex.lib;..\netex\debug\netex.lib;..\zlib\debug\zlib.lib;..\libpng\debug\libpng.lib;wsock32.lib;dinput.lib;dsound.lib;d3d9.lib;d3dx9.lib;dxguid.lib;winmm.lib;version.lib;ogg_static.lib;vorbis_static.lib;vorbisfile_static.lib;Vfw32.lib;%(AdditionalDependencies) + + + + + MultiThreaded + OnlyExplicitInline + true + true + MaxSpeed + true + Level3 + ../FoundationEx;../nGenEx;../NetEx;../Parser;../Opcode/OpcodeLib;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + .\Release\ + .\Release\Stars.pch + .\Release\ + .\Release\ + + + true + NDEBUG;%(PreprocessorDefinitions) + .\Release\Stars.tlb + true + Win32 + + + 0x0409 + NDEBUG;%(PreprocessorDefinitions) + + + true + .\Release\Stars.bsc + + + true + true + Windows + Game/Stars.exe + ..\Opcode\OpcodeLib\Release\OpcodeLib.lib;..\ngenex\release\ngenex.lib;..\netex\release\netex.lib;..\zlib\release\zlib.lib;..\libpng\release\libpng.lib;wsock32.lib;dinput.lib;dsound.lib;d3d9.lib;d3dx9.lib;dxguid.lib;winmm.lib;version.lib;ogg_static.lib;vorbis_static.lib;vorbisfile_static.lib;Vfw32.lib;%(AdditionalDependencies) + + + + + + + + + + + + + All + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + All + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Stars45/Stars.vcxproj.filters b/Stars45/Stars.vcxproj.filters new file mode 100644 index 0000000..eea4571 --- /dev/null +++ b/Stars45/Stars.vcxproj.filters @@ -0,0 +1,1123 @@ + + + + + {b8a66b66-1e1d-446a-891f-0e137a6a2a75} + cpp;c;cxx;rc;def;r;odl;idl;hpj;bat + + + {3d0b1a1b-1131-48e1-85d3-b5f35ce9219c} + h;hpp;hxx;hm;inl + + + {8540b9f2-52cf-4590-be61-3db9ea2de9f0} + ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/Stars45/Starshatter.cpp b/Stars45/Starshatter.cpp new file mode 100644 index 0000000..9b6fc38 --- /dev/null +++ b/Stars45/Starshatter.cpp @@ -0,0 +1,2903 @@ +/* Project Starshatter 5.0 + Destroyer Studios LLC + Copyright © 1997-2007. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Starshatter.cpp + AUTHOR: John DiCamillo + +*/ + + +#include "MemDebug.h" +#include "Starshatter.h" + +#include "MenuScreen.h" +#include "LoadScreen.h" +#include "PlanScreen.h" +#include "CmpnScreen.h" + +#include "AudioConfig.h" +#include "MusicDirector.h" +#include "HUDSounds.h" +#include "Player.h" + +#include "Shot.h" +#include "Drive.h" +#include "LandingGear.h" +#include "Explosion.h" +#include "FlightDeck.h" +#include "NavLight.h" +#include "Debris.h" +#include "Contact.h" +#include "QuantumDrive.h" +#include "Sensor.h" +#include "Power.h" +#include "SystemDesign.h" +#include "WeaponDesign.h" + +#include "Campaign.h" +#include "CampaignSaveGame.h" +#include "CombatRoster.h" +#include "CombatZone.h" +#include "CampaignPlan.h" + +#include "Galaxy.h" +#include "StarSystem.h" +#include "Mission.h" +#include "Sim.h" +#include "SimEvent.h" +#include "Element.h" +#include "Ship.h" +#include "ShipCtrl.h" +#include "ShipDesign.h" +#include "HUDView.h" +#include "MFD.h" +#include "RadioMessage.h" +#include "RadioTraffic.h" +#include "RadioVox.h" +#include "CameraDirector.h" +#include "ModConfig.h" +#include "KeyMap.h" + +#include "GameScreen.h" +#include "QuantumView.h" +#include "QuitView.h" +#include "RadioView.h" +#include "TacticalView.h" +#include "DisplayView.h" + +#include "LoadDlg.h" +#include "TacRefDlg.h" +#include "CmpLoadDlg.h" +#include "Terrain.h" + +#include "NetClientConfig.h" +#include "NetServerConfig.h" +#include "NetLayer.h" +#include "NetLobbyClient.h" +#include "NetLobbyServer.h" +#include "NetGame.h" +#include "NetUtil.h" + +#include "ParseUtil.h" +#include "Token.h" + +#include "MachineInfo.h" +#include "Game.h" +#include "VideoFactory.h" +#include "Screen.h" +#include "Window.h" +#include "ActiveWindow.h" +#include "Button.h" +#include "CameraView.h" +#include "ImgView.h" +#include "FadeView.h" +#include "Color.h" +#include "Bitmap.h" +#include "Font.h" +#include "FontMgr.h" +#include "Keyboard.h" +#include "Joystick.h" +#include "MouseController.h" +#include "Mouse.h" +#include "TrackIR.h" +#include "EventDispatch.h" +#include "MultiController.h" +#include "Archive.h" +#include "DataLoader.h" +#include "Random.h" +#include "Resource.h" +#include "Universe.h" +#include "Video.h" +#include "VideoSettings.h" +#include "WebBrowser.h" + +// +--------------------------------------------------------------------+ + +int quick_mode = 0; +char quick_mission_name[64]; +Mission* quick_mission = 0; + +int Starshatter::keymap[256]; +int Starshatter::keyalt[256]; +Starshatter* Starshatter::instance = 0; + +static Mission* current_mission = 0; +static Mission* cutscene_mission = 0; +static double cutscene_basetime = 0; +static int cut_efx_volume = 100; +static int cut_wrn_volume = 100; +static double time_til_change = 0; +static bool exit_latch = true; +static bool show_missions = false; +static bool use_file_system = false; +static bool no_splash = false; + +enum CHAT_MODES { + CHAT_BROADCAST = 1, + CHAT_TEAM = 2, + CHAT_WING = 3, + CHAT_UNIT = 4 +}; + +// +--------------------------------------------------------------------+ + +Starshatter::Starshatter() + : gamewin(0), menuscreen(0), loadscreen(0), planscreen(0), + cmpnscreen(0), gamescreen(0), splash(0), splash_index(0), + input(0), loader(0), cam_dir(0), music_dir(0), + field_of_view(2), time_mark(0), minutes(0), + player_ship(0), net_lobby(0), + spinning(false), tactical(false), mouse_x(0), mouse_y(0), + game_mode(MENU_MODE), mouse_input(0), head_tracker(0), + terminal(0), verdana(0), limerick18(0), limerick12(0), + HUDfont(0), GUIfont(0), GUI_small_font(0), title_font(0), + ocrb(0), req_change_video(0), video_changed(0), + lens_flare(true), corona(true), nebula(true), dust(0), + load_step(0), load_progress(0), + chat_mode(0), exit_time(1.2), cutscene(0) +{ + if (!instance) + instance = this; + + app_name = "Starshatter: The Gathering Storm"; + title_text = "STARSHATTER"; + palette_name = "alpha"; + + gamma = 128; // default - flat gamma ramp + + if (!DataLoader::GetLoader()) + DataLoader::Initialize(); + + loader = DataLoader::GetLoader(); + int loadstat = loader->EnableDatafile("shatter.dat"); + + if (loadstat != DataLoader::DATAFILE_OK) { + const char* err_msg = loadstat == DataLoader::DATAFILE_INVALID ? + "The file 'shatter.dat' appears to have been damaged. Please re-install Starshatter." : + "Starshatter cannot open the file 'shatter.dat'. Please re-install Starshatter."; + + ::MessageBox(hwnd, err_msg, "Starshatter - Error", MB_OK); + ::Print(err_msg); + ::Print("\n\nFATAL ERROR: EXIT."); + exit(-1); + } + + if (loader->FindFile("vox.dat")) + loader->EnableDatafile("vox.dat"); + +#ifndef STARSHATTER_DEMO_RELEASE + if (loader->FindFile("start.dat")) + loader->EnableDatafile("start.dat"); +#endif + + loadstat = loader->EnableDatafile("content.dat"); + + if (loadstat != DataLoader::DATAFILE_OK) { + const char* err_msg = loadstat == DataLoader::DATAFILE_INVALID ? + "The file 'content.dat' appears to have been damaged. Please re-install the latest Starshatter update." : + "Starshatter cannot open the file 'content.dat'. Please re-install the latest Starshatter update."; + + ::MessageBox(hwnd, err_msg, "Starshatter - Error", MB_OK); + ::Print(err_msg); + ::Print("\n\nFATAL ERROR: EXIT."); + exit(-1); + } + + LoadVideoConfig("video.cfg"); + + // create the fonts + loader->SetDataPath("Fonts/"); + + HUDfont = new(__FILE__,__LINE__) Font("HUDfont"); + FontMgr::Register("HUD", HUDfont); + + GUIfont = new(__FILE__,__LINE__) Font("GUIfont"); + FontMgr::Register("GUI", GUIfont); + + GUI_small_font = new(__FILE__,__LINE__) Font("GUIsmall"); + FontMgr::Register("GUIsmall", GUI_small_font); + + limerick12 = new(__FILE__,__LINE__) Font("Limerick12"); + limerick18 = new(__FILE__,__LINE__) Font("Limerick18"); + terminal = new(__FILE__,__LINE__) Font("Terminal"); + verdana = new(__FILE__,__LINE__) Font("Verdana"); + ocrb = new(__FILE__,__LINE__) Font("OCRB"); + + FontMgr::Register("Limerick12", limerick12); + FontMgr::Register("Limerick18", limerick18); + FontMgr::Register("Terminal", terminal); + FontMgr::Register("Verdana", verdana); + FontMgr::Register("OCRB", ocrb); + + loader->SetDataPath(0); + + ZeroMemory(keymap, sizeof(keymap)); + ZeroMemory(keyalt, sizeof(keyalt)); +} + +Starshatter::~Starshatter() +{ + if (video_changed) { + SaveVideoConfig("video.cfg"); + } + + DeleteFile("video2.cfg"); + StopLobby(); + + if (Status() <= EXIT) + Player::Save(); + + delete menuscreen; + delete loadscreen; + delete planscreen; + delete gamescreen; + delete cmpnscreen; + + menuscreen = 0; + loadscreen = 0; + planscreen = 0; + gamescreen = 0; + cmpnscreen = 0; + + music_dir = 0; + + // delete all the ships and stuff + // BEFORE getting rid of the system + // and weapons catalogs! + delete world; + world = 0; // don't let base class double delete the world + + delete quick_mission; + + AudioConfig::Close(); + HUDSounds::Close(); + MusicDirector::Close(); + + Player::Close(); + Drive::Close(); + LandingGear::Close(); + MFD::Close(); + Explosion::Close(); + FlightDeck::Close(); + Campaign::Close(); + CombatRoster::Close(); + Galaxy::Close(); + RadioTraffic::Close(); + RadioVox::Close(); + Ship::Close(); + WeaponDesign::Close(); + SystemDesign::Close(); + ModConfig::Close(); + NetClientConfig::Close(); + TacticalView::Close(); + QuantumView::Close(); + QuitView::Close(); + RadioView::Close(); + NetServerConfig::Close(); + + Mouse::Close(); + EventDispatch::Close(); + FontMgr::Close(); + Button::Close(); + DataLoader::Close(); + + delete ocrb; + delete limerick12; + delete limerick18; + delete verdana; + delete terminal; + delete HUDfont; + delete GUIfont; + delete GUI_small_font; + delete input; + delete head_tracker; + + instance = 0; +} + +void +Starshatter::Exit() +{ + MusicDirector::SetMode(MusicDirector::NONE); + SetGameMode(EXIT_MODE); +} + +// +--------------------------------------------------------------------+ + +bool +Starshatter::OnHelp() +{ + WebBrowser browser; + browser.OpenURL("http://matrixgames.com/support"); + return true; +} + +// +--------------------------------------------------------------------+ + +void +Starshatter::MapKeys() +{ + int nkeys = keycfg.GetNumKeys(); + + if (nkeys > 0) { + Starshatter::MapKeys(&keycfg, nkeys); + input->MapKeys(keycfg.GetMapping(), nkeys); + } +} + +void +Starshatter::MapKeys(KeyMap* mapping, int nkeys) +{ + for (int i = 0; i < nkeys; i++) { + KeyMapEntry* k = mapping->GetKeyMap(i); + + if (k->act >= KEY_MAP_FIRST && k->act <= KEY_MAP_LAST) + MapKey(k->act, k->key, k->alt); + } +} + +void +Starshatter::MapKey(int act, int key, int alt) +{ + keymap[act] = key; + keyalt[act] = alt; + + GetAsyncKeyState(key); + GetAsyncKeyState(alt); +} + +// +--------------------------------------------------------------------+ + +bool +Starshatter::Init(HINSTANCE hi, HINSTANCE hpi, LPSTR cmdline, int nCmdShow) +{ + if (strstr(cmdline, "-win") || strstr(cmdline, "-dbg")) { + if (video_settings) { + video_settings->is_windowed = true; + + /*** + // XXX temporary for video capture + video_settings->window_width = 720; //800; + video_settings->window_height = 386; //431; // 450 - 19 + /***/ + + Print(" STARSHATTER RUNNING IN WINDOW MODE\n"); + } + } + + if (strstr(cmdline, "-filesys")) { + use_file_system = true; + Print(" FILE SYSTEM ENABLED\n"); + } + + if (strstr(cmdline, "-nosplash")) { + no_splash = true; + } + + if (loader) + loader->UseFileSystem(use_file_system); + + return Game::Init(hi, hpi, cmdline, nCmdShow); +} + +// +--------------------------------------------------------------------+ + +bool +Starshatter::InitGame() +{ + if (!Game::InitGame()) + return false; + + RandomInit(); + + AudioConfig::Initialize(); + ModConfig::Initialize(); + + InitMouse(); + + Button::Initialize(); + EventDispatch::Create(); + NetClientConfig::Initialize(); + Player::Initialize(); + HUDSounds::Initialize(); + + int nkeys = keycfg.LoadKeyMap("key.cfg", 256); + + if (nkeys) + Print(" Loaded key.cfg\n\n"); + + // create the appropriate motion controller and player_ship + input = new(__FILE__,__LINE__) MultiController; + Keyboard* k = new(__FILE__,__LINE__) Keyboard; + input->AddController(k); + + mouse_input = new(__FILE__,__LINE__) MouseController; + input->AddController(mouse_input); + + ::Print("\nStarshatter::InitGame() create joystick\n"); + Joystick* j = new(__FILE__,__LINE__) Joystick; + j->SetSensitivity(15, 5000); + input->AddController(j); + + Joystick::EnumerateDevices(); + ::Print("\n"); + + head_tracker = new(__FILE__,__LINE__) TrackIR(); + MapKeys(); + + SystemDesign::Initialize("sys.def"); + WeaponDesign::Initialize("wep.def"); + MusicDirector::Initialize(); + + // if no splashes, we need to initialize the campaign engine now + if (no_splash) { + Ship::Initialize(); + Galaxy::Initialize(); + CombatRoster::Initialize(); + Campaign::Initialize(); + } + + // otherwise, the campaign engine will get initialized during the splashes + else { + SetupSplash(); + } + + time_mark = Game::GameTime(); + minutes = 0; + + return true; +} + +void +Starshatter::InitMouse() +{ + if (loader) { + loader->SetDataPath(0); + + Mouse::Create(screen); + Mouse::Show(false); + Mouse::LoadCursor(Mouse::ARROW, "MouseArrow.pcx", Mouse::HOTSPOT_NW); + Mouse::LoadCursor(Mouse::CROSS, "MouseCross.pcx", Mouse::HOTSPOT_CTR); + Mouse::LoadCursor(Mouse::DRAG, "MouseDrag.pcx", Mouse::HOTSPOT_NW); + Mouse::SetCursor(Mouse::ARROW); + } +} + +// +--------------------------------------------------------------------+ + +void +Starshatter::RequestChangeVideo() +{ + req_change_video = true; +} + +int +Starshatter::GetScreenWidth() +{ + if (video_settings) + return video_settings->GetWidth(); + + return 0; +} + +// +--------------------------------------------------------------------+ + +int +Starshatter::GetScreenHeight() +{ + if (video_settings) + return video_settings->GetHeight(); + + return 0; +} + +// +--------------------------------------------------------------------+ + +void +Starshatter::StartOrResumeGame() +{ + if (game_mode != MENU_MODE && game_mode != CMPN_MODE) + return; + + Player* p = Player::GetCurrentPlayer(); + if (!p) + return; + + List& list = Campaign::GetAllCampaigns(); + Campaign* c = 0; + Text saved = CampaignSaveGame::GetResumeFile(); + + + // resume saved game? + if (saved.length()) { + CampaignSaveGame savegame; + savegame.Load(saved); + c = savegame.GetCampaign(); + } + + // start training campaign? + else if (p->Trained() < 255) { + c = list[0]; + c->Load(); + } + + // start new dynamic campaign sequence? + else { + c = list[1]; + c->Load(); + } + + if (c) + Campaign::SelectCampaign(c->Name()); + + Mouse::Show(false); + SetGameMode(CLOD_MODE); +} + +// +--------------------------------------------------------------------+ + +bool +Starshatter::UseFileSystem() +{ + return use_file_system; +} + +// +--------------------------------------------------------------------+ + +void +Starshatter::OpenTacticalReference() +{ + if (menuscreen && game_mode == MENU_MODE) { + menuscreen->ShowLoadDlg(); + + LoadDlg* load_dlg = menuscreen->GetLoadDlg(); + + if (load_dlg && load_dlg->IsShown()) { + load_activity = Game::GetText("Starshatter.load.tac-ref"); + load_progress = 1; + catalog_index = 0; + } + else { + menuscreen->ShowTacRefDlg(); + } + } +} + +// +--------------------------------------------------------------------+ + +void +Starshatter::SetGameMode(int m) +{ + if (game_mode == m) + return; + + const char* mode_name[] = { + "MENU_MODE", // main menu + "CLOD_MODE", // loading campaign + "CMPN_MODE", // operational command for dynamic campaign + "PREP_MODE", // loading mission info for planning + "PLAN_MODE", // mission briefing + "LOAD_MODE", // loading mission into simulator + "PLAY_MODE", // active simulation + "EXIT_MODE" // shutting down + }; + + if (m >= MENU_MODE && m <= EXIT_MODE) + Print(">>> Starshatter::SetGameMode(%d) (%s)\n", m, mode_name[m]); + else + Print(">>> Starshatter::SetGameMode(%d) (UNKNOWN MODE)\n", m); + + MouseController* mouse_con = MouseController::GetInstance(); + if (mouse_con) + mouse_con->SetActive(false); + + if (m == CLOD_MODE || m == PREP_MODE || m == LOAD_MODE) { + load_step = 0; + load_progress = 0; + load_activity = Game::GetText("Starshatter.load.general"); + paused = true; + } + + else if (m == CMPN_MODE) { + load_step = 0; + load_progress = 100; + load_activity = Game::GetText("Starshatter.load.complete"); + paused = false; + } + + else if (m == PLAY_MODE) { + Print(" Starting Game...\n"); + + player_ship = 0; + load_progress = 100; + load_activity = Game::GetText("Starshatter.load.complete"); + + if (!world) { + CreateWorld(); + InstantiateMission(); + } + + if (gamescreen) + gamescreen->SetFieldOfView(field_of_view); + + HUDView::ClearMessages(); + RadioView::ClearMessages(); + + SetTimeCompression(1); + Pause(false); + + Print(" Stardate: %.1f\n", StarSystem::GetBaseTime()); + } + + else if (m == PLAN_MODE) { + if (game_mode == PLAY_MODE) { + Print(" Returning to Plan Mode...\n"); + if (soundcard) + soundcard->StopSoundEffects(); + + StopNetGame(); + Pause(true); + Print(" Stardate: %.1f\n", StarSystem::GetBaseTime()); + } + } + + else if (m == MENU_MODE) { + Print(" Returning to Main Menu...\n"); + + if (game_mode == PLAN_MODE || game_mode == PLAY_MODE) { + if (soundcard) + soundcard->StopSoundEffects(); + + StopNetGame(); + } + + paused = true; + } + + if (m == EXIT_MODE) { + Print(" Shutting Down (Returning to Windows)...\n"); + + if (game_mode == PLAN_MODE || game_mode == PLAY_MODE) { + if (soundcard) + soundcard->StopSoundEffects(); + + StopNetGame(); + } + + Print(" Stardate: %.1f\n", StarSystem::GetBaseTime()); + Print(" Bitmap Cache Footprint: %d KB\n", Bitmap::CacheMemoryFootprint() / 1024); + + paused = true; + } + + FlushKeys(); + game_mode = m; +} + +// +--------------------------------------------------------------------+ + +bool +Starshatter::ChangeVideo() +{ + bool result = false; + + if (menuscreen) { + delete menuscreen; + menuscreen = 0; + } + + if (loadscreen) { + delete loadscreen; + loadscreen = 0; + } + + if (planscreen) { + delete planscreen; + planscreen = 0; + } + + if (gamescreen) { + delete gamescreen; + gamescreen = 0; + } + + if (cmpnscreen) { + delete cmpnscreen; + cmpnscreen = 0; + } + + loader->SetDataPath(0); + + LoadVideoConfig("video2.cfg"); + + result = ResetVideo(); + + InitMouse(); + + req_change_video = false; + video_changed = true; + + return result; +} + +bool +Starshatter::ResizeVideo() +{ + if (Game::ResizeVideo()) { + InitMouse(); + Mouse::Show(true); + + return true; + } + + return false; +} + +// +--------------------------------------------------------------------+ + +void +Starshatter::CreateWorld() +{ + RadioTraffic::Initialize(); + RadioView::Initialize(); + RadioVox::Initialize(); + QuantumView::Initialize(); + QuitView::Initialize(); + TacticalView::Initialize(); + + // create world + if (!world) { + Sim* sim = new(__FILE__,__LINE__) Sim(input); + world = sim; + Print(" World Created.\n"); + } + + cam_dir = CameraDirector::GetInstance(); +} + +void +Starshatter::InstantiateMission() +{ + Memory::Check(); + + current_mission = 0; + + if (Campaign::GetCampaign()) { + current_mission = Campaign::GetCampaign()->GetMission(); + } + + Sim* sim = (Sim*) world; + + if (sim) { + bool dynamic = false; + Campaign* campaign = Campaign::GetCampaign(); + + if (campaign && campaign->IsDynamic()) + dynamic = true; + + sim->UnloadMission(); + sim->LoadMission(current_mission); + sim->ExecMission(); + sim->SetTestMode(test_mode && !dynamic ? true : false); + + Print(" Mission Instantiated.\n"); + } + + Memory::Check(); +} + +// +--------------------------------------------------------------------+ + +int +Starshatter::KeyDown(int action) const +{ + int k = Joystick::KeyDownMap(action) || + Keyboard::KeyDownMap(action); + + return k; +} + +// +--------------------------------------------------------------------+ + +bool +Starshatter::GameLoop() +{ + cam_dir = CameraDirector::GetInstance(); + + if (active && paused) { + // Route Events to EventTargets + EventDispatch* ed = EventDispatch::GetInstance(); + if (ed) + ed->Dispatch(); + + UpdateWorld(); + GameState(); + UpdateScreen(); + CollectStats(); + + /*** + static DWORD vmf_time = 0; + + if (real_time() - vmf_time > 5000) { + vmf_time = real_time(); + DWORD vmf = video->VidMemFree() / (1024 * 1024); + ::Print("\n###### %02d:%02d - Video Memory Free: %d MB\n\n", + vmf_time / 60000, + vmf_time / 1000, + vmf); + } + ***/ + } + + Game::GameLoop(); + return false; // must return false to keep processing + // true tells the outer loop to sleep until a + // windows event is available +} + +// +--------------------------------------------------------------------+ + +void +Starshatter::UpdateWorld() +{ + long new_time = real_time; + double delta = new_time - frame_time; // in milliseconds + seconds = max_frame_length; // in seconds + gui_seconds = delta * 0.001; + + if (frame_time == 0) + gui_seconds = 0; + + if (delta < time_comp * max_frame_length * 1000) { + seconds = time_comp * delta * 0.001; + } + else { + seconds = time_comp * max_frame_length; + } + + frame_time = new_time; + + Galaxy* galaxy = Galaxy::GetInstance(); + if (galaxy) galaxy->ExecFrame(); + + // Cutscene missions have a tendency to mess with the stardate + // to manage time-of-day and camera effects. It's a bad idea to + // evaluate campaign actions and events while the cutscene is + // changing the time base. + if (!cutscene_mission) { + Campaign* campaign = Campaign::GetCampaign(); + if (campaign) campaign->ExecFrame(); + } + + if (paused) { + if (world) + world->ExecFrame(0); + } + + else { + game_time += (DWORD) (seconds * 1000); + + Drive::StartFrame(); + + if (world) + world->ExecFrame(seconds); + } + + if (game_mode == PLAY_MODE || InCutscene()) { + if (cam_dir) { + if (head_tracker && head_tracker->IsRunning() && !InCutscene()) { + head_tracker->ExecFrame(); + cam_dir->VirtualHead(head_tracker->GetAzimuth(), head_tracker->GetElevation()); + cam_dir->VirtualHeadOffset(head_tracker->GetX(), head_tracker->GetY(), head_tracker->GetZ()); + } + + cam_dir->ExecFrame(gui_seconds); + } + + Sim* sim = Sim::GetSim(); + SimRegion* rgn = sim ? sim->GetActiveRegion() : 0; + + if (rgn) { + ListIter iter = rgn->Ships(); + while (++iter) { + Ship* s = iter.value(); + s->SelectDetail(seconds); + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +Starshatter::GameState() +{ + if (splash) { + static bool quick_splash = false; + + if (GetKey() != 0) + quick_splash = true; + + if (quick_splash) { + splash->FadeIn(0); + splash->StopHold(); + splash->FadeOut(0); + } + + if (splash->Done()) { + splash = 0; // this will get deleted along with gamewin + splash_index++; + + if (gamewin) { + screen->DelWindow(gamewin); + delete gamewin; + gamewin = 0; + } + + if (splash_index < 2) { + Ship::Initialize(); + Galaxy::Initialize(); + SetupSplash(); + } + else { + CombatRoster::Initialize(); + Campaign::Initialize(); + SetupMenuScreen(); + } + + FlushKeys(); + } + } + + else if (game_mode == MENU_MODE) { + bool campaign_select = false; + + if (cmpnscreen) { + campaign_select = cmpnscreen->IsShown(); + cmpnscreen->Hide(); + } + + if (gamescreen) + gamescreen->Hide(); + + if (planscreen) + planscreen->Hide(); + + if (loadscreen) + loadscreen->Hide(); + + if (!menuscreen) { + SetupMenuScreen(); + } + else { + menuscreen->Show(); + + if (campaign_select) + menuscreen->ShowCmpSelectDlg(); + } + + if (MusicDirector::GetInstance() && + MusicDirector::GetInstance()->GetMode() != MusicDirector::CREDITS) + MusicDirector::SetMode(MusicDirector::MENU); + + DoMenuScreenFrame(); + } + + else if (game_mode == CLOD_MODE || + game_mode == PREP_MODE || + game_mode == LOAD_MODE) { + if (menuscreen) + menuscreen->Hide(); + + if (planscreen) + planscreen->Hide(); + + if (cmpnscreen) + cmpnscreen->Hide(); + + if (!loadscreen) + SetupLoadScreen(); + else + loadscreen->Show(); + + if (game_mode == CLOD_MODE) + MusicDirector::SetMode(MusicDirector::MENU); + else + MusicDirector::SetMode(MusicDirector::BRIEFING); + + DoLoadScreenFrame(); + } + + else if (game_mode == PLAN_MODE) { + if (menuscreen) + menuscreen->Hide(); + + if (cmpnscreen) + menuscreen->Hide(); + + if (loadscreen) + loadscreen->Hide(); + + if (gamescreen) + gamescreen->Hide(); + + if (!planscreen) + SetupPlanScreen(); + else + planscreen->Show(); + + Player* p = Player::GetCurrentPlayer(); + if (p && p->ShowAward()) { + if (!planscreen->IsAwardShown()) + planscreen->ShowAwardDlg(); + } + + else if (ShipStats::NumStats()) { + if (!planscreen->IsDebriefShown()) { + planscreen->ShowDebriefDlg(); + show_missions = true; + } + } + + else { + if (!planscreen->IsMsnShown() && !planscreen->IsNavShown()) + planscreen->ShowMsnDlg(); + } + + MusicDirector::SetMode(MusicDirector::BRIEFING); + + DoPlanScreenFrame(); + } + + + else if (game_mode == CMPN_MODE) { + if (menuscreen) + menuscreen->Hide(); + + if (planscreen) + planscreen->Hide(); + + if (loadscreen) + loadscreen->Hide(); + + if (gamescreen) + gamescreen->Hide(); + + if (!cmpnscreen) + SetupCmpnScreen(); + else + cmpnscreen->Show(); + + DoCmpnScreenFrame(); + } + + else if (game_mode == PLAY_MODE) { + if (menuscreen) + menuscreen->Hide(); + + if (cmpnscreen) + cmpnscreen->Hide(); + + if (planscreen) + planscreen->Hide(); + + if (loadscreen) + loadscreen->Hide(); + + if (!gamescreen) + SetupGameScreen(); + else + gamescreen->Show(); + + DoGameScreenFrame(); + } + + if (game_mode == EXIT_MODE) { + exit_time -= Game::GUITime(); + + if (exit_time <= 0) + Game::Exit(); + } + + if (net_lobby) + net_lobby->ExecFrame(); + + if (music_dir) + music_dir->ExecFrame(); + + Memory::Check(); +} + +// +--------------------------------------------------------------------+ + +void +Starshatter::DoMenuScreenFrame() +{ + if (!Mouse::RButton()) { + Mouse::SetCursor(Mouse::ARROW); + Mouse::Show(true); + } + + if (time_til_change > 0) + time_til_change -= Game::GUITime(); + + if (!menuscreen) + return; + + if (KeyDown(KEY_EXIT)) { + if (time_til_change <= 0) { + time_til_change = 0.5; + + if (!exit_latch && !menuscreen->CloseTopmost()) { + menuscreen->ShowExitDlg(); + } + } + + exit_latch = true; + } + else { + exit_latch = false; + } + + LoadDlg* load_dlg = menuscreen->GetLoadDlg(); + + if (load_dlg && load_dlg->IsShown()) { + // load all ship designs in the standard catalog + if (catalog_index < ShipDesign::StandardCatalogSize()) { + ShipDesign::PreloadCatalog(catalog_index++); + load_activity = Game::GetText("Starshatter.load.tac-ref"); + + if (load_progress < 95) + load_progress++; + } + + else { + menuscreen->ShowTacRefDlg(); + } + } + + if (show_missions) { + if (net_lobby) + menuscreen->ShowNetLobbyDlg(); + else + menuscreen->ShowMsnSelectDlg(); + + show_missions = false; + } + + menuscreen->ExecFrame(); + + if (req_change_video) { + ChangeVideo(); + SetupMenuScreen(); + } +} + +// +--------------------------------------------------------------------+ + +void +Starshatter::DoPlanScreenFrame() +{ + Mouse::SetCursor(Mouse::ARROW); + + if (time_til_change > 0) + time_til_change -= Game::GUITime(); + + if (KeyDown(KEY_EXIT)) { + if (time_til_change <= 0) { + time_til_change = 1; + + if (!exit_latch && !planscreen->CloseTopmost()) { + Campaign* campaign = Campaign::GetCampaign(); + if (campaign && (campaign->IsDynamic() || campaign->IsTraining())) + SetGameMode(CMPN_MODE); + else + SetGameMode(MENU_MODE); + } + } + + exit_latch = true; + } + else { + exit_latch = false; + } + + planscreen->ExecFrame(); + show_missions = true; +} + +// +--------------------------------------------------------------------+ + +void +Starshatter::DoCmpnScreenFrame() +{ + Mouse::SetCursor(Mouse::ARROW); + + if (time_til_change > 0) + time_til_change -= Game::GUITime(); + + exit_latch = KeyDown(KEY_EXIT) ? true : false; + + if (InCutscene() && player_ship) { + // warp effect: + if (player_ship->WarpFactor() > 1) { + if (player_ship->WarpFactor() > field_of_view) + cmpnscreen->SetFieldOfView(player_ship->WarpFactor()); + else + cmpnscreen->SetFieldOfView(field_of_view); + } + + else { + if (cmpnscreen->GetFieldOfView() != field_of_view) + cmpnscreen->SetFieldOfView(field_of_view); + } + } + + if (InCutscene() && exit_latch) { + time_til_change = 1; + EndCutscene(); + EndMission(); + cmpnscreen->SetFieldOfView(field_of_view); + } + + else if (time_til_change <= 0 && exit_latch) { + time_til_change = 1; + + if (!cmpnscreen || !cmpnscreen->CloseTopmost()) { + SetGameMode(MENU_MODE); + } + } + + // time control for campaign mode: + else if (game_mode == CMPN_MODE) { + if (time_til_change <= 0) { + if (KeyDown(KEY_PAUSE)) { + time_til_change = 1; + Pause(!paused); + } + + else if (KeyDown(KEY_TIME_COMPRESS)) { + time_til_change = 1; + + switch (TimeCompression()) { + case 1: SetTimeCompression(2); break; + case 2: SetTimeCompression(4); break; + case 4: SetTimeCompression(8); break; + } + } + + else if (KeyDown(KEY_TIME_EXPAND)) { + time_til_change = 1; + + switch (TimeCompression()) { + case 8: SetTimeCompression( 4); break; + case 4: SetTimeCompression( 2); break; + default: SetTimeCompression( 1); break; + } + } + } + } + + if (show_missions && !InCutscene()) { + cmpnscreen->ShowCmdMissionsDlg(); + show_missions = false; + } + + cmpnscreen->ExecFrame(); +} + +// +--------------------------------------------------------------------+ + +void +Starshatter::DoLoadScreenFrame() +{ + Mouse::Show(false); + Mouse::SetCursor(Mouse::ARROW); + + if (game_mode == CLOD_MODE) { + CmpLoadDlg* dlg = loadscreen->GetCmpLoadDlg(); + + switch (load_step) { + case 0: + load_activity = Game::GetText("Starshatter.load.campaign"); + load_progress = 1; + catalog_index = 0; + break; + + case 1: + // load all ship designs in the standard catalog + if (catalog_index < ShipDesign::StandardCatalogSize()) { + ShipDesign::PreloadCatalog(catalog_index++); + load_activity = Game::GetText("Starshatter.load.campaign"); + + if (load_progress < 80) + load_progress++; + + load_step = 0; // force return to current step on next frame + } + else { + load_activity = Game::GetText("Starshatter.load.start"); + load_progress = 80; + } + break; + + case 2: + if (Campaign::GetCampaign()) + Campaign::GetCampaign()->Start(); + break; + + default: + if (dlg && load_progress < 100) { + load_progress++; + } + else { + load_activity = Game::GetText("Starshatter.load.ready"); + load_progress = 100; + SetGameMode(CMPN_MODE); + } + break; + } + } + else { + switch (load_step) { + case 0: + load_activity = Game::GetText("Starshatter.load.drives"); + load_progress = 0; + break; + + case 1: + Drive::Initialize(); + LandingGear::Initialize(); + load_activity = Game::GetText("Starshatter.load.explosions"); + load_progress = 5; + break; + + case 2: + Explosion::Initialize(); + load_activity = Game::GetText("Starshatter.load.systems"); + load_progress = 10; + break; + + case 3: + FlightDeck::Initialize(); + NavLight::Initialize(); + load_activity = Game::GetText("Starshatter.load.ships"); + load_progress = 15; + break; + + case 4: + Ship::Initialize(); + Shot::Initialize(); + load_activity = Game::GetText("Starshatter.load.hud"); + load_progress = 20; + break; + + case 5: + MFD::Initialize(); + load_activity = Game::GetText("Starshatter.load.menus"); + load_progress = 25; + break; + + case 6: + RadioTraffic::Initialize(); + RadioView::Initialize(); + TacticalView::Initialize(); + + if (!gamescreen && game_mode != PREP_MODE) + SetupGameScreen(); + + load_activity = Game::GetText("Starshatter.load.mission"); + load_progress = 65; + break; + + case 7: + if (game_mode == PREP_MODE) { + if (Campaign::GetCampaign()) + Campaign::GetCampaign()->GetMission(); + SetGameMode(PLAN_MODE); + load_activity = Game::GetText("Starshatter.load.loaded"); + load_progress = 100; + Button::PlaySound(4); + } + else { + CreateWorld(); + load_activity = Game::GetText("Starshatter.load.simulation"); + load_progress = 75; + } + break; + + case 8: + InstantiateMission(); + load_activity = Game::GetText("Starshatter.load.viewscreen"); + load_progress = 90; + break; + + default: + SetGameMode(PLAY_MODE); + load_activity = Game::GetText("Starshatter.load.ready"); + load_progress = 100; + break; + } + } + + load_step++; + loadscreen->ExecFrame(); +} + +// +--------------------------------------------------------------------+ + +void +Starshatter::PlayerCam(int mode) +{ + if (player_ship && !player_ship->InTransition() && player_ship->GetFlightPhase() >= Ship::LOCKED) { + gamescreen->CloseTopmost(); + if (mode == CameraDirector::MODE_DROP) { + player_ship->DropCam(15, 0); + cam_dir->SetShip(player_ship); + cam_dir->SetMode(CameraDirector::MODE_DROP, 0); + } + + else { + cam_dir->SetMode(mode); + + if (mode == CameraDirector::MODE_ORBIT) { + MouseController* mouse_con = MouseController::GetInstance(); + if (mouse_con) + mouse_con->SetActive(false); + } + } + } +} + +void +Starshatter::ViewSelection() +{ + Sim* sim = (Sim*) world; + List& seln = sim->GetSelection().container(); + + if (cam_dir) { + if (seln.isEmpty()) + cam_dir->SetViewObject(player_ship); + + else if (seln.size() == 1) + cam_dir->SetViewObject(seln[0]); + + else + cam_dir->SetViewObjectGroup(seln); + } +} + +// +--------------------------------------------------------------------+ + +void +Starshatter::DoGameScreenFrame() +{ + Sim* sim = (Sim*) world; + + if (!gamescreen || !sim) + return; + + if (InCutscene()) { + if (player_ship) { + // warp effect: + if (player_ship->WarpFactor() > 1) { + if (player_ship->WarpFactor() > field_of_view) + gamescreen->SetFieldOfView(player_ship->WarpFactor()); + else + gamescreen->SetFieldOfView(field_of_view); + } + + else { + if (gamescreen->GetFieldOfView() != field_of_view) + gamescreen->SetFieldOfView(field_of_view); + } + } + + gamescreen->FrameRate(frame_rate); + gamescreen->ExecFrame(); + + if (KeyDown(KEY_EXIT)) { + gamescreen->SetFieldOfView(field_of_view); + exit_latch = true; + time_til_change = 1; + + sim->SkipCutscene(); + } + + return; + } + + if (time_til_change > 0) + time_til_change -= Game::GUITime(); + + if (exit_latch && !KeyDown(KEY_EXIT)) + exit_latch = false; + + DoMouseFrame(); + + HUDView* hud_view = HUDView::GetInstance(); + + // changing to a new ship? + if (player_ship != sim->GetPlayerShip()) { + gamescreen->HideNavDlg(); + gamescreen->HideFltDlg(); + gamescreen->HideEngDlg(); + + Ship* new_player = sim->GetPlayerShip(); + + if (new_player) { + if (new_player->IsDropship() && Ship::GetFlightModel() < 2 && Ship::GetControlModel() < 1) + input->SwapYawRoll(true); + else + input->SwapYawRoll(false); + + if (hud_view) { + hud_view->SetHUDMode(HUDView::HUD_MODE_TAC); + hud_view->HideHUDWarn(); + } + } + } + + player_ship = sim->GetPlayerShip(); + + if (player_ship) { + // warp effect: + if (player_ship->WarpFactor() > 1) { + if (player_ship->WarpFactor() > field_of_view) + gamescreen->SetFieldOfView(player_ship->WarpFactor()); + else + gamescreen->SetFieldOfView(field_of_view); + } + + else { + if (gamescreen->GetFieldOfView() != field_of_view) + gamescreen->SetFieldOfView(field_of_view); + } + + gamescreen->ShowExternal(); + + if (CameraDirector::GetCameraMode() >= CameraDirector::MODE_ORBIT && !player_ship->InTransition()) + tactical = true; + else + tactical = false; + + if (player_ship->InTransition()) + gamescreen->CloseTopmost(); + } + + if (chat_mode) { + DoChatMode(); + } + + else { + DoGameKeys(); + } + + gamescreen->FrameRate(frame_rate); + gamescreen->ExecFrame(); + + if (Game::GameTime() - time_mark > 60000) { + time_mark = Game::GameTime(); + minutes++; + if (minutes > 60) + Print(" TIME %2d:%02d:00\n", minutes/60, minutes%60); + else + Print(" TIME %2d:00\n", minutes); + } +} + +void +Starshatter::DoGameKeys() +{ + Sim* sim = (Sim*) world; + HUDView* hud_view = HUDView::GetInstance(); + + if (time_til_change <= 0) { + if (KeyDown(KEY_CAM_BRIDGE)) { + PlayerCam(CameraDirector::MODE_COCKPIT); + time_til_change = 0.5; + } + + else if (KeyDown(KEY_CAM_VIRT)) { + PlayerCam(CameraDirector::MODE_VIRTUAL); + time_til_change = 0.5; + } + + else if (KeyDown(KEY_CAM_CHASE)) { + PlayerCam(CameraDirector::MODE_CHASE); + time_til_change = 0.5; + } + + else if (KeyDown(KEY_CAM_DROP)) { + PlayerCam(CameraDirector::MODE_DROP); + time_til_change = 0.5; + } + + else if (KeyDown(KEY_CAM_EXTERN)) { + PlayerCam(CameraDirector::MODE_ORBIT); + time_til_change = 0.5; + } + + else if (KeyDown(KEY_TARGET_PADLOCK)) { + PlayerCam(CameraDirector::MODE_TARGET); + time_til_change = 0.5; + } + + else if (KeyDown(KEY_SWAP_ROLL_YAW)) { + input->SwapYawRoll(!input->GetSwapYawRoll()); + time_til_change = 0.5; + } + + else if (KeyDown(KEY_ZOOM_WIDE)) { + time_til_change = 0.5; + if (gamescreen->GetFieldOfView() <= 2) + field_of_view = 3; // wide + else + field_of_view = 2; // normal + + // don't mess with fov during warp: + if (player_ship && player_ship->WarpFactor() <= 1) + gamescreen->SetFieldOfView(field_of_view); + } + + else if (!exit_latch && KeyDown(KEY_EXIT)) { + exit_latch = true; + time_til_change = 0.5; + + if (!gamescreen->CloseTopmost()) { + QuitView* quit = QuitView::GetInstance(); + if (quit) + quit->ShowMenu(); + else + SetGameMode(Starshatter::PLAN_MODE); + } + } + + else if (KeyDown(KEY_PAUSE)) { + Pause(!paused); + time_til_change = 0.5; + } + + + else if (KeyDown(KEY_TIME_COMPRESS)) { + time_til_change = 0.5; + if (NetGame::IsNetGame()) + SetTimeCompression(1); + else + switch (TimeCompression()) { + case 1: SetTimeCompression(2); break; + default: SetTimeCompression(4); break; + } + } + + else if (KeyDown(KEY_TIME_EXPAND)) { + time_til_change = 0.5; + + if (NetGame::IsNetGame()) + SetTimeCompression(1); + else + switch (TimeCompression()) { + case 4: SetTimeCompression( 2); break; + default: SetTimeCompression( 1); break; + } + } + + else if (KeyDown(KEY_TIME_SKIP)) { + time_til_change = 0.5; + + if (player_ship && !NetGame::IsNetGame()) { + player_ship->TimeSkip(); + } + } + + else if (KeyDown(KEY_COMMAND_MODE)) { + if (player_ship) { + time_til_change = 0.5; + player_ship->CommandMode(); + } + } + +/*** For Debug Convenience Only: ***/ + else if (KeyDown(KEY_INC_STARDATE)) { + StarSystem::SetBaseTime(StarSystem::GetBaseTime() + 600, true); + } + + else if (KeyDown(KEY_DEC_STARDATE)) { + StarSystem::SetBaseTime(StarSystem::GetBaseTime() - 600, true); + } +/***/ + } + + if (gamescreen && time_til_change <= 0) { + if (KeyDown(KEY_MFD1)) { + gamescreen->CycleMFDMode(0); + time_til_change = 0.5; + } + + else if (KeyDown(KEY_MFD2)) { + gamescreen->CycleMFDMode(1); + time_til_change = 0.5; + } + + else if (KeyDown(KEY_MFD3)) { + gamescreen->CycleMFDMode(2); + time_til_change = 0.5; + } + + else if (KeyDown(KEY_MFD4)) { + gamescreen->CycleMFDMode(3); + time_til_change = 0.5; + } + + else if (KeyDown(KEY_RADIO_MENU)) { + RadioView* radio_view = RadioView::GetInstance(); + if (radio_view) { + if (radio_view->IsMenuShown()) + radio_view->CloseMenu(); + else + radio_view->ShowMenu(); + } + time_til_change = 0.5; + } + + else if (KeyDown(KEY_QUANTUM_MENU)) { + QuantumView* quantum_view = QuantumView::GetInstance(); + if (quantum_view) { + if (quantum_view->IsMenuShown()) + quantum_view->CloseMenu(); + else + quantum_view->ShowMenu(); + } + time_til_change = 0.5; + } + + else if (KeyDown(KEY_CAM_VIEW_SELECTION)) { + time_til_change = 0.5; + ViewSelection(); + } + + else if (KeyDown(KEY_HUD_MODE)) { + time_til_change = 0.5; + if (hud_view) + hud_view->CycleHUDMode(); + } + + else if (KeyDown(KEY_HUD_COLOR)) { + time_til_change = 0.5; + if (hud_view) + hud_view->CycleHUDColor(); + } + + else if (KeyDown(KEY_HUD_WARN)) { + time_til_change = 0.5; + if (hud_view) + hud_view->CycleHUDWarn(); + } + + else if (KeyDown(KEY_HUD_INST)) { + time_til_change = 0.5; + if (hud_view) + hud_view->CycleHUDInst(); + } + + else if (KeyDown(KEY_SELF_DESTRUCT)) { + time_til_change = 0.5; + + if (player_ship && !player_ship->InTransition()) { + double damage = player_ship->Design()->scuttle; + + if (NetGame::IsNetGameClient()) { + NetUtil::SendSelfDestruct(player_ship, damage); + } + else { + Point scuttle_loc = player_ship->Location() + RandomDirection() * player_ship->Radius(); + player_ship->InflictDamage(damage, 0, 1, scuttle_loc); + } + + if (player_ship->Integrity() < 1) { + ::Print(" %s 0-0-0-Destruct-0\n\n", player_ship->Name()); + + ShipStats* s = ShipStats::Find(player_ship->Name()); + if (s) + s->AddEvent(SimEvent::DESTROYED, player_ship->Name()); + + player_ship->DeathSpiral(); + } + } + } + } + + if (gamescreen && player_ship && time_til_change <= 0 && !::GetAsyncKeyState(VK_SHIFT) && !::GetAsyncKeyState(VK_MENU)) { + if (KeyDown(KEY_NAV_DLG)) { + gamescreen->ShowNavDlg(); + time_til_change = 0.5; + } + + else if (KeyDown(KEY_WEP_DLG)) { + if (player_ship && player_ship->Design()->wep_screen) + gamescreen->ShowWeaponsOverlay(); + time_til_change = 0.5; + } + + else if (KeyDown(KEY_FLT_DLG)) { + if (player_ship && player_ship->NumFlightDecks() > 0) + gamescreen->ShowFltDlg(); + time_til_change = 0.5; + } + + else if (KeyDown(KEY_ENG_DLG)) { + if (player_ship && player_ship->Design()->repair_screen) + gamescreen->ShowEngDlg(); + time_til_change = 0.5; + } + } + + if (cam_dir) { + double spin = (PI/2) * Game::FrameTime(); // Game::GUITime(); + + if (avi_file) + spin /= 6; + + if (KeyDown(KEY_CAM_EXT_PLUS_AZ)) + cam_dir->ExternalAzimuth(spin); + + else if (KeyDown(KEY_CAM_EXT_MINUS_AZ)) + cam_dir->ExternalAzimuth(-spin); + + if (KeyDown(KEY_CAM_EXT_PLUS_EL)) + cam_dir->ExternalElevation(spin); + + else if (KeyDown(KEY_CAM_EXT_MINUS_EL)) + cam_dir->ExternalElevation(-spin); + + if (KeyDown(KEY_CAM_VIRT_PLUS_AZ)) + cam_dir->VirtualAzimuth(-spin); + + else if (KeyDown(KEY_CAM_VIRT_MINUS_AZ)) + cam_dir->VirtualAzimuth(spin); + + if (KeyDown(KEY_CAM_VIRT_PLUS_EL)) + cam_dir->VirtualElevation(spin); + + else if (KeyDown(KEY_CAM_VIRT_MINUS_EL)) + cam_dir->VirtualElevation(-spin); + + if (KeyDown(KEY_CAM_EXT_PLUS_RANGE)){ + if (!gamescreen->IsNavShown()) { + cam_dir->ExternalRange((float) (1 + 1.5 * Game::FrameTime())); // 1.1f); + } + } + + else if (KeyDown(KEY_CAM_EXT_MINUS_RANGE)) { + if (!gamescreen->IsNavShown()) { + cam_dir->ExternalRange((float) (1 - 1.5 * Game::FrameTime())); // 0.9f); + } + } + + if (tactical && !gamescreen->IsFormShown()) { + if (Mouse::Wheel()) { + int w = Mouse::Wheel(); + + if (w < 0) { + while (w < 0) { + cam_dir->ExternalRange(1.25f); + w += 120; + } + } + else { + while (w > 0) { + cam_dir->ExternalRange(0.75f); + w -= 120; + } + } + } + + else if (Mouse::LButton() && Mouse::RButton()) { + if (mouse_dy < 0) + cam_dir->ExternalRange(0.85f); + else if (mouse_dy > 0) + cam_dir->ExternalRange(1.15f); + } + + else if (Mouse::MButton() && time_til_change <= 0) { + time_til_change = 0.5; + ViewSelection(); + } + + else { + if (mouse_dx || mouse_dy) { + if (CameraDirector::GetCameraMode() == CameraDirector::MODE_VIRTUAL) { + cam_dir->VirtualAzimuth( mouse_dx * 0.2 * DEGREES); + cam_dir->VirtualElevation(mouse_dy * 0.2 * DEGREES); + } + else { + cam_dir->ExternalAzimuth( mouse_dx * 0.2 * DEGREES); + cam_dir->ExternalElevation(mouse_dy * 0.2 * DEGREES); + } + } + } + } + } + + // radio message hot keys: + static bool comm_key = false; + + if (KeyDown(KEY_COMM_ATTACK_TGT)) { + if (!comm_key) + RadioTraffic::SendQuickMessage(player_ship, RadioMessage::ATTACK); + comm_key = true; + } + + else if (KeyDown(KEY_COMM_ESCORT_TGT)) { + if (!comm_key) + RadioTraffic::SendQuickMessage(player_ship, RadioMessage::ESCORT); + comm_key = true; + } + + else if (KeyDown(KEY_COMM_WEP_FREE)) { + if (!comm_key) + RadioTraffic::SendQuickMessage(player_ship, RadioMessage::WEP_FREE); + comm_key = true; + } + + else if (KeyDown(KEY_COMM_WEP_HOLD)) { + if (!comm_key) + RadioTraffic::SendQuickMessage(player_ship, RadioMessage::FORM_UP); + comm_key = true; + } + + else if (KeyDown(KEY_COMM_COVER_ME)) { + if (!comm_key) + RadioTraffic::SendQuickMessage(player_ship, RadioMessage::COVER_ME); + comm_key = true; + } + + else if (KeyDown(KEY_COMM_SKIP_NAV)) { + if (!comm_key) + RadioTraffic::SendQuickMessage(player_ship, RadioMessage::SKIP_NAVPOINT); + comm_key = true; + } + + else if (KeyDown(KEY_COMM_RETURN_TO_BASE)) { + if (!comm_key) + RadioTraffic::SendQuickMessage(player_ship, RadioMessage::RTB); + comm_key = true; + } + + else if (KeyDown(KEY_COMM_CALL_INBOUND)) { + if (!comm_key) + RadioTraffic::SendQuickMessage(player_ship, RadioMessage::CALL_INBOUND); + comm_key = true; + } + + else if (KeyDown(KEY_COMM_REQUEST_PICTURE)) { + if (!comm_key) + RadioTraffic::SendQuickMessage(player_ship, RadioMessage::REQUEST_PICTURE); + comm_key = true; + } + + else if (KeyDown(KEY_COMM_REQUEST_SUPPORT)) { + if (!comm_key) + RadioTraffic::SendQuickMessage(player_ship, RadioMessage::REQUEST_SUPPORT); + comm_key = true; + } + + else if (KeyDown(KEY_CHAT_BROADCAST)) { + if (!comm_key) + SetChatMode(CHAT_BROADCAST); + comm_key = true; + } + + else if (KeyDown(KEY_CHAT_TEAM)) { + if (!comm_key) + SetChatMode(CHAT_TEAM); + comm_key = true; + } + + else if (KeyDown(KEY_CHAT_WING)) { + if (!comm_key) + SetChatMode(CHAT_WING); + comm_key = true; + } + + else if (KeyDown(KEY_CHAT_UNIT) && sim->GetSelection().container().size()) { + if (!comm_key) + SetChatMode(CHAT_UNIT); + comm_key = true; + } + + else { + comm_key = false; + } +} + +void +Starshatter::DoChatMode() +{ + Player* p = Player::GetCurrentPlayer(); + bool send_chat = false; + Text name = "Player"; + + if (player_ship) + name = player_ship->Name(); + + if (p) + name = p->Name(); + + if (chat_text.length()) { + if (chat_text[0] >= '0' && chat_text[0] <= '9') { + if (p) { + chat_text = p->ChatMacro(chat_text[0] - '0'); + send_chat = true; + } + } + } + + if (KeyDown(KEY_EXIT)) { + SetChatMode(0); + time_til_change = 0.5; + } + + else if (send_chat || Keyboard::KeyDown(VK_RETURN)) { + switch (chat_mode) { + default: + case CHAT_BROADCAST: + { + NetUtil::SendChat(0, name, chat_text); + } + break; + + case CHAT_TEAM: + if (player_ship) { + NetUtil::SendChat(player_ship->GetIFF()+1, name, chat_text); + } + break; + + case CHAT_WING: + if (player_ship) { + Element* elem = player_ship->GetElement(); + if (elem) { + for (int i = 1; i <= elem->NumShips(); i++) { + Ship* s = elem->GetShip(i); + if (s && s != player_ship) + NetUtil::SendChat(s->GetObjID(), name, chat_text); + } + } + } + break; + + case CHAT_UNIT: + { + Sim* sim = Sim::GetSim(); + ListIter seln = sim->GetSelection(); + + while (++seln) { + Ship* s = seln.value(); + if (s != player_ship) + NetUtil::SendChat(s->GetObjID(), name, chat_text); + } + } + break; + } + + HUDView::Message("%s> %s", name.data(), chat_text.data()); + + SetChatMode(0); + time_til_change = 0.5; + } + + else { + int key = 0; + int shift = 0; + + while (GetKeyPlus(key, shift)) { + if (key >= 'A' && key <= 'Z') { + if (shift & 1) + chat_text += (char) key; + else + chat_text += (char) tolower(key); + } + else { + switch (key) { + case VK_BACK: + chat_text = chat_text.substring(0, chat_text.length()-1); + break; + + case VK_SPACE: chat_text += ' '; break; + case '0': if (shift & 1) chat_text += ')'; else chat_text += '0'; break; + case '1': if (shift & 1) chat_text += '!'; else chat_text += '1'; break; + case '2': if (shift & 1) chat_text += '@'; else chat_text += '2'; break; + case '3': if (shift & 1) chat_text += '#'; else chat_text += '3'; break; + case '4': if (shift & 1) chat_text += '$'; else chat_text += '4'; break; + case '5': if (shift & 1) chat_text += '%'; else chat_text += '5'; break; + case '6': if (shift & 1) chat_text += '^'; else chat_text += '6'; break; + case '7': if (shift & 1) chat_text += '&'; else chat_text += '7'; break; + case '8': if (shift & 1) chat_text += '*'; else chat_text += '8'; break; + case '9': if (shift & 1) chat_text += '('; else chat_text += '9'; break; + case 186: if (shift & 1) chat_text += ':'; else chat_text += ';'; break; + case 187: if (shift & 1) chat_text += '+'; else chat_text += '='; break; + case 188: if (shift & 1) chat_text += '<'; else chat_text += ','; break; + case 189: if (shift & 1) chat_text += '_'; else chat_text += '-'; break; + case 190: if (shift & 1) chat_text += '>'; else chat_text += '.'; break; + case 191: if (shift & 1) chat_text += '?'; else chat_text += '/'; break; + case 192: if (shift & 1) chat_text += '~'; else chat_text += '`'; break; + case 219: if (shift & 1) chat_text += '{'; else chat_text += '['; break; + case 221: if (shift & 1) chat_text += '}'; else chat_text += ']'; break; + case 220: if (shift & 1) chat_text += '|'; else chat_text += '\\'; break; + case 222: if (shift & 1) chat_text += '"'; else chat_text += '\''; break; + } + } + } + } +} + +void +Starshatter::SetChatMode(int mode) +{ + if (mode >= 0 && mode <= 4 && mode != chat_mode) { + chat_text = ""; + + if (!NetGame::IsNetGame()) { + chat_mode = 0; + } + else { + chat_mode = mode; + + // flush input before reading chat message: + if (chat_mode) { + FlushKeys(); + } + + // flush input before sampling flight controls: + else { + for (int i = 1; i < 255; i++) { + Keyboard::KeyDown(i); + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +Starshatter::DoMouseFrame() +{ + EventDispatch* event_dispatch = EventDispatch::GetInstance(); + if (event_dispatch && event_dispatch->GetCapture()) + return; + + mouse_dx = 0; + mouse_dy = 0; + + static int old_mouse_x = 0; + static int old_mouse_y = 0; + + if (!spinning && Mouse::RButton()) { + spinning = true; + old_mouse_x = Mouse::X(); + old_mouse_y = Mouse::Y(); + mouse_x = Mouse::X(); + mouse_y = Mouse::Y(); + Mouse::Show(false); + } + + else if (spinning) { + if (!Mouse::RButton()) { + spinning = false; + + if (tactical) { + Mouse::Show(true); + Mouse::SetCursor(Mouse::ARROW); + } + + Mouse::SetCursorPos(old_mouse_x, old_mouse_y); + } + + else { + mouse_dx = Mouse::X() - mouse_x; + mouse_dy = Mouse::Y() - mouse_y; + + Mouse::SetCursorPos(mouse_x, mouse_y); + } + } + + else if (cutscene || !player_ship) { + Mouse::Show(false); + return; + } + + // hide mouse cursor when mouse controller is actively steering: + else if (mouse_input && mouse_input->Active()) { + if (mouse_input->Selector() == 1) { + Mouse::Show(false); + } + + else if (mouse_input->Selector() == 2) { + Mouse::Show(true); + Mouse::SetCursor(Mouse::CROSS); + } + } + + else { + HUDView* hud_view = HUDView::GetInstance(); + + if (hud_view && hud_view->GetHUDMode() != HUDView::HUD_MODE_OFF) { + Mouse::Show(true); + Mouse::SetCursor(Mouse::ARROW); + } + } + + if (gamescreen && gamescreen->IsFormShown()) + return; + + TacticalView* tac_view = TacticalView::GetInstance(); + + if (tac_view) + tac_view->DoMouseFrame(); +} + +// +--------------------------------------------------------------------+ + +void +Starshatter::SetupSplash() +{ + Color::SetFade(0); + + if (gamewin) { + screen->DelWindow(gamewin); + delete gamewin; + gamewin = 0; + } + + switch (splash_index) { + case 0: + splash_image.ClearImage(); + loader->SetDataPath(0); + loader->LoadBitmap("matrix.pcx", splash_image); + break; + + case 1: + default: + splash_image.ClearImage(); + loader->SetDataPath(0); + loader->LoadBitmap("studio.pcx", splash_image); + break; + } + + int screen_width = GetScreenWidth(); + int screen_height = GetScreenHeight(); + + gamewin = new(__FILE__,__LINE__) Window(screen, 0, 0, screen_width, screen_height); + splash = new(__FILE__,__LINE__) FadeView(gamewin, 2, 2, 2); + + gamewin->AddView(splash); + gamewin->AddView(new(__FILE__,__LINE__) ImgView(gamewin, &splash_image)); + screen->AddWindow(gamewin); +} + +// +--------------------------------------------------------------------+ + +void +Starshatter::SetupMenuScreen() +{ + if (menuscreen) { + delete menuscreen; + menuscreen = 0; + } + + menuscreen = new(__FILE__,__LINE__) MenuScreen(); + menuscreen->Setup(screen); +} + +// +--------------------------------------------------------------------+ + +void +Starshatter::SetupCmpnScreen() +{ + if (cmpnscreen) { + delete cmpnscreen; + cmpnscreen = 0; + } + + cmpnscreen = new(__FILE__,__LINE__) CmpnScreen(); + cmpnscreen->Setup(screen); +} + +// +--------------------------------------------------------------------+ + +void +Starshatter::SetupPlanScreen() +{ + if (planscreen) { + delete planscreen; + planscreen = 0; + } + + planscreen = new(__FILE__,__LINE__) PlanScreen(); + planscreen->Setup(screen); +} + +// +--------------------------------------------------------------------+ + +void +Starshatter::SetupLoadScreen() +{ + if (loadscreen) { + delete loadscreen; + loadscreen = 0; + } + + loadscreen = new(__FILE__,__LINE__) LoadScreen(); + loadscreen->Setup(screen); +} + +// +--------------------------------------------------------------------+ + +void +Starshatter::SetupGameScreen() +{ + if (gamescreen) { + delete gamescreen; + gamescreen = 0; + } + + gamescreen = new(__FILE__,__LINE__) GameScreen(); + gamescreen->Setup(screen); + gamescreen->SetFieldOfView(field_of_view); + + // initialize player_ship's MFD choices: + Player::SelectPlayer(Player::GetCurrentPlayer()); +} + +// +--------------------------------------------------------------------+ + +void +Starshatter::LoadVideoConfig(const char* filename) +{ + // set up defaults: + int screen_width = 1024; + int screen_height = 768; + int screen_depth = 32; + int terrain_detail_level = 3; + bool shadows_enabled = true; + bool spec_maps_enabled = true; + bool bump_maps_enabled = true; + bool vertex_shader = true; + bool pixel_shader = true; + float depth_bias = video_settings->depth_bias; + + max_tex_size = 2048; + + if (MachineInfo::GetCpuSpeed() >= 1000 && MachineInfo::GetTotalRam() > 128) + terrain_detail_level = 4; + + Terrain::SetDetailLevel(terrain_detail_level); + + // read the config file: + BYTE* block = 0; + int blocklen = 0; + + FILE* f = ::fopen(filename, "rb"); + + if (f) { + ::fseek(f, 0, SEEK_END); + blocklen = ftell(f); + ::fseek(f, 0, SEEK_SET); + + block = new(__FILE__,__LINE__) BYTE[blocklen+1]; + block[blocklen] = 0; + + ::fread(block, blocklen, 1, f); + ::fclose(f); + } + + if (blocklen == 0) + return; + + Parser parser(new(__FILE__,__LINE__) BlockReader((const char*) block, blocklen)); + Term* term = parser.ParseTerm(); + + if (!term) { + Print("ERROR: could not parse '%s'.\n", filename); + exit(-3); + } + else { + TermText* file_type = term->isText(); + if (!file_type || file_type->value() != "VIDEO") { + Print("WARNING: invalid %s file. Using defaults\n", filename); + return; + } + } + + do { + delete term; + + term = parser.ParseTerm(); + + if (term) { + TermDef* def = term->isDef(); + if (def) { + if (def->name()->value() == "width") { + int w; + GetDefNumber(w, def, filename); + + switch (w) { + case 800: + screen_width = 800; + screen_height = 600; + break; + + case 1024: + screen_width = 1024; + screen_height = 768; + break; + + case 1152: + screen_width = 1152; + screen_height = 864; + break; + + case 1280: + screen_width = 1280; + screen_height = 960; + break; + + case 1440: + screen_width = 1440; + screen_height = 900; + break; + + default: + screen_width = w; + screen_height = (w*3)/4; + break; + } + } + + else if (def->name()->value() == "height") { + int h; + GetDefNumber(h, def, filename); + + if (screen_width == 1280 && (h == 800 || h == 1024)) + screen_height = h; + else if (screen_width == 1600 && (h == 900 || h == 1200)) + screen_height = h; + } + + else if (def->name()->value() == "depth" || + def->name()->value() == "bpp") { + + int bpp; + GetDefNumber(bpp, def, filename); + + switch (bpp) { + case 8: + case 16: + case 24: + case 32: + screen_depth = bpp; + break; + + default: + Print("WARNING: Invalid screen bpp (%d) in '%s'\n", bpp, filename); + screen_depth = 8; + break; + } + } + + else if (def->name()->value() == "max_tex") { + int n; + GetDefNumber(n, def, filename); + if (n >= 64 && n <= 4096) + max_tex_size = n; + } + + else if (def->name()->value() == "gamma") { + int level; + GetDefNumber(level, def, filename); + if (level >= 0 && level <= 255) + gamma = level; + } + + else if (def->name()->value() == "terrain_detail_level") { + GetDefNumber(terrain_detail_level, def, filename); + Terrain::SetDetailLevel(terrain_detail_level); + } + + else if (def->name()->value() == "terrain_texture_enable") { + bool enable; + GetDefBool(enable, def, filename); + + // no longer supported! + } + + else if (def->name()->value() == "shadows") { + GetDefBool(shadows_enabled, def, filename); + } + + else if (def->name()->value() == "spec_maps") { + GetDefBool(spec_maps_enabled, def, filename); + } + + else if (def->name()->value() == "bump_maps") { + GetDefBool(bump_maps_enabled, def, filename); + } + + else if (def->name()->value() == "vertex_shader") { + GetDefBool(vertex_shader, def, filename); + } + + else if (def->name()->value() == "pixel_shader") { + GetDefBool(pixel_shader, def, filename); + } + + else if (def->name()->value().contains("bias")) { + GetDefNumber(depth_bias, def, filename); + } + + else if (def->name()->value() == "flare") { + bool b; + GetDefBool(b, def, filename); + lens_flare = b; + } + + else if (def->name()->value() == "corona") { + bool b; + GetDefBool(b, def, filename); + corona = b; + } + + else if (def->name()->value() == "nebula") { + bool b; + GetDefBool(b, def, filename); + nebula = b; + } + + else if (def->name()->value() == "dust") { + GetDefNumber(dust, def, filename); + } + + else if (def->name()->value().indexOf("cam_range") == 0) { + double range_max = 0; + GetDefNumber(range_max, def, filename); + CameraDirector::SetRangeLimit(range_max); + } + } + else { + Print("WARNING: term ignored in '%s'\n", filename); + term->print(); + } + } + } + while (term); + + loader->ReleaseBuffer(block); + + if (video_settings) { + video_settings->fullscreen_mode.width = screen_width; + video_settings->fullscreen_mode.height = screen_height; + + if (screen_depth == 16) + video_settings->fullscreen_mode.format = VideoMode::FMT_R5G6B5; + else + video_settings->fullscreen_mode.format = VideoMode::FMT_X8R8G8B8; + + video_settings->shadows = shadows_enabled; + video_settings->specmaps = spec_maps_enabled; + video_settings->bumpmaps = bump_maps_enabled; + video_settings->enable_vs = vertex_shader; + video_settings->enable_ps = pixel_shader; + video_settings->depth_bias = depth_bias; + } + + if (video) { + video->SetShadowEnabled(shadows_enabled); + video->SetSpecMapEnabled(spec_maps_enabled); + video->SetBumpMapEnabled(bump_maps_enabled); + } +} + +// +--------------------------------------------------------------------+ + +void +Starshatter::SaveVideoConfig(const char* filename) +{ + if (!video_settings) + return; + + FILE* f = fopen(filename, "w"); + if (f) { + fprintf(f, "VIDEO\n\n"); + fprintf(f, "width: %4d\n", video_settings->fullscreen_mode.width); + fprintf(f, "height: %4d\n", video_settings->fullscreen_mode.height); + fprintf(f, "depth: %4d\n", video_settings->fullscreen_mode.format == VideoMode::FMT_R5G6B5 ? 16 : 32); + fprintf(f, "\n"); + fprintf(f, "max_tex: %4d\n", max_tex_size); + fprintf(f, "primary3D: %s\n", "true"); + fprintf(f, "gamma: %4d\n", gamma); + fprintf(f, "\n"); + fprintf(f, "terrain_detail_level: %d\n", Terrain::DetailLevel()); + fprintf(f, "terrain_texture_enable: %true\n"); + fprintf(f, "\n"); + + fprintf(f, "shadows: %s\n", video_settings->shadows ? "true" : "false"); + fprintf(f, "spec_maps: %s\n", video_settings->specmaps ? "true" : "false"); + fprintf(f, "bump_maps: %s\n", video_settings->bumpmaps ? "true" : "false"); + fprintf(f, "bias: %f\n", video_settings->depth_bias); + fprintf(f, "\n"); + + fprintf(f, "flare: %s\n", lens_flare ? "true" : "false"); + fprintf(f, "corona: %s\n", corona ? "true" : "false"); + fprintf(f, "nebula: %s\n", nebula ? "true" : "false"); + fprintf(f, "dust: %d\n", dust); + + if (CameraDirector::GetRangeLimit() != 300e3) + fprintf(f, " cam_range_max: %f,\n", CameraDirector::GetRangeLimit()); + + fclose(f); + } + + video_changed = false; +} + +// +--------------------------------------------------------------------+ + +void +Starshatter::InvalidateTextureCache() +{ + if (video) + video->InvalidateCache(); +} + +// +--------------------------------------------------------------------+ + +void +Starshatter::ExecCutscene(const char* msn_file, const char* path) +{ + if (InCutscene() || !msn_file || !*msn_file) return; + + if (!world) + CreateWorld(); + + cutscene_mission = new(__FILE__,__LINE__) Mission(0); + cutscene_basetime = StarSystem::GetBaseTime(); + + if (cutscene_mission->Load(msn_file, path)) { + Sim* sim = (Sim*) world; + + if (sim) { + bool dynamic = false; + Campaign* campaign = Campaign::GetCampaign(); + + if (campaign && campaign->IsDynamic()) + dynamic = true; + + sim->UnloadMission(); + sim->LoadMission(cutscene_mission, true); // attempt to preload the tex cache + sim->ExecMission(); + sim->ShowGrid(false); + player_ship = sim->GetPlayerShip(); + + Print(" Cutscene Instantiated.\n"); + + UpdateWorld(); + } + } + else { + delete cutscene_mission; + cutscene_mission = 0; + cutscene_basetime = 0; + } +} + +void +Starshatter::BeginCutscene() +{ + Sim* sim = Sim::GetSim(); + + if (cutscene == 0 && !sim->IsNetGame()) { + HUDView* hud_view = HUDView::GetInstance(); + if (hud_view) + hud_view->SetHUDMode(HUDView::HUD_MODE_OFF); + + if (sim->GetPlayerShip()) + sim->GetPlayerShip()->SetControls(0); + + AudioConfig* audio_cfg = AudioConfig::GetInstance(); + + if (audio_cfg) { + cut_efx_volume = audio_cfg->GetEfxVolume(); + cut_wrn_volume = audio_cfg->GetWrnVolume(); + } + + Ship::SetFlightModel(Ship::FM_ARCADE); + } + + cutscene++; +} + +void +Starshatter::EndCutscene() +{ + cutscene--; + + if (cutscene == 0) { + DisplayView* disp_view = DisplayView::GetInstance(); + if (disp_view) + disp_view->ClearDisplay(); + + HUDView* hud_view = HUDView::GetInstance(); + if (hud_view) + hud_view->SetHUDMode(HUDView::HUD_MODE_TAC); + + Sim* sim = Sim::GetSim(); + if (sim->GetPlayerShip()) + sim->GetPlayerShip()->SetControls(sim->GetControls()); + + if (cam_dir) { + cam_dir->SetViewOrbital(0); + CameraDirector::SetRangeLimits(10, CameraDirector::GetRangeLimit()); + cam_dir->SetOrbitPoint(PI/4,PI/4,1); + cam_dir->SetOrbitRates(0,0,0); + } + + AudioConfig* audio_cfg = AudioConfig::GetInstance(); + + if (audio_cfg) { + audio_cfg->SetEfxVolume(cut_efx_volume); + audio_cfg->SetWrnVolume(cut_wrn_volume); + } + + Player* p = Player::GetCurrentPlayer(); + if (p) + Ship::SetFlightModel(p->FlightModel()); + } +} + +void +Starshatter::EndMission() +{ + if (cutscene_mission) { + Sim* sim = Sim::GetSim(); + + if (sim && sim->GetMission() == cutscene_mission) { + ShipStats::Initialize(); + sim->UnloadMission(); + + // restore world clock (true => absolute time reference) + if (cutscene_basetime != 0) + StarSystem::SetBaseTime(cutscene_basetime, true); + + delete cutscene_mission; + cutscene_mission = 0; + cutscene_basetime = 0; + + return; + } + } + + SetGameMode(Starshatter::PLAN_MODE); +} + +Mission* +Starshatter::GetCutsceneMission() const +{ + return cutscene_mission; +} + +const char* +Starshatter::GetSubtitles() const +{ + if (cutscene_mission) + return cutscene_mission->Subtitles(); + + return ""; +} + +// +--------------------------------------------------------------------+ + +int +Starshatter::GetLobbyMode() +{ + return lobby_mode; +} + +void +Starshatter::SetLobbyMode(int mode) +{ + lobby_mode = mode; +} + +void +Starshatter::StartLobby() +{ + if (!net_lobby) { + if (lobby_mode == NET_LOBBY_SERVER) { + NetServerConfig::Initialize(); + NetServerConfig* server_config = NetServerConfig::GetInstance(); + + if (server_config) + net_lobby = new(__FILE__,__LINE__) NetLobbyServer; + } + + else { + NetClientConfig* client_config = NetClientConfig::GetInstance(); + if (client_config) + client_config->Login(); + + net_lobby = NetLobby::GetInstance(); + } + } + + lobby_mode = NET_LOBBY_CLIENT; +} + +void +Starshatter::StopLobby() +{ + if (net_lobby) { + if (net_lobby->IsServer()) { + delete net_lobby; + NetServerConfig::Close(); + } + + else { + NetClientConfig* client_config = NetClientConfig::GetInstance(); + if (client_config) + client_config->Logout(); + } + + net_lobby = 0; + } + + lobby_mode = NET_LOBBY_CLIENT; +} + +void +Starshatter::StopNetGame() +{ + // local server: + NetLobby* lobby = NetLobby::GetInstance(); + + if (lobby && lobby->IsServer()) { + lobby->GameStop(); + } + + // client connected to remote server: + else { + NetClientConfig* config = NetClientConfig::GetInstance(); + if (config && config->GetHostRequest()) { + config->Login(); + + NetLobbyClient* conn = config->GetConnection(); + + if (conn) { + conn->GameStop(); + conn->SelectMission(0); + } + } + } +} diff --git a/Stars45/Starshatter.h b/Stars45/Starshatter.h new file mode 100644 index 0000000..023408b --- /dev/null +++ b/Stars45/Starshatter.h @@ -0,0 +1,225 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Starshatter.h + AUTHOR: John DiCamillo + +*/ + +#ifndef Starshatter_h +#define Starshatter_h + +#include "Types.h" +#include "Game.h" +#include "Bitmap.h" +#include "KeyMap.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class Campaign; +class MenuScreen; +class CmpnScreen; +class PlanScreen; +class LoadScreen; +class GameScreen; +class Ship; +class Sim; +class FadeView; +class CameraDirector; +class MultiController; +class MouseController; +class MusicDirector; +class DataLoader; +class Font; +class TrackIR; +class Mission; + +class NetServer; +class NetLobby; + +// +--------------------------------------------------------------------+ + +class Starshatter : public Game +{ +public: + Starshatter(); + virtual ~Starshatter(); + + virtual bool Init(HINSTANCE hi, HINSTANCE hpi, LPSTR cmdline, int nCmdShow); + virtual bool InitGame(); + virtual bool ChangeVideo(); + virtual void GameState(); + virtual void Exit(); + virtual bool OnHelp(); + + enum MODE { MENU_MODE, // main menu + CLOD_MODE, // loading campaign + CMPN_MODE, // operational command for dynamic campaign + PREP_MODE, // loading mission info for planning + PLAN_MODE, // mission briefing + LOAD_MODE, // loading mission into simulator + PLAY_MODE, // active simulation + EXIT_MODE // shutting down + }; + + enum LOBBY { NET_LOBBY_CLIENT, + NET_LOBBY_SERVER + }; + + int GetGameMode() { return game_mode; } + void SetGameMode(int mode); + void RequestChangeVideo(); + void LoadVideoConfig(const char* filename); + void SaveVideoConfig(const char* filename); + void SetupSplash(); + void SetupMenuScreen(); + void SetupCmpnScreen(); + void SetupPlanScreen(); + void SetupLoadScreen(); + void SetupGameScreen(); + void OpenTacticalReference(); + void CreateWorld(); + int KeyDown(int action) const; + + void PlayerCam(int mode); + void ViewSelection(); + + void MapKeys(); + static void MapKeys(KeyMap* mapping, int nkeys); + static void MapKey(int act, int key, int alt=0); + + void SetTestMode(int t) { test_mode = t; } + + static Starshatter* GetInstance() { return instance; } + + int GetScreenWidth(); + int GetScreenHeight(); + + // graphic options: + int LensFlare() { return lens_flare; } + int Corona() { return corona; } + int Nebula() { return nebula; } + int Dust() { return dust; } + + KeyMap& GetKeyMap() { return keycfg; } + + int GetLoadProgress() { return load_progress; } + const char* GetLoadActivity() { return load_activity; } + + void InvalidateTextureCache(); + + int GetChatMode() const { return chat_mode; } + void SetChatMode(int c); + const char* GetChatText() const { return chat_text.data(); } + + void StopNetGame(); + + int GetLobbyMode(); + void SetLobbyMode(int mode = NET_LOBBY_CLIENT); + void StartLobby(); + void StopLobby(); + + void ExecCutscene(const char* msn_file, const char* path); + void BeginCutscene(); + void EndCutscene(); + bool InCutscene() const { return cutscene > 0; } + Mission* GetCutsceneMission() const; + const char* GetSubtitles() const; + void EndMission(); + + void StartOrResumeGame(); + + static bool UseFileSystem(); + +protected: + virtual void DoMenuScreenFrame(); + virtual void DoCmpnScreenFrame(); + virtual void DoPlanScreenFrame(); + virtual void DoLoadScreenFrame(); + virtual void DoGameScreenFrame(); + virtual void DoMouseFrame(); + + virtual void DoChatMode(); + virtual void DoGameKeys(); + + virtual bool GameLoop(); + virtual void UpdateWorld(); + virtual void InstantiateMission(); + virtual bool ResizeVideo(); + virtual void InitMouse(); + + static Starshatter* instance; + Window* gamewin; + MenuScreen* menuscreen; + LoadScreen* loadscreen; + PlanScreen* planscreen; + GameScreen* gamescreen; + CmpnScreen* cmpnscreen; + + FadeView* splash; + int splash_index; + Bitmap splash_image; + MultiController* input; + MouseController* mouse_input; + TrackIR* head_tracker; + DataLoader* loader; + + Ship* player_ship; + CameraDirector* cam_dir; + MusicDirector* music_dir; + + Font* HUDfont; + Font* GUIfont; + Font* GUI_small_font; + Font* terminal; + Font* verdana; + Font* title_font; + Font* limerick18; + Font* limerick12; + Font* ocrb; + + DWORD time_mark; + DWORD minutes; + + double field_of_view; + double orig_fov; + + static int keymap[256]; + static int keyalt[256]; + KeyMap keycfg; + + bool tactical; + bool spinning; + int mouse_x; + int mouse_y; + int mouse_dx; + int mouse_dy; + + int game_mode; + int test_mode; + int req_change_video; + int video_changed; + + int lens_flare; + int corona; + int nebula; + int dust; + + double exit_time; + + int load_step; + int load_progress; + Text load_activity; + int catalog_index; + + int cutscene; + int lobby_mode; + NetLobby* net_lobby; + int chat_mode; + Text chat_text; +}; + +#endif Starshatter_h diff --git a/Stars45/StarshipAI.cpp b/Stars45/StarshipAI.cpp new file mode 100644 index 0000000..6b78fee --- /dev/null +++ b/Stars45/StarshipAI.cpp @@ -0,0 +1,822 @@ +/* Project Starshatter 5.0 + Destroyer Studios LLC + Copyright © 1997-2007. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: StarshipAI.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Starship (low-level) Artificial Intelligence class +*/ + +#include "MemDebug.h" +#include "StarshipAI.h" +#include "StarshipTacticalAI.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "Element.h" +#include "Mission.h" +#include "Instruction.h" +#include "RadioMessage.h" +#include "Contact.h" +#include "WeaponGroup.h" +#include "Drive.h" +#include "Sim.h" +#include "StarSystem.h" +#include "FlightComp.h" +#include "Farcaster.h" +#include "QuantumDrive.h" + +#include "Game.h" +#include "Random.h" + +// +----------------------------------------------------------------------+ + +StarshipAI::StarshipAI(SimObject* s) + : ShipAI(s), sub_select_time(0), subtarget(0), tgt_point_defense(false) +{ + ai_type = STARSHIP; + + // signifies this ship is a dead hulk: + if (ship && ship->Design()->auto_roll < 0) { + Point torque(rand()-16000, rand()-16000, rand()-16000); + torque.Normalize(); + torque *= ship->Mass() / 10; + + ship->SetFLCSMode(0); + if (ship->GetFLCS()) + ship->GetFLCS()->PowerOff(); + + ship->ApplyTorque(torque); + ship->SetVelocity(RandomDirection() * Random(20, 50)); + + for (int i = 0; i < 64; i++) { + Weapon* w = ship->GetWeaponByIndex(i+1); + if (w) + w->DrainPower(0); + else + break; + } + } + + else { + tactical = new(__FILE__,__LINE__) StarshipTacticalAI(this); + } + + sub_select_time = Game::GameTime() + (DWORD) Random(0, 2000); + point_defense_time = sub_select_time; +} + + +// +--------------------------------------------------------------------+ + +StarshipAI::~StarshipAI() +{ } + +// +--------------------------------------------------------------------+ + +void +StarshipAI::FindObjective() +{ + distance = 0; + + int order = ship->GetRadioOrders()->Action(); + + if (order == RadioMessage::QUANTUM_TO || + order == RadioMessage::FARCAST_TO) { + + FindObjectiveQuantum(); + objective = Transform(obj_w); + return; + } + + bool hold = order == RadioMessage::WEP_HOLD || + order == RadioMessage::FORM_UP; + + bool form = hold || + (!order && !target) || + (farcaster); + + // if not the element leader, stay in formation: + if (form && element_index > 1) { + ship->SetDirectorInfo(Game::GetText("ai.formation")); + + if (navpt && navpt->Action() == Instruction::LAUNCH) { + FindObjectiveNavPoint(); + } + else { + navpt = 0; + FindObjectiveFormation(); + } + + // transform into camera coords: + objective = Transform(obj_w); + return; + } + + // under orders? + bool directed = false; + double threat_level = 0; + double support_level = 1; + Ship* ward = ship->GetWard(); + + if (tactical) { + directed = (tactical->RulesOfEngagement() == TacticalAI::DIRECTED); + threat_level = tactical->ThreatLevel(); + support_level = tactical->SupportLevel(); + } + + // threat processing: + if (hold || !directed && threat_level >= 2*support_level) { + + // seek support: + if (support) { + double d_support = Point(support->Location() - ship->Location()).length(); + if (d_support > 35e3) { + ship->SetDirectorInfo(Game::GetText("ai.regroup")); + FindObjectiveTarget(support); + objective = Transform(obj_w); + return; + } + } + + // run away: + else if (threat && threat != target) { + ship->SetDirectorInfo(Game::GetText("ai.retreat")); + obj_w = ship->Location() + Point(ship->Location() - threat->Location()) * 100; + objective = Transform(obj_w); + return; + } + } + + // weapons hold: + if (hold) { + if (navpt) { + ship->SetDirectorInfo(Game::GetText("ai.seek-navpt")); + FindObjectiveNavPoint(); + } + + else if (patrol) { + ship->SetDirectorInfo(Game::GetText("ai.patrol")); + FindObjectivePatrol(); + } + + else { + ship->SetDirectorInfo(Game::GetText("ai.holding")); + objective = Point(); + } + } + + // normal processing: + else if (target) { + ship->SetDirectorInfo(Game::GetText("ai.seek-target")); + FindObjectiveTarget(target); + } + + else if (patrol) { + ship->SetDirectorInfo(Game::GetText("ai.patrol")); + FindObjectivePatrol(); + } + + else if (ward) { + ship->SetDirectorInfo(Game::GetText("ai.seek-ward")); + FindObjectiveFormation(); + } + + else if (navpt) { + ship->SetDirectorInfo(Game::GetText("ai.seek-navpt")); + FindObjectiveNavPoint(); + } + + else if (rumor) { + ship->SetDirectorInfo(Game::GetText("ai.search")); + FindObjectiveTarget(rumor); + } + + else { + objective = Point(); + } + + // transform into camera coords: + objective = Transform(obj_w); +} + +// +--------------------------------------------------------------------+ + +void +StarshipAI::Navigator() +{ + // signifies this ship is a dead hulk: + if (ship && ship->Design()->auto_roll < 0) { + ship->SetDirectorInfo(Game::GetText("ai.dead")); + return; + } + + accumulator.Clear(); + magnitude = 0; + + hold = false; + if ((ship->GetElement() && ship->GetElement()->GetHoldTime() > 0) || + (navpt && navpt->Status() == Instruction::COMPLETE && navpt->HoldTime() > 0)) + hold = true; + + ship->SetFLCSMode(Ship::FLCS_HELM); + + if (!ship->GetDirectorInfo()) { + if (target) + ship->SetDirectorInfo(Game::GetText("ai.seek-target")); + else if (ship->GetWard()) + ship->SetDirectorInfo(Game::GetText("ai.seek-ward")); + else + ship->SetDirectorInfo(Game::GetText("ai.patrol")); + } + + if (farcaster && distance < 25e3) { + accumulator = SeekTarget(); + } + else { + accumulator = AvoidCollision(); + + if (!other && !hold) + accumulator = SeekTarget(); + } + + HelmControl(); + ThrottleControl(); + FireControl(); + AdjustDefenses(); +} + +// +--------------------------------------------------------------------+ + +void +StarshipAI::HelmControl() +{ + // signifies this ship is a dead hulk: + if (ship && ship->Design()->auto_roll < 0) { + return; + } + + double trans_x = 0; + double trans_y = 0; + double trans_z = 0; + + bool station_keeping = distance < 0; + + if (station_keeping) { + accumulator.brake = 1; + accumulator.stop = 1; + + ship->SetHelmPitch(0); + } + + else { + Element* elem = ship->GetElement(); + + Ship* ward = ship->GetWard(); + Ship* s_threat = 0; + if (threat && threat->Class() >= ship->Class()) + s_threat = threat; + + if (other || target || ward || s_threat || navpt || patrol || farcaster || element_index > 1) { + ship->SetHelmHeading(accumulator.yaw); + + if (elem->Type() == Mission::FLIGHT_OPS) { + ship->SetHelmPitch(0); + + if (ship->NumInbound() > 0) { + ship->SetHelmHeading(ship->CompassHeading()); + } + } + + else if (accumulator.pitch > 60*DEGREES) { + ship->SetHelmPitch(60*DEGREES); + } + + else if (accumulator.pitch < -60*DEGREES) { + ship->SetHelmPitch(-60*DEGREES); + } + + else { + ship->SetHelmPitch(accumulator.pitch); + } + + } + else { + ship->SetHelmPitch(0); + } + } + + ship->SetTransX(trans_x); + ship->SetTransY(trans_y); + ship->SetTransZ(trans_z); + + ship->ExecFLCSFrame(); +} + +void +StarshipAI::ThrottleControl() +{ + // signifies this ship is a dead hulk: + if (ship && ship->Design()->auto_roll < 0) { + return; + } + + // station keeping: + + if (distance < 0) { + old_throttle = 0; + throttle = 0; + + ship->SetThrottle(0); + + if (ship->GetFLCS()) + ship->GetFLCS()->FullStop(); + + return; + } + + // normal throttle processing: + + double ship_speed = ship->Velocity() * ship->Heading(); + double brakes = 0; + Ship* ward = ship->GetWard(); + Ship* s_threat = 0; + + if (threat && threat->Class() >= ship->Class()) + s_threat = threat; + + if (target || s_threat) { // target pursuit, or retreat + throttle = 100; + + if (target && distance < 50e3) { + double closing_speed = ship_speed; + + if (target) { + Point delta = target->Location() - ship->Location(); + delta.Normalize(); + + closing_speed = ship->Velocity() * delta; + } + + if (closing_speed > 300) { + throttle = 30; + brakes = 0.25; + } + } + + throttle *= (1 - accumulator.brake); + + if (throttle < 1 && ship->GetFLCS() != 0) + ship->GetFLCS()->FullStop(); + } + + else if (ward) { // escort, match speed of ward + double speed = ward->Velocity().length(); + throttle = old_throttle; + + if (speed == 0) { + double d = (ship->Location() - ward->Location()).length(); + + if (d > 30e3) + speed = (d - 30e3) / 100; + } + + if (speed > 0) { + if (ship_speed > speed) { + throttle = old_throttle - 1; + brakes = 0.2; + } + else if (ship_speed < speed - 10) { + throttle = old_throttle + 1; + } + } + else { + throttle = 0; + brakes = 0.5; + } + } + + else if (patrol || farcaster) { // seek patrol point + throttle = 100; + + if (distance < 10 * ship_speed) { + if (ship->Velocity().length() > 200) + throttle = 5; + else + throttle = 50; + } + } + + else if (navpt) { // lead only, get speed from navpt + double speed = navpt->Speed(); + throttle = old_throttle; + + if (hold) { + throttle = 0; + brakes = 1; + } + + else { + if (speed <= 0) + speed = 300; + + if (ship_speed > speed) { + if (throttle > 0 && old_throttle > 1) + throttle = old_throttle - 1; + + brakes = 0.25; + } + else if (ship_speed < speed - 10) { + throttle = old_throttle + 1; + } + } + } + + else if (element_index > 1) { // wingman + Ship* lead = ship->GetElement()->GetShip(1); + double lv = lead->Velocity().length(); + double sv = ship_speed; + double dv = lv-sv; + double dt = 0; + + if (dv > 0) dt = dv * 1e-2 * seconds; + else if (dv < 0) dt = dv * 1e-2 * seconds; + + throttle = old_throttle + dt; + } + + else { + throttle = 0; + } + + old_throttle = throttle; + ship->SetThrottle(throttle); + + if (ship_speed > 1 && brakes > 0) + ship->SetTransY(-brakes * ship->Design()->trans_y); + + else if (throttle > 10 && (ship->GetEMCON() < 2 || ship->GetFuelLevel() < 10)) + ship->SetTransY(ship->Design()->trans_y); +} + +// +--------------------------------------------------------------------+ + +Steer +StarshipAI::SeekTarget() +{ + if (navpt) { + SimRegion* self_rgn = ship->GetRegion(); + SimRegion* nav_rgn = navpt->Region(); + QuantumDrive* qdrive = ship->GetQuantumDrive(); + + if (self_rgn && !nav_rgn) { + nav_rgn = self_rgn; + navpt->SetRegion(nav_rgn); + } + + bool use_farcaster = self_rgn != nav_rgn && + (navpt->Farcast() || + !qdrive || + !qdrive->IsPowerOn() || + qdrive->Status() < System::DEGRADED + ); + + if (use_farcaster) { + if (!farcaster) { + ListIter s = self_rgn->Ships(); + while (++s && !farcaster) { + if (s->GetFarcaster()) { + const Ship* dest = s->GetFarcaster()->GetDest(); + if (dest && dest->GetRegion() == nav_rgn) { + farcaster = s->GetFarcaster(); + } + } + } + } + + if (farcaster) { + if (farcaster->GetShip()->GetRegion() != self_rgn) + farcaster = farcaster->GetDest()->GetFarcaster(); + + obj_w = farcaster->EndPoint(); + distance = Point(obj_w - ship->Location()).length(); + + if (distance < 1000) + farcaster = 0; + } + } + else if (self_rgn != nav_rgn) { + QuantumDrive* q = ship->GetQuantumDrive(); + + if (q) { + if (q->ActiveState() == QuantumDrive::ACTIVE_READY) { + q->SetDestination(navpt->Region(), navpt->Location()); + q->Engage(); + } + } + } + } + + return ShipAI::SeekTarget(); +} + +// +--------------------------------------------------------------------+ + +Steer +StarshipAI::AvoidCollision() +{ + if (!ship || ship->Velocity().length() < 25) + return Steer(); + + return ShipAI::AvoidCollision(); +} + +// +--------------------------------------------------------------------+ + +void +StarshipAI::FireControl() +{ + // identify unknown contacts: + if (identify) { + if (fabs(ship->GetHelmHeading() - ship->CompassHeading()) < 10*DEGREES) { + Contact* contact = ship->FindContact(target); + + if (contact && !contact->ActLock()) { + if (!ship->GetProbe()) { + ship->LaunchProbe(); + } + } + } + + return; + } + + // investigate last known location of enemy ship: + if (rumor && !target && ship->GetProbeLauncher() && !ship->GetProbe()) { + // is rumor in basket? + Point rmr = Transform(rumor->Location()); + rmr.Normalize(); + + double dx = fabs(rmr.x); + double dy = fabs(rmr.y); + + if (dx < 10*DEGREES && dy < 10*DEGREES && rmr.z > 0) { + ship->LaunchProbe(); + } + } + + // Corvettes and Frigates are anti-air platforms. They need to + // target missile threats even when the threat is aimed at another + // friendly ship. Forward facing weapons must be on auto fire, + // while lateral and aft facing weapons are set to point defense. + + if (ship->Class() == Ship::CORVETTE || ship->Class() == Ship::FRIGATE) { + ListIter iter = ship->Weapons(); + while (++iter) { + WeaponGroup* group = iter.value(); + + ListIter w_iter = group->GetWeapons(); + while (++w_iter) { + Weapon* weapon = w_iter.value(); + + double az = weapon->GetAzimuth(); + if (fabs(az) < 45*DEGREES) { + weapon->SetFiringOrders(Weapon::AUTO); + weapon->SetTarget(target, 0); + } + + else { + weapon->SetFiringOrders(Weapon::POINT_DEFENSE); + } + } + } + } + + // All other starships are free to engage ship targets. Weapon + // fire control is managed by the type of weapon. + + else { + System* subtgt = SelectSubtarget(); + + ListIter iter = ship->Weapons(); + while (++iter) { + WeaponGroup* weapon = iter.value(); + + if (weapon->GetDesign()->target_type & Ship::DROPSHIPS) { // anti-air weapon? + weapon->SetFiringOrders(Weapon::POINT_DEFENSE); + } + else if (weapon->IsDrone()) { // torpedoes + weapon->SetFiringOrders(Weapon::MANUAL); + weapon->SetTarget(target, 0); + + if (target && target->GetRegion() == ship->GetRegion()) { + Point delta = target->Location() - ship->Location(); + double range = delta.length(); + + if (range < weapon->GetDesign()->max_range * 0.9 && + !AssessTargetPointDefense()) + weapon->SetFiringOrders(Weapon::AUTO); + + else if (range < weapon->GetDesign()->max_range * 0.5) + weapon->SetFiringOrders(Weapon::AUTO); + } + } + else { // anti-ship weapon + weapon->SetFiringOrders(Weapon::AUTO); + weapon->SetTarget(target, subtgt); + weapon->SetSweep(subtgt ? Weapon::SWEEP_NONE : Weapon::SWEEP_TIGHT); + } + } + } +} + +// +--------------------------------------------------------------------+ + +System* +StarshipAI::SelectSubtarget() +{ + if (Game::GameTime() - sub_select_time < 2345) + return subtarget; + + subtarget = 0; + + if (!target || target->Type() != SimObject::SIM_SHIP || GetAILevel() < 1) + return subtarget; + + Ship* tgt_ship = (Ship*) target; + + if (!tgt_ship->IsStarship()) + return subtarget; + + Weapon* subtgt = 0; + double dist = 50e3; + Point svec = ship->Location() - tgt_ship->Location(); + + sub_select_time = Game::GameTime(); + + // first pass: turrets + ListIter g_iter = tgt_ship->Weapons(); + while (++g_iter) { + WeaponGroup* g = g_iter.value(); + + if (g->GetDesign() && g->GetDesign()->turret_model) { + ListIter w_iter = g->GetWeapons(); + while (++w_iter) { + Weapon* w = w_iter.value(); + + if (w->Availability() < 35) + continue; + + if (w->GetAimVector() * svec < 0) + continue; + + if (w->GetTurret()) { + Point tloc = w->GetTurret()->Location(); + Point delta = tloc - ship->Location(); + double dlen = delta.length(); + + if (dlen < dist) { + subtgt = w; + dist = dlen; + } + } + } + } + } + + // second pass: major weapons + if (!subtgt) { + g_iter.reset(); + while (++g_iter) { + WeaponGroup* g = g_iter.value(); + + if (g->GetDesign() && !g->GetDesign()->turret_model) { + ListIter w_iter = g->GetWeapons(); + while (++w_iter) { + Weapon* w = w_iter.value(); + + if (w->Availability() < 35) + continue; + + if (w->GetAimVector() * svec < 0) + continue; + + Point tloc = w->MountLocation(); + Point delta = tloc - ship->Location(); + double dlen = delta.length(); + + if (dlen < dist) { + subtgt = w; + dist = dlen; + } + } + } + } + } + + subtarget = subtgt; + return subtarget; +} + +// +--------------------------------------------------------------------+ + +bool +StarshipAI::AssessTargetPointDefense() +{ + if (Game::GameTime() - point_defense_time < 3500) + return tgt_point_defense; + + tgt_point_defense = false; + + if (!target || target->Type() != SimObject::SIM_SHIP || GetAILevel() < 2) + return tgt_point_defense; + + Ship* tgt_ship = (Ship*) target; + + if (!tgt_ship->IsStarship()) + return tgt_point_defense; + + Weapon* subtgt = 0; + Point svec = ship->Location() - tgt_ship->Location(); + + point_defense_time = Game::GameTime(); + + // first pass: turrets + ListIter g_iter = tgt_ship->Weapons(); + while (++g_iter && !tgt_point_defense) { + WeaponGroup* g = g_iter.value(); + + if (g->CanTarget(1)) { + ListIter w_iter = g->GetWeapons(); + while (++w_iter && !tgt_point_defense) { + Weapon* w = w_iter.value(); + + if (w->Availability() > 35 && w->GetAimVector() * svec > 0) + tgt_point_defense = true; + } + } + } + + return tgt_point_defense; +} + + +// +--------------------------------------------------------------------+ + +Point +StarshipAI::Transform(const Point& point) +{ + return point - self->Location(); +} + +Steer +StarshipAI::Seek(const Point& point) +{ + // the point is in relative world coordinates + // x: distance east(-) / west(+) + // y: altitude down(-) / up(+) + // z: distance north(-) / south(+) + + Steer result; + + result.yaw = atan2(point.x, point.z) + PI; + + double adjacent = sqrt(point.x*point.x + point.z*point.z); + if (fabs(point.y) > ship->Radius() && adjacent > ship->Radius()) + result.pitch = atan(point.y / adjacent); + + if (!_finite(result.yaw)) + result.yaw = 0; + + if (!_finite(result.pitch)) + result.pitch = 0; + + return result; +} + +Steer +StarshipAI::Flee(const Point& point) +{ + Steer result = Seek(point); + result.yaw += PI; + return result; +} + +Steer +StarshipAI::Avoid(const Point& point, float radius) +{ + Steer result = Seek(point); + + if (point * ship->BeamLine() > 0) + result.yaw -= PI/2; + else + result.yaw += PI/2; + + return result; +} + + diff --git a/Stars45/StarshipAI.h b/Stars45/StarshipAI.h new file mode 100644 index 0000000..1a20ec0 --- /dev/null +++ b/Stars45/StarshipAI.h @@ -0,0 +1,62 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: FighterAI.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Starship (low-level) Artifical Intelligence class +*/ + +#ifndef StarshipAI_h +#define StarshipAI_h + +#include "Types.h" +#include "ShipAI.h" + +// +--------------------------------------------------------------------+ + +class StarshipAI : public ShipAI +{ +public: + StarshipAI(SimObject* s); + virtual ~StarshipAI(); + + // convert the goal point from world to local coords: + virtual void FindObjective(); + +protected: + // accumulate behaviors: + virtual void Navigator(); + virtual Steer SeekTarget(); + virtual Steer AvoidCollision(); + + // steering functions: + virtual Steer Seek(const Point& point); + virtual Steer Flee(const Point& point); + virtual Steer Avoid(const Point& point, float radius); + + virtual Point Transform(const Point& pt); + + // fire on target if appropriate: + virtual void FireControl(); + virtual void HelmControl(); + virtual void ThrottleControl(); + + System* SelectSubtarget(); + bool AssessTargetPointDefense(); + + DWORD sub_select_time; + DWORD point_defense_time; + System* subtarget; + bool tgt_point_defense; +}; + +// +--------------------------------------------------------------------+ + +#endif StarshipAI_h + diff --git a/Stars45/StarshipTacticalAI.cpp b/Stars45/StarshipTacticalAI.cpp new file mode 100644 index 0000000..c6756b1 --- /dev/null +++ b/Stars45/StarshipTacticalAI.cpp @@ -0,0 +1,276 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: StarshipTacticalAI.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Starship-specific mid-level (tactical) AI +*/ + +#include "MemDebug.h" +#include "StarshipTacticalAI.h" +#include "ShipAI.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "Shot.h" +#include "Element.h" +#include "Instruction.h" +#include "RadioMessage.h" +#include "RadioTraffic.h" +#include "Contact.h" +#include "WeaponGroup.h" +#include "Drive.h" +#include "QuantumDrive.h" +#include "Sim.h" +#include "StarSystem.h" +#include "Starshatter.h" +#include "Random.h" + +#include "Game.h" + +const double STARSHIP_TACTICAL_DROP_TIME = 15; + +// +----------------------------------------------------------------------+ + +StarshipTacticalAI::StarshipTacticalAI(ShipAI* ai) + : TacticalAI(ai), drop_time(1.0e9), bugout(false), ai_level(0), initial_integrity(0) +{ + if (ai && ai->GetShip()) { + ai_level = ai->GetAILevel(); + initial_integrity = ai->GetShip()->Integrity(); + } + + switch (ai_level) { + default: + case 2: THREAT_REACTION_TIME = 500; break; + case 1: THREAT_REACTION_TIME = 1000; break; + case 0: THREAT_REACTION_TIME = 2500; + drop_time = STARSHIP_TACTICAL_DROP_TIME + + Random(0, STARSHIP_TACTICAL_DROP_TIME); + break; + } +} + + +// +--------------------------------------------------------------------+ + +StarshipTacticalAI::~StarshipTacticalAI() +{ } + +// +--------------------------------------------------------------------+ + +void +StarshipTacticalAI::ExecFrame(double seconds) +{ + TacticalAI::ExecFrame(seconds); + + drop_time -= seconds; + + if (drop_time <= 0) { + drop_time = STARSHIP_TACTICAL_DROP_TIME; + + ship_ai->DropTarget(STARSHIP_TACTICAL_DROP_TIME/4); + } +} + +// +--------------------------------------------------------------------+ + +void +StarshipTacticalAI::FindThreat() +{ + // pick the closest contact on Threat Warning System: + Ship* threat_ship = 0; + Shot* threat_missile = 0; + Ship* rumor = 0; + double threat_dist = 1e9; + double CELL_SIZE = 20e3; + + threat_level = 0; + support_level = ship->AIValue() / CELL_SIZE; + + ListIter iter = ship->ContactList(); + + while (++iter) { + Contact* contact = iter.value(); + Ship* c_ship = contact->GetShip(); + Shot* c_shot = contact->GetShot(); + + if (!c_ship && !c_shot) + continue; + + if (c_ship && c_ship != ship) { + double basis = max(contact->Range(ship), CELL_SIZE); + double ai_value = c_ship->AIValue() / basis; + + if (c_ship->GetIFF() == ship->GetIFF()) { + support_level += ai_value; + } + else if (ship->GetIFF() > 0 && c_ship->GetIFF() > 0) { + threat_level += ai_value; + } + else if (c_ship->GetIFF() > 1) { // neutrals should not be afraid of alliance + threat_level += ai_value; + } + } + + if (contact->Threat(ship) && + (Game::GameTime() - contact->AcquisitionTime()) > THREAT_REACTION_TIME) { + + if (c_shot) { + threat_missile = c_shot; + rumor = (Ship* ) threat_missile->Owner(); + } + else { + double rng = contact->Range(ship); + + if (c_ship && + c_ship->Class() != Ship::FREIGHTER && + c_ship->Class() != Ship::FARCASTER) { + + + if (c_ship->GetTarget() == ship) { + if (!threat_ship || c_ship->Class() > threat_ship->Class()) { + threat_ship = c_ship; + threat_dist = 0; + } + } + else if (rng < threat_dist) { + threat_ship = c_ship; + threat_dist = rng; + } + + CheckBugOut(c_ship, rng); + } + } + } + } + + if (rumor) { + iter.reset(); + + while (++iter) { + if (iter->GetShip() == rumor) { + rumor = 0; + ship_ai->ClearRumor(); + break; + } + } + } + + ship_ai->SetRumor(rumor); + ship_ai->SetThreat(threat_ship); + ship_ai->SetThreatMissile(threat_missile); +} + +// +--------------------------------------------------------------------+ + +void +StarshipTacticalAI::FindSupport() +{ + if (threat_level < 0.01) { + ship_ai->SetSupport(0); + return; + } + + // pick the biggest friendly contact in the sector: + Ship* support = 0; + double support_dist = 1e9; + + ListIter c_iter = ship->ContactList(); + + while (++c_iter) { + Contact* contact = c_iter.value(); + if (contact->GetShip() && contact->GetIFF(ship) == ship->GetIFF()) { + Ship* c_ship = contact->GetShip(); + + if (c_ship != ship && c_ship->Class() >= ship->Class()) { + if (!support || c_ship->Class() > support->Class()) + support = c_ship; + } + } + } + + ship_ai->SetSupport(support); +} + +void +StarshipTacticalAI::CheckBugOut(Ship* c_ship, double rng) +{ + // see if carrier should bug out... + if (!ship || !c_ship || ship->Class() != Ship::CARRIER) + return; + + if (bugout) + return; + + if (ship->GetElement() && ship->GetElement()->GetZoneLock()) + return; + + if (c_ship->Class() < Ship::DESTROYER || c_ship->Class() > Ship::STATION) + return; + + Starshatter* stars = Starshatter::GetInstance(); + if (stars && stars->InCutscene()) + return; + + double sustained_damage = initial_integrity - ship->Integrity(); + double allowable_damage = ship->Design()->integrity * 0.25; + + if (rng > 50e3 && sustained_damage < allowable_damage) + return; + + // still here? we must need to bug out! + + Sim* sim = Sim::GetSim(); + SimRegion* dst = 0; + + List& regions = sim->GetRegions(); + + if (regions.size() > 1) { + int tries = 10; + while (!dst && tries--) { + int n = RandomIndex() % regions.size(); + dst = regions[n]; + + if (dst == ship->GetRegion() || dst->IsAirSpace()) + dst = 0; + } + } + + if (dst) { + // bug out! + QuantumDrive* quantum = ship->GetQuantumDrive(); + if (quantum) { + quantum->SetDestination(dst, Point(0,0,0)); + quantum->Engage(); + } + + // ask highest ranking escort to go with you: + Element* escort = 0; + + ListIter iter = sim->GetElements(); + while (++iter) { + Element* elem = iter.value(); + + if (!escort || elem->GetShipClass() > escort->GetShipClass()) { + if (ship->GetElement()->CanCommand(elem)) + escort = elem; + } + } + + if (escort) { + RadioMessage* msg = new(__FILE__,__LINE__) RadioMessage(escort, ship, RadioMessage::QUANTUM_TO); + if (msg) { + msg->SetInfo(dst->Name()); + RadioTraffic::Transmit(msg); + } + } + + bugout = true; + } +} diff --git a/Stars45/StarshipTacticalAI.h b/Stars45/StarshipTacticalAI.h new file mode 100644 index 0000000..007c9ad --- /dev/null +++ b/Stars45/StarshipTacticalAI.h @@ -0,0 +1,47 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: StarshipTacticalAI.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Starship-specific mid-level (tactical) AI +*/ + +#ifndef StarshipTacticalAI_h +#define StarshipTacticalAI_h + +#include "Types.h" +#include "TacticalAI.h" + +// +--------------------------------------------------------------------+ + +class StarshipTacticalAI : public TacticalAI +{ +public: + StarshipTacticalAI(ShipAI* ai); + virtual ~StarshipTacticalAI(); + + virtual void ExecFrame(double seconds); + +protected: + virtual void FindThreat(); + virtual void FindSupport(); + + virtual void CheckBugOut(Ship* c_ship, double range); + + DWORD THREAT_REACTION_TIME; + int ai_level; + double drop_time; + double initial_integrity; + bool bugout; +}; + +// +--------------------------------------------------------------------+ + +#endif StarshipTacticalAI_h + diff --git a/Stars45/SteerAI.cpp b/Stars45/SteerAI.cpp new file mode 100644 index 0000000..7287949 --- /dev/null +++ b/Stars45/SteerAI.cpp @@ -0,0 +1,453 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: SteerAI.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Steering (low-level) Artificial Intelligence class +*/ + +#include "MemDebug.h" +#include "SteerAI.h" +#include "SeekerAI.h" +#include "FighterAI.h" +#include "StarshipAI.h" +#include "GroundAI.h" +#include "System.h" + +#include "Game.h" +#include "Physical.h" + +// +----------------------------------------------------------------------+ + +Steer Steer::operator+(const Steer& s) const +{ + return Steer(yaw+s.yaw, pitch+s.pitch, roll+s.roll, (brake>s.brake)?brake:s.brake); +} + +Steer Steer::operator-(const Steer& s) const +{ + return Steer(yaw-s.yaw, pitch-s.pitch, roll-s.roll, (brake brake) + brake = s.brake; + + if (s.stop) + stop = 1; + + return *this; +} + +Steer& Steer::operator-=(const Steer& s) +{ + yaw -= s.yaw; + pitch -= s.pitch; + roll -= s.roll; + + if (s.brake < brake) + brake = s.brake; + + if (s.stop) + stop = 1; + + return *this; +} + + +double +Steer::Magnitude() const +{ + return sqrt(yaw*yaw + pitch*pitch); +} + +// +--------------------------------------------------------------------+ + +Director* +SteerAI::Create(SimObject* self, int type) +{ + switch (type) { + case SEEKER: return new(__FILE__,__LINE__) SeekerAI(self); + break; + + case STARSHIP: return new(__FILE__,__LINE__) StarshipAI(self); + break; + + case GROUND: return new(__FILE__,__LINE__) GroundAI(self); + break; + + default: + case FIGHTER: return new(__FILE__,__LINE__) FighterAI(self); + break; + } +} + +// +----------------------------------------------------------------------+ + +SteerAI::SteerAI(SimObject* ship) + : self(ship), + target(0), subtarget(0), other(0), distance(0.0), evade_time(0), + objective(0.0f, 0.0f, 0.0f) +{ + seek_gain = 20; + seek_damp = 0.5; + + for (int i = 0; i < 3; i++) + az[i] = el[i] = 0; +} + + +// +--------------------------------------------------------------------+ + +SteerAI::~SteerAI() +{ } + +// +--------------------------------------------------------------------+ + +void +SteerAI::SetTarget(SimObject* targ, System* sub) +{ + if (target != targ) { + target = targ; + + if (target) + Observe(target); + } + + subtarget = sub; +} + +void +SteerAI::DropTarget(double dtime) +{ + SetTarget(0); +} + +// +--------------------------------------------------------------------+ + +bool +SteerAI::Update(SimObject* obj) +{ + if (obj == target) { + target = 0; + subtarget = 0; + } + + if (obj == other) { + other = 0; + } + + return SimObserver::Update(obj); +} + +const char* +SteerAI::GetObserverName() const +{ + static char name[64]; + sprintf(name, "SteerAI(%s)", self->Name()); + return name; +} + +// +--------------------------------------------------------------------+ + +Point +SteerAI::ClosingVelocity() +{ + if (self) { + if (target) + return self->Velocity() - target->Velocity(); + else + return self->Velocity(); + } + + return Point(1, 0, 0); +} + +void +SteerAI::FindObjective() +{ + if (!self || !target) return; + + Point cv = ClosingVelocity(); + double cvl = cv.length(); + double time = 0; + + if (cvl > 5) { + // distance from self to target: + distance = Point(target->Location() - self->Location()).length(); + + // time to reach target: + time = distance / cvl; + + // where the target will be when we reach it: + Point run_vec = target->Velocity(); + obj_w = target->Location() + (run_vec * time); + } + + else { + obj_w = target->Location(); + } + + // subsystem offset: + if (subtarget) { + Point offset = target->Location() - subtarget->MountLocation(); + obj_w -= offset; + } + + distance = Point(obj_w - self->Location()).length(); + + if (cvl > 5) + time = distance / cvl; + + // where we will be when the target gets there: + Point self_dest = self->Location() + cv * time; + Point err = obj_w - self_dest; + + obj_w += err; + + // transform into camera coords: + objective = Transform(obj_w); + objective.Normalize(); + + distance = Point(obj_w - self->Location()).length(); +} + +Point +SteerAI::Transform(const Point& pt) +{ + Point obj_t = pt - self->Location(); + Point result; + + if (self->FlightPathYawAngle() != 0 || self->FlightPathPitchAngle() != 0) { + double az = self->FlightPathYawAngle(); + double el = self->FlightPathPitchAngle(); + + const double MAX_ANGLE = 15*DEGREES; + const double MIN_ANGLE = 3*DEGREES; + + if (az > MAX_ANGLE) + az = MAX_ANGLE; + else if (az < -MAX_ANGLE) + az = -MAX_ANGLE; + else if (az > MIN_ANGLE) + az = MIN_ANGLE + (az-MIN_ANGLE)/2; + else if (az < -MIN_ANGLE) + az = -MIN_ANGLE + (az+MIN_ANGLE)/2; + + if (el > MAX_ANGLE) + el = MAX_ANGLE; + else if (el < -MAX_ANGLE) + el = -MAX_ANGLE; + else if (el > MIN_ANGLE) + el = MIN_ANGLE + (el-MIN_ANGLE)/2; + else if (el < -MIN_ANGLE) + el = -MIN_ANGLE + (el+MIN_ANGLE)/2; + + Camera cam; + cam.Clone(self->Cam()); + cam.Yaw(az); + cam.Pitch(-el); + + result = Point(obj_t * cam.vrt(), + obj_t * cam.vup(), + obj_t * cam.vpn()); + } + else { + Camera& cam = (Camera&) self->Cam(); // cast away const + + result = Point(obj_t * cam.vrt(), + obj_t * cam.vup(), + obj_t * cam.vpn()); + } + + return result; +} + +Point +SteerAI::AimTransform(const Point& pt) +{ + Camera& cam = (Camera&) self->Cam(); // cast away const + Point obj_t = pt - self->Location(); + + Point result = Point(obj_t * cam.vrt(), + obj_t * cam.vup(), + obj_t * cam.vpn()); + + return result; +} + +// +--------------------------------------------------------------------+ + +void +SteerAI::Navigator() +{ + accumulator.Clear(); + magnitude = 0; +} + +int +SteerAI::Accumulate(const Steer& steer) +{ + int overflow = 0; + + double mag = steer.Magnitude(); + + if (magnitude + mag > 1) { + overflow = 1; + double scale = (1 - magnitude) / mag; + + accumulator += steer * scale; + magnitude = 1; + + if (seeking) { + az[0] *= scale; + el[0] *= scale; + seeking = 0; + } + } + else { + accumulator += steer; + magnitude += mag; + } + + return overflow; +} + +// +--------------------------------------------------------------------+ + +Steer +SteerAI::Seek(const Point& point) +{ + Steer s; + + // advance memory pipeline: + az[2] = az[1]; az[1] = az[0]; + el[2] = el[1]; el[1] = el[0]; + + // approach + if (point.z > 0.0f) { + az[0] = atan2(fabs(point.x), point.z) * seek_gain; + el[0] = atan2(fabs(point.y), point.z) * seek_gain; + + if (point.x < 0) az[0] = -az[0]; + if (point.y > 0) el[0] = -el[0]; + + s.yaw = az[0] - seek_damp * (az[1] + az[2] * 0.5); + s.pitch = el[0] - seek_damp * (el[1] + el[2] * 0.5); + } + + // reverse + else { + if (point.x > 0) s.yaw = 1.0f; + else s.yaw = -1.0f; + + s.pitch = -point.y * 0.5f; + } + + seeking = 1; + + return s; +} + +// +--------------------------------------------------------------------+ + +Steer +SteerAI::Flee(const Point& pt) +{ + Steer s; + + Point point = pt; + point.Normalize(); + + // approach + if (point.z > 0.0f) { + if (point.x > 0) s.yaw = -1.0f; + else s.yaw = 1.0f; + } + + // flee + else { + s.yaw = -point.x; + s.pitch = point.y; + } + + return s; +} + +// +--------------------------------------------------------------------+ + +Steer +SteerAI::Avoid(const Point& point, float radius) +{ + Steer s; + + if (point.z > 0) { + double ax = radius - fabs(point.x); + double ay = radius - fabs(point.y); + + // go around? + if (ax < ay) { + s.yaw = atan2(ax, point.z) * seek_gain; + if (point.x > 0) s.yaw = -s.yaw; + } + + // go over/under: + else { + s.pitch = atan2(ay, point.z) * seek_gain; + if (point.y < 0) s.pitch = -s.pitch; + } + } + + return s; +} + +// +--------------------------------------------------------------------+ + +Steer +SteerAI::Evade(const Point& point, const Point& vel) +{ + Steer evade; + + if (Game::GameTime() - evade_time > 1250) { + evade_time = Game::GameTime(); + + int direction = (rand()>>9) & 0x07; + + switch (direction) { + default: + case 0: evade.yaw = 0; evade.pitch = -0.5; break; + case 1: evade.yaw = 0; evade.pitch = -1.0; break; + case 2: evade.yaw = 1; evade.pitch = -0.3; break; + case 3: evade.yaw = 1; evade.pitch = -0.6; break; + case 4: evade.yaw = 1; evade.pitch = -1.0; break; + case 5: evade.yaw = -1; evade.pitch = -0.3; break; + case 6: evade.yaw = -1; evade.pitch = -0.6; break; + case 7: evade.yaw = -1; evade.pitch = -1.0; break; + } + } + + return evade; +} + diff --git a/Stars45/SteerAI.h b/Stars45/SteerAI.h new file mode 100644 index 0000000..7a107d5 --- /dev/null +++ b/Stars45/SteerAI.h @@ -0,0 +1,125 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: SteerAI.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Steering (low-level) Artifical Intelligence class +*/ + +#ifndef SteerAI_h +#define SteerAI_h + +#include "Types.h" +#include "SimObject.h" +#include "Director.h" +#include "Geometry.h" + +// +--------------------------------------------------------------------+ + +class System; + +// +--------------------------------------------------------------------+ + +struct Steer +{ + Steer() : yaw(0), pitch(0), roll(0), brake(0), stop(0) { } + Steer(double y, double p, double r, double b=0) : yaw(y), pitch(p), roll(r), brake(b), stop(0) { } + Steer(const Steer& s) : yaw(s.yaw), pitch(s.pitch), roll(s.roll), brake(s.brake), stop(s.stop) { } + + Steer& operator = (const Steer& s) { yaw=s.yaw; pitch=s.pitch; roll=s.roll; brake = s.brake; stop = s.stop; return *this; } + + Steer operator+(const Steer& s) const; + Steer operator-(const Steer& s) const; + Steer operator*(double f) const; + Steer operator/(double f) const; + + Steer& operator+=(const Steer& s); + Steer& operator-=(const Steer& s); + + double Magnitude() const; + + void Clear() { yaw=0; pitch=0; roll=0; brake=0; stop=0; } + + double yaw, pitch, roll; + double brake; + int stop; +}; + +// +--------------------------------------------------------------------+ + +class SteerAI : public Director, public SimObserver +{ +public: + enum Type { SEEKER = 1000, FIGHTER, STARSHIP, GROUND }; + + SteerAI(SimObject* self); + virtual ~SteerAI(); + + static Director* Create(SimObject*, int type); + + virtual void SetTarget(SimObject* targ, System* sub=0); + virtual SimObject* GetTarget() const { return target; } + virtual System* GetSubTarget() const { return subtarget; } + virtual void DropTarget(double drop_time=1.5); + virtual int Type() const { return ai_type; } + + virtual bool Update(SimObject* obj); + virtual const char* GetObserverName() const; + + // debug: + virtual Point GetObjective() const { return obj_w; } + virtual SimObject* GetOther() const { return other; } + +protected: + + // accumulate behaviors: + virtual void Navigator(); + virtual int Accumulate(const Steer& steer); + + // steering functions: + virtual Steer Seek(const Point& point); + virtual Steer Flee(const Point& point); + virtual Steer Avoid(const Point& point, float radius); + virtual Steer Evade(const Point& point, const Point& vel); + + // compute the goal point based on target stats: + virtual void FindObjective(); + virtual Point ClosingVelocity(); + + virtual Point Transform(const Point& pt); + virtual Point AimTransform(const Point& pt); + + int seeking; + + SimObject* self; + SimObject* target; + System* subtarget; + SimObject* other; + + Point obj_w; + Point objective; + + double distance; + double az[3], el[3]; + + Steer accumulator; + double magnitude; + DWORD evade_time; + + double seek_gain; + double seek_damp; + + int ai_type; +}; + + +// +--------------------------------------------------------------------+ + +#endif SteerAI_h + diff --git a/Stars45/System.cpp b/Stars45/System.cpp new file mode 100644 index 0000000..2dc552a --- /dev/null +++ b/Stars45/System.cpp @@ -0,0 +1,399 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: System.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Generic Ship Subsystem class +*/ + +#include "MemDebug.h" +#include "System.h" +#include "SystemDesign.h" +#include "Component.h" +#include "NetUtil.h" + +#include "Game.h" + +// +----------------------------------------------------------------------+ + +System::System(System::CATEGORY t, int s, const char* n, int maxv, + double e, double c, double r) + : type(t), id(0), ship(0), subtype(s), status(NOMINAL), availability(1.0f), + safety(1.0f), stability(1.0f), crit_level(0.5f), net_avail(-1.0f), + mount_rel(0.0f, 0.0f, 0.0f), radius(0.0f), safety_overload(0.0f), + hull_factor(0.5f), energy((float) e), capacity((float) c), sink_rate((float) r), + power_level(1.0f), power_flags(0), source_index(0), power_on(true), + explosion_type(0), name(n), abrv(name), design(0), emcon(3) +{ + if (maxv < 100) + max_value = maxv; + else + max_value = (int) (maxv/100.0); + + emcon_power[0] = 100; + emcon_power[1] = 100; + emcon_power[2] = 100; +} + +// +----------------------------------------------------------------------+ + +System::System(const System& s) + : type(s.type), id(s.id), ship(0), subtype(s.subtype), status(s.status), + availability(s.availability), safety(s.safety), stability(s.stability), + crit_level(s.crit_level), net_avail(-1.0f), + mount_rel(s.mount_rel), radius(s.radius), safety_overload(0.0f), + hull_factor(s.hull_factor), energy(s.energy), capacity(s.capacity), + sink_rate(s.sink_rate), power_level(s.power_level), power_flags(s.power_flags), + source_index(s.source_index), power_on(s.power_on), max_value(s.max_value), + explosion_type(s.explosion_type), name(s.name), abrv(s.abrv), design(s.design), + emcon(s.emcon) +{ + if (design) { + // cast-away const + ListIter c = (List&) s.components; + while (++c) { + Component* comp = new(__FILE__,__LINE__) Component(*(c.value())); + comp->SetSystem(this); + components.append(comp); + } + } + + emcon_power[0] = s.emcon_power[0]; + emcon_power[1] = s.emcon_power[1]; + emcon_power[2] = s.emcon_power[2]; +} + +// +--------------------------------------------------------------------+ + +System::~System() +{ + components.destroy(); +} + +// +--------------------------------------------------------------------+ + +void +System::SetDesign(SystemDesign* d) +{ + if (design) { + design = 0; + components.destroy(); + } + + if (d) { + design = d; + + ListIter cd = design->components; + while (++cd) { + Component* comp = new(__FILE__,__LINE__) Component(cd.value(), this); + components.append(comp); + } + } +} + +// +--------------------------------------------------------------------+ + +void +System::SetPowerLevel(double level) +{ + if (level > 100) + level = 100; + else if (level < 0) + level = 0; + + level /= 100; + + if (power_level != level) { + // if the system is on emergency override power, + // do not let the EMCON system use this method + // to drop it back to normal power: + if (power_level > 1 && level == 1) + return; + + power_level = (float) level; + + NetUtil::SendSysStatus(ship, this); + } +} + +void +System::SetOverride(bool over) +{ + bool changed = false; + + if (over && power_level != 1.2f) { + power_level = 1.2f; + changed = true; + } + + else if (!over && power_level > 1) { + power_level = 1.0f; + changed = true; + } + + if (changed) + NetUtil::SendSysStatus(ship, this); +} + +void +System::SetEMCONPower(int index, int power_level) +{ + if (index >= 1 && index <= 3) { + emcon_power[index-1] = (BYTE) power_level; + } +} + +int +System::GetEMCONPower(int index) +{ + if (index >= 1 && index <= 3) { + return emcon_power[index-1]; + } + + return 100; +} + +void +System::DoEMCON(int index) +{ + int e = GetEMCONPower(index); + + if (power_level * 100 > e || emcon != index) { + if (e == 0) { + PowerOff(); + } + else { + if (emcon != index) + PowerOn(); + + SetPowerLevel(e); + } + } + + emcon = index; +} + +// +--------------------------------------------------------------------+ + +void +System::ExecFrame(double seconds) +{ + if (seconds < 0.01) + seconds = 0.01; + + STATUS s = DESTROYED; + + if (availability > 0.99) + s = NOMINAL; + else if (availability > crit_level) + s = DEGRADED; + else + s = CRITICAL; + + bool repair = false; + + if (components.size() > 0) { + ListIter comp = components; + while (++comp) { + if (comp->Status() > Component::NOMINAL) { + repair = true; + break; + } + } + } + + if (repair) { + Repair(); + } + + else { + if (status != s) { + status = s; + NetUtil::SendSysStatus(ship, this); + } + + // collateral damage due to unsafe operation: + if (power_on && power_level > safety) { + safety_overload += (float) seconds; + + // inflict some damage now: + if (safety_overload > 60) { + safety_overload -= (float) (rand() / (1000 * (power_level-safety))); + ApplyDamage(15); + + NetUtil::SendSysStatus(ship, this); + } + } + + else if (safety_overload > 0) { + safety_overload -= (float) seconds; + } + } +} + +void +System::Repair() +{ + if (status != MAINT) { + status = MAINT; + safety_overload = 0.0f; + + NetUtil::SendSysStatus(ship, this); + } +} + +// +--------------------------------------------------------------------+ + +void +System::ExecMaintFrame(double seconds) +{ + if (components.size() > 0) { + ListIter comp = components; + while (++comp) { + if (comp->Status() > Component::NOMINAL) { + comp->ExecMaintFrame(seconds); + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +System::ApplyDamage(double damage) +{ + if (!power_on) + damage /= 10; + + if (components.size() > 0) { + int index = rand() % components.size(); + + if (damage > 50) { + damage/=2; + components[index]->ApplyDamage(damage); + + index = rand() % components.size(); + } + + components[index]->ApplyDamage(damage); + + if (safety < 0.5) + SetPowerLevel(50); + + else if (safety < 1.0) + SetPowerLevel(safety * 100); + } + else { + availability -= (float) (damage/100.0f); + if (availability < 0.01) availability = 0.0f; + } +} + +void +System::CalcStatus() +{ + if (components.size() > 0) { + availability = 1.0f; + safety = 1.0f; + stability = 1.0f; + + ListIter comp = components; + while (++comp) { + if (comp->DamageEfficiency()) + availability *= comp->Availability() / 100.0f; + + if (comp->DamageSafety()) + safety *= comp->Availability() / 100.0f; + + if (comp->DamageStability()) + stability *= comp->Availability() / 100.0f; + + if (comp->IsJerried()) { + safety *= 0.95f; + stability *= 0.95f; + } + } + + if (net_avail >= 0 && availability < net_avail) + availability = net_avail; + } +} + +// +--------------------------------------------------------------------+ + +void +System::Mount(Point loc, float rad, float hull) +{ + mount_rel = loc; + radius = rad; + hull_factor = hull; +} + +void +System::Mount(const System& system) +{ + mount_rel = system.mount_rel; + radius = system.radius; + hull_factor = system.hull_factor; +} + +// +--------------------------------------------------------------------+ + +void +System::Orient(const Physical* rep) +{ + const Matrix& orientation = rep->Cam().Orientation(); + Point loc = rep->Location(); + + mount_loc = (mount_rel * orientation) + loc; +} + +// +----------------------------------------------------------------------+ + +double +System::GetRequest(double seconds) const +{ + if (!power_on || capacity == energy) + return 0; + + else + return power_level * sink_rate * seconds; +} + +// +----------------------------------------------------------------------+ + +void +System::Distribute(double delivered_energy, double seconds) +{ + if (UsesWatts()) { + if (seconds < 0.01) + seconds = 0.01; + + // convert Joules to Watts: + energy = (float) (delivered_energy/seconds); + } + + else if (!Game::Paused()) { + energy += (float) delivered_energy; + + if (energy > capacity) + energy = capacity; + + else if (energy < 0) + energy = 0.0f; + } +} + +// +----------------------------------------------------------------------+ + +void +System::DrainPower(double to_level) +{ + energy = 0.0f; +} diff --git a/Stars45/System.h b/Stars45/System.h new file mode 100644 index 0000000..61cefbe --- /dev/null +++ b/Stars45/System.h @@ -0,0 +1,174 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: System.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Generic ship System class +*/ + +#ifndef System_h +#define System_h + +#include "Types.h" +#include "Physical.h" +#include "Geometry.h" + +#include "List.h" +#include "text.h" + +// +--------------------------------------------------------------------+ + +class Component; +class Ship; +class SystemDesign; + +// +--------------------------------------------------------------------+ + +class System +{ + friend Component; + +public: + static const char* TYPENAME() { return "System"; } + + enum CATEGORY { MISC_SYSTEM=0, DRIVE=1, WEAPON, SHIELD, SENSOR, + COMPUTER, POWER_SOURCE, FLIGHT_DECK, FARCASTER }; + enum STATUS { DESTROYED, CRITICAL, DEGRADED, NOMINAL, MAINT }; + enum POWER_FLAGS { POWER_WATTS=1, POWER_CRITICAL=2 }; + + System(CATEGORY t, int s, const char* n, int maxv, + double energy=0, double capacity=100, double sink_rate=1); + System(const System& s); + virtual ~System(); + + int operator==(const System& s) const { return this == &s; } + + CATEGORY Type() const { return type; } + int Subtype() const { return subtype; } + const char* Name() const { return name; } + const char* Abbreviation() const { return abrv; } + + void SetName(const char* n) { name = n; } + void SetAbbreviation(const char* a) { abrv = a; } + void SetDesign(SystemDesign* d); + + virtual int Value() const { return (int) (max_value*availability*100); } + int MaxValue() const { return (int) (max_value*100); } + STATUS Status() const { return status; } + double Availability() const { return availability*100; } + double Safety() const { return safety*100; } + double Stability() const { return stability*100; } + virtual void CalcStatus(); + virtual void Repair(); + + double NetAvail() const { return net_avail; } + void SetNetAvail(double d){ net_avail = (float) d; } + + List& GetComponents() { return components; } + + virtual void ApplyDamage(double damage); + virtual void ExecFrame(double seconds); + virtual void ExecMaintFrame(double seconds); + virtual void DoEMCON(int emcon); + + // PHYSICAL LOCATION (for inflicting system damage): + virtual void Orient(const Physical* rep); + virtual void Mount(Point loc, float radius, float hull_factor=0.5f); + virtual void Mount(const System& system); + + Point MountLocation() const { return mount_loc; } + double Radius() const { return radius; } + double HullProtection() const { return hull_factor; } + + // POWER UTILIZATION: + bool IsPowerCritical() const { return (power_flags & POWER_CRITICAL)?true:false; } + bool UsesWatts() const { return (power_flags & POWER_WATTS)?true:false; } + + virtual double GetRequest(double seconds) const; + virtual void Distribute(double delivered_energy, double seconds); + + int GetSourceIndex() const { return source_index; } + void SetSourceIndex(int i) { source_index = i; } + + virtual int Charge() const { return (int) (100 * energy/capacity); } + + bool IsPowerOn() const { return power_on; } + virtual void PowerOn() { power_on = true; } + virtual void PowerOff() { power_on = false; } + + // percentage, but stored as 0-1 + virtual double GetPowerLevel() const { return power_level * 100; } + virtual void SetPowerLevel(double level); + virtual void SetOverride(bool over); + + // for power drain damage: + virtual void DrainPower(double to_level); + + void SetCapacity(double c) { capacity = (float) c; } + double GetCapacity() const { return capacity; } + double GetEnergy() const { return energy; } + double GetSinkRate() const { return sink_rate; } + void SetEMCONPower(int emcon, int power_level); + int GetEMCONPower(int emcon); + + int GetExplosionType() const { return explosion_type; } + void SetExplosionType(int t) { explosion_type = t; } + + Ship* GetShip() const { return ship; } + void SetShip(Ship* s) { ship = s; } + int GetID() const { return id; } + void SetID(int n) { id = n; } + +protected: + // AI information: + CATEGORY type; + Ship* ship; + int id; + int subtype; + int max_value; + + // Displayable name: + Text name; + Text abrv; + + // System health status: + STATUS status; + float crit_level; + float availability; + float safety; + float stability; + float safety_overload; + float net_avail; + + // Mounting: + Point mount_loc; // world space + Point mount_rel; // object space + float radius; + float hull_factor; + + // Power Sink: + float energy; + float capacity; + float sink_rate; + float power_level; + int source_index; + DWORD power_flags; + bool power_on; + BYTE emcon_power[3]; + BYTE emcon; + + int explosion_type; + + // Subcomponents: + SystemDesign* design; + List components; +}; + +#endif System_h + diff --git a/Stars45/SystemDesign.cpp b/Stars45/SystemDesign.cpp new file mode 100644 index 0000000..8b33524 --- /dev/null +++ b/Stars45/SystemDesign.cpp @@ -0,0 +1,168 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: SystemDesign.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Weapon Design parameters class +*/ + +#include "MemDebug.h" +#include "SystemDesign.h" +#include "Component.h" + +#include "Game.h" +#include "Bitmap.h" +#include "DataLoader.h" +#include "ParseUtil.h" + +// +--------------------------------------------------------------------+ + +List SystemDesign::catalog; + +#define GET_DEF_TEXT(p,d,x) if(p->name()->value()==(#x))GetDefText(d->x,p,filename) +#define GET_DEF_NUM(p,d,x) if(p->name()->value()==(#x))GetDefNumber(d->x,p,filename) + +// +--------------------------------------------------------------------+ + +SystemDesign::SystemDesign() +{ } + +SystemDesign::~SystemDesign() +{ + components.destroy(); +} + +// +--------------------------------------------------------------------+ + +void +SystemDesign::Initialize(const char* filename) +{ + Print("Loading System Designs '%s'\n", filename); + + // Load Design File: + DataLoader* loader = DataLoader::GetLoader(); + BYTE* block; + + int blocklen = loader->LoadBuffer(filename, block, true); + Parser parser(new(__FILE__,__LINE__) BlockReader((const char*) block, blocklen)); + Term* term = parser.ParseTerm(); + + if (!term) { + Print("ERROR: could not parse '%s'\n", filename); + exit(-3); + } + else { + TermText* file_type = term->isText(); + if (!file_type || file_type->value() != "SYSTEM") { + Print("ERROR: invalid system design file '%s'\n", filename); + exit(-4); + } + } + + int type = 1; + + do { + delete term; + + term = parser.ParseTerm(); + + if (term) { + TermDef* def = term->isDef(); + if (def) { + if (def->name()->value() == "system") { + + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: system structure missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + SystemDesign* design = new(__FILE__,__LINE__) SystemDesign; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + GET_DEF_TEXT(pdef, design, name); + + else if (pdef->name()->value()==("component")) { + if (!pdef->term() || !pdef->term()->isStruct()) { + Print("WARNING: component structure missing in system '%s' in '%s'\n", (const char*) design->name, filename); + } + else { + TermStruct* val2 = pdef->term()->isStruct(); + ComponentDesign* comp_design = new(__FILE__,__LINE__) ComponentDesign; + + for (int i = 0; i < val2->elements()->size(); i++) { + TermDef* pdef2 = val2->elements()->at(i)->isDef(); + if (pdef2) { + GET_DEF_TEXT(pdef2, comp_design, name); + else GET_DEF_TEXT(pdef2, comp_design, abrv); + else GET_DEF_NUM (pdef2, comp_design, repair_time); + else GET_DEF_NUM (pdef2, comp_design, replace_time); + else GET_DEF_NUM (pdef2, comp_design, spares); + else GET_DEF_NUM (pdef2, comp_design, affects); + + else { + Print("WARNING: parameter '%s' ignored in '%s'\n", + pdef2->name()->value().data(), filename); + } + } + } + + design->components.append(comp_design); + } + } + + else { + Print("WARNING: parameter '%s' ignored in '%s'\n", + pdef->name()->value().data(), filename); + } + } + else { + Print("WARNING: term ignored in '%s'\n", filename); + val->elements()->at(i)->print(); + } + } + + catalog.append(design); + } + } + + else + Print("WARNING: unknown definition '%s' in '%s'\n", + def->name()->value().data(), filename); + } + else { + Print("WARNING: term ignored in '%s'\n", filename); + term->print(); + } + } + } + while (term); + + loader->ReleaseBuffer(block); +} + +// +--------------------------------------------------------------------+ + +void +SystemDesign::Close() +{ + catalog.destroy(); +} + +// +--------------------------------------------------------------------+ + +SystemDesign* +SystemDesign::Find(const char* name) +{ + SystemDesign test; + test.name = name; + return catalog.find(&test); +} + diff --git a/Stars45/SystemDesign.h b/Stars45/SystemDesign.h new file mode 100644 index 0000000..4e7bfc2 --- /dev/null +++ b/Stars45/SystemDesign.h @@ -0,0 +1,54 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: SystemDesign.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Generic ship System Design class +*/ + +#ifndef SystemDesign_h +#define SystemDesign_h + +#include "Types.h" +#include "List.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class ComponentDesign; + +// +--------------------------------------------------------------------+ + +class SystemDesign +{ +public: + static const char* TYPENAME() { return "SystemDesign"; } + + SystemDesign(); + ~SystemDesign(); + int operator == (const SystemDesign& rhs) const { return name == rhs.name; } + + static void Initialize(const char* filename); + static void Close(); + static SystemDesign* Find(const char* name); + + // Unique ID: + Text name; + + // Sub-components: + List components; + + static List catalog; +}; + +// +--------------------------------------------------------------------+ + + +#endif SystemDesign_h + diff --git a/Stars45/TacRefDlg.cpp b/Stars45/TacRefDlg.cpp new file mode 100644 index 0000000..a492bc2 --- /dev/null +++ b/Stars45/TacRefDlg.cpp @@ -0,0 +1,689 @@ +/* Project Starshatter 5.0 + Destroyer Studios LLC + Copyright © 1997-2007. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: TacRefDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Tactical Reference Dialog Active Window class +*/ + +#include "MemDebug.h" +#include "TacRefDlg.h" +#include "MenuScreen.h" +#include "Campaign.h" +#include "Mission.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "WeaponDesign.h" +#include "WeaponGroup.h" + +#include "Game.h" +#include "EventDispatch.h" +#include "Mouse.h" +#include "Button.h" +#include "ListBox.h" +#include "Slider.h" +#include "ParseUtil.h" +#include "FormatUtil.h" +#include "Light.h" +#include "Solid.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: +DEF_MAP_CLIENT(TacRefDlg, OnClose); +DEF_MAP_CLIENT(TacRefDlg, OnSelect); +DEF_MAP_CLIENT(TacRefDlg, OnMode); +DEF_MAP_CLIENT(TacRefDlg, OnCamRButtonDown); +DEF_MAP_CLIENT(TacRefDlg, OnCamRButtonUp); +DEF_MAP_CLIENT(TacRefDlg, OnCamMove); +DEF_MAP_CLIENT(TacRefDlg, OnCamZoom); + +// +--------------------------------------------------------------------+ + +TacRefDlg::TacRefDlg(Screen* s, FormDef& def, MenuScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), manager(mgr), + beauty(0), camview(0), imgview(0), + txt_caption(0), txt_stats(0), txt_description(0), + lst_designs(0), btn_close(0), btn_ships(0), btn_weaps(0), + mode(MODE_NONE), radius(100), mouse_x(0), mouse_y(0), + cam_zoom(2.5), cam_az(-PI/6), cam_el(PI/7), captured(false), + ship_index(0), weap_index(0) +{ + Init(def); +} + +TacRefDlg::~TacRefDlg() +{ + if (beauty) { + beauty->DelView(camview); + beauty->DelView(imgview); + } + + delete camview; + delete imgview; +} + +// +--------------------------------------------------------------------+ + +void +TacRefDlg::RegisterControls() +{ + btn_close = (Button*) FindControl(1); + btn_ships = (Button*) FindControl(101); + btn_weaps = (Button*) FindControl(102); + lst_designs = (ListBox*) FindControl(200); + txt_caption = FindControl(301); + beauty = FindControl(401); + txt_stats = (RichTextBox*) FindControl(402); + txt_description = (RichTextBox*) FindControl(403); + + if (btn_close) { + REGISTER_CLIENT(EID_CLICK, btn_close, TacRefDlg, OnClose); + } + + if (btn_ships) { + btn_ships->SetButtonState(mode == MODE_SHIPS); + REGISTER_CLIENT(EID_CLICK, btn_ships, TacRefDlg, OnMode); + } + + if (btn_weaps) { + btn_weaps->SetButtonState(mode == MODE_WEAPONS); + REGISTER_CLIENT(EID_CLICK, btn_weaps, TacRefDlg, OnMode); + } + + if (lst_designs) { + REGISTER_CLIENT(EID_SELECT, lst_designs, TacRefDlg, OnSelect); + } + + if (beauty) { + REGISTER_CLIENT(EID_RBUTTON_DOWN, beauty, TacRefDlg, OnCamRButtonDown); + REGISTER_CLIENT(EID_RBUTTON_UP, beauty, TacRefDlg, OnCamRButtonUp); + REGISTER_CLIENT(EID_MOUSE_MOVE, beauty, TacRefDlg, OnCamMove); + REGISTER_CLIENT(EID_MOUSE_WHEEL, beauty, TacRefDlg, OnCamZoom); + + scene.SetAmbient(Color(60,60,60)); + + Point light_pos(3e6, 5e6, 4e6); + + Light* main_light = new Light(1.0f); //1.25f); + main_light->MoveTo(light_pos); + main_light->SetType(Light::LIGHT_DIRECTIONAL); + main_light->SetColor(Color::White); + main_light->SetShadow(true); + + scene.AddLight(main_light); + + Light* back_light = new Light(0.5f); + back_light->MoveTo(light_pos * -1); + back_light->SetType(Light::LIGHT_DIRECTIONAL); + back_light->SetColor(Color::White); + back_light->SetShadow(false); + + scene.AddLight(back_light); + + camview = new(__FILE__,__LINE__) CameraView(beauty, &cam, &scene); + camview->SetProjectionType(Video::PROJECTION_PERSPECTIVE); + camview->SetFieldOfView(2); + + beauty->AddView(camview); + + imgview = new(__FILE__,__LINE__) ImgView(beauty, 0); + imgview->SetBlend(Video::BLEND_ALPHA); + } +} + +// +--------------------------------------------------------------------+ + +void +TacRefDlg::Show() +{ + update_scene = !shown; + + FormWindow::Show(); + + if (update_scene) { + AWEvent event(btn_ships, EID_CLICK); + OnMode(&event); + } +} + +struct WepGroup { + Text name; + int count; + + WepGroup() : count(0) { } +}; +WepGroup* FindWepGroup(WepGroup* weapons, const char* name); + + +void +TacRefDlg::SelectShip(const ShipDesign* design) +{ + if (beauty && camview) { + scene.Graphics().clear(); + + if (design) { + radius = design->radius; + + UpdateCamera(); + + int level = design->lod_levels-1; + int n = design->models[level].size(); + + for (int i = 0; i < n; i++) { + Model* model = design->models[level].at(i); + + Solid* s = new(__FILE__,__LINE__) Solid; + s->UseModel(model); + s->CreateShadows(1); + s->MoveTo(*design->offsets[level].at(i)); + + scene.Graphics().append(s); + } + } + } + + if (txt_caption) { + txt_caption->SetText(""); + + if (design) { + char txt[256]; + sprintf(txt, "%s %s", design->abrv, design->DisplayName()); + txt_caption->SetText(txt); + } + } + + if (txt_stats) { + txt_stats->SetText(""); + + if (design) { + Text desc; + char txt[256]; + + sprintf(txt, "%s\t\t\t%s\n", Game::GetText("tacref.type").data(), Ship::ClassName(design->type)); + desc += txt; + + sprintf(txt, "%s\t\t\t%s\n", Game::GetText("tacref.class").data(), design->DisplayName()); + desc += txt; + desc += Game::GetText("tacref.length"); + desc += "\t\t"; + + if (design->type < Ship::STATION) + FormatNumber(txt, design->radius/2); + else + FormatNumber(txt, design->radius*2); + + strcat(txt, " m\n"); + desc += txt; + desc += Game::GetText("tacref.mass"); + desc += "\t\t\t"; + + FormatNumber(txt, design->mass); + strcat(txt, " T\n"); + desc += txt; + desc += Game::GetText("tacref.hull"); + desc += "\t\t\t"; + + FormatNumber(txt, design->integrity); + strcat(txt, "\n"); + desc += txt; + + if (design->weapons.size()) { + desc += Game::GetText("tacref.weapons"); + + WepGroup groups[8]; + for (int w = 0; w < design->weapons.size(); w++) { + Weapon* gun = design->weapons[w]; + WepGroup* group = FindWepGroup(groups, gun->Group()); + + if (group) + group->count++; + } + + for (int g = 0; g < 8; g++) { + WepGroup* group = &groups[g]; + if (group && group->count) { + sprintf(txt, "\t\t%s (%d)\n\t\t", group->name.data(), group->count); + desc += txt; + + for (int w = 0; w < design->weapons.size(); w++) { + Weapon* gun = design->weapons[w]; + + if (group->name == gun->Group()) { + sprintf(txt, "\t\t\t%s\n\t\t", (const char*) gun->Design()->name); + desc += txt; + } + } + } + } + + desc += "\n"; + } + + txt_stats->SetText(desc); + } + } + + if (txt_description) { + if (design && design->description.length()) { + txt_description->SetText(design->description); + } + else { + txt_description->SetText(Game::GetText("tacref.mass")); + } + } +} + +// +--------------------------------------------------------------------+ + +void +TacRefDlg::SelectWeapon(const WeaponDesign* design) +{ + if (beauty && imgview) { + imgview->SetPicture(0); + + if (design) + imgview->SetPicture(design->beauty_img); + } + + if (txt_caption) { + txt_caption->SetText(""); + + if (design) + txt_caption->SetText(design->name); + } + + if (txt_stats) { + txt_stats->SetText(""); + + if (design) { + Text desc; + char txt[256]; + + desc = Game::GetText("tacref.name"); + desc += "\t"; + desc += design->name; + desc += "\n"; + desc += Game::GetText("tacref.type"); + desc += "\t\t"; + + if (design->damage < 1) + desc += Game::GetText("tacref.wep.other"); + else if (design->beam) + desc += Game::GetText("tacref.wep.beam"); + else if (design->primary) + desc += Game::GetText("tacref.wep.bolt"); + else if (design->drone) + desc += Game::GetText("tacref.wep.drone"); + else if (design->guided) + desc += Game::GetText("tacref.wep.guided"); + else + desc += Game::GetText("tacref.wep.missile"); + + if (design->turret_model && design->damage >= 1) { + desc += " "; + desc += Game::GetText("tacref.wep.turret"); + desc += "\n"; + } + else { + desc += "\n"; + } + + desc += Game::GetText("tacref.targets"); + desc += "\t"; + + if ((design->target_type & Ship::DROPSHIPS) != 0) { + if ((design->target_type & Ship::STARSHIPS) != 0) { + if ((design->target_type & Ship::GROUND_UNITS) != 0) { + desc += Game::GetText("tacref.targets.fsg"); + } + else { + desc += Game::GetText("tacref.targets.fs"); + } + } + else { + if ((design->target_type & Ship::GROUND_UNITS) != 0) { + desc += Game::GetText("tacref.targets.fg"); + } + else { + desc += Game::GetText("tacref.targets.f"); + } + } + } + + else if ((design->target_type & Ship::STARSHIPS) != 0) { + if ((design->target_type & Ship::GROUND_UNITS) != 0) { + desc += Game::GetText("tacref.targets.sg"); + } + else { + desc += Game::GetText("tacref.targets.s"); + } + } + + else if ((design->target_type & Ship::GROUND_UNITS) != 0) { + desc += Game::GetText("tacref.targets.g"); + } + + desc += "\n"; + desc += Game::GetText("tacref.speed"); + desc += "\t"; + + FormatNumber(txt, design->speed); + desc += txt; + desc += "m/s\n"; + desc += Game::GetText("tacref.range"); + desc += "\t"; + + FormatNumber(txt, design->max_range); + desc += txt; + desc += "m\n"; + desc += Game::GetText("tacref.damage"); + desc += "\t"; + + if (design->damage > 0) { + FormatNumber(txt, design->damage * design->charge); + desc += txt; + if (design->beam) + desc += "/s"; + } + else { + desc += Game::GetText("tacref.none"); + } + + desc += "\n"; + + if (!design->primary && design->damage > 0) { + desc += Game::GetText("tacref.kill-radius"); + desc += "\t"; + FormatNumber(txt, design->lethal_radius); + desc += txt; + desc += " m\n"; + } + + txt_stats->SetText(desc); + } + } + + if (txt_description) { + if (design && design->description.length()) { + txt_description->SetText(design->description); + } + else { + txt_description->SetText(Game::GetText("tacref.no-info")); + } + } +} + +// +--------------------------------------------------------------------+ + +void +TacRefDlg::ExecFrame() +{ +} + +// +--------------------------------------------------------------------+ + +bool +TacRefDlg::SetCaptureBeauty() +{ + EventDispatch* dispatch = EventDispatch::GetInstance(); + if (dispatch && beauty) + return dispatch->CaptureMouse(beauty) ? true : false; + + return 0; +} + +bool +TacRefDlg::ReleaseCaptureBeauty() +{ + EventDispatch* dispatch = EventDispatch::GetInstance(); + if (dispatch && beauty) + return dispatch->ReleaseMouse(beauty) ? true : false; + + return 0; +} + +// +--------------------------------------------------------------------+ + +void +TacRefDlg::UpdateAzimuth(double a) +{ + cam_az += a; + + if (cam_az > PI) + cam_az = -2*PI + cam_az; + + else if (cam_az < -PI) + cam_az = 2*PI + cam_az; +} + +void +TacRefDlg::UpdateElevation(double e) +{ + cam_el += e; + + const double limit = (0.43 * PI); + + if (cam_el > limit) + cam_el = limit; + else if (cam_el < -limit) + cam_el = -limit; +} + +void +TacRefDlg::UpdateZoom(double delta) +{ + cam_zoom *= delta; + + if (cam_zoom < 1.2) + cam_zoom = 1.2; + + else if (cam_zoom > 10) + cam_zoom = 10; +} + +void +TacRefDlg::UpdateCamera() +{ + double x = cam_zoom * radius * sin(cam_az) * cos(cam_el); + double y = cam_zoom * radius * cos(cam_az) * cos(cam_el); + double z = cam_zoom * radius * sin(cam_el); + + cam.LookAt(Point(0,0,0), Point(x,z,y), Point(0,1,0)); +} + +// +--------------------------------------------------------------------+ + +void +TacRefDlg::OnSelect(AWEvent* event) +{ + if (lst_designs) { + int seln = lst_designs->GetSelection(); + DWORD dsn = lst_designs->GetItemData(seln); + + if (mode == MODE_SHIPS) { + ship_index = seln; + + if (dsn) { + SelectShip((ShipDesign*) dsn); + } + } + + else if (mode == MODE_WEAPONS) { + weap_index = seln; + + if (dsn) { + SelectWeapon((WeaponDesign*) dsn); + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +TacRefDlg::OnCamRButtonDown(AWEvent* event) +{ + captured = SetCaptureBeauty(); + mouse_x = event->x; + mouse_y = event->y; +} + +void +TacRefDlg::OnCamRButtonUp(AWEvent* event) +{ + if (captured) + ReleaseCaptureBeauty(); + + captured = false; + mouse_x = 0; + mouse_y = 0; +} + +void +TacRefDlg::OnCamMove(AWEvent* event) +{ + if (captured) { + int mouse_dx = event->x - mouse_x; + int mouse_dy = event->y - mouse_y; + + UpdateAzimuth( mouse_dx * 0.3 * DEGREES); + UpdateElevation( mouse_dy * 0.3 * DEGREES); + UpdateCamera(); + + mouse_x = event->x; + mouse_y = event->y; + } +} + +void +TacRefDlg::OnCamZoom(AWEvent* event) +{ + int w = Mouse::Wheel(); + + if (w < 0) { + while (w < 0) { + UpdateZoom(1.25); + w += 120; + } + } + else { + while (w > 0) { + UpdateZoom(0.75f); + w -= 120; + } + } + + UpdateCamera(); +} + +// +--------------------------------------------------------------------+ + +void +TacRefDlg::OnMode(AWEvent* event) +{ + if (event->window == btn_ships && mode != MODE_SHIPS) { + mode = MODE_SHIPS; + + if (lst_designs) { + lst_designs->ClearItems(); + + List designs; + + for (int n = 0; n < 16; n++) { + int type = 1 << n; + ShipDesign::GetDesignList(type, designs); + + ListIter iter = designs; + while (++iter) { + Text* val = iter.value(); + + const ShipDesign* dsn = ShipDesign::Get(*val); + + if (dsn) { + char txt[256]; + sprintf(txt, "%s %s", dsn->abrv, dsn->DisplayName()); + + lst_designs->AddItemWithData(txt, (DWORD) dsn); + } + else { + lst_designs->AddItemWithData(*val, 0); + } + } + } + + lst_designs->SetSelected(ship_index); + } + + if (beauty) { + beauty->AddView(camview); + beauty->DelView(imgview); + } + + DWORD dsn = lst_designs->GetItemData(ship_index); + + if (dsn) { + SelectShip((ShipDesign*) dsn); + } + } + + else if (event->window == btn_weaps && mode != MODE_WEAPONS) { + mode = MODE_WEAPONS; + + const WeaponDesign* design = 0; + + if (lst_designs) { + lst_designs->ClearItems(); + List designs; + + WeaponDesign::GetDesignList(designs); + + ListIter iter = designs; + while (++iter) { + Text* val = iter.value(); + + if (val->contains("Zolon") || val->contains("Inverted")) + continue; + + const WeaponDesign* dsn = WeaponDesign::Find(*val); + + if (dsn && !dsn->secret) { + lst_designs->AddItemWithData(*val, (DWORD) dsn); + + if (!design) + design = dsn; + } + } + + lst_designs->SetSelected(weap_index); + } + + if (beauty) { + beauty->DelView(camview); + beauty->AddView(imgview); + } + + DWORD dsn = lst_designs->GetItemData(weap_index); + + if (dsn) { + SelectWeapon((WeaponDesign*) dsn); + } + } + + btn_ships->SetButtonState(mode == MODE_SHIPS); + btn_weaps->SetButtonState(mode == MODE_WEAPONS); +} + +// +--------------------------------------------------------------------+ + +void +TacRefDlg::OnClose(AWEvent* event) +{ + manager->ShowMenuDlg(); +} + diff --git a/Stars45/TacRefDlg.h b/Stars45/TacRefDlg.h new file mode 100644 index 0000000..a0be975 --- /dev/null +++ b/Stars45/TacRefDlg.h @@ -0,0 +1,101 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: TacRefDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Mission Briefing Dialog Active Window class +*/ + +#ifndef TacRefDlg_h +#define TacRefDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "MsnDlg.h" +#include "Bitmap.h" +#include "Button.h" +#include "CameraView.h" +#include "ImgView.h" +#include "Scene.h" +#include "Font.h" +#include "Text.h" +#include "ListBox.h" +#include "RichTextBox.h" + +// +--------------------------------------------------------------------+ + +class MenuScreen; +class ShipDesign; +class WeaponDesign; + +// +--------------------------------------------------------------------+ + +class TacRefDlg : public FormWindow +{ +public: + enum MODES { MODE_NONE, MODE_SHIPS, MODE_WEAPONS }; + + TacRefDlg(Screen* s, FormDef& def, MenuScreen* mgr); + virtual ~TacRefDlg(); + + virtual void RegisterControls(); + virtual void ExecFrame(); + virtual void Show(); + + // Operations: + virtual void OnClose(AWEvent* event); + virtual void OnMode(AWEvent* event); + virtual void OnSelect(AWEvent* event); + virtual void OnCamRButtonDown(AWEvent* event); + virtual void OnCamRButtonUp(AWEvent* event); + virtual void OnCamMove(AWEvent* event); + virtual void OnCamZoom(AWEvent* event); + +protected: + virtual void SelectShip(const ShipDesign* dsn); + virtual void SelectWeapon(const WeaponDesign* dsn); + + virtual void UpdateZoom(double r); + virtual void UpdateAzimuth(double a); + virtual void UpdateElevation(double e); + virtual void UpdateCamera(); + virtual bool SetCaptureBeauty(); + virtual bool ReleaseCaptureBeauty(); + + MenuScreen* manager; + ActiveWindow* beauty; + ListBox* lst_designs; + ActiveWindow* txt_caption; + RichTextBox* txt_stats; + RichTextBox* txt_description; + Button* btn_ships; + Button* btn_weaps; + Button* btn_close; + + ImgView* imgview; + CameraView* camview; + Scene scene; + Camera cam; + + int mode; + double radius; + double cam_zoom; + double cam_az; + double cam_el; + int mouse_x; + int mouse_y; + bool update_scene; + bool captured; + + int ship_index; + int weap_index; +}; + +#endif TacRefDlg_h + diff --git a/Stars45/TacticalAI.cpp b/Stars45/TacticalAI.cpp new file mode 100644 index 0000000..c9217aa --- /dev/null +++ b/Stars45/TacticalAI.cpp @@ -0,0 +1,958 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: TacticalAI.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Generic Ship Tactical Level AI class +*/ + +#include "MemDebug.h" +#include "TacticalAI.h" +#include "ShipAI.h" +#include "CarrierAI.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "Element.h" +#include "Instruction.h" +#include "RadioMessage.h" +#include "RadioTraffic.h" +#include "Contact.h" +#include "WeaponGroup.h" +#include "Drive.h" +#include "Hangar.h" +#include "Sim.h" +#include "Shot.h" +#include "Drone.h" +#include "StarSystem.h" + +#include "Game.h" +#include "Random.h" + +// +----------------------------------------------------------------------+ + +static int exec_time_seed = 0; + +// +----------------------------------------------------------------------+ + +TacticalAI::TacticalAI(ShipAI* ai) + : ship(0), ship_ai(0), carrier_ai(0), navpt(0), orders(0), + action(0), threat_level(0), support_level(1), + directed_tgtid(0) +{ + if (ai) { + ship_ai = ai; + ship = ai->GetShip(); + + Sim* sim = Sim::GetSim(); + + if (ship && ship->GetHangar() && ship->GetCommandAILevel() > 0 && + ship != sim->GetPlayerShip()) + carrier_ai = new(__FILE__,__LINE__) CarrierAI(ship, ship_ai->GetAILevel()); + } + + agression = 0; + roe = FLEXIBLE; + element_index = 1; + exec_time = exec_time_seed; + exec_time_seed += 17; +} + +TacticalAI::~TacticalAI() +{ + delete carrier_ai; +} + +// +--------------------------------------------------------------------+ + +void +TacticalAI::ExecFrame(double secs) +{ + const int exec_period = 1000; + + if (!ship || !ship_ai) + return; + + navpt = ship->GetNextNavPoint(); + orders = ship->GetRadioOrders(); + + if ((int) Game::GameTime() - exec_time > exec_period) { + element_index = ship->GetElementIndex(); + + CheckOrders(); + SelectTarget(); + FindThreat(); + FindSupport(); + + if (element_index > 1) { + int formation = 0; + + if (orders && orders->Formation() >= 0) + formation = orders->Formation(); + + else if (navpt) + formation = navpt->Formation(); + + FindFormationSlot(formation); + } + + ship_ai->SetNavPoint(navpt); + + if (carrier_ai) + carrier_ai->ExecFrame(secs); + + exec_time += exec_period; + } +} + +// +--------------------------------------------------------------------+ + +void +TacticalAI::CheckOrders() +{ + directed_tgtid = 0; + + if (CheckShipOrders()) + return; + + if (CheckFlightPlan()) + return; + + if (CheckObjectives()) + return; +} + +// +--------------------------------------------------------------------+ + +bool +TacticalAI::CheckShipOrders() +{ + return ProcessOrders(); +} + +// +--------------------------------------------------------------------+ + +bool +TacticalAI::CheckObjectives() +{ + bool processed = false; + Ship* ward = 0; + Element* elem = ship->GetElement(); + + if (elem) { + Instruction* obj = elem->GetTargetObjective(); + + if (obj) { + ship_ai->ClearPatrol(); + + if (obj->Action()) { + switch (obj->Action()) { + case Instruction::INTERCEPT: + case Instruction::STRIKE: + case Instruction::ASSAULT: + { + SimObject* tgt = obj->GetTarget(); + if (tgt && tgt->Type() == SimObject::SIM_SHIP) { + roe = DIRECTED; + SelectTargetDirected((Ship*) tgt); + } + } + break; + + case Instruction::DEFEND: + case Instruction::ESCORT: + { + SimObject* tgt = obj->GetTarget(); + if (tgt && tgt->Type() == SimObject::SIM_SHIP) { + roe = DEFENSIVE; + ward = (Ship*) tgt; + } + } + break; + + default: + break; + } + } + + orders = obj; + processed = true; + } + } + + ship_ai->SetWard(ward); + return processed; +} + +// +--------------------------------------------------------------------+ + +bool +TacticalAI::ProcessOrders() +{ + ship_ai->ClearPatrol(); + + if (orders && orders->EMCON() > 0) { + int desired_emcon = orders->EMCON(); + + if (ship_ai && (ship_ai->GetThreat() || ship_ai->GetThreatMissile())) + desired_emcon = 3; + + if (ship->GetEMCON() != desired_emcon) + ship->SetEMCON(desired_emcon); + } + + if (orders && orders->Action()) { + switch (orders->Action()) { + case RadioMessage::ATTACK: + case RadioMessage::BRACKET: + case RadioMessage::IDENTIFY: + { + bool tgt_ok = false; + SimObject* tgt = orders->GetTarget(); + + if (tgt && tgt->Type() == SimObject::SIM_SHIP) { + Ship* tgt_ship = (Ship*) tgt; + + if (CanTarget(tgt_ship)) { + roe = DIRECTED; + SelectTargetDirected((Ship*) tgt); + + ship_ai->SetBracket(orders->Action() == RadioMessage::BRACKET); + ship_ai->SetIdentify(orders->Action() == RadioMessage::IDENTIFY); + ship_ai->SetNavPoint(0); + + tgt_ok = true; + } + } + + if (!tgt_ok) + ClearRadioOrders(); + } + break; + + case RadioMessage::ESCORT: + case RadioMessage::COVER_ME: + { + SimObject* tgt = orders->GetTarget(); + if (tgt && tgt->Type() == SimObject::SIM_SHIP) { + roe = DEFENSIVE; + ship_ai->SetWard((Ship*) tgt); + ship_ai->SetNavPoint(0); + } + else { + ClearRadioOrders(); + } + } + break; + + case RadioMessage::WEP_FREE: + roe = AGRESSIVE; + ship_ai->DropTarget(0.1); + break; + + case RadioMessage::WEP_HOLD: + case RadioMessage::FORM_UP: + roe = NONE; + ship_ai->DropTarget(5); + break; + + case RadioMessage::MOVE_PATROL: + roe = SELF_DEFENSIVE; + ship_ai->SetPatrol(orders->Location()); + ship_ai->SetNavPoint(0); + ship_ai->DropTarget(Random(5, 10)); + break; + + case RadioMessage::RTB: + case RadioMessage::DOCK_WITH: + roe = NONE; + + ship_ai->DropTarget(10); + + if (!ship->GetInbound()) { + RadioMessage* msg = 0; + Ship* controller = ship->GetController(); + + if (orders->Action() == RadioMessage::DOCK_WITH && orders->GetTarget()) { + controller = (Ship*) orders->GetTarget(); + } + + if (!controller) { + Element* elem = ship->GetElement(); + if (elem && elem->GetCommander()) { + Element* cmdr = elem->GetCommander(); + controller = cmdr->GetShip(1); + } + } + + if (controller && controller->GetHangar() && + controller->GetHangar()->CanStow(ship)) { + SimRegion* self_rgn = ship->GetRegion(); + SimRegion* rtb_rgn = controller->GetRegion(); + + if (self_rgn == rtb_rgn) { + double range = Point(controller->Location() - ship->Location()).length(); + + if (range < 50e3) { + msg = new(__FILE__,__LINE__) RadioMessage(controller, ship, RadioMessage::CALL_INBOUND); + RadioTraffic::Transmit(msg); + } + } + } + else { + ship->ClearRadioOrders(); + } + + ship_ai->SetNavPoint(0); + } + break; + + case RadioMessage::QUANTUM_TO: + case RadioMessage::FARCAST_TO: + roe = NONE; + ship_ai->DropTarget(10); + break; + + } + + action = orders->Action(); + return true; + } + + // if we had an action before, this must be a "cancel orders" + else if (action) { + ClearRadioOrders(); + } + + return false; +} + +void +TacticalAI::ClearRadioOrders() +{ + action = 0; + roe = FLEXIBLE; + + if (ship_ai) + ship_ai->DropTarget(0.1); + + if (ship) + ship->ClearRadioOrders(); + +} + +// +--------------------------------------------------------------------+ + +bool +TacticalAI::CheckFlightPlan() +{ + Ship* ward = 0; + + // Find next Instruction: + navpt = ship->GetNextNavPoint(); + + roe = FLEXIBLE; + + if (navpt) { + switch (navpt->Action()) { + case Instruction::LAUNCH: + case Instruction::DOCK: + case Instruction::RTB: roe = NONE; + break; + + case Instruction::VECTOR: roe = SELF_DEFENSIVE; + break; + + case Instruction::DEFEND: + case Instruction::ESCORT: roe = DEFENSIVE; + break; + + case Instruction::INTERCEPT: + roe = DIRECTED; + break; + + case Instruction::RECON: + case Instruction::STRIKE: + case Instruction::ASSAULT: roe = DIRECTED; + break; + + case Instruction::PATROL: + case Instruction::SWEEP: roe = FLEXIBLE; + break; + + default: break; + } + + if (roe == DEFENSIVE) { + SimObject* tgt = navpt->GetTarget(); + + if (tgt && tgt->Type() == SimObject::SIM_SHIP) + ward = (Ship*) tgt; + } + + + if (navpt->EMCON() > 0) { + int desired_emcon = navpt->EMCON(); + + if (ship_ai && (ship_ai->GetThreat() || ship_ai->GetThreatMissile())) + desired_emcon = 3; + + if (ship->GetEMCON() != desired_emcon) + ship->SetEMCON(desired_emcon); + } + } + + if (ship_ai) + ship_ai->SetWard(ward); + + return (navpt != 0); +} + +// +--------------------------------------------------------------------+ + +void +TacticalAI::SelectTarget() +{ + if (!ship) { + roe = NONE; + return; + } + + // unarmed vessels should never engage an enemy: + if (ship->Weapons().size() < 1) + roe = NONE; + + SimObject* target = ship_ai->GetTarget(); + SimObject* ward = ship_ai->GetWard(); + + // if not allowed to engage, drop and return: + if (roe == NONE) { + if (target) + ship_ai->DropTarget(); + return; + } + + // if we have abandoned our ward, drop and return: + if (ward && roe != AGRESSIVE) { + double d = (ward->Location() - ship->Location()).length(); + double safe_zone = 50e3; + + if (target) { + if (ship->IsStarship()) + safe_zone = 100e3; + + if (d > safe_zone) { + ship_ai->DropTarget(); + return; + } + } + else { + if (d > safe_zone) { + return; + } + } + } + + // already have a target, keep it: + if (target) { + if (target->Life()) { + CheckTarget(); + + // frigates need to be ready to abandon ship-type targets + // in favor of drone-type targets, others should just go + // with what they have: + if (ship->Class() != Ship::CORVETTE && ship->Class() != Ship::FRIGATE) + return; + + // in case the check decided to drop the target: + target = ship_ai->GetTarget(); + } + + // if the old target is dead, forget it: + else { + ship_ai->DropTarget(); + target = 0; + } + } + + // if not allowed to acquire, forget it: + if (ship_ai->DropTime() > 0) + return; + + if (roe == DIRECTED) { + if (target && target->Type() == SimObject::SIM_SHIP) + SelectTargetDirected((Ship*) target); + else if (navpt && navpt->GetTarget() && navpt->GetTarget()->Type() == SimObject::SIM_SHIP) + SelectTargetDirected((Ship*) navpt->GetTarget()); + else + SelectTargetDirected(); + } + + else { + SelectTargetOpportunity(); + + // don't switch one ship target for another... + if (ship->Class() == Ship::CORVETTE || ship->Class() == Ship::FRIGATE) { + SimObject* potential_target = ship_ai->GetTarget(); + if (target && potential_target && target != potential_target) { + if (target->Type() == SimObject::SIM_SHIP && + potential_target->Type() == SimObject::SIM_SHIP) { + + ship_ai->SetTarget(target); + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +TacticalAI::SelectTargetDirected(Ship* tgt) +{ + Ship* potential_target = tgt; + + // try to target one of the element's objectives + // (if it shows up in the contact list) + + if (!tgt) { + Element* elem = ship->GetElement(); + + if (elem) { + Instruction* objective = elem->GetTargetObjective(); + + if (objective) { + SimObject* obj_sim_obj = objective->GetTarget(); + Ship* obj_tgt = 0; + + if (obj_sim_obj && obj_sim_obj->Type() == SimObject::SIM_SHIP) + obj_tgt = (Ship*) obj_sim_obj; + + if (obj_tgt) { + ListIter contact = ship->ContactList(); + while (++contact && !potential_target) { + Ship* test = contact->GetShip(); + + if (obj_tgt == test) { + potential_target = test; + } + } + } + } + } + } + + if (!CanTarget(potential_target)) + potential_target = 0; + + ship_ai->SetTarget(potential_target); + + if (tgt && tgt == ship_ai->GetTarget()) + directed_tgtid = tgt->Identity(); + else + directed_tgtid = 0; +} + +// +--------------------------------------------------------------------+ + +bool +TacticalAI::CanTarget(Ship* tgt) +{ + bool result = false; + + if (tgt && !tgt->InTransition()) { + if (tgt->IsRogue() || tgt->GetIFF() != ship->GetIFF()) + result = true; + } + + return result; +} + +// +--------------------------------------------------------------------+ + +void +TacticalAI::SelectTargetOpportunity() +{ + // NON-COMBATANTS do not pick targets of opportunity: + if (ship->GetIFF() == 0) + return; + + SimObject* potential_target = 0; + + // pick the closest combatant ship with a different IFF code: + double target_dist = ship->Design()->commit_range; + + SimObject* ward = ship_ai->GetWard(); + + // FRIGATES are primarily anti-air platforms, but may + // also attack smaller starships: + + if (ship->Class() == Ship::CORVETTE || ship->Class() == Ship::FRIGATE) { + Ship* current_ship_target = 0; + Shot* current_shot_target = 0; + + // if we are escorting a larger warship, it is good to attack + // the same target as our ward: + + if (ward) { + Ship* s = (Ship*) ward; + + if (s->Class() > ship->Class()) { + SimObject* obj = s->GetTarget(); + + if (obj && obj->Type() == SimObject::SIM_SHIP) { + current_ship_target = (Ship*) obj; + target_dist = (ship->Location() - obj->Location()).length(); + } + } + } + + ListIter contact = ship->ContactList(); + while (++contact) { + Ship* c_ship = contact->GetShip(); + Shot* c_shot = contact->GetShot(); + + if (!c_ship && !c_shot) + continue; + + int c_iff = contact->GetIFF(ship); + bool rogue = c_ship && c_ship->IsRogue(); + bool tgt_ok = c_iff > 0 && + c_iff != ship->GetIFF() && + c_iff < 1000; + + if (rogue || tgt_ok) { + if (c_ship && c_ship != ship && !c_ship->InTransition()) { + if (c_ship->Class() < Ship::DESTROYER || + (c_ship->Class() >= Ship::MINE && c_ship->Class() <= Ship::SWACS)) { + // found an enemy, check distance: + double dist = (ship->Location() - c_ship->Location()).length(); + + if (dist < 0.75 * target_dist && + (!current_ship_target || c_ship->Class() <= current_ship_target->Class())) { + current_ship_target = c_ship; + target_dist = dist; + } + } + } + + else if (c_shot) { + // found an enemy shot, is there enough time to engage? + if (c_shot->GetEta() < 3) + continue; + + // found an enemy shot, check distance: + double dist = (ship->Location() - c_shot->Location()).length(); + + if (!current_shot_target) { + current_shot_target = c_shot; + target_dist = dist; + } + + // is this shot a better target than the one we've found? + else { + Ship* ward = ship_ai->GetWard(); + + if ((c_shot->IsTracking(ward) || c_shot->IsTracking(ship)) && + (!current_shot_target->IsTracking(ward) || + !current_shot_target->IsTracking(ship))) { + current_shot_target = c_shot; + target_dist = dist; + } + else if (dist < target_dist) { + current_shot_target = c_shot; + target_dist = dist; + } + } + } + } + } + + if (current_shot_target) + potential_target = current_shot_target; + else + potential_target = current_ship_target; + } + + // ALL OTHER SHIP CLASSES ignore fighters and only engage + // other starships: + + else { + List ward_threats; + + ListIter contact = ship->ContactList(); + while (++contact) { + Ship* c_ship = contact->GetShip(); + + if (!c_ship) + continue; + + int c_iff = contact->GetIFF(ship); + bool rogue = c_ship->IsRogue(); + bool tgt_ok = c_ship != ship && + c_iff > 0 && + c_iff != ship->GetIFF() && + !c_ship->InTransition(); + + if (rogue || tgt_ok) { + if (c_ship->IsStarship() || c_ship->IsStatic()) { + // found an enemy, check distance: + double dist = (ship->Location() - c_ship->Location()).length(); + + if (dist < 0.75 * target_dist) { + potential_target = c_ship; + target_dist = dist; + } + + if (ward && c_ship->IsTracking(ward)) { + ward_threats.append(c_ship); + } + } + } + } + + // if this ship is protecting a ward, + // prefer targets that are threatening that ward: + if (potential_target && ward_threats.size() && !ward_threats.contains((Ship*)potential_target)) { + target_dist *= 2; + + ListIter iter = ward_threats; + while (++iter) { + Ship* threat = iter.value(); + + double dist = (ward->Location() - threat->Location()).length(); + + if (dist < target_dist) { + potential_target = threat; + target_dist = dist; + } + } + } + } + + if (ship->Class() != Ship::CARRIER) + ship_ai->SetTarget(potential_target); +} + +// +--------------------------------------------------------------------+ + +void +TacticalAI::CheckTarget() +{ + SimObject* tgt = ship_ai->GetTarget(); + + if (!tgt) return; + + if (tgt->GetRegion() != ship->GetRegion()) { + ship_ai->DropTarget(); + return; + } + + if (tgt->Type() == SimObject::SIM_SHIP) { + Ship* target = (Ship*) tgt; + + // has the target joined our side? + if (target->GetIFF() == ship->GetIFF() && !target->IsRogue()) { + ship_ai->DropTarget(); + return; + } + + // is the target already jumping/breaking/dying? + if (target->InTransition()) { + ship_ai->DropTarget(); + return; + } + + // have we been ordered to pursue the target? + if (directed_tgtid) { + if (directed_tgtid != target->Identity()) { + ship_ai->DropTarget(); + } + + return; + } + + // can we catch the target? + if (target->Design()->vlimit <= ship->Design()->vlimit || + ship->Velocity().length() <= ship->Design()->vlimit) + return; + + // is the target now out of range? + WeaponDesign* wep_dsn = ship->GetPrimaryDesign(); + if (!wep_dsn) + return; + + // compute the "give up" range: + double drop_range = 3 * wep_dsn->max_range; + if (drop_range > 0.75 * ship->Design()->commit_range) + drop_range = 0.75 * ship->Design()->commit_range; + + double range = Point(target->Location() - ship->Location()).length(); + if (range < drop_range) + return; + + // is the target closing or separating? + Point delta = (target->Location() + target->Velocity()) - + (ship->Location() + ship->Velocity()); + + if (delta.length() < range) + return; + + ship_ai->DropTarget(); + } + + else if (tgt->Type() == SimObject::SIM_DRONE) { + Drone* drone = (Drone*) tgt; + + // is the target still a threat? + if (drone->GetEta() < 1 || drone->GetTarget() == 0) + ship_ai->DropTarget(); + } +} + +// +--------------------------------------------------------------------+ + +void +TacticalAI::FindThreat() +{ + // pick the closest contact on Threat Warning System: + Ship* threat = 0; + Shot* threat_missile = 0; + Ship* rumor = 0; + double threat_dist = 1e9; + const DWORD THREAT_REACTION_TIME = 1000; // 1 second + + ListIter iter = ship->ContactList(); + + while (++iter) { + Contact* contact = iter.value(); + + if (contact->Threat(ship) && + (Game::GameTime() - contact->AcquisitionTime()) > THREAT_REACTION_TIME) { + + if (contact->GetShot()) { + threat_missile = contact->GetShot(); + rumor = (Ship*) threat_missile->Owner(); + } + else { + double rng = contact->Range(ship); + + Ship* c_ship = contact->GetShip(); + if (c_ship && !c_ship->InTransition() && + c_ship->Class() != Ship::FREIGHTER && + c_ship->Class() != Ship::FARCASTER) { + + if (c_ship->GetTarget() == ship) { + if (!threat || c_ship->Class() > threat->Class()) { + threat = c_ship; + threat_dist = 0; + } + } + else if (rng < threat_dist) { + threat = c_ship; + threat_dist = rng; + } + } + } + } + } + + if (rumor && !rumor->InTransition()) { + iter.reset(); + + while (++iter) { + if (iter->GetShip() == rumor) { + rumor = 0; + ship_ai->ClearRumor(); + break; + } + } + } + else { + rumor = 0; + ship_ai->ClearRumor(); + } + + ship_ai->SetRumor(rumor); + ship_ai->SetThreat(threat); + ship_ai->SetThreatMissile(threat_missile); +} + +// +--------------------------------------------------------------------+ + +void +TacticalAI::FindSupport() +{ + if (!ship_ai->GetThreat()) { + ship_ai->SetSupport(0); + return; + } + + // pick the biggest friendly contact in the sector: + Ship* support = 0; + double support_dist = 1e9; + + ListIter contact = ship->ContactList(); + + while (++contact) { + if (contact->GetShip() && contact->GetIFF(ship) == ship->GetIFF()) { + Ship* c_ship = contact->GetShip(); + + if (c_ship != ship && c_ship->Class() >= ship->Class() && !c_ship->InTransition()) { + if (!support || c_ship->Class() > support->Class()) + support = c_ship; + } + } + } + + ship_ai->SetSupport(support); +} + +// +--------------------------------------------------------------------+ + +void +TacticalAI::FindFormationSlot(int formation) +{ + // find the formation delta: + int s = element_index - 1; + Point delta(10*s, 0, 10*s); + + // diamond: + if (formation == Instruction::DIAMOND) { + switch (element_index) { + case 2: delta = Point( 10, 0, -12); break; + case 3: delta = Point(-10, 0, -12); break; + case 4: delta = Point( 0, 0, -24); break; + } + } + + // spread: + if (formation == Instruction::SPREAD) { + switch (element_index) { + case 2: delta = Point( 15, 0, 0); break; + case 3: delta = Point(-15, 0, 0); break; + case 4: delta = Point(-30, 0, 0); break; + } + } + + // box: + if (formation == Instruction::BOX) { + switch (element_index) { + case 2: delta = Point(15, 0, 0); break; + case 3: delta = Point( 0, -1, -15); break; + case 4: delta = Point(15, -1, -15); break; + } + } + + // trail: + if (formation == Instruction::TRAIL) { + delta = Point(0, 0, -15*s); + } + + ship_ai->SetFormationDelta(delta * ship->Radius() * 2); +} diff --git a/Stars45/TacticalAI.h b/Stars45/TacticalAI.h new file mode 100644 index 0000000..3a56136 --- /dev/null +++ b/Stars45/TacticalAI.h @@ -0,0 +1,91 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: TacticalAI.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Common base class and interface for mid-level (tactical) AI +*/ + +#ifndef TacticalAI_h +#define TacticalAI_h + +#include "Types.h" +#include "Director.h" + +// +--------------------------------------------------------------------+ + +class Ship; +class ShipAI; +class Instruction; +class CarrierAI; + +// +--------------------------------------------------------------------+ + +class TacticalAI : public Director +{ +public: + TacticalAI(ShipAI* ai); + virtual ~TacticalAI(); + + enum ROE { + NONE, + SELF_DEFENSIVE, + DEFENSIVE, + DIRECTED, + FLEXIBLE, + AGRESSIVE + }; + + virtual void ExecFrame(double seconds); + + virtual ROE RulesOfEngagement() const { return roe; } + virtual double ThreatLevel() const { return threat_level; } + virtual double SupportLevel() const { return support_level; } + +protected: + // pick the best target if we don't have one yet: + virtual void CheckOrders(); + virtual bool CheckShipOrders(); + virtual bool ProcessOrders(); + virtual bool CheckFlightPlan(); + virtual bool CheckObjectives(); + + virtual void SelectTarget(); + virtual void SelectTargetDirected(Ship* tgt=0); + virtual void SelectTargetOpportunity(); + virtual void CheckTarget(); + virtual void FindThreat(); + virtual void FindSupport(); + virtual void FindFormationSlot(int formation); + + virtual bool CanTarget(Ship* tgt); + virtual void ClearRadioOrders(); + + Ship* ship; + ShipAI* ship_ai; + CarrierAI* carrier_ai; + + Instruction* navpt; + Instruction* orders; + + double agression; + ROE roe; + int element_index; + int action; + int exec_time; + int directed_tgtid; + + double threat_level; + double support_level; +}; + +// +--------------------------------------------------------------------+ + +#endif TacticalAI_h + diff --git a/Stars45/TacticalView.cpp b/Stars45/TacticalView.cpp new file mode 100644 index 0000000..e27c8f1 --- /dev/null +++ b/Stars45/TacticalView.cpp @@ -0,0 +1,1495 @@ +/* Project Starshatter 5.0 + Destroyer Studios LLC + Copyright © 1997-2007. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: TacticalView.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + View class for Tactical Data Readout HUD Overlay +*/ + +#include "MemDebug.h" +#include "TacticalView.h" +#include "QuantumView.h" +#include "RadioView.h" +#include "RadioMessage.h" +#include "RadioTraffic.h" +#include "HUDSounds.h" +#include "HUDView.h" +#include "WepView.h" +#include "CameraDirector.h" +#include "Ship.h" +#include "ShipCtrl.h" +#include "ShipDesign.h" +#include "QuantumDrive.h" +#include "Farcaster.h" +#include "Instruction.h" +#include "Element.h" +#include "Contact.h" +#include "Sim.h" +#include "Starshatter.h" +#include "GameScreen.h" +#include "MenuView.h" + +#include "Projector.h" +#include "Color.h" +#include "Window.h" +#include "Video.h" +#include "DataLoader.h" +#include "Scene.h" +#include "FontMgr.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "MouseController.h" +#include "Menu.h" +#include "Game.h" +#include "FormatUtil.h" + +static Color hud_color = Color::Black; +static Color txt_color = Color::Black; + +// +--------------------------------------------------------------------+ + +TacticalView* TacticalView::tac_view = 0; + +// +--------------------------------------------------------------------+ + +TacticalView::TacticalView(Window* c, GameScreen* parent) + : View(c), gamescreen(parent), ship(0), camview(0), projector(0), + mouse_down(0), right_down(0), shift_down(0), + show_move(0), show_action(0), active_menu(0), menu_view(0), + msg_ship(0), base_alt(0), move_alt(0) +{ + tac_view = this; + sim = Sim::GetSim(); + + width = window->Width(); + height = window->Height(); + xcenter = (width / 2.0) - 0.5; + ycenter = (height / 2.0) + 0.5; + font = FontMgr::Find("HUD"); + + SetColor(Color::White); + + mouse_start.x = 0; + mouse_start.y = 0; + mouse_action.x = 0; + mouse_action.y = 0; + + menu_view = new(__FILE__,__LINE__) MenuView(window); +} + +TacticalView::~TacticalView() +{ + delete menu_view; + tac_view = 0; +} + +void +TacticalView::OnWindowMove() +{ + width = window->Width(); + height = window->Height(); + xcenter = (width / 2.0) - 0.5; + ycenter = (height / 2.0) + 0.5; + + if (menu_view) + menu_view->OnWindowMove(); +} + +// +--------------------------------------------------------------------+ + +bool +TacticalView::Update(SimObject* obj) +{ + if (obj == ship) { + ship = 0; + } + + if (obj == msg_ship) { + msg_ship = 0; + } + + return SimObserver::Update(obj); +} + +const char* +TacticalView::GetObserverName() const +{ + return "TacticalView"; +} + +void +TacticalView::UseProjector(Projector* p) +{ + projector = p; +} + +// +--------------------------------------------------------------------+ + +void +TacticalView::Refresh() +{ + sim = Sim::GetSim(); + + if (sim) { + bool rebuild = false; + + if (ship != sim->GetPlayerShip()) { + ship = sim->GetPlayerShip(); + + if (ship) { + if (ship->Life() == 0 || ship->IsDying() || ship->IsDead()) { + ship = 0; + } + else { + Observe(ship); + } + } + + rebuild = true; + } + + if (ship) { + if (current_sector != ship->GetRegion()->Name()) + rebuild = true; + + if (rebuild) { + BuildMenu(); + current_sector = ship->GetRegion()->Name(); + } + } + } + + if (!ship || ship->InTransition()) + return; + + DrawMouseRect(); + + if (sim) { + ListIter sel = sim->GetSelection(); + + if (sel.size()) { + while (++sel) { + Ship* selection = sel.value(); + + // draw selection rect on selected ship: + if (selection && selection->Rep()) + DrawSelection(selection); + } + + RadioView* rv = RadioView::GetInstance(); + QuantumView* qv = QuantumView::GetInstance(); + + if ((!rv || !rv->IsMenuShown()) && (!qv || !qv->IsMenuShown())) { + sel.reset(); + + if (sel.size() == 1) { + DrawSelectionInfo(sel.next()); + } + else { + DrawSelectionList(sel); + } + } + } + } + + DrawMenu(); + + if (show_move) { + Mouse::Show(false); + DrawMove(); + } + else if (show_action) { + Mouse::Show(false); + DrawAction(); + } +} + +// +--------------------------------------------------------------------+ + +void +TacticalView::ExecFrame() +{ + HUDView* hud = HUDView::GetInstance(); + if (hud) { + if (hud_color != hud->GetTextColor()) { + hud_color = hud->GetTextColor(); + SetColor(hud_color); + } + } +} + +// +--------------------------------------------------------------------+ + +void +TacticalView::SetColor(Color c) +{ + HUDView* hud = HUDView::GetInstance(); + + if (hud) { + hud_color = hud->GetHUDColor(); + txt_color = hud->GetTextColor(); + } + else { + hud_color = c; + txt_color = c; + } +} + +// +--------------------------------------------------------------------+ + +void +TacticalView::DrawMouseRect() +{ + if (mouse_rect.w > 0 && mouse_rect.h > 0) { + Color c = hud_color * 0.66; + + if (shift_down) + c = Color::Orange; + + window->DrawRect(mouse_rect, c); + } +} + +// +--------------------------------------------------------------------+ + +void +TacticalView::DrawSelection(Ship* seln) +{ + Graphic* g = seln->Rep(); + Rect r = g->ScreenRect(); + + Point mark_pt; + + if (seln) + mark_pt = seln->Location(); + + projector->Transform(mark_pt); + + // clip: + if (mark_pt.z > 1.0) { + projector->Project(mark_pt); + + int x = (int) mark_pt.x; + int y = r.y; + + if (y >= 2000) + y = (int) mark_pt.y; + + if (x > 4 && x < width-4 && + y > 4 && y < height-4) { + + const int BAR_LENGTH = 40; + + // life bars: + int sx = x - BAR_LENGTH/2; + int sy = y - 8; + + double hull_strength = seln->HullStrength() / 100.0; + + int hw = (int) (BAR_LENGTH * hull_strength); + int sw = (int) (BAR_LENGTH * (seln->ShieldStrength() / 100.0)); + + if (hw < 0) hw = 0; + if (sw < 0) sw = 0; + + System::STATUS s = System::NOMINAL; + + if (hull_strength < 0.30) s = System::CRITICAL; + else if (hull_strength < 0.60) s = System::DEGRADED; + + Color hc = HUDView::GetStatusColor(s); + Color sc = hud_color; + + window->FillRect(sx, sy, sx+hw, sy+1, hc); + window->FillRect(sx, sy+3, sx+sw, sy+4, sc); + } + } +} + +// +--------------------------------------------------------------------+ + +void +TacticalView::DrawSelectionInfo(Ship* seln) +{ + if (!ship || !seln) return; + + Rect label_rect(width-140, 10, 90, 12); + Rect info_rect(width-100, 10, 90, 12); + + if (width >= 800) { + label_rect.x -= 20; + info_rect.x -= 20; + info_rect.w += 20; + } + + static char name[64]; + static char design[64]; + static char shield[32]; + static char hull[32]; + static char range[32]; + static char heading[32]; + static char speed[32]; + static char orders[64]; + static char psv[32]; + static char act[32]; + + int show_labels = width > 640; + int full_info = true; + int shield_val = seln->ShieldStrength(); + int hull_val = seln->HullStrength(); + + if (shield_val < 0) shield_val = 0; + if (hull_val < 0) hull_val = 0; + + sprintf(name, "%s", seln->Name()); + + if (show_labels) { + sprintf(shield, "%s %03d", Game::GetText("HUDView.symbol.shield").data(), shield_val); + sprintf(hull, "%s %03d", Game::GetText("HUDView.symbol.hull").data(), hull_val); + } + else { + sprintf(shield, "%03d", shield_val); + sprintf(hull, "%03d", hull_val); + } + + FormatNumberExp(range, Point(seln->Location()-ship->Location()).length()/1000); + strcat(range, " km"); + sprintf(heading, "%03d %s", (int) (seln->CompassHeading() / DEGREES), Game::GetText("HUDView.symbol.degrees").data()); + + double ss = seln->Velocity().length(); + if (seln->Velocity() * seln->Heading() < 0) + ss = -ss; + + FormatNumberExp(speed, ss); + strcat(speed, " m/s"); + + Contact* contact = 0; + + // always recognize ownside: + if (seln->GetIFF() != ship->GetIFF()) { + ListIter c = ship->ContactList(); + while (++c) { + if (c->GetShip() == seln) { + contact = c.value(); + if (c->GetIFF(ship) > seln->GetIFF()) { + sprintf(name, "%s %04d", Game::GetText("TacView.contact").data(), seln->GetContactID()); + full_info = false; + } + + break; + } + } + } + + if (show_labels) { + font->SetColor(txt_color); + font->SetAlpha(1); + + font->DrawText(Game::GetText("TacView.name"), 5, label_rect, DT_LEFT); + label_rect.y += 10; + font->DrawText(Game::GetText("TacView.type"), 5, label_rect, DT_LEFT); + label_rect.y += 10; + + if (full_info) { + font->DrawText(Game::GetText("TacView.shield"), 5, label_rect, DT_LEFT); + label_rect.y += 10; + font->DrawText(Game::GetText("TacView.hull"), 5, label_rect, DT_LEFT); + label_rect.y += 10; + } + + font->DrawText(Game::GetText("TacView.range"), 4, label_rect, DT_LEFT); + label_rect.y += 10; + + if (full_info) { + font->DrawText(Game::GetText("TacView.speed"), 4, label_rect, DT_LEFT); + label_rect.y += 10; + font->DrawText(Game::GetText("TacView.heading"), 4, label_rect, DT_LEFT); + label_rect.y += 10; + } + else { + font->DrawText(Game::GetText("TacView.passive"), 4, label_rect, DT_LEFT); + label_rect.y += 10; + font->DrawText(Game::GetText("TacView.active"), 4, label_rect, DT_LEFT); + label_rect.y += 10; + } + } + + font->DrawText(name, 0, info_rect, DT_LEFT); + info_rect.y += 10; + + if (full_info) { + sprintf(design, "%s %s", seln->Abbreviation(), seln->Design()->display_name); + font->DrawText(design, 0, info_rect, DT_LEFT); + info_rect.y += 10; + } + else { + if (seln->IsStarship()) + font->DrawText(Game::GetText("TacView.starship"), 8, info_rect, DT_LEFT); + else + font->DrawText(Game::GetText("TacView.fighter"), 7, info_rect, DT_LEFT); + + info_rect.y += 10; + } + + if (full_info) { + font->DrawText(shield, 0, info_rect, DT_LEFT); + info_rect.y += 10; + + font->DrawText(hull, 0, info_rect, DT_LEFT); + info_rect.y += 10; + } + + font->DrawText(range, 0, info_rect, DT_LEFT); + info_rect.y += 10; + + if (full_info) { + font->DrawText(speed, 0, info_rect, DT_LEFT); + info_rect.y += 10; + + font->DrawText(heading, 0, info_rect, DT_LEFT); + info_rect.y += 10; + + if (seln->GetIFF() == ship->GetIFF()) { + Instruction* instr = seln->GetRadioOrders(); + if (instr && instr->Action()) { + strcpy(orders, RadioMessage::ActionName(instr->Action())); + + if (instr->Action() == RadioMessage::QUANTUM_TO) { + strcat(orders, " "); + strcat(orders, instr->RegionName()); + } + } + else { + *orders = 0; + } + + if (*orders) { + if (show_labels) { + font->DrawText(Game::GetText("TacView.orders"), 5, label_rect, DT_LEFT); + label_rect.y += 10; + } + + font->DrawText(orders, 0, info_rect, DT_LEFT); + info_rect.y += 10; + } + } + } + else { + sprintf(psv, "%03d", (int) (contact->PasReturn() * 100.0)); + sprintf(act, "%03d", (int) (contact->ActReturn() * 100.0)); + + if (contact->Threat(ship)) + strcat(psv, " !"); + + font->DrawText(psv, 0, info_rect, DT_LEFT); + info_rect.y += 10; + font->DrawText(act, 0, info_rect, DT_LEFT); + info_rect.y += 10; + } + + /*** XXX DEBUG + font->DrawText(seln->GetDirectorInfo(), 0, info_rect, DT_LEFT); + info_rect.y += 10; + /***/ +} + +// +--------------------------------------------------------------------+ + +void +TacticalView::DrawSelectionList(ListIter seln) +{ + int index = 0; + Rect info_rect(width-100, 10, 90, 12); + + while (++seln) { + char name[64]; + sprintf(name, "%s", seln->Name()); + + // always recognize ownside: + if (seln->GetIFF() != ship->GetIFF()) { + ListIter c = ship->ContactList(); + while (++c) { + if (c->GetShip() == seln.value()) { + if (c->GetIFF(ship) > seln->GetIFF()) { + sprintf(name, "%s %04d", Game::GetText("TacView.contact").data(), seln->GetContactID()); + } + + break; + } + } + } + + font->DrawText(name, 0, info_rect, DT_LEFT); + info_rect.y += 10; + index++; + + if (index >= 10) + break; + } +} + +// +--------------------------------------------------------------------+ + +void +TacticalView::DoMouseFrame() +{ + static DWORD rbutton_latch = 0; + + Starshatter* stars = Starshatter::GetInstance(); + + if (stars->InCutscene()) + return; + + if (Mouse::RButton()) { + MouseController* mouse_con = MouseController::GetInstance(); + if (!right_down && (!mouse_con || !mouse_con->Active())) { + rbutton_latch = Game::RealTime(); + right_down = true; + } + } + else { + if (sim && right_down && (Game::RealTime() - rbutton_latch < 250)) { + Ship* seln = WillSelectAt(Mouse::X(), Mouse::Y()); + + if (seln && sim->IsSelected(seln) && + seln->GetIFF() == ship->GetIFF() && + ship->GetElement()->CanCommand(seln->GetElement())) { + + msg_ship = seln; + Observe(msg_ship); + } + + else if (ship && seln == ship && + (!ship->GetDirector() || + ship->GetDirector()->Type() != ShipCtrl::DIR_TYPE)) { + + msg_ship = seln; + } + + else { + msg_ship = 0; + } + } + + right_down = false; + } + + if (menu_view) + menu_view->DoMouseFrame(); + + MouseController* mouse_con = MouseController::GetInstance(); + + if (!mouse_con || !mouse_con->Active()) { + if (Mouse::LButton()) { + if (!mouse_down) { + mouse_start.x = Mouse::X(); + mouse_start.y = Mouse::Y(); + + shift_down = Keyboard::KeyDown(VK_SHIFT); + } + + else { + if (Mouse::X() < mouse_start.x) { + mouse_rect.x = Mouse::X(); + mouse_rect.w = mouse_start.x - Mouse::X(); + } + else { + mouse_rect.x = mouse_start.x; + mouse_rect.w = Mouse::X() - mouse_start.x; + } + + if (Mouse::Y() < mouse_start.y) { + mouse_rect.y = Mouse::Y(); + mouse_rect.h = mouse_start.y - Mouse::Y(); + } + else { + mouse_rect.y = mouse_start.y; + mouse_rect.h = Mouse::Y() - mouse_start.y; + } + + // don't draw seln rectangle while zooming: + if (Mouse::RButton() || show_move || show_action) { + mouse_rect.w = 0; + mouse_rect.h = 0; + } + + else { + SelectRect(mouse_rect); + } + } + + mouse_down = true; + } + + else { + if (mouse_down) { + int mouse_x = Mouse::X(); + int mouse_y = Mouse::Y(); + + if (menu_view && menu_view->GetAction()) { + ProcessMenuItem(menu_view->GetAction()); + Mouse::Show(true); + } + else if (show_move) { + SendMove(); + show_move = false; + Mouse::Show(true); + } + else if (show_action) { + SendAction(); + show_action = false; + Mouse::Show(true); + } + else { + if (!HUDView::IsMouseLatched() && !WepView::IsMouseLatched()) { + int dx = (int) fabs((double) (mouse_x - mouse_start.x)); + int dy = (int) fabs((double) (mouse_y - mouse_start.y)); + + static DWORD click_time = 0; + + if (dx < 3 && dy < 3) { + bool hit = SelectAt(mouse_x, mouse_y); + + if (ship->IsStarship() && Game::RealTime() - click_time < 350) + SetHelm(hit); + + click_time = Game::RealTime(); + } + } + } + + mouse_rect = Rect(); + mouse_down = false; + } + } + } + + if (show_action && !mouse_down && !right_down) { + mouse_action.x = Mouse::X(); + mouse_action.y = Mouse::Y(); + } +} + +// +--------------------------------------------------------------------+ + +bool +TacticalView::SelectAt(int x, int y) +{ + if (!ship) return false; + + Ship* selection = WillSelectAt(x,y); + + if (selection && shift_down) + ship->SetTarget(selection); + + else if (sim && selection) + sim->SetSelection(selection); + + return selection != 0; +} + +// +--------------------------------------------------------------------+ + +bool +TacticalView::SelectRect(const Rect& rect) +{ + bool result = false; + + if (!ship || !sim) return result; + + if (rect.w > 8 || rect.h > 8) + sim->ClearSelection(); + + // check distance to each contact: + List& contact_list = ship->ContactList(); + + for (int i = 0; i < ship->NumContacts(); i++) { + Ship* test = contact_list[i]->GetShip(); + + if (test && test != ship) { + + Point test_loc = test->Location(); + projector->Transform(test_loc); + + if (test_loc.z > 1) { + projector->Project(test_loc); + + if (rect.Contains((int) test_loc.x, (int) test_loc.y)) { + // shift-select targets: + if (shift_down) { + if (test->GetIFF() == 0 || test->GetIFF() == ship->GetIFF()) + continue; + + ship->SetTarget(test); + result = true; + } + else { + sim->AddSelection(test); + result = true; + } + } + } + } + } + + // select self only in orbit cam + if (!shift_down && CameraDirector::GetCameraMode() == CameraDirector::MODE_ORBIT) { + Point test_loc = ship->Location(); + projector->Transform(test_loc); + + if (test_loc.z > 1) { + projector->Project(test_loc); + + if (rect.Contains((int) test_loc.x, (int) test_loc.y)) { + sim->AddSelection(ship); + result = true; + } + } + } + + return result; +} + +// +--------------------------------------------------------------------+ + +Ship* +TacticalView::WillSelectAt(int x, int y) +{ + Ship* selection = 0; + + if (ship) { + // check distance to each contact: + List& contact_list = ship->ContactList(); + + for (int i = 0; i < ship->NumContacts(); i++) { + Ship* test = contact_list[i]->GetShip(); + + if (test) { + // shift-select targets: + if (shift_down) { + if (test->GetIFF() == 0 || test->GetIFF() == ship->GetIFF()) + continue; + } + + Graphic* g = test->Rep(); + if (g) { + Rect r = g->ScreenRect(); + + if (r.x == 2000 && r.y == 2000 && r.w == 0 && r.h == 0) { + if (projector) { + Point loc = test->Location(); + projector->Transform(loc); + projector->Project(loc); + + r.x = (int) loc.x; + r.y = (int) loc.y; + } + } + + if (r.w < 20 || r.h < 20) + r.Inflate(20,20); + else + r.Inflate(10,10); + + if (r.Contains(x,y)) { + selection = test; + break; + } + } + } + } + + if (!selection && !shift_down) { + Graphic* g = ship->Rep(); + if (g) { + Rect r = g->ScreenRect(); + + if (r.Contains(x,y)) { + selection = ship; + } + } + } + } + + if (selection == ship && CameraDirector::GetCameraMode() != CameraDirector::MODE_ORBIT) + selection = 0; + + return selection; +} + +// +--------------------------------------------------------------------+ + +void +TacticalView::SetHelm(bool approach) +{ + Point delta; + + // double-click on ship: set helm to approach + if (sim && approach) { + ListIter iter = sim->GetSelection(); + ++iter; + Ship* selection = iter.value(); + + if (selection != ship) { + delta = selection->Location() - ship->Location(); + delta.Normalize(); + } + } + + // double-click on space: set helm in direction + if (delta.length() < 1) { + int mx = Mouse::X(); + int my = Mouse::Y(); + + if (projector) { + double focal_dist = width / tan(projector->XAngle()); + + delta = projector->vpn() * focal_dist + + projector->vup() * -1 * (my-height/2) + + projector->vrt() * (mx-width/2); + + delta.Normalize(); + } + + else { + return; + } + } + + double az = atan2(fabs(delta.x), delta.z); + double el = asin(delta.y); + + if (delta.x < 0) + az *= -1; + + az += PI; + + if (az >= 2*PI) + az -= 2*PI; + + ship->SetHelmHeading(az); + ship->SetHelmPitch(el); +} + +// +====================================================================+ +// +// TACTICAL COMMUNICATIONS MENU: +// + +static Menu* main_menu = 0; +static Menu* view_menu = 0; +static Menu* emcon_menu = 0; + +static Menu* fighter_menu = 0; +static Menu* starship_menu = 0; +static Menu* action_menu = 0; +static Menu* formation_menu = 0; +static Menu* sensors_menu = 0; +static Menu* quantum_menu = 0; +static Menu* farcast_menu = 0; + +static Element* dst_elem = 0; + +enum VIEW_MENU { + VIEW_FORWARD = 1000, + VIEW_CHASE, + VIEW_PADLOCK, + VIEW_ORBIT, + VIEW_NAV, + VIEW_WEP, + VIEW_ENG, + VIEW_FLT, + VIEW_INS, + VIEW_CMD +}; + +const int QUANTUM = 2000; +const int FARCAST = 2001; + +void +TacticalView::Initialize() +{ + static int initialized = 0; + if (initialized) return; + + view_menu = new(__FILE__,__LINE__) Menu(Game::GetText("TacView.menu.view")); + view_menu->AddItem(Game::GetText("TacView.item.forward"), VIEW_FORWARD); + view_menu->AddItem(Game::GetText("TacView.item.chase"), VIEW_CHASE); + view_menu->AddItem(Game::GetText("TacView.item.orbit"), VIEW_ORBIT); + view_menu->AddItem(Game::GetText("TacView.item.padlock"), VIEW_PADLOCK); + + emcon_menu = new(__FILE__,__LINE__) Menu(Game::GetText("TacView.menu.emcon")); + + quantum_menu = new(__FILE__,__LINE__) Menu(Game::GetText("TacView.menu.quantum")); + farcast_menu = new(__FILE__,__LINE__) Menu(Game::GetText("TacView.menu.farcast")); + + main_menu = new(__FILE__,__LINE__) Menu(Game::GetText("TacView.menu.main")); + + action_menu = new(__FILE__,__LINE__) Menu(Game::GetText("TacView.menu.action")); + action_menu->AddItem(Game::GetText("TacView.item.engage"), RadioMessage::ATTACK); + action_menu->AddItem(Game::GetText("TacView.item.bracket"), RadioMessage::BRACKET); + action_menu->AddItem(Game::GetText("TacView.item.escort"), RadioMessage::ESCORT); + action_menu->AddItem(Game::GetText("TacView.item.identify"), RadioMessage::IDENTIFY); + action_menu->AddItem(Game::GetText("TacView.item.hold"), RadioMessage::WEP_HOLD); + + formation_menu = new(__FILE__,__LINE__) Menu(Game::GetText("TacView.menu.formation")); + formation_menu->AddItem(Game::GetText("TacView.item.diamond"), RadioMessage::GO_DIAMOND); + formation_menu->AddItem(Game::GetText("TacView.item.spread"), RadioMessage::GO_SPREAD); + formation_menu->AddItem(Game::GetText("TacView.item.box"), RadioMessage::GO_BOX); + formation_menu->AddItem(Game::GetText("TacView.item.trail"), RadioMessage::GO_TRAIL); + + sensors_menu = new(__FILE__,__LINE__) Menu(Game::GetText("TacView.menu.emcon")); + sensors_menu->AddItem(Game::GetText("TacView.item.emcon-1"), RadioMessage::GO_EMCON1); + sensors_menu->AddItem(Game::GetText("TacView.item.emcon-2"), RadioMessage::GO_EMCON2); + sensors_menu->AddItem(Game::GetText("TacView.item.emcon-3"), RadioMessage::GO_EMCON3); + sensors_menu->AddItem(Game::GetText("TacView.item.probe"), RadioMessage::LAUNCH_PROBE); + + fighter_menu = new(__FILE__,__LINE__) Menu(Game::GetText("TacView.menu.context")); + fighter_menu->AddMenu(Game::GetText("TacView.item.action"), action_menu); + fighter_menu->AddMenu(Game::GetText("TacView.item.formation"), formation_menu); + fighter_menu->AddMenu(Game::GetText("TacView.item.sensors"), sensors_menu); + fighter_menu->AddItem(Game::GetText("TacView.item.patrol"), RadioMessage::MOVE_PATROL); + fighter_menu->AddItem(Game::GetText("TacView.item.cancel"), RadioMessage::RESUME_MISSION); + fighter_menu->AddItem("", 0); + fighter_menu->AddItem(Game::GetText("TacView.item.rtb"), RadioMessage::RTB); + fighter_menu->AddItem(Game::GetText("TacView.item.dock"), RadioMessage::DOCK_WITH); + fighter_menu->AddMenu(Game::GetText("TacView.item.farcast"), farcast_menu); + + starship_menu = new(__FILE__,__LINE__) Menu(Game::GetText("TacView.menu.context")); + starship_menu->AddMenu(Game::GetText("TacView.item.action"), action_menu); + starship_menu->AddMenu(Game::GetText("TacView.item.sensors"), sensors_menu); + starship_menu->AddItem(Game::GetText("TacView.item.patrol"), RadioMessage::MOVE_PATROL); + starship_menu->AddItem(Game::GetText("TacView.item.cancel"), RadioMessage::RESUME_MISSION); + starship_menu->AddItem("", 0); + starship_menu->AddMenu(Game::GetText("TacView.item.quantum"), quantum_menu); + starship_menu->AddMenu(Game::GetText("TacView.item.farcast"), farcast_menu); + + initialized = 1; +} + +void +TacticalView::Close() +{ + delete view_menu; + delete emcon_menu; + delete main_menu; + delete fighter_menu; + delete starship_menu; + delete action_menu; + delete formation_menu; + delete sensors_menu; + delete quantum_menu; + delete farcast_menu; +} + +// +--------------------------------------------------------------------+ + +void +TacticalView::ProcessMenuItem(int action) +{ + Starshatter* stars = Starshatter::GetInstance(); + + switch (action) { + case RadioMessage::MOVE_PATROL: + show_move = true; + base_alt = 0; + move_alt = 0; + + if (msg_ship) base_alt = msg_ship->Location().y; + break; + + case RadioMessage::ATTACK: + case RadioMessage::BRACKET: + case RadioMessage::ESCORT: + case RadioMessage::IDENTIFY: + case RadioMessage::DOCK_WITH: + show_action = action; + break; + + case RadioMessage::WEP_HOLD: + case RadioMessage::RESUME_MISSION: + case RadioMessage::RTB: + case RadioMessage::GO_DIAMOND: + case RadioMessage::GO_SPREAD: + case RadioMessage::GO_BOX: + case RadioMessage::GO_TRAIL: + case RadioMessage::GO_EMCON1: + case RadioMessage::GO_EMCON2: + case RadioMessage::GO_EMCON3: + case RadioMessage::LAUNCH_PROBE: + if (msg_ship) { + Element* elem = msg_ship->GetElement(); + RadioMessage* msg = new(__FILE__,__LINE__) RadioMessage(elem, ship, action); + if (msg) + RadioTraffic::Transmit(msg); + } + else if (ship) { + if (action == RadioMessage::GO_EMCON1) + ship->SetEMCON(1); + else if (action == RadioMessage::GO_EMCON2) + ship->SetEMCON(2); + else if (action == RadioMessage::GO_EMCON3) + ship->SetEMCON(3); + else if (action == RadioMessage::LAUNCH_PROBE) + ship->LaunchProbe(); + } + break; + + case VIEW_FORWARD: stars->PlayerCam(CameraDirector::MODE_COCKPIT); break; + case VIEW_CHASE: stars->PlayerCam(CameraDirector::MODE_CHASE); break; + case VIEW_PADLOCK: stars->PlayerCam(CameraDirector::MODE_TARGET); break; + case VIEW_ORBIT: stars->PlayerCam(CameraDirector::MODE_ORBIT); break; + + case VIEW_NAV: gamescreen->ShowNavDlg(); break; + case VIEW_WEP: gamescreen->ShowWeaponsOverlay(); break; + case VIEW_ENG: gamescreen->ShowEngDlg(); break; + case VIEW_INS: HUDView::GetInstance()->CycleHUDInst(); break; + case VIEW_FLT: gamescreen->ShowFltDlg(); break; + + case VIEW_CMD: if (ship && ship->IsStarship()) { + ship->CommandMode(); + } + break; + + case QUANTUM: if (sim) { + Ship* s = msg_ship; + + if (!s) + s = ship; + + if (s && s->GetQuantumDrive()) { + QuantumDrive* quantum = s->GetQuantumDrive(); + if (quantum) { + MenuItem* menu_item = menu_view->GetMenuItem(); + Text rgn_name = menu_item->GetText(); + SimRegion* rgn = sim->FindRegion(rgn_name); + + if (rgn) { + if (s == ship) { + quantum->SetDestination(rgn, Point(0,0,0)); + quantum->Engage(); + } + + else { + Element* elem = msg_ship->GetElement(); + RadioMessage* msg = new(__FILE__,__LINE__) RadioMessage(elem, ship, RadioMessage::QUANTUM_TO); + if (msg) { + msg->SetInfo(rgn_name); + RadioTraffic::Transmit(msg); + } + } + } + } + } + } + break; + + case FARCAST: if (sim && msg_ship) { + MenuItem* menu_item = menu_view->GetMenuItem(); + Text rgn_name = menu_item->GetText(); + SimRegion* rgn = sim->FindRegion(rgn_name); + + if (rgn) { + Element* elem = msg_ship->GetElement(); + RadioMessage* msg = new(__FILE__,__LINE__) RadioMessage(elem, ship, RadioMessage::FARCAST_TO); + if (msg) { + msg->SetInfo(rgn_name); + RadioTraffic::Transmit(msg); + } + } + } + break; + + default: + break; + } +} + +// +--------------------------------------------------------------------+ + +void +TacticalView::BuildMenu() +{ + main_menu->ClearItems(); + quantum_menu->ClearItems(); + farcast_menu->ClearItems(); + emcon_menu->ClearItems(); + + if (!ship) + return; + + // prepare quantum and farcast menus: + ListIter iter = sim->GetRegions(); + while (++iter) { + SimRegion* rgn = iter.value(); + if (rgn != ship->GetRegion() && rgn->Type() != SimRegion::AIR_SPACE) + quantum_menu->AddItem(rgn->Name(), QUANTUM); + } + + if (ship->GetRegion()) { + ListIter iter = ship->GetRegion()->Ships(); + while (++iter) { + Ship* s = iter.value(); + + if (s && s->GetFarcaster()) { + Farcaster* farcaster = s->GetFarcaster(); + + // ensure that the farcaster is connected: + farcaster->ExecFrame(0); + + // now find the destination + const Ship* dest = farcaster->GetDest(); + + if (dest && dest->GetRegion()) { + SimRegion* rgn = dest->GetRegion(); + farcast_menu->AddItem(rgn->Name(), FARCAST); + } + } + } + } + + // build the main menu: + main_menu->AddMenu(Game::GetText("TacView.item.camera"), view_menu); + main_menu->AddItem("", 0); + main_menu->AddItem(Game::GetText("TacView.item.instructions"), VIEW_INS); + main_menu->AddItem(Game::GetText("TacView.item.navigation"), VIEW_NAV); + + if (ship->Design()->repair_screen) + main_menu->AddItem(Game::GetText("TacView.item.engineering"), VIEW_ENG); + + if (ship->Design()->wep_screen) + main_menu->AddItem(Game::GetText("TacView.item.weapons"), VIEW_WEP); + + if (ship->NumFlightDecks() > 0) + main_menu->AddItem(Game::GetText("TacView.item.flight"), VIEW_FLT); + + emcon_menu->AddItem(Game::GetText("TacView.item.emcon-1"), RadioMessage::GO_EMCON1); + emcon_menu->AddItem(Game::GetText("TacView.item.emcon-2"), RadioMessage::GO_EMCON2); + emcon_menu->AddItem(Game::GetText("TacView.item.emcon-3"), RadioMessage::GO_EMCON3); + + if (ship->GetProbeLauncher()) + emcon_menu->AddItem(Game::GetText("TacView.item.probe"), RadioMessage::LAUNCH_PROBE); + + main_menu->AddItem("", 0); + main_menu->AddMenu(Game::GetText("TacView.item.sensors"), emcon_menu); + + if (sim && ship->GetQuantumDrive()) { + main_menu->AddItem("", 0); + main_menu->AddMenu(Game::GetText("TacView.item.quantum"), quantum_menu); + } + + if (ship->IsStarship()) { + main_menu->AddItem("", 0); + main_menu->AddItem(Game::GetText("TacView.item.command"), VIEW_CMD); + } +} + +// +--------------------------------------------------------------------+ + +void +TacticalView::DrawMenu() +{ + active_menu = 0; + + if (ship) + active_menu = main_menu; + + if (msg_ship) { + if (msg_ship->IsStarship()) + active_menu = starship_menu; + else if (msg_ship->IsDropship()) + active_menu = fighter_menu; + } + + if (menu_view) { + menu_view->SetBackColor(hud_color); + menu_view->SetTextColor(txt_color); + menu_view->SetMenu(active_menu); + menu_view->Refresh(); + } +} + +// +--------------------------------------------------------------------+ + +bool +TacticalView::GetMouseLoc3D() +{ + int mx = Mouse::X(); + int my = Mouse::Y(); + + if (projector) { + double focal_dist = width / tan(projector->XAngle()); + Point focal_vect = projector->vpn() * focal_dist + + projector->vup() * -1 * (my-height/2) + + projector->vrt() * (mx-width/2); + + focal_vect.Normalize(); + + if (Keyboard::KeyDown(VK_SHIFT)) { + if (Mouse::RButton()) + return true; + + if (fabs(focal_vect.x) > fabs(focal_vect.z)) { + double dx = move_loc.x - projector->Pos().x; + double t = -1 * ((projector->Pos().x - dx) / focal_vect.x); + + if (t > 0) { + Point p = projector->Pos() + focal_vect * t; + move_alt = p.y - base_alt; + } + } + else { + double dz = move_loc.z - projector->Pos().z; + double t = -1 * ((projector->Pos().z - dz) / focal_vect.z); + Point p = projector->Pos() + focal_vect * t; + + if (t > 0) { + Point p = projector->Pos() + focal_vect * t; + move_alt = p.y - base_alt; + } + } + + if (move_alt > 25e3) + move_alt = 25e3; + else if (move_alt < -25e3) + move_alt = -25e3; + + return true; + } + else { + if (fabs(focal_vect.y) > 1e-5) { + if (Mouse::RButton()) + return true; + + bool clamp = false; + double t = -1 * ((projector->Pos().y - base_alt) / focal_vect.y); + + while (t <= 0 && my < height-1) { + my++; + clamp = true; + + focal_vect = projector->vpn() * focal_dist + + projector->vup() * -1 * (my-height/2) + + projector->vrt() * (mx-width/2); + + focal_vect.Normalize(); + t = -1 * ((projector->Pos().y - base_alt) / focal_vect.y); + } + + if (t > 0) { + if (clamp) + Mouse::SetCursorPos(mx, my); + + move_loc = projector->Pos() + focal_vect * t; + } + + return true; + } + } + } + + return false; +} + +void +TacticalView::DrawMove() +{ + if (!projector || !show_move || !msg_ship) return; + + Point origin = msg_ship->Location(); + + if (GetMouseLoc3D()) { + Point dest = move_loc; + + double distance = (dest - origin).length(); + + projector->Transform(origin); + projector->Project(origin); + + int x0 = (int) origin.x; + int y0 = (int) origin.y; + + projector->Transform(dest); + projector->Project(dest); + + int x = (int) dest.x; + int y = (int) dest.y; + + window->DrawEllipse(x-10, y-10, x+10, y+10, Color::White); + window->DrawLine(x0, y0, x, y, Color::White); + + char range[32]; + Rect range_rect(x+12, y-8, 120, 20); + + if (fabs(move_alt) > 1) { + dest = move_loc; + dest.y += move_alt; + distance = (dest - msg_ship->Location()).length(); + + projector->Transform(dest); + projector->Project(dest); + + int x1 = (int) dest.x; + int y1 = (int) dest.y; + + window->DrawEllipse(x1-10, y1-10, x1+10, y1+10, Color::White); + window->DrawLine(x0, y0, x1, y1, Color::White); + window->DrawLine(x1, y1, x, y, Color::White); + + range_rect.x = x1+12; + range_rect.y = y1-8; + } + + FormatNumber(range, distance); + font->SetColor(Color::White); + font->DrawText(range, 0, range_rect, DT_LEFT | DT_SINGLELINE); + font->SetColor(txt_color); + } +} + +void +TacticalView::SendMove() +{ + if (!projector || !show_move || !msg_ship) return; + + if (GetMouseLoc3D()) { + Element* elem = msg_ship->GetElement(); + RadioMessage* msg = new(__FILE__,__LINE__) RadioMessage(elem, ship, RadioMessage::MOVE_PATROL); + Point dest = move_loc; + dest.y += move_alt; + msg->SetLocation(dest); + RadioTraffic::Transmit(msg); + HUDSounds::PlaySound(HUDSounds::SND_TAC_ACCEPT); + } +} + +// +--------------------------------------------------------------------+ + +static int invalid_action = false; + +void +TacticalView::DrawAction() +{ + if (!projector || !show_action || !msg_ship) return; + + Point origin = msg_ship->Location(); + projector->Transform(origin); + projector->Project(origin); + + int x0 = (int) origin.x; + int y0 = (int) origin.y; + + int mx = mouse_action.x; + int my = mouse_action.y; + int r = 10; + + int enemy = 2; + if (ship->GetIFF() > 1) + enemy = 1; + + Ship* tgt = WillSelectAt(mx, my); + int tgt_iff = 0; + + if (tgt) + tgt_iff = tgt->GetIFF(); + + Color c = Color::White; + + switch (show_action) { + case RadioMessage::ATTACK: + case RadioMessage::BRACKET: + c = Ship::IFFColor(enemy); + if (tgt) { + if (tgt_iff == ship->GetIFF() || tgt_iff == 0) + r = 0; + } + break; + + case RadioMessage::ESCORT: + case RadioMessage::DOCK_WITH: + c = ship->MarkerColor(); + if (tgt) { + if (tgt_iff == enemy) + r = 0; + + // must have a hangar to dock with... + if (show_action == RadioMessage::DOCK_WITH && tgt->GetHangar() == 0) + r = 0; + } + break; + + default: + if (tgt) { + if (tgt_iff == ship->GetIFF()) + r = 0; + } + break; + } + + if (tgt && r) { + if ((Game::RealTime()/200) & 1) + r = 20; + else + r = 15; + } + + if (r) { + invalid_action = false; + window->DrawEllipse(mx-r, my-r, mx+r, my+r, c); + } + + else { + invalid_action = true; + window->DrawLine(mx-10, my-10, mx+10, my+10, c); + window->DrawLine(mx+10, my-10, mx-10, my+10, c); + } + + window->DrawLine(x0, y0, mx, my, c); +} + +void +TacticalView::SendAction() +{ + if (!show_action || !msg_ship || invalid_action) { + HUDSounds::PlaySound(HUDSounds::SND_TAC_REJECT); + return; + } + + int mx = mouse_action.x; + int my = mouse_action.y; + + Ship* tgt = WillSelectAt(mx, my); + + if (tgt) { + Element* elem = msg_ship->GetElement(); + RadioMessage* msg = new(__FILE__,__LINE__) RadioMessage(elem, ship, show_action); + + /*** + Element* tgt_elem = tgt->GetElement(); + + if (tgt_elem) { + for (int i = 1; i <= tgt_elem->NumShips(); i++) + msg->AddTarget(tgt_elem->GetShip(i)); + } + else { + msg->AddTarget(tgt); + } + ***/ + + msg->AddTarget(tgt); + + RadioTraffic::Transmit(msg); + HUDSounds::PlaySound(HUDSounds::SND_TAC_ACCEPT); + } + else { + HUDSounds::PlaySound(HUDSounds::SND_TAC_REJECT); + } +} + + diff --git a/Stars45/TacticalView.h b/Stars45/TacticalView.h new file mode 100644 index 0000000..7b09a32 --- /dev/null +++ b/Stars45/TacticalView.h @@ -0,0 +1,120 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: TacticalView.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + View class for Radio Communications HUD Overlay +*/ + +#ifndef TacticalView_h +#define TacticalView_h + +#include "Types.h" +#include "View.h" +#include "Color.h" +#include "SimObject.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class Font; +class Ship; +class RadioMessage; +class CameraView; +class Projector; +class HUDView; +class Menu; +class MenuItem; +class MenuView; +class GameScreen; + +// +--------------------------------------------------------------------+ + +class TacticalView : public View, + public SimObserver +{ +public: + TacticalView(Window* c, GameScreen* parent); + virtual ~TacticalView(); + + // Operations: + virtual void Refresh(); + virtual void OnWindowMove(); + virtual void ExecFrame(); + virtual void UseProjector(Projector* p); + + virtual void DoMouseFrame(); + + virtual bool Update(SimObject* obj); + virtual const char* GetObserverName() const; + + static void SetColor(Color c); + + static void Initialize(); + static void Close(); + + static TacticalView* GetInstance() { return tac_view; } + +protected: + virtual bool SelectAt(int x, int y); + virtual bool SelectRect(const Rect& r); + virtual Ship* WillSelectAt(int x, int y); + virtual void SetHelm(bool approach); + + virtual void DrawMouseRect(); + virtual void DrawSelection(Ship* seln); + virtual void DrawSelectionInfo(Ship* seln); + virtual void DrawSelectionList(ListIter seln); + + virtual void BuildMenu(); + virtual void DrawMenu(); + virtual void ProcessMenuItem(int action); + + virtual void DrawMove(); + virtual void SendMove(); + virtual bool GetMouseLoc3D(); + + virtual void DrawAction(); + virtual void SendAction(); + + GameScreen* gamescreen; + CameraView* camview; + Projector* projector; + + int width, height; + double xcenter, ycenter; + + int shift_down; + int mouse_down; + int right_down; + int show_move; + int show_action; + + Point move_loc; + double base_alt; + double move_alt; + + POINT mouse_action; + POINT mouse_start; + Rect mouse_rect; + + Font* font; + Sim* sim; + Ship* ship; + Ship* msg_ship; + Text current_sector; + + Menu* active_menu; + MenuView* menu_view; + + static TacticalView* tac_view; +}; + +#endif TacticalView_h + diff --git a/Stars45/Terrain.cpp b/Stars45/Terrain.cpp new file mode 100644 index 0000000..7a0251c --- /dev/null +++ b/Stars45/Terrain.cpp @@ -0,0 +1,561 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2005. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Terrain.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== +*/ + +#include "MemDebug.h" +#include "Terrain.h" +#include "TerrainApron.h" +#include "TerrainClouds.h" +#include "TerrainLayer.h" +#include "TerrainPatch.h" +#include "TerrainRegion.h" +#include "Water.h" + +#include "CameraView.h" +#include "Projector.h" +#include "Scene.h" +#include "Bitmap.h" +#include "DataLoader.h" +#include "Game.h" +#include "MachineInfo.h" + +// +--------------------------------------------------------------------+ + +int Terrain::detail_level = 3; // default = MEDIUM DETAIL + +const int PATCH_SIZE = 16; + +// +--------------------------------------------------------------------+ + +Terrain::Terrain(TerrainRegion* trgn) + : region(trgn), patches(0), water_patches(0), water(0), + aprons(0), clouds(0), terrain_normals(0) +{ + detail_frame = 0; + datapath = "Galaxy/"; + terrain_texture = 0; + apron_texture = 0; + water_texture = 0; + + for (int i = 0; i < 2; i++) { + cloud_texture[i] = 0; + shade_texture[i] = 0; + noise_texture[i] = 0; + } + + if (region) { + scale = region->LateralScale(); + mtnscale = region->MountainScale(); + + ListIter iter = region->GetLayers(); + while (++iter) { + TerrainLayer* orig = iter.value(); + TerrainLayer* copy = new(__FILE__,__LINE__) TerrainLayer; + *copy = *orig; + layers.append(copy); + } + + layers.sort(); + } + + else { + scale = 1; + mtnscale = 1; + } +} + +// +--------------------------------------------------------------------+ + +Terrain::~Terrain() +{ + int i, j; + + if (patches) + for (i = 0; i < subdivisions; i++) + for (j = 0; j < subdivisions; j++) + GRAPHIC_DESTROY(patches[i*subdivisions+j]); + + if (water_patches) + for (i = 0; i < subdivisions; i++) + for (j = 0; j < subdivisions; j++) + GRAPHIC_DESTROY(water_patches[i*subdivisions+j]); + + if (aprons) + for (i = 0; i < 8; i++) + GRAPHIC_DESTROY(aprons[i]); + + if (clouds) + for (i = 0; i < nclouds; i++) + GRAPHIC_DESTROY(clouds[i]); + + if (water) + for (i = 0; i < 6; i++) + delete water[i]; + + delete [] aprons; + delete [] clouds; + delete [] patches; + delete [] water; + delete [] terrain_normals; + + terrain_patch.ClearImage(); + terrain_apron.ClearImage(); + + layers.destroy(); +} + +// +--------------------------------------------------------------------+ + +void +Terrain::BuildTerrain() +{ + DataLoader* loader = DataLoader::GetLoader(); + loader->SetDataPath(datapath); + loader->LoadBitmap( region->PatchName(), terrain_patch); + loader->LoadBitmap( region->ApronName(), terrain_apron); + loader->LoadTexture(region->PatchTexture(), terrain_texture); + loader->LoadTexture(region->ApronTexture(), apron_texture); + if (region->WaterTexture().length()) { + loader->LoadTexture(region->WaterTexture(), water_texture); + + if (region->EnvironmentTexture(0).length() > 0) { + loader->LoadTexture(region->EnvironmentTexture(0), env_texture[0]); + loader->LoadTexture(region->EnvironmentTexture(1), env_texture[1]); + loader->LoadTexture(region->EnvironmentTexture(2), env_texture[2]); + loader->LoadTexture(region->EnvironmentTexture(3), env_texture[3]); + loader->LoadTexture(region->EnvironmentTexture(4), env_texture[4]); + loader->LoadTexture(region->EnvironmentTexture(5), env_texture[5]); + } + } + + loader->LoadTexture(region->CloudsHigh(), cloud_texture[0], Bitmap::BMP_TRANSLUCENT); + loader->LoadTexture(region->CloudsLow(), cloud_texture[1], Bitmap::BMP_TRANSLUCENT); + loader->LoadTexture(region->ShadesLow(), shade_texture[1], Bitmap::BMP_TRANSLUCENT); + + if (region->DetailTexture0().length()) + loader->LoadTexture(region->DetailTexture0(),noise_texture[0], Bitmap::BMP_TRANSLUCENT, false, true); + + if (region->DetailTexture1().length()) + loader->LoadTexture(region->DetailTexture1(),noise_texture[1], Bitmap::BMP_TRANSLUCENT, false, true); + + subdivisions = terrain_patch.Width() / PATCH_SIZE; + patch_size = terrain_patch.Width() / subdivisions; + + BuildNormals(); + + int i, j; + int ntiles = terrain_patch.Width()/2 * terrain_patch.Height()/2; + double dx = scale * patch_size; + double dz = scale * patch_size; + double offset = -subdivisions/2; + + if (water_texture) { + water = new(__FILE__,__LINE__) Water*[6]; + for (i = 0; i < 6; i++) { + water[i] = new(__FILE__,__LINE__) Water(); + int n = (1<Init(n, (float) (scale*patch_size), 90.0f); + } + } + + // load tile textures: + for (i = 0; i < layers.size(); i++) { + TerrainLayer* layer = layers.at(i); + + if (i < layers.size()-1) + layer->max_height = layers.at(i+1)->min_height; + else + layer->max_height = 1e6; + + if (layer->tile_name.length()) + loader->LoadTexture(layer->tile_name, layer->tile_texture); + + if (layer->detail_name.length()) + loader->LoadTexture(layer->detail_name, layer->detail_texture); + } + + patches = new(__FILE__,__LINE__) TerrainPatch*[subdivisions*subdivisions]; + + if (water_texture) + water_patches = new(__FILE__,__LINE__) TerrainPatch*[subdivisions*subdivisions]; + + for (i = 0; i < subdivisions; i++) { + for (j = 0; j < subdivisions; j++) { + int j1 = subdivisions - j; + Rect rect(j * patch_size, i * patch_size, patch_size, patch_size); + Point p1((j1 + offset )*dx, 0, (i + offset )*dz); + Point p2((j1 + offset+1)*dx, mtnscale, (i + offset+1)*dz); + + int index = i*subdivisions+j; + patches[index] = new(__FILE__,__LINE__) TerrainPatch(this, &terrain_patch, rect, p1, p2); + + if (water_texture && patches[index]->MinHeight() < 3) + water_patches[index] = new(__FILE__,__LINE__) TerrainPatch(this, rect, p1, p2, 30); + + else if (water_patches != 0) + water_patches[index] = 0; + } + } + + int a = 0; + dx = scale * terrain_patch.Width(); + dz = scale * terrain_patch.Height(); + offset = -3.0/2; + double xoffset = offset + 1.0/16.0; + + aprons = new(__FILE__,__LINE__) TerrainApron*[8]; + + for (i = 0; i < 3; i++) { + for (j = 0; j < 3; j++) { + int j1 = 2 - j; + if (i != 1 || j1 != 1) { + Rect rect(j * subdivisions, i * subdivisions, subdivisions, subdivisions); + Point p1((j1 + xoffset )*dx, 0, (i + offset )*dz); + Point p2((j1 + xoffset+1)*dx, mtnscale, (i + offset+1)*dz); + + aprons[a++] = new(__FILE__,__LINE__) TerrainApron(this, &terrain_apron, rect, p1, p2); + } + } + } + + Weather::STATE state = region->GetWeather().State(); + + if (state == Weather::HIGH_CLOUDS || state == Weather::MODERATE_CLOUDS) { + double altitude = region->CloudAltHigh(); + nclouds = 9; + + if (state == Weather::MODERATE_CLOUDS) + nclouds *= 2; + + clouds = new(__FILE__,__LINE__) TerrainClouds*[nclouds]; + + a = 0; + for (i = 0; i < 3; i++) { + for (j = 0; j < 3; j++) { + clouds[a] = new(__FILE__,__LINE__) TerrainClouds(this, 0); + + double xloc = (j-1) * 75000 + (rand()/32768.0 - 0.5) * 50000; + double yloc = (i-1) * 75000 + (rand()/32768.0 - 0.5) * 50000; + + clouds[a]->MoveTo(Point(xloc, altitude, yloc)); + a++; + } + } + + if (state == Weather::MODERATE_CLOUDS) { + altitude = region->CloudAltLow(); + + for (i = 0; i < 3; i++) { + for (j = 0; j < 3; j++) { + clouds[a] = new(__FILE__,__LINE__) TerrainClouds(this, 1); + + double xloc = (j-1) * 75000 + (rand()/32768.0 - 0.5) * 50000; + double yloc = (i-1) * 75000 + (rand()/32768.0 - 0.5) * 50000; + + clouds[a]->MoveTo(Point(xloc, altitude, yloc)); + a++; + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +Terrain::BuildNormals() +{ + if (terrain_normals) { + delete [] terrain_normals; + terrain_normals = 0; + } + + int i, x, y; + + int w = terrain_patch.Width(); + int h = terrain_patch.Height(); + Color* pix = terrain_patch.HiPixels(); + BYTE* alt = new(__FILE__,__LINE__) BYTE[h*w]; + int nverts = w * h; + double scale = region->MountainScale() / (region->LateralScale() * 2.0); + + terrain_normals = new(__FILE__,__LINE__) Vec3B[nverts]; + + ZeroMemory(terrain_normals, sizeof(Vec3B) * nverts); + + for (i = 0; i < w; i++) { + alt [ i] = 0; + alt [(h-1)*w + i] = 0; + terrain_normals[ i] = Vec3B(128,128,255); + terrain_normals[(h-1)*w + i] = Vec3B(128,128,255); + } + + for (i = 0; i < h; i++) { + alt [i*w ] = 0; + alt [i*w + (w-1)] = 0; + terrain_normals[i*w ] = Vec3B(128,128,255); + terrain_normals[i*w + (w-1)] = Vec3B(128,128,255); + } + + for (y = 1; y < h-1; y++) { + for (x = 1; x < w-1; x++) { + alt[y*w+x] = (BYTE) pix[y*w+x].Red(); + } + } + + for (y = 1; y < h-1; y++) { + for (x = 1; x < w-1; x++) { + double dx = (alt[y*w + (x-1)] - alt[y*w + (x+1)]) * scale + + (alt[(y-1)*w + (x-1)] - alt[(y-1)*w + (x+1)]) * scale * 0.5 + + (alt[(y+1)*w + (x-1)] - alt[(y+1)*w + (x+1)]) * scale * 0.5; + + double dy = (alt[(y-1)*w + x ] - alt[(y+1)*w + x ]) * scale + + (alt[(y-1)*w + (x-1)] - alt[(y+1)*w + (x-1)]) * scale * 0.5 + + (alt[(y-1)*w + (x+1)] - alt[(y+1)*w + (x+1)]) * scale * 0.5; + + Point norm(dx,dy,1); + norm.Normalize(); + + Vec3B* tnorm = &terrain_normals[y*w + x]; + + tnorm->x = (BYTE) (norm.x * 127 + 128); + tnorm->y = (BYTE) (norm.y * 127 + 128); + tnorm->z = (BYTE) (norm.z * 127 + 128); + + double foo = dx/dy; + } + } + + delete [] alt; +} + +// +--------------------------------------------------------------------+ + +void +Terrain::Activate(Scene& scene) +{ + int i, j; + + StarSystem* system = region->System(); + if (system) + datapath = system->GetDataPath(); + + region->GetWeather().Update(); + + if (!patches) + BuildTerrain(); + + if (patches) { + for (i = 0; i < subdivisions; i++) + for (j = 0; j < subdivisions; j++) + if (patches[i*subdivisions+j]) + scene.AddGraphic(patches[i*subdivisions+j]); + } + + if (water_patches) { + for (i = 0; i < subdivisions; i++) + for (j = 0; j < subdivisions; j++) + if (water_patches[i*subdivisions+j]) + scene.AddGraphic(water_patches[i*subdivisions+j]); + } + + if (aprons) { + for (i = 0; i < 8; i++) + if (aprons[i]) + scene.AddGraphic(aprons[i]); + } + + if (clouds) { + for (i = 0; i < nclouds; i++) + if (clouds[i]) + scene.AddGraphic(clouds[i]); + } +} + +void +Terrain::Deactivate(Scene& scene) +{ + int i, j; + + if (patches) { + for (i = 0; i < subdivisions; i++) { + for (j = 0; j < subdivisions; j++) { + TerrainPatch* p = patches[i*subdivisions+j]; + + if (p) { + p->DeletePrivateData(); + scene.DelGraphic(p); + } + } + } + } + + if (water_patches) { + for (i = 0; i < subdivisions; i++) { + for (j = 0; j < subdivisions; j++) { + TerrainPatch* p = water_patches[i*subdivisions+j]; + + if (p) { + p->DeletePrivateData(); + scene.DelGraphic(p); + } + } + } + } + + if (aprons) { + for (i = 0; i < 8; i++) + if (aprons[i]) + scene.DelGraphic(aprons[i]); + } + + if (clouds) { + for (i = 0; i < nclouds; i++) + if (clouds[i]) + scene.DelGraphic(clouds[i]); + } + + StarSystem* system = region->System(); + + // restore sunlight color and brightness on exit: + if (system) { + system->RestoreTrueSunColor(); + } +} + +// +--------------------------------------------------------------------+ + +void +Terrain::ExecFrame(double seconds) +{ + if (water) { + for (int i = 0; i < 6; i++) { + if (water[i]) + water[i]->CalcWaves(seconds); + } + } +} + +// +--------------------------------------------------------------------+ + +void +Terrain::SelectDetail(Projector* projector) +{ + if (!patches) + return; + + if (detail_frame >= Game::Frame()) + return; + + // compute detail map: + int x, z; + + for (z = 0; z < subdivisions; z++) { + for (x = 0; x < subdivisions; x++) { + TerrainPatch* patch = patches[z*subdivisions + x]; + int ndetail = 0; + Point loc = patch->Location(); + float radius = patch->Radius(); + + if (loc.length() < 2*radius) { + ndetail = detail_level; + } + + else { + double threshold = 4; //16; + + for (int level = 1; level <= detail_level; level++) { + double feature_size = radius / (1 << level); + + if (projector->ApparentRadius(loc, (float) feature_size) > threshold) + ndetail = level; + } + } + + patch->SetDetailLevel(ndetail); + + if (water_patches) { + patch = water_patches[z*subdivisions + x]; + if (patch) + patch->SetDetailLevel(ndetail); + } + } + } + + // compute fog fade level: + double hour = region->DayPhase(); + + if (hour < 12) + fog_fade = (hour) / 12.0; + else + fog_fade = (24-hour) / 12.0; + + fog_fade = fog_fade * (1-region->HazeFade()) + region->HazeFade(); + + detail_frame = Game::Frame(); +} + +// +--------------------------------------------------------------------+ + +double +Terrain::Height(double x, double y) const +{ + double h = 0; + + if (patches) { + int ix = (int) floor(x / (patch_size * scale)); + int iy = (int) floor(y / (patch_size * scale)); + + double px = x - ix * patch_size * scale; + double py = y - iy * patch_size * scale; + + ix = subdivisions/2 - ix; + iy = subdivisions/2 + iy; + + TerrainPatch* patch = 0; + + if (ix >= 0 && ix < subdivisions && + iy >= 0 && iy < subdivisions) + patch = patches[iy*subdivisions+ix]; + + if (patch) + h = patch->Height(px, py); + } + + if (water_patches && h < 30) + h = 30; + + return h; +} + +// +--------------------------------------------------------------------+ + +void +Terrain::SetDetailLevel(int detail) +{ + if (detail >= 1 && detail <= 4) { + + // limit detail on low memory machines: + if (detail > 3 && MachineInfo::GetTotalRam() < 64) + detail = 3; + + detail_level = detail; + } +} + +// +--------------------------------------------------------------------+ + +bool +Terrain::IsFirstPatch(TerrainPatch* p) const +{ + return (patches && *patches == p); +} \ No newline at end of file diff --git a/Stars45/Terrain.h b/Stars45/Terrain.h new file mode 100644 index 0000000..036dab4 --- /dev/null +++ b/Stars45/Terrain.h @@ -0,0 +1,119 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2005. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Terrain.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Test pseudo-random terrain heightfield, based on Solid +*/ + +#ifndef Terrain_h +#define Terrain_h + +#include "Types.h" +#include "Graphic.h" +#include "Geometry.h" +#include "Bitmap.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class Projector; +class Scene; +class TerrainApron; +class TerrainClouds; +class TerrainLayer; +class TerrainPatch; +class TerrainRegion; +class Water; + +// +--------------------------------------------------------------------+ + +struct Vec3B +{ + Vec3B() { } + Vec3B(BYTE a, BYTE b, BYTE c) : x(a), y(b), z(c) { } + + BYTE x, y, z; +}; + +// +--------------------------------------------------------------------+ + +class Terrain +{ +public: + Terrain(TerrainRegion* region); + virtual ~Terrain(); + + virtual void Activate(Scene& scene); + virtual void Deactivate(Scene& scene); + + virtual void SelectDetail(Projector* proj); + virtual void BuildTerrain(); + virtual void BuildNormals(); + + virtual void ExecFrame(double seconds); + + double Height(double x, double y) const; + + const Vec3B* Normals() const { return terrain_normals; } + TerrainRegion* GetRegion() { return region; } + double FogFade() const { return fog_fade; } + + Bitmap* Texture() { return terrain_texture; } + Bitmap* ApronTexture() { return apron_texture; } + Bitmap* WaterTexture() { return water_texture; } + Bitmap** EnvironmentTexture() { return env_texture; } + Bitmap* TileTexture(int n) { return tiles[n]; } + Bitmap* CloudTexture(int n) { return cloud_texture[n]; } + Bitmap* ShadeTexture(int n) { return shade_texture[n]; } + Bitmap* DetailTexture(int n) { return noise_texture[n]; } + Water* GetWater(int level) { return water[level]; } + List& GetLayers() { return layers; } + + bool IsFirstPatch(TerrainPatch* p) const; + + static int DetailLevel() { return detail_level; } + static void SetDetailLevel(int detail); + +protected: + TerrainRegion* region; + TerrainPatch** patches; + TerrainPatch** water_patches; + Water** water; + TerrainApron** aprons; + TerrainClouds** clouds; + int nclouds; + + Bitmap terrain_patch; + Bitmap terrain_apron; + Bitmap* terrain_texture; + Bitmap* apron_texture; + Bitmap* water_texture; + Bitmap* env_texture[6]; + Bitmap* tiles[256]; + Bitmap* cloud_texture[2]; + Bitmap* shade_texture[2]; + Bitmap* noise_texture[2]; + + Vec3B* terrain_normals; + List layers; + + Text datapath; + double scale; + double mtnscale; + int subdivisions; + int patch_size; + DWORD detail_frame; + double fog_fade; + + static int detail_level; +}; + +#endif Terrain_h + diff --git a/Stars45/TerrainApron.cpp b/Stars45/TerrainApron.cpp new file mode 100644 index 0000000..24125ac --- /dev/null +++ b/Stars45/TerrainApron.cpp @@ -0,0 +1,336 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2005. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: TerrainApron.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== +*/ + +#include "MemDebug.h" +#include "Terrain.h" +#include "TerrainApron.h" +#include "TerrainRegion.h" + +#include "CameraView.h" +#include "Bitmap.h" +#include "DataLoader.h" +#include "Game.h" +#include "Light.h" +#include "Scene.h" + +// +====================================================================+ + +const int PATCH_SIZE = 17; +const int HALF_PATCH_SIZE = 8; +const int MAX_VERTS = PATCH_SIZE * PATCH_SIZE; +const int NUM_INDICES_TRI = 3; + +// +--------------------------------------------------------------------+ + +TerrainApron::TerrainApron(Terrain* terr,const Bitmap* patch, const Rect& r, + const Point& p1, const Point& p2) + : terrain(terr), rect(r) +{ + size = fabs(p2.x - p1.x); + scale = size / (PATCH_SIZE-1); + mtnscale = 1.3 * (p2.y - p1.y); + base = p1.y; + + terrain_width = patch->Width(); + + loc = (p1 + p2) * 0.5; + loc.y = base; + + radius = (float) (size * 0.75); + heights = new(__FILE__,__LINE__) float[MAX_VERTS]; + + float* pHeight = heights; + + int i, j; + + for (i = 0; i < PATCH_SIZE; i++) { + int ty = rect.y + i; + + if (ty < 0) + ty = 0; + + if (ty > patch->Height()-1) + ty = patch->Height()-1; + + for (j = 0; j < PATCH_SIZE; j++) { + int tx = rect.x + (PATCH_SIZE-1 - j); + + if (tx < 0) + tx = 0; + + if (tx > patch->Width()-1) + tx = patch->Width()-1; + + *pHeight++ = (float) (patch->GetColor(tx,ty).Red() * mtnscale); + } + } +} + +// +--------------------------------------------------------------------+ + +TerrainApron::~TerrainApron() +{ + delete [] heights; +} + +// +--------------------------------------------------------------------+ + +void +TerrainApron::SetScales(double s, double m, double b) +{ + scale = s; + mtnscale = m; + base = b; +} + +// +--------------------------------------------------------------------+ + +bool +TerrainApron::BuildApron() +{ + int i, j; + + int detail_size = PATCH_SIZE-1; + int ds1 = PATCH_SIZE; + int nverts = MAX_VERTS; + int npolys = detail_size * detail_size * 2; + + model = new(__FILE__,__LINE__) Model; + model->SetLuminous(true); + model->SetDynamic(true); + + Material* mtl = new(__FILE__,__LINE__) Material; + mtl->Ka = ColorValue(0.5f, 0.5f, 0.5f); + mtl->Kd = ColorValue(0.3f, 0.6f, 0.2f); + mtl->Ks = Color::Black; + + mtl->tex_diffuse = terrain->ApronTexture(); + strcpy(mtl->name, "Terrain Apron"); + + model->GetMaterials().append(mtl); + + Surface* s = new(__FILE__,__LINE__) Surface; + VertexSet* vset = 0; + + if (s) { + s->SetName("default"); + s->CreateVerts(nverts); + s->CreatePolys(npolys); + s->AddIndices(npolys*NUM_INDICES_TRI); + + vset = s->GetVertexSet(); + + ZeroMemory(vset->loc, nverts * sizeof(Vec3)); + ZeroMemory(vset->diffuse, nverts * sizeof(DWORD)); + ZeroMemory(vset->specular, nverts * sizeof(DWORD)); + ZeroMemory(vset->tu, nverts * sizeof(float)); + ZeroMemory(vset->tv, nverts * sizeof(float)); + ZeroMemory(vset->rw, nverts * sizeof(float)); + + + // initialize vertices + Vec3* pVert = vset->loc; + float* pTu = vset->tu; + float* pTv = vset->tv; + double dt = (1.0/3.0) / (double) detail_size; + double tu0 = (double) rect.x / rect.w / 3.0 + (1.0/3.0); + double tv0 = (double) rect.y / rect.h / 3.0; + + for (i = 0; i < ds1; i++) { + for (j = 0; j < ds1; j++) { + *pVert++ = Vec3((float) (j* scale - (HALF_PATCH_SIZE*scale)), + (float) (heights[i*PATCH_SIZE + j]), + (float) (i* scale - (HALF_PATCH_SIZE*scale))); + + *pTu++ = (float) (tu0 - j*dt); + *pTv++ = (float) (tv0 + i*dt); + } + } + + // create the polys + for (i = 0; i < npolys; i++) { + Poly* p = s->GetPolys() + i; + p->nverts = 3; + p->vertex_set = vset; + p->material = mtl; + p->visible = 1; + p->sortval = 0; + } + + int index = 0; + + // build main patch polys: + for (i = 0; i < detail_size; i++) { + for (j = 0; j < detail_size; j++) { + // first triangle + Poly* p = s->GetPolys() + index++; + p->verts[0] = (ds1 * (i ) + (j )); + p->verts[1] = (ds1 * (i ) + (j+1)); + p->verts[2] = (ds1 * (i+1) + (j+1)); + + // second triangle + p = s->GetPolys() + index++; + p->verts[0] = (ds1 * (i ) + (j )); + p->verts[1] = (ds1 * (i+1) + (j+1)); + p->verts[2] = (ds1 * (i+1) + (j )); + } + } + + // update the verts and colors of each poly: + for (i = 0; i < npolys; i++) { + Poly* p = s->GetPolys() + i; + Plane& plane = p->plane; + WORD* v = p->verts; + + plane = Plane(vset->loc[v[0]] + loc, + vset->loc[v[1]] + loc, + vset->loc[v[2]] + loc); + } + + // create continguous segments for each material: + s->Normalize(); + + Segment* segment = new(__FILE__,__LINE__) Segment(npolys, s->GetPolys(), mtl, model); + s->GetSegments().append(segment); + + model->AddSurface(s); + + + // copy vertex normals: + Vec3 normal = Vec3(0, 1, 0); + + for (i = 0; i < ds1; i++) { + for (j = 0; j < ds1; j++) { + vset->nrm[i*ds1+j] = normal; + } + } + } + + return true; +} + +// +--------------------------------------------------------------------+ + +int +TerrainApron::CollidesWith(Graphic& o) +{ + return 0; +} + +// +--------------------------------------------------------------------+ + +void +TerrainApron::Update() +{ +} + +// +--------------------------------------------------------------------+ + +void +TerrainApron::Illuminate(Color ambient, List& lights) +{ + if (!model || model->NumVerts() < 1) return; + Surface* s = model->GetSurfaces().first(); + if (!s) return; + + // clear the solid lights to ambient: + VertexSet* vset = s->GetVertexSet(); + int nverts = vset->nverts; + DWORD aval = ambient.Value(); + + for (int i = 0; i < nverts; i++) { + vset->diffuse[i] = aval; + } + + TerrainRegion* trgn = terrain->GetRegion(); + bool eclipsed = false; + + // for each light: + ListIter iter = lights; + while (++iter) { + Light* light = iter.value(); + + if (light->CastsShadow()) + eclipsed = light->Location().y < -100; + + if (!light->CastsShadow() || !eclipsed) { + Vec3 vl = light->Location(); + vl.Normalize(); + + for (i = 0; i < nverts; i++) { + Vec3& nrm = vset->nrm[i]; + double val = 0; + + if (light->IsDirectional()) { + double gain = vl * nrm; + + if (gain > 0) { + val = light->Intensity() * (0.85 * gain); + + if (val > 1) + val = 1; + } + } + + if (val > 0.01) + vset->diffuse[i] = ((light->GetColor().dim(val)) + vset->diffuse[i]).Value(); + } + } + } + + InvalidateSurfaceData(); +} + +// +--------------------------------------------------------------------+ + +void +TerrainApron::Render(Video* video, DWORD flags) +{ + if (!video || (flags & RENDER_ADDITIVE) || (flags & RENDER_ADD_LIGHT)) return; + + if (!model) + BuildApron(); + + if (scene) { + Illuminate(scene->Ambient(), scene->Lights()); + } + + double visibility = terrain->GetRegion()->GetWeather().Visibility(); + FLOAT fog_density = (FLOAT) (terrain->GetRegion()->FogDensity() * 2.5e-5 * 1/visibility); + + video->SetRenderState(Video::LIGHTING_ENABLE, false); + video->SetRenderState(Video::SPECULAR_ENABLE, false); + video->SetRenderState(Video::FOG_ENABLE, true); + video->SetRenderState(Video::FOG_COLOR, terrain->GetRegion()->FogColor().Value()); + video->SetRenderState(Video::FOG_DENSITY, *((DWORD*) &fog_density)); + + Solid::Render(video, flags); + + video->SetRenderState(Video::LIGHTING_ENABLE, true); + video->SetRenderState(Video::SPECULAR_ENABLE, true); + video->SetRenderState(Video::FOG_ENABLE, false); +} + +// +--------------------------------------------------------------------+ + +int +TerrainApron::CheckRayIntersection(Point Q, Point w, double len, Point& ipt, bool ttpas) +{ + // compute leading edge of ray: + Point sun = Q + w*len; + + if (sun.y < loc.y) + return 1; + + return 0; +} diff --git a/Stars45/TerrainApron.h b/Stars45/TerrainApron.h new file mode 100644 index 0000000..dd2e4fd --- /dev/null +++ b/Stars45/TerrainApron.h @@ -0,0 +1,71 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2005. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: TerrainApron.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + A Single Edge Section of a Terrain Object +*/ + +#ifndef TerrainApron_h +#define TerrainApron_h + +#include "Types.h" +#include "Solid.h" + +// +--------------------------------------------------------------------+ + +class Terrain; + +// +--------------------------------------------------------------------+ + +class TerrainApron : public Solid +{ +public: + TerrainApron(Terrain* terrain, + const Bitmap* patch, const Rect& rect, + const Point& p1, const Point& p2); + virtual ~TerrainApron(); + + virtual void Render(Video* video, DWORD flags); + virtual void Update(); + + virtual int CollidesWith(Graphic& o); + virtual bool Luminous() const { return false; } + virtual bool Translucent() const { return false; } + + // accessors: + double Scale() const { return scale; } + double MountainScale() const { return mtnscale; } + double SeaLevel() const { return base; } + void SetScales(double scale, double mtnscale, double base); + + void Illuminate(Color ambient, List& lights); + virtual int CheckRayIntersection(Point pt, Point vpn, double len, Point& ipt, + bool treat_translucent_polys_as_solid=true); + +protected: + virtual bool BuildApron(); + + Terrain* terrain; + int nverts; + int npolys; + int terrain_width; + + Rect rect; + float* heights; + + double scale; + double mtnscale; + double base; + double size; +}; + + +#endif TerrainApron_h + diff --git a/Stars45/TerrainClouds.cpp b/Stars45/TerrainClouds.cpp new file mode 100644 index 0000000..49f7113 --- /dev/null +++ b/Stars45/TerrainClouds.cpp @@ -0,0 +1,269 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2005. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: TerrainClouds.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== +*/ + +#include "MemDebug.h" +#include "TerrainClouds.h" +#include "Terrain.h" +#include "TerrainRegion.h" + +#include "Light.h" +#include "CameraView.h" +#include "Bitmap.h" +#include "DataLoader.h" +#include "Game.h" +#include "Fix.h" +#include "Scene.h" + +// +--------------------------------------------------------------------+ + +TerrainClouds::TerrainClouds(Terrain* terr, int t) + : terrain(terr), type(t) +{ + nverts = 0; + npolys = 0; + mverts = 0; + verts = 0; + polys = 0; + + loc = Point(0, 15000, 0); + radius = (float) (25000.0f); + + BuildClouds(); +} + +// +--------------------------------------------------------------------+ + +TerrainClouds::~TerrainClouds() +{ + delete [] mverts; + delete verts; + delete [] polys; +} + +// +--------------------------------------------------------------------+ + +static const double BANK_SIZE = 20000; +static const int CIRRUS_BANKS = 4; +static const int CUMULUS_BANKS = 4; + +void +TerrainClouds::BuildClouds() +{ + if (type == 0) { + nbanks = CIRRUS_BANKS; + nverts = 4 * nbanks; + npolys = 2 * nbanks; + } + else { + nbanks = CUMULUS_BANKS; + nverts = 8 * nbanks; + npolys = 3 * nbanks; + } + + Bitmap* cloud_texture = terrain->CloudTexture(type); + Bitmap* shade_texture = terrain->ShadeTexture(type); + + strcpy(mtl_cloud.name, "cloud"); + mtl_cloud.Ka = Color::White; + mtl_cloud.Kd = Color::White; + mtl_cloud.luminous = true; + mtl_cloud.blend = Material::MTL_TRANSLUCENT; + mtl_cloud.tex_diffuse = cloud_texture; + + strcpy(mtl_shade.name, "shade"); + mtl_shade.Ka = Color::White; + mtl_shade.Kd = Color::White; + mtl_shade.luminous = true; + mtl_shade.blend = Material::MTL_TRANSLUCENT; + mtl_shade.tex_diffuse = shade_texture; + + verts = new(__FILE__,__LINE__) VertexSet(nverts); + mverts = new(__FILE__,__LINE__) Vec3[nverts]; + polys = new(__FILE__,__LINE__) Poly[npolys]; + + verts->nverts = nverts; + + // initialize vertices + Vec3* pVert = mverts; + float* pTu = verts->tu; + float* pTv = verts->tv; + + int i, j, n; + double az = 0; + double r = 0; + + for (n = 0; n < nbanks; n++) { + double xloc = r * cos(az); + double yloc = r * sin(az); + double alt = rand() / 32.768; + + for (i = 0; i < 2; i++) { + for (j = 0; j < 2; j++) { + *pVert = Vec3((float) ((2*j-1) * BANK_SIZE + xloc), + (float) (alt), + (float) ((2*i-1) * BANK_SIZE + yloc)); + + *pTu++ = (float) (-j); + *pTv++ = (float) ( i); + + float dist = pVert->length(); + if (dist > radius) + radius = dist; + + pVert++; + } + } + + if (type > 0) { + for (i = 0; i < 2; i++) { + for (j = 0; j < 2; j++) { + *pVert = Vec3((float) ((2*j-1) * BANK_SIZE + xloc), + (float) (alt-100), + (float) ((2*i-1) * BANK_SIZE + yloc)); + + *pTu++ = (float) (-j); + *pTv++ = (float) ( i); + + float dist = pVert->length(); + if (dist > radius) + radius = dist; + + pVert++; + } + } + } + + az += (0.66 + rand()/32768.0) * 0.25 * PI; + + if (r < BANK_SIZE) + r += BANK_SIZE; + else if (r < 1.75*BANK_SIZE) + r += BANK_SIZE/4; + else + r += BANK_SIZE/8; + } + + // create the polys + for (i = 0; i < npolys; i++) { + Poly* p = polys + i; + p->nverts = 4; + p->vertex_set = verts; + p->material = (i<4*nbanks) ? &mtl_cloud : &mtl_shade; + p->visible = 1; + p->sortval = (i<4*nbanks) ? 1 : 2; + } + + // build main patch polys: (facing down) + Poly* p = polys; + + int stride = 4; + if (type > 0) + stride = 8; + + // clouds: + for (n = 0; n < nbanks; n++) { + p->verts[0] = 0 + n*stride; + p->verts[1] = 1 + n*stride; + p->verts[2] = 3 + n*stride; + p->verts[3] = 2 + n*stride; + p++; + + // reverse side: (facing up) + p->verts[0] = 0 + n*stride; + p->verts[3] = 1 + n*stride; + p->verts[2] = 3 + n*stride; + p->verts[1] = 2 + n*stride; + p++; + } + + // shades: + if (type > 0) { + for (n = 0; n < nbanks; n++) { + p->verts[0] = 4 + n*stride; + p->verts[1] = 5 + n*stride; + p->verts[2] = 7 + n*stride; + p->verts[3] = 6 + n*stride; + p++; + } + } + + // update the verts and colors of each poly: + for (i = 0; i < npolys; i++) { + Poly* p = polys + i; + WORD* v = p->verts; + + p->plane = Plane(mverts[v[0]], + mverts[v[1]], + mverts[v[2]]); + } +} + +// +--------------------------------------------------------------------+ + +void +TerrainClouds::Update() +{ + if (!nverts || !mverts || !verts) + return; + + for (int i = 0; i < nverts; ++i) + verts->loc[i] = mverts[i] + loc; +} + +// +--------------------------------------------------------------------+ + +void +TerrainClouds::Illuminate(Color ambient, List& lights) +{ + int i, n; + + if (terrain) { + DWORD cloud_color = terrain->GetRegion()->CloudColor().Value() | Color(0,0,0,255).Value(); + DWORD shade_color = terrain->GetRegion()->ShadeColor().Value() | Color(0,0,0,255).Value(); + + int stride = 4; + if (type > 0) + stride = 8; + + for (i = 0; i < nbanks; i++) { + for (n = 0; n < 4; n++) { + verts->diffuse[stride*i + n] = cloud_color; + verts->specular[stride*i + n] = 0xff000000; + } + + if (type > 0) { + for (; n < 8; n++) { + verts->diffuse[stride*i + n] = shade_color; + verts->specular[stride*i + n] = 0xff000000; + } + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +TerrainClouds::Render(Video* video, DWORD flags) +{ + if ((flags & Graphic::RENDER_ALPHA) == 0) + return; + + if (video && life && polys && npolys && verts) { + if (scene) + Illuminate(scene->Ambient(), scene->Lights()); + + video->SetRenderState(Video::FOG_ENABLE, false); + video->DrawPolys(npolys, polys); + } +} diff --git a/Stars45/TerrainClouds.h b/Stars45/TerrainClouds.h new file mode 100644 index 0000000..5cf7466 --- /dev/null +++ b/Stars45/TerrainClouds.h @@ -0,0 +1,64 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2005. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: TerrainClouds.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + A Single Edge Section of a Terrain Object +*/ + +#ifndef TerrainClouds_h +#define TerrainClouds_h + +#include "Types.h" +#include "Graphic.h" +#include "Geometry.h" +#include "Polygon.h" + +// +--------------------------------------------------------------------+ + +class Terrain; +class TerrainRegion; + +// +--------------------------------------------------------------------+ + +class TerrainClouds : public Graphic +{ +public: + TerrainClouds(Terrain* terr, int type); + virtual ~TerrainClouds(); + + virtual void Render(Video* video, DWORD flags); + virtual void Update(); + + // accessors: + virtual int CollidesWith(Graphic& o) { return 0; } + virtual bool Luminous() const { return true; } + virtual bool Translucent() const { return true; } + + void Illuminate(Color ambient, List& lights); + +protected: + void BuildClouds(); + + Terrain* terrain; + Vec3* mverts; + VertexSet* verts; + Poly* polys; + Material mtl_cloud; + Material mtl_shade; + + int type; + int nbanks; + int nverts; + int npolys; +}; + + +#endif TerrainClouds_h + diff --git a/Stars45/TerrainHaze.cpp b/Stars45/TerrainHaze.cpp new file mode 100644 index 0000000..9f32e68 --- /dev/null +++ b/Stars45/TerrainHaze.cpp @@ -0,0 +1,90 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2005. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: TerrainHaze.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== +*/ + +#include "MemDebug.h" +#include "Terrain.h" +#include "TerrainHaze.h" +#include "TerrainRegion.h" + +#include "Light.h" +#include "CameraView.h" +#include "Bitmap.h" +#include "DataLoader.h" +#include "Game.h" + +// +====================================================================+ + +static Bitmap terrain_texture; + +// +--------------------------------------------------------------------+ + +TerrainHaze::TerrainHaze() + : tregion(0) +{ +} + +// +--------------------------------------------------------------------+ + +TerrainHaze::~TerrainHaze() +{ +} + +// +--------------------------------------------------------------------+ + +void +TerrainHaze::Render(Video* video, DWORD flags) +{ + if (flags & RENDER_ADDITIVE) + return; + + if (model) { + if (!Luminous()) { + SetLuminous(true); + model->SetDynamic(true); + } + + Surface* surface = model->GetSurfaces().first(); + + if (!surface) return; + + int i; + DWORD sky = 0; + DWORD fog = 0; + + if (tregion) { + sky = tregion->SkyColor().Value(); + fog = tregion->FogColor().Value(); + } + + // clear the solid lights to ambient: + VertexSet* vset = surface->GetVertexSet(); + + for (i = 0; i < vset->nverts; i++) { + if (vset->loc[i].y > 0) + vset->diffuse[i] = sky; + else + vset->diffuse[i] = fog; + } + + InvalidateSurfaceData(); + Solid::Render(video, flags); + } +} + +// +--------------------------------------------------------------------+ + +int +TerrainHaze::CheckRayIntersection(Point Q, Point w, double len, Point& ipt, bool ttpas) +{ + return 0; +} diff --git a/Stars45/TerrainHaze.h b/Stars45/TerrainHaze.h new file mode 100644 index 0000000..3c8bcb6 --- /dev/null +++ b/Stars45/TerrainHaze.h @@ -0,0 +1,46 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2005. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: TerrainHaze.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Atmospheric fog along the horizon +*/ + +#ifndef TerrainHaze_h +#define TerrainHaze_h + +#include "Types.h" +#include "Solid.h" +#include "Geometry.h" + +// +--------------------------------------------------------------------+ + +class TerrainRegion; + +// +--------------------------------------------------------------------+ + +class TerrainHaze : public Solid +{ +public: + TerrainHaze(); + virtual ~TerrainHaze(); + + virtual void Render(Video* video, DWORD flags); + + virtual int CheckRayIntersection(Point pt, Point vpn, double len, Point& ipt, + bool treat_translucent_polys_as_solid=true); + + virtual void UseTerrainRegion(TerrainRegion* tr) { tregion = tr; } + +protected: + TerrainRegion* tregion; +}; + +#endif TerrainHaze_h + diff --git a/Stars45/TerrainLayer.h b/Stars45/TerrainLayer.h new file mode 100644 index 0000000..aff6718 --- /dev/null +++ b/Stars45/TerrainLayer.h @@ -0,0 +1,63 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2005. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: TerrainLayer.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + A blended detail texture applied to a terrain patch + through a specific range of altitudes +*/ + +#ifndef TerrainLayer_h +#define TerrainLayer_h + +#include "Types.h" +#include "Bitmap.h" + +// +--------------------------------------------------------------------+ + +class Terrain; +class TerrainRegion; + +// +--------------------------------------------------------------------+ + +class TerrainLayer +{ + friend class Terrain; + friend class TerrainRegion; + +public: + static const char* TYPENAME() { return "TerrainLayer"; } + + TerrainLayer() : tile_texture(0), detail_texture(0), min_height(0), max_height(-1) { } + ~TerrainLayer() { } + + int operator < (const TerrainLayer& t) const { return min_height < t.min_height; } + int operator <= (const TerrainLayer& t) const { return min_height <= t.min_height; } + int operator == (const TerrainLayer& t) const { return min_height == t.min_height; } + + // accessors: + const char* GetTileName() const { return tile_name; } + const char* GetDetailName() const { return detail_name; } + Bitmap* GetTileTexture() const { return tile_texture; } + Bitmap* GetDetailTexture() const { return detail_texture; } + double GetMinHeight() const { return min_height; } + double GetMaxHeight() const { return max_height; } + +private: + Text tile_name; + Text detail_name; + Bitmap* tile_texture; + Bitmap* detail_texture; + double min_height; + double max_height; +}; + + +#endif TerrainLayer_h + diff --git a/Stars45/TerrainPatch.cpp b/Stars45/TerrainPatch.cpp new file mode 100644 index 0000000..bb8718a --- /dev/null +++ b/Stars45/TerrainPatch.cpp @@ -0,0 +1,1113 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2005. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: TerrainPatch.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== +*/ + +#include "MemDebug.h" +#include "Terrain.h" +#include "TerrainLayer.h" +#include "TerrainPatch.h" +#include "TerrainRegion.h" +#include "Sim.h" + +#include "Light.h" +#include "CameraView.h" +#include "Projector.h" +#include "Bitmap.h" +#include "DataLoader.h" +#include "Game.h" +#include "Scene.h" +#include "Water.h" + +// +====================================================================+ + +// #define DEBUG_DETAIL 1 + +// +====================================================================+ + +const int MAX_DETAIL = 4; +const int PATCH_SIZE = 17; +const int HALF_PATCH_SIZE = 8; +const int MAX_VERTS = PATCH_SIZE * PATCH_SIZE; + +static bool illuminating = false; + +// +--------------------------------------------------------------------+ + +TerrainPatch::TerrainPatch(Terrain* terr,const Bitmap* patch, const Rect& r, + const Point& p1, const Point& p2) + : ndetail(0), terrain(terr), rect(r), water(0), min_height(1e9), max_height(-1e9) +{ + luminous = true; // we will do our own lighting + own_model = false; // manage the model lifetimes in this derived class + + max_detail = Terrain::DetailLevel(); + scale = fabs(p1.x - p2.x) / (PATCH_SIZE-1); + mtnscale = p2.y - p1.y; + base = p1.y; + size = p2.x - p1.x; + + ZeroMemory(detail_levels, sizeof(detail_levels)); + + terrain_width = patch->Width(); + + loc = (p1 + p2) * 0.5; + loc.y = base; + + radius = (float) (size * 0.75); + heights = new(__FILE__,__LINE__) float[MAX_VERTS]; + + float* pHeight = heights; + int tscale = rect.w / (PATCH_SIZE-1); + int i, j; + + for (i = 0; i < PATCH_SIZE; i++) { + int ty = rect.y + i * tscale; + + if (ty < 0) + ty = 0; + + if (ty > patch->Height()-1) + ty = patch->Height()-1; + + for (j = 0; j < PATCH_SIZE; j++) { + int tx = rect.x + (PATCH_SIZE-1 - j) * tscale; + + if (tx < 0) + tx = 0; + + if (tx > patch->Width()-1) + tx = patch->Width()-1; + + int red = patch->GetColor(tx,ty).Red(); + float alt = (float) (red * mtnscale); + + if (alt < min_height) + min_height = alt; + if (alt > max_height) + max_height = alt; + + if (terrain->WaterTexture() && red < 2) + alt = -5000.0f; + + *pHeight++ = alt; + } + } + + Material* mtl = new(__FILE__,__LINE__) Material; + mtl->Ka = ColorValue(0.5f, 0.5f, 0.5f); + mtl->Kd = ColorValue(0.3f, 0.6f, 0.2f); + mtl->Ks = Color::Black; + + mtl->tex_diffuse = terrain->Texture(); + + materials.append(mtl); + + List& layers = terrain->GetLayers(); + for (i = 0; i < layers.size(); i++) { + Bitmap* tex0 = layers.at(i)->GetTileTexture(); + Bitmap* tex1 = 0; + Bitmap* texd = layers.at(i)->GetDetailTexture(); + + if (i < layers.size()-1) + tex1 = layers.at(i+1)->GetTileTexture(); + + if (!texd) + texd = terrain->DetailTexture(0); + + mtl = new(__FILE__,__LINE__) Material; + mtl->Ka = ColorValue(0.5f, 0.5f, 0.5f); + mtl->Kd = ColorValue(0.3f, 0.6f, 0.2f); + mtl->Ks = Color::Black; + + if ((i & 1) != 0) { + mtl->tex_diffuse = tex1; + mtl->tex_alternate = tex0; + } + else { + mtl->tex_diffuse = tex0; + mtl->tex_alternate = tex1; + } + + mtl->tex_detail = texd; + + materials.append(mtl); + } + + for (i = 0; i <= max_detail; i++) + BuildDetailLevel(i); + + model = detail_levels[1]; +} + +// +--------------------------------------------------------------------+ + +TerrainPatch::TerrainPatch(Terrain* terr, + const Rect& r, + const Point& p1, + const Point& p2, + double sea_level) + : ndetail(0), terrain(terr), rect(r), water(0) +{ + luminous = true; // water is lit by the graphics engine + own_model = false; // manage the model lifetimes in this derived class + + max_detail = Terrain::DetailLevel(); + scale = fabs(p1.x - p2.x) / (PATCH_SIZE-1); + mtnscale = 0; + base = sea_level; + size = p2.x - p1.x; + + ZeroMemory(detail_levels, sizeof(detail_levels)); + + terrain_width = 0; + + loc = (p1 + p2) * 0.5; + loc.y = base; + + radius = (float) (size * 0.75); + heights = new(__FILE__,__LINE__) float[MAX_VERTS]; + + float* pHeight = heights; + int i, j; + + for (i = 0; i < PATCH_SIZE; i++) { + for (j = 0; j < PATCH_SIZE; j++) { + *pHeight++ = (float) sea_level; + } + } + + Material* mtl = new(__FILE__,__LINE__) Material; + mtl->Ka = Color::Gray; + mtl->Kd = Color::White; + mtl->Ks = Color::White; + mtl->power = 30.0f; + mtl->shadow = false; + mtl->tex_diffuse = terrain->WaterTexture(); + //strcpy(mtl->shader, "WaterReflections"); + materials.append(mtl); + + water = terrain->GetWater(1); + + for (i = 0; i <= max_detail; i++) + BuildDetailLevel(i); + + model = detail_levels[1]; +} + +// +--------------------------------------------------------------------+ + +TerrainPatch::~TerrainPatch() +{ + delete [] heights; + + for (int i = 0; i < MAX_LOD; i++) { + Model* m = detail_levels[i]; + + if (m) { + m->GetMaterials().clear(); + delete m; + } + } + + materials.destroy(); +} + +// +--------------------------------------------------------------------+ + +void +TerrainPatch::SetScales(double s, double m, double b) +{ + scale = s; + mtnscale = m; + base = b; +} + +// +--------------------------------------------------------------------+ + +static int mcomp(const void* a, const void* b) +{ + Poly* pa = (Poly*) a; + Poly* pb = (Poly*) b; + + if (pa->sortval == pb->sortval) + return 0; + + if (pa->sortval < pb->sortval) + return 1; + + return -1; +} + + +static void bisect(VertexSet* vset, int v[4]) +{ + double d1 = fabs(vset->loc[ v[0] ].y - + vset->loc[ v[3] ].y); + + double d2 = fabs(vset->loc[ v[1] ].y - + vset->loc[ v[2] ].y); + + if (d2 < 0.7*d1) { + int odd[4] = { v[1], v[3], v[0], v[2] }; + for (int i = 0; i < 4; i++) + v[i] = odd[i]; + } +} + +bool +TerrainPatch::BuildDetailLevel(int level) +{ + int i, j; + + int detail_size = 1 << level; + int ds1 = detail_size+1; + + if (detail_size > PATCH_SIZE) + return false; + + Model* model = new(__FILE__,__LINE__) Model; + detail_levels[level] = model; + + model->SetLuminous(luminous); + model->SetDynamic(true); + + const int NUM_STRIPS = 4; + const int NUM_INDICES_TRI = 3; + const int NUM_INDICES_QUAD = 6; + + int nverts = ds1*ds1 + ds1*2*NUM_STRIPS; + int npolys = detail_size*detail_size*2; + int strip_len = detail_size; + int total = npolys + strip_len*NUM_STRIPS; + + if (water) { + nverts = ds1*ds1; + strip_len = 0; + total = npolys; + } + + Surface* s = new(__FILE__,__LINE__) Surface; + VertexSet* vset = 0; + + if (s) { + s->SetName("default"); + s->CreateVerts(nverts); + s->CreatePolys(total); + s->AddIndices(npolys*NUM_INDICES_TRI + strip_len*NUM_STRIPS*NUM_INDICES_QUAD); + + vset = s->GetVertexSet(); + if (!water) + vset->CreateAdditionalTexCoords(); + + ZeroMemory(vset->loc, nverts * sizeof(Vec3)); + ZeroMemory(vset->diffuse, nverts * sizeof(DWORD)); + ZeroMemory(vset->specular, nverts * sizeof(DWORD)); + ZeroMemory(vset->tu, nverts * sizeof(float)); + ZeroMemory(vset->tv, nverts * sizeof(float)); + if (!water) { + ZeroMemory(vset->tu1, nverts * sizeof(float)); + ZeroMemory(vset->tv1, nverts * sizeof(float)); + } + ZeroMemory(vset->rw, nverts * sizeof(float)); + + // initialize vertices + Vec3* pVert = vset->loc; + float* pTu = vset->tu; + float* pTv = vset->tv; + float* pTu1 = vset->tu1; + float* pTv1 = vset->tv1; + DWORD* pSpec = vset->specular; + + int dscale = (PATCH_SIZE-1)/detail_size; + double dt = 0.0625 / (ds1-1); // terrain texture scale + double dtt = 2.0000 / (ds1-1); // tile texture scale + double tu0 = (double) rect.x / rect.w / 16.0 + 1.0/16.0; + double tv0 = (double) rect.y / rect.h / 16.0; + + // surface verts + for (i = 0; i < ds1; i++) { + for (j = 0; j < ds1; j++) { + *pVert = Vec3((float) (j* scale * dscale - (HALF_PATCH_SIZE*scale)), + (float) (heights[i*dscale*PATCH_SIZE + j*dscale]), + (float) (i* scale * dscale - (HALF_PATCH_SIZE*scale))); + + if (level >= 2) { + *pTu++ = (float) (-j*dtt); + *pTv++ = (float) ( i*dtt); + + if (level >= 4 && !water) { + *pTu1++ = (float) (-j*dtt*3); + *pTv1++ = (float) ( i*dtt*3); + } + + *pSpec++ = BlendValue(pVert->y); + } + + else { + *pTu++ = (float) (tu0 - j*dt); + *pTv++ = (float) (tv0 + i*dt); + } + + pVert++; + } + } + + if (!water) { + // strip 1 & 2 verts + for (i = 0; i < ds1; i += detail_size) { + for (j = 0; j < ds1; j++) { + Vec3 vl = Vec3((float) (j* scale * dscale - (HALF_PATCH_SIZE*scale)), + (float) (heights[i*dscale*PATCH_SIZE + j*dscale]), + (float) (i* scale * dscale - (HALF_PATCH_SIZE*scale))); + + *pVert++ = vl; + + DWORD blend = 0; + + if (level >= 2) { + blend = BlendValue(vl.y); + + *pSpec++ = blend; + *pTu++ = (float) (-j*dtt); + *pTv++ = (float) ( i*dtt); + } + + else { + *pTu++ = (float) (tu0 - j*dt); + *pTv++ = (float) (tv0 + i*dt); + } + + vl.y = -5000.0f; + + *pVert++ = vl; + + if (level >= 2) { + *pSpec++ = blend; + *pTu++ = (float) (-j*dtt); + *pTv++ = (float) ( i*dtt); + } + + else { + *pTu++ = (float) (tu0 - j*dt); + *pTv++ = (float) (tv0 + i*dt); + } + } + } + + // strip 3 & 4 verts + for (j = 0; j < ds1; j += detail_size) { + for (i = 0; i < ds1; i++) { + Vec3 vl = Vec3((float) (j* scale * dscale - (HALF_PATCH_SIZE*scale)), + (float) (heights[i*dscale*PATCH_SIZE + j*dscale]), + (float) (i* scale * dscale - (HALF_PATCH_SIZE*scale))); + + *pVert++ = vl; + + DWORD blend = 0; + + if (level >= 2) { + blend = BlendValue(vl.y); + + *pSpec++ = blend; + *pTu++ = (float) (-j*dtt); + *pTv++ = (float) ( i*dtt); + } + + else { + *pTu++ = (float) (tu0 - j*dt); + *pTv++ = (float) (tv0 + i*dt); + } + + vl.y = -5000.0f; + + *pVert++ = vl; + + if (level >= 2) { + *pSpec++ = blend; + *pTu++ = (float) (-j*dtt); + *pTv++ = (float) ( i*dtt); + } + + else { + *pTu++ = (float) (tu0 - j*dt); + *pTv++ = (float) (tv0 + i*dt); + } + } + } + } + + Material* m = materials.first(); + + // initialize the polys + for (i = 0; i < npolys; i++) { + Poly* p = s->GetPolys() + i; + p->nverts = 3; + p->vertex_set = vset; + p->visible = 1; + p->sortval = 0; + p->material = m; + + if (level >= 2 && !water) { + p->material = materials.at(1); + p->sortval = 1; + } + } + + for (i = npolys; i < total; i++) { + Poly* p = s->GetPolys() + i; + p->nverts = 4; + p->vertex_set = vset; + p->visible = 1; + p->sortval = 0; + p->material = m; + } + + int index = 0; + + // build main patch polys: + for (i = 0; i < detail_size; i++) { + for (j = 0; j < detail_size; j++) { + int v[4] = { + (ds1 * (i ) + (j )), + (ds1 * (i ) + (j+1)), + (ds1 * (i+1) + (j )), + (ds1 * (i+1) + (j+1)) }; + + bisect(vset, v); + + // first triangle + Poly* p = s->GetPolys() + index++; + p->verts[0] = v[0]; + p->verts[1] = v[1]; + p->verts[2] = v[3]; + + if (level >= 2 && !water) { + int layer = CalcLayer(p) + 1; + p->material = materials.at(layer); + p->sortval = layer; + } + + // second triangle + p = s->GetPolys() + index++; + p->verts[0] = v[0]; + p->verts[1] = v[3]; + p->verts[2] = v[2]; + + if (level >= 2 && !water) { + int layer = CalcLayer(p) + 1; + p->material = materials.at(layer); + p->sortval = layer; + } + } + } + + // build vertical edge strip polys: + + if (!water) { + for (i = 0; i < NUM_STRIPS; i++) { + Poly* p = s->GetPolys() + npolys + i*strip_len; + int base_index = ds1*ds1 + ds1*2*i; + + for (j = 0; j < strip_len; j++) { + int v = base_index + j * 2; + p->nverts = 4; + + if (i == 1 || i == 2) { + p->verts[0] = v; + p->verts[1] = v+2; + p->verts[2] = v+3; + p->verts[3] = v+1; + } + + else { + p->verts[0] = v; + p->verts[1] = v+1; + p->verts[2] = v+3; + p->verts[3] = v+2; + } + + if (level >= 2) { + int layer = CalcLayer(p) + 1; + p->material = materials.at(layer); + p->sortval = layer; + } + + p++; + } + } + } + + // update the poly planes: + for (i = 0; i < total; i++) { + Poly* p = s->GetPolys() + i; + Plane& plane = p->plane; + WORD* v = p->verts; + + plane = Plane(vset->loc[v[0]] + loc, + vset->loc[v[1]] + loc, + vset->loc[v[2]] + loc); + } + + s->Normalize(); + + // create continguous segments for each material: + // sort the polys by material index: + qsort((void*) s->GetPolys(), s->NumPolys(), sizeof(Poly), mcomp); + + // then assign them to cohesive segments: + Segment* segment = 0; + Poly* spolys = s->GetPolys(); + + for (int n = 0; n < s->NumPolys(); n++) { + if (segment && segment->material == spolys[n].material) { + segment->npolys++; + } + else { + segment = 0; + } + + if (!segment) { + segment = new(__FILE__,__LINE__) Segment; + + segment->npolys = 1; + segment->polys = &spolys[n]; + segment->material = segment->polys->material; + segment->model = model; + + s->GetSegments().append(segment); + } + } + + Solid::EnableCollision(false); + model->AddSurface(s); + Solid::EnableCollision(true); + + // copy vertex normals: + const Vec3B* tnorms = terrain->Normals(); + + for (i = 0; i < ds1; i++) { + for (j = 0; j < ds1; j++) { + + if (water) { + vset->nrm[i*ds1+j] = Point(0,1,0); + } + + // blend adjacent normals: + else if (dscale > 1) { + Point normal; + + // but don't blend more than 16 normals per vertex: + int step = 1; + if (dscale > 4) + step = dscale / 4; + + for (int dy = -dscale/2; dy < dscale/2; dy += step) { + for (int dx = -dscale/2; dx < dscale/2; dx += step) { + int ix = rect.x + (ds1-1-j)*dscale + dx; + int iy = rect.y + i*dscale + dy; + + if (ix < 0) ix = 0; + if (ix > terrain_width-1) ix = terrain_width-1; + if (iy < 0) iy = 0; + if (iy > terrain_width-1) iy = terrain_width-1; + + Vec3B vbn = tnorms[iy*terrain_width + ix]; + normal += Point((128-vbn.x)/127.0, (vbn.z-128)/127.0, (vbn.y-128)/127.0); + } + } + + normal.Normalize(); + vset->nrm[i*ds1+j] = normal; + } + + // just copy the one normal: + else { + Vec3B vbn = tnorms[(rect.y + i*dscale)*terrain_width + (rect.x + (ds1-1-j) * dscale)]; + Point normal = Point((128-vbn.x)/127.0, (vbn.z-128)/127.0, (vbn.y-128)/127.0); + vset->nrm[i*ds1+j] = normal; + } + } + } + + if (!water) { + pVert = &vset->nrm[ds1*ds1]; + + // strip 1 & 2 verts + for (i = 0; i < ds1; i += detail_size) { + for (j = 0; j < ds1; j++) { + Vec3 vn = vset->nrm[i*ds1 + j]; + + *pVert++ = vn; + *pVert++ = vn; + } + } + + // strip 3 & 4 verts + for (j = 0; j < ds1; j += detail_size) { + for (i = 0; i < ds1; i++) { + Vec3 vn = vset->nrm[i*ds1 + j]; + + *pVert++ = vn; + *pVert++ = vn; + } + } + } + } + + if (level > max_detail) + max_detail = level; + + return true; +} + +// +--------------------------------------------------------------------+ + +DWORD +TerrainPatch::BlendValue(double y) +{ + if (terrain && y >= 0 && !water) { + // find the proper layer: + for (int i = 0; i < terrain->GetLayers().size(); i++) { + TerrainLayer* layer = terrain->GetLayers().at(i); + + if (y >= layer->GetMinHeight() && y < layer->GetMaxHeight()) { + double scale = (y-layer->GetMinHeight()) / (layer->GetMaxHeight()-layer->GetMinHeight()); + + if (scale < 0.2) + scale = 0; + else if (scale > 0.8) + scale = 1; + else + scale = (scale - 0.2) / 0.6; + + if ((i & 1) == 0) { + scale = 1 - scale; + } + + DWORD val = (DWORD) (scale*255); + + return val << 24; + } + } + } + + return 0; +} + +int +TerrainPatch::CalcLayer(Poly* poly) +{ + if (terrain && poly) { + if (water) + return 0; + + double y = 1e6; + + for (int i = 0; i < poly->nverts; i++) { + double h = poly->vertex_set->loc[ poly->verts[i] ].y; + + if (h >= 0 && h < y) { + y = h; + } + } + + if (y <= terrain->GetLayers().first()->GetMinHeight()) + return 0; + + for (i = 0; i < terrain->GetLayers().size(); i++) { + TerrainLayer* layer = terrain->GetLayers().at(i); + + if (y >= layer->GetMinHeight() && y < layer->GetMaxHeight()) { + return i; + } + } + } + + return -1; +} + +// +--------------------------------------------------------------------+ + +void +TerrainPatch::UpdateSurfaceWaves(Vec3& eyePos) +{ + if (water && model && model->NumVerts() > 1) { + Surface* s = model->GetSurfaces().first(); + if (s) { + VertexSet* vset = s->GetVertexSet(); + for (int i = 0; i < vset->nverts; i++) + vset->loc[i].y = 0.0f; + + water->UpdateSurface(eyePos, vset); + } + } +} + +// +--------------------------------------------------------------------+ + +int +TerrainPatch::CollidesWith(Graphic& o) +{ + return 0; +} + +// +--------------------------------------------------------------------+ + +void +TerrainPatch::SelectDetail(Projector* projector) +{ + // This is where all the work is done, + // Delegate to the overall terrain to + // compute a detail level for every patch: + + if (terrain) { + terrain->SelectDetail(projector); + } + + // Build detail levels on demand: + + if (detail_levels[ndetail] == 0) + BuildDetailLevel(ndetail); +} + +void +TerrainPatch::SetDetailLevel(int nd) +{ + if (nd >= 0 && nd <= max_detail) { + if (ndetail != nd) + DeletePrivateData(); + + ndetail = nd; + + // build detail levels on demand: + if (detail_levels[ndetail] == 0) + BuildDetailLevel(ndetail); + + model = detail_levels[ndetail]; + + if (water) + water = terrain->GetWater(ndetail); + } +} + +// +--------------------------------------------------------------------+ + +void +TerrainPatch::Illuminate(Color ambient, List& lights) +{ + if (!model || model->NumVerts() < 1) return; + Surface* s = model->GetSurfaces().first(); + if (!s) return; + + illuminating = true; + + // clear the solid lights to ambient: + VertexSet* vset = s->GetVertexSet(); + int nverts = vset->nverts; + DWORD aval = ambient.Value(); + + for (int i = 0; i < nverts; i++) { + vset->diffuse[i] = aval; + } + + TerrainRegion* trgn = terrain->GetRegion(); + bool eclipsed = false; + bool first = terrain->IsFirstPatch(this); + + if (trgn && !first) { + eclipsed = trgn->IsEclipsed(); + } + + // for sun and back lights: + ListIter iter = lights; + while (++iter) { + Light* light = iter.value(); + + if (!light->IsDirectional()) // only do sun and + continue; // back lights + + if (light->CastsShadow() && first) { + eclipsed = light->Location().y < -100 || // has sun set, or + + scene->IsLightObscured(vset->loc[0], // is sun in eclipse + light->Location(), // by orbital body + radius); // such as a moon? + } + + if (!light->CastsShadow() || !eclipsed) { + Vec3 vl = light->Location(); + vl.Normalize(); + + for (i = 0; i < nverts; i++) { + Vec3& nrm = vset->nrm[i]; + double val = 0; + double gain = vl * nrm; + + if (gain > 0) { + val = light->Intensity() * (0.85 * gain); + + if (val > 1) + val = 1; + } + + if (val > 0.01) + vset->diffuse[i] = ((light->GetColor().dim(val)) + vset->diffuse[i]).Value(); + } + } + } + + // combine blend weights: + if (ndetail >= 2) { + for (i = 0; i < nverts; i++) { + vset->diffuse[i] = vset->specular[i] | (vset->diffuse[i] & 0x00ffffff); + } + } + + if (trgn && first) { + trgn->SetEclipsed(eclipsed); + } + + InvalidateSurfaceData(); + illuminating = false; +} + +// +--------------------------------------------------------------------+ + +void +TerrainPatch::Render(Video* video, DWORD flags) +{ + if (max_height < 0) + return; + + if (flags & RENDER_ADDITIVE) + return; + + if (!model) + model = detail_levels[0]; + + if (scene) { + /*** + *** TWO PASS LIGHTING FOR TERRAIN SHADOWS DOESN'T + *** WORK - IT MESSES UP THE HARDWARE FOG CALCS + *** + *** PLUS, IT'S INCREDIBLY SLOW!!! + ***/ + + if ((flags & RENDER_ADD_LIGHT) != 0) + return; + + if (water) { + UpdateSurfaceWaves(Vec3(0,0,0)); + Illuminate(scene->Ambient(), scene->Lights()); + } + else { + Illuminate(scene->Ambient(), scene->Lights()); + } + } + else { + if ((flags & RENDER_ADD_LIGHT) != 0) + return; + } + + Bitmap* details[16]; + ZeroMemory(details, sizeof(details)); + + if (ndetail < 3) { + for (int i = 0; i < 16 && i < materials.size(); i++) { + Material* mtl = materials[i]; + + if (mtl->tex_detail) { + details[i] = mtl->tex_detail; + mtl->tex_detail = 0; + } + } + } + + double visibility = terrain->GetRegion()->GetWeather().Visibility(); + FLOAT fog_density = (FLOAT) (terrain->GetRegion()->FogDensity() * 2.5e-5 * 1/visibility); + + video->SetRenderState(Video::LIGHTING_ENABLE, false); + video->SetRenderState(Video::SPECULAR_ENABLE, false); //water != 0); + video->SetRenderState(Video::FOG_ENABLE, true); + video->SetRenderState(Video::FOG_COLOR, terrain->GetRegion()->FogColor().Value()); + video->SetRenderState(Video::FOG_DENSITY, *((DWORD*) &fog_density)); + + // Too bad this doesn't work right. But it makes the + // ground mostly disappear. :-( + // + //video->SetRenderState(Video::Z_BIAS, -2); + + Solid::Render(video, flags); + + video->SetRenderState(Video::LIGHTING_ENABLE, true); + video->SetRenderState(Video::SPECULAR_ENABLE, true); + video->SetRenderState(Video::FOG_ENABLE, false); + //video->SetRenderState(Video::Z_BIAS, 0); + + if (ndetail < 3) { + for (int i = 0; i < 16 && i < materials.size(); i++) { + Material* mtl = materials[i]; + + if (details[i] && !mtl->tex_detail) { + mtl->tex_detail = details[i]; + } + } + } +} + +// +--------------------------------------------------------------------+ + +int +TerrainPatch::CheckRayIntersection(Point obj_pos, Point dir, double len, Point& ipt, bool ttpas) +{ + Point light_pos = obj_pos + dir * len; + int impact = light_pos.y < -100; + + // Special case for illumination - + // just check if sun is above or below the horizon: + + if (illuminating || impact) { + return impact; + } + + if (obj_pos.x != 0 || obj_pos.y != 0 || obj_pos.z != 0) { + return impact; + } + + // the rest of this code is only used for eclipsing + // the solar lens flare: + + // check right angle spherical distance: + Point d0 = loc; + Point d1 = d0.cross(dir); + double dlen = d1.length(); // distance of point from line + + if (dlen > radius) // clean miss + return 0; // (no impact) + + // make sure some part of this patch falls between + // the camera and the sun: + + Point closest = loc + dir * radius; + + if (closest * dir < 0) + return 0; + + // probable hit at this point... + // test for polygon intersection: + if (!model) + return 0; + + Surface* s = model->GetSurfaces().first(); + + if (!s) + return 0; + + + // transform ray into object space: + Matrix xform(Orientation()); + + Vec3 tmp = dir; + + dir.x = tmp * Vec3(xform(0,0), xform(0,1), xform(0,2)); + dir.y = tmp * Vec3(xform(1,0), xform(1,1), xform(1,2)); + dir.z = tmp * Vec3(xform(2,0), xform(2,1), xform(2,2)); + + tmp = obj_pos-loc; + + obj_pos.x = tmp * Vec3(xform(0,0), xform(0,1), xform(0,2)); + obj_pos.y = tmp * Vec3(xform(1,0), xform(1,1), xform(1,2)); + obj_pos.z = tmp * Vec3(xform(2,0), xform(2,1), xform(2,2)); + + double min = 2 * len; + + // check each polygon: + Poly* p = s->GetPolys(); + + for (int i = 0; i < s->NumPolys(); i++) { + Point v = p->plane.normal; + double d = p->plane.distance; + + double denom = dir*v; + + if (denom < -1.0e-5) { + Point P = v * d; + double ilen = ((P-obj_pos)*v)/denom; + + if (ilen > 0 && ilen < min) { + Point intersect = obj_pos + dir * ilen; + + if (p->Contains(intersect)) { + ipt = intersect; + min = ilen; + impact = 1; + } + } + } + + p++; + } + + // xform impact point back into world coordinates: + + if (impact) { + ipt = (ipt * Orientation()) + loc; + } + + return impact; +} + +// +--------------------------------------------------------------------+ + +double +TerrainPatch::Height(double x, double z) const +{ + if (water) return base; + + double height = 0; + + x /= scale; + z /= scale; + + int x0 = (int) x; + int z0 = (int) z; + + if (x0 >= 0 && x0 < PATCH_SIZE && z0 >= 0 && z0 < PATCH_SIZE) { + double dx = x-x0; + double dz = z-z0; + + double h[4]; + + h[0] = heights[(z0*PATCH_SIZE + x0)]; + h[1] = heights[((z0+1)*PATCH_SIZE + x0)]; + h[2] = heights[(z0*PATCH_SIZE + x0+1)]; + h[3] = heights[((z0+1)*PATCH_SIZE + x0+1)]; + + // if level, just return height of one vertex: + if (h[0] == h[1] && h[1] == h[2] && h[2] == h[3]) { + height = h[0]; + } + + // if sloped, interpolate between vertex heights: + else { + double hl = h[0]*(1-dz) + h[1]*dz; + double hr = h[2]*(1-dz) + h[3]*dz; + + height = hl*(1-dx) + hr*dx + 5; // fudge + } + } + + if (height < 0) + height = 0; + + return height; +} diff --git a/Stars45/TerrainPatch.h b/Stars45/TerrainPatch.h new file mode 100644 index 0000000..553cf78 --- /dev/null +++ b/Stars45/TerrainPatch.h @@ -0,0 +1,94 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2005. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: TerrainPatch.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + A Single Multi-LOD Section of a Terrain Object +*/ + +#ifndef TerrainPatch_h +#define TerrainPatch_h + +#include "Types.h" +#include "Solid.h" +#include "Geometry.h" +#include "Polygon.h" + +// +--------------------------------------------------------------------+ + +class Projector; +class Terrain; +class Water; + +// +--------------------------------------------------------------------+ + +class TerrainPatch : public Solid +{ +public: + TerrainPatch(Terrain* terrain, + const Bitmap* patch, const Rect& rect, + const Point& p1, const Point& p2); + TerrainPatch(Terrain* terrain, const Rect& rect, + const Point& p1, const Point& p2, + double sea_level); + virtual ~TerrainPatch(); + + virtual void SelectDetail(Projector* projector); + virtual void Render(Video* video, DWORD flags); + + virtual int CollidesWith(Graphic& o); + + // accessors: + double Scale() const { return scale; } + double MountainScale() const { return mtnscale; } + double SeaLevel() const { return base; } + double MinHeight() const { return min_height; } + double MaxHeight() const { return max_height; } + bool IsWater() const { return water != 0; } + + void UpdateSurfaceWaves(Vec3& eyePos); + + void SetScales(double scale, double mtnscale, double base); + void SetDetailLevel(int nd); + + virtual int CheckRayIntersection(Point pt, Point vpn, double len, Point& ipt, + bool treat_translucent_polys_as_solid=true); + + double Height(double x, double y) const; + DWORD BlendValue(double y); + int CalcLayer(Poly* p); + void Illuminate(Color ambient, List& lights); + +protected: + virtual bool BuildDetailLevel(int level); + + enum { MAX_LOD=8 }; + + Terrain* terrain; + int patch_size; + int ndetail; + int max_detail; + int terrain_width; + + Rect rect; + float* heights; + Model* detail_levels[MAX_LOD]; + List materials; + Water* water; + + double scale; + double mtnscale; + double base; + double size; + float min_height; + float max_height; +}; + +#endif TerrainPatch_h + diff --git a/Stars45/TerrainRegion.cpp b/Stars45/TerrainRegion.cpp new file mode 100644 index 0000000..17be0bc --- /dev/null +++ b/Stars45/TerrainRegion.cpp @@ -0,0 +1,266 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2005. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: TerrainRegion.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Various Heavenly Bodies +*/ + +#include "MemDebug.h" +#include "TerrainRegion.h" +#include "Sky.h" +#include "TerrainHaze.h" +#include "TerrainLayer.h" +#include "Sim.h" +#include "Grid.h" +#include "CameraDirector.h" +#include "HUDView.h" + +#include "Game.h" +#include "Sound.h" +#include "Solid.h" +#include "Sprite.h" +#include "Light.h" +#include "Bitmap.h" +#include "DataLoader.h" +#include "Scene.h" +#include "ParseUtil.h" + +// +====================================================================+ + +TerrainRegion::TerrainRegion(StarSystem* s, const char* n, double r, Orbital* p) + : OrbitalRegion(s, n, 0.0, r, 0.0, p), + scale(10e3), + mtnscale(1e3), + fog_density(0), + fog_scale(0), + day_phase(0), + eclipsed(false) +{ + type = TERRAIN; + + if (primary) { + orbit = primary->Radius(); + period = primary->Rotation(); + } + + clouds_alt_high = 12.0e3; + clouds_alt_low = 8.0e3; + + Update(); +} + +// +--------------------------------------------------------------------+ + +TerrainRegion::~TerrainRegion() +{ + layers.destroy(); +} + +// +--------------------------------------------------------------------+ + +void +TerrainRegion::Update() +{ + if (system && primary) { + phase = primary->RotationPhase(); + loc = primary->Location() + Point((double) (orbit * cos(phase)), + (double) (orbit * sin(phase)), + 0); + } + + if (rep) + rep->MoveTo(loc.OtherHand()); + + // calc day phase: + OrbitalBody* star = system->Bodies()[0]; + + Point tvpn = Location() - primary->Location(); + Point tvst = star->Location() - primary->Location(); + + tvpn.Normalize(); + tvst.Normalize(); + + day_phase = acos(tvpn * tvst) + PI; + + Point meridian = tvpn.cross(tvst); + + if (meridian.z < 0) { + day_phase = 2*PI - day_phase; + } + + day_phase *= 24 / (2*PI); + + if (!system || system->ActiveRegion() != this) + return; + + // set sky color: + int base_hour = (int) day_phase; + double fraction = day_phase - base_hour; + + sky_color[24] = Color::Scale(sky_color[base_hour], sky_color[base_hour+1], fraction); + sun_color[24] = Color::Scale(sun_color[base_hour], sun_color[base_hour+1], fraction); + fog_color[24] = Color::Scale(fog_color[base_hour], fog_color[base_hour+1], fraction); + ambient[24] = Color::Scale(ambient[base_hour], ambient[base_hour+1], fraction); + overcast[24] = Color::Scale(overcast[base_hour], overcast[base_hour+1], fraction); + cloud_color[24] = Color::Scale(cloud_color[base_hour], cloud_color[base_hour+1], fraction); + shade_color[24] = Color::Scale(shade_color[base_hour], shade_color[base_hour+1], fraction); + + CameraDirector* cam_dir = CameraDirector::GetInstance(); + Sim* sim = Sim::GetSim(); + double alt = 0; + double dim = 1; + + if (cam_dir && cam_dir->GetCamera()) + alt = cam_dir->GetCamera()->Pos().y; + + if (alt > 0) { + if (alt < TERRAIN_ALTITUDE_LIMIT) { + if (weather.Ceiling() > 0) { + fog_color[24] = overcast[24]; + sky_color[24] = overcast[24]; + dim = 0.125; + + /*** + if (alt < weather.Ceiling()) { + sky_color[24] = overcast[24]; + dim = 0.125; + } + else if (alt < weather.Ceiling() + 500) { + double thick = (weather.Ceiling() + 500.0 - alt) / 500.0; + sky_color[24] = sky_color[24] * (1.0 - thick); + sky_color[24] += overcast[24] * thick; + + dim = 1 - thick * 0.875; + } + else { + sky_color[24] = sky_color[24] * (1 - alt/TERRAIN_ALTITUDE_LIMIT); + } + ***/ + } + + else { + sky_color[24] = sky_color[24] * (1 - alt/TERRAIN_ALTITUDE_LIMIT); + } + } + else { + sky_color[24] = Color::Black; + } + } + + if (system) { + system->SetSunlight(sun_color[24], dim); + system->SetBacklight(sky_color[24], dim); + + HUDView* hud = HUDView::GetInstance(); + if (hud) { + Color night_vision = hud->Ambient(); + sky_color[24] += night_vision * 0.15; + } + } +} + +// +--------------------------------------------------------------------+ + +void +TerrainRegion::LoadSkyColors(const char* bmp_name) +{ + Bitmap sky_colors_bmp; + + DataLoader* loader = DataLoader::GetLoader(); + loader->LoadBitmap(bmp_name, sky_colors_bmp); + + int max_color = sky_colors_bmp.Width(); + + if (max_color > 24) + max_color = 24; + + for (int i = 0; i < 25; i++) { + sun_color[i] = Color::White; + sky_color[i] = Color::Black; + fog_color[i] = Color::White; + ambient[i] = Color::DarkGray; + cloud_color[i] = Color::White; + shade_color[i] = Color::Gray; + } + + for (i = 0; i < max_color; i++) + sky_color[i] = sky_colors_bmp.GetColor(i, 0); + + if (sky_colors_bmp.Height() > 1) + for (i = 0; i < max_color; i++) + fog_color[i].Set(sky_colors_bmp.GetColor(i, 1).Value() | Color(0,0,0,255).Value()); + + if (sky_colors_bmp.Height() > 2) + for (i = 0; i < max_color; i++) + ambient[i] = sky_colors_bmp.GetColor(i, 2); + + if (sky_colors_bmp.Height() > 3) + for (i = 0; i < max_color; i++) + sun_color[i] = sky_colors_bmp.GetColor(i, 3); + + if (sky_colors_bmp.Height() > 4) + for (i = 0; i < max_color; i++) + overcast[i] = sky_colors_bmp.GetColor(i, 4); + + if (sky_colors_bmp.Height() > 5) + for (i = 0; i < max_color; i++) + cloud_color[i] = sky_colors_bmp.GetColor(i, 5); + + if (sky_colors_bmp.Height() > 6) + for (i = 0; i < max_color; i++) + shade_color[i] = sky_colors_bmp.GetColor(i, 6); +} + +// +--------------------------------------------------------------------+ + +void +TerrainRegion::AddLayer(double h, const char* tile, const char* detail) +{ + TerrainLayer* layer = new(__FILE__,__LINE__) TerrainLayer; + + layer->min_height = h; + layer->tile_name = tile; + + if (detail && *detail) + layer->detail_name = detail; + + layers.append(layer); +} + +const Text& +TerrainRegion::EnvironmentTexture(int face) const +{ + switch (face) { + case 0: return env_texture_positive_x; + + case 1: if (env_texture_negative_x.length() > 0) + return env_texture_negative_x; + return env_texture_positive_x; + + case 2: return env_texture_positive_y; + + case 3: if (env_texture_negative_y.length() > 0) + return env_texture_negative_y; + return env_texture_positive_y; + + case 4: if (env_texture_positive_z.length() > 0) + return env_texture_positive_z; + return env_texture_positive_x; + + case 5: if (env_texture_negative_z.length() > 0) + return env_texture_negative_z; + if (env_texture_positive_z.length() > 0) + return env_texture_positive_z; + return env_texture_positive_x; + } + + return env_texture_positive_x; +} + diff --git a/Stars45/TerrainRegion.h b/Stars45/TerrainRegion.h new file mode 100644 index 0000000..5a38547 --- /dev/null +++ b/Stars45/TerrainRegion.h @@ -0,0 +1,126 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2005. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: TerrainRegion.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Various heavenly bodies +*/ + +#ifndef TerrainRegion_h +#define TerrainRegion_h + +#include "Types.h" +#include "StarSystem.h" +#include "Weather.h" + +// +--------------------------------------------------------------------+ + +const double TERRAIN_ALTITUDE_LIMIT = 35e3; + +class TerrainLayer; + +// +--------------------------------------------------------------------+ + +class TerrainRegion : public OrbitalRegion +{ + friend class StarSystem; + +public: + TerrainRegion(StarSystem* sys, const char* n, double r, Orbital* prime=0); + virtual ~TerrainRegion(); + + // operations: + virtual void Update(); + + // accessors: + const Text& PatchName() const { return patch_name; } + const Text& PatchTexture() const { return patch_texture; } + const Text& ApronName() const { return apron_name; } + const Text& ApronTexture() const { return apron_texture; } + const Text& WaterTexture() const { return water_texture; } + const Text& DetailTexture0() const { return noise_tex0; } + const Text& DetailTexture1() const { return noise_tex1; } + const Text& HazeName() const { return haze_name; } + const Text& CloudsHigh() const { return clouds_high; } + const Text& ShadesHigh() const { return shades_high; } + const Text& CloudsLow() const { return clouds_low; } + const Text& ShadesLow() const { return shades_low; } + const Text& EnvironmentTexture(int n) const; + + Color SunColor() const { return sun_color[24]; } + Color SkyColor() const { return sky_color[24]; } + Color FogColor() const { return fog_color[24]; } + Color Ambient() const { return ambient[24]; } + Color Overcast() const { return overcast[24]; } + Color CloudColor() const { return cloud_color[24];} + Color ShadeColor() const { return shade_color[24];} + + double LateralScale() const { return scale; } + double MountainScale() const { return mtnscale; } + double FogDensity() const { return fog_density; } + double FogScale() const { return fog_scale; } + double DayPhase() const { return day_phase; } + double HazeFade() const { return haze_fade; } + double CloudAltHigh() const { return clouds_alt_high; } + double CloudAltLow() const { return clouds_alt_low; } + Weather& GetWeather() { return weather; } + List& GetLayers() { return layers; } + + bool IsEclipsed() const { return eclipsed; } + void SetEclipsed(bool e) { eclipsed = e; } + + void LoadSkyColors(const char* bmp_name); + void AddLayer(double h, const char* tile, const char* detail=0); + +protected: + Text patch_name; + Text patch_texture; + Text apron_name; + Text apron_texture; + Text water_texture; + Text env_texture_positive_x; + Text env_texture_negative_x; + Text env_texture_positive_y; + Text env_texture_negative_y; + Text env_texture_positive_z; + Text env_texture_negative_z; + Text noise_tex0; + Text noise_tex1; + Text haze_name; + Text clouds_high; + Text clouds_low; + Text shades_high; + Text shades_low; + + Color sun_color[25]; + Color sky_color[25]; + Color fog_color[25]; + Color ambient[25]; + Color overcast[25]; + Color cloud_color[25]; + Color shade_color[25]; + + double scale; + double mtnscale; + + double fog_density; + double fog_scale; + double day_phase; + double haze_fade; + double clouds_alt_high; + double clouds_alt_low; + + Weather weather; + bool eclipsed; + + List layers; +}; + +#endif TerrainRegion_h + diff --git a/Stars45/Thruster.cpp b/Stars45/Thruster.cpp new file mode 100644 index 0000000..a189a26 --- /dev/null +++ b/Stars45/Thruster.cpp @@ -0,0 +1,595 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Thruster.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Weapon class +*/ + +#include "MemDebug.h" +#include "Thruster.h" +#include "Component.h" +#include "Drive.h" +#include "FlightComp.h" +#include "SystemDesign.h" +#include "Ship.h" +#include "ShipDesign.h" +#include "Sim.h" +#include "CameraDirector.h" +#include "AudioConfig.h" +#include "Random.h" + +#include "Light.h" +#include "Bitmap.h" +#include "Sound.h" +#include "DataLoader.h" +#include "Bolt.h" +#include "Sprite.h" +#include "Game.h" + +// +----------------------------------------------------------------------+ + +static Sound* thruster_resource = 0; +static Sound* thruster_sound = 0; + +extern Bitmap* drive_flare_bitmap[8]; +extern Bitmap* drive_trail_bitmap[8]; + +#define CLAMP(x, a, b) if (x < (a)) x = (a); else if (x > (b)) x = (b); + +// +----------------------------------------------------------------------+ + +ThrusterPort::ThrusterPort(int t, const Point& l, DWORD f, float s) + : type(t), loc(l), flare(0), trail(0), fire(f), burn(0), scale(s) +{ } + +ThrusterPort::~ThrusterPort() +{ + GRAPHIC_DESTROY(flare); + GRAPHIC_DESTROY(trail); +} + +// +----------------------------------------------------------------------+ + +static int sys_value = 2; + +// +----------------------------------------------------------------------+ + +Thruster::Thruster(int dtype, double max_thrust, float flare_scale) + : System(DRIVE, dtype, "Thruster", sys_value, + max_thrust, max_thrust, max_thrust), + ship(0), thrust(1.0f), scale(flare_scale), + avail_x(1.0f), avail_y(1.0f), avail_z(1.0f) +{ + name = Game::GetText("sys.thruster"); + abrv = Game::GetText("sys.thruster.abrv"); + + power_flags = POWER_WATTS; + + ZeroMemory(burn, sizeof(burn)); + + emcon_power[0] = 50; + emcon_power[1] = 50; + emcon_power[2] = 100; +} + +// +----------------------------------------------------------------------+ + +Thruster::Thruster(const Thruster& t) + : System(t), ship(0), + thrust(1.0f), scale(t.scale), + avail_x(1.0f), avail_y(1.0f), avail_z(1.0f) +{ + power_flags = POWER_WATTS; + Mount(t); + + ZeroMemory(burn, sizeof(burn)); + + if (subtype != Drive::STEALTH) { + for (int i = 0; i < t.ports.size(); i++) { + ThrusterPort* p = t.ports[i]; + CreatePort(p->type, p->loc, p->fire, p->scale); + } + } +} + +// +--------------------------------------------------------------------+ + +Thruster::~Thruster() +{ + ports.destroy(); + + if (thruster_sound && thruster_sound->IsPlaying()) { + thruster_sound->Stop(); + } +} + +// +--------------------------------------------------------------------+ + +void +Thruster::Initialize() +{ + static int initialized = 0; + if (initialized) return; + + DataLoader* loader = DataLoader::GetLoader(); + + const int SOUND_FLAGS = Sound::LOCALIZED | + Sound::LOC_3D | + Sound::LOOP | + Sound::LOCKED; + + loader->SetDataPath("Sounds/"); + loader->LoadSound("thruster.wav", thruster_resource, SOUND_FLAGS); + loader->SetDataPath(0); + + if (thruster_resource) + thruster_resource->SetMaxDistance(15.0e3f); + + initialized = 1; +} + +void +Thruster::Close() +{ + delete thruster_resource; + thruster_resource = 0; + + if (thruster_sound) { + thruster_sound->Stop(); + thruster_sound->Release(); + } +} + +// +--------------------------------------------------------------------+ + +void +Thruster::Orient(const Physical* rep) +{ + System::Orient(rep); + + bool hide_all = false; + if (!ship || (ship->IsAirborne() && ship->Class() != Ship::LCA)) { + hide_all = true; + } + + if (ship->Rep() && ship->Rep()->Hidden()) { + hide_all = true; + } + + if (thrust <= 0) { + hide_all = true; + } + + const Matrix& orientation = rep->Cam().Orientation(); + Point ship_loc = rep->Location(); + + for (int i = 0; i < ports.size(); i++) { + ThrusterPort* p = ports[i]; + + Point projector = (p->loc * orientation) + ship_loc; + + if (p->flare) + p->flare->MoveTo(projector); + + if (p->trail) { + double intensity = p->burn; + + if (intensity > 0.5 && !hide_all) { + Bolt* t = (Bolt*) p->trail; + double len = -50 * p->scale * intensity; + + t->Show(); + + switch (p->type) { + case LEFT: t->SetEndPoints(projector, projector + rep->Cam().vrt() * len); break; + case RIGHT: t->SetEndPoints(projector, projector - rep->Cam().vrt() * len); break; + case AFT: t->SetEndPoints(projector, projector + rep->Cam().vpn() * len); break; + case FORE: t->SetEndPoints(projector, projector - rep->Cam().vpn() * len); break; + case BOTTOM: t->SetEndPoints(projector, projector + rep->Cam().vup() * len); break; + case TOP: t->SetEndPoints(projector, projector - rep->Cam().vup() * len); break; + default: t->Hide(); break; + } + } + else { + p->trail->Hide(); + if (p->flare) + p->flare->Hide(); + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +Thruster::ExecFrame(double seconds) +{ + System::ExecFrame(seconds); + + if (ship) { + double rr = 0, pr = 0, yr = 0; + double rd = 0, pd = 0, yd = 0; + + double agility = 1; + double stability = 1; + + FlightComp* flcs = ship->GetFLCS(); + + if (flcs) { + if (!flcs->IsPowerOn() || flcs->Status() < DEGRADED) { + agility = 0.3; + stability = 0.0; + } + } + + // check for thruster damage here: + if (components.size() >= 3) { + int stat = components[0]->Status(); + if (stat == Component::NOMINAL) + avail_x = 1.0f; + else if (stat == Component::DEGRADED) + avail_x = 0.5f; + else + avail_x = 0.0f; + + stat = components[1]->Status(); + if (stat == Component::NOMINAL) + avail_z = 1.0f; + else if (stat == Component::DEGRADED) + avail_z = 0.5f; + else + avail_z = 0.0f; + + stat = components[2]->Status(); + if (stat == Component::NOMINAL) + avail_y = 1.0f; + else if (stat == Component::DEGRADED) + avail_y = 0.5f; + else + avail_y = 0.0f; + } + + // thrust limited by power distribution: + thrust = energy/capacity; + energy = 0.0f; + + if (thrust < 0) + thrust = 0.0f; + + agility *= thrust; + stability *= thrust; + + rr = roll_rate * agility * avail_y; + pr = pitch_rate * agility * avail_y; + yr = yaw_rate * agility * avail_x; + + rd = roll_drag * stability * avail_y; + pd = pitch_drag * stability * avail_y; + yd = yaw_drag * stability * avail_x; + + ship->SetAngularRates(rr,pr,yr); + ship->SetAngularDrag (rd,pd,yd); + } +} + +// +--------------------------------------------------------------------+ + +void +Thruster::SetShip(Ship* s) +{ + const double ROLL_SPEED = PI * 0.0400; + const double PITCH_SPEED = PI * 0.0250; + const double YAW_SPEED = PI * 0.0250; + + ship = s; + + if (ship) { + ShipDesign* design = (ShipDesign*) ship->Design(); + + trans_x = design->trans_x; + trans_y = design->trans_y; + trans_z = design->trans_z; + + roll_drag = design->roll_drag; + pitch_drag = design->pitch_drag; + yaw_drag = design->yaw_drag; + + roll_rate = (float) (design->roll_rate * PI / 180); + pitch_rate = (float) (design->pitch_rate * PI / 180); + yaw_rate = (float) (design->yaw_rate * PI / 180); + + double agility = design->agility; + + if (roll_rate == 0) roll_rate = (float) (agility * ROLL_SPEED); + if (pitch_rate == 0) pitch_rate = (float) (agility * PITCH_SPEED); + if (yaw_rate == 0) yaw_rate = (float) (agility * YAW_SPEED); + } +} + +// +--------------------------------------------------------------------+ + +double +Thruster::TransXLimit() +{ + return trans_x * avail_x; +} + +double +Thruster::TransYLimit() +{ + return trans_y * avail_y; +} + +double +Thruster::TransZLimit() +{ + return trans_z * avail_z; +} + +// +--------------------------------------------------------------------+ + +void +Thruster::ExecTrans(double x, double y, double z) +{ + if (!ship || (ship->IsAirborne() && ship->Class() != Ship::LCA)) { + if (thruster_sound && thruster_sound->IsPlaying()) + thruster_sound->Stop(); + + for (int i = 0; i < ports.size(); i++) { + ThrusterPort* p = ports[i]; + if (p->flare) p->flare->Hide(); + if (p->trail) p->trail->Hide(); + } + + return; + } + + bool sound_on = false; + bool show_flare = true; + + if (ship->Rep() && ship->Rep()->Hidden()) + show_flare = false; + + if (ship->Class() == Ship::LCA && + ship->IsAirborne() && + ship->Velocity().length() < 250 && + ship->AltitudeAGL() > ship->Radius()/2) { + + sound_on = true; + IncBurn(BOTTOM, TOP); + } + + else if (!ship->IsAirborne()) { + double tx_limit = ship->Design()->trans_x; + double ty_limit = ship->Design()->trans_y; + double tz_limit = ship->Design()->trans_z; + + if (x < -0.15 * tx_limit) IncBurn(RIGHT, LEFT); + else if (x > 0.15 * tx_limit) IncBurn(LEFT, RIGHT); + else DecBurn(LEFT, RIGHT); + + if (y < -0.15 * ty_limit) IncBurn(FORE, AFT); + else if (y > 0.15 * ty_limit) IncBurn(AFT, FORE); + else DecBurn(FORE, AFT); + + if (z < -0.15 * tz_limit) IncBurn(TOP, BOTTOM); + else if (z > 0.15 * tz_limit) IncBurn(BOTTOM, TOP); + else DecBurn(TOP, BOTTOM); + + double r, p, y; + ship->GetAngularThrust(r, p, y); + + // Roll seems to have the opposite sign from + // the pitch and yaw thrust factors. Not sure why. + + if (r > 0) IncBurn(ROLL_L, ROLL_R); + else if (r < 0) IncBurn(ROLL_R, ROLL_L); + else DecBurn(ROLL_R, ROLL_L); + + if (y < 0) IncBurn(YAW_L, YAW_R); + else if (y > 0) IncBurn(YAW_R, YAW_L); + else DecBurn(YAW_R, YAW_L); + + if (p < 0) IncBurn(PITCH_D, PITCH_U); + else if (p > 0) IncBurn(PITCH_U, PITCH_D); + else DecBurn(PITCH_U, PITCH_D); + } + + else { + for (int i = 0; i < 12; i++) { + burn[i] -= 0.1f; + if (burn[i] < 0) + burn[i] = 0.0f; + } + } + + for (int i = 0; i < ports.size(); i++) { + ThrusterPort* p = ports[i]; + + if (p->fire) { + p->burn = 0; + + int flag = 1; + + for (int n = 0; n < 12; n++) { + if ((p->fire & flag) != 0 && burn[n] > p->burn) + p->burn = burn[n]; + + flag <<= 1; + } + } + + else { + p->burn = burn[p->type]; + } + + if (p->burn > 0 && thrust > 0) { + sound_on = true; + + if (show_flare) { + Sprite* flare_rep = (Sprite*) p->flare; + if (flare_rep) { + flare_rep->Show(); + flare_rep->SetShade(1); + } + + if (p->trail) { + Bolt* t = (Bolt*) p->trail; + t->Show(); + t->SetShade(1); + } + } + } + else { + if (p->flare) p->flare->Hide(); + if (p->trail) p->trail->Hide(); + } + } + + // thruster sound: + if (ship && ship == Sim::GetSim()->GetPlayerShip() && ports.size() > 0) { + CameraDirector* cam_dir = CameraDirector::GetInstance(); + + // no sound when paused! + if (!Game::Paused() && cam_dir && cam_dir->GetCamera()) { + if (!thruster_sound) { + if (thruster_resource) + thruster_sound = thruster_resource->Duplicate(); + } + + if (thruster_sound) { + if (sound_on) { + Point cam_loc = cam_dir->GetCamera()->Pos(); + double dist = (ship->Location() - cam_loc).length(); + + long max_vol = AudioConfig::EfxVolume(); + long volume = -2000; + + if (volume > max_vol) + volume = max_vol; + + if (dist < thruster_sound->GetMaxDistance()) { + thruster_sound->SetLocation(ship->Location()); + thruster_sound->SetVolume(volume); + thruster_sound->Play(); + } + else if (thruster_sound->IsPlaying()) { + thruster_sound->Stop(); + } + } + else if (thruster_sound->IsPlaying()) { + thruster_sound->Stop(); + } + } + } + } + + ship->SetTransX(x * thrust); + ship->SetTransY(y * thrust); + ship->SetTransZ(z * thrust); +} + +// +--------------------------------------------------------------------+ + +void +Thruster::AddPort(int type, const Point& loc, DWORD fire, float flare_scale) +{ + if (flare_scale == 0) flare_scale = scale; + ThrusterPort* port = new(__FILE__,__LINE__) ThrusterPort(type, loc, fire, flare_scale); + ports.append(port); +} + +void +Thruster::CreatePort(int type, const Point& loc, DWORD fire, float flare_scale) +{ + Bitmap* flare_bmp = drive_flare_bitmap[subtype]; + Bitmap* trail_bmp = drive_trail_bitmap[subtype]; + + if (subtype != Drive::STEALTH) { + Sprite* flare_rep = new(__FILE__,__LINE__) Sprite(flare_bmp); + flare_rep->Scale(flare_scale * 0.667f); + flare_rep->SetShade(0); + + Bolt* trail_rep = new(__FILE__,__LINE__) Bolt(flare_scale * 30, flare_scale * 8, trail_bmp, true); + + ThrusterPort* port = new(__FILE__,__LINE__) ThrusterPort(type, loc, fire, flare_scale); + port->flare = flare_rep; + port->trail = trail_rep; + ports.append(port); + } +} + +// +--------------------------------------------------------------------+ + +int +Thruster::NumThrusters() const +{ + return ports.size(); +} + +Graphic* +Thruster::Flare(int engine) const +{ + if (engine >= 0 && engine < ports.size()) + return ports[engine]->flare; + + return 0; +} + +Graphic* +Thruster::Trail(int engine) const +{ + if (engine >= 0 && engine < ports.size()) + return ports[engine]->trail; + + return 0; +} + +// +--------------------------------------------------------------------+ + +void +Thruster::IncBurn(int inc, int dec) +{ + burn[inc] += 0.1f; + if (burn[inc] > 1) + burn[inc] = 1.0f; + + burn[dec] -= 0.1f; + if (burn[dec] < 0) + burn[dec] = 0.0f; +} + +void +Thruster::DecBurn(int a, int b) +{ + burn[a] -= 0.1f; + if (burn[a] < 0) + burn[a] = 0.0f; + + burn[b] -= 0.1f; + if (burn[b] < 0) + burn[b] = 0.0f; +} + +// +----------------------------------------------------------------------+ + +double +Thruster::GetRequest(double seconds) const +{ + if (!power_on) + return 0; + + for (int i = 0; i < 12; i++) + if (burn[i] != 0) + return power_level * sink_rate * seconds; + + return 0; +} + diff --git a/Stars45/Thruster.h b/Stars45/Thruster.h new file mode 100644 index 0000000..c7f9e00 --- /dev/null +++ b/Stars45/Thruster.h @@ -0,0 +1,97 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Thruster.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Conventional Thruster (system) class +*/ + +#ifndef Thruster_h +#define Thruster_h + +#include "Types.h" +#include "System.h" +#include "Geometry.h" + +// +--------------------------------------------------------------------+ + +class Ship; + +// +--------------------------------------------------------------------+ + +struct ThrusterPort { + static const char* TYPENAME() { return "ThrusterPort"; } + + ThrusterPort(int t, const Point& l, DWORD f, float s); + ~ThrusterPort(); + + int type; + DWORD fire; + float burn; + float scale; + Point loc; + Graphic* flare; + Graphic* trail; +}; + +// +--------------------------------------------------------------------+ + +class Thruster : public System +{ +public: + static const char* TYPENAME() { return "Thruster"; } + + enum Constants { + LEFT, RIGHT, FORE, AFT, TOP, BOTTOM, + YAW_L, YAW_R, PITCH_D, PITCH_U, ROLL_L, ROLL_R + }; + + Thruster(int dtype, double thrust, float flare_scale=0); + Thruster(const Thruster& rhs); + virtual ~Thruster(); + + static void Initialize(); + static void Close(); + + virtual void ExecFrame(double seconds); + virtual void ExecTrans(double x, double y, double z); + virtual void SetShip(Ship* s); + + virtual double TransXLimit(); + virtual double TransYLimit(); + virtual double TransZLimit(); + + virtual void AddPort(int type, const Point& loc, DWORD fire, float flare_scale=0); + virtual void CreatePort(int type, const Point& loc, DWORD fire, float flare_scale); + + int NumThrusters() const; + Graphic* Flare(int engine) const; + Graphic* Trail(int engine) const; + virtual void Orient(const Physical* rep); + virtual double GetRequest(double seconds) const; + +protected: + void IncBurn(int inc, int dec); + void DecBurn(int a, int b); + + Ship* ship; + float thrust; + float scale; + float burn[12]; + + float avail_x, avail_y, avail_z; + float trans_x, trans_y, trans_z; + float roll_rate, pitch_rate, yaw_rate; + float roll_drag, pitch_drag, yaw_drag; + + List ports; +}; + +#endif Thruster_h + diff --git a/Stars45/TrackIR.cpp b/Stars45/TrackIR.cpp new file mode 100644 index 0000000..8145f39 --- /dev/null +++ b/Stars45/TrackIR.cpp @@ -0,0 +1,247 @@ +/* Project Starshatter 4.6 + Destroyer Studios LLC + Copyright © 1997-2006. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: TrackIR.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + TrackIR head tracker interface class +*/ + +#include "MemDebug.h" +#include "TrackIR.h" +#include "NPClient.h" +#include "NPClientWraps.h" + +#include "Game.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +const double TRACK_TOP = -8000; +const double TRACK_BOTTOM = 8000; +const double TRACK_LEFT = 16000; +const double TRACK_RIGHT = -16000; +const double TRACK_XYZ = 24000; + +// +--------------------------------------------------------------------+ + +static Text GetDllFromRegistry() +{ + Text dllLoc; + BYTE dllBuf[1024]; + DWORD dllLen = 0; + HKEY hkey = 0; + + ZeroMemory(dllBuf, sizeof(dllBuf)); + + RegOpenKeyEx(HKEY_CURRENT_USER, + "Software\\NaturalPoint\\NATURALPOINT\\NPClient Location", + 0, + KEY_QUERY_VALUE, + &hkey); + + if (hkey) { + dllLen = 1024; + + LONG result = + RegQueryValueEx(hkey, + "Path", + NULL, + NULL, + dllBuf, + &dllLen); + + if (result == ERROR_SUCCESS && dllLen > 0) + dllLoc = (const char*) dllBuf; + + RegCloseKey(hkey); + } + + return dllLoc; +} + +static const char* NPErrString(NPRESULT r) +{ + switch (r) { + case NP_OK: return "OK"; + case NP_ERR_DEVICE_NOT_PRESENT: return "Device not present"; + case NP_ERR_UNSUPPORTED_OS: return "Unsupported O/S"; + case NP_ERR_INVALID_ARG: return "Invalid argument"; + case NP_ERR_DLL_NOT_FOUND: return "NaturalPoint DLL not found"; + case NP_ERR_NO_DATA: return "No data available"; + case NP_ERR_INTERNAL_DATA: return "Internal error"; + } + + return "Unknown error code"; +} + +// +--------------------------------------------------------------------+ + +TrackIR::TrackIR() : + running(false), frame_signature(0), + az(0), el(0), x(0), y(0), z(0) +{ + Print("*** NaturalPoint Game Client Initialization ***\n"); + + // Hook up the NaturalPoint game client DLL using the wrapper module + NPRESULT result; + Text dllPath = GetDllFromRegistry(); + + // Initialize the NPClient interface + result = NPClient_Init(dllPath); + if (result == NP_OK) { + Print("NPClient - Initialize successful.\n"); + } + else { + Print("NPClient - Unable to initialize interface: %s\n", NPErrString(result)); + return; + } + + // Register the app's window handle + result = NP_RegisterWindowHandle(Game::GetHWND()); + + if (result == NP_OK) { + Print("NPClient - Window handle registration successful.\n"); + } + else { + Print("NPClient - Error registering window handle: %s\n", NPErrString(result)); + return; + } + + // Query the NaturalPoint software version + unsigned short wNPClientVer; + result = NP_QueryVersion( &wNPClientVer ); + + if (result == NP_OK) { + Print("NPClient - NaturalPoint software version: %d.%02d\n", (wNPClientVer >> 8), (wNPClientVer & 0x00FF)); + } + else { + Print("NPClient - Error querying NaturalPoint software version: %s\n", NPErrString(result)); + } + + + // It is *required* that your application registers the Developer ID + // assigned by NaturalPoint! + + // Your assigned developer ID needs to be inserted below! + #define NP_DEVELOPER_ID 6401 + + // NOTE : The title of your project must show up + // in the list of supported titles shown in the Profiles + // tab of the TrackIR software, if it does not then the + // TrackIR software will *not* transmit data to your + // application. If your title is not present in the list, + // you may need to have the TrackIR software perform a + // game list update (to download support for new Developer IDs) + // using the menu item under the "Help" or "Update" menu. + + NP_RegisterProgramProfileID(NP_DEVELOPER_ID); + + unsigned int DataFields = 0; + DataFields |= NPPitch; + DataFields |= NPYaw; + + NP_RequestData(DataFields); + + result = NP_StopCursor(); + if (result == NP_OK) + Print("NPClient - Cursor stopped.\n"); + else + Print("NPClient - Error stopping cursor: %s\n", NPErrString(result)); + + + result = NP_StartDataTransmission(); + if (result == NP_OK) { + Print("NPClient - Data transmission started.\n"); + running = true; + } + else { + Print("NPClient - Error starting data transmission: %s\n", NPErrString(result)); + } + +} + +TrackIR::~TrackIR() +{ + if (running) { + Print("NaturalPoint Game Client Shutdown\n"); + + NP_StopDataTransmission(); + NP_UnregisterWindowHandle(); + } +} + +// +--------------------------------------------------------------------+ + +DWORD +TrackIR::ExecFrame() +{ + TRACKIRDATA tid; + + // Go get the latest data + NPRESULT result = NP_GetData( &tid ); + + if (result == NP_OK) { + // Got data to process ... + running = true; + + // compare the last frame signature to the current one + // if they are not the same then new data has arrived since then + + if (tid.wNPStatus == NPSTATUS_REMOTEACTIVE) { + if (frame_signature != tid.wPFrameSignature) { + double pitch = tid.fNPPitch; + double yaw = tid.fNPYaw; + + if (pitch < 0) { + el = pitch / TRACK_TOP; + } + else { + el = -pitch / TRACK_BOTTOM; + } + + if (yaw < 0) { + az = yaw / TRACK_RIGHT; + } + else { + az = -yaw / TRACK_LEFT; + } + + x = tid.fNPX / TRACK_XYZ * -1; + y = tid.fNPY / TRACK_XYZ; + z = tid.fNPZ / TRACK_XYZ * -1; + + if (z < -0.25) z = -0.25; + + frame_signature = tid.wPFrameSignature; + } + else { + // Either there is no tracking data, the user has + // paused the trackIR, or the call happened before + // the TrackIR was able to update the interface + // with new data + + az *= 0.75; + el *= 0.75; + x *= 0.75; + y *= 0.75; + z *= 0.75; + + result = NP_ERR_NO_DATA; + } + } + else { + // The user has set the device out of trackIR Enhanced Mode + // and into Mouse Emulation mode with the hotkey + result = NP_ERR_NO_DATA; + running = false; + } + } + + return result; +} diff --git a/Stars45/TrackIR.h b/Stars45/TrackIR.h new file mode 100644 index 0000000..0545a14 --- /dev/null +++ b/Stars45/TrackIR.h @@ -0,0 +1,53 @@ +/* Project Starshatter 4.6 + Destroyer Studios LLC + Copyright © 1997-2006. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: TrackIR.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + TrackIR head tracker interface class +*/ + +#ifndef TrackIR_h +#define TrackIR_h + +#include "Types.h" + +// +--------------------------------------------------------------------+ + +class TrackIR +{ +public: + TrackIR(); + ~TrackIR(); + + DWORD ExecFrame(); + + bool IsRunning() const { return running; } + double GetAzimuth() const { return az; } + double GetElevation() const { return el; } + + double GetX() const { return x; } + double GetY() const { return y; } + double GetZ() const { return z; } + +protected: + + bool running; + DWORD stale_frames; + DWORD frame_signature; + + double az; + double el; + + double x; // vrt + double y; // vup + double z; // vpn (i think) +}; + +#endif TrackIR_h + diff --git a/Stars45/Trail.cpp b/Stars45/Trail.cpp new file mode 100644 index 0000000..ab9c7a7 --- /dev/null +++ b/Stars45/Trail.cpp @@ -0,0 +1,200 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Trail.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Missile Trail representation class +*/ + +#include "MemDebug.h" +#include "Trail.h" +#include "Weapon.h" +#include "Sim.h" + +#include "Game.h" +#include "Light.h" +#include "Bitmap.h" +#include "DataLoader.h" +#include "Sound.h" + +// +--------------------------------------------------------------------+ + +Trail::Trail(Bitmap* tex, int n) + : ntrail(0), length0(0), length1(0), texture(tex), maxtrail(n), dim(1) +{ + trans = true; + luminous = true; + + mtl.Kd = Color::White; + mtl.tex_diffuse = texture; + mtl.tex_emissive = texture; + mtl.blend = Video::BLEND_ADDITIVE; + mtl.luminous = true; + + if (maxtrail < 4) maxtrail = 512; + trail = new(__FILE__,__LINE__) Point[maxtrail]; + verts = new(__FILE__,__LINE__) VertexSet(maxtrail*2); + verts->Clear(); + verts->nverts = 0; + + for (int i = 0; i < maxtrail*2; i++) { + verts->diffuse[i] = D3DCOLOR_RGBA(255,255,255,255); + verts->specular[i] = D3DCOLOR_RGBA( 0, 0, 0,255); + verts->tu[i] = 0.0f; + verts->tv[i] = (i & 1) ? 1.0f : 0.0f; + } + + polys = new(__FILE__,__LINE__) Poly[maxtrail]; + + for (i = 0; i < maxtrail; i++) { + polys[i].vertex_set = verts; + polys[i].nverts = 4; + polys[i].material = &mtl; + } + + npolys = 0; + nverts = 0; + width = 6; + length = 0; + dim = 3; + + last_point_time = 0; +} + +Trail::~Trail() +{ + delete [] trail; + delete [] polys; + delete verts; +} + +void +Trail::UpdateVerts(const Point& cam_pos) +{ + if (ntrail < 2) return; + + int bright = 255 - dim*ntrail; + + Point head = trail[1] + loc; + Point tail = trail[0] + loc; + Point vcam = cam_pos - head; + Point vtmp = vcam.cross(head-tail); + vtmp.Normalize(); + Point vlat = vtmp * (width + (0.1 * width * ntrail)); + + verts->loc[0] = tail - vlat; + verts->loc[1] = tail + vlat; + verts->diffuse[0] = 0; + verts->diffuse[1] = 0; + + for (int i = 0; i < ntrail-1; i++) { + bright+=dim; + Point head = trail[i+1] + loc; + Point tail = trail[i] + loc; + Point vcam = cam_pos - head; + Point vtmp = vcam.cross(head-tail); + vtmp.Normalize(); + + float trail_width = (float) (width + (ntrail-i) * width * 0.1); + if (i == ntrail-2) trail_width = (float) (width * 0.7); + Point vlat = vtmp * trail_width; + + verts->loc[2*i+2] = head - vlat; + verts->loc[2*i+3] = head + vlat; + + if (bright <= 0) { + verts->diffuse[2*i+2] = 0; + verts->diffuse[2*i+3] = 0; + } + else { + verts->diffuse[2*i+2] = D3DCOLOR_RGBA(bright,bright,bright,bright); + verts->diffuse[2*i+3] = D3DCOLOR_RGBA(bright,bright,bright,bright); + } + } +} + +void +Trail::Render(Video* video, DWORD flags) +{ + if (!npolys) return; + + if ((flags & RENDER_ADDITIVE) == 0) + return; + + if (video && life) { + const Camera* cam = video->GetCamera(); + + if (cam) + UpdateVerts(cam->Pos()); + + video->DrawPolys(npolys, polys); + } +} + +void +Trail::AddPoint(const Point& v) +{ + if (ntrail >= maxtrail-1) return; + + double real_time = Game::RealTime() / 1000.0; + + if (ntrail == 0) { + radius = 1000; + } + else { + radius = (float) Point(v-loc).length(); + } + + // just adjust the last point: + if (ntrail > 1 && real_time - last_point_time < 0.05) { + trail[ntrail-1] = v; + } + + // add a new point: + else { + last_point_time = real_time; + trail[ntrail++] = v; + + nverts += 2; + verts->nverts = nverts; + + if (ntrail > 1) { + length0 = length1; + length1 = length0 + (trail[ntrail-1]-trail[ntrail-2]).length(); + + polys[npolys].vertex_set = verts; + polys[npolys].nverts = 4; + + polys[npolys].verts[0] = nverts-4; + polys[npolys].verts[1] = nverts-2; + polys[npolys].verts[2] = nverts-1; + polys[npolys].verts[3] = nverts-3; + + float tu1 = (float) length1 / 250.0f; + + verts->tu[2*ntrail-1] = tu1; + verts->tu[2*ntrail-2] = tu1; + + npolys++; + } + } +} + +double +Trail::AverageLength() +{ + double avg = 0; + + for (int i = 0; i < ntrail-1; i++) + avg += (trail[i+1]-trail[i]).length(); + avg /= ntrail; + + return avg; +} + diff --git a/Stars45/Trail.h b/Stars45/Trail.h new file mode 100644 index 0000000..59a3873 --- /dev/null +++ b/Stars45/Trail.h @@ -0,0 +1,60 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Trail.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Missile Trail (Graphic) class +*/ + +#ifndef Trail_h +#define Trail_h + +#include "Types.h" +#include "Geometry.h" +#include "Polygon.h" +#include "SimObject.h" +#include "Graphic.h" + +// +--------------------------------------------------------------------+ + +class Trail : public Graphic +{ +public: + Trail(Bitmap* tex, int n=512); + virtual ~Trail(); + + virtual void UpdateVerts(const Point& cam_pos); + virtual void Render(Video* video, DWORD flags); + virtual void AddPoint(const Point& v); + virtual double AverageLength(); + + virtual void SetWidth(double w) { width = w; } + virtual void SetDim(int d) { dim = d; } + +protected: + int ntrail; + int maxtrail; + Point* trail; + + double length; + double width; + int dim; + + int npolys, nverts; + Poly* polys; + VertexSet* verts; + Bitmap* texture; + Material mtl; + + double length0, length1; + double last_point_time; +}; + +#endif Trail_h + diff --git a/Stars45/VidDlg.cpp b/Stars45/VidDlg.cpp new file mode 100644 index 0000000..329a17b --- /dev/null +++ b/Stars45/VidDlg.cpp @@ -0,0 +1,516 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: VidDlg.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Main Menu Dialog Active Window class +*/ + +#include "MemDebug.h" +#include "VidDlg.h" +#include "BaseScreen.h" +#include "Starshatter.h" +#include "Ship.h" +#include "Terrain.h" +#include "CameraDirector.h" + +#include "DataLoader.h" +#include "Button.h" +#include "ListBox.h" +#include "Slider.h" +#include "Video.h" +#include "VideoSettings.h" +#include "Keyboard.h" +#include "MachineInfo.h" + +// +--------------------------------------------------------------------+ +// DECLARE MAPPING FUNCTIONS: + +DEF_MAP_CLIENT(VidDlg, OnTexSize); +DEF_MAP_CLIENT(VidDlg, OnMode); +DEF_MAP_CLIENT(VidDlg, OnDetail); +DEF_MAP_CLIENT(VidDlg, OnTexture); +DEF_MAP_CLIENT(VidDlg, OnGamma); +DEF_MAP_CLIENT(VidDlg, OnApply); +DEF_MAP_CLIENT(VidDlg, OnCancel); +DEF_MAP_CLIENT(VidDlg, OnAudio); +DEF_MAP_CLIENT(VidDlg, OnVideo); +DEF_MAP_CLIENT(VidDlg, OnOptions); +DEF_MAP_CLIENT(VidDlg, OnControls); +DEF_MAP_CLIENT(VidDlg, OnMod); + +// +--------------------------------------------------------------------+ + +VidDlg::VidDlg(Screen* s, FormDef& def, BaseScreen* mgr) + : FormWindow(s, 0, 0, s->Width(), s->Height()), manager(mgr), + selected_mode(0), selected_detail(0), orig_gamma(128), + selected_card(0), selected_tex_size(0), selected_render(0), selected_texture(0), + mode(0), tex_size(0), detail(0), texture(0), gamma(0), shadows(0), spec_maps(0), + bump_maps(0), lens_flare(0), corona(0), nebula(0), dust(0), + apply(0), cancel(0), vid_btn(0), aud_btn(0), ctl_btn(0), opt_btn(0), mod_btn(0), + closed(true) +{ + stars = Starshatter::GetInstance(); + + Init(def); + orig_gamma = Game::GammaLevel(); +} + +VidDlg::~VidDlg() +{ +} + +// +--------------------------------------------------------------------+ + +void +VidDlg::RegisterControls() +{ + if (apply) + return; + + mode = (ComboBox*) FindControl(203); + REGISTER_CLIENT(EID_SELECT, mode, VidDlg, OnMode); + + tex_size = (ComboBox*) FindControl(204); + REGISTER_CLIENT(EID_SELECT, tex_size, VidDlg, OnTexSize); + + detail = (ComboBox*) FindControl(205); + REGISTER_CLIENT(EID_SELECT, detail, VidDlg, OnDetail); + + texture = (ComboBox*) FindControl(206); + REGISTER_CLIENT(EID_SELECT, texture, VidDlg, OnTexture); + + gamma = (Slider*) FindControl(215); + + if (gamma) { + gamma->SetRangeMin(32); + gamma->SetRangeMax(224); + gamma->SetStepSize(16); + + REGISTER_CLIENT(EID_CLICK, gamma, VidDlg, OnGamma); + } + + lens_flare = (ComboBox*) FindControl(211); + corona = (ComboBox*) FindControl(212); + nebula = (ComboBox*) FindControl(213); + dust = (ComboBox*) FindControl(214); + shadows = (ComboBox*) FindControl(222); + spec_maps = (ComboBox*) FindControl(223); + bump_maps = (ComboBox*) FindControl(224); + + apply = (Button*) FindControl(1); + REGISTER_CLIENT(EID_CLICK, apply, VidDlg, OnApply); + + cancel = (Button*) FindControl(2); + REGISTER_CLIENT(EID_CLICK, cancel, VidDlg, OnCancel); + + vid_btn = (Button*) FindControl(901); + REGISTER_CLIENT(EID_CLICK, vid_btn, VidDlg, OnVideo); + + aud_btn = (Button*) FindControl(902); + REGISTER_CLIENT(EID_CLICK, aud_btn, VidDlg, OnAudio); + + ctl_btn = (Button*) FindControl(903); + REGISTER_CLIENT(EID_CLICK, ctl_btn, VidDlg, OnControls); + + opt_btn = (Button*) FindControl(904); + REGISTER_CLIENT(EID_CLICK, opt_btn, VidDlg, OnOptions); + + mod_btn = (Button*) FindControl(905); + if (mod_btn) + REGISTER_CLIENT(EID_CLICK, mod_btn, VidDlg, OnMod); +} + +// +--------------------------------------------------------------------+ + +void +VidDlg::Show() +{ + FormWindow::Show(); + + if (closed) { + bool fullscreen = true; + + if (stars) { + selected_render = 9; + selected_card = 0; + + int n = stars->MaxTexSize(); + + for (int i = 0; i < 7; i++) { + if (n <= pow(2, i+6)) { + selected_tex_size = i; + break; + } + } + + Video* video = Game::GetVideo(); + + if (video) { + if (shadows) + shadows->SetSelection(video->IsShadowEnabled()); + + if (spec_maps) + spec_maps->SetSelection(video->IsSpecMapEnabled()); + + if (bump_maps) + bump_maps->SetSelection(video->IsBumpMapEnabled()); + + fullscreen = video->IsFullScreen(); + } + + if (lens_flare) + lens_flare->SetSelection(stars->LensFlare()); + + if (corona) + corona->SetSelection(stars->Corona()); + + if (nebula) + nebula->SetSelection(stars->Nebula()); + + if (dust) + dust->SetSelection(stars->Dust()); + } + + selected_detail = Terrain::DetailLevel() - 2; + selected_texture = true; + + if (mode) { + BuildModeList(); + mode->SetSelection(selected_mode); + mode->SetEnabled(fullscreen); + } + + if (tex_size) + tex_size->SetSelection(selected_tex_size); + + if (detail) + detail->SetSelection(selected_detail); + + if (texture) + texture->SetSelection(selected_texture); + + + if (gamma) { + orig_gamma = Game::GammaLevel(); + gamma->SetValue(orig_gamma); + } + } + + if (vid_btn) vid_btn->SetButtonState(1); + if (aud_btn) aud_btn->SetButtonState(0); + if (ctl_btn) ctl_btn->SetButtonState(0); + if (opt_btn) opt_btn->SetButtonState(0); + if (mod_btn) mod_btn->SetButtonState(0); + + closed = false; +} + +// +--------------------------------------------------------------------+ + +void +VidDlg::ExecFrame() +{ + if (Keyboard::KeyDown(VK_RETURN)) { + OnApply(0); + } +} + +// +--------------------------------------------------------------------+ + +void +VidDlg::OnMode(AWEvent* event) +{ + selected_mode = mode->GetSelectedIndex(); +} + +void +VidDlg::OnTexSize(AWEvent* event) +{ + selected_tex_size = tex_size->GetSelectedIndex(); +} + +void +VidDlg::OnDetail(AWEvent* event) +{ + selected_detail = detail->GetSelectedIndex(); +} + +void +VidDlg::OnTexture(AWEvent* event) +{ + selected_texture = texture->GetSelectedIndex(); +} + +void +VidDlg::OnGamma(AWEvent* event) +{ + int g = gamma->GetValue(); + + if (g >= 0 && g <= 255) { + Game::SetGammaLevel(g); + } +} + +// +--------------------------------------------------------------------+ + +void VidDlg::OnAudio(AWEvent* event) { manager->ShowAudDlg(); } +void VidDlg::OnVideo(AWEvent* event) { manager->ShowVidDlg(); } +void VidDlg::OnOptions(AWEvent* event) { manager->ShowOptDlg(); } +void VidDlg::OnControls(AWEvent* event) { manager->ShowCtlDlg(); } +void VidDlg::OnMod(AWEvent* event) { manager->ShowModDlg(); } + +// +--------------------------------------------------------------------+ + +void +VidDlg::OnApply(AWEvent* event) +{ + manager->ApplyOptions(); +} + +void +VidDlg::OnCancel(AWEvent* event) +{ + manager->CancelOptions(); +} + +// +--------------------------------------------------------------------+ + +void +VidDlg::Apply() +{ + if (closed) return; + + int w = 800; + int h = 600; + int d = 32; + int a = 1; + int g = 128; + int t = 2048; + float bias = 0; + + const char* mode_desc = mode->GetSelectedItem(); + + if (strstr(mode_desc, "800 x 600")) { + w = 800; + h = 600; + } + else if (strstr(mode_desc, "1024 x 768")) { + w = 1024; + h = 768; + } + else if (strstr(mode_desc, "1152 x 864")) { + w = 1152; + h = 864; + } + else if (strstr(mode_desc, "1280 x 800")) { + w = 1280; + h = 800; + } + else if (strstr(mode_desc, "1280 x 960")) { + w = 1280; + h = 960; + } + else if (strstr(mode_desc, "1280 x 1024")) { + w = 1280; + h = 1024; + } + else if (strstr(mode_desc, "1440 x 900")) { + w = 1440; + h = 900; + } + else if (strstr(mode_desc, "1600 x 900")) { + w = 1600; + h = 900; + } + else if (strstr(mode_desc, "1600 x 1200")) { + w = 1600; + h = 1200; + } + + if (strstr(mode_desc, "x 16")) + d = 16; + else if (strstr(mode_desc, "x 32")) + d = 32; + + if (selected_tex_size) + t = (int) pow(2, selected_tex_size + 6); + + bool video_change = false; + + Video* video = Game::GetVideo(); + if (video) { + const VideoSettings* vs = video->GetVideoSettings(); + + if (vs) + bias = vs->depth_bias; + + if (video->IsFullScreen()) { + if (video->Width() != w) + video_change = true; + + if (video->Height() != h) + video_change = true; + + if (video->Depth() != d) + video_change = true; + } + else if (vs) { + w = vs->fullscreen_mode.width; + h = vs->fullscreen_mode.height; + + if (vs->fullscreen_mode.format == VideoMode::FMT_R5G6B5 || + vs->fullscreen_mode.format == VideoMode::FMT_R5G5B5) + d = 16; + else + d = 32; + } + + if (Game::MaxTexSize() != t) + video_change = true; + } + + FILE* f = 0; + + if (video_change) + f = fopen("video2.cfg", "w"); + else + f = fopen("video.cfg", "w"); + + if (gamma) { + g = gamma->GetValue(); + } + + if (f) { + fprintf(f, "VIDEO\n\n"); + fprintf(f, "width: %4d\n", w); + fprintf(f, "height: %4d\n", h); + fprintf(f, "depth: %4d\n", d); + fprintf(f, "\n"); + fprintf(f, "max_tex: %d\n", (int) pow(2, 6 + selected_tex_size)); + fprintf(f, "primary3D: %s\n", (a>0)?"true":"false"); + fprintf(f, "gamma: %4d\n", g); + fprintf(f, "\n"); + fprintf(f, "terrain_detail_level: %d\n", selected_detail + 2); + fprintf(f, "terrain_texture_enable: %s\n", selected_texture ? "true" : "false"); + fprintf(f, "\n"); + fprintf(f, "shadows: %s\n", shadows->GetSelectedIndex() ? "true" : "false"); + fprintf(f, "spec_maps: %s\n", spec_maps->GetSelectedIndex() ? "true" : "false"); + fprintf(f, "bump_maps: %s\n", bump_maps->GetSelectedIndex() ? "true" : "false"); + fprintf(f, "bias: %f\n", bias); + fprintf(f, "\n"); + fprintf(f, "flare: %s\n", lens_flare->GetSelectedIndex() ? "true" : "false"); + fprintf(f, "corona: %s\n", corona->GetSelectedIndex() ? "true" : "false"); + fprintf(f, "nebula: %s\n", nebula->GetSelectedIndex() ? "true" : "false"); + fprintf(f, "dust: %d\n", dust->GetSelectedIndex()); + + if (CameraDirector::GetRangeLimit() != 300e3) + fprintf(f, " cam_range_max: %f,\n", CameraDirector::GetRangeLimit()); + + fclose(f); + } + + Starshatter* stars = Starshatter::GetInstance(); + + if (stars) { + if (video_change) + stars->RequestChangeVideo(); + else + stars->LoadVideoConfig("video.cfg"); + } + + closed = true; +} + +void +VidDlg::Cancel() +{ + Game::SetGammaLevel(orig_gamma); + closed = true; +} + +// +--------------------------------------------------------------------+ + +void +VidDlg::BuildModeList() +{ + char mode_desc[32]; + Starshatter* stars = Starshatter::GetInstance(); + + mode->ClearItems(); + selected_mode = 0; + + if (Game::DisplayModeSupported( 800, 600, 16)) mode->AddItem("800 x 600 x 16"); + if (Game::DisplayModeSupported( 800, 600, 32)) mode->AddItem("800 x 600 x 32"); + + if (Game::DisplayModeSupported(1024, 768, 16)) mode->AddItem("1024 x 768 x 16"); + if (Game::DisplayModeSupported(1024, 768, 32)) mode->AddItem("1024 x 768 x 32"); + + if (Game::DisplayModeSupported(1152, 864, 16)) mode->AddItem("1152 x 864 x 16"); + if (Game::DisplayModeSupported(1152, 864, 32)) mode->AddItem("1152 x 864 x 32"); + + if (Game::DisplayModeSupported(1280, 800, 16)) mode->AddItem("1280 x 800 x 16"); + if (Game::DisplayModeSupported(1280, 800, 32)) mode->AddItem("1280 x 800 x 32"); + + if (Game::DisplayModeSupported(1280, 960, 16)) mode->AddItem("1280 x 960 x 16"); + if (Game::DisplayModeSupported(1280, 960, 32)) mode->AddItem("1280 x 960 x 32"); + + if (Game::DisplayModeSupported(1280,1024, 16)) mode->AddItem("1280 x 1024 x 16"); + if (Game::DisplayModeSupported(1280,1024, 32)) mode->AddItem("1280 x 1024 x 32"); + + if (Game::DisplayModeSupported(1440, 900, 16)) mode->AddItem("1440 x 900 x 16"); + if (Game::DisplayModeSupported(1440, 900, 32)) mode->AddItem("1440 x 900 x 32"); + + if (Game::DisplayModeSupported(1600, 900, 16)) mode->AddItem("1600 x 900 x 16"); + if (Game::DisplayModeSupported(1600, 900, 32)) mode->AddItem("1600 x 900 x 32"); + + if (Game::DisplayModeSupported(1600,1200, 16)) mode->AddItem("1600 x 1200 x 16"); + if (Game::DisplayModeSupported(1600,1200, 32)) mode->AddItem("1600 x 1200 x 32"); + + if (Game::DisplayModeSupported(1680,1050, 16)) mode->AddItem("1680 x 1050 x 16"); + if (Game::DisplayModeSupported(1680,1050, 32)) mode->AddItem("1680 x 1050 x 32"); + + Video* video = Game::GetVideo(); + + if (stars && video) { + switch (video->Width()) { + case 800: strcpy(mode_desc, "800 x 600 x "); break; + default: + case 1024: strcpy(mode_desc, "1024 x 768 x "); break; + case 1152: strcpy(mode_desc, "1152 x 864 x "); break; + case 1280: + if (video->Height() < 900) + strcpy(mode_desc, "1280 x 800 x "); + if (video->Height() < 1000) + strcpy(mode_desc, "1280 x 960 x "); + else + strcpy(mode_desc, "1280 x 1024 x "); + break; + case 1440: strcpy(mode_desc, "1440 x 900 x "); break; + case 1600: + if (video->Height() < 1000) + strcpy(mode_desc, "1600 x 900 x "); + else + strcpy(mode_desc, "1600 x 1200 x "); + break; + } + + switch (video->Depth()) { + default: + case 8: strcat(mode_desc, "8"); break; + case 16: strcat(mode_desc, "16"); break; + case 32: strcat(mode_desc, "32"); break; + } + + for (int i = 0; i < mode->GetCount(); i++) { + if (!strcmp(mode->GetItem(i), mode_desc)) + selected_mode = i; + } + } +} diff --git a/Stars45/VidDlg.h b/Stars45/VidDlg.h new file mode 100644 index 0000000..c0f8ea1 --- /dev/null +++ b/Stars45/VidDlg.h @@ -0,0 +1,106 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: VidDlg.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Main Menu Dialog Active Window class +*/ + +#ifndef VidDlg_h +#define VidDlg_h + +#include "Types.h" +#include "FormWindow.h" +#include "Bitmap.h" +#include "Button.h" +#include "ComboBox.h" +#include "ListBox.h" +#include "Slider.h" +#include "Font.h" + +// +--------------------------------------------------------------------+ + +class BaseScreen; +class Starshatter; + +// +--------------------------------------------------------------------+ + +class VidDlg : public FormWindow +{ +public: + VidDlg(Screen* s, FormDef& def, BaseScreen* mgr); + virtual ~VidDlg(); + + virtual void RegisterControls(); + virtual void Show(); + virtual void ExecFrame(); + + // Operations: + virtual void OnTexSize(AWEvent* event); + virtual void OnMode(AWEvent* event); + virtual void OnDetail(AWEvent* event); + virtual void OnTexture(AWEvent* event); + virtual void OnGamma(AWEvent* event); + + virtual void Apply(); + virtual void Cancel(); + + virtual void OnApply(AWEvent* event); + virtual void OnCancel(AWEvent* event); + + virtual void OnAudio(AWEvent* event); + virtual void OnVideo(AWEvent* event); + virtual void OnOptions(AWEvent* event); + virtual void OnControls(AWEvent* event); + virtual void OnMod(AWEvent* event); + +protected: + virtual void BuildModeList(); + + BaseScreen* manager; + Starshatter* stars; + + ComboBox* mode; + ComboBox* tex_size; + ComboBox* detail; + ComboBox* texture; + + ComboBox* shadows; + ComboBox* bump_maps; + ComboBox* spec_maps; + + ComboBox* lens_flare; + ComboBox* corona; + ComboBox* nebula; + ComboBox* dust; + + Slider* gamma; + + Button* aud_btn; + Button* vid_btn; + Button* opt_btn; + Button* ctl_btn; + Button* mod_btn; + + Button* apply; + Button* cancel; + + int selected_render; + int selected_card; + int selected_tex_size; + int selected_mode; + int selected_detail; + int selected_texture; + int orig_gamma; + + bool closed; +}; + +#endif VidDlg_h + diff --git a/Stars45/Weapon.cpp b/Stars45/Weapon.cpp new file mode 100644 index 0000000..163a71a --- /dev/null +++ b/Stars45/Weapon.cpp @@ -0,0 +1,1184 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Weapon.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Weapon class +*/ + +#include "MemDebug.h" +#include "Weapon.h" +#include "Shot.h" +#include "Drone.h" +#include "Contact.h" +#include "Ship.h" +#include "Sim.h" +#include "SimEvent.h" +#include "Random.h" + +#include "NetGame.h" +#include "NetUtil.h" + +#include "Game.h" +#include "Solid.h" + +// +----------------------------------------------------------------------+ + +Weapon::Weapon(WeaponDesign* d, int nmuz, Vec3* muzzles, double az, double el) + : System(WEAPON, d->type, d->name, d->value, + d->capacity, d->capacity, d->recharge_rate), + design(d), group(d->group), ammo(-1), ripple_count(0), + aim_azimuth((float) az), aim_elevation((float) el), + old_azimuth(0.0f), old_elevation(0.0f), aim_time(0), + enabled(true), refire(0.0f), + mass(d->carry_mass), resist(d->carry_resist), + guided(d->guided), shot_speed(d->speed), + active_barrel(0), locked(false), centered(false), firing(false), blocked(false), + index(0), target(0), subtarget(0), beams(0), orders(MANUAL), + control(SINGLE_FIRE), sweep(SWEEP_TIGHT), turret(0), turret_base(0) +{ + ZeroMemory(visible_stores, sizeof(visible_stores)); + + if (design->primary) + abrv = Game::GetText("sys.weapon.primary.abrv"); + else + abrv = Game::GetText("sys.weapon.secondary.abrv"); + + nbarrels = nmuz; + + if (nbarrels > MAX_BARRELS) + nbarrels = MAX_BARRELS; + + if (nbarrels == 0 && design->nbarrels > 0) { + nbarrels = design->nbarrels; + + for (int i = 0; i < nbarrels; i++) + muzzle_pts[i] = rel_pts[i] = design->muzzle_pts[i] * design->scale; + + ammo = design->ammo * nbarrels; + } + else if (nbarrels == 1 && design->nstores > 0) { + nbarrels = design->nstores; + + for (int i = 0; i < nbarrels; i++) + muzzle_pts[i] = rel_pts[i] = (muzzles[0] + design->attachments[i]); + + ammo = nbarrels; + } + else { + for (int i = 0; i < nbarrels; i++) + muzzle_pts[i] = rel_pts[i] = muzzles[i]; + + ammo = design->ammo * nbarrels; + } + + if (design->syncro) + active_barrel = -1; + + emcon_power[0] = 0; + emcon_power[1] = 0; + emcon_power[2] = 100; + + aim_az_max = design->aim_az_max; + aim_az_min = design->aim_az_min; + aim_az_rest = design->aim_az_rest; + + aim_el_max = design->aim_el_max; + aim_el_min = design->aim_el_min; + aim_el_rest = design->aim_el_rest; +} + +// +----------------------------------------------------------------------+ + +Weapon::Weapon(const Weapon& w) + : System(w), design(w.design), ammo(-1), ripple_count(0), + enabled(true), refire(0.0f), + mass(w.mass), resist(w.resist), + aim_azimuth(w.aim_azimuth), aim_elevation(w.aim_elevation), + old_azimuth(0.0f), old_elevation(0.0f), aim_time(0), + guided(w.guided), shot_speed(w.shot_speed), + active_barrel(0), locked(false), centered(false), firing(false), blocked(false), + target(0), subtarget(0), beams(0), orders(MANUAL), + control(SINGLE_FIRE), sweep(SWEEP_TIGHT), group(w.group), + aim_az_max(w.aim_az_max), aim_az_min(w.aim_az_min), aim_az_rest(w.aim_az_rest), + aim_el_max(w.aim_el_max), aim_el_min(w.aim_el_min), aim_el_rest(w.aim_el_rest), + turret(0), turret_base(0) +{ + Mount(w); + ZeroMemory(visible_stores, sizeof(visible_stores)); + + nbarrels = w.nbarrels; + + for (int i = 0; i < nbarrels; i++) { + muzzle_pts[i] = rel_pts[i] = w.muzzle_pts[i]; + } + + ammo = design->ammo * nbarrels; + + if (design->syncro) + active_barrel = -1; + + if (design->beam) { + beams = new(__FILE__,__LINE__) Shot* [nbarrels]; + ZeroMemory(beams, sizeof(Shot*) * nbarrels); + } + + if (aim_az_rest >= 2*PI) + aim_az_rest = design->aim_az_rest; + + if (aim_el_rest >= 2*PI) + aim_el_rest = design->aim_el_rest; +} + +// +--------------------------------------------------------------------+ + +Weapon::~Weapon() +{ + if (beams) { + for (int i = 0; i < nbarrels; i++) { + if (beams[i]) { + Ignore(beams[i]); + delete beams[i]; + beams[i] = 0; + } + } + + delete [] beams; + } + + GRAPHIC_DESTROY(turret); + GRAPHIC_DESTROY(turret_base); + + for (int i = 0; i < MAX_BARRELS; i++) + GRAPHIC_DESTROY(visible_stores[i]); +} + +// +--------------------------------------------------------------------+ + +bool +Weapon::IsPrimary() const +{ + return design->primary; +} + +bool +Weapon::IsDrone() const +{ + return design->drone; +} + +bool +Weapon::IsDecoy() const +{ + return design->decoy_type != 0; +} + +bool +Weapon::IsProbe() const +{ + return design->probe != 0; +} + +bool +Weapon::IsMissile() const +{ + return !design->primary; +} + +bool +Weapon::IsBeam() const +{ + return design->beam; +} + +// +--------------------------------------------------------------------+ + +Shot* +Weapon::GetBeam(int i) +{ + if (beams && i >= 0 && i < nbarrels) + return beams[i]; + + return 0; +} + +// +--------------------------------------------------------------------+ + +void +Weapon::SetOwner(Ship* s) +{ + ship = s; + + if (design->turret_model) { + Solid* t = new(__FILE__,__LINE__) Solid; + t->UseModel(design->turret_model); + turret = t; + } + + if (design->turret_base_model) { + Solid* t = new(__FILE__,__LINE__) Solid; + t->UseModel(design->turret_base_model); + turret_base = t; + } + + if (!design->primary && + design->visible_stores && + ammo == nbarrels && + design->shot_model != 0) + { + for (int i = 0; i < nbarrels; i++) { + Solid* s = new(__FILE__,__LINE__) Solid; + s->UseModel(design->shot_model); + + visible_stores[i] = s; + } + } +} + +Solid* +Weapon::GetTurret() +{ + return turret; +} + +Solid* +Weapon::GetTurretBase() +{ + return turret_base; +} + +Solid* +Weapon::GetVisibleStore(int i) +{ + if (i >= 0 && i < MAX_BARRELS) + return visible_stores[i]; + + return 0; +} + +void +Weapon::SetAmmo(int a) +{ + if (a >= 0) { + if (active_barrel >= 0 && design->visible_stores) { + while (a < ammo) { + if (active_barrel >= nbarrels) + active_barrel = 0; + + if (visible_stores[active_barrel]) { + GRAPHIC_DESTROY(visible_stores[active_barrel]); + active_barrel++; + ammo--; + } + } + } + + ammo = a; + } +} + +// +--------------------------------------------------------------------+ + +void +Weapon::ExecFrame(double seconds) +{ + System::ExecFrame(seconds); + + if (refire > 0) + refire -= (float) seconds; + + locked = false; + centered = false; + + if (!ship) + return; + + if (orders == POINT_DEFENSE && enabled) + SelectTarget(); + + if (beams && !target) { + for (int i = 0; i < nbarrels; i++) { + if (beams[i]) { + // aim beam straight: + Aim(); + SetBeamPoints(false); + return; + } + } + } + + if (design->self_aiming) { + Track(target, subtarget); + } + else if (turret) { + ZeroAim(); + } + + if (ship->CheckFire()) + return; + + // aim beam at target: + bool aim_beams = false; + + if (beams) { + for (int i = 0; i < nbarrels; i++) { + if (beams[i]) { + aim_beams = true; + SetBeamPoints(true); + break; + } + } + } + + if (!aim_beams) { + if (ripple_count > 0) { + if (Fire()) + ripple_count--; + } + + else if (locked && !blocked) { + if (!ship->IsHostileTo(target)) + return; + + if (orders == AUTO && centered) { + if (energy >= design->charge && + (ammo < 0 || target && target->Integrity() >= 1) && + objective.length() < design->max_range) + Fire(); + } + + else if (orders == POINT_DEFENSE) { + if (energy >= design->min_charge && + (ammo < 0 || target && target->Integrity() >= 1) && + objective.length() < design->max_range) + Fire(); + } + } + } +} + +// +----------------------------------------------------------------------+ + +void +Weapon::Distribute(double delivered_energy, double seconds) +{ + if (UsesWatts()) { + if (seconds < 0.01) + seconds = 0.01; + + // convert Joules to Watts: + energy = (float) (delivered_energy/seconds); + } + + else if (!Game::Paused()) { + energy += (float) (delivered_energy * 1.25); + + if (energy > capacity) + energy = capacity; + + else if (energy < 0) + energy = 0.0f; + } +} + + +// +--------------------------------------------------------------------+ + +bool +Weapon::Update(SimObject* obj) +{ + if (obj == target) { + target = 0; + } + + else if (beams) { + for (int i = 0; i < nbarrels; i++) + if (obj == beams[i]) + beams[i] = 0; + } + + return SimObserver::Update(obj); +} + +const char* +Weapon::GetObserverName() const +{ + static char name[256]; + sprintf(name, "Weapon %s", design->name); + return name; +} + +// +--------------------------------------------------------------------+ + +void +Weapon::SetFiringOrders(int o) +{ + if (o >= MANUAL && o <= POINT_DEFENSE) + orders = o; +} + +void +Weapon::SetControlMode(int m) +{ + if (m >= SINGLE_FIRE && m <= SALVO_FIRE) + control = m; +} + +void +Weapon::SetSweep(int s) +{ + if (s >= SWEEP_NONE && s <= SWEEP_WIDE) + sweep = s; +} + +// +--------------------------------------------------------------------+ + +bool +Weapon::CanTarget(DWORD classification) const +{ + return (design->target_type & classification) ? true : false; +} + +// +--------------------------------------------------------------------+ + +void +Weapon::SetTarget(SimObject* targ, System* sub) +{ + // check self targeting: + if (targ == (SimObject*) ship) + return; + + // check target class filter: + if (targ) { + switch (targ->Type()) { + case SimObject::SIM_SHIP: { + Ship* tgt_ship = (Ship*) targ; + + if ((tgt_ship->Class() & design->target_type) == 0) + return; + } + break; + + case SimObject::SIM_SHOT: + return; + + case SimObject::SIM_DRONE: { + if ((design->target_type & Ship::DRONE) == 0) + return; + } + break; + + default: + return; + } + } + + // if ok, target this object: + if (target != targ) { + target = targ; + + if (target) + Observe(target); + } + + subtarget = sub; +} + +// +--------------------------------------------------------------------+ + +void +Weapon::SelectTarget() +{ + bool select_locked = false; + SimObject* targ = 0; + double dist = 1e9; + double az = 0; + double el = 0; + + if (ammo && enabled && (availability > crit_level)) { + ZeroAim(); + + ListIter contact = ship->ContactList(); + + // lock onto any threatening shots first (if we can): + if (design->target_type & Ship::DRONE) { + while (++contact) { + Shot* c_shot = contact->GetShot(); + + if (c_shot && contact->Threat(ship)) { + + // distance from self to target: + double distance = Point(c_shot->Location() - muzzle_pts[0]).length(); + + if (distance > design->min_range && + distance < design->max_range && + distance < dist) { + // check aim basket: + select_locked = CanLockPoint(c_shot->Location(), az, el); + + if (select_locked) { + targ = c_shot; + dist = distance; + } + } + } + } + } + + // lock onto a threatening ship only if it is (much) closer: + dist *= 0.2; + contact.reset(); + while (++contact) { + Ship* c_ship = contact->GetShip(); + + if (!c_ship) continue; + + // can we lock onto this target? + if ((c_ship->IsRogue() || c_ship->GetIFF() > 0 && c_ship->GetIFF() != ship->GetIFF()) && + (c_ship->Class() & design->target_type) && + c_ship->Weapons().size() > 0) { + // distance from self to target: + double distance = Point(c_ship->Location() - muzzle_pts[0]).length(); + + if (distance < design->max_range && distance < dist) { + // check aim basket: + select_locked = CanLockPoint(c_ship->Location(), az, el); + + if (select_locked) { + targ = c_ship; + dist = distance; + } + } + } + } + } + + if (!ammo || !enabled) { + SetTarget(0,0); + locked = false; + } + + else { + SetTarget(targ, 0); + } +} + +// +--------------------------------------------------------------------+ + +int +Weapon::Track(SimObject* targ, System* sub) +{ + if (ammo && enabled && (availability > crit_level)) { + firing = 0; + Aim(); + } + else if (turret) { + ZeroAim(); + } + + return locked; +} + +// +--------------------------------------------------------------------+ + +Shot* +Weapon::Fire() +{ + if (Game::Paused()) + return 0; + + if (ship && ship->CheckFire()) + return 0; + + if (ship->IsStarship() && target && !centered) + return 0; + + if (beams && active_barrel >= 0 && active_barrel < nbarrels && beams[active_barrel]) + return 0; + + Shot* shot = 0; + + if (ammo && enabled && + (refire <= 0) && (energy > design->min_charge) && + (availability > crit_level)) { + + refire = design->refire_delay; + + NetGame* net_game = NetGame::GetInstance(); + bool net_client = net_game ? net_game->IsClient() : false; + + // all barrels + if (active_barrel < 0) { + if (net_client || IsPrimary()) + NetUtil::SendWepTrigger(this, nbarrels); + + if (!net_game || IsPrimary()) { + for (int i = 0; i < nbarrels; i++) + shot = FireBarrel(i); + } + + else if (net_game && net_game->IsServer() && IsMissile()) { + for (int i = 0; i < nbarrels; i++) { + shot = FireBarrel(i); + NetUtil::SendWepRelease(this, shot); + } + } + } + + // single barrel + else { + if (net_client || IsPrimary()) + NetUtil::SendWepTrigger(this, nbarrels); + + if (!net_game || IsPrimary()) { + shot = FireBarrel(active_barrel++); + } + + else if (net_game && net_game->IsServer() && IsMissile()) { + shot = FireBarrel(active_barrel++); + NetUtil::SendWepRelease(this, shot); + } + + if (active_barrel >= nbarrels) { + active_barrel = 0; + refire += design->salvo_delay; + } + } + + if (design->ripple_count > 0 && ripple_count <= 0) + ripple_count = design->ripple_count-1; + + if (status != NOMINAL) + refire *= 2; + } + + return shot; +} + +// +--------------------------------------------------------------------+ + +Shot* +Weapon::NetFirePrimary(SimObject* tgt, System* sub, int count) +{ + Shot* shot = 0; + + if (!IsPrimary() || Game::Paused()) + return shot; + + if (active_barrel < 0 || active_barrel >= nbarrels) + active_barrel = 0; + + target = tgt; + subtarget = sub; + aim_time = 0; + + for (int i = 0; i < count; i++) { + shot = FireBarrel(active_barrel++); + + if (active_barrel >= nbarrels) { + active_barrel = 0; + refire += design->salvo_delay; + } + } + + if (target) + Observe(target); + + return shot; +} + +Shot* +Weapon::NetFireSecondary(SimObject* tgt, System* sub, DWORD objid) +{ + Shot* shot = 0; + + if (IsPrimary() || Game::Paused()) + return shot; + + if (active_barrel < 0 || active_barrel >= nbarrels) + active_barrel = 0; + + target = tgt; + subtarget = sub; + aim_time = 0; + + shot = FireBarrel(active_barrel++); + + if (active_barrel >= nbarrels) { + active_barrel = 0; + refire += design->salvo_delay; + } + + if (shot) + shot->SetObjID(objid); + + if (target) + Observe(target); + + return shot; +} + +// +--------------------------------------------------------------------+ + +Shot* +Weapon::FireBarrel(int n) +{ + const Point& base_vel = ship->Velocity(); + Shot* shot = 0; + SimRegion* region = ship->GetRegion(); + + if (!region || n < 0 || n >= nbarrels || Game::Paused()) + return 0; + + firing = 1; + Aim(); + + Camera rail_cam; + rail_cam.Clone(aim_cam); + + Point shotpos = muzzle_pts[n]; + if (design->length > 0) + shotpos = shotpos + aim_cam.vpn() * design->length; + + // guns may be slewed towards target: + if (design->primary) { + shot = CreateShot(shotpos, aim_cam, design, ship); + + if (shot) { + shot->SetVelocity(shot->Velocity() + base_vel); + } + } + + // missiles always launch in rail direction: + else { + // unless they are on a mobile launcher + if (turret && design->self_aiming) { + shot = CreateShot(shotpos, aim_cam, design, ship); + shot->SetVelocity(base_vel); + } + else { + shot = CreateShot(shotpos, rail_cam, design, ship); + if (shot /* && !turret */) { + Matrix orient = ship->Cam().Orientation(); + if (aim_azimuth != 0) orient.Yaw(aim_azimuth); + if (aim_elevation != 0) orient.Pitch(aim_elevation); + + Point eject = design->eject * orient; + shot->SetVelocity(base_vel + eject); + } + } + + if (shot && visible_stores[n]) { + GRAPHIC_DESTROY(visible_stores[n]); + } + } + + if (shot) { + if (ammo > 0) + ammo--; + + if (guided && target) + shot->SeekTarget(target, subtarget); + + float shot_load; + if (energy > design->charge) + shot_load = design->charge; + else + shot_load = energy; + + energy -= shot_load; + shot->SetCharge(shot_load * availability); + + if (target && design->flak && !design->guided) { + double speed = shot->Velocity().length(); + double range = (target->Location() - shot->Location()).length(); + + if (range > design->min_range && range < design->max_range) { + shot->SetFuse(range / speed); + } + } + + region->InsertObject(shot); + + if (beams) { + beams[n] = shot; + Observe(beams[n]); + + // aim beam at target: + SetBeamPoints(true); + } + + if (ship) { + ShipStats* stats = ShipStats::Find(ship->Name()); + + if (design->primary) + stats->AddGunShot(); + else if (design->decoy_type == 0 && design->damage > 0) + stats->AddMissileShot(); + } + } + + return shot; +} + +Shot* +Weapon::CreateShot(const Point& loc, const Camera& cam, WeaponDesign* dsn, const Ship* own) +{ + if (dsn->drone) + return new(__FILE__,__LINE__) Drone(loc, cam, dsn, own); + + else + return new(__FILE__,__LINE__) Shot(loc, cam, dsn, own); +} + +// +--------------------------------------------------------------------+ + +void +Weapon::Orient(const Physical* rep) +{ + System::Orient(rep); + + const Matrix& orientation = rep->Cam().Orientation(); + Point loc = rep->Location(); + + // align graphics with camera: + if (turret) { + if (!design->self_aiming) + ZeroAim(); + + const Matrix& aim = aim_cam.Orientation(); + + turret->MoveTo(mount_loc); + turret->SetOrientation(aim); + + if (turret_base) { + Matrix base = orientation; + + if (design->turret_axis == 1) { + base.Pitch(aim_elevation); + base.Pitch(old_elevation); + } + else { + base.Yaw(aim_azimuth); + base.Yaw(old_azimuth); + } + + turret_base->MoveTo(mount_loc); + turret_base->SetOrientation(base); + } + + for (int i = 0; i < nbarrels; i++) { + muzzle_pts[i] = (rel_pts[i] * aim) + mount_loc; + + if (visible_stores[i]) { + visible_stores[i]->SetOrientation(aim); + visible_stores[i]->MoveTo(muzzle_pts[i]); + } + } + } + else { + for (int i = 0; i < nbarrels; i++) { + muzzle_pts[i] = (rel_pts[i] * orientation) + loc; + + if (visible_stores[i]) { + visible_stores[i]->SetOrientation(orientation); + visible_stores[i]->MoveTo(muzzle_pts[i]); + } + } + } +} + +// +--------------------------------------------------------------------+ + +void +Weapon::SetBeamPoints(bool aim) +{ + for (int i = 0; i < nbarrels; i++) { + if (beams && beams[i]) { + Point from = muzzle_pts[i]; + Point to; + double len = design->length; + + if (len < 1 || len > 1e7) + len = design->max_range; + + if (len < 1) + len = 3e5; + + else if (len > 1e7) + len = 1e7; + + // always use the aim cam, to enforce slew_rate + to = from + aim_cam.vpn() * len; + + beams[i]->SetBeamPoints(from, to); + } + } +} + +// +--------------------------------------------------------------------+ + +void +Weapon::Aim() +{ + locked = false; + centered = false; + + FindObjective(); + + if (target) { + double az = 0; + double el = 0; + + locked = CanLockPoint(obj_w, az, el, &objective); + + double spread_az = design->spread_az; + double spread_el = design->spread_el; + + // beam sweep target: + if (design->beam) { + double factor = 0; + double az_phase = 0; + double el_phase = 0; + + if (target->Type() == SimObject::SIM_SHIP) { + Ship* s = (Ship*) target; + + if (s->IsStarship()) { + switch (sweep) { + default: + case SWEEP_NONE: factor = 0; break; + case SWEEP_TIGHT: factor = 1; break; + case SWEEP_WIDE: factor = 2; break; + } + } + } + + if (factor > 0) { + factor *= atan2(target->Radius(), (double) objective.z); + + for (int i = 0; i < nbarrels; i++) { + if (beams && beams[i]) { + az_phase = sin(beams[i]->Life() * 0.4 * PI); + el_phase = sin(beams[i]->Life() * 1.0 * PI); + break; + } + } + + az += factor * spread_az * az_phase; + el += factor * spread_el * el_phase * 0.25; + } + } + + else if (!design->beam) { + if (spread_az > 0) + az += Random(-spread_az, spread_az); + + if (spread_el > 0) + el += Random(-spread_el, spread_el); + } + + AimTurret(az, -el); + + // check range for guided weapons: + if (locked && guided) { + double range = objective.length(); + + if (range > design->max_track) + locked = false; + + else if (range > design->max_range) { + if (firing) { + if (RandomChance(1,4)) // 1 in 4 chance of locking anyway + locked = false; + } + else { + locked = false; + } + } + } + + if (locked) { + Point tloc = target->Location(); + tloc = Transform(tloc); + + if (tloc.z > 1) { + az = atan2(fabs(tloc.x), tloc.z); + el = atan2(fabs(tloc.y), tloc.z); + + double firing_cone = 10*DEGREES; + + if (orders == MANUAL) + firing_cone = 30*DEGREES; + + if (az < firing_cone && el < firing_cone) + centered = true; + } + } + } + else { + AimTurret(aim_az_rest, -aim_el_rest); + } +} + +void +Weapon::FindObjective() +{ + ZeroAim(); + + if (!target || !design->self_aiming) { + objective = Point(); + return; + } + + obj_w = target->Location(); + + if (subtarget) { + obj_w = subtarget->MountLocation(); + } + else if (target->Type() == SimObject::SIM_SHIP) { + Ship* tgt_ship = (Ship*) target; + + if (tgt_ship->IsGroundUnit()) + obj_w += Point(0,150,0); + } + + if (!design->beam && shot_speed > 0) { + // distance from self to target: + double distance = Point(obj_w - muzzle_pts[0]).length(); + + // TRUE shot speed is relative to ship speed: + Point eff_shot_vel = ship->Velocity() + aim_cam.vpn() * shot_speed - target->Velocity(); + double eff_shot_speed = eff_shot_vel.length(); + + // time to reach target: + double time = distance / eff_shot_speed; + + // where the target will be when the shot reaches it: + obj_w += (target->Velocity() - ship->Velocity()) * time + + target->Acceleration() * 0.25 * time * time; + } + + // transform into camera coords: + objective = Transform(obj_w); +} + +Point +Weapon::Transform(const Point& pt) +{ + Point result; + + Point obj_t = pt - aim_cam.Pos(); + result.x = obj_t * aim_cam.vrt(); + result.y = obj_t * aim_cam.vup(); + result.z = obj_t * aim_cam.vpn(); + + return result; +} + +bool +Weapon::CanLockPoint(const Point& test, double& az, double& el, Point* obj) +{ + Point pt = Transform(test); + bool locked = true; + + // first compute az: + if (fabs(pt.z) < 0.1) pt.z = 0.1; + az = atan(pt.x / pt.z); + if (pt.z < 0) az -= PI; + if (az < -PI) az += 2*PI; + + // then, rotate target into az-coords to compute el: + Camera tmp; + tmp.Clone(aim_cam); + aim_cam.Yaw(az); + pt = Transform(test); + aim_cam.Clone(tmp); + + if (fabs(pt.z) < 0.1) pt.z = 0.1; + el = atan(pt.y / pt.z); + + if (obj) *obj = pt; + + // is the target in the basket? + // clamp if necessary: + + if (az > aim_az_max) { + az = aim_az_max; + locked = false; + } + else if (az < aim_az_min) { + az = aim_az_min; + locked = false; + } + + if (el > aim_el_max) { + el = aim_el_max; + locked = false; + } + else if (el < aim_el_min) { + el = aim_el_min; + locked = false; + } + + if (IsDrone() && guided) { + double firing_cone = 10*DEGREES; + + if (orders == MANUAL) + firing_cone = 20*DEGREES; + + if (az < firing_cone && el < firing_cone) + locked = true; + } + + return locked; +} + +// +--------------------------------------------------------------------+ + +void +Weapon::AimTurret(double az, double el) +{ + double seconds = (Game::GameTime() - aim_time) / 1000.0; + + // don't let the weapon turn faster than turret slew rate: + double max_turn = design->slew_rate * seconds; + + if (fabs(az-old_azimuth) > max_turn) { + if (az > old_azimuth) + az = old_azimuth + max_turn; + else + az = old_azimuth - max_turn; + } + + if (fabs(el-old_elevation) > PI/2 * seconds) { + if (el > old_elevation) + el = old_elevation + max_turn; + else + el = old_elevation - max_turn; + } + + aim_cam.Yaw(az); + aim_cam.Pitch(el); + + old_azimuth = (float) az; + old_elevation = (float) el; + + aim_time = Game::GameTime(); +} + +void +Weapon::ZeroAim() +{ + aim_cam.Clone(ship->Cam()); + if (aim_azimuth != 0) aim_cam.Yaw(aim_azimuth); + if (aim_elevation != 0) aim_cam.Pitch(aim_elevation); + + aim_cam.MoveTo(muzzle_pts[0]); +} diff --git a/Stars45/Weapon.h b/Stars45/Weapon.h new file mode 100644 index 0000000..3e4335c --- /dev/null +++ b/Stars45/Weapon.h @@ -0,0 +1,204 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Weapon.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Weapon (gun or missile launcher) class +*/ + +#ifndef Weapon_h +#define Weapon_h + +#include "Types.h" +#include "SimObject.h" +#include "System.h" +#include "WeaponDesign.h" +#include "Geometry.h" +#include "text.h" + +// +--------------------------------------------------------------------+ + +class Weapon; +class Ship; +class Shot; + +class Solid; + +// +--------------------------------------------------------------------+ + +class Weapon : public System, public SimObserver +{ +public: + static const char* TYPENAME() { return "Weapon"; } + + enum Constants { MAX_BARRELS=8 }; + enum Orders { MANUAL, AUTO, POINT_DEFENSE }; + enum Control { SINGLE_FIRE, RIPPLE_FIRE, SALVO_FIRE }; + enum Sweep { SWEEP_NONE, SWEEP_TIGHT, SWEEP_WIDE }; + + Weapon(WeaponDesign* d, int nmuz, Vec3* muzzles, double az=0, double el=0); + Weapon(const Weapon& rhs); + virtual ~Weapon(); + + int operator==(const Weapon& w) const { return this == &w; } + + int Track(SimObject* targ, System* sub); + Shot* Fire(); + Shot* NetFirePrimary(SimObject* targ, System* sub, int count); + Shot* NetFireSecondary(SimObject* targ, System* sub, DWORD objid); + void SetTarget(SimObject* t, System* sub); + void SelectTarget(); + bool CanTarget(DWORD classification) const; + SimObject* GetTarget() const { return target; } + System* GetSubTarget() const { return subtarget; } + void SetFiringOrders(int o); + int GetFiringOrders() const { return orders; } + void SetControlMode(int m); + int GetControlMode() const { return control; } + void SetSweep(int s); + int GetSweep() const { return sweep; } + + void Enable() { enabled = true; } + void Disable() { enabled = false; } + + const WeaponDesign* Design() const { return design; } + const char* Group() const { return group; } + int Enabled() const { return enabled; } + int Ammo() const { return ammo; } + int Guided() const { return guided; } + int Locked() const { return locked; } + float Velocity() const { return shot_speed; } + float Mass() const { return mass*ammo; } + float Resistance()const { return resist*ammo; } + Shot* GetBeam(int i); + + // needed to set proper ammo level when joining multiplayer in progress: + void SetAmmo(int a); + + bool IsPrimary() const; + bool IsDrone() const; + bool IsDecoy() const; + bool IsProbe() const; + bool IsMissile() const; + bool IsBeam() const; + + virtual void ExecFrame(double factor); + virtual void Orient(const Physical* rep); + virtual void Distribute(double delivered_energy, double seconds); + + const Ship* Owner() const { return ship; } + void SetOwner(Ship* ship); + int GetIndex() const { return index; } + void SetIndex(int n) { index = n; } + + Point GetAimVector() const { return aim_cam.vpn(); } + void SetAzimuth(double a) { aim_azimuth = (float) a; } + double GetAzimuth() const { return aim_azimuth; } + void SetElevation(double e) { aim_elevation = (float) e; } + double GetElevation() const { return aim_elevation; } + + void SetRestAzimuth(double a) { aim_az_rest = (float) a; } + double GetRestAzimuth() const { return aim_az_rest; } + void SetRestElevation(double e) { aim_el_rest = (float) e; } + double GetRestElevation() const { return aim_el_rest; } + + void SetAzimuthMax(double a) { aim_az_max = (float) a; } + double GetAzimuthMax() const { return aim_az_max; } + void SetAzimuthMin(double a) { aim_az_min = (float) a; } + double GetAzimuthMin() const { return aim_az_min; } + + void SetElevationMax(double e) { aim_el_max = (float) e; } + double GetElevationMax() const { return aim_el_max; } + void SetElevationMin(double e) { aim_el_min = (float) e; } + double GetElevationMin() const { return aim_el_min; } + + void SetGroup(const char* n) { group = n; } + + bool IsBlockedFriendly() const { return blocked; } + void SetBlockedFriendly(bool b) { blocked = b; } + + Solid* GetTurret(); + Solid* GetTurretBase(); + Solid* GetVisibleStore(int i); + + virtual bool Update(SimObject* obj); + virtual const char* GetObserverName() const; + +protected: + Shot* FireBarrel(int n); + Shot* CreateShot(const Point& loc, const Camera& cam, WeaponDesign* dsn, const Ship* owner); + + void SetBeamPoints(bool aim=false); + void Aim(); + void AimTurret(double az, double el); + void ZeroAim(); + void FindObjective(); + Point Transform(const Point& pt); + bool CanLockPoint(const Point& test, double& az, double& el, Point* obj=0); + + // data members: + WeaponDesign* design; + Text group; + Point muzzle_pts[MAX_BARRELS]; + Point rel_pts[MAX_BARRELS]; + Solid* turret; + Solid* turret_base; + Solid* visible_stores[MAX_BARRELS]; + int nbarrels; + int active_barrel; + + float refire; + int ammo; + int ripple_count; + + // carrying costs per shot: + float mass; + float resist; + + // for targeting computer: + int guided; + bool enabled; + bool locked; + bool centered; + bool firing; + bool blocked; + float shot_speed; + + int index; + + int orders; + int control; + int sweep; + + SimObject* target; + System* subtarget; + + Point objective; + Point obj_w; + Camera aim_cam; + float aim_azimuth; + float aim_elevation; + float old_azimuth; + float old_elevation; + + // auto-aiming arc + float aim_az_max; // maximum deflection in azimuth + float aim_az_min; // minimum deflection in azimuth + float aim_az_rest; // azimuth of turret at rest + float aim_el_max; // maximum deflection in elevation + float aim_el_min; // minimum deflection in elevation + float aim_el_rest; // elevation of turret at rest + + DWORD aim_time; + + Shot** beams; +}; + +#endif Weapon_h + diff --git a/Stars45/WeaponDesign.cpp b/Stars45/WeaponDesign.cpp new file mode 100644 index 0000000..fecd505 --- /dev/null +++ b/Stars45/WeaponDesign.cpp @@ -0,0 +1,726 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: WeaponDesign.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Weapon Design parameters class +*/ + +#include "MemDebug.h" +#include "WeaponDesign.h" +#include "ShipDesign.h" +#include "Weapon.h" + +#include "Game.h" +#include "Sprite.h" +#include "Light.h" +#include "Bitmap.h" +#include "Solid.h" +#include "Sound.h" +#include "DataLoader.h" + +#include "ParseUtil.h" + +// +--------------------------------------------------------------------+ + +static List catalog; +static List mod_catalog; +static bool degrees; + +#define GET_DEF_BOOL(x) if(defname==(#x))GetDefBool(design->x,pdef,filename) +#define GET_DEF_TEXT(x) if(defname==(#x))GetDefText(design->x,pdef,filename) +#define GET_DEF_NUM(x) if(defname==(#x))GetDefNumber(design->x,pdef,filename) + +// +--------------------------------------------------------------------+ + +WeaponDesign::WeaponDesign() +{ + type = 0; + secret = 0; + drone = 0; + primary = 0; + beam = 0; + flak = 0; + guided = 0; + self_aiming = 0; + syncro = 0; + value = 0; + decoy_type = 0; + probe = 0; + target_type = 0; + + visible_stores = 0; + nstores = 0; + nbarrels = 0; + + recharge_rate = 0.0f; + refire_delay = 0.0f; + salvo_delay = 0.0f; + charge = 0.0f; + min_charge = 0.0f; + carry_mass = 0.0f; + carry_resist = 0.0f; + + speed = 0.0f; + life = 0.0f; + mass = 0.0f; + drag = 0.0f; + thrust = 0.0f; + roll_rate = 0.0f; + pitch_rate = 0.0f; + yaw_rate = 0.0f; + roll_drag = 0.0f; + pitch_drag = 0.0f; + yaw_drag = 0.0f; + + min_range = 0.0f; + max_range = 0.0f; + max_track = 0.0f; + + graphic_type = 0; + width = 0; + length = 0; + + scale = 1.0f; + explosion_scale = 0.0f; + light = 0.0f; + light_color = Color::White; + flash_scale = 0.0f; + flare_scale = 0.0f; + + spread_az = 0.0f; + spread_el = 0.0f; + + beauty_img = 0; + turret_model = 0; + turret_base_model = 0; + animation = 0; + anim_length = 0; + shot_img = 0; + shot_model = 0; + trail_img = 0; + flash_img = 0; + flare_img = 0; + sound_resource = 0; + + ammo = -1; + ripple_count = 0; + capacity = 100.0f; + damage = 1.0f; + damage_type = 0; + penetration = 1.0f; + firing_cone = 0.0f; + aim_az_max = 1.5f; + aim_az_min = -1.5f; + aim_az_rest = 0.0f; + aim_el_max = 1.5f; + aim_el_min = -1.5f; + aim_el_rest = 0.0f; + slew_rate = (float) (60*DEGREES); + turret_axis = 0; + lethal_radius = 500.0f; + integrity = 100.0f; + + eject = Vec3(0.0f, -100.0f, 0.0f); + + det_range = 0.0f; + det_count = 0; + det_spread = (float) (PI/8); + + trail_length = 0; + trail_width = 0; + trail_dim = 0; + + for (int i = 0; i < MAX_STORES; i++) { + muzzle_pts[i] = Vec3(0,0,0); + attachments[i] = Vec3(0,0,0); + } +} + +WeaponDesign::~WeaponDesign() +{ + delete turret_model; + delete turret_base_model; + delete shot_model; + delete sound_resource; +} + +// +--------------------------------------------------------------------+ + +void +WeaponDesign::Initialize(const char* filename) +{ + Print("Loading Weapon Designs '%s'\n", filename); + LoadDesign("Weapons/", filename); +} + +// +--------------------------------------------------------------------+ + +void +WeaponDesign::Close() +{ + catalog.destroy(); + mod_catalog.destroy(); +} + +void +WeaponDesign::ClearModCatalog() +{ + mod_catalog.destroy(); +} + +// +--------------------------------------------------------------------+ + +void +WeaponDesign::LoadDesign(const char* path, const char* filename, bool mod) +{ + // Load Design File: + DataLoader* loader = DataLoader::GetLoader(); + loader->SetDataPath(path); + + BYTE* block; + int blocklen = loader->LoadBuffer(filename, block, true); + + Parser parser(new(__FILE__,__LINE__) BlockReader((const char*) block, blocklen)); + Term* term = parser.ParseTerm(); + + if (!term) { + Print("ERROR: could not parse '%s'\n", filename); + return; + } + else { + TermText* file_type = term->isText(); + if (!file_type || file_type->value() != "WEAPON") { + Print("ERROR: invalid weapon design file '%s'\n", filename); + return; + } + } + + int type = 1; + degrees = false; + + do { + delete term; + + term = parser.ParseTerm(); + + if (term) { + TermDef* def = term->isDef(); + if (def) { + Text defname = def->name()->value(); + defname.setSensitive(false); + + if (defname == "primary" || + defname == "missile" || + defname == "drone" || + defname == "beam") { + + if (!def->term() || !def->term()->isStruct()) { + Print("WARNING: weapon structure missing in '%s'\n", filename); + } + else { + TermStruct* val = def->term()->isStruct(); + WeaponDesign* design = new(__FILE__,__LINE__) WeaponDesign; + + design->type = type++; + if (defname == "primary") { + design->primary = true; + } + + else if (defname == "beam") { + design->primary = true; + design->beam = true; + design->guided = true; + + design->spread_az = 0.15f; + design->spread_el = 0.15f; + + } + + else if (defname == "drone") { + design->drone = true; + design->penetration = 5.0f; + } + + else { // missile + design->penetration = 5.0f; + } + + float sound_min_dist = 1.0f; + float sound_max_dist = 100.0e3f; + + for (int i = 0; i < val->elements()->size(); i++) { + TermDef* pdef = val->elements()->at(i)->isDef(); + if (pdef) { + defname = pdef->name()->value(); + defname.setSensitive(false); + + GET_DEF_TEXT(name); + else GET_DEF_TEXT(group); + else GET_DEF_TEXT(description); + else GET_DEF_NUM (guided); + else GET_DEF_BOOL(self_aiming); + else GET_DEF_BOOL(flak); + else GET_DEF_BOOL(syncro); + else GET_DEF_BOOL(visible_stores); + else GET_DEF_NUM (value); + + else if (defname==("degrees")) { + GetDefBool(degrees,pdef,filename); + } + + else if (defname==("secret")) { + GetDefBool(design->secret,pdef,filename); + } + + else if (defname==("aim_az_max")) { + GetDefNumber(design->aim_az_max,pdef,filename); + if (degrees) design->aim_az_max *= (float) DEGREES; + design->aim_az_min = 0.0f - design->aim_az_max; + } + + else if (defname==("aim_el_max")) { + GetDefNumber(design->aim_el_max,pdef,filename); + if (degrees) design->aim_el_max *= (float) DEGREES; + design->aim_el_min = 0.0f - design->aim_el_max; + } + + else if (defname==("aim_az_min")) { + GetDefNumber(design->aim_az_min,pdef,filename); + if (degrees) design->aim_az_min *= (float) DEGREES; + } + + else if (defname==("aim_el_min")) { + GetDefNumber(design->aim_el_min,pdef,filename); + if (degrees) design->aim_el_min *= (float) DEGREES; + } + + else if (defname==("aim_az_rest")) { + GetDefNumber(design->aim_az_rest,pdef,filename); + if (degrees) design->aim_az_rest *= (float) DEGREES; + } + + else if (defname==("aim_el_rest")) { + GetDefNumber(design->aim_el_rest,pdef,filename); + if (degrees) design->aim_el_rest *= (float) DEGREES; + } + + else if (defname==("spread_az")) { + GetDefNumber(design->spread_az,pdef,filename); + if (degrees) design->spread_az *= (float) DEGREES; + } + + else if (defname==("spread_el")) { + GetDefNumber(design->spread_el,pdef,filename); + if (degrees) design->spread_el *= (float) DEGREES; + } + + else GET_DEF_NUM (capacity); + else GET_DEF_NUM (recharge_rate); + else GET_DEF_NUM (refire_delay); + else GET_DEF_NUM (salvo_delay); + else GET_DEF_NUM (ammo); + else GET_DEF_NUM (ripple_count); + else GET_DEF_NUM (charge); + else GET_DEF_NUM (min_charge); + else GET_DEF_NUM (carry_mass); + else GET_DEF_NUM (carry_resist); + else GET_DEF_NUM (damage); + else GET_DEF_NUM (penetration); + else GET_DEF_NUM (speed); + else GET_DEF_NUM (life); + else GET_DEF_NUM (mass); + else GET_DEF_NUM (drag); + else GET_DEF_NUM (thrust); + else GET_DEF_NUM (roll_rate); + else GET_DEF_NUM (pitch_rate); + else GET_DEF_NUM (yaw_rate); + else GET_DEF_NUM (roll_drag); + else GET_DEF_NUM (pitch_drag); + else GET_DEF_NUM (yaw_drag); + else GET_DEF_NUM (lethal_radius); + else GET_DEF_NUM (integrity); + + else GET_DEF_NUM (det_range); + else GET_DEF_NUM (det_count); + else GET_DEF_NUM (det_spread); + else GET_DEF_TEXT(det_child); + + else GET_DEF_NUM (slew_rate); + + else GET_DEF_NUM (min_range); + else GET_DEF_NUM (max_range); + else GET_DEF_NUM (max_track); + + else GET_DEF_NUM (graphic_type); + else GET_DEF_NUM (width); + else GET_DEF_NUM (length); + else GET_DEF_NUM (scale); + else GET_DEF_NUM (explosion_scale); + else GET_DEF_NUM (light); + else GET_DEF_NUM (flash_scale); + else GET_DEF_NUM (flare_scale); + + else GET_DEF_NUM (trail_length); + else GET_DEF_NUM (trail_width); + else GET_DEF_NUM (trail_dim); + + else GET_DEF_TEXT(beauty); + else GET_DEF_TEXT(bitmap); + else GET_DEF_TEXT(turret); + else GET_DEF_TEXT(turret_base); + else GET_DEF_TEXT(model); + else GET_DEF_TEXT(trail); + else GET_DEF_TEXT(flash); + else GET_DEF_TEXT(flare); + else GET_DEF_TEXT(sound); + else GET_DEF_BOOL(probe); + else GET_DEF_NUM (turret_axis); + else GET_DEF_NUM (target_type); + + else if (defname == "animation") { + if (design->anim_length < 16) { + GetDefText(design->anim_frames[design->anim_length++], pdef, filename); + } + else { + Print("WARNING: too many animation frames for weapon '%s' in '%s'\n", + (const char*) design->name, filename); + } + } + + else if (defname == "light_color") + GetDefColor(design->light_color, pdef, filename); + + else if (defname == "sound_min_dist") + GetDefNumber(sound_min_dist,pdef,filename); + + else if (defname == "sound_max_dist") + GetDefNumber(sound_max_dist,pdef,filename); + + else if (defname == "muzzle") { + if (design->nbarrels < MAX_STORES) { + Vec3 a; + GetDefVec(a, pdef, filename); + design->muzzle_pts[design->nbarrels++] = a; + } + else { + Print("WARNING: too many muzzles for weapon '%s' in '%s'\n", + (const char*) design->name, filename); + } + } + + else if (defname == "attachment") { + if (design->nstores < MAX_STORES) { + Vec3 a; + GetDefVec(a, pdef, filename); + design->attachments[design->nstores++] = a; + } + else { + Print("WARNING: too many attachments for weapon '%s' in '%s'\n", + (const char*) design->name, filename); + } + } + + else if (defname == "eject") + GetDefVec(design->eject,pdef,filename); + + else if (defname == "decoy") { + char typestr[32]; + GetDefText(typestr, pdef, filename); + design->decoy_type = ShipDesign::ClassForName(typestr); + } + + else if (defname == "damage_type") { + char typestr[32]; + GetDefText(typestr, pdef, filename); + + if (!stricmp(typestr, "normal")) + design->damage_type = DMG_NORMAL; + + else if (!stricmp(typestr, "emp")) + design->damage_type = DMG_EMP; + + else if (!stricmp(typestr, "power")) + design->damage_type = DMG_POWER; + + else + Print("WARNING: unknown weapon damage type '%s' in '%s'\n", + typestr, filename); + } + + + else { + Print("WARNING: parameter '%s' ignored in '%s'\n", + defname.data(), filename); + } + } + else { + Print("WARNING: term ignored in '%s'\n", filename); + val->elements()->at(i)->print(); + } + } + + if (design->description.length()) { + design->description = Game::GetText(design->description); + } + + if (design->anim_length > 0) { + design->animation = new(__FILE__,__LINE__) Bitmap[design->anim_length]; + for (int i = 0; i < design->anim_length; i++) { + Bitmap* p = design->animation + i; + loader->LoadBitmap(design->anim_frames[i], *p, Bitmap::BMP_TRANSLUCENT); + p->MakeTexture(); + } + } + + else if (design->bitmap.length()) { + loader->LoadTexture(design->bitmap, design->shot_img, Bitmap::BMP_TRANSLUCENT); + } + + if (design->beauty.length()) { + loader->LoadTexture(design->beauty, design->beauty_img, Bitmap::BMP_TRANSLUCENT); + } + + if (design->turret.length()) { + Text p; + Text t = design->turret; + const char* s = strrchr(t.data(), '/'); + + if (s) { + p = Text(path) + t.substring(0, s-t.data()+1); + t = t.substring(s-t.data()+1, t.length()); + } + else { + s = strrchr(t.data(), '\\'); + + if (s) { + p = Text(path) + t.substring(0, s-t.data()+1); + t = t.substring(s-t.data()+1, t.length()); + } + } + + if (p.length()) + loader->SetDataPath(p); + + design->turret_model = new(__FILE__,__LINE__) Model; + design->turret_model->Load(t, design->scale); + + if (design->turret_base.length()) { + t = design->turret_base; + s = strrchr(t.data(), '/'); + + if (s) { + p = Text(path) + t.substring(0, s-t.data()+1); + t = t.substring(s-t.data()+1, t.length()); + } + else { + s = strrchr(t.data(), '\\'); + + if (s) { + p = Text(path) + t.substring(0, s-t.data()+1); + t = t.substring(s-t.data()+1, t.length()); + } + } + + if (p.length()) + loader->SetDataPath(p); + + design->turret_base_model = new(__FILE__,__LINE__) Model; + design->turret_base_model->Load(t, design->scale); + } + + loader->SetDataPath(path); + } + + if (design->model.length()) { + Text p; + Text t = design->model; + const char* s = strrchr(t.data(), '/'); + + if (s) { + p = Text(path) + t.substring(0, s-t.data()+1); + t = t.substring(s-t.data()+1, t.length()); + } + else { + s = strrchr(t.data(), '\\'); + + if (s) { + p = Text(path) + t.substring(0, s-t.data()+1); + t = t.substring(s-t.data()+1, t.length()); + } + } + + if (p.length()) + loader->SetDataPath(p); + + design->shot_model = new(__FILE__,__LINE__) Model; + design->shot_model->Load(t, design->scale); + + loader->SetDataPath(path); + } + + if (design->trail.length()) { + loader->LoadTexture(design->trail, design->trail_img, Bitmap::BMP_TRANSLUCENT); + } + + if (design->flash.length()) { + loader->LoadTexture(design->flash, design->flash_img, Bitmap::BMP_TRANSLUCENT); + } + + if (design->flare.length()) { + loader->LoadTexture(design->flare, design->flare_img, Bitmap::BMP_TRANSLUCENT); + } + + if (design->sound.length()) { + int SOUND_FLAGS = Sound::LOCALIZED | Sound::LOC_3D; + + if (design->beam) + SOUND_FLAGS = Sound::LOCALIZED | Sound::LOC_3D | Sound::LOCKED; + + if (strstr(path, "Mods") == 0) + loader->SetDataPath("Sounds/"); + loader->LoadSound(design->sound, design->sound_resource, SOUND_FLAGS); + loader->SetDataPath(path); + + if (design->sound_resource) { + design->sound_resource->SetMinDistance(sound_min_dist); + design->sound_resource->SetMaxDistance(sound_max_dist); + } + } + + if (design->max_range == 0.0f) + design->max_range = design->speed * design->life; + + if (design->max_track == 0.0f) + design->max_track = 3.0f * design->max_range; + + if (design->probe && design->lethal_radius < 1e3) + design->lethal_radius = 50e3f; + + if (design->beam) + design->flak = false; + + if (design->self_aiming) { + if (fabs(design->aim_az_max) > design->firing_cone) + design->firing_cone = (float) fabs(design->aim_az_max); + + if (fabs(design->aim_az_min) > design->firing_cone) + design->firing_cone = (float) fabs(design->aim_az_min); + + if (fabs(design->aim_el_max) > design->firing_cone) + design->firing_cone = (float) fabs(design->aim_el_max); + + if (fabs(design->aim_el_min) > design->firing_cone) + design->firing_cone = (float) fabs(design->aim_el_min); + } + + if (mod) + mod_catalog.append(design); + else + catalog.append(design); + } + } + + else + Print("WARNING: unknown definition '%s' in '%s'\n", + def->name()->value().data(), filename); + } + else { + Print("WARNING: term ignored in '%s'\n", filename); + term->print(); + } + } + } + while (term); + + loader->ReleaseBuffer(block); + loader->SetDataPath(0); +} + +// +--------------------------------------------------------------------+ + +WeaponDesign* +WeaponDesign::Get(int type) +{ + WeaponDesign test; + test.type = type; + + WeaponDesign* result = catalog.find(&test); + + if (!result) + result = mod_catalog.find(&test); + + return result; +} + +// +--------------------------------------------------------------------+ + +WeaponDesign* +WeaponDesign::Find(const char* name) +{ + for (int i = 0; i < catalog.size(); i++) { + WeaponDesign* d = catalog.at(i); + + if (d->name == name) { + return d; + } + } + + for (i = 0; i < mod_catalog.size(); i++) { + WeaponDesign* d = mod_catalog.at(i); + + if (d->name == name) { + return d; + } + } + + Print("WeaponDesign: no catalog entry for design '%s', checking mods...\n", name); + return WeaponDesign::FindModDesign(name); +} + +WeaponDesign* +WeaponDesign::FindModDesign(const char* name) +{ + Text fname = name; + fname += ".def"; + + LoadDesign("Mods/Weapons/", fname, true); + + for (int i = 0; i < mod_catalog.size(); i++) { + WeaponDesign* d = mod_catalog.at(i); + + if (d->name == name) { + Print("WeaponDesign: found mod weapon '%s'\n", d->name); + return d; + } + } + + Print("WeaponDesign: no mod found for design '%s'\n", name); + return 0; +} + +// +--------------------------------------------------------------------+ + +int +WeaponDesign::GetDesignList(List& designs) +{ + designs.clear(); + + for (int i = 0; i < catalog.size(); i++) { + WeaponDesign* design = catalog[i]; + designs.append(&design->name); + } + + for (i = 0; i < mod_catalog.size(); i++) { + WeaponDesign* design = mod_catalog[i]; + designs.append(&design->name); + } + + return designs.size(); +} diff --git a/Stars45/WeaponDesign.h b/Stars45/WeaponDesign.h new file mode 100644 index 0000000..c31472e --- /dev/null +++ b/Stars45/WeaponDesign.h @@ -0,0 +1,187 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: WeaponDesign.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Weapon (gun or missile launcher) Design parameters class +*/ + +#ifndef WeaponDesign_h +#define WeaponDesign_h + +#include "Types.h" +#include "Geometry.h" +#include "Color.h" +#include "List.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class Bitmap; +class Model; +class Sound; + +// +--------------------------------------------------------------------+ + +class WeaponDesign +{ +public: + static const char* TYPENAME() { return "WeaponDesign"; } + + enum CONSTANTS { + DMG_NORMAL=0, + DMG_EMP =1, + DMG_POWER =2, + MAX_STORES=8 + }; + + WeaponDesign(); + ~WeaponDesign(); + int operator == (const WeaponDesign& rhs) const { return (type == rhs.type) || + (name == rhs.name); } + + static void Initialize(const char* filename); + static void Close(); + + static WeaponDesign* Get(int type); + static WeaponDesign* Find(const char* name); + static WeaponDesign* FindModDesign(const char* name); + static void ClearModCatalog(); + static int GetDesignList(List& designs); + + // identification: + int type; // unique id + Text name; + Text group; + Text description; // background info for tactical reference + bool secret; // don't display in the tactical reference + + bool drone; // visible to sensors? + bool primary; // laser or missile? + bool beam; // if laser, beam or bolt? + bool self_aiming; // turret or fixed? + bool syncro; // fire all barrels? + bool flak; // splash damage + int guided; // straight, pure pursuit, lead pursuit + int value; // AI importance of system + int decoy_type; // Ship Classifcation of decoy signature + bool probe; // is sensor probe? + DWORD target_type; // bitmask of acceptable target classes + + // for turrets: + Vec3 muzzle_pts[MAX_STORES]; // default turret muzzle points + int nbarrels; // number of barrels on the turret + + // for missile hard points: + bool visible_stores; // are external stores visible? + Vec3 attachments[MAX_STORES]; // attachment points on the rail + int nstores; // number of stores on this hard point + Vec3 eject; // eject velocity from rail in 3D + + // auto-aiming arc + float firing_cone; // maximum deflection in any orientation + float aim_az_max; // maximum deflection in azimuth + float aim_az_min; // minimum deflection in azimuth + float aim_az_rest; // azimuth of turret at rest + float aim_el_max; // maximum deflection in elevation + float aim_el_min; // minimum deflection in elevation + float aim_el_rest; // elevation of turret at rest + float slew_rate; // max rate of turret slew in rad/sec + int turret_axis; // 0=az 1=el 2=not supported + + // functional parameters: + float capacity; // full charge (joules) + float recharge_rate; // watts + float refire_delay; // seconds - mechanical limit + float salvo_delay; // seconds - ai refire time + int ammo; + int ripple_count; // number of rounds per salvo + + // carrying costs per shot: + float charge; // energy cost of full charge + float min_charge; // minimum energy needed to fire + float carry_mass; + float carry_resist; + + // shot parameters: + int damage_type; // 0: normal, 1: EMP, 2: power drain + float damage; // if beam, damage per second; + // else, damage per shot. + float penetration; // ability to pierce shields, 1 is default + float speed; + float life; + float mass; + float drag; + float thrust; + float roll_rate; + float pitch_rate; + float yaw_rate; + float roll_drag; + float pitch_drag; + float yaw_drag; + float integrity; // hit points for drones = 100 + float lethal_radius; // detonation range for missiles + + float det_range; // detonation range for cluster weapons + Text det_child; // type of submunition + int det_count; // number of submunitions + float det_spread; // spread of submunition deployment + + // HUD parameters: + float min_range; + float max_range; + float max_track; + + // shot representation: + int graphic_type; // sprite or blob? + float width; // blob width + float length; // blob length + float scale; // sprite scale + float explosion_scale; // scale factor for damage to this drone + float light; // light emitted by shot + Color light_color; // color of light emitted by shot + float flash_scale; // size of muzzle flash sprite + float flare_scale; // size of drive flare sprite + + float spread_az; // spread range in radians + float spread_el; // spread range in radians + + Text anim_frames[16]; + int anim_length; + Text beauty; + Text bitmap; + Text model; + Text turret; + Text turret_base; + Text trail; + Text flash; + Text flare; + Text sound; + + Bitmap* beauty_img; + Bitmap* animation; + Bitmap* shot_img; + Bitmap* trail_img; + Bitmap* flash_img; + Bitmap* flare_img; + Model* shot_model; + Model* turret_model; + Model* turret_base_model; + Sound* sound_resource; + + int trail_length; + float trail_width; + int trail_dim; + +private: + static void LoadDesign(const char* path, const char* filename, bool mod=false); +}; + +#endif WeaponDesign_h + diff --git a/Stars45/WeaponGroup.cpp b/Stars45/WeaponGroup.cpp new file mode 100644 index 0000000..3998ff5 --- /dev/null +++ b/Stars45/WeaponGroup.cpp @@ -0,0 +1,348 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2005. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: WeaponGroup.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Weapon Control Category (Group) class +*/ + +#include "MemDebug.h" +#include "WeaponGroup.h" +#include "Ship.h" + +// +----------------------------------------------------------------------+ + +WeaponGroup::WeaponGroup(const char* n) + : selected(0), trigger(false), orders(Weapon::MANUAL), + control(Weapon::SINGLE_FIRE), sweep(Weapon::SWEEP_TIGHT), + mass(0.0f), resist(0.0f), name(n), ammo(0) +{ } + +// +--------------------------------------------------------------------+ + +WeaponGroup::~WeaponGroup() +{ + weapons.destroy(); +} + +// +--------------------------------------------------------------------+ + +void +WeaponGroup::SetName(const char* n) +{ + name = n; +} + +void +WeaponGroup::SetAbbreviation(const char* a) +{ + abrv = a; +} + +// +--------------------------------------------------------------------+ + +bool +WeaponGroup::IsPrimary() const +{ + if (weapons.size() > 0) + return weapons[0]->IsPrimary(); + + return false; +} + +bool +WeaponGroup::IsDrone() const +{ + if (weapons.size() > 0) + return weapons[0]->IsDrone(); + + return false; +} + +bool +WeaponGroup::IsDecoy() const +{ + if (weapons.size() > 0) + return weapons[0]->IsDecoy(); + + return false; +} + +bool +WeaponGroup::IsProbe() const +{ + if (weapons.size() > 0) + return weapons[0]->IsProbe(); + + return false; +} + +bool +WeaponGroup::IsMissile() const +{ + if (weapons.size() > 0) + return weapons[0]->IsMissile(); + + return false; +} + +bool +WeaponGroup::IsBeam() const +{ + if (weapons.size() > 0) + return weapons[0]->IsBeam(); + + return false; +} + +// +--------------------------------------------------------------------+ + +void +WeaponGroup::AddWeapon(Weapon* w) +{ + weapons.append(w); +} + +int +WeaponGroup::NumWeapons() const +{ + return weapons.size(); +} + +List& +WeaponGroup::GetWeapons() +{ + return weapons; +} + +bool +WeaponGroup::Contains(const Weapon* w) const +{ + return weapons.contains(w)?true:false; +} + +// +--------------------------------------------------------------------+ + +void +WeaponGroup::SelectWeapon(int n) +{ + if (n >= 0 && n < weapons.size()) + selected = n; +} + +void +WeaponGroup::CycleWeapon() +{ + selected++; + + if (selected >= weapons.size()) + selected = 0; +} + +Weapon* +WeaponGroup::GetWeapon(int n) const +{ + if (n >= 0 && n < weapons.size()) + return weapons[n]; + + return 0; +} + +Weapon* +WeaponGroup::GetSelected() const +{ + return weapons[selected]; +} + +bool +WeaponGroup::CanTarget(DWORD tgt_class) const +{ + if (selected >= 0 && selected < weapons.size()) + return weapons[selected]->CanTarget(tgt_class); + + return false; +} + +// +--------------------------------------------------------------------+ + +void +WeaponGroup::ExecFrame(double seconds) +{ + ammo = 0; + mass = 0.0f; + resist = 0.0f; + + ListIter iter = weapons; + while (++iter) { + Weapon* w = iter.value(); + w->ExecFrame(seconds); + + ammo += w->Ammo(); + mass += w->Mass(); + resist += w->Resistance(); + } +} + +void +WeaponGroup::CheckAmmo() +{ + ammo = 0; + mass = 0.0f; + resist = 0.0f; + + ListIter iter = weapons; + while (++iter) { + Weapon* w = iter.value(); + + ammo += w->Ammo(); + mass += w->Mass(); + resist += w->Resistance(); + } +} + +// +--------------------------------------------------------------------+ + +void +WeaponGroup::SetTarget(SimObject* target, System* subtarget) +{ + ListIter w = weapons; + while (++w) + w->SetTarget(target, subtarget); +} + +SimObject* +WeaponGroup::GetTarget() const +{ + SimObject* target = 0; + + if (weapons.size()) + target = weapons[0]->GetTarget(); + + return target; +} + +System* +WeaponGroup::GetSubTarget() const +{ + System* subtarget = 0; + + if (weapons.size()) + subtarget = weapons[0]->GetSubTarget(); + + return subtarget; +} + +void +WeaponGroup::DropTarget() +{ + ListIter w = weapons; + while (++w) + w->SetTarget(0, 0); +} + +// +--------------------------------------------------------------------+ + +WeaponDesign* +WeaponGroup::GetDesign() const +{ + if (selected >= 0 && selected < weapons.size()) + return (WeaponDesign*) weapons[selected]->Design(); + + return 0; +} + +// +--------------------------------------------------------------------+ + +int +WeaponGroup::Status() const +{ + int status = System::NOMINAL; + int critical = true; + + ListIter iter = (List&) weapons; // cast-away const + while (++iter) { + Weapon* w = iter.value(); + + if (w->Status() < System::NOMINAL) + status = System::DEGRADED; + + if (w->Status() > System::CRITICAL) + critical = false; + } + + if (critical) + return System::CRITICAL; + + return status; +} + +// +--------------------------------------------------------------------+ + +void +WeaponGroup::SetFiringOrders(int o) +{ + orders = o; + + ListIter w = weapons; + while (++w) + w->SetFiringOrders(orders); +} + +void +WeaponGroup::SetControlMode(int m) +{ + control = m; + + ListIter w = weapons; + while (++w) + w->SetControlMode(control); +} + +void +WeaponGroup::SetSweep(int s) +{ + sweep = s; + + ListIter w = weapons; + while (++w) + w->SetSweep(sweep); +} + +// +--------------------------------------------------------------------+ + +void +WeaponGroup::PowerOff() +{ + ListIter w = weapons; + while (++w) + w->PowerOff(); +} + +void +WeaponGroup::PowerOn() +{ + ListIter w = weapons; + while (++w) + w->PowerOn(); +} + +// +--------------------------------------------------------------------+ + +int +WeaponGroup::Value() const +{ + int result = 0; + + for (int i = 0; i < weapons.size(); i++) { + const Weapon* w = weapons[i]; + result += w->Value(); + } + + return result; +} diff --git a/Stars45/WeaponGroup.h b/Stars45/WeaponGroup.h new file mode 100644 index 0000000..2547ec2 --- /dev/null +++ b/Stars45/WeaponGroup.h @@ -0,0 +1,106 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: WeaponGroup.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Weapon Control Category (Group) class +*/ + +#ifndef WeaponGroup_h +#define WeaponGroup_h + +#include "Types.h" +#include "Weapon.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class WeaponGroup +{ +public: + static const char* TYPENAME() { return "WeaponGroup"; } + + WeaponGroup(const char* name); + ~WeaponGroup(); + + void ExecFrame(double factor); + + // identification: + const char* Name() const { return name; } + const char* Abbreviation() const { return abrv; } + void SetName(const char* n); + void SetAbbreviation(const char* a); + + bool IsPrimary() const; + bool IsDrone() const; + bool IsDecoy() const; + bool IsProbe() const; + bool IsMissile() const; + bool IsBeam() const; + int Value() const; + + // weapon list: + void AddWeapon(Weapon* w); + int NumWeapons() const; + List& GetWeapons(); + bool Contains(const Weapon* w) const; + + // weapon selection: + void SelectWeapon(int n); + void CycleWeapon(); + Weapon* GetWeapon(int n) const; + Weapon* GetSelected() const; + + // operations: + bool GetTrigger() const { return trigger; } + void SetTrigger(bool t=true) { trigger = t; } + int Ammo() const { return ammo; } + float Mass() const { return mass; } + float Resistance() const { return resist; } + void CheckAmmo(); + + void SetTarget(SimObject* t, System* sub=0); + SimObject* GetTarget() const; + System* GetSubTarget() const; + void DropTarget(); + void SetFiringOrders(int o); + int GetFiringOrders() const { return orders; } + void SetControlMode(int m); + int GetControlMode() const { return control; } + void SetSweep(int s); + int GetSweep() const { return sweep; } + int Status() const; + + WeaponDesign* GetDesign() const; + bool CanTarget(DWORD tgt_class) const; + + void PowerOn(); + void PowerOff(); + +protected: + // Displayable name: + Text name; + Text abrv; + + List weapons; + + int selected; + bool trigger; + int ammo; + + int orders; + int control; + int sweep; + + float mass; + float resist; +}; + +#endif WeaponGroup_h + diff --git a/Stars45/Weather.cpp b/Stars45/Weather.cpp new file mode 100644 index 0000000..49a6259 --- /dev/null +++ b/Stars45/Weather.cpp @@ -0,0 +1,178 @@ +/* Project Starshatter 5.0 + Destroyer Studios LLC + Copyright © 1997-2007. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Weather.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Manages local weather conditions according to the system stardate +*/ + +#include "MemDebug.h" +#include "Weather.h" +#include "StarSystem.h" +#include "Game.h" + +// +--------------------------------------------------------------------+ + +Weather::Weather() +{ + state = CLEAR; + period = 7 * 20 * 3600; + ceiling = 0; + visibility = 1; + + for (int i = 0; i < NUM_STATES; i++) + chances[i] = 0; + + chances[0] = 1; +} + +// +--------------------------------------------------------------------+ + +Weather::~Weather() +{ } + +// +--------------------------------------------------------------------+ + +void +Weather::SetChance(int n, double c) +{ + if (n >= 0 && n < NUM_STATES) { + if (c > 1 && c <= 100) + chances[n] = c / 100; + + else if (c < 1) + chances[n] = c; + } +} + +void +Weather::NormalizeChances() +{ + double total = 0; + + for (int i = 1; i < NUM_STATES; i++) + total += chances[i]; + + if (total <= 1) { + chances[0] = 1 - total; + } + + else { + chances[0] = 0; + + for (i = 1; i < NUM_STATES; i++) + chances[i] /= total; + } + + int index = 0; + double level = 0; + + for (i = 0; i < NUM_STATES; i++) { + if (chances[i] > 0) { + level += chances[i]; + + active_states[index] = (STATE) i; + thresholds[index] = level; + + index++; + } + } + + while (index < NUM_STATES) + thresholds[index++] = 10; +} + +// +--------------------------------------------------------------------+ + +void +Weather::Update() +{ + NormalizeChances(); + + double weather = (sin(StarSystem::Stardate() * 2 * PI / period)+1)/2; + + state = active_states[0]; + + for (int i = 1; i < NUM_STATES; i++) { + if (weather > thresholds[i-1] && weather <= thresholds[i]) { + state = active_states[i]; + break; + } + } + + switch (state) { + default: + case CLEAR: + ceiling = 0; + visibility = 1.0; + break; + + case HIGH_CLOUDS: + ceiling = 0; + visibility = 0.9; + break; + + case MODERATE_CLOUDS: + ceiling = 0; + visibility = 0.8; + break; + + case OVERCAST: + ceiling = 6000; + visibility = 0.7; + break; + + case FOG: + ceiling = 3500; + visibility = 0.6; + break; + + case STORM: + ceiling = 7500; + visibility = 0.5; + break; + } +} + +// +--------------------------------------------------------------------+ + +Text +Weather::Description() const +{ + Text description; + + switch (state) { + default: + case CLEAR: + description = Game::GetText("weather.clear"); + break; + + case HIGH_CLOUDS: + description = Game::GetText("weather.high-clouds"); + break; + + case MODERATE_CLOUDS: + description = Game::GetText("weather.partly-cloudy"); + break; + + case OVERCAST: + description = Game::GetText("weather.overcast"); + break; + + case FOG: + description = Game::GetText("weather.fog"); + break; + + case STORM: + description = Game::GetText("weather.storm"); + break; + } + + return description; +} \ No newline at end of file diff --git a/Stars45/Weather.h b/Stars45/Weather.h new file mode 100644 index 0000000..6196e92 --- /dev/null +++ b/Stars45/Weather.h @@ -0,0 +1,67 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: Weather.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + Manages local weather conditions according to the system stardate +*/ + +#ifndef Weather_h +#define Weather_h + +#include "Types.h" +#include "Text.h" + +// +--------------------------------------------------------------------+ + +class Weather +{ +public: + Weather(); + virtual ~Weather(); + + enum STATE { CLEAR, + HIGH_CLOUDS, + MODERATE_CLOUDS, + OVERCAST, + FOG, + STORM, + + NUM_STATES + }; + + virtual void Update(); + + // accessors: + STATE State() const { return state; } + Text Description() const; + double Period() const { return period; } + double Chance(STATE s) const { return chances[(int)s]; } + double Ceiling() const { return ceiling; } + double Visibility() const { return visibility; } + + void SetPeriod(double p) { period = p; } + void SetChance(int n, double c); + +protected: + void NormalizeChances(); + + STATE state; + double period; + double chances[NUM_STATES]; + double ceiling; + double visibility; + + STATE active_states[NUM_STATES]; + double thresholds[NUM_STATES]; +}; + + +#endif Weather_h + diff --git a/Stars45/WepView.cpp b/Stars45/WepView.cpp new file mode 100644 index 0000000..bcbec1f --- /dev/null +++ b/Stars45/WepView.cpp @@ -0,0 +1,531 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: WepView.cpp + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + View class for Tactical HUD Overlay +*/ + +#include "MemDebug.h" +#include "WepView.h" +#include "HUDView.h" +#include "HUDSounds.h" +#include "Ship.h" +#include "Computer.h" +#include "NavSystem.h" +#include "Drive.h" +#include "Power.h" +#include "Shield.h" +#include "Contact.h" +#include "ShipDesign.h" +#include "Shot.h" +#include "Drone.h" +#include "Weapon.h" +#include "Sim.h" +#include "StarSystem.h" +#include "WeaponGroup.h" + +#include "CameraView.h" +#include "Color.h" +#include "Window.h" +#include "Video.h" +#include "Screen.h" +#include "DataLoader.h" +#include "Scene.h" +#include "FontMgr.h" +#include "Graphic.h" +#include "Keyboard.h" +#include "Mouse.h" +#include "Game.h" +#include "FormatUtil.h" + +static Bitmap tac_left; +static Bitmap tac_right; +static Bitmap tac_button; +static Bitmap tac_man; +static Bitmap tac_aut; +static Bitmap tac_def; + +static BYTE* tac_left_shade; +static BYTE* tac_right_shade; +static BYTE* tac_button_shade; +static BYTE* tac_man_shade; +static BYTE* tac_aut_shade; +static BYTE* tac_def_shade; + +static Color hud_color = Color::Black; +static Color txt_color = Color::Black; + +static bool mouse_in = false; + +static Font* hud_font = 0; +static Font* big_font = 0; + +// +--------------------------------------------------------------------+ + +WepView* WepView::wep_view = 0; + +// +--------------------------------------------------------------------+ + +WepView::WepView(Window* c) + : View(c), sim(0), ship(0), target(0), active_region(0), + transition(false), mode(0), mouse_down(0) +{ + wep_view = this; + + sim = Sim::GetSim(); + + HUDView::PrepareBitmap("TAC_left.pcx", tac_left, tac_left_shade); + HUDView::PrepareBitmap("TAC_right.pcx", tac_right, tac_right_shade); + HUDView::PrepareBitmap("TAC_button.pcx", tac_button, tac_button_shade); + HUDView::PrepareBitmap("MAN.pcx", tac_man, tac_man_shade); + HUDView::PrepareBitmap("AUTO.pcx", tac_aut, tac_aut_shade); + HUDView::PrepareBitmap("DEF.pcx", tac_def, tac_def_shade); + + tac_left.SetType(Bitmap::BMP_TRANSLUCENT); + tac_right.SetType(Bitmap::BMP_TRANSLUCENT); + tac_man.SetType(Bitmap::BMP_TRANSLUCENT); + tac_aut.SetType(Bitmap::BMP_TRANSLUCENT); + tac_def.SetType(Bitmap::BMP_TRANSLUCENT); + + OnWindowMove(); + + hud_font = FontMgr::Find("HUD"); + big_font = FontMgr::Find("GUI"); + + hud = HUDView::GetInstance(); + if (hud) + SetColor(hud->GetHUDColor()); +} + +WepView::~WepView() +{ + tac_left.ClearImage(); + tac_right.ClearImage(); + tac_button.ClearImage(); + tac_man.ClearImage(); + tac_aut.ClearImage(); + tac_def.ClearImage(); + + delete [] tac_left_shade; + delete [] tac_right_shade; + delete [] tac_button_shade; + delete [] tac_man_shade; + delete [] tac_aut_shade; + delete [] tac_def_shade; + + tac_left_shade = 0; + tac_right_shade = 0; + tac_button_shade = 0; + tac_man_shade = 0; + tac_aut_shade = 0; + tac_def_shade = 0; + + wep_view = 0; +} + +void +WepView::OnWindowMove() +{ + width = window->Width(); + height = window->Height(); + xcenter = (width / 2.0) - 0.5; + ycenter = (height / 2.0) + 0.5; + + int btn_loc = width/2 - 147 - 45; + int man_loc = width/2 - 177 - 16; + int aut_loc = width/2 - 145 - 16; + int def_loc = width/2 - 115 - 16; + + int index = 0; + + for (int i = 0; i < MAX_WEP; i++) { + btn_rect[index++] = Rect(btn_loc, 30, 90, 20); + btn_rect[index++] = Rect(man_loc, 56, 32, 8); + btn_rect[index++] = Rect(aut_loc, 56, 32, 8); + btn_rect[index++] = Rect(def_loc, 56, 32, 8); + + btn_loc += 98; + man_loc += 98; + aut_loc += 98; + def_loc += 98; + } +} + +// +--------------------------------------------------------------------+ + +bool +WepView::Update(SimObject* obj) +{ + if (obj == ship) { + ship = 0; + target = 0; + } + else if (obj == target) { + target = 0; + } + + return SimObserver::Update(obj); +} + +const char* +WepView::GetObserverName() const +{ + return "WepView"; +} + +// +--------------------------------------------------------------------+ + +void +WepView::Refresh() +{ + sim = Sim::GetSim(); + if (!sim || !hud || hud->GetHUDMode() == HUDView::HUD_MODE_OFF) + return; + + if (ship != sim->GetPlayerShip()) { + ship = sim->GetPlayerShip(); + + if (ship) { + if (ship->Life() == 0 || ship->IsDying() || ship->IsDead()) { + ship = 0; + } + else { + Observe(ship); + } + } + } + + if (mode < 1) + return; + + if (ship) { + // no tactical overlay for fighters: + if (ship->Design() && !ship->Design()->wep_screen) { + mode = 0; + return; + } + + // no hud in transition: + if (ship->InTransition()) { + transition = true; + return; + } + + else if (transition) { + transition = false; + RestoreOverlay(); + } + + if (target != ship->GetTarget()) { + target = ship->GetTarget(); + if (target) Observe(target); + } + + DrawOverlay(); + } + else { + if (target) { + target = 0; + } + } +} + +// +--------------------------------------------------------------------+ + +void +WepView::ExecFrame() +{ + int hud_mode = 1; + + // update the position of HUD elements that are + // part of the 3D scene (like fpm and lcos sprites) + + if (hud) { + if (hud_color != hud->GetHUDColor()) { + hud_color = hud->GetHUDColor(); + SetColor(hud_color); + } + + if (hud->GetHUDMode() == HUDView::HUD_MODE_OFF) + hud_mode = 0; + } + + if (ship && !transition && mode > 0 && hud_mode > 0) { + if (mode > 0) { + DoMouseFrame(); + } + } +} + +// +--------------------------------------------------------------------+ + +void +WepView::SetOverlayMode(int m) +{ + if (mode != m) { + mode = m; + + if (hud) + hud->SetOverlayMode(mode); + + RestoreOverlay(); + } +} + +void +WepView::CycleOverlayMode() +{ + SetOverlayMode(!mode); +} + +void +WepView::RestoreOverlay() +{ + if (mode > 0) { + HUDSounds::PlaySound(HUDSounds::SND_WEP_DISP); + } + + else { + HUDSounds::PlaySound(HUDSounds::SND_WEP_MODE); + } +} + +// +--------------------------------------------------------------------+ + +void +WepView::SetColor(Color c) +{ + HUDView* hud = HUDView::GetInstance(); + + if (hud) { + hud_color = hud->GetHUDColor(); + txt_color = hud->GetTextColor(); + } + else { + hud_color = c; + txt_color = c; + } + + HUDView::ColorizeBitmap(tac_left, tac_left_shade, hud_color); + HUDView::ColorizeBitmap(tac_right, tac_right_shade, hud_color); + HUDView::ColorizeBitmap(tac_button, tac_button_shade, hud_color); + HUDView::ColorizeBitmap(tac_man, tac_man_shade, hud_color); + HUDView::ColorizeBitmap(tac_aut, tac_aut_shade, hud_color); + HUDView::ColorizeBitmap(tac_def, tac_def_shade, hud_color); +} + +// +--------------------------------------------------------------------+ + +void +WepView::DrawOverlay() +{ + int cx = width/2; + int cy = 0; + int w = tac_left.Width(); + int h = tac_left.Height(); + + window->DrawBitmap(cx-w, cy, cx, cy+h, &tac_left, Video::BLEND_ALPHA); + window->DrawBitmap(cx, cy, cx+w, cy+h, &tac_right, Video::BLEND_ALPHA); + + if (ship) { + List& weapons = ship->Weapons(); + for (int i = 0; i < MAX_WEP; i++) { + if (weapons.size() > i) { + // draw main fire button: + Rect r = btn_rect[i*4]; + + w = tac_button.Width(); + h = tac_button.Height(); + cx = r.x + r.w/2 - w/2; + cy = r.y + r.h/2 - h/2; + + r.Deflate(5,5); + + big_font->SetColor(txt_color); + window->SetFont(big_font); + window->DrawBitmap(cx, cy, cx+w, cy+h, &tac_button, Video::BLEND_ALPHA); + window->DrawText(weapons[i]->Name(), 0, r, DT_SINGLELINE | DT_CENTER); + + r.Inflate(5,5); + + // draw firing orders: + int o = weapons[i]->GetFiringOrders(); + w = tac_man.Width(); + h = tac_man.Height(); + + Color c0 = Color::Gray; + Color c1 = Color::White; + + r = btn_rect[i*4 + 1]; + cx = r.x + r.w/2 - w/2; + cy = r.y + r.h/2 - h/2; + window->FadeBitmap(cx, cy, cx+w, cy+h, &tac_man, (o==0 ? c1 : c0), Video::BLEND_ALPHA); + + r = btn_rect[i*4 + 2]; + cx = r.x + r.w/2 - w/2; + cy = r.y + r.h/2 - h/2; + window->FadeBitmap(cx, cy, cx+w, cy+h, &tac_aut, (o==1 ? c1 : c0), Video::BLEND_ALPHA); + + r = btn_rect[i*4 + 3]; + cx = r.x + r.w/2 - w/2; + cy = r.y + r.h/2 - h/2; + window->FadeBitmap(cx, cy, cx+w, cy+h, &tac_def, (o==2 ? c1 : c0), Video::BLEND_ALPHA); + } + } + + Rect tgt_rect; + tgt_rect.x = width/2 + 73; + tgt_rect.y = 74; + tgt_rect.w = 100; + tgt_rect.h = 15; + + Text subtxt; + Color stat = hud_color; + static DWORD blink = Game::RealTime(); + + if (ship->GetTarget()) { + if (ship->GetSubTarget()) { + int blink_delta = Game::RealTime() - blink; + + System* sys = ship->GetSubTarget(); + subtxt = sys->Abbreviation(); + switch (sys->Status()) { + case System::DEGRADED: stat = Color(255,255, 0); break; + case System::CRITICAL: + case System::DESTROYED: stat = Color(255, 0, 0); break; + case System::MAINT: + if (blink_delta < 250) + stat = Color(8,8,8); + break; + } + + if (blink_delta > 500) + blink = Game::RealTime(); + } + + else + subtxt = ship->GetTarget()->Name(); + } + else { + subtxt = "NO TGT"; + } + + subtxt.toUpper(); + + hud_font->SetColor(stat); + window->SetFont(hud_font); + window->DrawText(subtxt.data(), subtxt.length(), tgt_rect, DT_SINGLELINE | DT_CENTER); + } +} + +// +--------------------------------------------------------------------+ + +void +WepView::DoMouseFrame() +{ + static int mouse_down = false; + static int mouse_down_x = 0; + static int mouse_down_y = 0; + + int x = Mouse::X(); + int y = Mouse::Y(); + + // coarse-grained test: is mouse in overlay at all? + if (x < width/2-256 || x > width/2+256 || y > 90) { + mouse_in = false; + return; + } + + mouse_in = true; + + if (Mouse::LButton()) { + if (!mouse_down) { + mouse_down = true; + mouse_down_x = x; + mouse_down_y = y; + } + + // check weapons buttons: + int max_wep = ship->Weapons().size(); + + if (max_wep > MAX_WEP) + max_wep = MAX_WEP; + + for (int i = 0; i < max_wep; i++) { + int index = i * 4; + + if (CheckButton(index, mouse_down_x, mouse_down_y)) { + ship->FireWeapon(i); + return; + } + + else if (CheckButton(index + 1, mouse_down_x, mouse_down_y)) { + ship->Weapons()[i]->SetFiringOrders(Weapon::MANUAL); + return; + } + + else if (CheckButton(index + 2, mouse_down_x, mouse_down_y)) { + ship->Weapons()[i]->SetFiringOrders(Weapon::AUTO); + return; + } + + else if (CheckButton(index + 3, mouse_down_x, mouse_down_y)) { + ship->Weapons()[i]->SetFiringOrders(Weapon::POINT_DEFENSE); + return; + } + } + } + + else if (mouse_down) { + mouse_down = false; + mouse_down_x = 0; + mouse_down_y = 0; + + // check subtarget buttons: + if (ship->GetTarget()) { + Rect r(width/2+50, 70, 20, 20); + if (r.Contains(x,y)) { + CycleSubTarget(-1); + return; + } + + r.x = width/2 + 180; + if (r.Contains(x,y)) { + CycleSubTarget(1); + return; + } + } + } +} + +bool +WepView::CheckButton(int index, int x, int y) +{ + if (index >= 0 && index < MAX_BTN) { + return btn_rect[index].Contains(x,y)?true:false; + } + + return false; +} + +void +WepView::CycleSubTarget(int direction) +{ + if (ship->GetTarget() == 0 || ship->GetTarget()->Type() != SimObject::SIM_SHIP) + return; + + ship->CycleSubTarget(direction); +} + +bool +WepView::IsMouseLatched() +{ + return mouse_in; +} diff --git a/Stars45/WepView.h b/Stars45/WepView.h new file mode 100644 index 0000000..1f5bcfb --- /dev/null +++ b/Stars45/WepView.h @@ -0,0 +1,88 @@ +/* Project Starshatter 4.5 + Destroyer Studios LLC + Copyright © 1997-2004. All Rights Reserved. + + SUBSYSTEM: Stars.exe + FILE: WepView.h + AUTHOR: John DiCamillo + + + OVERVIEW + ======== + View class for Tactical HUD Overlay +*/ + +#ifndef WepView_h +#define WepView_h + +#include "Types.h" +#include "View.h" +#include "Projector.h" +#include "Bitmap.h" +#include "Font.h" +#include "System.h" +#include "SimObject.h" + +// +--------------------------------------------------------------------+ + +class Graphic; +class Sprite; +class Ship; +class Contact; +class HUDView; + +// +--------------------------------------------------------------------+ + +class WepView : public View, + public SimObserver +{ +public: + WepView(Window* c); + virtual ~WepView(); + + // Operations: + virtual void Refresh(); + virtual void OnWindowMove(); + virtual void ExecFrame(); + virtual void SetOverlayMode(int mode); + virtual int GetOverlayMode() const { return mode; } + virtual void CycleOverlayMode(); + + virtual void RestoreOverlay(); + + virtual bool Update(SimObject* obj); + virtual const char* GetObserverName() const; + + static WepView* GetInstance() { return wep_view; } + static void SetColor(Color c); + + static bool IsMouseLatched(); + +protected: + void DrawOverlay(); + + void DoMouseFrame(); + bool CheckButton(int index, int x, int y); + void CycleSubTarget(int direction); + + int mode; + int transition; + int mouse_down; + int width, height, aw, ah; + double xcenter, ycenter; + + Sim* sim; + Ship* ship; + SimObject* target; + HUDView* hud; + + enum { MAX_WEP = 4, MAX_BTN = 16 }; + Rect btn_rect[MAX_BTN]; + + SimRegion* active_region; + + static WepView* wep_view; +}; + +#endif WepView_h + diff --git a/Stars45/resource.h b/Stars45/resource.h new file mode 100644 index 0000000..3c14a64 --- /dev/null +++ b/Stars45/resource.h @@ -0,0 +1,18 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by Stars.rc +// +#define Stars 100 +#define IDI_ICON1 101 +#define IDI_ICON2 102 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 104 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif -- cgit v1.1