Author
Topic: The Nanocaust (Read 4501 times)

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.

Logged

Have ideas or bug reports for one of our games? Mantis for Suggestions and Bug Reports. Thanks for helping to make our games better!

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.

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.

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:

//note that I have to use different delims for Fleet and NanocaustMgrprivate char exportDelim = '#';private char listDelim = ','; //for exporting/importing listspublic override string ToString(){ //Exports all the information for serialization in a string form

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).

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

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

Logged

Have ideas or bug reports for one of our games? Mantis for Suggestions and Bug Reports. Thanks for helping to make our games better!

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.

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).

Logged

Have ideas or bug reports for one of our games? Mantis for Suggestions and Bug Reports. Thanks for helping to make our games better!

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

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

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?

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.

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 see10/23/2017 4:53:28 PM BUG: nanocaustInfectedPlanet, entry[0] is null somehowThis 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.

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.

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_NanocaustConstructorDataList<ConstructorData> ForVis_NanocaustConstructorData

Do your work on the ForSim listThen create a new list and copy ForSim into itThen assign that new list to ForVisAnd 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

Logged

Have ideas or bug reports for one of our games? Mantis for Suggestions and Bug Reports. Thanks for helping to make our games better!