I'd like to have my Waves know why GameEntityTypeData they will be using (so I can warn things like "Incoming Fighter Wave" if desired). Is there an obvious way to Serialize/Deserialize those? I believe I can "make it work the hard way" if necessary, but if there's something easy then that would be preferrable.
I'm just going to give you my stream of consciousness of how I would plan to implement this feature (announced waves, in general), with some explanatory notes. If you want to do it, be my guest. Otherwise I'll already have the notes for when I do it
Extending the schema to fit the new gamestate:
1) Add an ExternalDataPattern named "PlannedWaves" or something like that. Its GetShouldInitializeOn would return true on "Faction" (that is, it's attached to Faction objects, rather than World or GameEntity or whatever).
2) In that pattern have a List<PlannedWave>
3) Define PlannedWave as a class containing:
- Planet TargetPlanet
- ArcenSparseLookup<GameEntityTypeData,int> Composition
- int GameSecondToSpawn
4) Implement the pattern's InitializeData, SerializeData, and DeserializeData methods to handle that list.
5) Similar to how DoomData does it, write a ExtensionMethodsFor_DoomData class with a GetPlannedWaves method so we can later write faction.GetPlannedWaves() and get that list with no extra fuss.
- if you like, you can add a convenience method there for AddWave or something like that
On the serialization, a few notes:
- for how to serialize a list at all, look at how DoomData handles DoomedPlanetIndices
- for serializing PlannedWave, give it the usual signatures:
-- public void SerializeTo( ArcenSerializationBuffer Buffer, bool IsForSendingOverNetwork )
-- public static PlannedWave DeserializeFrom( ArcenDeserializationBuffer Buffer, bool IsLoadingFromNetwork, GameVersion DeserializingFromGameVersion )
-- and where you'd normally just call Buffer.Add(int) call plannedWave.SerializeTo(Buffer,...), and similar on the deserialization side
- for serializing the Planet, just send planet.PlanetIndex down as an int, and when it comes back up call World_AIW2.Instance.GetPlanetByIndex(Buffer.ReadInt32())
- for serializing the <type,int> lookup, do it like a list with sending the count down first (.GetPairCount()) and then looping, but instead of doing one Add/SerializeTo call you do two.
- for serializing the GameEntityTypeData itself, just call GameEntityTypeDataTable.Instance.SerializeTo(type,buffer,...) ; there's a similar DeserializeFrom method for picking it back up.
-- if you're curious, all it does is serialize the InternalName (string) of the row, which is why InternalName mustn't change once a savegame has been created that references that particular row
Extending the simulation to populate the new gamestate:
1) Split the innards of SendWave into something like:
PlannedWave PlanWave()
- gets all the stuff involved in choosing the target, what ships to send, and (new logic) what time it should spawn
void SpawnWave(PlannedWave)
- gets all the rest of the stuff from SendWave (i.e. the actual spawning)
2) Have SendWave just call the first and then immediately the second.
3) Have TryToSpendBudget_Wave call only PlanWave, and then faction.AddWave(newWave)
Extending the simulation to use the new gamestate:
Override DoPerSimStepLogic in the class you're working in (SpecialFaction_AI), and have it:
- loop over GetPlannedWaves()
-- if GameSecondToSpawn > World_AIW2.Instance.GameSecond, skip
-- else call SpawnWave on that wave
--- and remove that wave from the list
Extending the UI to represent the new gamestate:
In Window_ResourceBar.tThreat_BottomLine.GetTextToShow() :
- instead of that stuff about threshold and perSecond and all that, just loop over GetPlannedWaves() inside the faction loop
- remember the smallest wave.GameSecondToSpawn you find
-- optionally filter on TargetPlanet.GetControllingSide().Type == Player or something like that, in case of later logic with the AI launching waves against minor factions or other AIs or other craziness
- take smallest GameSecondToSpawn - World_AIW2.Instance.GameSecond, and save that as leastSecondsLeft, and the existing code will display it fine
- reimplement HandleMouseover to contain info on the ships that are coming, where it's going, etc.
You may want to encapsulate that "get the soonest-to-spawn wave" logic somewhere so you can use it in coloring a planet name on the galaxy map, etc. Though there it may be best to call it at the beginning of a UI frame and reference that computed value for the duration of the frame, or something like that. I'm not sure about the thread-safety considerations, though things like GetTextToShow are fairly well timed to occur when the sim is "stable" (not being written to), unlike other parts of the visualization code.
By the way, I forget if I asked before: why do you not use MonoDevelop? I think it will at least give you intellisense and some syntax autocompletion, etc.