Author Topic: The Nanocaust  (Read 1329 times)

Offline keith.lamothe

  • Arcen Games Staff
  • Administrator
  • Zenith Council Member Mark III
  • *****
  • Posts: 19,339
Re: The Nanocaust
« Reply #15 on: July 28, 2017, 06:44:33 AM »
Quick question: if I'm not inside the Nanocaust code, how can I refer to the data inside the SpecialFaction_Nanocaust.Instance?
To get at the stuff in your mgr sub object you could use SpecialFaction_Nanocaust.Instance.mgr , though you'll need to make mgr public or internal for that to work; it's currently private.

Quote
I'm trying to implement hacking the Nanobot Hive, and I think the most efficient way to do this is to set a flag in the NanocaustMgr that can be checked PerSimStem.
That might work in the moment, but how would you get it to/from disk during save/load?

The ability to serialize/deserialize your own data as a modder is something I need to address, but for now I think you'd have more success with replacing the hive with an alternate entity type that behaves in the "hacked" way.
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
  • *****
  • Posts: 689
  • BadgerBadgerBadgerBadger
Re: The Nanocaust
« Reply #16 on: August 02, 2017, 10:28:52 AM »
This patch should make it possible for the humans to Hack the Nanocaust; if you can manage to pull that off then the Nanobots will be your allies and will help you try to kill the AI with you. May need some tuning eventually if the hacking process is too easy/hard, but the alternate win condition now works, and it's really satisfying.

And before you ask, the hacked-ness of the nanocaust persists across saves.
« Last Edit: August 02, 2017, 04:55:33 PM by BadgerBadger »

Offline BadgerBadger

  • Hero Member
  • *****
  • Posts: 689
  • BadgerBadgerBadgerBadger
Re: The Nanocaust
« Reply #17 on: August 24, 2017, 01:15:57 PM »
This is basically a full internal rework of the Nanocaust. Changes include
  • Improved comments/code organization and quieted compiler warnings
  • updating the AI Progress so that the Nanocaust doesn't drive the progress up enormously (this is intended as a workaround till Keith has a chance to make Factions not affect the progress)
  • Nanocaust actions governed by various fleets, of which there can be many at once. So the Nanocaust could launch multiple attack simultaneously at different targets. Previously it used a very crude method of "Pick a target and send a bunch of ships there immediately" which did not scale at all. Now it will carefully choose ships (and the number of ships it can send is tunable), stage them all to a given location and then launch the entire force simultaneously
  • Improving the XML to allow inheriting nanobot center properties
  • Improving the tuning levers so that most can be eventually read from XML for easier balancing

Overall the behaviour isn't "that" much different. It will expand slowly enough that you should hopefully be able to fight it. Most of the work is behind the scenes, getting similar behaviour in a scalable, maintainable fashion.

If you review this on youtube I will be much less embarrassed.

Offline TheVampire100

  • Master Member
  • *****
  • Posts: 1,261
  • Ordinary Vampire
Re: The Nanocaust
« Reply #18 on: August 24, 2017, 01:55:05 PM »
I think you should patches like this directly to Keith, so he can pack it into the next official patch as well.

Offline BadgerBadger

  • Hero Member
  • *****
  • Posts: 689
  • BadgerBadgerBadgerBadger
Re: The Nanocaust
« Reply #19 on: September 26, 2017, 07:33:16 PM »
Okay, I think this patch will enable External Data and External Constants.

Patch contents:
A. Let the Nanocaust state persist across save/load 
B. begin exporting Nanocaust tunables out to xml.
C. Other miscellaneous code cleanup and improvements, but nothing major to gameplay

Keith, lemme know if there are any problems. I don't think I forgot to include anything but you never know.

Offline keith.lamothe

  • Arcen Games Staff
  • Administrator
  • Zenith Council Member Mark III
  • *****
  • Posts: 19,339
Re: The Nanocaust
« Reply #20 on: September 27, 2017, 09:23:49 AM »
Goodness, I'm sorry you had to figure out how to serialize/deserialize all that the hard way. I had hoped that the example in DoomData would have sufficiently highlighted the use of ArcenSerializationBuffer and ArcenDeserializationBuffer, which are highly specialized classes we've developed over years to make this sort of thing much easier.

As a side note, in C# strings are immutable so using concatenation (+ and +=) to chain data onto them is inefficient. Hence the C# StringBuilder class, which ArcenSerialization somewhat mimics.

