Arcen Games

Games => AI War II => AI War II - Modding => Topic started by: BadgerBadger on July 18, 2017, 10:58:00 AM

Title: The Nanocaust
Post by: BadgerBadger on July 18, 2017, 10:58:00 AM
Florid Description: In some far flung corner of the galaxy, a plague of nanobots has arisen. Some say they were a last ditch effort to destroy the AI from renegade human scientists; others say they were a forgotten weapon from a long dead race. But wherever they came from, these nanobots are an insatiable plague. They will conquer planet after planet with their zombification weapons, and once they conquer a planet they will continue to build more of their race. If you've ever wondered what the AI dealing with when it wasn't fighting you, well......

Design Goal: I wanted a galaxy that felt more like a "galaxy" with lots of things going on, and less like a game board between you and the AI. I also felt like the notion that the AI isn't worried about humanity at the start would make more sense if the game contained things that could actually scare the AI. I was also inspired by https://bugtracker.arcengames.com/view.php?id=9936, and the name is borrowed from the book Century Rain.

Implementation: If you enable the Nanocaust special faction, one Nanbot Construction Center is seeded in the galaxy. It begins spawning a nanobot fleet with zombifying weapons. Once the nanobots clear one planet, they build another Nanobot Construction Center to produce even more nanobots, and then move on to the next planet.

At the moment the nanobots will conquer the galaxy outright in about 25 minutes. They need a lot of tuning to make them "Strong, but not too strong", and there are a bunch of cool additional features I have in mind (for example, if you can hack the original nanobot construction center, the nanobots will become you allies; this would be an alternative win condition). But since I have ironed out the initial bugs, I felt it was concrete enough to share my idea and ask for feedback. If people are interested in trying it out for themselves I can post the code.
Title: Re: The Nanocaust
Post by: x4000 on July 18, 2017, 11:01:24 AM
It certainly sounds fascinating!
Title: Re: The Nanocaust
Post by: BadgerBadger on July 18, 2017, 11:43:16 AM
I would be happy to take advice on scope (ie how impactful the mode is in gameplay); for example, the Nanocaust could be constrained to a small corner of the galaxy until you disturb it, and only then does it really start to spread. I was also considering putting a cap on the number of planets the Nanocaust will try to conquer (for example, it starts only wanting to take 3 planets, and then every 20 minutes it will be willing to take an additional planet).

Does this sort of thing mesh will with the gameplay idioms you have in mind for AIW2? It is your game, and I would prefer anything I do to feel like it belongs in the same universe.
Title: Re: The Nanocaust
Post by: x4000 on July 18, 2017, 01:16:04 PM
That's really going to be more up to Keith -- from a gameplay standpoint, since the second kickstarter this is now his game.  I'm here for technical pieces with the front-end regarding art and how you interact with units and all that sort of thing, but I'm not involved at all in the actual game design process on this. 

We just can't do the "two cooks" thing (we learned that years ago), and with the reduced budget it was clear I'd need to support myself in some other fashion.  And at any rate, he's been the king of the AI War universe since version 4.0 or so of the original, and it was clear that people really loved what he was doing in a lot of respects more than what I'd done previously (in terms of how he refined things and how the later-stage details shook out), so that division of labor made sense.

Right now he's traveling because of some family stuff, and so won't be commenting on much this week.  I'll be out next week, as it happens, heh.  But then we're both around and in town for the duration until 1.0.

I'll be very curious to hear his thoughts on your mechanics as well, and if/how they get integrated into the game (as a Conduct, maybe)?
Title: Re: The Nanocaust
Post by: BadgerBadger on July 18, 2017, 01:43:18 PM
Fair enough, I'll solicit Keith's opinion when he's back.

It's currently implemented as a Special Faction. The game start screen automatically shows all the Factions defined in the XML which is really convenient. The modding capabilities of the game are pretty great, and Keith deserves kudos for how easy it is to do cool and powerful things.

I modeled the Nanobot Constructor after the Dyson Sphere (so what it makes are Drones that spawn according to the build speed specified by the XML). For the nanobot ships, I made unique ships in the XML, and their guns use a DeathEffect  method I wrote. A Special Faction has a DoLongRangePlanning function (which is how the nanobots figure out what to attack next). To figure out how to place new Nanobot Constructors, the PerSimStep faction logic checks whether there are any planets that have been conquered by the Nanobots (aka the planets have A. a lot of nanobot fleet strength and B. minimal AI/human strength). It then calls GameEntity.CreateNew() on the Nanobot Constructor.

