Author Topic: Improving Wave Options  (Read 1421 times)

Offline BadgerBadger

  • Hero Member Mark II
  • *****
  • Posts: 839
  • BadgerBadgerBadgerBadger
Improving Wave Options
« on: November 08, 2017, 10:46:55 AM »
I want to make sure that AIW2 comes with enough per game customization to satisfy an AIWC player. I was casting around for another AI War project, and I have an idea.

The Wave generation code is now in External Code. I think it would be possible to add a few extra flags pretty easily as Conducts.
  • Disable Wave Warnings: Pretty straightfoward
  • Wave Target Warnings: Determine which planet will get the next wave when a given wave is sent so that we can alert the player in advance of the wave target
  • Direct Waves: Pick a planet owned by the humans, then drop a bunch of units of one type on it. Current behaviour
  • Threat Waves: waves will now always spawn on an AI homeworld and just join the threat fleet
  • Cross Planet Waves: waves will spawn at a nearby Warp Gate then have to travel to the target (Original AIW2 behaviour)
  • Wave Composition: Homogenous/Schizophrenic: allow for a variety of ships to be sent in a wave
For Wave Target Warnings, the player will be able to potentially affect the wave behaviour by destroying the Warp Gate the AI intends to use. I think in this case the wave should not be spawned as planned. However, this means that the next wave will be twice as large (since there will be a lot more budget to spend), and an additional "You've made the AI mad" increase to the budget should be given. So you can put off the next wave, but there's a price to pay.

Does this sound like a desirable set of flags? Are there any cool behaviours that I'm missing?

One thing I am intrigued by is an additional Wave Composition option. Instead of either Homogenous (and chosen at random) or Schizophrenic (purely random ships), the AI analyzes your planet and figures out what mix of ships will do the most damage. If you have lots of shields it includes a strong mix of shield busters. If you have no tachyon emitters then it will include some cloaked ships, etc...

I realize this is trending back toward "A million customization flags at game start that could be overwhelming for a novice", but having all those flags was one of the things I really enjoyed about AIWC. There was always another cool thing to try for my next game.
If necessary can hide such flags under an "Advanced Customization" menu so that a first time player wouldn't even have to look.[/list]

Offline keith.lamothe

  • Arcen Games Staff
  • Administrator
  • Zenith Council Member Mark III
  • *****
  • Posts: 19,504
Re: Improving Wave Options
« Reply #1 on: November 08, 2017, 11:11:10 AM »
This does look like a good area for modders to tinker with at the moment, in that you're likely to enjoy it and it would be helpful if someone fleshed this out while I'm absorbed in the UI upheaval.

Bear in mind one key structural difference between AIWC and AIW2: AIWC had an intermediate state _before_ a wave was launched where the wave was "planned" and assigned a timer. That plan was then added to a world-level list that was counted down every second, shown in the alert box, etc.

AIW2 doesn't have that. It has a budget, and a target "launch a wave when I get this much in the wave budget" number, and when it reaches that threshold it immediately generates a wave and does all the targeting and composition decisions in that instant.

So the timer you see in AIW2 is just a projection, based on current AIP levels (and anything else that impacts the wave budget), whereas in AIWC it was a literal countdown.

Accordingly, to do a number of the things you have in mind we need to change AIW2's behavior from "when budget hits threadshold, immediately spawn stuff" to "when budget hits threshold, create a PlannedWave object and add it to a list for this faction, and every game-second count down each of those objects, and when the timer hits zero do the spawn logic"; and then the visual timer could be changed to look at those lists.

That can probably be achieved through the per-faction custom data and faction custom per-second logic, so you could do it yourself. But I'm happy to do it if you'd rather not deal with that particular element of the problem.


Another point is that, while you could use the conduct structure for this, I think you'll get more mileage using the per-faction custom fields. This allows you to have different AIs with different wave behaviors. So if you have multiple AIs controlling different areas of the map, your defensive situation on a planet is different depending on the AI(s) it neighbors.


As always, thanks for your many contributions :)

I hope some of the other players become more active in this effort, as well, but I understand that wandering into a multidimensional lumber mill is not for everyone ;) And there's other factors like schedule and willingness to deal with C#, etc.
« Last Edit: November 08, 2017, 11:13:33 AM by keith.lamothe »
Have ideas or bug reports for one of our games? Mantis for Suggestions and Bug Reports. Thanks for helping to make our games better!

Offline BadgerBadger

  • Hero Member Mark II
  • *****
  • Posts: 839
  • BadgerBadgerBadgerBadger
Re: Improving Wave Options
« Reply #2 on: November 08, 2017, 04:32:51 PM »
Cool, I'll investigate this. If you have other ideas for some low hanging fruit that needs doing feel free to ask, we'll see what my (or other modders) schedule permits.

Offline BadgerBadger

  • Hero Member Mark II
  • *****
  • Posts: 839
  • BadgerBadgerBadgerBadger