I tested your serialization (it worked), revised it to follow our own patterns and retested it to make sure it still worked. Here's what to replace:

1)

Code: [Select]
//note that I have to use different delims for Fleet and NanocaustMgr
private char exportDelim = '#';
private char listDelim = ','; //for exporting/importing lists
public override string ToString()
{
  //Exports all the information for serialization in a string form

  //Things that need to get serialized: Fleet objects. time and strength modifiers.
  //hasHumanVision, humanEncounter, progressModification, totalPlanetsEverTaken*/
  string output = "";
  output += humanVision.ToString() + exportDelim;
  output += humanEncounter.ToString() + exportDelim;
  output += progressModification.ToString() + exportDelim;
  if(debug)
    ArcenDebugging.ArcenDebugLogSingleLine("ToString output after humanVision, humanEncounter and progressModification: <" + output + ">", Verbosity.DoNotShow);

  string totalPlanetsTakenStr = "";
  for(int i = 0; i < totalPlanetsEverTaken.Count; i++)
    {
      totalPlanetsTakenStr = totalPlanetsEverTaken[i].PlanetIndex.ToString() + listDelim;
    }
  if(totalPlanetsEverTaken.Count > 0) //trim the listDelim off the end
      totalPlanetsTakenStr = totalPlanetsTakenStr.Substring(0, (totalPlanetsTakenStr.Length - 1));
  output += totalPlanetsTakenStr + exportDelim;
  if(debug)
    ArcenDebugging.ArcenDebugLogSingleLine("ToString output after humanVision, humanEncounter,  progressModification, totalPlanets: <" + output + ">", Verbosity.DoNotShow);

  string fleetsStr = "";
  for(int i = 0; i < fleets.Count; i++)
    {
      fleetsStr += fleets[i].Export() + listDelim;
    }
  if(fleets.Count > 0) //trim the listDelim off the end
      fleetsStr = fleetsStr.Substring(0, (fleetsStr.Length - 1));
   
  output += fleetsStr + exportDelim;
  output += numFleetsSent.ToString() + exportDelim;
  output += timeForNextFrenzy.ToString() + exportDelim;
  output += modifierMaxStrengthPerPlanetForFrenzy.ToString() + exportDelim;
  output += timeModifierForNextFrenzy.ToString() + exportDelim;

  if(debug)
    ArcenDebugging.ArcenDebugLogSingleLine("ToString output: <" + output + ">", Verbosity.DoNotShow);
  return output;
}

With:

Code: [Select]
public void SerializeTo( ArcenSerializationBuffer Buffer, bool IsForSendingOverNetwork )
{
    Buffer.AddItem( humanVision );
    Buffer.AddItem( humanEncounter );
    Buffer.AddItem( progressModification );

    Buffer.AddItem( totalPlanetsEverTaken.Count );
    for ( int i = 0; i < totalPlanetsEverTaken.Count; i++ )
        Buffer.AddItem( totalPlanetsEverTaken[i].PlanetIndex );

    Buffer.AddItem( fleets.Count );
    for ( int i = 0; i < fleets.Count; i++ )
        fleets[i].SerializeTo( Buffer, IsForSendingOverNetwork );

    Buffer.AddItem( numFleetsSent );
    Buffer.AddItem( timeForNextFrenzy );
    Buffer.AddItem( modifierMaxStrengthPerPlanetForFrenzy );
    Buffer.AddItem( timeModifierForNextFrenzy );
}

2)

