Okay, even with the ability to add on to a list of children, and even with the Principle of Last Surprise, there still are some things that can't be accomplished. For example, what if you wanted to replace a sector? The sectors child of the Map object will take more than one item easily, so the Principle doesn't help there. You'd have to remove the sector and then add onto it, but of course you can't do that.
Until now!
You might be wondering why the "child.add" syntax is like that. Specifically, why there's an "add" there at all, considering that's all we've done to date. And you'd be right to wonder: there are other operations that can be used in place of 'add'. The first and most obvious is the opposite: remove. You can use the remove command to remove a given child from the list. For example, here's a plugin that will remove the opening dialog from the tutorial (by removing the mission from the 'special' child of the sector):
# No Tutorial # #(A plugin for 'How to Play TraderMissions') # This plugin removes the initial mission compelling you to land # on Earth. Sol <sector> { child special.remove Tutorial; }
Since 'Tutorial' was the only object in the list, the list reverts to being empty. Note that this removes the first matching item it finds: in order to remove all instances of an object from a child, you use the keyword "(all)":
# No Tutorial # #(A plugin for 'How to Play TraderMissions') # This plugin removes the initial mission compelling you to land # on Earth. Sol <sector> { child special.remove (all) Tutorial; }
If you ask that an object be removed which is not in the list, nothing will happen. Likewise if the list is empty and you request removal. The only exception is that the object referred to in the remove statement must exist at the time the statement is parsed. Remove is not like "Add" in this respect: Add will queue adds until a previously undeclared object is created, remove will violently end your program if you attempt such a thing. This is not likely to be a problem in plugins, since you're likely to be removing objects in the scenario itself, which will have been parsed and loaded already.
Also note that remove only removes the specified object from that child. If you want to remove something from the game entirely (a sector, for example), you need to remove every reference to it.
Earlier I mentioned that the only way to replace something would be to remove it and then add on another object? While that is one way to do it, you could also use the 'substitute' keyword, which is a shortcut way of doing the above with the added benefit that it preserves order: If you replace something which is second in a list of five with something else using the 'substitute' keyword, the replacement will be put in the second slot in place of the original. As order is important in a number of children, this is quite a useful feature.
How to do it? Let's say we wanted to replace that pesky Vollox sector with a more easy to deal with empty sector:
# No Vollox #(A plugin for 'How to Play TraderMissions') # This plugin replaces Vollox with a far friendlier system Empty <sector> { field mapx = 0.0; field mapy = 150.0; field title = "Vollox"; field description = "A peaceful empty sector."; child owner.add Aliens; child links.add Sol; } OurMap <map> { child sectors.substitute Vollox (with) Empty; } Sol <sector> { child links.substitute Vollox (with) Empty; } EndingScoutVollox <condition> { child playerAt.substitute Vollox (with) Empty; }
Should you want to replace every instance of an object within a child, the substitute command also has an (all) clause.
The substitute directive is like 'remove' in that it requires the object being replaced (and, in the case of 'substitute', the object that we're replacing it with) to already exist at the time the line is parsed, and also in that neither is global. For instance, in order to substitute Vollox with Empty, we had to find every reference to Vollox and change it to Empty ourselves.
As an example of how to write a more complicated plugin, let's expand the above plugin to create a neutral organization that owns the empty sector:
Nobody <organization> { field title = "Nobody"; list attitudes = { 0, 0, 0, 0}; }
The problem with this is that we can't just change the 'owner' child of Empty to point to Nobody, we have to also put Nobody in the list of organizations, as well as add onto the lists of other organizations to indicate how they feel about Nobody. And in order to do that, we'll have to add on to the 'attitudes' list of everyone else. Luckily, this is easy: though lists are just lists of numbers, they behave more like children in that the default mode of operation for a list is to add onto the end of it. Thus the plugin looks like this:
# No Vollox #(A plugin for 'How to Play TraderMissions') # This plugin replaces Vollox with a far friendlier system Nobody <organization> { field title = "Nobody"; list attitudes = { 0, 0, 0, 0}; } gamestate <gamestate> { child orgs.add Nobody; } Aliens <organization> { list attitudes = { 0 }; } Humans <organization> { list attitudes = { 0 }; } Player <organization> { list attitudes = { 0 }; } Empty <sector> { field mapx = 0.0; field mapy = 150.0; field title = "Vollox"; field description = "A peaceful empty sector."; child owner.add Nobody; child links.add Sol; } OurMap <map> { child sectors.substitute Vollox (with) Empty; } Sol <sector> { child links.substitute Vollox (with) Empty; } EndingScoutVollox <condition> { child playerAt.substitute Vollox (with) Empty; }
As you can see, you only had to specify one item for each organization's list.
Unfortunately, there's no way to selectively remove or replace individual elements in a list: the only option is to specify an entirely new list, which you can do with the keyword 'new':
Player <organization> { list attitudes = new { 0, 0, 0, 0 }; }
This is less flexible, but there are few areas where lists are used, so their modification should not be required very often.