Re: Improving Wave Options
« Reply #3 on: November 10, 2017, 09:36:52 PM »
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.

Same question for a GameEntity so I can save the spawn location (though in this case the "Hard Way" is not very hard, I just make some ExternalData to attach to the Wormhole).
« Last Edit: November 10, 2017, 10:03:19 PM by BadgerBadger »

Offline keith.lamothe

  • Arcen Games Staff
  • Administrator
  • Zenith Council Member Mark III
  • *****
  • Posts: 19,504
Re: Improving Wave Options
« Reply #4 on: November 10, 2017, 10:38:06 PM »
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.
Have ideas or bug reports for one of our games? Mantis for Suggestions and Bug Reports. Thanks for helping to make our games better!

Offline BadgerBadger

  • Hero Member Mark II
  • *****
  • Posts: 839
  • BadgerBadgerBadgerBadger
Re: Improving Wave Options
« Reply #5 on: November 11, 2017, 12:03:25 AM »
You'll be pleased to know that your sketched plan was very similar to what I was already planning, though more concretely and accurately realized. Good stuff.

I actually have MonoDevelop installed, but it crashes whenever I try to start it and I haven't gotten around to debugging it.

Offline BadgerBadger

  • Hero Member Mark II
  • *****
  • Posts: 839
  • BadgerBadgerBadgerBadger
Re: Improving Wave Options
« Reply #6 on: November 13, 2017, 12:19:41 AM »
Okay, I'm not quite done yet, but things are coming along nicely.

I do have one question. How large should the first wave be? For all waves past the first wave, I just spend the value in faction.AITypeData.BudgetItems[AIBudgetType.Wave] which is generated by the Budgeting code. But for the first wave the budget won't have accumulated yet.

Offline keith.lamothe

  • Arcen Games Staff
  • Administrator
  • Zenith Council Member Mark III
  • *****
  • Posts: 19,504
Re: Improving Wave Options
« Reply #7 on: November 13, 2017, 07:42:41 AM »
But for the first wave the budget won't have accumulated yet.
TryToSpendBudget (which calls TryToSpendBudget_Wave) is only called when faction.StoredStrengthByBudget[budget] >= faction.GetSpecificBudgetThreshold(budget) , so whenever TryToSpendBudget_Wave calls PlanWave you can trust that spending the amount in faction.StoredStrengthByBudget[budget] is a reasonable thing to do.

Are you not finding that to be the case?
Have ideas or bug reports for one of our games? Mantis for Suggestions and Bug Reports. Thanks for helping to make our games better!

Offline BadgerBadger

  • Hero Member Mark II
  • *****
  • Posts: 839
  • BadgerBadgerBadgerBadger
Re: Improving Wave Options
« Reply #8 on: November 13, 2017, 08:57:09 AM »
Ah, I didn't know how TryToSpendBudget was called (that call doesn't seem to be in external code). I actually assumed it was called every X seconds, based on faction.AITypeData.BudgetItems[AIBudgetType.Wave].SecondsBetweenAttemptsToSpend.

That said, the problem with the first wave is this. Lets say that the budget accumulates such that a wave comes every 5 minutes. In the old regime, this meant at the 5 minute mark a wave would show up. In the new regime I only queue the wave once the budget is filled, then the wave actually spawns faction.AITypeData.BudgetItems[AIBudgetType.Wave].SecondsBetweenAttemptsToSpend after it has been queued. So in the new regime, the first wave would show up at 10 minutes in. This means that the very first wave is a special case, since I would like to queue it as soon as the game begins.

Or am I missing something?

Offline keith.lamothe

  • Arcen Games Staff
  • Administrator
  • Zenith Council Member Mark III
  • *****
  • Posts: 19,504
Re: Improving Wave Options
« Reply #9 on: November 13, 2017, 09:02:47 AM »
Ah, I didn't know how TryToSpendBudget was called (that call doesn't seem to be in external code). I actually assumed it was called every X seconds, based on faction.AITypeData.BudgetItems[AIBudgetType.Wave].SecondsBetweenAttemptsToSpend.
It's only called when the threshold is hit. The threshold = (amount accumulated per second) * (SecondsBetweenAttemptsToSpend)


Quote
This means that the very first wave is a special case, since I would like to queue it as soon as the game begins.

Or am I missing something?
I think the thing you're missing is that the first game did not queue a wave as soon as the game begins, and that's why this model (adopted from the first game) does not fit well with an immediate wave :)
Have ideas or bug reports for one of our games? Mantis for Suggestions and Bug Reports. Thanks for helping to make our games better!

Offline BadgerBadger

  • Hero Member Mark II
  • *****
  • Posts: 839
  • BadgerBadgerBadgerBadger
Re: Improving Wave Options
« Reply #10 on: November 13, 2017, 09:13:26 AM »
Oh. That's fine then. Whew. I guess it makes more sense not to queue the wave immediately from a gameplay point of view (noone wants to be attacked at 1 second into the game).