Code: [Select]
public NanocaustMgr(string data)
{
  //this constructor is used to undo ToString for the serialization code
  //takes as input a string ToString() and
  //creates the correct Manager
  hasSimStepRun = false;
  string[] tokens = data.Split( exportDelim );
  if(debug)
    ArcenDebugging.ArcenDebugLogSingleLine("NanocaustMgrConstructor(string): <" + data + ">", Verbosity.DoNotShow);

  for(int i = 0; i < tokens.Length; i++)
    {
      if(debug)
        ArcenDebugging.ArcenDebugLogSingleLine("NanocaustMgrConstructor(string): " + i + ": " + tokens[i], Verbosity.DoNotShow);
      if(tokens[i] == String.Empty)
        {
          continue;
        }
      if(i == 0)
        {
          humanVision = Convert.ToBoolean(tokens[i]);
        }
      if(i == 1)
        {
          humanEncounter = Convert.ToBoolean(tokens[i]);
        }
      if(i == 2)
        {
          progressModification = Convert.ToInt32(tokens[i]);
        }
      if(i == 3)
        {
          //totalPlanetsTaken list
          string[] planetTokens = tokens[i].Split( listDelim );
          for(int j = 0; j < planetTokens.Length; j++)
            {
              int idx = Convert.ToInt32(planetTokens[j]);
              totalPlanetsEverTaken.Add(World_AIW2.Instance.GetPlanetByIndex(idx));
            }
        }
      if(i == 4)
        {
          //fleets list
          string[] fleetTokens = tokens[i].Split( listDelim );
          for(int j = 0; j < fleetTokens.Length; j++)
            {
              FrenzyFleet fleet = new FrenzyFleet(fleetTokens[j]);
              fleets.Add(fleet);
            }
        }
      if(i == 5)
        {
          numFleetsSent = Convert.ToInt32(tokens[i]);
        }
      if(i == 6)
        {
          timeForNextFrenzy = Convert.ToInt32(tokens[i]);
        }
      if(i == 7)
        {
          modifierMaxStrengthPerPlanetForFrenzy = Convert.ToInt32(tokens[i]);
        }
      if(i == 8)
        {
          timeModifierForNextFrenzy = Convert.ToInt32(tokens[i]);
        }

    }
  if(debug)
    ArcenDebugging.ArcenDebugLogSingleLine("Mgr(string) numFleetsSent " + numFleetsSent, Verbosity.DoNotShow);
}

With:

Code: [Select]
public NanocaustMgr( ArcenDeserializationBuffer Buffer, bool IsLoadingFromNetwork, GameVersion DeserializingFromGameVersion )
{
    hasSimStepRun = false;

    this.humanVision = Buffer.ReadBool();
    this.humanEncounter = Buffer.ReadBool();
    this.progressModification = Buffer.ReadInt32();

    int countToExpect;

    countToExpect = Buffer.ReadInt32();
    for ( int i = 0; i < countToExpect; i++ )
        totalPlanetsEverTaken.Add( World_AIW2.Instance.GetPlanetByIndex( Buffer.ReadInt32() ) );

    countToExpect = Buffer.ReadInt32();
    for ( int i = 0; i < countToExpect; i++ )
        fleets.Add( new FrenzyFleet( Buffer, IsLoadingFromNetwork, DeserializingFromGameVersion ) );

    this.numFleetsSent = Buffer.ReadInt32();
    this.timeForNextFrenzy = Buffer.ReadInt32();
    this.modifierMaxStrengthPerPlanetForFrenzy = Buffer.ReadInt32();
    this.timeModifierForNextFrenzy = Buffer.ReadInt32();
}

3)

Code: [Select]
public FrenzyFleet(string exportedString)
{
  //takes as input a string from Export() and
  //creates the correct Fleet
  if(debug)
    ArcenDebugging.ArcenDebugLogSingleLine("FrenzyFleet constructor(string): full string: <" + exportedString + ">", Verbosity.DoNotShow);
  ships = new List<GameEntity>();
  string[] tokens = exportedString.Split( exportDelim );
  for(int i = 0; i < tokens.Length; i++)
    {
      if(debug)
        ArcenDebugging.ArcenDebugLogSingleLine("FrenzyFleet constructor(string): " + i + ": " + tokens[i], Verbosity.DoNotShow);
      if(i == 0)
        {
          //set up target
          int planetIdx = Convert.ToInt32(tokens[i]);
          if(planetIdx == -1)
            target = null;
          else
            target = World_AIW2.Instance.GetPlanetByIndex(planetIdx);
        }
      if(i == 1)
        {
          //set up target
          int planetIdx = Convert.ToInt32(tokens[i]);
          if(planetIdx == -1)
            stagingArea = null;
          else
            stagingArea = World_AIW2.Instance.GetPlanetByIndex(planetIdx);
        }
      if(i == 2)
        {
          time = Convert.ToInt32(tokens[i]);
        }
      if(i == 3)
        {
          fleetId = Convert.ToInt32(tokens[i]);
        }
      if(i == 4)
        {
          goal = stringToFrenzyGoal(tokens[i]);
        }
      if(i == 5)
        {
          startStaging = Convert.ToBoolean(tokens[i]);
        }
      if(i == 6)
        {
          stillStaging = Convert.ToBoolean(tokens[i]);
        }
      if(i == 7)
        {
          attackingFleet = Convert.ToBoolean(tokens[i]);
        }
      if(i == 8)
        {
          creatingFleet = Convert.ToBoolean(tokens[i]);
        }
      if(i == 9)
        {
          fleetTriumphant = Convert.ToBoolean(tokens[i]);
        }
      if(i == 10)
        {
          fleetDefeated = Convert.ToBoolean(tokens[i]);
        }
    }
}