Title: Re: The Nanocaust
Post by: Draco18s on July 18, 2017, 02:01:28 PM
That's really going to be more up to Keith -- from a gameplay standpoint, since the second kickstarter this is now his game.  I'm here for technical pieces with the front-end regarding art and how you interact with units and all that sort of thing, but I'm not involved at all in the actual game design process on this. 

Just so you know, Chris, I really liked the late 3.x versions. Sure, there were balance problems or whatnot, but I liked the game. You were doing a great job with it on your own even if Keith has taken it to new heights. I don't think he'd have done as well without what you started.

So I wouldn't be so quick to discount your own opinion. If there's a faction you think might be neat, nothing is stopping you from with talking about it and having someone else (Keith, Badger, me...) from picking it up or heck, even working on it yourself like any other modder would.

Obviously you have your own project that comes first, but we still respect your opinion about AIW.

:)
Title: Re: The Nanocaust
Post by: x4000 on July 18, 2017, 02:31:23 PM
Cheers guys. :)

On the special factions thing, that of course makes perfect sense and I'm glad that worked!

Regarding my role and the 3.x days, etc -- certainly I definitely intend to give my opinions and so forth, and I don't plan on being completely absent or something like that.  However, as some of the base is maturing right now, my meddling too much at this particular point would be counterproductive for Keith, I think.  If that makes sense?  If I'd had Keith trying to design the 4.0+ stuff when I was still working on 1.0 or 2.0 back in the day, then that would have been an absolute disaster.  He and I worked together some on 3.x, with me being more the main, and then on 4.0 it was more of a split, and then after that it was pretty much all him.

Other people (anybody) saying things or suggesting things is awesome at this point already, but I have to be a bit careful what I say because I don't want to accidentally rock the boat in a way that isn't helpful for the process, if that makes sense.  I do put little notes on things like "I like this idea someone has" or "I think this one should wait" or things of that nature, but I try not to be a bottleneck in the middle of the discussion or push Keith toward specific features pre-1.0.

I would just be a really, really bad producer if I didn't let the lead designer actually do his thing at this stage in the game.  He has my respect and confidence, and so I'm trying to let him get past a certain point before I really start giving too much unsolicited feedback. Everything I've seen thus far has been proceeding well anyway, some GUI things aside which he and I have discussed at length, and which have since been altered (two builds ago).  That was more from a technical framework standpoint and how that merges into the variety of user experiences, though, so that was an okay thing for a producer to do without being a micromanager. :)

All things in their own time!
Title: Re: The Nanocaust
Post by: BadgerBadger on July 24, 2017, 04:52:53 PM
So the current behaviour is as follows.

A single nanobot constructor is seeded randomly onto the map when you enable the special faction. A  Constructor produces drone ships like the Dyson Sphere, and once a constructor has been on the map for "long enough" it will upgrade into a more powerful form that produces stronger ships.

Every "short time interval", a constructor will send a few ships to attack a randomly chosen adjacent non-Nanobot world (if all the adjoining worlds are controlled by Nanobots, the ships on a planet just chill).

Every "longer time interval", the Nanobots will frenzy. Some ships are taken from each nanobot planet and they all go to attack a nearby planet. If they destroy the human or AI defenses, a new Constructor will be built and the controller will be destroyed (so the AI knows to try to recapture it).

There are a number of cool enhancements that can be made (periodic "Kill the King" frenzies that just bum rush either the AI master controller or the Ark. Making the Nanocaust hackable. Having a mini-frenzy whenever a constructor is destroyed. Adding some additional Scary nanobot ships), but I wanted to sound people (cough Keith cough) as to what they think of this as a basic approach/whether this idea sounds good to them anyway.
Title: Re: The Nanocaust
Post by: keith.lamothe on July 24, 2017, 05:18:32 PM
Sounds great to me :)
Title: Re: The Nanocaust
Post by: BadgerBadger on July 24, 2017, 11:39:31 PM
For your testing pleasure, I give you.... The Nanocaust!

I'm uploading a tarball with 7 xml files and one .cs file, which contains the faction in a currently working and running state. The forum only allows four attachments at a time, hence the requirement for a tarball (On windows, I think WinZip or 7zip should handle a tarball). I applied these files to a build of .502 and it seemed to work fine.