Offline keith.lamothe

  • Arcen Games Staff
  • Administrator
  • Zenith Council Member Mark III
  • *****
  • Posts: 19,504
Re: Improving Wave Options
« Reply #11 on: November 13, 2017, 09:23:19 AM »
Oh. That's fine then. Whew. I guess it makes more sense not to queue the wave immediately from a gameplay point of view (noone wants to be attacked at 1 second into the game).
Yep.

If we did want that I could just set the starting budget equal to the threshold, done.

I'm planning to move more of the budget framework down into external later this week; we'll see. I don't think it will give you much more power for the already-defined budget-types, but it will make it easier to have budget-logic for factions that don't do the same things the AI does.
Have ideas or bug reports for one of our games? Mantis for Suggestions and Bug Reports. Thanks for helping to make our games better!

Offline BadgerBadger

  • Hero Member Mark II
  • *****
  • Posts: 839
  • BadgerBadgerBadgerBadger
Re: Improving Wave Options
« Reply #12 on: November 13, 2017, 06:34:12 PM »
See the hover text. How does that look?

Also note I tweaked the time modifier text to be like AIWC. Opinions?

Offline keith.lamothe

  • Arcen Games Staff
  • Administrator
  • Zenith Council Member Mark III
  • *****
  • Posts: 19,504
Re: Improving Wave Options
« Reply #13 on: November 13, 2017, 06:56:47 PM »
See the hover text. How does that look?

Also note I tweaked the time modifier text to be like AIWC. Opinions?
Looks good to me, great work getting the underlying guts to that point :)
Have ideas or bug reports for one of our games? Mantis for Suggestions and Bug Reports. Thanks for helping to make our games better!

Offline BadgerBadger

  • Hero Member Mark II
  • *****
  • Posts: 839
  • BadgerBadgerBadgerBadger
Re: Improving Wave Options
« Reply #14 on: November 13, 2017, 09:02:48 PM »
I've got a weird one. I don't think this is a bug in my code, so I'm asking for assistance. I have here this printout line in PlannedWave::SerializeTo()
Code: [Select]
          ArcenDebugging.ArcenDebugLogSingleLine( "SerializeTo: PlannedWave  " + output + " planetwithWarpGate " + planetWithWarpGate.Name + "(" + planetWithWarpGate.PlanetIndex + ")" + " targetPlanet " + targetPlanet.Name + "(" + targetPlanet.PlanetIndex + ")" , Verbosity.DoNotShow )\

This prints out
11/13/2017 8:54:14 PM   SerializeTo: PlannedWave   2 Laser Gatling,  6 Grenade Launchers to Colmerauer in 47s planetwithWarpGate Sten(18) targetPlanet Colmerauer(0)
So you see that targetPlanet.PlanetIndex is 0. However, when I run this through the Deserialize thusly
Code: [Select]
          int targetidx = buffer.ReadInt32();
          ArcenDebugging.ArcenDebugLogSingleLine( "ctor 2 target idx " + targetidx , Verbosity.DoNotShow );
          this.targetPlanet = World_AIW2.Instance.GetPlanetByIndex( targetidx );
          if(targetPlanet == null)
            {
              ArcenDebugging.ArcenDebugLogSingleLine( " targetPlanet is null" , Verbosity.DoNotShow );
            }

I get the log
11/13/2017 8:54:18 PM   ctor 2 target idx 0
11/13/2017 8:54:18 PM    targetPlanet is null

So it looks like a planet can legitimately have Index 0, but calling GetPlanetByIndex(0) gets "null".

Am I missing something? If I use a different seed where the planet index is non-zero then it works fine.

Edit: It looks like GetPlanetByIndex is just not working right....

Code: [Select]
              for(int i = 0; i < 80; i++)
                {
                  Planet planet = World_AIW2.Instance.GetPlanetByIndex( i );
                  if(planet == null)
                    {
                      ArcenDebugging.ArcenDebugLogSingleLine( " planet idx " + i + " is null" , Verbosity.DoNotShow );
                    }
                  else
                    {
                      ArcenDebugging.ArcenDebugLogSingleLine( " planet idx " + i + " is " + planet.Name , Verbosity.DoNotShow );
                    }
                }

tells me every planet is null:
Code: [Select]
11/13/2017 9:17:35 PM planet idx 0 is null
11/13/2017 9:17:35 PM planet idx 1 is null
11/13/2017 9:17:35 PM planet idx 2 is null
11/13/2017 9:17:35 PM planet idx 3 is null
11/13/2017 9:17:35 PM planet idx 4 is null
11/13/2017 9:17:35 PM planet idx 5 is null
11/13/2017 9:17:35 PM planet idx 6 is null
11/13/2017 9:17:35 PM planet idx 7 is null
11/13/2017 9:17:35 PM planet idx 8 is null
11/13/2017 9:17:35 PM planet idx 9 is null
<etc>

« Last Edit: November 13, 2017, 09:19:38 PM by BadgerBadger »