With:

Code: [Select]
public FrenzyFleet( ArcenDeserializationBuffer Buffer, bool IsLoadingFromNetwork, GameVersion DeserializingFromGameVersion )
{
    ships = new List<GameEntity>();

    this.target = World_AIW2.Instance.GetPlanetByIndex( Buffer.ReadInt32() );
    this.stagingArea = World_AIW2.Instance.GetPlanetByIndex( Buffer.ReadInt32() );
    this.time = Buffer.ReadInt32();
    this.fleetId = Buffer.ReadInt32();
    this.goal = (FrenzyGoal)Buffer.ReadInt32();
    this.startStaging = Buffer.ReadBool();
    this.stillStaging = Buffer.ReadBool();
    this.attackingFleet = Buffer.ReadBool();
    this.creatingFleet = Buffer.ReadBool();
    this.fleetTriumphant = Buffer.ReadBool();
    this.fleetDefeated = Buffer.ReadBool();
}

By the way, the "ships = new List<GameEntity>();" line could also just happen on the field declaration. I.e. I'd suggest changing "public List<GameEntity> ships;" to "public readonly List<GameEntity> ships = new List<GameEntity>();". The readonly is optional and just means "at compile-time, never allow this list reference to be assigned outside the constructor" to avoid coding typos; the list can still be modified whenever.

Same deal with a lot of the initialization logic you have in the constructors: they can be done at declaration, and save lines (and code duplication in the case of multiple constructors, though you could avoid that by having one constructor call another).

4)

Code: [Select]
public string Export()
{
  //for Serialize/Deserialize, we need to export/import all the data
  //Since we are going to using the NanocaustMgr as the unit of serialize/deserialize,
  //this class does not explicitly do it as well; instead the NanocaustMgr will call Export() for each fleet.
  //. I could imagine wanting a FrenzyFleet to eventually handle Serialize/Deserialize though

  string output = "";
  //Note that for Export/Import, we export the planet Index (and then use GetPlanetByIndex)
  int targetIdx = -1;
  if(target != null)
    targetIdx = target.PlanetIndex;
  int stagingIdx = -1;
  if(stagingArea != null)
    stagingIdx = stagingArea.PlanetIndex;
  output += (targetIdx).ToString() + exportDelim;
  output += (stagingIdx).ToString() + exportDelim;
  output += time.ToString() + exportDelim;
  output += fleetId.ToString() + exportDelim;
  output += frenzyGoalToString(this.goal) + exportDelim;
  output += startStaging.ToString() + exportDelim;
  output += stillStaging.ToString() + exportDelim;
  output += attackingFleet.ToString() + exportDelim;
  output += creatingFleet.ToString() + exportDelim;
  output += fleetTriumphant.ToString() + exportDelim;
  output += fleetDefeated.ToString(); //don't put the export delim on the last entry
  if(debug)
    ArcenDebugging.ArcenDebugLogSingleLine("FrenzyFleet::Export: full string: <" + output + ">", Verbosity.DoNotShow);

  return output;
}

With:

Code: [Select]
public void SerializeTo( ArcenSerializationBuffer Buffer, bool IsForSendingOverNetwork )
{
    //Note that for Export/Import, we export the planet Index (and then use GetPlanetByIndex)
    Buffer.AddItem( target == null ? -1 : target.PlanetIndex );
    Buffer.AddItem( stagingArea == null ? -1 : stagingArea.PlanetIndex );
    Buffer.AddItem( time );
    Buffer.AddItem( fleetId );
    Buffer.AddItem( (int)goal );
    Buffer.AddItem( startStaging );
    Buffer.AddItem( stillStaging );
    Buffer.AddItem( attackingFleet );
    Buffer.AddItem( creatingFleet );
    Buffer.AddItem( fleetTriumphant );
    Buffer.AddItem( fleetDefeated );
}

