-
Notifications
You must be signed in to change notification settings - Fork 0
/
ModuleRackMount.cs
434 lines (373 loc) · 19 KB
/
ModuleRackMount.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
using UnityEngine;
using System.Collections.Generic;
using System;
using System.Reflection;
using UnityEngine.EventSystems;
namespace RackMount
{
public class ModuleRackMount : PartModule
{
[KSPField]
public float evaDistance = 3;
[KSPField]
public bool requiresEngineer = true;
[KSPField]
public bool canAlwaysRackmount = false;
//removes existing ModuleCommand. ModuleCommand is a special snowflake and needs additional logic to mount properly
//this starts with having an existing ModuleCommand then removing it. This fixes issues with launch checks and on rails issues.
//enable if you plan on mounting ModuleCommand
[KSPField]
public bool removeModuleCommand = false;
[KSPField]
public bool autoCalculateVolume = true;
[KSPField]
public float volumeAdjustPercent = 0.7f;
[KSPField]
public string partType = "";
Dictionary<uint, int> rackMountableParts = new Dictionary<uint, int>();
Dictionary<uint, int> unMountableParts = new Dictionary<uint, int>();
private ModuleInventoryPart inv;
private BasePAWGroup rackmountGroup = new BasePAWGroup("rackmountGroup", "Rackmount Inventory", false);
private bool onLoad;
private bool previousCanRackmount = true;
//needed to turn on vessel renaming when a ModuleCommand part is installed.
//For some reason putting it in OnUpdate() allows it to be displayed
//on load.
private bool displayVesselNaming = false;
public override void OnLoad(ConfigNode node)
{
inv = part.Modules.GetModule<ModuleInventoryPart>();
//checks for no volume set
if (autoCalculateVolume && inv.packedVolumeLimit == 0)
{
Bounds bounds = default(Bounds);
foreach (var bound in part.GetRendererBounds())
{
bounds.Encapsulate(bound);
}
float vol = ((float)Math.Round(bounds.size.x * bounds.size.y * bounds.size.z * volumeAdjustPercent, 2));
inv.packedVolumeLimit = vol * 1000f;
}
base.OnLoad(node);
//disable existing ModuleCommand
if (removeModuleCommand)
{
PartModule p = part.Modules.GetModule<ModuleCommand>();
if (p != null)
part.RemoveModule(p);
}
//modules need to be added after base.OnLoad();
onLoad = true;
if (inv.storedParts != null)
{
//add buttons
for (int i = 0; i < inv.storedParts.Count; i++)
{
AddRackmountButton(inv.storedParts.At(i));
}
//add mounted modules
for (int i = 0; i < inv.storedParts.Count; i++)
{
bool mounted = false;
inv.storedParts.At(i).snapshot.partData.TryGetValue("partRackmounted", ref mounted);
if (mounted)
RackmountPart(inv.storedParts.At(i));
}
}
onLoad = false;
}
public override void OnStart(StartState state)
{
if(inv == null)
inv = part.Modules.GetModule<ModuleInventoryPart>();
base.OnStart(state);
inv.Fields["InventorySlots"].group = rackmountGroup;
inv.Fields["InventorySlots"].guiName = null;
if (HighLogic.LoadedSceneIsFlight)
inv.Fields["InventorySlots"].group.startCollapsed = true;
}
public override void OnUpdate()
{
if (HighLogic.LoadedSceneIsFlight)
{
//fugly way to display Vessel Renaming when ModuleCommand is
//mounted OnLoad
if (displayVesselNaming)
{
part.Events["SetVesselNaming"].guiActive = true;
displayVesselNaming = false;
}
//show or hide rackmount buttons
if (!previousCanRackmount && CanRackmount())
{
foreach (var button in Events.FindAll(x => x.name.Contains("RackmountButton")))
button.active = true;
previousCanRackmount = true;
}
else if (previousCanRackmount && !CanRackmount())
{
foreach (var button in Events.FindAll(x => x.name.Contains("RackmountButton")))
button.active = false;
previousCanRackmount = false;
}
}
base.OnUpdate();
}
private void Update()
{
List<uint> currentParts = new List<uint>();
Dictionary<uint, int> buttonsToRemove = new Dictionary<uint, int>();
//looks for new parts
for (int i = 0; i < inv.storedParts.Count; i++)
{
if (!rackMountableParts.ContainsKey(inv.storedParts.At(i).snapshot.persistentId) && !unMountableParts.ContainsKey(inv.storedParts.At(i).snapshot.persistentId))
AddRackmountButton(inv.storedParts.At(i));
currentParts.Add(inv.storedParts.At(i).snapshot.persistentId);
}
//looks for removed parts
foreach (KeyValuePair<uint, int> rackMountablePart in rackMountableParts)
{
if (!currentParts.Contains(rackMountablePart.Key))
buttonsToRemove.Add(rackMountablePart.Key, rackMountablePart.Value);
}
foreach (KeyValuePair<uint, int> button in buttonsToRemove)
{
RemoveRackmountButton(button.Key, button.Value);
}
//locks mounted parts. Done in Update() because doing an EVA and going into Engineering mode re-enables the slotButton.
if (part.PartActionWindow != null)
{
UIPartActionInventory inventoryUI = (UIPartActionInventory)part.PartActionWindow.ListItems.Find(x => x.GetType() == typeof(UIPartActionInventory));
for (int i = 0; i < inv.storedParts.Count; i++)
{
bool mounted = false;
inv.storedParts.At(i).snapshot.partData.TryGetValue("partRackmounted", ref mounted);
if (mounted)
{
inventoryUI.slotButton[inv.storedParts.At(i).slotIndex].enabled = false;
inventoryUI.slotButton[inv.storedParts.At(i).slotIndex].gameObject.SetActive(false);
}
}
}
//checks for construction mode and locks/unlocks mounted items
UIPartActionInventory constructionUI = inv.constructorModeInventory;
if (constructionUI != null)
{
for (int i = 0; i < inv.storedParts.Count; i++)
{
bool mounted = false;
inv.storedParts.At(i).snapshot.partData.TryGetValue("partRackmounted", ref mounted);
if (mounted)
{
constructionUI.slotButton[inv.storedParts.At(i).slotIndex].enabled = false;
constructionUI.slotButton[inv.storedParts.At(i).slotIndex].gameObject.SetActive(false);
}
else
{
constructionUI.slotButton[inv.storedParts.At(i).slotIndex].enabled = true;
constructionUI.slotButton[inv.storedParts.At(i).slotIndex].gameObject.SetActive(true);
}
}
}
//fugly way to display Vessel Renaming when ModuleCommand is
//mounted OnLoad in the Editor
if (HighLogic.LoadedSceneIsEditor && displayVesselNaming)
{
part.Events["SetVesselNaming"].guiActiveEditor = true;
displayVesselNaming = false;
}
}
//adds button for mounting and unmounting parts
private void AddRackmountButton(StoredPart storedPart)
{
ConfigNode partConfig = storedPart.snapshot.partInfo.partConfig;
ConfigNode rackMountPart = partConfig.GetNode("MODULE", "name", "ModuleRackMountPart");
string requiresPartType = "";
if (rackMountPart != null)
{
rackMountPart.TryGetValue("requiresPartType", ref requiresPartType);
if (requiresPartType == "" || partType.ToUpper() == "ANY" || requiresPartType.ToUpper() == partType.ToUpper())
{
rackMountableParts.Add(storedPart.snapshot.persistentId, storedPart.slotIndex);
KSPEvent mount = new KSPEvent
{
name = "RackmountButton" + storedPart.slotIndex,
active = previousCanRackmount,
guiActive = true,
guiActiveEditor = true,
guiActiveUnfocused = true,
guiActiveUncommand = true,
unfocusedRange = evaDistance,
guiName = "<b><color=green>Rackmount</color> " + storedPart.snapshot.partInfo.title + "</b>"
};
BaseEvent RackmountButton = new BaseEvent(Events, mount.name, () => RackmountButtonPressed(storedPart), mount);
RackmountButton.group = rackmountGroup;
Events.Add(RackmountButton);
}
else if (!unMountableParts.ContainsKey(storedPart.snapshot.persistentId))
{
unMountableParts.Add(storedPart.snapshot.persistentId, storedPart.slotIndex);
if (partType == "")
ScreenMessages.PostScreenMessage("<color=orange>Part " + storedPart.snapshot.partInfo.title + " can only be mounted on part type of " +
rackMountPart.GetValue("requiresPartType") + "!</color>\nIt is currently stored in part without a part type.", 7);
else
ScreenMessages.PostScreenMessage("<color=orange>Part " + storedPart.snapshot.partInfo.title + " can only be mounted on part type of " +
rackMountPart.GetValue("requiresPartType") + "!</color>\nIt is currently stored in a part type of " + partType + ".", 7);
}
}
}
private void RemoveRackmountButton(uint id, int slot)
{
Events.Find(x => x.name == "RackmountButton" + slot).active = false;
Events.Remove(Events.Find(x => x.name == "RackmountButton" + slot));
rackMountableParts.Remove(id);
}
private void RackmountButtonPressed(StoredPart storedPart)
{
bool mounted = false;
storedPart.snapshot.partData.TryGetValue("partRackmounted", ref mounted);
if (mounted)
UnmountPart(storedPart);
else
RackmountPart(storedPart);
}
private void RackmountPart(StoredPart storedPart)
{
bool rackMountable = true;
ConfigNode partConfig = storedPart.snapshot.partInfo.partConfig;
foreach (ConfigNode moduleConfigNode in partConfig.GetNodes("MODULE"))
{
if (moduleConfigNode.TryGetValue("rackMountable", ref rackMountable))
{
//ModuleScienceExperiment fixes
if (moduleConfigNode.GetValue("name") == "ModuleScienceExperiment")
{
moduleConfigNode.RemoveValue("FxModules");
}
PartModule partModule = part.AddModule(moduleConfigNode, true);
int moduleIndex = part.Modules.IndexOf(partModule);
ProtoPartModuleSnapshot moduleSnapshot = storedPart.snapshot.FindModule(partModule, moduleIndex);
part.LoadModule(moduleSnapshot.moduleValues, ref moduleIndex);
//ModuleCommand fixes
if (partModule.GetType() == typeof(ModuleCommand))
{
part.Events["SetVesselNaming"].guiActive = true;
part.Events["SetVesselNaming"].guiActiveEditor = true;
//Here so it display the Vessel Naming from OnLoad using Update
displayVesselNaming = true;
ModuleCommand c = (ModuleCommand)partModule;
DictionaryValueList<string, ControlPoint> controlPoints = new DictionaryValueList<string, ControlPoint>();
ControlPoint _default = new ControlPoint("_default", c.defaultControlPointDisplayName, part.transform, new Vector3(0, 0, 0));
controlPoints.Add(_default.name, _default);
foreach (var node in moduleConfigNode.GetNodes("CONTROLPOINT"))
{
Vector3 orientation = new Vector3(0, 0, 0);
node.TryGetValue("orientation", ref orientation);
ControlPoint point = new ControlPoint(node.GetValue("name"), node.GetValue("displayName"), part.transform, orientation);
controlPoints.Add(point.name, point);
}
c.controlPoints = controlPoints;
}
//Modules loaded with OnLoad() already includes modulePersistentID from save file
if (!onLoad)
moduleSnapshot.moduleValues.AddValue("modulePersistentId", partModule.GetPersistentId());
}
}
foreach (var resource in storedPart.snapshot.resources)
{
rackMountable = false;
var configNode = storedPart.snapshot.partInfo.partConfig.GetNode("RESOURCE", "name", resource.resourceName);
configNode.TryGetValue("rackMountable", ref rackMountable);
var partResource = part.Resources.Get(resource.resourceName);
if (partResource != null && rackMountable)
{
partResource.maxAmount += resource.maxAmount;
partResource.amount += resource.amount;
}
else if (rackMountable)
resource.Load(part);
}
storedPart.snapshot.partData.SetValue("partRackmounted", true, true);
BaseEvent button = (BaseEvent)Events.Find(x => x.name == "RackmountButton" + storedPart.slotIndex);
button.guiName = "<b><color=orange>Unmount</color> " + storedPart.snapshot.partInfo.title + "</b>";
//creates a potential bug when existing mods are 'restarted' since it restarts ALL existing modules.
if (!onLoad)
{
part.ModulesOnActivate();
part.ModulesOnStart();
part.ModulesOnStartFinished();
}
}
private void UnmountPart(StoredPart storedPart)
{
List<PartModule> removeModules = new List<PartModule>();
//likely needs a better way of storing/retrieving moduleValues
if (HighLogic.LoadedSceneIsFlight)
GamePersistence.SaveGame("RackMount", HighLogic.SaveFolder, SaveMode.BACKUP);
foreach (PartModule partModule in part.Modules)
{
foreach (var module in storedPart.snapshot.modules)
{
if (module.moduleValues.GetValue("modulePersistentId") == partModule.GetPersistentId().ToString())
{
//moduleValues only accessible in flight?
if (HighLogic.LoadedSceneIsFlight)
module.moduleValues = partModule.snapshot.moduleValues;
removeModules.Add(partModule);
module.moduleValues.RemoveValue("modulePersistentId");
if (module.GetType() == typeof(ModuleCommand))
{
part.Events["SetVesselNaming"].guiActive = false;
part.Events["SetVesselNaming"].guiActiveEditor = false;
displayVesselNaming = false;
}
}
}
}
foreach (PartModule partModule in removeModules)
part.RemoveModule(partModule);
foreach (ProtoPartResourceSnapshot resource in storedPart.snapshot.resources)
{
bool rackMountable = false;
var configNode = storedPart.snapshot.partInfo.partConfig.GetNode("RESOURCE", "name", resource.resourceName);
configNode.TryGetValue("rackMountable", ref rackMountable);
PartResource storedResource = part.Resources.Get(resource.resourceName);
if (storedResource != null && rackMountable)
{
resource.amount = storedResource.amount * (resource.maxAmount / storedResource.maxAmount);
storedResource.amount -= resource.amount;
storedResource.maxAmount -= resource.maxAmount;
}
}
storedPart.snapshot.partData.SetValue("partRackmounted", false, true);
//unlocking always happens from paw?
UIPartActionInventory inventoryUI = (UIPartActionInventory)part.PartActionWindow.ListItems.Find(x => x.GetType() == typeof(UIPartActionInventory));
inventoryUI.slotButton[storedPart.slotIndex].enabled = true;
inventoryUI.slotButton[storedPart.slotIndex].gameObject.SetActive(true);
BaseEvent button = (BaseEvent)Events.Find(x => x.name == "RackmountButton" + storedPart.slotIndex);
button.guiName = "<b><color=green>Rackmount</color> " + storedPart.snapshot.partInfo.title + "</b>";
part.ModulesOnDeactivate();
}
private bool CanRackmount()
{
//for debugging
if (canAlwaysRackmount)
return true;
//First check for kerbal on EVA. If EVA doesn't qualify, return false.
if (FlightGlobals.ActiveVessel.isEVA)
{
ProtoCrewMember crew = FlightGlobals.ActiveVessel.GetVesselCrew()[0];
float kerbalDistanceToPart = Vector3.Distance(FlightGlobals.ActiveVessel.transform.position, part.collider.ClosestPointOnBounds(FlightGlobals.ActiveVessel.transform.position));
if (kerbalDistanceToPart < evaDistance && (!requiresEngineer || crew.trait == "Engineer"))
return true;
else
return false;
}
//Next check onboard crew
foreach (var crew in vessel.GetVesselCrew())
if (!requiresEngineer || crew.trait == "Engineer")
return true;
return false;
}
}
}