The xml files are named to let you know which directory in the XML code to put it in AIWar2/GameData/Configuration/<correct directory based on xml file name>.

The Nanocaust.cs file should go in AIWarExternalCode/src/SpecialFactions, then you will need to recompile the external code.

Fair warning, the Nanocaust probably won't interfere with the first hour or two of gameplay at the moment. So if you just turn it on and play without cheats or map reveal, you may not even realize it is there. But you'll find out eventually ;-)
Title: Re: The Nanocaust
Post by: keith.lamothe on July 25, 2017, 01:08:32 PM
(sound of several trillion microscopic synthesizers humming "The Imperial March" in unison)

https://youtu.be/Sij3mK1O454



Many thanks :)
Title: Re: The Nanocaust
Post by: BadgerBadger on July 27, 2017, 01:04:34 PM
For Keith, I present some nanocaust improvements. Code is tidied up in many places and should be more satisfactory. Tags and FInts are used now (fancy!). Some balance improvements; frenzies will start small but grow when they fail to capture a planet.

The Nanocaust now will carve out a small but steadliy growing empire for itself; it could have perhaps 6 or 7 planets per hour instead of "every planet in an hour", which is a much more sensible growth rate.

Also the initially spawned Constructor is now named the Hive; this will make it easier to implement Hacking later.
Title: Re: The Nanocaust
Post by: keith.lamothe on July 27, 2017, 01:34:48 PM
For Keith, I present some nanocaust improvements.
Thanks, just tested those and they're in for 0.504 :)

I added an extra chunk of code to the game-second-1 logic to kill any guardians present on the planet, as otherwise if the hive is seeded on a MkIV planet it's likely to get killed before it can take over that first planet.
Title: Re: The Nanocaust
Post by: BadgerBadger on July 27, 2017, 02:12:48 PM
Thanks! The smiting of guardians is a great tweak and will hopefully let me simplify some of my early game logic. Thanks!
Title: Re: The Nanocaust
Post by: BadgerBadger on July 27, 2017, 11:33:13 PM
Quick question: if I'm not inside the Nanocaust code, how can I refer to the data inside the SpecialFaction_Nanocaust.Instance? 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.
Title: Re: The Nanocaust
Post by: keith.lamothe 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.
Title: Re: The Nanocaust
Post by: BadgerBadger 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.
Title: Re: The Nanocaust
Post by: BadgerBadger on August 24, 2017, 01:15:57 PM
This is basically a full internal rework of the Nanocaust. Changes include

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.
Title: Re: The Nanocaust
Post by: TheVampire100 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.
Title: Re: The Nanocaust
Post by: BadgerBadger 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.
Title: Re: The Nanocaust
Post by: keith.lamothe 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 :)
Title: Re: The Nanocaust
Post by: BadgerBadger 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.
Title: Re: The Nanocaust
Post by: keith.lamothe 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/ (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).
Title: Re: The Nanocaust
Post by: BadgerBadger 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
Title: Re: The Nanocaust
Post by: BadgerBadger 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?
Title: Re: The Nanocaust
Post by: keith.lamothe 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.
Title: Re: The Nanocaust
Post by: BadgerBadger 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.
Title: Re: The Nanocaust
Post by: BadgerBadger 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.

Title: Re: The Nanocaust
Post by: BadgerBadger 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.

Title: Re: The Nanocaust
Post by: keith.lamothe 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 ;)
Title: Re: The Nanocaust
Post by: BadgerBadger on November 12, 2017, 05:21:05 PM
Ah, that concern was from before you allowed a faction to have Influence over a planet. It's not an issue anymore.
Title: Re: The Nanocaust
Post by: keith.lamothe on November 12, 2017, 09:09:27 PM
Ah, that concern was from before you allowed a faction to have Influence over a planet. It's not an issue anymore.
Sorry, for some reason I thought that was today's post. Well, for future reference it's a useful technique :)
Title: Re: The Nanocaust
Post by: BadgerBadger on November 15, 2017, 12:11:08 AM
I found another Nanocaust crash when reloading a game. Note that this file doesn't have my previous save game fix, so merging may take slightly more work. You will want both fixes.

Title: Re: The Nanocaust
Post by: BadgerBadger on November 19, 2017, 06:46:17 PM
Here's a patch to prevent all the Nanocaust forces from showing up as Thread (except if that nanocaust force is trying to kill the human Ark).