5) Some snippets where the serialization is actually called:

Code: [Select]
string mgrString = thisMgr.ToString();
if(debug)
ArcenDebugging.ArcenDebugLogSingleLine( "NanocaustMgrData::SerializeData got string <" + mgrString + ">", Verbosity.DoNotShow);

Buffer.AddItem(mgrString);

With:

Code: [Select]
thisMgr.SerializeTo( Buffer, IsForSendingOverNetwork );
And

Code: [Select]
//reverses SerializeData; gets the date out of the buffer and populates the variables
string mgrString = Buffer.ReadString();
if(debug)
ArcenDebugging.ArcenDebugLogSingleLine( "NanocaustMgrData::DeserializeData got string <" + mgrString + ">", Verbosity.DoNotShow);
mgr = new NanocaustMgr(mgrString);
Target[0] = mgr;

With:

Code: [Select]
Target[0] = new NanocaustMgr( Buffer, IsLoadingFromNetwork, DeserializingFromGameVersion );
And that should take care of it, though there are some other things you had that you won't need (like the to-string and from-string logic for the enum thing; by the way enum.ToString() works fine, as does Enum.Parse(), though often there are better solutions).

Sorry you had to fight all that, but at least now there's a clear example of how to do it :)
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
  • *****
  • Posts: 689
  • BadgerBadgerBadgerBadger
Re: The Nanocaust
« Reply #21 on: September 27, 2017, 10:39:31 AM »
Thanks for all the help! I really appreciate it. I've kinda taken the approach of plowing my way through a number of things the hard way, which can be frustrating for me but it will make things much easier for anyone else following along. Your examples are great in principle, but without a clear API I don't like using things I don't understand; maybe if I had a program with IntelliSense then it would all be easier, but I don't.

Also, I basically write C# as if it was C. I had no idea there would be things like enum.Parse() and enum.ToString(). So cool!


Now lets talk a bit about Nanocaust behaviour. Here's a proposal for what the Nanocaust will do in game. I want it to be pretty simple, but interesting enough to add some depth to things.

First, If you ignore the Nanocaust then it will slowly expand to cover more of the galaxy, but it won't expand enough to prevent you from winning normally. So you can turn on the Nanocaust and then ignore it and be able to play the game that way. (Note that if we get Intensifier settings, like Dyson Sphere 4/10 from AIWC, then turning up the Nanocaust will make it much harder to ignore.

Second, if you attack the Nanocaust two things will change. First, every time you attack a Nanocaust planet it will rally forces to chase you away, then if you don't destroy the counterattack fleet the Nanocaust will use that fleet to conquer a new planet (a planet leading toward your Ark, to be precise).

In addition, once attack the Nanocaust it will begin to sporadically generate a fleet and send it to chase your Ark (say once an hour, like an Exo Wave). It will also spawn a super-strong Leviathan ship to send with the fleet when it does so, and if you can destroy the Leviathan then the rest of the fleet will die immediately.

