summaryrefslogtreecommitdiffhomepage
path: root/Stars45
diff options
context:
space:
mode:
authorFWoltermann@gmail.com <FWoltermann@gmail.com@076cb2c4-205e-83fd-5cf3-1be9aa105544>2011-12-08 14:53:40 +0000
committerFWoltermann@gmail.com <FWoltermann@gmail.com@076cb2c4-205e-83fd-5cf3-1be9aa105544>2011-12-08 14:53:40 +0000
commite33e19d0587146859d48a134ec9fd94e7b7ba5cd (patch)
tree69d048c8801858d2756ab3a487090a7a1b74bf14 /Stars45
downloadstarshatter-e33e19d0587146859d48a134ec9fd94e7b7ba5cd.zip
starshatter-e33e19d0587146859d48a134ec9fd94e7b7ba5cd.tar.gz
starshatter-e33e19d0587146859d48a134ec9fd94e7b7ba5cd.tar.bz2
Initial upload
Diffstat (limited to 'Stars45')
-rw-r--r--Stars45/Asteroid.cpp116
-rw-r--r--Stars45/Asteroid.h35
-rw-r--r--Stars45/AudDlg.cpp201
-rw-r--r--Stars45/AudDlg.h79
-rw-r--r--Stars45/AudioConfig.cpp382
-rw-r--r--Stars45/AudioConfig.h71
-rw-r--r--Stars45/Authorization.cpp202
-rw-r--r--Stars45/Authorization.h30
-rw-r--r--Stars45/AwardDlg.cpp152
-rw-r--r--Stars45/AwardDlg.h59
-rw-r--r--Stars45/AwardShowDlg.cpp159
-rw-r--r--Stars45/AwardShowDlg.h63
-rw-r--r--Stars45/BaseScreen.h83
-rw-r--r--Stars45/Callsign.cpp102
-rw-r--r--Stars45/Callsign.h29
-rw-r--r--Stars45/CameraDirector.cpp1196
-rw-r--r--Stars45/CameraDirector.h156
-rw-r--r--Stars45/Campaign.cpp2347
-rw-r--r--Stars45/Campaign.h282
-rw-r--r--Stars45/CampaignMissionFighter.cpp2152
-rw-r--r--Stars45/CampaignMissionFighter.h121
-rw-r--r--Stars45/CampaignMissionRequest.cpp39
-rw-r--r--Stars45/CampaignMissionRequest.h84
-rw-r--r--Stars45/CampaignMissionStarship.cpp1405
-rw-r--r--Stars45/CampaignMissionStarship.h107
-rw-r--r--Stars45/CampaignPlan.h58
-rw-r--r--Stars45/CampaignPlanAssignment.cpp158
-rw-r--r--Stars45/CampaignPlanAssignment.h50
-rw-r--r--Stars45/CampaignPlanEvent.cpp1315
-rw-r--r--Stars45/CampaignPlanEvent.h72
-rw-r--r--Stars45/CampaignPlanMission.cpp374
-rw-r--r--Stars45/CampaignPlanMission.h58
-rw-r--r--Stars45/CampaignPlanMovement.cpp168
-rw-r--r--Stars45/CampaignPlanMovement.h44
-rw-r--r--Stars45/CampaignPlanStrategic.cpp483
-rw-r--r--Stars45/CampaignPlanStrategic.h59
-rw-r--r--Stars45/CampaignSaveGame.cpp729
-rw-r--r--Stars45/CampaignSaveGame.h69
-rw-r--r--Stars45/CampaignSituationReport.cpp416
-rw-r--r--Stars45/CampaignSituationReport.h59
-rw-r--r--Stars45/CarrierAI.cpp400
-rw-r--r--Stars45/CarrierAI.h64
-rw-r--r--Stars45/CmdDlg.cpp186
-rw-r--r--Stars45/CmdDlg.h83
-rw-r--r--Stars45/CmdForceDlg.cpp718
-rw-r--r--Stars45/CmdForceDlg.h69
-rw-r--r--Stars45/CmdIntelDlg.cpp393
-rw-r--r--Stars45/CmdIntelDlg.h76
-rw-r--r--Stars45/CmdMissionsDlg.cpp325
-rw-r--r--Stars45/CmdMissionsDlg.h65
-rw-r--r--Stars45/CmdMsgDlg.cpp92
-rw-r--r--Stars45/CmdMsgDlg.h54
-rw-r--r--Stars45/CmdOrdersDlg.cpp139
-rw-r--r--Stars45/CmdOrdersDlg.h56
-rw-r--r--Stars45/CmdTheaterDlg.cpp216
-rw-r--r--Stars45/CmdTheaterDlg.h62
-rw-r--r--Stars45/CmdTitleDlg.cpp78
-rw-r--r--Stars45/CmdTitleDlg.h56
-rw-r--r--Stars45/CmpCompleteDlg.cpp108
-rw-r--r--Stars45/CmpCompleteDlg.h50
-rw-r--r--Stars45/CmpFileDlg.cpp191
-rw-r--r--Stars45/CmpFileDlg.h64
-rw-r--r--Stars45/CmpLoadDlg.cpp118
-rw-r--r--Stars45/CmpLoadDlg.h46
-rw-r--r--Stars45/CmpSceneDlg.cpp188
-rw-r--r--Stars45/CmpSceneDlg.h68
-rw-r--r--Stars45/CmpSelectDlg.cpp623
-rw-r--r--Stars45/CmpSelectDlg.h91
-rw-r--r--Stars45/CmpnScreen.cpp647
-rw-r--r--Stars45/CmpnScreen.h137
-rw-r--r--Stars45/CombatAction.cpp352
-rw-r--r--Stars45/CombatAction.h205
-rw-r--r--Stars45/CombatAssignment.cpp68
-rw-r--r--Stars45/CombatAssignment.h57
-rw-r--r--Stars45/CombatEvent.cpp153
-rw-r--r--Stars45/CombatEvent.h122
-rw-r--r--Stars45/CombatGroup.cpp1589
-rw-r--r--Stars45/CombatGroup.h231
-rw-r--r--Stars45/CombatRoster.cpp93
-rw-r--r--Stars45/CombatRoster.h48
-rw-r--r--Stars45/CombatUnit.cpp402
-rw-r--r--Stars45/CombatUnit.h134
-rw-r--r--Stars45/CombatZone.cpp282
-rw-r--r--Stars45/CombatZone.h103
-rw-r--r--Stars45/Combatant.cpp172
-rw-r--r--Stars45/Combatant.h73
-rw-r--r--Stars45/Component.cpp177
-rw-r--r--Stars45/Component.h100
-rw-r--r--Stars45/Computer.cpp93
-rw-r--r--Stars45/Computer.h41
-rw-r--r--Stars45/ConfirmDlg.cpp150
-rw-r--r--Stars45/ConfirmDlg.h64
-rw-r--r--Stars45/Contact.cpp365
-rw-r--r--Stars45/Contact.h91
-rw-r--r--Stars45/CtlDlg.cpp461
-rw-r--r--Stars45/CtlDlg.h119
-rw-r--r--Stars45/DebriefDlg.cpp375
-rw-r--r--Stars45/DebriefDlg.h81
-rw-r--r--Stars45/Debris.cpp220
-rw-r--r--Stars45/Debris.h43
-rw-r--r--Stars45/DetailSet.cpp228
-rw-r--r--Stars45/DetailSet.h73
-rw-r--r--Stars45/DisplayView.cpp239
-rw-r--r--Stars45/DisplayView.h71
-rw-r--r--Stars45/Drive.cpp497
-rw-r--r--Stars45/Drive.h93
-rw-r--r--Stars45/DriveSprite.cpp143
-rw-r--r--Stars45/DriveSprite.h47
-rw-r--r--Stars45/Drone.cpp196
-rw-r--r--Stars45/Drone.h69
-rw-r--r--Stars45/DropShipAI.cpp122
-rw-r--r--Stars45/DropShipAI.h47
-rw-r--r--Stars45/Element.cpp667
-rw-r--r--Stars45/Element.h172
-rw-r--r--Stars45/EngDlg.cpp1043
-rw-r--r--Stars45/EngDlg.h106
-rw-r--r--Stars45/ExceptionHandler.cpp429
-rw-r--r--Stars45/ExitDlg.cpp152
-rw-r--r--Stars45/ExitDlg.h60
-rw-r--r--Stars45/Explosion.cpp611
-rw-r--r--Stars45/Explosion.h84
-rw-r--r--Stars45/Farcaster.cpp288
-rw-r--r--Stars45/Farcaster.h92
-rw-r--r--Stars45/FighterAI.cpp1831
-rw-r--r--Stars45/FighterAI.h85
-rw-r--r--Stars45/FighterTacticalAI.cpp562
-rw-r--r--Stars45/FighterTacticalAI.h56
-rw-r--r--Stars45/FirstTimeDlg.cpp147
-rw-r--r--Stars45/FirstTimeDlg.h56
-rw-r--r--Stars45/FlightComp.cpp305
-rw-r--r--Stars45/FlightComp.h63
-rw-r--r--Stars45/FlightDeck.cpp1186
-rw-r--r--Stars45/FlightDeck.h195
-rw-r--r--Stars45/FlightPlanner.cpp372
-rw-r--r--Stars45/FlightPlanner.h51
-rw-r--r--Stars45/FltDlg.cpp1084
-rw-r--r--Stars45/FltDlg.h81
-rw-r--r--Stars45/Galaxy.cpp283
-rw-r--r--Stars45/Galaxy.h74
-rw-r--r--Stars45/GameScreen.cpp1254
-rw-r--r--Stars45/GameScreen.h192
-rw-r--r--Stars45/Grid.cpp92
-rw-r--r--Stars45/Grid.h50
-rw-r--r--Stars45/GroundAI.cpp191
-rw-r--r--Stars45/GroundAI.h61
-rw-r--r--Stars45/HUDSounds.cpp118
-rw-r--r--Stars45/HUDSounds.h46
-rw-r--r--Stars45/HUDView.cpp4364
-rw-r--r--Stars45/HUDView.h218
-rw-r--r--Stars45/Hangar.cpp933
-rw-r--r--Stars45/Hangar.h127
-rw-r--r--Stars45/HardPoint.cpp104
-rw-r--r--Stars45/HardPoint.h82
-rw-r--r--Stars45/Hoop.cpp156
-rw-r--r--Stars45/Hoop.h45
-rw-r--r--Stars45/Instruction.cpp569
-rw-r--r--Stars45/Instruction.h166
-rw-r--r--Stars45/Intel.cpp46
-rw-r--r--Stars45/Intel.h37
-rw-r--r--Stars45/JoyDlg.cpp230
-rw-r--r--Stars45/JoyDlg.h56
-rw-r--r--Stars45/KeyDlg.cpp199
-rw-r--r--Stars45/KeyDlg.h65
-rw-r--r--Stars45/KeyMap.cpp824
-rw-r--r--Stars45/KeyMap.h181
-rw-r--r--Stars45/LandingGear.cpp278
-rw-r--r--Stars45/LandingGear.h66
-rw-r--r--Stars45/LoadDlg.cpp77
-rw-r--r--Stars45/LoadDlg.h41
-rw-r--r--Stars45/LoadScreen.cpp163
-rw-r--r--Stars45/LoadScreen.h64
-rw-r--r--Stars45/Main.cpp186
-rw-r--r--Stars45/MapView.cpp3478
-rw-r--r--Stars45/MapView.h223
-rw-r--r--Stars45/MenuDlg.cpp276
-rw-r--r--Stars45/MenuDlg.h86
-rw-r--r--Stars45/MenuScreen.cpp1076
-rw-r--r--Stars45/MenuScreen.h199
-rw-r--r--Stars45/MenuView.cpp333
-rw-r--r--Stars45/MenuView.h77
-rw-r--r--Stars45/Mfd.cpp1403
-rw-r--r--Stars45/Mfd.h104
-rw-r--r--Stars45/Mission.cpp2160
-rw-r--r--Stars45/Mission.h426
-rw-r--r--Stars45/MissionEvent.cpp874
-rw-r--r--Stars45/MissionEvent.h167
-rw-r--r--Stars45/MissionTemplate.cpp846
-rw-r--r--Stars45/MissionTemplate.h117
-rw-r--r--Stars45/ModConfig.cpp381
-rw-r--r--Stars45/ModConfig.h75
-rw-r--r--Stars45/ModDlg.cpp350
-rw-r--r--Stars45/ModDlg.h94
-rw-r--r--Stars45/ModInfo.cpp292
-rw-r--r--Stars45/ModInfo.h122
-rw-r--r--Stars45/ModInfoDlg.cpp115
-rw-r--r--Stars45/ModInfoDlg.h65
-rw-r--r--Stars45/MsnDlg.cpp303
-rw-r--r--Stars45/MsnDlg.h73
-rw-r--r--Stars45/MsnEditDlg.cpp896
-rw-r--r--Stars45/MsnEditDlg.h116
-rw-r--r--Stars45/MsnEditNavDlg.cpp300
-rw-r--r--Stars45/MsnEditNavDlg.h78
-rw-r--r--Stars45/MsnElemDlg.cpp805
-rw-r--r--Stars45/MsnElemDlg.h99
-rw-r--r--Stars45/MsnEventDlg.cpp416
-rw-r--r--Stars45/MsnEventDlg.h86
-rw-r--r--Stars45/MsnNavDlg.cpp104
-rw-r--r--Stars45/MsnNavDlg.h54
-rw-r--r--Stars45/MsnObjDlg.cpp316
-rw-r--r--Stars45/MsnObjDlg.h68
-rw-r--r--Stars45/MsnPkgDlg.cpp336
-rw-r--r--Stars45/MsnPkgDlg.h66
-rw-r--r--Stars45/MsnSelectDlg.cpp499
-rw-r--r--Stars45/MsnSelectDlg.h81
-rw-r--r--Stars45/MsnWepDlg.cpp517
-rw-r--r--Stars45/MsnWepDlg.h86
-rw-r--r--Stars45/MusicDirector.cpp520
-rw-r--r--Stars45/MusicDirector.h114
-rw-r--r--Stars45/MusicTrack.cpp273
-rw-r--r--Stars45/MusicTrack.h77
-rw-r--r--Stars45/NPClient.h190
-rw-r--r--Stars45/NPClientWraps.cpp257
-rw-r--r--Stars45/NPClientWraps.h33
-rw-r--r--Stars45/NavAI.cpp622
-rw-r--r--Stars45/NavAI.h74
-rw-r--r--Stars45/NavDlg.cpp1126
-rw-r--r--Stars45/NavDlg.h119
-rw-r--r--Stars45/NavLight.cpp188
-rw-r--r--Stars45/NavLight.h67
-rw-r--r--Stars45/NavSystem.cpp113
-rw-r--r--Stars45/NavSystem.h55
-rw-r--r--Stars45/NetAddrDlg.cpp160
-rw-r--r--Stars45/NetAddrDlg.h60
-rw-r--r--Stars45/NetAdminChat.cpp122
-rw-r--r--Stars45/NetAdminChat.h33
-rw-r--r--Stars45/NetAdminServer.cpp887
-rw-r--r--Stars45/NetAdminServer.h91
-rw-r--r--Stars45/NetAuth.cpp216
-rw-r--r--Stars45/NetAuth.h51
-rw-r--r--Stars45/NetBrokerClient.cpp239
-rw-r--r--Stars45/NetBrokerClient.h43
-rw-r--r--Stars45/NetChat.cpp53
-rw-r--r--Stars45/NetChat.h52
-rw-r--r--Stars45/NetClientConfig.cpp332
-rw-r--r--Stars45/NetClientConfig.h73
-rw-r--r--Stars45/NetClientDlg.cpp431
-rw-r--r--Stars45/NetClientDlg.h80
-rw-r--r--Stars45/NetData.cpp1453
-rw-r--r--Stars45/NetData.h966
-rw-r--r--Stars45/NetFileServlet.cpp118
-rw-r--r--Stars45/NetFileServlet.h50
-rw-r--r--Stars45/NetGame.cpp330
-rw-r--r--Stars45/NetGame.h127
-rw-r--r--Stars45/NetGameClient.cpp1069
-rw-r--r--Stars45/NetGameClient.h87
-rw-r--r--Stars45/NetGameServer.cpp1199
-rw-r--r--Stars45/NetGameServer.h90
-rw-r--r--Stars45/NetLobby.cpp630
-rw-r--r--Stars45/NetLobby.h289
-rw-r--r--Stars45/NetLobbyClient.cpp809
-rw-r--r--Stars45/NetLobbyClient.h104
-rw-r--r--Stars45/NetLobbyDlg.cpp475
-rw-r--r--Stars45/NetLobbyDlg.h89
-rw-r--r--Stars45/NetLobbyServer.cpp1359
-rw-r--r--Stars45/NetLobbyServer.h119
-rw-r--r--Stars45/NetPacket.cpp356
-rw-r--r--Stars45/NetPacket.h74
-rw-r--r--Stars45/NetPassDlg.cpp143
-rw-r--r--Stars45/NetPassDlg.h58
-rw-r--r--Stars45/NetPlayer.cpp464
-rw-r--r--Stars45/NetPlayer.h98
-rw-r--r--Stars45/NetServerConfig.cpp463
-rw-r--r--Stars45/NetServerConfig.h99
-rw-r--r--Stars45/NetServerDlg.cpp179
-rw-r--r--Stars45/NetServerDlg.h64
-rw-r--r--Stars45/NetUnitDlg.cpp641
-rw-r--r--Stars45/NetUnitDlg.h93
-rw-r--r--Stars45/NetUser.cpp117
-rw-r--r--Stars45/NetUser.h112
-rw-r--r--Stars45/NetUtil.cpp424
-rw-r--r--Stars45/NetUtil.h56
-rw-r--r--Stars45/OptDlg.cpp322
-rw-r--r--Stars45/OptDlg.h87
-rw-r--r--Stars45/PlanScreen.cpp395
-rw-r--r--Stars45/PlanScreen.h108
-rw-r--r--Stars45/Player.cpp1552
-rw-r--r--Stars45/Player.h187
-rw-r--r--Stars45/PlayerDlg.cpp389
-rw-r--r--Stars45/PlayerDlg.h86
-rw-r--r--Stars45/Power.cpp300
-rw-r--r--Stars45/Power.h62
-rw-r--r--Stars45/QuantumDrive.cpp246
-rw-r--r--Stars45/QuantumDrive.h73
-rw-r--r--Stars45/QuantumFlash.cpp175
-rw-r--r--Stars45/QuantumFlash.h60
-rw-r--r--Stars45/QuantumView.cpp318
-rw-r--r--Stars45/QuantumView.h73
-rw-r--r--Stars45/QuitView.cpp302
-rw-r--r--Stars45/QuitView.h64
-rw-r--r--Stars45/RLoc.cpp88
-rw-r--r--Stars45/RLoc.h71
-rw-r--r--Stars45/RadioHandler.cpp561
-rw-r--r--Stars45/RadioHandler.h53
-rw-r--r--Stars45/RadioMessage.cpp165
-rw-r--r--Stars45/RadioMessage.h155
-rw-r--r--Stars45/RadioTraffic.cpp391
-rw-r--r--Stars45/RadioTraffic.h65
-rw-r--r--Stars45/RadioView.cpp640
-rw-r--r--Stars45/RadioView.h87
-rw-r--r--Stars45/RadioVox.cpp249
-rw-r--r--Stars45/RadioVox.h59
-rw-r--r--Stars45/SeekerAI.cpp257
-rw-r--r--Stars45/SeekerAI.h73
-rw-r--r--Stars45/Sensor.cpp850
-rw-r--r--Stars45/Sensor.h86
-rw-r--r--Stars45/Shield.cpp259
-rw-r--r--Stars45/Shield.h77
-rw-r--r--Stars45/ShieldRep.cpp250
-rw-r--r--Stars45/ShieldRep.h48
-rw-r--r--Stars45/Ship.cpp5305
-rw-r--r--Stars45/Ship.h583
-rw-r--r--Stars45/ShipAI.cpp1358
-rw-r--r--Stars45/ShipAI.h153
-rw-r--r--Stars45/ShipCtrl.cpp341
-rw-r--r--Stars45/ShipCtrl.h59
-rw-r--r--Stars45/ShipDesign.cpp3700
-rw-r--r--Stars45/ShipDesign.h292
-rw-r--r--Stars45/ShipKiller.cpp263
-rw-r--r--Stars45/ShipKiller.h55
-rw-r--r--Stars45/ShipSolid.cpp96
-rw-r--r--Stars45/ShipSolid.h52
-rw-r--r--Stars45/Shot.cpp626
-rw-r--r--Stars45/Shot.h127
-rw-r--r--Stars45/Sim.cpp3810
-rw-r--r--Stars45/Sim.h330
-rw-r--r--Stars45/SimEvent.cpp262
-rw-r--r--Stars45/SimEvent.h163
-rw-r--r--Stars45/SimObject.cpp150
-rw-r--r--Stars45/SimObject.h99
-rw-r--r--Stars45/Sky.cpp726
-rw-r--r--Stars45/Sky.h105
-rw-r--r--Stars45/StarServer.cpp545
-rw-r--r--Stars45/StarServer.h76
-rw-r--r--Stars45/StarSystem.cpp1974
-rw-r--r--Stars45/StarSystem.h322
-rw-r--r--Stars45/Stars.dsp1585
-rw-r--r--Stars45/Stars.dsw29
-rw-r--r--Stars45/Stars.icobin0 -> 2998 bytes
-rw-r--r--Stars45/Stars.rc99
-rw-r--r--Stars45/Stars.vcxproj522
-rw-r--r--Stars45/Stars.vcxproj.filters1123
-rw-r--r--Stars45/Starshatter.cpp2903
-rw-r--r--Stars45/Starshatter.h225
-rw-r--r--Stars45/StarshipAI.cpp822
-rw-r--r--Stars45/StarshipAI.h62
-rw-r--r--Stars45/StarshipTacticalAI.cpp276
-rw-r--r--Stars45/StarshipTacticalAI.h47
-rw-r--r--Stars45/SteerAI.cpp453
-rw-r--r--Stars45/SteerAI.h125
-rw-r--r--Stars45/System.cpp399
-rw-r--r--Stars45/System.h174
-rw-r--r--Stars45/SystemDesign.cpp168
-rw-r--r--Stars45/SystemDesign.h54
-rw-r--r--Stars45/TacRefDlg.cpp689
-rw-r--r--Stars45/TacRefDlg.h101
-rw-r--r--Stars45/TacticalAI.cpp958
-rw-r--r--Stars45/TacticalAI.h91
-rw-r--r--Stars45/TacticalView.cpp1495
-rw-r--r--Stars45/TacticalView.h120
-rw-r--r--Stars45/Terrain.cpp561
-rw-r--r--Stars45/Terrain.h119
-rw-r--r--Stars45/TerrainApron.cpp336
-rw-r--r--Stars45/TerrainApron.h71
-rw-r--r--Stars45/TerrainClouds.cpp269
-rw-r--r--Stars45/TerrainClouds.h64
-rw-r--r--Stars45/TerrainHaze.cpp90
-rw-r--r--Stars45/TerrainHaze.h46
-rw-r--r--Stars45/TerrainLayer.h63
-rw-r--r--Stars45/TerrainPatch.cpp1113
-rw-r--r--Stars45/TerrainPatch.h94
-rw-r--r--Stars45/TerrainRegion.cpp266
-rw-r--r--Stars45/TerrainRegion.h126
-rw-r--r--Stars45/Thruster.cpp595
-rw-r--r--Stars45/Thruster.h97
-rw-r--r--Stars45/TrackIR.cpp247
-rw-r--r--Stars45/TrackIR.h53
-rw-r--r--Stars45/Trail.cpp200
-rw-r--r--Stars45/Trail.h60
-rw-r--r--Stars45/VidDlg.cpp516
-rw-r--r--Stars45/VidDlg.h106
-rw-r--r--Stars45/Weapon.cpp1184
-rw-r--r--Stars45/Weapon.h204
-rw-r--r--Stars45/WeaponDesign.cpp726
-rw-r--r--Stars45/WeaponDesign.h187
-rw-r--r--Stars45/WeaponGroup.cpp348
-rw-r--r--Stars45/WeaponGroup.h106
-rw-r--r--Stars45/Weather.cpp178
-rw-r--r--Stars45/Weather.h67
-rw-r--r--Stars45/WepView.cpp531
-rw-r--r--Stars45/WepView.h88
-rw-r--r--Stars45/resource.h18
401 files changed, 139229 insertions, 0 deletions
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<Text> mod_asteroids;
+ loader->SetDataPath("Mods/Galaxy/Asteroids/");
+ loader->ListFiles("*.mag", mod_asteroids);
+
+ ListIter<Text> 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<Contact> 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<Ship> 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<Ship> 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<Ship> 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<Ship> 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<Ship> 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<Campaign> 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<Campaign> 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<Campaign> 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>&
+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<Text> 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<CampaignPlan> 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<CombatZone> 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<Combatant> 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<CombatAction> 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<MissionInfo> 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<MissionInfo> 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<CampaignPlan> 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<CombatGroup> 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> 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<CombatGroup>& groups)
+{
+ if (g->Type() == type && g->IntelLevel() > Intel::RESERVE) {
+ if (!near_group || g->GetAssignedZone() == near_group->GetAssignedZone())
+ groups.append(g);
+ }
+
+ ListIter<CombatGroup> 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<CombatGroup> groups;
+
+ ListIter<Combatant> 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<CombatGroup>& 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<CombatGroup> subgroup = g->GetComponents();
+ while (++subgroup)
+ FindStrikeTargets(subgroup.value(), strike_group, groups);
+}
+
+CombatGroup*
+Campaign::FindStrikeTarget(int iff, CombatGroup* strike_group)
+{
+ CombatGroup* result = 0;
+
+ List<CombatGroup> groups;
+
+ ListIter<Combatant> 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<CombatAction> 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<Combatant> 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<CombatUnit>& units)
+{
+ if (g) {
+ ListIter<CombatUnit> unit = g->GetUnits();
+ while (++unit) {
+ CombatUnit* u = unit.value();
+
+ if (u->Count() - u->DeadCount() > 0)
+ units.append(u);
+ }
+
+ ListIter<CombatGroup> comp = g->GetComponents();
+ while (++comp) {
+ CombatGroup* g2 = comp.value();
+
+ if (!g2->IsReserve())
+ GetCombatUnits(g2, units);
+ }
+ }
+}
+
+int
+Campaign::GetAllCombatUnits(int iff, List<CombatUnit>& units)
+{
+ units.clear();
+
+ ListIter<Combatant> 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<MissionInfo> 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<MissionInfo>& GetMissionList() { return missions; }
+ List<Combatant>& GetCombatants() { return combatants; }
+ List<CombatZone>& GetZones() { return zones; }
+ List<StarSystem>& GetSystemList() { return systems; }
+ List<CombatAction>& GetActions() { return actions; }
+ List<CombatEvent>& 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<CombatUnit>& units);
+
+ static void Initialize();
+ static void Close();
+ static Campaign* GetCampaign();
+ static List<Campaign>&
+ 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<Combatant> combatants;
+ List<StarSystem> systems;
+ List<CombatZone> zones;
+ List<CampaignPlan> planners;
+ List<MissionInfo> missions;
+ List<TemplateList> templates;
+ List<CombatAction> actions;
+ List<CombatEvent> 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<CombatGroup> 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<CombatZone> z_iter = campaign->GetZones();
+ while (++z_iter) {
+ CombatZone* z = z_iter.value();
+
+ ListIter<ZoneForce> iter = z->GetForces();
+ while (++iter) {
+ ZoneForce* force = iter.value();
+ ListIter<CombatGroup> 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<CombatUnit>& 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<MissionElement> 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<MissionElement> patrols;
+
+ ListIter<MissionElement> 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<CombatZone>& 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<CombatUnit>& 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<CombatUnit>& 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<Instruction> 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<Instruction> 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<MissionElement> 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<MissionElement> 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<CombatGroup> 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> 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<CombatZone> z = campaign->GetZones();
+ while (++z) {
+ ListIter<ZoneForce> iter = z->GetForces();
+ while (++iter) {
+ ZoneForce* force = iter.value();
+ ListIter<CombatGroup> 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<MissionElement> 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<CombatUnit>& 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<MissionElement> 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<CombatZone>& 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<MissionElement> 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<CombatGroup> groups;
+ ListIter<CombatGroup> 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<MissionElement> 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<Combatant> 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<CombatZone> zone = campaign->GetZones();
+ while (++zone) {
+ ProcessZone(c, zone.value());
+ }
+}
+
+// +--------------------------------------------------------------------+
+
+void
+CampaignPlanAssignment::BuildZoneList(CombatGroup* g, CombatZone* zone, List<CombatGroup>& groups)
+{
+ if (!g)
+ return;
+
+ if (g->GetAssignedZone() == zone)
+ groups.append(g);
+
+ ListIter<CombatGroup> iter = g->GetComponents();
+ while (++iter)
+ BuildZoneList(iter.value(), zone, groups);
+}
+
+// +--------------------------------------------------------------------+
+
+void
+CampaignPlanAssignment::BuildAssetList(const int* pref,
+ List<CombatGroup>& groups,
+ List<CombatGroup>& assets)
+{
+ if (!pref)
+ return;
+
+ while (*pref) {
+ ListIter<CombatGroup> g = groups;
+ while (++g) {
+ if (g->Type() == *pref && g->CountUnits() > 0)
+ assets.append(g.value());
+ }
+
+ pref++;
+ }
+}
+
+// +--------------------------------------------------------------------+
+
+void
+CampaignPlanAssignment::ProcessZone(Combatant* c, CombatZone* zone)
+{
+ List<CombatGroup> groups;
+ BuildZoneList(c->GetForce(), zone, groups);
+
+ ZoneForce* force = zone->FindForce(c->GetIFF());
+
+ // defensive assignments:
+ ListIter<CombatGroup> def = force->GetDefendList();
+ while (++def) {
+ List<CombatGroup> assets;
+ BuildAssetList(CombatGroup::PreferredDefender(def->Type()), groups, assets);
+
+ ListIter<CombatGroup> 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<CombatGroup> tgt = force->GetTargetList();
+ while (++tgt) {
+ CombatGroup* target = tgt.value();
+
+ List<CombatGroup> assets;
+ BuildAssetList(CombatGroup::PreferredAttacker(tgt->Type()), groups, assets);
+
+ ListIter<CombatGroup> 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<CombatGroup>& list);
+ virtual void BuildAssetList(const int* pref, List<CombatGroup>& avail, List<CombatGroup>& 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<CombatAction> 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<Text> iter = action->AssetKills();
+ while (++iter) {
+ Text* name = iter.value();
+ CombatUnit* asset = g->FindUnit(*name);
+
+ if (asset) {
+ int value_killed = asset->Kill(1);
+
+ ListIter<Combatant> 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<Text> iter = action->TargetKills();
+ while (++iter) {
+ Text* name = iter.value();
+ CombatUnit* target = g->FindUnit(*name);
+
+ if (target) {
+ int value_killed = target->Kill(1);
+
+ ListIter<Combatant> 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<Combatant> 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<CombatAssignment>& alist)
+{
+ if (!g) return;
+
+ alist.append(g->GetAssignments());
+
+ ListIter<CombatGroup> iter = g->GetComponents();
+ while (++iter)
+ FindAssignments(iter.value(), alist);
+}
+
+CombatAssignment*
+CampaignPlanEvent::ChooseAssignment(CombatGroup* g)
+{
+ List<CombatAssignment> 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<MissionInfo>& 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<CombatAction> 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<CombatAssignment> assignments;
+ assignments.append(player_group->GetAssignments());
+
+ if (player_group->Type() == CombatGroup::WING) {
+ ListIter<CombatGroup> 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<CombatUnit> 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<CombatUnit> 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<CombatUnit> 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<CombatZone> zone = campaign->GetZones();
+ while (++zone)
+ zone->Clear();
+
+ ListIter<Combatant> 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<CombatGroup> iter = g->GetComponents();
+ while (++iter)
+ PlaceGroup(iter.value());
+}
+
+// +--------------------------------------------------------------------+
+
+void
+CampaignPlanStrategic::ScoreCombatant(Combatant* c)
+{
+ // prep lists:
+ c->GetDefendList().clear();
+ c->GetTargetList().clear();
+
+ ScoreDefensible(c);
+
+ ListIter<Combatant> 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<CombatGroup> 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<CombatGroup> iter = g->GetComponents();
+ while (++iter) {
+ ScoreTarget(c, iter.value());
+ }
+}
+
+// +--------------------------------------------------------------------+
+
+void
+CampaignPlanStrategic::ScoreNeeds(Combatant* c)
+{
+ ListIter<CombatZone> 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<CombatGroup> 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<CombatGroup> tgt = force->GetTargetList();
+ while (++tgt) {
+ int attacker_type = *CombatGroup::PreferredAttacker(tgt->Type());
+ force->AddNeed(attacker_type, tgt->Value());
+ }
+ }
+}
+
+// +--------------------------------------------------------------------+
+
+void
+CampaignPlanStrategic::BuildGroupList(CombatGroup* g, List<CombatGroup>& groups)
+{
+ if (!g || g->IsReserve())
+ return;
+
+ if (g->IsAssignable())
+ groups.append(g);
+
+ ListIter<CombatGroup> iter = g->GetComponents();
+ while (++iter)
+ BuildGroupList(iter.value(), groups);
+}
+
+// +--------------------------------------------------------------------+
+
+void
+CampaignPlanStrategic::AssignZones(Combatant* c)
+{
+ // find the list of assignable groups, in priority order:
+ List<CombatGroup> groups;
+ BuildGroupList(c->GetForce(), groups);
+ groups.sort();
+
+ // for each group, assign a zone:
+ ListIter<CombatGroup> 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<CombatZone> possible_zones;
+
+ if (g->IsZoneLocked()) {
+ current_zone = g->GetAssignedZone();
+ current_force = current_zone->FindForce(g->GetIFF());
+ }
+
+ else {
+ ListIter<CombatZone> 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<CombatGroup> squadron = g->GetComponents();
+ while (++squadron) {
+ squadron->SetAssignedZone(assigned_zone);
+ assigned_force->AddNeed(squadron->Type(), -(squadron->Value()));
+
+ if (squadron->Type() == CombatGroup::WING) {
+ ListIter<CombatGroup> 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<CombatZone> possible_zones;
+
+ if (g->IsZoneLocked()) {
+ current_zone = g->GetAssignedZone();
+ current_force = current_zone->FindForce(g->GetIFF());
+ }
+
+ else {
+ ListIter<CombatZone> 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<CombatGroup> 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<CombatGroup>& 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<Campaign>& 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<CombatAction> 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<Campaign>& 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<Combatant> 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<CombatAction> 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<CombatEvent> 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<Text> 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<Text>& 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<Text>& 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<MissionElement> 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<Instruction> nav = player->NavList();
+ while (++nav) {
+ if (rgn0 != nav->RegionName())
+ rgn1 = nav->RegionName();
+ }
+
+ ListIter<MissionElement> 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<Element> 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<Element> assigned;
+ ListIter<Element> 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<Element> 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<ShipLoad> sl = (List<ShipLoad>&) 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<Combatant>& 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<CombatGroup>& 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<CombatGroup>& 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<CombatUnit> 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<CombatGroup>& 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<Combatant> 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<CombatGroup> 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<Combatant>& 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<CombatEvent>& 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("<font Limerick12><color ffff80>");
+ info += event->Title();
+ info += "<font Verdana><color ffffff>\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<MissionInfo>& 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<MissionInfo>& 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("<font Limerick12><color ffff80>");
+ desc += info->name;
+ desc += "<font Verdana><color ffffff>\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("<font Limerick12><color ffff80>");
+ orders += Game::GetText("CmdOrdersDlg.situation");
+ orders += "\n<font Verdana><color ffffff>";
+ if (*campaign->Situation())
+ orders += campaign->Situation();
+ else
+ orders += campaign->Description();
+
+ orders += "\n\n<font Limerick12><color ffff80>";
+ orders += Game::GetText("CmdOrdersDlg.orders");
+ orders += "\n<font Verdana><color ffffff>";
+ 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<Text> 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("<font Limerick12><color ffffff>") +
+ campaign->Name() +
+ Text("<font Verdana>\n\n") +
+ Text("<color ffff80>") +
+ Game::GetText("CmpSelectDlg.scenario") +
+ Text("<color ffffff>\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("<font Limerick12><color ffffff>") +
+ campaign->Name() +
+ Text("<font Verdana>\n\n") +
+ Text("<color ffff80>") +
+ Game::GetText("CmpSelectDlg.scenario") +
+ Text("<color ffffff>\n\t") +
+ campaign->Description() +
+ Text("\n\n<color ffff80>") +
+ Game::GetText("CmpSelectDlg.campaign-time") +
+ Text("<color ffffff>\n\t") +
+ time_buf +
+ Text("\n\n<color ffff80>") +
+ Game::GetText("CmpSelectDlg.assignment") +
+ Text("<color ffffff>\n\t");
+
+ if (campaign->GetPlayerGroup())
+ desc += campaign->GetPlayerGroup()->GetDescription();
+ else
+ desc += "n/a";
+
+ desc += Text("\n\n<color ffff80>") +
+ Game::GetText("CmpSelectDlg.team-score") +
+ Text("<color ffffff>\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<Campaign> 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<Text> 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<Campaign>& 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<Campaign>& 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<Bitmap> 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<<campaign->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<Campaign>& 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<CombatEvent> 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<CombatActionReq> iter = pThis->requirements;
+ while (++iter) {
+ CombatActionReq* r = iter.value();
+ bool ok = false;
+
+ if (r->action > 0) {
+ ListIter<CombatAction> 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<Text>& AssetKills() { return asset_kills; }
+ int TargetType() const { return target_type; }
+ int TargetId() const { return target_id; }
+ int TargetIFF() const { return target_iff; }
+ List<Text>& 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<Text> asset_kills;
+ int target_type;
+ int target_id;
+ int target_iff;
+ List<Text> target_kills;
+
+ List<CombatActionReq> 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<CombatGroup> 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<CombatUnit> 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<CombatGroup> 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<CombatGroup> iter = components;
+ while (++iter) {
+ CombatGroup* g = iter.value();
+ g->SetAssignedZone(z);
+ }
+}
+
+void
+CombatGroup::ClearUnlockedZones()
+{
+ if (!zone_lock)
+ assigned_zone = 0;
+
+ ListIter<CombatGroup> 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<CombatGroup> 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<CombatUnit> unit = units;
+ while (++unit)
+ val += unit->GetValue();
+
+ ListIter<CombatGroup> comp = components;
+ while (++comp)
+ val += comp->CalcValue();
+
+ value = val;
+ return value;
+}
+
+int
+CombatGroup::CountUnits() const
+{
+ int n = 0;
+
+ CombatGroup* g = (CombatGroup*) this;
+
+ ListIter<CombatUnit> unit = g->units;
+ while (++unit)
+ n += unit->Count() - unit->DeadCount();
+
+ CombatGroup* pThis = ((CombatGroup*) this);
+ pThis->live_comp.clear();
+ ListIter<CombatGroup> 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<CombatGroup> 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<CombatUnit> live;
+
+ ListIter<CombatUnit> 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<CombatGroup> 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<CombatUnit> live;
+
+ ListIter<CombatUnit> 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<CombatUnit> 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<CombatGroup> comp = components;
+ while (++comp)
+ comp->AssignRegion(rgn);
+
+ ListIter<CombatUnit> 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<CombatUnit> 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<CombatUnit> 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<CombatUnit> 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<CombatUnit> 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<CombatUnit> u = g->GetUnits();
+ while (++u) {
+ SaveCombatUnit(f, u.value());
+ }
+
+ fprintf(f, " }\n");
+
+ ListIter<CombatGroup> 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<CombatGroup>& GetComponents() { return components; }
+ List<CombatGroup>& GetLiveComponents() { return live_comp; }
+ List<CombatUnit>& 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<CombatAssignment>& 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<CombatUnit> units;
+ List<CombatGroup> components;
+ List<CombatGroup> 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<CombatAssignment> 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<Text> 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<CombatGroup> 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<CombatGroup> 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<CombatUnit> u_iter = group->GetUnits();
+ while (++u_iter) {
+ CombatUnit* u = u_iter.value();
+ value_killed += u->Kill(u->LiveCount());
+ }
+
+ ListIter<CombatGroup> 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<CombatGroup> 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<CombatUnit>& 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<CombatUnit> 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<ZoneForce> 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<CombatZone> zonelist;
+
+List<CombatZone>&
+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<Text>& GetRegions() { return regions; }
+ List<ZoneForce>& GetForces() { return forces; }
+
+ ZoneForce* FindForce(int iff);
+ ZoneForce* MakeForce(int iff);
+
+ void Clear();
+
+ static List<CombatZone>&
+ Load(const char* filename);
+
+private:
+ // attributes:
+ Text name;
+ Text system;
+ List<Text> regions;
+ List<ZoneForce> forces;
+};
+
+// +--------------------------------------------------------------------+
+
+class ZoneForce
+{
+public:
+ ZoneForce(int i);
+
+ int GetIFF() { return iff; }
+ List<CombatGroup>& GetGroups() { return groups; }
+ List<CombatGroup>& GetTargetList() { return target_list; }
+ List<CombatGroup>& 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<CombatGroup> groups;
+ List<CombatGroup> defend_list;
+ List<CombatGroup> 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<CombatGroup> 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<CombatGroup>& GetTargetList() { return target_list; }
+ List<CombatGroup>& GetDefendList() { return defend_list; }
+ List<Mission>& 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<CombatGroup> target_list;
+ List<CombatGroup> defend_list;
+ List<Mission> 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<SimEvent> 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<Graphic> 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<Graphic> rep[MAX_DETAIL];
+ List<Point> off[MAX_DETAIL];
+ double rad[MAX_DETAIL];
+
+ List<Point> spin;
+ List<Point> 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<DisplayElement> 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<DisplayElement> 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<DisplayElement>
+ 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<DrivePort> 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<Instruction> 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<Instruction> navpt = flight_plan;
+ while (++navpt) {
+ index++;
+ if (navpt.value() == n)
+ return index;
+ }
+ }
+
+ return 0;
+}
+
+// +----------------------------------------------------------------------+
+
+List<Instruction>&
+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<Element> 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<Ship> 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<Instruction> 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<Instruction>& 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<Ship> ships;
+ List<Text> ship_names;
+ List<Text> instructions;
+ List<Instruction> objectives;
+ List<Instruction> 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<PowerSource> 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<System> 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<System> iter = ship->RepairQueue();
+ while (++iter) {
+ double time_remaining = 0;
+ char etr[20];
+
+ System* sys = iter.value();
+
+ ListIter<Component> 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<Component> 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<Component> 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<System>& 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<Component> 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<System>& 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<System> 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<System> 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<System> 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<System> 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<System> 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<Component>& 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<Component>& 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<Component> 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<System> route_list;
+
+ PowerSource* selected_source;
+ List<System> 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 <windows.h>
+#include <imagehlp.h>
+
+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<Ship> 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<Ship> 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<Ship> 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<Ship> 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<WeaponGroup> g_iter = starship->Weapons();
+ while (++g_iter) {
+ WeaponGroup* group = g_iter.value();
+
+ ListIter<Weapon> 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<Contact> 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<WeaponGroup>& weps)
+{
+ weps.clear();
+
+ if (tgt) {
+ ListIter<WeaponGroup> 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<WeaponGroup> 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<WeaponGroup> 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<Contact> 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<WeaponGroup> g_iter = s->Weapons();
+ while (++g_iter) {
+ WeaponGroup* w = g_iter.value();
+
+ if (w->Ammo() && w->CanTarget(target->Class())) {
+ ListIter<Weapon> 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<WeaponGroup>& 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<InboundSlot> 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<InboundSlot> 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<InboundSlot>& 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<InboundSlot> 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<Instruction> 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<ShipLoad> sl = (List<ShipLoad>&) 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<Element> 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<ShipLoad> sl = (List<ShipLoad>&) 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<Text> mod_galaxies;
+ loader->SetDataPath("Mods/Galaxy/");
+ loader->ListFiles("*.def", mod_galaxies);
+
+ ListIter<Text> 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<StarSystem> sys = systems;
+ while (++sys) {
+ sys->ExecFrame();
+ }
+}
+
+// +--------------------------------------------------------------------+
+
+StarSystem*
+Galaxy::GetSystem(const char* name)
+{
+ ListIter<StarSystem> sys = systems;
+ while (++sys) {
+ if (!strcmp(sys->Name(), name))
+ return sys.value();
+ }
+
+ return 0;
+}
+
+// +--------------------------------------------------------------------+
+
+StarSystem*
+Galaxy::FindSystemByRegion(const char* rgn_name)
+{
+ ListIter<StarSystem> 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<StarSystem>& GetSystemList() { return systems; }
+ List<Star>& 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<StarSystem> systems;
+ List<Star> 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<Contact> 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<WeaponGroup> 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> 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 && xtarg<width-1 && ytarg>0 && ytarg<height-1) {
+ double range = Point(t->Location() - 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<WeaponGroup> 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 && xtarg<width-1 && ytarg>0 && ytarg<height-1) {
+ double range = Point(t->Location() - 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<PowerSource> 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<Drive> 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<Weapon> 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<Computer> 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<FlightDeck> 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<Instruction> 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<Ship> 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<OrbitalBody> 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<OrbitalBody> 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<Element>& 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<Element>& 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<Surface> 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<Element> 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<Element> 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<Element> 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<StarSystem>& 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<OrbitalBody> 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<OrbitalBody> planet = star->Satellites();
+ while (++planet) {
+ planets.append(planet.value());
+
+ ListIter<OrbitalBody> moon = planet->Satellites();
+ while (++moon) {
+ planets.append(moon.value());
+ }
+ }
+
+ ListIter<OrbitalRegion> 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<StarSystem> 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<Text> 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<Text>& 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<MissionElement> 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<Orbital> 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<Ship> 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<MissionElement> 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<Orbital> 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<Ship> 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<MissionElement> 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<Orbital> 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<Ship> 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<StarSystem> 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<Orbital> 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<Orbital> 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<Orbital> 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<MissionElement> elem = mission->GetElements();
+ while (++elem) {
+ MissionElement* e = elem.value();
+
+ if (!e->IsSquadron() && (editor || e->GetIFF() == mission->Team())) {
+ ListIter<Instruction> 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<Instruction> 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> 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<StarSystem> 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<StarSystem> 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<Star> 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<OrbitalBody> star = system->Bodies();
+ while (++star) {
+ int p_orb = 1;
+
+ ListIter<OrbitalBody> planet = star->Satellites();
+ while (++planet) {
+ DrawOrbital(*planet, p_orb++);
+
+ int m_orb = 1;
+
+ ListIter<OrbitalBody> moon = planet->Satellites();
+ while (++moon) {
+ DrawOrbital(*moon, m_orb++);
+
+ ListIter<OrbitalRegion> region = moon->Regions();
+ while (++region) {
+ DrawOrbital(*region, 1);
+ }
+ }
+
+ ListIter<OrbitalRegion> region = planet->Regions();
+ while (++region) {
+ DrawOrbital(*region, 1);
+ }
+ }
+
+ ListIter<OrbitalRegion> 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 = (cx<cy) ? cx : cy;
+ r = rgn->Radius() * 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<Combatant> iter = campaign->GetCombatants();
+ while (++iter) {
+ Combatant* combatant = iter.value();
+ DrawCombatGroup(combatant->GetForce(), rep);
+ }
+ }
+
+ else if (mission && rgn) {
+ // draw the elements in this region:
+ ListIter<MissionElement> 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<Contact> 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<Ship> 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<SimRegion> r_iter = sim->GetRegions();
+ while (++r_iter) {
+ SimRegion* r = r_iter.value();
+
+ if (r != simrgn) {
+ ListIter<Ship> 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 = (cx<cy) ? cx : cy;
+ r = system->Radius() * 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<Combatant> 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 = (cx<cy) ? cx : cy;
+ r = rgn->Radius() * 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 = (cx<cy) ? cx : cy;
+ r = rgn->Radius() * 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<Instruction>& 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 = (cx<cy) ? cx : cy;
+ r = system->Radius() * 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<CombatGroup> 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 = (cx<cy) ? cx : cy;
+ r = rgn->Radius() * 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<CombatGroup> 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<Ship> 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 = (cx<cy) ? cx : cy;
+ r = system->Radius() * 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<MissionElement> 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 = (cx<cy) ? cx : cy;
+ r = system->Radius() * 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<Instruction>& 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<StarSystem>& 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<StarSystem>& 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<Text>& result);
+ void SetupScroll(Orbital* s);
+
+ double GetMinRadius(int type);
+
+ Text title;
+ Rect rect;
+ Campaign* campaign;
+ Mission* mission;
+ List<StarSystem> system_list;
+ StarSystem* system;
+ List<Orbital> stars;
+ List<Orbital> planets;
+ List<Orbital> 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<MenuItem> 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<MenuItem> 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<MenuItem> 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<Contact> 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> 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> 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<MissionElement> 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<MissionElement> 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<MissionElement> 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<MissionEvent> 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<StarSystem> 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<MissionElement> 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<MissionLoad> 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<Instruction> 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<Instruction> 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<Text> i_iter = elem->Instructions();
+ while (++i_iter) {
+ s += " instr: \"";
+ s += SafeString(*i_iter.value());
+ s += "\"\n";
+ }
+ }
+
+ if (elem->Ships().size()) {
+ ListIter<MissionShip> 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<MissionEvent> 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<Instruction> 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<StarSystem>& GetSystemList() { return system_list; }
+ const char* GetRegion() const { return region; }
+
+ List<MissionElement>& GetElements() { return elements; }
+ virtual MissionElement* FindElement(const char* name);
+ virtual void AddElement(MissionElement* elem);
+
+ List<MissionEvent>& 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<StarSystem> system_list;
+
+ List<MissionElement> elements;
+ List<MissionEvent> 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<Instruction>& Objectives() { return objectives; }
+ List<Text>& Instructions() { return instructions; }
+ List<Instruction>& NavList() { return navlist; }
+ List<MissionLoad>& Loadouts() { return loadouts; }
+ List<MissionShip>& 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<Instruction> objectives;
+ List<Text> instructions;
+ List<Instruction> navlist;
+ List<MissionLoad> loadouts;
+ List<MissionShip> 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<SimRegion> iter = sim->GetRegions();
+ while (++iter) {
+ SimRegion* rgn = iter.value();
+
+ ListIter<Ship> 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<MissionEvent> 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<MissionEvent> 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<MissionEvent> 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<WeaponGroup> g_iter = ship->Weapons();
+ while (++g_iter) {
+ ListIter<Weapon> 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<Instruction> 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<Instruction> 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<CombatGroup>& 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<CombatGroup> 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<CombatGroup> group_list;
+ static int combat_group_index = 0;
+
+ if (campaign) {
+ ListIter<Combatant> 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<MissionCallsign> c_iter = callsigns;
+ while (++c_iter) {
+ MissionCallsign* c = c_iter.value();
+ if (c->Name() == name) {
+ name = c->Callsign();
+ break;
+ }
+ }
+
+ ListIter<MissionAlias> a_iter = aliases;
+ while (++a_iter) {
+ MissionAlias* a = a_iter.value();
+ if (a->Name() == name)
+ return a->Element();
+ }
+
+ ListIter<MissionElement> 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<MissionElement> iter = elements;
+ while (++iter) {
+ MissionElement* elem = iter.value();
+
+ ListIter<Instruction> 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<MissionAlias> aliases;
+ List<MissionCallsign> 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<Text> 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<Text> 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<Text> 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<Text> 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<Text> 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<ModInfo> 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<Text>& EnabledMods() { return enabled; }
+ List<Text>& DisabledMods() { return disabled; }
+ List<ModInfo>& GetModInfoList() { return mods; }
+
+ ModInfo* GetModInfo(const char* filename);
+
+private:
+ List<Text> enabled;
+ List<Text> disabled;
+ List<ModInfo> 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<Text> iter_d = config->DisabledMods();
+ while (++iter_d) {
+ Text* t = iter_d.value();
+ lst_disabled->AddItem(*t);
+ }
+
+ ListIter<Text> 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<ModCampaign>& 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<ModCampaign> 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<Instruction> 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<StarSystem> 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<OrbitalRegion> regions;
+ regions.append(sys->AllRegions());
+ regions.sort();
+
+ i = 0;
+ ListIter<OrbitalRegion> 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<MissionElement> 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<MissionEvent> 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<ActiveWindow> 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<StarSystem> 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<OrbitalRegion> regions;
+ regions.append(sys->AllRegions());
+ regions.sort();
+
+ ListIter<OrbitalRegion> 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<MissionElement>& 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<MissionElement>& 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<MissionEvent>& 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<MissionEvent>& 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<StarSystem> 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<OrbitalRegion> regions;
+ regions.append(sys->AllRegions());
+ regions.sort();
+
+ i = 0;
+ ListIter<OrbitalRegion> 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<StarSystem> 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<OrbitalRegion> regions;
+ regions.append(sys->AllRegions());
+ regions.sort();
+
+ ListIter<OrbitalRegion> 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<OrbitalRegion> regions;
+ regions.append(sys->AllRegions());
+ regions.sort();
+
+ ListIter<OrbitalRegion> 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<MissionElement> 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<MissionElement> 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<MissionElement> 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<Text> 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<ShipLoad>& 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<Skin> 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<MissionElement> 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<Instruction>& 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<MissionElement>& 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<StarSystem> iter = mission->GetSystemList();
+ while (++iter) {
+ StarSystem* sys = iter.value();
+
+ ListIter<OrbitalRegion> 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<Skin> 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<MissionElement> 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<Instruction> 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<Instruction> 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<MissionElement> 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<MissionElement> 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<Campaign> 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<Campaign> 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<MissionInfo> 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<MissionInfo> iter = campaign->GetMissionList();
+ while (++iter) {
+ cmb_campaigns->AddItem(iter->name);
+ }
+ }
+
+ else if (lst_missions) {
+ lst_missions->ClearItems();
+
+ ListIter<MissionInfo> 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<MissionInfo>& 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("<font Limerick12><color ffffff>");
+ d += info->name;
+ d += "<font Verdana>\n\n<color ffff80>";
+ d += Game::GetText("MsnSelectDlg.mission-type");
+ d += "<color ffffff>\n\t";
+ d += Mission::RoleName(info->type);
+ d += "\n\n<color ffff80>";
+ d += Game::GetText("MsnSelectDlg.scenario");
+ d += "<color ffffff>\n\t";
+ d += info->description;
+ d += "\n\n<color ffff80>";
+ d += Game::GetText("MsnSelectDlg.location");
+ d += "<color ffffff>\n\t";
+ d += info->region;
+ d += " ";
+ d += Game::GetText("MsnSelectDlg.sector");
+ d += " / ";
+ d += info->system;
+ d += " ";
+ d += Game::GetText("MsnSelectDlg.system");
+ d += "\n\n<color ffff80>";
+ d += Game::GetText("MsnSelectDlg.start-time");
+ d += "<color ffffff>\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<ShipLoad> sl = (List<ShipLoad>&) 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<HardPoint> 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<ShipLoad> 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<MissionLoad> 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<ShipLoad> sl = (List<ShipLoad>&) 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<MissionLoad> 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<Text> files;
+ loader->ListFiles("*.ogg", files, true);
+
+ if (files.size() == 0) {
+ loader->UseFileSystem(old_file_system);
+ no_music = true;
+ return;
+ }
+
+ no_music = false;
+
+ ListIter<Text> 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<Text>* 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<Text>* 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<Text> menu_tracks;
+ List<Text> intro_tracks;
+ List<Text> brief_tracks;
+ List<Text> debrief_tracks;
+ List<Text> promote_tracks;
+ List<Text> flight_tracks;
+ List<Text> combat_tracks;
+ List<Text> launch_tracks;
+ List<Text> recovery_tracks;
+ List<Text> victory_tracks;
+ List<Text> defeat_tracks;
+ List<Text> 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<Ship> 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<OrbitalBody> 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<OrbitalBody> planet = star->Satellites();
+ while (++planet) {
+ planets.append(planet.value());
+
+ ListIter<OrbitalBody> moon = planet->Satellites();
+ while (++moon) {
+ planets.append(moon.value());
+ }
+ }
+ }
+
+ ListIter<OrbitalRegion> 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<MissionElement> 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<Orbital> 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<Orbital> 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<OrbitalRegion> 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<StarSystem> 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<Orbital> 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<OrbitalRegion> 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<MissionElement> 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<SimRegion> r_iter = sim->GetRegions();
+ while (++r_iter) {
+ SimRegion* rgn = r_iter.value();
+
+ ListIter<Ship> 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<Orbital> stars;
+ List<Orbital> planets;
+ List<OrbitalRegion> regions;
+ List<Ship> 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 = "<div style=\"overflow-y:scroll; height:240px; padding-right:4pt; padding-left:4pt; padding-bottom:4pt; padding-top:4pt; margin:4pt;\">\n";
+
+ int nchat = 0;
+ NetLobbyServer* lobby = NetLobbyServer::GetInstance();
+ if (lobby) {
+ content += "\n<table width=\"90%\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n";
+
+ ListIter<NetChatEntry> iter = lobby->GetChat();
+ while (++iter) {
+ NetChatEntry* c = iter.value();
+
+ content += " <tr><td nowrap width=\"130\" class=\"tiny\">";
+ content += FormatTimeString(c->GetTime());
+ content += "</td><td nowrap width=\"80\" class=\"tiny\">";
+ content += c->GetUser();
+ content += "</td><td class=\"tiny\">";
+ content += c->GetMessage();
+ content += "</td></tr>\n";
+ }
+
+ content += "</table>\n\n";
+ }
+
+ content += "</div>\n<div class=\"content\">\n\
+ <form name=\"chatForm\" method=\"post\"action=\"/chat\">\n\
+ <table border=\"0\">\n\
+ <tr>\n\
+ <td valign=\"middle\">&nbsp;&nbsp;<input type=\"text\" name=\"msg\" size=\"80\"></td>\n\
+ <td valign=\"middle\">&nbsp;&nbsp;<input type=\"submit\" value=\"Send\"></td>\n\
+ </tr>\n\
+ <tr>\n\
+ <td colspan=\"2\" valign=\"middle\" class=\"std\">&nbsp;&nbsp;<a href=\"/chat\">Refresh</a>\
+&nbsp;&nbsp;&#183;&nbsp;&nbsp;<a href=\"/chat?action=save\">Save</a>&nbsp;&nbsp;&#183;&nbsp;&nbsp;<a href=\"/chat?action=clear\">Clear</a></td>\n\
+ </tr>\n\
+ </table>\n\
+ </form>\n\
+</div>\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") +
+ "<body><br>You are already logged in.<br>" +
+ 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") +
+ "<body><br>You have successfully logged in.<br>" +
+ 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 =
+" <table border=\"0\" cellspacing=\"0\" cellpadding=\"4\" align=\"left\" width =\"100%\">\n\
+ <tr>\n\
+ <td width=\"100\">&nbsp;&nbsp;</td>\n\
+ <td valign=\"top\" align=\"left\" width=\"400\"><br><br>\n\
+ <span class=\"subhead\">Welcome to the Starshatter Server!</span><br><br>\n\
+ <span class=\"std\">Login to access the server <b>";
+
+ NetServerConfig* config = NetServerConfig::GetInstance();
+ if (config)
+ content += config->Name();
+ else
+ content += "server";
+
+ content += "</b></span><br>\n\
+ <form name=\"loginForm\" method=\"get\" action=\"/login\">\n\
+ <table border=\"0\" cellspacing=\"0\" cellpadding=\"4\" width=\"100\">\n\
+ <tr>\n\
+ <td align=\"left\" valign=\"middle\" width=\"80\"><span class=\"std\">Username:</span></td>\n\
+ <td align=\"left\" valign=\"middle\"><input type=\"text\" name=\"user\" size=\"25\"></td>\n\
+ </tr>\n\
+ <tr>\n\
+ <td align=\"left\" valign=\"middle\" width=\"80\"><span class=\"std\">Password:</span></td>\n\
+ <td align=\"left\" valign=\"middle\"><input type=\"password\" name=\"pass\" size=\"25\"></td>\n\
+ </tr>\n\
+ <tr>\n\
+ <td>&nbsp;</td>\n\
+ <td align=\"right\"><input type=\"submit\" value=\"Login\"></td>\n\
+ </tr>\n\
+ </table>\n\
+ </form>\n\
+ <td>&nbsp;</td>\n\
+ </tr>\n\
+ </table>\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() +
+ "<div class=\"content\"><b>The Starshatter Server will restart in three (3) seconds.</b><br></div>" +
+ 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() +
+ "<div class=\"content\"><b>The Starshatter Server will shutdown in three (3) seconds.</b><br></div>" +
+ 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") +
+ "<body><br>Unknown Action.<br>" +
+ 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 =
+"<script LANGUAGE=\"JavaScript\">\n\
+<!--\n\
+function doConfirm() {\n\
+ return confirm(\"Are you sure you want to ban this player?\");\n\
+}\n\
+// -->\n\
+</script>\n\
+<div class=\"content\">\n\
+ <table border=\"0\" width=\"95%\">\n\
+ <tr class=\"heading\">\n\
+ <td nowrap valign=\"middle\" align=\"left\">\n\
+ <span class=\"heading\">&nbsp;User List</span>\n\
+ </td>\n\
+ </tr>\n\
+ </table>\n\n";
+
+ content +=
+" <table border=\"0\" width=\"95%\" class=\"std\">\n\
+ <tr>\n\
+ <td nowrap width=\"1%\">&nbsp;</td>\n\
+ <td nowrap width=\"20%\" valign=\"middle\" align=\"left\"><b>Name</b></td>\n\
+ <td nowrap width=\"10%\" valign=\"middle\" align=\"left\"><b>Address</b></td>\n\
+ <td nowrap width=\"10%\" valign=\"middle\" align=\"center\"><b>Is Host</b></td>\n\
+ <td nowrap width=\"20%\" valign=\"middle\" align=\"left\"><b>Squadron</b></td>\n\
+ <td nowrap width=\"20%\" valign=\"middle\" align=\"center\"><b>Stats</b></td>\n\
+ <td nowrap width=\"19%\" valign=\"middle\" align=\"center\"><b>Ban</b></td>\n\
+ <td></td>\n\
+ </tr>\n";
+
+ NetLobbyServer* lobby = NetLobbyServer::GetInstance();
+
+ if (lobby) {
+ ListIter<NetUser> 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 += "<tr>\n<td nowrap width=\"1%\">&nbsp;</td>\n\
+ <td nowrap valign=\"middle\" align=\"left\">";
+ content += u->Name();
+ content += "</td><td nowrap valign=\"middle\" align=\"left\">";
+ content += addr_dotted;
+ content += "</td><td nowrap valign=\"middle\" align=\"center\">";
+ content += u->IsHost() ? "*" : "&nbsp;";
+ content += "</td><td nowrap valign=\"middle\" align=\"left\">";
+ content += u->Squadron();
+ content += "</td><td nowrap valign=\"middle\" align=\"center\">";
+ content += user_stats;
+ content += "</td><td nowrap valign=\"middle\" align=\"center\">";
+ content += "<a onclick=\"return doConfirm()\" href=\"/ban?name=";
+ content += HttpRequest::EncodeParam(u->Name());
+ content += "&addr=";
+ content += addr_hex;
+ content += "\">BAN</a></td></tr>\n";
+ }
+ }
+
+ content += " </table>\n\n";
+
+ content += "</div>\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<NetUser> 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() +
+ "<div class=\"content\">User Banned.<br></div>" +
+ 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<NetChatEntry>
+NetAdminServer::GetChat()
+{
+ NetLobbyServer* lobby = NetLobbyServer::GetInstance();
+
+ if (lobby)
+ return lobby->GetChat();
+
+ static List<NetChatEntry> 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<NetUser>&
+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<NetUser> iter = admin_users;
+ while (++iter) {
+ NetUser* u = iter.value();
+ if (u->GetSessionID() == id)
+ return u;
+ }
+
+ return 0;
+}
+
+void
+NetAdminServer::DoSyncedCheck()
+{
+ ListIter<NetUser> iter = admin_users;
+ while (++iter) {
+ NetUser* u = iter.value();
+
+ bool found = false;
+
+ ListIter<HttpSession> 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 = "<html>\n<head>\n<title>Starshatter Server";
+
+ if (title && *title) {
+ head += " - ";
+ head += title;
+ }
+
+ head += "</title>\n<style type=\"text/css\" media=\"screen\">\n";
+ head += GetCSS();
+ head += "</style>\n</head>\n";
+
+ return head;
+}
+
+Text
+NetAdminServlet::GetBody()
+{
+ return GetTitleBar(GetStatLine()) +
+ GetContent() +
+ GetBodyClose();
+}
+
+Text
+NetAdminServlet::GetTitleBar(const char* statline, const char* onload)
+{
+ Text bar = "<body ";
+
+ if (onload && *onload)
+ bar += onload;
+
+ bar += " leftmargin=\"0\" topmargin=\"0\" marginwidth=\"0\" marginheight=\"0\">\n\
+ <table border=\"0\" cellspacing=\"0\" cellpadding=\"0\" width=\"100%\" class=\"top-bar\">\n\
+ <tr height=\"50\">\n\
+ <td>&nbsp;</td>\n\
+ <td valign=\"middle\" align=\"left\">\n\
+ <span class=\"topbarsmall\">Administration Console</span><br>\n";
+
+ if (statline) {
+ bar += "<a href=\"/home\">";
+ }
+
+ bar += "<span class=\"topbarbig\">Starshatter Server ";
+ bar += versionInfo;
+ bar += "</span>";
+
+ if (statline) {
+ bar += "</a>";
+ }
+
+ bar += "\n\
+ </td>\n\
+ </tr>\n\
+ <tr class=\"top-line\">\n\
+ <td colspan=\"2\">";
+
+ if (statline && *statline)
+ bar += statline;
+ else
+ bar += "&nbsp;";
+
+ bar += "</td>\n\
+ </tr>\n\
+ </table>\n\n";
+
+ return bar;
+}
+
+Text
+NetAdminServlet::GetStatLine()
+{
+ NetServerConfig* config = NetServerConfig::GetInstance();
+
+ Text line =
+" <table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" border=\"0\">\n\
+ <tr>\n\
+ <td nowrap width=\"33%\" class=\"top-line\" align=\"left\">\n\
+ <span class=\"status\">&nbsp;&nbsp;Connected to <b>";
+
+ char buffer[256];
+ sprintf(buffer, "%s:%d", config->Name().data(), config->GetAdminPort());
+ line += buffer;
+
+ line += "</b></span>\n\
+ </td>\n\
+ <td nowrap width=\"34%\" class=\"top-line\" align=\"center\">\n\
+ <span class=\"status\">Server Mode: <b>";
+
+ 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 += "</b></span>\n\
+ </td>\n\
+ <td nowrap width=\"33%\" class=\"top-line\" align=\"right\">\n\
+ <span class=\"status\">";
+
+ line += FormatTimeString();
+
+ line += "&nbsp;&nbsp;</span>\n\
+ </td>\n\
+ </tr>\n\
+ </table>\n";
+
+ return line;
+}
+
+Text
+NetAdminServlet::GetContent()
+{
+ Text content =
+"<script LANGUAGE=\"JavaScript\">\n\
+<!--\n\
+function doConfirm() {\n\
+ return confirm(\"Are you sure you want to do this?\");\n\
+}\n\
+// -->\n\
+</script>\n\
+<div class=\"content\">\n\
+ <table border=\"0\" width=\"95%\">\n\
+ <tr class=\"heading\">\n\
+ <td nowrap valign=\"middle\" align=\"left\">\n\
+ <span class=\"heading\">&nbsp;Game Admin Functions</span>\n\
+ </td>\n\
+ </tr>\n\
+ </table>\n\n\
+ <table border=\"0\" width=\"95%\">\n\
+ <tr>\n\
+ <td nowrap width=\"1%\">&nbsp;</td>\n\
+ <td nowrap width=\"33%\" valign=\"middle\" align=\"left\">\n\
+ <a href=\"/chat\">Lobby Chat</a>\n\
+ </td>\n\
+ <td nowrap width=\"33%\" valign=\"middle\" align=\"left\">\n\
+ <a href=\"/home\">Mission List</a>\n\
+ </td>\n\
+ <td></td>\n\
+ </tr>\n\
+ <tr>\n\
+ <td nowrap width=\"1%\">&nbsp;</td>\n\
+ <td nowrap width=\"33%\" valign=\"middle\" align=\"left\">\n\
+ <a href=\"/file?name=errlog.txt\">View Error Log</a>\n\
+ </td>\n\
+ <td nowrap width=\"33%\" valign=\"middle\" align=\"left\">\n\
+ <a href=\"/users\">Player List</a>\n\
+ </td>\n\
+ <td></td>\n\
+ </tr>\n\
+ <tr>\n\
+ <td nowrap width=\"1%\">&nbsp;</td>\n\
+ <td nowrap width=\"33%\" valign=\"middle\" align=\"left\">\n\
+ <a href=\"/file?name=serverlog.txt\">View Server Log</a>\n\
+ </td>\n\
+ <td nowrap width=\"33%\" valign=\"middle\" align=\"left\">\n\
+ <a href=\"/home\">Ban List</a>\n\
+ </td>\n\
+ <td></td>\n\
+ </tr>\n\
+ <tr>\n\
+ <td nowrap width=\"1%\">&nbsp;</td>\n\
+ <td nowrap width=\"33%\" valign=\"middle\" align=\"left\"></td>\n\
+ <td nowrap width=\"33%\" valign=\"middle\" align=\"left\"></td>\n\
+ <td></td>\n\
+ </tr>\n\
+ </table>\n\n";
+
+ content +=
+" <table border=\"0\" width=\"95%\">\n\
+ <tr class=\"heading\">\n\
+ <td nowrap valign=\"middle\" align=\"left\">\n\
+ <span class=\"heading\">&nbsp;Server Admin Functions</span>\n\
+ </td>\n\
+ </tr>\n\
+ </table>\n\n\
+ <table border=\"0\" width=\"95%\">\n\
+ <tr>\n\
+ <td nowrap width=\"1%\">&nbsp;</td>\n\
+ <td nowrap width=\"33%\" valign=\"middle\" align=\"left\">\n\
+ <a onclick=\"return doConfirm()\" href=\"/server?action=restart\">Restart Server</a>\n\
+ </td>\n\
+ <td nowrap width=\"33%\" valign=\"middle\" align=\"left\">\n\
+ <a onclick=\"return doConfirm()\" href=\"/server?action=shutdown\">Shutdown Server</a>\n\
+ </td>\n\
+ <td></td>\n\
+ </tr>\n\
+ </table>\n\n";
+
+ content += "</div>\n\n";
+ content += GetCopyright();
+ return content;
+}
+
+Text
+NetAdminServlet::GetBodyClose()
+{
+ return "\n\n</body>\n</html>\n";
+}
+
+Text
+NetAdminServlet::GetCopyright()
+{
+ return "<br><span class=\"copy\">&nbsp;&nbsp;&nbsp;&nbsp;Copyright &copy; 1997-2004 Destroyer Studios. All rights reserved.</span><br>";
+}
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<NetUser>& GetUsers();
+
+ virtual NetUser* FindUserBySession(Text id);
+
+ virtual void AddChat(NetUser* user, const char* msg);
+ ListIter<NetChatEntry> 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<NetUser> 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<ModInfo>& mods = config->GetModInfoList();
+ ListIter<ModInfo> 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<NetServerInfo>& 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<NetServerInfo>& 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<NetServerInfo> 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<NetServerInfo> 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<NetServerInfo>& 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<NetServerInfo> 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<NetServerInfo> 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<NetPlayer> 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<NetJoinAnnounce> 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<NetJoinAnnounce> 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<SimRegion> rgn_iter = sim->GetRegions();
+ while (++rgn_iter) {
+ SimRegion* rgn = rgn_iter.value();
+
+ ListIter<Ship> 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<NetPlayer> 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<Ship> iter = ships;
+ while (++iter) {
+ Ship* s = iter.value();
+ s->SetRespawnCount(0);
+ }
+
+ zombies.destroy();
+ ships.clear();
+}
+
+// +--------------------------------------------------------------------+
+
+void
+NetGameServer::ExecFrame()
+{
+ NetGame::ExecFrame();
+ CheckSessions();
+
+ ListIter<SimRegion> rgn_iter = sim->GetRegions();
+ while (++rgn_iter) {
+ SimRegion* rgn = rgn_iter.value();
+
+ ListIter<Ship> 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<NetPlayer> 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<NetPlayer> 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<Ship> 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<NetPlayer> 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<NetPlayer> 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<NetPlayer> 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<NetPlayer> 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<Ship> 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<NetPlayer> 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<Ship> ships;
+ List<NetPlayer> 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<NetUser>&
+NetLobby::GetUsers()
+{
+ return users;
+}
+
+List<ModInfo>&
+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<NetLobbyParam>& 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<NetChatEntry>&
+NetLobby::GetChat()
+{
+ return chat_log;
+}
+
+void
+NetLobby::ClearChat()
+{
+ chat_log.destroy();
+}
+
+// +-------------------------------------------------------------------+
+
+List<NetCampaignInfo>&
+NetLobby::GetCampaigns()
+{
+ return campaigns;
+}
+
+// +-------------------------------------------------------------------+
+
+void
+NetLobby::AddUnitMap(MissionElement* elem, int index)
+{
+ if (elem)
+ unit_map.append(new(__FILE__,__LINE__) NetUnitEntry(elem, index));
+}
+
+List<NetUnitEntry>&
+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<Campaign> 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<MissionElement> 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<NetUser>& GetUsers();
+
+ virtual List<ModInfo>& 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<NetChatEntry>&
+ GetChat();
+ virtual void ClearChat();
+ virtual void SaveChat() { }
+ virtual DWORD GetStartTime() const { return start_time; }
+
+ virtual List<NetCampaignInfo>&
+ GetCampaigns();
+
+ virtual void AddUnitMap(MissionElement* elem, int index=0);
+ virtual List<NetUnitEntry>&
+ 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<NetLobbyParam>& params);
+
+ NetLink* link;
+ NetUser* local_user;
+ List<NetUser> users;
+ List<NetChatEntry> chat_log;
+ List<NetCampaignInfo> campaigns;
+ List<NetUnitEntry> unit_map;
+ Text machine_info;
+ List<ModInfo> 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<MissionInfo> 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<NetChatEntry>&
+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<NetUser>&
+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<ModInfo>&
+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<NetUnitEntry>&
+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<NetCampaignInfo>&
+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<NetLobbyParam> 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<NetLobbyParam> 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<NetLobbyParam> 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<NetLobbyParam> 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<NetLobbyParam> 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<NetLobbyParam> 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<NetLobbyParam> 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<NetLobbyParam> 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<NetChatEntry>&
+ GetChat();
+
+ NetAddr GetServerAddr() const { return addr; }
+ virtual bool IsHost() const { return host; }
+ virtual bool IsClient() const { return true; }
+
+ virtual List<NetUser>& GetUsers();
+ virtual List<NetCampaignInfo>& GetCampaigns();
+ virtual List<NetUnitEntry>& GetUnitMap();
+ virtual void MapUnit(int n, const char* user, bool lock=false);
+ virtual void SelectMission(DWORD id);
+ virtual Mission* GetSelectedMission();
+
+ virtual List<ModInfo>& 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<MissionInfo> 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<NetUser> 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<NetChatEntry> 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<NetCampaignInfo>& campaigns = net_lobby->GetCampaigns();
+
+ if (campaigns.size()) {
+ ListIter<NetCampaignInfo> 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<MissionInfo> 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<NetCampaignInfo>& 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<NetCampaignInfo>& 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<NetCampaignInfo>& 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<MissionInfo> iter = c->missions;
+ while (++iter) {
+ MissionInfo* m = iter.value();
+ lst_missions->AddItem(m->name);
+ }
+ }
+ }
+ }
+}
+
+void
+NetLobbyDlg::OnMissionSelect(AWEvent* event)
+{
+ if (net_lobby) {
+ List<NetCampaignInfo>& 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<Campaign> c_iter = Campaign::GetAllCampaigns();
+ while (++c_iter && !mission_id) {
+ Campaign* campaign = c_iter.value();
+
+ if (campaign->GetCampaignId() == Campaign::MULTIPLAYER_MISSIONS) {
+ ListIter<MissionInfo> 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<Campaign> 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<MissionInfo> m_iter = campaign->GetMissionList();
+ while (++m_iter) {
+ MissionInfo* m = m_iter.value();
+ c->missions.append(m);
+ }
+ }
+ }
+ }
+
+ ModConfig* config = ModConfig::GetInstance();
+ List<ModInfo>& mods = config->GetModInfoList();
+
+ server_mods.clear();
+ server_mods.append(mods);
+
+ net_lobby_server = this;
+}
+
+NetLobbyServer::~NetLobbyServer()
+{
+ ListIter<NetUser> 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<ModInfo>& 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<Campaign> c_iter = Campaign::GetAllCampaigns();
+ while (++c_iter && !mission_id) {
+ Campaign* campaign = c_iter.value();
+
+ if (campaign->GetCampaignId() == Campaign::MULTIPLAYER_MISSIONS) {
+ ListIter<MissionInfo> 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<NetUser> 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<NetUser> 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<NetUser> 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<NetUser> iter = users;
+ while (++iter) {
+ NetUser* u = iter.value();
+ SendData(u, NET_LOBBY_MISSION_SELECT, buffer);
+ }
+}
+
+// +-------------------------------------------------------------------+
+
+List<NetUnitEntry>&
+NetLobbyServer::GetUnitMap()
+{
+ if (!mission) {
+ unit_map.destroy();
+ return unit_map;
+ }
+
+ List<NetUnitEntry> units;
+ ListIter<MissionElement> 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<NetUnitEntry> map_iter = GetUnitMap();
+ while (++map_iter) {
+ NetUnitEntry* unit = map_iter.value();
+ reply += unit->GetDescription();
+ }
+
+ ListIter<NetUser> 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<NetUnitEntry> map_iter = GetUnitMap();
+ while (++map_iter) {
+ NetUnitEntry* unit = map_iter.value();
+ reply += unit->GetDescription();
+ }
+
+ ListIter<NetUser> u_iter = users;
+ while (++u_iter) {
+ NetUser* u = u_iter.value();
+ SendData(u, NET_LOBBY_UNIT_LIST, reply);
+ }
+}
+
+void
+NetLobbyServer::SendUnits()
+{
+ Text content;
+
+ ListIter<NetUnitEntry> map_iter = GetUnitMap();
+ while (++map_iter) {
+ NetUnitEntry* unit = map_iter.value();
+ content += unit->GetDescription();
+ }
+
+ ListIter<NetUser> 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<NetUnitEntry> 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<ModInfo>& mods = config->GetModInfoList();
+ ListIter<ModInfo> 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<NetLobbyParam> 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<NetLobbyParam> 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<NetChatEntry> 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<NetUser> 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<NetLobbyParam> 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<Campaign> 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<MissionInfo> 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<NetLobbyParam> 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<NetUnitEntry> 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<NetLobbyParam> 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<NetUnitEntry>&
+ 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<Text> 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<WeaponGroup> iter = ship->Weapons();
+ while (++iter) {
+ WeaponGroup* group = iter.value();
+
+ ListIter<Weapon> 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<NetAddr> 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<Text> 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<NetAddr> banned_addrs;
+ List<Text> 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<NetUnitEntry> iter = net_lobby->GetUnitMap();
+ List<NetUnitEntry>& units = iter.container();
+ List<NetUser>& 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<NetUser> available_users;
+
+ NetUser* u = net_lobby->GetLocalUser();
+ if (u) {
+ bool assigned = false;
+ ListIter<NetUnitEntry> iter = net_lobby->GetUnitMap();
+ while (++iter) {
+ NetUnitEntry* unit = iter.value();
+ if (unit->GetUserName() == u->Name())
+ assigned = true;
+ }
+
+ if (!assigned)
+ available_users.append(u);
+ }
+
+ ListIter<NetUser> iter = net_lobby->GetUsers();
+ while (++iter) {
+ NetUser* u = iter.value();
+ bool assigned = false;
+ ListIter<NetUnitEntry> 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<NetUnitEntry> iter = net_lobby->GetUnitMap();
+ List<NetUnitEntry>& units = iter.container();
+ List<NetUser>& 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<NetChatEntry> 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<AwardInfo> rank_table;
+static List<AwardInfo> 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<AwardInfo> 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<AwardInfo> 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<AwardInfo> 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<AwardInfo> iter = rank_table;
+ while (++iter) {
+ AwardInfo* award = iter.value();
+ if (award->id == rank)
+ return award->desc;
+ }
+
+ return "";
+}
+
+int
+Player::RankFromName(const char* name)
+{
+ ListIter<AwardInfo> 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<AwardInfo> 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<AwardInfo> 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<AwardInfo> 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<AwardInfo> 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<AwardInfo> 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<AwardInfo> 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> player_roster;
+static Player* current_player = 0;
+
+List<Player>&
+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<Player> 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<Player> 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<Player>& 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<Player>& 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<Player>& 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<Player>& 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<Player>& 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<System> 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<System>& 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<System> 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<Ship> 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<Ship> 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<Ship> 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<MenuItem> 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<SimRegion> rgn_list;
+
+ ListIter<SimRegion> 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<Contact> 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<Contact> 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<SimObject>& 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<SimObject> 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<RadioMessage> 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<MenuItem> 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<Element> 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<RadioVox> 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<Sound> 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<Shot> 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> 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<Shot> threat_iter(ship->GetThreatList());
+ while (++threat_iter) {
+ ProcessContact(threat_iter.value(), az1, az2);
+ }
+
+ ListIter<Drone> drone_iter(ship->GetRegion()->Drones());
+ while (++drone_iter) {
+ ProcessContact(drone_iter.value(), az1, az2);
+ }
+
+ List<Contact>& track_list = ship->GetRegion()->TrackList(ship->GetIFF());
+ ListIter<Contact> 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<Contact> iter(contacts);
+ while (++iter) {
+ Contact* c = iter.value();
+ if (c->GetShip() == s)
+ return c;
+ }
+
+ return 0;
+}
+
+Contact*
+Sensor::FindContact(Shot* s)
+{
+ ListIter<Contact> 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> contact(ship->ContactList());
+
+ List<TargetOffset> 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<TargetOffset> 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> 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> 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> 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<Contact> 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<FlightDeck> 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<Bitmap>& 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<WeaponGroup> g = weapons;
+ while (++g) {
+ ListIter<Weapon> 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<WeaponGroup> g = weapons;
+ while (++g) {
+ ListIter<Weapon> 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<Contact>&
+Ship::ContactList()
+{
+ if (region)
+ return region->TrackList(GetIFF());
+
+ static List<Contact> empty_contact_list;
+ return empty_contact_list;
+}
+
+Contact*
+Ship::FindContact(SimObject* s) const
+{
+ if (!s) return 0;
+
+ ListIter<Contact> 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<Ship> 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<WeaponGroup> 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<Weapon> 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<Component> 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<Weapon> 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<Contact> 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<Weapon> 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<Weapon> 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<Instruction>&
+Ship::GetFlightPlan()
+{
+ if (element)
+ return element->GetFlightPlan();
+
+ static List<Instruction> 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<WeaponGroup> 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<System> 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<Ship> 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<Contact> 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<System> 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<System> 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<System> 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<WeaponGroup> g = weapons;
+ while (++g) {
+ ListIter<Weapon> 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<WeaponGroup> g = weapons;
+ while (++g) {
+ ListIter<Weapon> 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<WeaponGroup> g = weapons;
+ while (++g) {
+ ListIter<Weapon> 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<WeaponGroup> g = weapons;
+ while (++g) {
+ ListIter<Weapon> 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<Shadow> 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<Contact> 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<System> 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<WeaponGroup> 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<System> 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<Weapon>& 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<Shot>&
+Ship::GetActiveDecoys()
+{
+ return decoy_list;
+}
+
+List<Shot>&
+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<System> 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<System> 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<System> 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<System> 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<Component> 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<System> 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<System> 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<Bitmap>& 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<Shot>& GetActiveDecoys();
+ virtual void AddActiveDecoy(Drone* d);
+ virtual int* GetLoadout() { return loadout; }
+
+ List<Shot>& 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<Contact>& 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<Instruction>& 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<System>& 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<System>& Systems() { return systems; }
+ List<WeaponGroup>& Weapons() { return weapons; }
+ List<Drive>& Drives() { return drives; }
+ List<Computer>& Computers() { return computers; }
+ List<FlightDeck>& FlightDecks() { return flight_decks; }
+ List<PowerSource>& Reactors() { return reactors; }
+ List<NavLight>& 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<Shot> decoy_list;
+ List<Shot> threat_list;
+
+ List<System> systems;
+ List<PowerSource> reactors;
+ List<WeaponGroup> weapons;
+ List<Drive> drives;
+ List<Computer> computers;
+ List<FlightDeck> flight_decks;
+ List<NavLight> navlights;
+ List<System> 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<Ship> 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<Ship> 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> 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<Debris> 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<Asteroid> 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<ShipCatalogEntry> catalog;
+static List<ShipCatalogEntry> 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<Text> detail[4];
+static List<Point> 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<Material> 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<Text> 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<ShipLoad> 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<Text> 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<Text> list;
+ DataLoader* loader = DataLoader::GetLoader();
+ bool oldfs = loader->IsFileSystemEnabled();
+
+ loader->UseFileSystem(true);
+ loader->SetDataPath(path);
+ loader->ListArchiveFiles(archive, "*.def", list);
+
+ ListIter<Text> 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<ShipCatalogEntry> 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<Skin> iter = e->design->skins;
+
+ while (++iter) {
+ Skin* skin = iter.value();
+ if (*skin->Path())
+ iter.removeItem();
+ }
+ }
+ }
+}
+
+// +--------------------------------------------------------------------+
+
+int
+ShipDesign::GetDesignList(int type, List<Text>& 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<Text>& 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<Model> models[4];
+ List<Point> offsets[4];
+ float feature_size[4];
+ List<Point> spin_rates;
+
+ // player selectable skins:
+ List<Skin> 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<PowerSource> reactors;
+ List<Weapon> weapons;
+ List<HardPoint> hard_points;
+ List<Drive> drives;
+ List<Computer> computers;
+ List<FlightDeck> flight_decks;
+ List<NavLight> 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<ShipLoad> loadouts;
+ List<Bitmap> map_sprites;
+ List<ShipSquadron> 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<System> 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<Model> all_models;
+ //List<Bitmap> all_textures;
+
+ ListIter<MissionElement> 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<Model>& models = (List<Model>&) design->models[i]; // cast-away const
+
+ ListIter<Model> 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<Surface> 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<Bitmap> 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<StarSystem> iter = mission->GetSystemList();
+ while (++iter) {
+ StarSystem* sys = iter.value();
+
+ // insert objects from star system:
+ ListIter<OrbitalBody> star = sys->Bodies();
+ while (++star) {
+ ListIter<OrbitalBody> planet = star->Satellites();
+ while (++planet) {
+ ListIter<OrbitalBody> moon = planet->Satellites();
+ while (++moon) {
+ ListIter<OrbitalRegion> 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<OrbitalRegion> 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<OrbitalRegion> 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<SimRegion> iter = regions;
+ while (++iter) {
+ SimRegion* rgn = iter.value();
+ OrbitalRegion* orb = rgn->GetOrbitalRegion();
+
+ if (orb) {
+ ListIter<Text> 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<MissionElement> 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<ShipLoad> 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<Instruction> 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<Text> instr = msn_elem->Instructions();
+ while (++instr) {
+ element->AddInstruction(*instr);
+ }
+ }
+
+ ListIter<Instruction> 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<ShipLoad> 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<MissionLoad> l = msn_elem->Loadouts();
+ while (++l) {
+ if ((l->GetShip() == i) || (l->GetShip() < 0 && loadout == 0)) {
+ if (l->GetName().length()) {
+ ListIter<ShipLoad> 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<WeaponGroup> 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<MissionEvent> 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<Element> 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<Element>& 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<SimRegion> 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<SimRegion> rgn = regions;
+ while (++rgn && !ship)
+ ship = rgn->FindShipByObjID(objid);
+
+ return ship;
+}
+
+Shot*
+Sim::FindShotByObjID(DWORD objid)
+{
+ Shot* shot = 0;
+
+ ListIter<SimRegion> rgn = regions;
+ while (++rgn && !shot)
+ shot = rgn->FindShotByObjID(objid);
+
+ return shot;
+}
+
+// +--------------------------------------------------------------------+
+
+Orbital*
+Sim::FindOrbitalBody(const char* name)
+{
+ Orbital* body = 0;
+
+ if (mission) {
+ ListIter<StarSystem> 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<SimRegion> rgn = regions;
+ while (++rgn) {
+ rgn->ShowGrid(show);
+ }
+
+ grid_shown = show?true:false;
+}
+
+bool
+Sim::GridShown() const
+{
+ return grid_shown;
+}
+
+// +--------------------------------------------------------------------+
+
+List<StarSystem>&
+Sim::GetSystemList()
+{
+ if (mission)
+ return mission->GetSystemList();
+
+ static List<StarSystem> 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<SimRegion> 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<Ship>
+Sim::GetSelection()
+{
+ if (active_region)
+ return active_region->GetSelection();
+
+ static List<Ship> 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<SimRegion> rgn = regions;
+ while (++rgn)
+ if (rgn->name == name)
+ return rgn.value();
+
+ return 0;
+}
+
+SimRegion*
+Sim::FindRegion(OrbitalRegion* orgn)
+{
+ ListIter<SimRegion> 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<SimRegion> 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<SimRegion> rgn = regions;
+ while (++rgn && !result) {
+ if (rgn->IsOrbital()) {
+ OrbitalRegion* orgn = rgn->GetOrbitalRegion();
+ if (orgn) {
+ ListIter<OrbitalRegion> 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<Element> elem = elements;
+ while (++elem)
+ if (!elem->IsSquadron())
+ elem->ExecFrame(seconds);
+
+ ListIter<SimRegion> 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<Element> 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<MissionEvent> 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<SimHyper> 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<FlightDeck> 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<Ship> riders;
+ ListIter<Ship> 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<SimSplash> iter = splashlist;
+ while (++iter) {
+ SimSplash* splash = iter.value();
+
+ if (!splash->rgn)
+ continue;
+
+ // damage ships:
+ ListIter<Ship> 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> 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<MissionEvent> 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<MissionEvent> 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<Element> 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<MissionElement>
+Sim::GetMissionElements()
+{
+ mission_elements.destroy();
+
+ ListIter<Element> 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<Instruction> 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<SimRegion> iter = regions;
+ while (++iter && !hangar) {
+ SimRegion* rgn = iter.value();
+
+ ListIter<Ship> 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<Ship>
+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> ship = ships;
+ while (++ship)
+ ship->Activate(sim->scene);
+
+ ListIter<Shot> shot = shots;
+ while (++shot)
+ shot->Activate(sim->scene);
+
+ ListIter<Explosion> exp = explosions;
+ while (++exp)
+ exp->Activate(sim->scene);
+
+ ListIter<Debris> deb = debris;
+ while (++deb)
+ deb->Activate(sim->scene);
+
+ ListIter<Asteroid> 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> ship = ships;
+ while (++ship)
+ ship->Deactivate(sim->scene);
+
+ ListIter<Shot> shot = shots;
+ while (++shot)
+ shot->Deactivate(sim->scene);
+
+ ListIter<Explosion> exp = explosions;
+ while (++exp)
+ exp->Deactivate(sim->scene);
+
+ ListIter<Debris> deb = debris;
+ while (++deb)
+ deb->Deactivate(sim->scene);
+
+ ListIter<Asteroid> 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<Asteroid> 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> 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> 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<Explosion> 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> 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> 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> 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> 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> 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<Asteroid> 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<Ship> kill_list;
+
+ int s_index = 0;
+
+ ListIter<Ship> 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<Ship> 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> 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<Asteroid> 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<Ship> killed(kill_list);
+ while (++killed) {
+ Ship* kill = killed.value();
+ kill->DeathSpiral();
+ }
+}
+
+// +--------------------------------------------------------------------+
+
+void
+SimRegion::CrashShips()
+{
+ if (type != AIR_SPACE || NetGame::IsNetGameClient())
+ return;
+
+ ListIter<Ship> 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> 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> 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> 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> 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> shot_iter = shots;
+ while (++shot_iter && !shot) {
+ Shot* test = shot_iter.value();
+ if (test->GetObjID() == objid)
+ shot = test;
+ }
+
+ if (!shot) {
+ ListIter<Drone> 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> 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<Contact>&
+SimRegion::TrackList(int iff)
+{
+ if (iff >= 0 && iff < 5)
+ return track_database[iff];
+
+ static List<Contact> empty;
+ return empty;
+}
+
+void
+SimRegion::UpdateTracks(double seconds)
+{
+ for (int i = 0; i < 5; i++) {
+ ListIter<Contact> 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> 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<FlightDeck>& 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<MissionEvent>& GetEvents() { return events; }
+ List<SimRegion>& 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<StarSystem>& GetSystemList();
+ Scene* GetScene() { return &scene; }
+ Ship* GetPlayerShip();
+ Element* GetPlayerElement();
+ Orbital* FindOrbitalBody(const char* name);
+
+ void SetSelection(Ship* s);
+ bool IsSelected(Ship* s);
+ ListIter<Ship> 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<Element>& GetElements() { return elements; }
+
+ int GetAssignedElements(Element* elem, List<Element>& 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<MissionElement> 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<SimRegion> regions;
+ List<SimRegion> rgn_queue;
+ List<SimHyper> jumplist;
+ List<SimSplash> splashlist;
+ List<Element> elements;
+ List<Element> finished;
+ List<MissionEvent> events;
+ List<MissionElement> 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<Ship>& Ships() { return ships; }
+ List<Ship>& Carriers() { return carriers; }
+ List<Shot>& Shots() { return shots; }
+ List<Drone>& Drones() { return drones; }
+ List<Debris>& Rocks() { return debris; }
+ List<Asteroid>& Roids() { return asteroids; }
+ List<Explosion>& Explosions() { return explosions; }
+ List<SimRegion>& Links() { return links; }
+ StarSystem* System() { return star_system; }
+
+ Point Location() const { return location; }
+
+ void SetSelection(Ship* s);
+ bool IsSelected(Ship* s);
+ ListIter<Ship> GetSelection();
+ void ClearSelection();
+ void AddSelection(Ship* s);
+
+ List<Contact>& 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<Ship> ships;
+ List<Ship> carriers;
+ List<Ship> selection;
+ List<Ship> dead_ships;
+ List<Shot> shots;
+ List<Drone> drones;
+ List<Explosion> explosions;
+ List<Debris> debris;
+ List<Asteroid> asteroids;
+ List<Contact> track_database[5];
+ List<SimRegion> 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<ShipStats> 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<SimEvent> 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<ShipStats> 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<SimEvent>&
+ 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<SimEvent> 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<SimObject> 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<SimObserver> 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<SimObserver> 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<SimObject> 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<Text> 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<OrbitalBody> star = bodies;
+ while (++star) {
+ CreateBody(*star);
+
+ ListIter<OrbitalBody> planet = star->Satellites();
+ while (++planet) {
+ CreateBody(*planet);
+
+ ListIter<OrbitalBody> 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<OrbitalBody> 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<OrbitalBody> planet = star->Satellites();
+ while (++planet) {
+ GRAPHIC_DESTROY(planet->rep);
+
+ ListIter<OrbitalBody> 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<OrbitalBody> 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<OrbitalBody> planet = star->Satellites();
+ while (++planet) {
+ scene.AddGraphic(planet->rep);
+
+ ListIter<OrbitalBody> 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<OrbitalBody> 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<OrbitalBody> planet = star->Satellites();
+ while (++planet) {
+ if (planet->rep && planet->rep->GetScene())
+ planet->rep->GetScene()->DelGraphic(planet->rep);
+
+ ListIter<OrbitalBody> 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<OrbitalBody> 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<OrbitalBody> 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<OrbitalBody> star = bodies;
+ while (++star)
+ star->Update();
+
+ ListIter<OrbitalRegion> 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<OrbitalBody> 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<OrbitalBody> 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<OrbitalBody> 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<OrbitalBody> star = bodies;
+ while (++star) {
+ if (!stricmp(star->Name(), name))
+ return star.value();
+
+ ListIter<OrbitalRegion> star_rgn = star->Regions();
+ while (++star_rgn) {
+ if (!stricmp(star_rgn->Name(), name))
+ return star_rgn.value();
+ }
+
+ ListIter<OrbitalBody> planet = star->Satellites();
+ while (++planet) {
+ if (!stricmp(planet->Name(), name))
+ return planet.value();
+
+ ListIter<OrbitalRegion> planet_rgn = planet->Regions();
+ while (++planet_rgn) {
+ if (!stricmp(planet_rgn->Name(), name))
+ return planet_rgn.value();
+ }
+
+ ListIter<OrbitalBody> moon = planet->Satellites();
+ while (++moon) {
+ if (!stricmp(moon->Name(), name))
+ return moon.value();
+
+ ListIter<OrbitalRegion> moon_rgn = moon->Regions();
+ while (++moon_rgn) {
+ if (!stricmp(moon_rgn->Name(), name))
+ return moon_rgn.value();
+ }
+ }
+ }
+ }
+
+ ListIter<OrbitalRegion> 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<OrbitalRegion> region = all_regions;
+ while (++region) {
+ if (!stricmp(region->Name(), name))
+ return region.value();
+ }
+
+ return 0;
+}
+
+// +--------------------------------------------------------------------+
+
+bool
+StarSystem::HasLinkTo(StarSystem* s) const
+{
+ ListIter<OrbitalRegion> iter = ((StarSystem*) this)->all_regions;
+ while (++iter) {
+ OrbitalRegion* rgn = iter.value();
+
+ ListIter<Text> 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<Light> 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<Light> 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<Light> 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<OrbitalBody> 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<OrbitalRegion> 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<OrbitalBody> 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<OrbitalBody>& Bodies() { return bodies; }
+ List<OrbitalRegion>& Regions() { return regions; }
+ List<OrbitalRegion>& 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<Light> sun_lights;
+ List<Light> back_lights;
+
+ Graphic* point_stars;
+ Solid* poly_stars;
+ Solid* nebula;
+ Solid* haze;
+
+ List<OrbitalBody> bodies;
+ List<OrbitalRegion> regions;
+ List<OrbitalRegion> 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<OrbitalRegion> 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<OrbitalRegion> 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<OrbitalBody> 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<OrbitalBody> 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<Text>& Links() { return links; }
+
+protected:
+ double grid;
+ double inclination;
+ int asteroids;
+ List<Text> 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
--- /dev/null
+++ b/Stars45/Stars.ico
Binary files 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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Template|Win32">
+ <Configuration>Template</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <SccProjectName />
+ <SccLocalPath />
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Template|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseOfMfc>false</UseOfMfc>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseOfMfc>false</UseOfMfc>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Template|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="$(VCTargetsPath)Microsoft.Cpp.UpgradeFromVC60.props" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="$(VCTargetsPath)Microsoft.Cpp.UpgradeFromVC60.props" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <OutDir>.\Debug\</OutDir>
+ <IntDir>.\Debug\</IntDir>
+ <LinkIncremental>true</LinkIncremental>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <OutDir>.\Release\</OutDir>
+ <IntDir>.\Release\</IntDir>
+ <LinkIncremental>false</LinkIncremental>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+ <InlineFunctionExpansion>Default</InlineFunctionExpansion>
+ <FunctionLevelLinking>false</FunctionLevelLinking>
+ <Optimization>Disabled</Optimization>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <WarningLevel>Level3</WarningLevel>
+ <MinimalRebuild>true</MinimalRebuild>
+ <DebugInformationFormat>EditAndContinue</DebugInformationFormat>
+ <AdditionalIncludeDirectories>../FoundationEx;../nGenEx;../NetEx;../Parser;../Opcode/OpcodeLib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AssemblerListingLocation>.\Debug\</AssemblerListingLocation>
+ <BrowseInformation>true</BrowseInformation>
+ <PrecompiledHeaderOutputFile>.\Debug\Stars.pch</PrecompiledHeaderOutputFile>
+ <ObjectFileName>.\Debug\</ObjectFileName>
+ <ProgramDataBaseFileName>.\Debug\</ProgramDataBaseFileName>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ </ClCompile>
+ <Midl>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <TypeLibraryName>.\Debug\Stars.tlb</TypeLibraryName>
+ <MkTypLibCompatible>true</MkTypLibCompatible>
+ <TargetEnvironment>Win32</TargetEnvironment>
+ </Midl>
+ <ResourceCompile>
+ <Culture>0x0409</Culture>
+ <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ResourceCompile>
+ <Bscmake>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <OutputFile>.\Debug\Stars.bsc</OutputFile>
+ </Bscmake>
+ <Link>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <SubSystem>Windows</SubSystem>
+ <OutputFile>Game/Stars_d.exe</OutputFile>
+ <AdditionalDependencies>..\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)</AdditionalDependencies>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
+ <StringPooling>true</StringPooling>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <Optimization>MaxSpeed</Optimization>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <WarningLevel>Level3</WarningLevel>
+ <AdditionalIncludeDirectories>../FoundationEx;../nGenEx;../NetEx;../Parser;../Opcode/OpcodeLib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AssemblerListingLocation>.\Release\</AssemblerListingLocation>
+ <PrecompiledHeaderOutputFile>.\Release\Stars.pch</PrecompiledHeaderOutputFile>
+ <ObjectFileName>.\Release\</ObjectFileName>
+ <ProgramDataBaseFileName>.\Release\</ProgramDataBaseFileName>
+ </ClCompile>
+ <Midl>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <TypeLibraryName>.\Release\Stars.tlb</TypeLibraryName>
+ <MkTypLibCompatible>true</MkTypLibCompatible>
+ <TargetEnvironment>Win32</TargetEnvironment>
+ </Midl>
+ <ResourceCompile>
+ <Culture>0x0409</Culture>
+ <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ResourceCompile>
+ <Bscmake>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <OutputFile>.\Release\Stars.bsc</OutputFile>
+ </Bscmake>
+ <Link>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <SubSystem>Windows</SubSystem>
+ <OutputFile>Game/Stars.exe</OutputFile>
+ <AdditionalDependencies>..\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)</AdditionalDependencies>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="Asteroid.cpp" />
+ <ClCompile Include="AudDlg.cpp" />
+ <ClCompile Include="AudioConfig.cpp" />
+ <ClCompile Include="Authorization.cpp" />
+ <ClCompile Include="AwardDlg.cpp" />
+ <ClCompile Include="AwardShowDlg.cpp" />
+ <ClCompile Include="Callsign.cpp" />
+ <ClCompile Include="CameraDirector.cpp" />
+ <ClCompile Include="..\nGenEx\CameraView.cpp">
+ <AssemblerOutput Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">All</AssemblerOutput>
+ </ClCompile>
+ <ClCompile Include="Campaign.cpp" />
+ <ClCompile Include="CampaignMissionFighter.cpp" />
+ <ClCompile Include="CampaignMissionRequest.cpp" />
+ <ClCompile Include="CampaignMissionStarship.cpp" />
+ <ClCompile Include="CampaignPlanAssignment.cpp" />
+ <ClCompile Include="CampaignPlanEvent.cpp" />
+ <ClCompile Include="CampaignPlanMission.cpp" />
+ <ClCompile Include="CampaignPlanMovement.cpp" />
+ <ClCompile Include="CampaignPlanStrategic.cpp" />
+ <ClCompile Include="CampaignSaveGame.cpp" />
+ <ClCompile Include="CampaignSituationReport.cpp" />
+ <ClCompile Include="CarrierAI.cpp" />
+ <ClCompile Include="CmdDlg.cpp" />
+ <ClCompile Include="CmdForceDlg.cpp" />
+ <ClCompile Include="CmdIntelDlg.cpp" />
+ <ClCompile Include="CmdMissionsDlg.cpp" />
+ <ClCompile Include="CmdMsgDlg.cpp" />
+ <ClCompile Include="CmdOrdersDlg.cpp" />
+ <ClCompile Include="CmdTheaterDlg.cpp" />
+ <ClCompile Include="CmdTitleDlg.cpp" />
+ <ClCompile Include="CmpCompleteDlg.cpp" />
+ <ClCompile Include="CmpFileDlg.cpp" />
+ <ClCompile Include="CmpLoadDlg.cpp" />
+ <ClCompile Include="CmpnScreen.cpp" />
+ <ClCompile Include="CmpSceneDlg.cpp" />
+ <ClCompile Include="CmpSelectDlg.cpp" />
+ <ClCompile Include="CombatAction.cpp" />
+ <ClCompile Include="Combatant.cpp" />
+ <ClCompile Include="CombatAssignment.cpp" />
+ <ClCompile Include="CombatEvent.cpp" />
+ <ClCompile Include="CombatGroup.cpp" />
+ <ClCompile Include="CombatRoster.cpp" />
+ <ClCompile Include="CombatUnit.cpp" />
+ <ClCompile Include="CombatZone.cpp" />
+ <ClCompile Include="Component.cpp" />
+ <ClCompile Include="Computer.cpp" />
+ <ClCompile Include="ConfirmDlg.cpp" />
+ <ClCompile Include="Contact.cpp" />
+ <ClCompile Include="CtlDlg.cpp" />
+ <ClCompile Include="DebriefDlg.cpp" />
+ <ClCompile Include="Debris.cpp" />
+ <ClCompile Include="DetailSet.cpp" />
+ <ClCompile Include="DisplayView.cpp" />
+ <ClCompile Include="Drive.cpp" />
+ <ClCompile Include="DriveSprite.cpp" />
+ <ClCompile Include="Drone.cpp" />
+ <ClCompile Include="DropShipAI.cpp" />
+ <ClCompile Include="Element.cpp" />
+ <ClCompile Include="EngDlg.cpp" />
+ <ClCompile Include="ExceptionHandler.cpp" />
+ <ClCompile Include="ExitDlg.cpp" />
+ <ClCompile Include="Explosion.cpp" />
+ <ClCompile Include="Farcaster.cpp" />
+ <ClCompile Include="FighterAI.cpp" />
+ <ClCompile Include="FighterTacticalAI.cpp" />
+ <ClCompile Include="FirstTimeDlg.cpp" />
+ <ClCompile Include="FlightComp.cpp" />
+ <ClCompile Include="FlightDeck.cpp" />
+ <ClCompile Include="FlightPlanner.cpp" />
+ <ClCompile Include="FltDlg.cpp" />
+ <ClCompile Include="..\nGenEx\Font.cpp" />
+ <ClCompile Include="..\nGenEx\FormDef.cpp" />
+ <ClCompile Include="Galaxy.cpp" />
+ <ClCompile Include="GameScreen.cpp" />
+ <ClCompile Include="Grid.cpp" />
+ <ClCompile Include="GroundAI.cpp" />
+ <ClCompile Include="Hangar.cpp" />
+ <ClCompile Include="HardPoint.cpp" />
+ <ClCompile Include="Hoop.cpp" />
+ <ClCompile Include="HUDSounds.cpp" />
+ <ClCompile Include="HUDView.cpp" />
+ <ClCompile Include="Instruction.cpp" />
+ <ClCompile Include="Intel.cpp" />
+ <ClCompile Include="JoyDlg.cpp" />
+ <ClCompile Include="KeyDlg.cpp" />
+ <ClCompile Include="KeyMap.cpp" />
+ <ClCompile Include="LandingGear.cpp" />
+ <ClCompile Include="LoadDlg.cpp" />
+ <ClCompile Include="LoadScreen.cpp" />
+ <ClCompile Include="Main.cpp" />
+ <ClCompile Include="MapView.cpp" />
+ <ClCompile Include="MenuDlg.cpp" />
+ <ClCompile Include="MenuScreen.cpp" />
+ <ClCompile Include="MenuView.cpp" />
+ <ClCompile Include="Mfd.cpp" />
+ <ClCompile Include="Mission.cpp" />
+ <ClCompile Include="MissionEvent.cpp" />
+ <ClCompile Include="MissionTemplate.cpp" />
+ <ClCompile Include="ModConfig.cpp" />
+ <ClCompile Include="ModDlg.cpp" />
+ <ClCompile Include="ModInfo.cpp" />
+ <ClCompile Include="ModInfoDlg.cpp" />
+ <ClCompile Include="..\nGenEx\Mouse.cpp" />
+ <ClCompile Include="MsnDlg.cpp" />
+ <ClCompile Include="MsnEditDlg.cpp" />
+ <ClCompile Include="MsnEditNavDlg.cpp" />
+ <ClCompile Include="MsnElemDlg.cpp" />
+ <ClCompile Include="MsnEventDlg.cpp" />
+ <ClCompile Include="MsnNavDlg.cpp" />
+ <ClCompile Include="MsnObjDlg.cpp" />
+ <ClCompile Include="MsnPkgDlg.cpp" />
+ <ClCompile Include="MsnSelectDlg.cpp" />
+ <ClCompile Include="MsnWepDlg.cpp" />
+ <ClCompile Include="MusicDirector.cpp" />
+ <ClCompile Include="MusicTrack.cpp" />
+ <ClCompile Include="NavAI.cpp" />
+ <ClCompile Include="NavDlg.cpp" />
+ <ClCompile Include="NavLight.cpp" />
+ <ClCompile Include="NavSystem.cpp" />
+ <ClCompile Include="NetAddrDlg.cpp" />
+ <ClCompile Include="NetAdminChat.cpp" />
+ <ClCompile Include="NetAdminServer.cpp" />
+ <ClCompile Include="NetAuth.cpp" />
+ <ClCompile Include="NetBrokerClient.cpp" />
+ <ClCompile Include="NetChat.cpp" />
+ <ClCompile Include="NetClientConfig.cpp" />
+ <ClCompile Include="NetClientDlg.cpp" />
+ <ClCompile Include="NetData.cpp" />
+ <ClCompile Include="NetFileServlet.cpp" />
+ <ClCompile Include="NetGame.cpp" />
+ <ClCompile Include="NetGameClient.cpp" />
+ <ClCompile Include="NetGameServer.cpp" />
+ <ClCompile Include="NetLobby.cpp" />
+ <ClCompile Include="NetLobbyClient.cpp" />
+ <ClCompile Include="NetLobbyDlg.cpp" />
+ <ClCompile Include="NetLobbyServer.cpp" />
+ <ClCompile Include="NetPacket.cpp" />
+ <ClCompile Include="NetPassDlg.cpp" />
+ <ClCompile Include="NetPlayer.cpp" />
+ <ClCompile Include="NetServerConfig.cpp" />
+ <ClCompile Include="NetServerDlg.cpp" />
+ <ClCompile Include="NetUnitDlg.cpp" />
+ <ClCompile Include="NetUser.cpp" />
+ <ClCompile Include="NetUtil.cpp" />
+ <ClCompile Include="NPClientWraps.cpp" />
+ <ClCompile Include="OptDlg.cpp" />
+ <ClCompile Include="..\nGenEx\Particles.cpp" />
+ <ClCompile Include="PlanScreen.cpp" />
+ <ClCompile Include="Player.cpp" />
+ <ClCompile Include="PlayerDlg.cpp" />
+ <ClCompile Include="Power.cpp" />
+ <ClCompile Include="QuantumDrive.cpp" />
+ <ClCompile Include="QuantumFlash.cpp" />
+ <ClCompile Include="QuantumView.cpp" />
+ <ClCompile Include="QuitView.cpp" />
+ <ClCompile Include="RadioHandler.cpp" />
+ <ClCompile Include="RadioMessage.cpp" />
+ <ClCompile Include="RadioTraffic.cpp" />
+ <ClCompile Include="RadioView.cpp" />
+ <ClCompile Include="RadioVox.cpp" />
+ <ClCompile Include="RLoc.cpp" />
+ <ClCompile Include="SeekerAI.cpp" />
+ <ClCompile Include="Sensor.cpp" />
+ <ClCompile Include="Shield.cpp" />
+ <ClCompile Include="ShieldRep.cpp" />
+ <ClCompile Include="Ship.cpp" />
+ <ClCompile Include="ShipAI.cpp" />
+ <ClCompile Include="ShipCtrl.cpp" />
+ <ClCompile Include="ShipDesign.cpp" />
+ <ClCompile Include="ShipKiller.cpp" />
+ <ClCompile Include="ShipSolid.cpp" />
+ <ClCompile Include="Shot.cpp" />
+ <ClCompile Include="Sim.cpp">
+ <AssemblerOutput Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">All</AssemblerOutput>
+ </ClCompile>
+ <ClCompile Include="SimEvent.cpp" />
+ <ClCompile Include="SimObject.cpp" />
+ <ClCompile Include="Sky.cpp" />
+ <ClCompile Include="StarServer.cpp" />
+ <ClCompile Include="Starshatter.cpp" />
+ <ClCompile Include="StarshipAI.cpp" />
+ <ClCompile Include="StarshipTacticalAI.cpp" />
+ <ClCompile Include="StarSystem.cpp" />
+ <ClCompile Include="SteerAI.cpp" />
+ <ClCompile Include="System.cpp" />
+ <ClCompile Include="SystemDesign.cpp" />
+ <ClCompile Include="TacRefDlg.cpp" />
+ <ClCompile Include="TacticalAI.cpp" />
+ <ClCompile Include="TacticalView.cpp" />
+ <ClCompile Include="Terrain.cpp" />
+ <ClCompile Include="TerrainApron.cpp" />
+ <ClCompile Include="TerrainClouds.cpp" />
+ <ClCompile Include="TerrainHaze.cpp" />
+ <ClCompile Include="TerrainPatch.cpp" />
+ <ClCompile Include="TerrainRegion.cpp" />
+ <ClCompile Include="Thruster.cpp" />
+ <ClCompile Include="TrackIR.cpp" />
+ <ClCompile Include="Trail.cpp" />
+ <ClCompile Include="VidDlg.cpp" />
+ <ClCompile Include="..\nGenEx\Water.cpp" />
+ <ClCompile Include="Weapon.cpp" />
+ <ClCompile Include="WeaponDesign.cpp" />
+ <ClCompile Include="WeaponGroup.cpp" />
+ <ClCompile Include="Weather.cpp" />
+ <ClCompile Include="WepView.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="Stars.rc" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="Asteroid.h" />
+ <ClInclude Include="AudDlg.h" />
+ <ClInclude Include="AudioConfig.h" />
+ <ClInclude Include="Authorization.h" />
+ <ClInclude Include="AwardDlg.h" />
+ <ClInclude Include="AwardShowDlg.h" />
+ <ClInclude Include="BaseScreen.h" />
+ <ClInclude Include="Callsign.h" />
+ <ClInclude Include="CameraDirector.h" />
+ <ClInclude Include="Campaign.h" />
+ <ClInclude Include="CampaignMissionFighter.h" />
+ <ClInclude Include="CampaignMissionRequest.h" />
+ <ClInclude Include="CampaignMissionStarship.h" />
+ <ClInclude Include="CampaignPlan.h" />
+ <ClInclude Include="CampaignPlanAssignment.h" />
+ <ClInclude Include="CampaignPlanEvent.h" />
+ <ClInclude Include="CampaignPlanMission.h" />
+ <ClInclude Include="CampaignPlanMovement.h" />
+ <ClInclude Include="CampaignPlanStrategic.h" />
+ <ClInclude Include="CampaignSituationReport.h" />
+ <ClInclude Include="CmdDlg.h" />
+ <ClInclude Include="CmdForceDlg.h" />
+ <ClInclude Include="CmdIntelDlg.h" />
+ <ClInclude Include="CmdMissionsDlg.h" />
+ <ClInclude Include="CmdMsgDlg.h" />
+ <ClInclude Include="CmdOrdersDlg.h" />
+ <ClInclude Include="CmdTheaterDlg.h" />
+ <ClInclude Include="CmdTitleDlg.h" />
+ <ClInclude Include="CmpCompleteDlg.h" />
+ <ClInclude Include="CmpFileDlg.h" />
+ <ClInclude Include="CmpLoadDlg.h" />
+ <ClInclude Include="CmpnScreen.h" />
+ <ClInclude Include="CmpSceneDlg.h" />
+ <ClInclude Include="CmpSelectDlg.h" />
+ <ClInclude Include="CombatAction.h" />
+ <ClInclude Include="CombatEvent.h" />
+ <ClInclude Include="CombatGroup.h" />
+ <ClInclude Include="CombatRoster.h" />
+ <ClInclude Include="CombatUnit.h" />
+ <ClInclude Include="CombatZone.h" />
+ <ClInclude Include="Component.h" />
+ <ClInclude Include="ConfirmDlg.h" />
+ <ClInclude Include="CtlDlg.h" />
+ <ClInclude Include="..\nGenEx\DataLoader.h" />
+ <ClInclude Include="DebriefDlg.h" />
+ <ClInclude Include="Debris.h" />
+ <ClInclude Include="DetailSet.h" />
+ <ClInclude Include="DisplayView.h" />
+ <ClInclude Include="DriveSprite.h" />
+ <ClInclude Include="Drone.h" />
+ <ClInclude Include="DropShipAI.h" />
+ <ClInclude Include="Element.h" />
+ <ClInclude Include="EngDlg.h" />
+ <ClInclude Include="ExitDlg.h" />
+ <ClInclude Include="Explosion.h" />
+ <ClInclude Include="FighterAI.h" />
+ <ClInclude Include="FighterTacticalAI.h" />
+ <ClInclude Include="FlightPlanner.h" />
+ <ClInclude Include="FltDlg.h" />
+ <ClInclude Include="Galaxy.h" />
+ <ClInclude Include="GameScreen.h" />
+ <ClInclude Include="Grid.h" />
+ <ClInclude Include="GroundAI.h" />
+ <ClInclude Include="Hoop.h" />
+ <ClInclude Include="HUDSounds.h" />
+ <ClInclude Include="HUDView.h" />
+ <ClInclude Include="Instruction.h" />
+ <ClInclude Include="Intel.h" />
+ <ClInclude Include="JoyDlg.h" />
+ <ClInclude Include="KeyDlg.h" />
+ <ClInclude Include="KeyMap.h" />
+ <ClInclude Include="LoadDlg.h" />
+ <ClInclude Include="LoadScreen.h" />
+ <ClInclude Include="MapView.h" />
+ <ClInclude Include="MenuDlg.h" />
+ <ClInclude Include="MenuScreen.h" />
+ <ClInclude Include="MenuView.h" />
+ <ClInclude Include="Mfd.h" />
+ <ClInclude Include="Mission.h" />
+ <ClInclude Include="MissionEvent.h" />
+ <ClInclude Include="MissionTemplate.h" />
+ <ClInclude Include="ModConfig.h" />
+ <ClInclude Include="ModDlg.h" />
+ <ClInclude Include="ModInfo.h" />
+ <ClInclude Include="ModInfoDlg.h" />
+ <ClInclude Include="MsnDlg.h" />
+ <ClInclude Include="MsnEditDlg.h" />
+ <ClInclude Include="MsnElemDlg.h" />
+ <ClInclude Include="MsnEventDlg.h" />
+ <ClInclude Include="MsnNavDlg.h" />
+ <ClInclude Include="MsnObjDlg.h" />
+ <ClInclude Include="MsnPkgDlg.h" />
+ <ClInclude Include="MsnSelectDlg.h" />
+ <ClInclude Include="MsnWepDlg.h" />
+ <ClInclude Include="MusicDirector.h" />
+ <ClInclude Include="MusicTrack.h" />
+ <ClInclude Include="NavAI.h" />
+ <ClInclude Include="NavDlg.h" />
+ <ClInclude Include="NavLight.h" />
+ <ClInclude Include="NetAddrDlg.h" />
+ <ClInclude Include="NetAdminChat.h" />
+ <ClInclude Include="NetAdminServer.h" />
+ <ClInclude Include="NetAuth.h" />
+ <ClInclude Include="NetBrokerClient.h" />
+ <ClInclude Include="NetChat.h" />
+ <ClInclude Include="NetClientConfig.h" />
+ <ClInclude Include="NetClientDlg.h" />
+ <ClInclude Include="NetData.h" />
+ <ClInclude Include="NetFileServlet.h" />
+ <ClInclude Include="NetGame.h" />
+ <ClInclude Include="NetGameClient.h" />
+ <ClInclude Include="NetGameServer.h" />
+ <ClInclude Include="NetLobby.h" />
+ <ClInclude Include="NetLobbyClient.h" />
+ <ClInclude Include="NetLobbyDlg.h" />
+ <ClInclude Include="NetLobbyServer.h" />
+ <ClInclude Include="NetPacket.h" />
+ <ClInclude Include="NetPassDlg.h" />
+ <ClInclude Include="NetPlayer.h" />
+ <ClInclude Include="NetServerConfig.h" />
+ <ClInclude Include="NetServerDlg.h" />
+ <ClInclude Include="NetUnitDlg.h" />
+ <ClInclude Include="NetUtil.h" />
+ <ClInclude Include="NPClientWraps.h" />
+ <ClInclude Include="OptDlg.h" />
+ <ClInclude Include="PlanScreen.h" />
+ <ClInclude Include="Player.h" />
+ <ClInclude Include="PlayerDlg.h" />
+ <ClInclude Include="Power.h" />
+ <ClInclude Include="QuantumFlash.h" />
+ <ClInclude Include="QuantumView.h" />
+ <ClInclude Include="RadioHandler.h" />
+ <ClInclude Include="RadioMessage.h" />
+ <ClInclude Include="RadioView.h" />
+ <ClInclude Include="RadioVox.h" />
+ <ClInclude Include="RLoc.h" />
+ <ClInclude Include="SeekerAI.h" />
+ <ClInclude Include="ShieldRep.h" />
+ <ClInclude Include="ShipCtrl.h" />
+ <ClInclude Include="ShipKiller.h" />
+ <ClInclude Include="StarServer.h" />
+ <ClInclude Include="Starshatter.h" />
+ <ClInclude Include="StarshipAI.h" />
+ <ClInclude Include="StarshipTacticalAI.h" />
+ <ClInclude Include="StarSystem.h" />
+ <ClInclude Include="SteerAI.h" />
+ <ClInclude Include="SystemDesign.h" />
+ <ClInclude Include="TacRefDlg.h" />
+ <ClInclude Include="TacticalAI.h" />
+ <ClInclude Include="Terrain.h" />
+ <ClInclude Include="TerrainApron.h" />
+ <ClInclude Include="TerrainClouds.h" />
+ <ClInclude Include="TerrainHaze.h" />
+ <ClInclude Include="TerrainLayer.h" />
+ <ClInclude Include="TerrainPatch.h" />
+ <ClInclude Include="Thruster.h" />
+ <ClInclude Include="TrackIR.h" />
+ <ClInclude Include="Trail.h" />
+ <ClInclude Include="VidDlg.h" />
+ <ClInclude Include="WeaponDesign.h" />
+ <ClInclude Include="Weather.h" />
+ <ClInclude Include="WepView.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <CustomBuild Include="Stars.ico" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ 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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{b8a66b66-1e1d-446a-891f-0e137a6a2a75}</UniqueIdentifier>
+ <Extensions>cpp;c;cxx;rc;def;r;odl;idl;hpj;bat</Extensions>
+ </Filter>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{3d0b1a1b-1131-48e1-85d3-b5f35ce9219c}</UniqueIdentifier>
+ <Extensions>h;hpp;hxx;hm;inl</Extensions>
+ </Filter>
+ <Filter Include="Resource Files">
+ <UniqueIdentifier>{8540b9f2-52cf-4590-be61-3db9ea2de9f0}</UniqueIdentifier>
+ <Extensions>ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe</Extensions>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="Asteroid.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="AudDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="AudioConfig.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Authorization.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="AwardDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="AwardShowDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Callsign.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="CameraDirector.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\nGenEx\CameraView.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Campaign.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="CampaignMissionFighter.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="CampaignMissionRequest.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="CampaignMissionStarship.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="CampaignPlanAssignment.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="CampaignPlanEvent.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="CampaignPlanMission.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="CampaignPlanMovement.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="CampaignPlanStrategic.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="CampaignSaveGame.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="CampaignSituationReport.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="CarrierAI.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="CmdDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="CmdForceDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="CmdIntelDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="CmdMissionsDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="CmdMsgDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="CmdOrdersDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="CmdTheaterDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="CmdTitleDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="CmpCompleteDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="CmpFileDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="CmpLoadDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="CmpnScreen.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="CmpSceneDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="CmpSelectDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="CombatAction.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Combatant.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="CombatAssignment.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="CombatEvent.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="CombatGroup.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="CombatRoster.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="CombatUnit.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="CombatZone.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Component.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Computer.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ConfirmDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Contact.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="CtlDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="DebriefDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Debris.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="DetailSet.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="DisplayView.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Drive.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="DriveSprite.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Drone.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="DropShipAI.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Element.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="EngDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ExceptionHandler.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ExitDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Explosion.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Farcaster.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="FighterAI.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="FighterTacticalAI.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="FirstTimeDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="FlightComp.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="FlightDeck.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="FlightPlanner.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="FltDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\nGenEx\Font.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\nGenEx\FormDef.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Galaxy.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="GameScreen.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Grid.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="GroundAI.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Hangar.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="HardPoint.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Hoop.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="HUDSounds.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="HUDView.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Instruction.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Intel.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="JoyDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="KeyDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="KeyMap.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="LandingGear.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="LoadDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="LoadScreen.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Main.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="MapView.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="MenuDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="MenuScreen.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="MenuView.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Mfd.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Mission.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="MissionEvent.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="MissionTemplate.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ModConfig.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ModDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ModInfo.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ModInfoDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\nGenEx\Mouse.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="MsnDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="MsnEditDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="MsnEditNavDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="MsnElemDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="MsnEventDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="MsnNavDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="MsnObjDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="MsnPkgDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="MsnSelectDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="MsnWepDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="MusicDirector.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="MusicTrack.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="NavAI.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="NavDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="NavLight.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="NavSystem.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="NetAddrDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="NetAdminChat.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="NetAdminServer.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="NetAuth.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="NetBrokerClient.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="NetChat.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="NetClientConfig.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="NetClientDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="NetData.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="NetFileServlet.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="NetGame.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="NetGameClient.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="NetGameServer.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="NetLobby.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="NetLobbyClient.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="NetLobbyDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="NetLobbyServer.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="NetPacket.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="NetPassDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="NetPlayer.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="NetServerConfig.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="NetServerDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="NetUnitDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="NetUser.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="NetUtil.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="NPClientWraps.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="OptDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\nGenEx\Particles.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="PlanScreen.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Player.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="PlayerDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Power.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="QuantumDrive.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="QuantumFlash.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="QuantumView.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="QuitView.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="RadioHandler.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="RadioMessage.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="RadioTraffic.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="RadioView.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="RadioVox.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="RLoc.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="SeekerAI.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Sensor.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Shield.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ShieldRep.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Ship.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ShipAI.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ShipCtrl.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ShipDesign.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ShipKiller.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ShipSolid.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Shot.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Sim.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="SimEvent.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="SimObject.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Sky.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="StarServer.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Starshatter.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="StarshipAI.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="StarshipTacticalAI.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="StarSystem.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="SteerAI.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="System.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="SystemDesign.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="TacRefDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="TacticalAI.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="TacticalView.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Terrain.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="TerrainApron.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="TerrainClouds.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="TerrainHaze.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="TerrainPatch.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="TerrainRegion.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Thruster.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="TrackIR.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Trail.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="VidDlg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\nGenEx\Water.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Weapon.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="WeaponDesign.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="WeaponGroup.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Weather.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="WepView.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="Stars.rc">
+ <Filter>Source Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="Asteroid.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="AudDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="AudioConfig.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Authorization.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="AwardDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="AwardShowDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="BaseScreen.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Callsign.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="CameraDirector.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Campaign.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="CampaignMissionFighter.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="CampaignMissionRequest.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="CampaignMissionStarship.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="CampaignPlan.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="CampaignPlanAssignment.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="CampaignPlanEvent.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="CampaignPlanMission.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="CampaignPlanMovement.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="CampaignPlanStrategic.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="CampaignSituationReport.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="CmdDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="CmdForceDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="CmdIntelDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="CmdMissionsDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="CmdMsgDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="CmdOrdersDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="CmdTheaterDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="CmdTitleDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="CmpCompleteDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="CmpFileDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="CmpLoadDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="CmpnScreen.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="CmpSceneDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="CmpSelectDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="CombatAction.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="CombatEvent.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="CombatGroup.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="CombatRoster.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="CombatUnit.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="CombatZone.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Component.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ConfirmDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="CtlDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\nGenEx\DataLoader.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="DebriefDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Debris.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="DetailSet.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="DisplayView.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="DriveSprite.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Drone.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="DropShipAI.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Element.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="EngDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ExitDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Explosion.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="FighterAI.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="FighterTacticalAI.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="FlightPlanner.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="FltDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Galaxy.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="GameScreen.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Grid.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="GroundAI.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Hoop.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="HUDSounds.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="HUDView.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Instruction.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Intel.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="JoyDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="KeyDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="KeyMap.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="LoadDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="LoadScreen.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="MapView.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="MenuDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="MenuScreen.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="MenuView.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Mfd.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Mission.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="MissionEvent.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="MissionTemplate.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ModConfig.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ModDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ModInfo.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ModInfoDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="MsnDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="MsnEditDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="MsnElemDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="MsnEventDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="MsnNavDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="MsnObjDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="MsnPkgDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="MsnSelectDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="MsnWepDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="MusicDirector.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="MusicTrack.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="NavAI.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="NavDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="NavLight.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="NetAddrDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="NetAdminChat.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="NetAdminServer.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="NetAuth.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="NetBrokerClient.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="NetChat.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="NetClientConfig.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="NetClientDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="NetData.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="NetFileServlet.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="NetGame.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="NetGameClient.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="NetGameServer.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="NetLobby.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="NetLobbyClient.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="NetLobbyDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="NetLobbyServer.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="NetPacket.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="NetPassDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="NetPlayer.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="NetServerConfig.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="NetServerDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="NetUnitDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="NetUtil.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="NPClientWraps.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="OptDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="PlanScreen.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Player.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="PlayerDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Power.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="QuantumFlash.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="QuantumView.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="RadioHandler.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="RadioMessage.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="RadioView.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="RadioVox.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="RLoc.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="SeekerAI.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ShieldRep.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ShipCtrl.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ShipKiller.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="StarServer.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Starshatter.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="StarshipAI.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="StarshipTacticalAI.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="StarSystem.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="SteerAI.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="SystemDesign.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="TacRefDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="TacticalAI.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Terrain.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="TerrainApron.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="TerrainClouds.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="TerrainHaze.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="TerrainLayer.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="TerrainPatch.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Thruster.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="TrackIR.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Trail.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="VidDlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="WeaponDesign.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Weather.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="WepView.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <CustomBuild Include="Stars.ico">
+ <Filter>Resource Files</Filter>
+ </CustomBuild>
+ </ItemGroup>
+</Project> \ 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<Campaign>& 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<Ship> 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<Ship>& 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<Ship> 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<Ship> 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<WeaponGroup> iter = ship->Weapons();
+ while (++iter) {
+ WeaponGroup* group = iter.value();
+
+ ListIter<Weapon> 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<WeaponGroup> 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<WeaponGroup> g_iter = tgt_ship->Weapons();
+ while (++g_iter) {
+ WeaponGroup* g = g_iter.value();
+
+ if (g->GetDesign() && g->GetDesign()->turret_model) {
+ ListIter<Weapon> 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<Weapon> 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<WeaponGroup> g_iter = tgt_ship->Weapons();
+ while (++g_iter && !tgt_point_defense) {
+ WeaponGroup* g = g_iter.value();
+
+ if (g->CanTarget(1)) {
+ ListIter<Weapon> 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<Contact> 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<Contact> 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<SimRegion>& 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<Element> 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<s.brake)?brake:s.brake);
+}
+
+Steer Steer::operator*(double f) const
+{
+ return Steer(yaw*f, pitch*f, roll*f, brake);
+}
+
+Steer Steer::operator/(double f) const
+{
+ return Steer(yaw/f, pitch/f, roll/f, brake);
+}
+
+
+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;
+}
+
+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<Component> c = (List<Component>&) 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<ComponentDesign> 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<Component> 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<Component> 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<Component> 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<Component>& 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<Component> 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> 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<ComponentDesign> components;
+
+ static List<SystemDesign> 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<Text> designs;
+
+ for (int n = 0; n < 16; n++) {
+ int type = 1 << n;
+ ShipDesign::GetDesignList(type, designs);
+
+ ListIter<Text> 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<Text> designs;
+
+ WeaponDesign::GetDesignList(designs);
+
+ ListIter<Text> 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> 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> 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<Ship> ward_threats;
+
+ ListIter<Contact> 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<Ship> 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<Contact> 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> 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<Ship> 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<Contact> 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<Ship> 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<Contact> 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>& 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>& 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<Ship> 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<SimRegion> 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<Ship> 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<Ship> 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<TerrainLayer> 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<<i) + 1;
+ water[i]->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<TerrainLayer>& 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<TerrainLayer> 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<Light>& 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<Light> 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<Light>& 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<Light>& 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<Light>& 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<TerrainLayer>& 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<Light>& 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<Light> 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<Light>& 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<Material> 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<TerrainLayer>& 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<TerrainLayer> 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<ThrusterPort> 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> 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<WeaponDesign> catalog;
+static List<WeaponDesign> 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<Text>& 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<Text>& 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<Weapon>&
+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<Weapon> 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<Weapon> 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<Weapon> 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<Weapon> 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<Weapon> iter = (List<Weapon>&) 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<Weapon> w = weapons;
+ while (++w)
+ w->SetFiringOrders(orders);
+}
+
+void
+WeaponGroup::SetControlMode(int m)
+{
+ control = m;
+
+ ListIter<Weapon> w = weapons;
+ while (++w)
+ w->SetControlMode(control);
+}
+
+void
+WeaponGroup::SetSweep(int s)
+{
+ sweep = s;
+
+ ListIter<Weapon> w = weapons;
+ while (++w)
+ w->SetSweep(sweep);
+}
+
+// +--------------------------------------------------------------------+
+
+void
+WeaponGroup::PowerOff()
+{
+ ListIter<Weapon> w = weapons;
+ while (++w)
+ w->PowerOff();
+}
+
+void
+WeaponGroup::PowerOn()
+{
+ ListIter<Weapon> 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<Weapon>& 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<Weapon> 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<WeaponGroup>& 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