Hacking the nanocaust will mean it will send those Anti-Ark fleets against the Master Controller instead (and it won't attack you anymore).

Does that sound like a worthy addition from a gameplay point of view? It will be easy enough to have the Nanocaust do other things.

Offline keith.lamothe

  • Arcen Games Staff
  • Administrator
  • Zenith Council Member Mark III
  • *****
  • Posts: 19,339
Re: The Nanocaust
« Reply #22 on: September 27, 2017, 11:20:16 AM »
but without a clear API I don't like using things I don't understand
A reasonable approach. The flaw would be in the implication that you do understand any of the various sawblades you're already interacting with in the AIW2 code ;)

Quote
maybe if I had a program with IntelliSense then it would all be easier, but I don't.
I haven't tried http://www.omnisharp.net/ , but does it help you?

Quote
Also, I basically write C# as if it was C. I had no idea there would be things like enum.Parse() and enum.ToString(). So cool!
The built-in stuff is not necessarily the best approach to any given problem. For instance, the really central "collection of all entities in the game" is something I hacked together from eldritch bits that ought never to have been. It kind of has to be, given our very specific needs (note: what we don't need is at least as important as what we do need, in designing something like that). But I also use the built-in List<T> class heavily elsewhere, despite it not being the best performance. It just works.

On the Nanocaust behavior you describe, that does sound like a good baseline. Factions in general don't have to be majorly multi-dimensional, just interesting. In AIWC there was the Fallen Spire which was super-complex for a faction (an alternate game mode, really), but most of the others were relatively simple. The complexity came in the emergent interactions between multiple factions and the different circumstances of each game, etc.

I think that will be much more the case in AIW2, as things move towards the AI being "just another faction" (may never get quite there, but you get the idea).
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
  • *****
  • Posts: 689
  • BadgerBadgerBadgerBadger
Re: The Nanocaust
« Reply #23 on: September 27, 2017, 01:07:10 PM »
Omnisharp looks really cool! I'll have to give it a look later.

Alright, so I think I'm set up for finishing off Nanocaust 1.0 this week. Great.

This leaves me with one additional question. Art assets for the Nanocaust. What do we want to do there? I've reused some capriciously chosen models for the various ships, but I don't have any great ideas for icons or the Nanobot Constructor/Hive. While my INT and WIS are reasonably high, my ART is a -6 and has -15 on saving throws. I guess I'm kinda hoping that Arcen is willing to provide something suitable?

If you guys are willing to tackle the Art and are interested in my vision, I'd probably do the following: "Take a couple other assets, then add some weird green glowiness to them to represent the nanobots infesting the ship, then just use that." Think like the borg: https://www.youtube.com/watch?v=2nefGRGu5lo

Offline BadgerBadger

  • Hero Member
  • *****
  • Posts: 689
  • BadgerBadgerBadgerBadger
Re: The Nanocaust
« Reply #24 on: October 10, 2017, 11:37:37 PM »
After some hours of effort and frustration, I conclude that  I'm not smart enough or determined to get Omnisharp to work on my computer. I'll just rely on disassembling the DLLs.

So I'd like to have planets conquered by the Nanocaust show the planet name in the Nanocaust colour. However, the nanocaust information is all in Arcen.AIW2.External, not Arcen.AIW2.ExternalVisualization. I tried this as a test
Code: [Select]
        public void GetTextAndColorForNameText( Planet planet, out string text, out Color color )
        {
          List<Planet> nanocaustPlanets = Arcen.AIW2.External.SpecialFaction_Nanocaust.Instance.mgr.nanobotCenterPlanets;

but I get    
src/GalaxyMapDisplayModes/GalaxyMapDisplayMode_Normal.cs(60,43): error CS0234: The type or namespace name 'External' does not exist in the namespace 'Arcen.AIW2' (are you missing an assembly reference?).

Is this a weird artifact of my build environment where I call xbuild seperately for AIWarExternalCode and AIWAreExternalVisualizationCode? Am I missing something?
« Last Edit: October 10, 2017, 11:41:02 PM by BadgerBadger »

Offline keith.lamothe

  • Arcen Games Staff
  • Administrator
  • Zenith Council Member Mark III
  • *****
  • Posts: 19,339
Re: The Nanocaust
« Reply #25 on: October 11, 2017, 08:33:04 AM »
After some hours of effort and frustration, I conclude that  I'm not smart enough or determined to get Omnisharp to work on my computer. I'll just rely on disassembling the DLLs.
Sorry omnisharp was such a pain. Have you tried MonoDevelop?

Quote
Code: [Select]
        public void GetTextAndColorForNameText( Planet planet, out string text, out Color color )
        {
          List<Planet> nanocaustPlanets = Arcen.AIW2.External.SpecialFaction_Nanocaust.Instance.mgr.nanobotCenterPlanets;
To get that to work you look in AIWarExternalVisualizationCode.csproj to find:

Code: [Select]
    <Reference Include="ArcenAIW2Visualization">
      <HintPath>..\ReliableDLLStorage\ArcenDLLs\ArcenAIW2Visualization.dll</HintPath>
      <Private>False</Private>
    </Reference>

And right after that add:

Code: [Select]
    <Reference Include="AIWarExternalCode">
      <HintPath>..\AIWarExternalCode\bin\Debug\AIWarExternalCode.dll</HintPath>
      <Private>False</Private>
    </Reference>

Save the file, rebuild visualization, and the code you quoted should work fine. I've made this change for the next version, just letting know how you can do it locally in the meantime.

You don't need to include the "Arcen.AIW2.External." prefix if you add it in the using block at the top of the file.
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
  • *****
  • Posts: 689
  • BadgerBadgerBadgerBadger
Re: The Nanocaust
« Reply #26 on: October 17, 2017, 01:50:15 PM »
This code will make planet names reflect the Nanocaust's domination. A few bits aren't implemented as cleanly as possible, I suspect, but it works. Feel free to polish it.

Also, I think now that we colour the planet names depending on who controls them, and Unclaimed planets have a different colour from claimed planets, saying "(Unclaimed)" after a planet name is no longer necessary.
« Last Edit: October 17, 2017, 03:15:40 PM by BadgerBadger »

Offline BadgerBadger

  • Hero Member
  • *****
  • Posts: 689
  • BadgerBadgerBadgerBadger
Re: The Nanocaust
« Reply #27 on: October 23, 2017, 04:57:59 PM »
I tweaked some of my code to do C# generation of ships. This involved having my default "List of planets controlled by the nanocaust" no longer be a List<Planet>, but a List<ConstructorData>, where a ConstructorData includes a Planet. This list is regenerated by the PerSimStep code (it will call Clear() then rebuild the list every 20 SimSteps or so, in case a new constructor has been added or an existing one is destroyed).

The problem is that my Visualization code wants to examine this list, and there seems to be a race condition when the Visualization code is running at the same time that the list is being recreated. I was seeing consistent crashes so I added some logging code and now see
10/23/2017 4:53:28 PM   BUG: nanocaustInfectedPlanet, entry[0] is null somehow
This means that the SimStep code just ran list.Clear() and is apparently in the process of rebuilding things. I'm not sure what to do about this except to just catch such scenarios and hope that the next time the visualization thread runs it gets up to date information.


Offline BadgerBadger

  • Hero Member
  • *****
  • Posts: 689
  • BadgerBadgerBadgerBadger
Re: The Nanocaust
« Reply #28 on: November 12, 2017, 12:52:03 PM »
There's a null reference exception in the Nanocaust; I failed to initialize some variables when loading from a saved game. Here's a patch that fixes it (and adds some additional debug logging, which is currently disabled.


Offline keith.lamothe

  • Arcen Games Staff
  • Administrator
  • Zenith Council Member Mark III
  • *****
  • Posts: 19,339
Re: The Nanocaust
« Reply #29 on: November 12, 2017, 02:57:29 PM »
I'm not sure what to do about this except to just catch such scenarios
Some other solution will be necessary for this. My normal approach is to make things "implicitly threadsafe" by either making race conditions impossible-in-principle (making sure that no sim-modifying thread is started during the short-term-planning threads that check for forcefield coverage or tractors or gravity or movement or targeting, for instance), or by making the race conditions irrelevant: either thread can win, and the result may vary, but I just don't care.

Since vis runs all the time, this generally means leaving data in a safe place for vis to find, and updating that data in an atomic way. By "atomic" I mean "nothing can happen between the start and end", and thus vis will either pick up the old data or the new data but will not get some inconsistent nonsense in the middle.

So in this case, I suggest for a first naive solution having two lists:
List<ConstructorData> ForSim_NanocaustConstructorData
List<ConstructorData> ForVis_NanocaustConstructorData

Do your work on the ForSim list
Then create a new list and copy ForSim into it
Then assign that new list to ForVis
And in the vis code you do:
List<ConstructorData> constructorDataListForThisCycle=ForVis;
and then only use constructorDataListForThisCycle (NOT ForVis) as your reference to the list in the vis code.

That way your vis code might get old data, but the list will be stable.

This has two major disadvantages:
- additional heap allocation; in general we want to avoid the new operator in stuff that runs frequently (I do it a lot but during performance passes I optimize lots of them out)
- it doesn't prevent race conditions with what's stored inside the ConstructorData objects, you have to practice vigilance at that level as well

But it has the advantage of being a simple and straightforward approach.

It may be that we get to a point with the race conditions where we make a major architectural shift to simply avoid them by not allowing access to the World_AIW2.Instance or other gamestate directly from external code anymore, but instead have the Context object parameter contain an interface that grants the appropriate level of access to the appropriate parts of the gamestate. That would be a lot safer in general, but for now I'm trying the Unrestricted Lumber Mill approach ;)
Have ideas or bug reports for one of our games? Mantis for Suggestions and Bug Reports. Thanks for helping to make our games better!