I forget sometimes that I'm not talking to my coworkers. So here comes the long winded explanation that I was trying to avoid before.
It does seem like the XML would be less flexible. You have pointed out that the main impediment to modding is C++ classes. So lets get rid of those all together. With an XML interface those would not need exist. Instead there would just be a set of hard coded type independent functions that are called. This needs a change in thinking, so instead of thinking of a class as an object, think of a class as a definition of what an object should be. I'll show you an example by converting freeER's bomber. Something to keep in mind as you read this is that I have not actually read the source code for the game, this is just my opinion of how it should work and is based on my own experiences as a professional programmer.
So lets assume that these are some of the global functions available to modders.
Code: Select all
create_group(char *name, char* parent, FILE *xml_file)
/*
Creates a group and adds it to the internal group tree structure
if there is a parent string then add this group as a child node of parent
*/
create_item(char *name, FILE *xml_file)
/*
Creates an item with the given name is stores it and the xml pointer in an internal item tree
*/
create_technology(char *name, FILE *xml_file, bool locked)
/*
Create a technology and add it to the tech tree
*/
unlock_technology(char *name, FILE *xml_file)
/*
traverses the technology tree and unlocks the tech pointed to by *name
*/
create_recipe(int *item, FILE *xml_file)
/*
Create a recipe for the item pointed to by the item pointer and set the ingredients for it by
parsing the XML
*/
consume_fuel(float amount, FILE *xml_file)
/*
Consumes an amount of type independent fuel
*/
emission(float amount, FILE *xml_file)
/*
Emits an amount of pollution
*/
on_key_press(int key, FILE *xml_file)
/*
First this function check to see if the key pressed is a base function (like pressing "e"
will always open the crafting screen). next the function searches through the item
tree and looks at the XML file for <on-keypress> tags. If the key is a match then
run the associated lua script.
*/
accelerate(float energy, float rate, float friction, int weight)
/*
Any item with the "pushable" flag will call this to both accelerate and decelerate.
We can get away with only one function because deceleration can be expressed
as acceleration in reverse
*/
place_item(int *item, int x, int y, FILE xml_file)
/*
Places an item, at x,y , that is described by *item
*/
animate(FILE *spritesheet, int frameWidth, int frameHeight)
/*
This function will be called each tick for every item in the scene graph. it will update
it's current frame and draw it to the back buffer.
*/
So lets start with the easy part and define our groups that will appear on the crafting screen.
Code: Select all
<group name="bomber" icon="__bomber__/graphics/bomber.png" order=4>
<subgroup name="planes" order=1 \>
<subgroup name="bombs" order=2 \>
</group>
No conversion to or knowledge of lua is needed at this point since there is no complex logic to execute. Instead, when the game is loaded it checks the XML in the mods directory. When the "group" tag is encountered the create_group function is called for each group and subgroup tag. No classes are extended and no new classes are created or inherited. This is where the memory thing comes into play. It takes less memory and processing time to parse a file then to create an instance of a group class. It's just a few fractions of a difference, but those fractions add up over time and increase with the complexity and inheritance level.
Now lets move on to something a little more complex and create a bomb.
Code: Select all
<item name="bomb" icon="__bomber__/graphics/bomb.png" subgroup="bombs" locked="true">
<stack size = 5>
<recipe>
<ingredient type="electronic-circuit" amount=10 />
<ingredient type="iron-gear-wheel" amount=10 />
<ingredient type="iron-plate" amount=20 />
</recipe>
<flags>
goes-to-main-inventory
</flags>
</item>
Going step by step, the first thing that happens at load time is the XML parser encounters and "item" tag. The program then calls the create_item function which creates an item of name "bomb" the uses the icon located at "__bomber__/graphics/bomb.png" and then places a pointer in the item tree in the "bombs" node with the parent of "bomber". Notice there is no type. Because there are no hard coded classes there is no type to extend.
Next the parser encounters the "recipe" tag and calls the create_recipe function. The recipe and it's ingredients are saved in a table.
Now without using lua or even having any know of lua or the internal data structures of C++ we have just created a completely dynamic class the can be created at runtime instead of compile. It may not look like a C++ class and that's because it's not a traditional class definition. However, when you get right down to it, a class in C++ is nothing more than a highly restrictive data structure. Knowing this means if we define our own data structure, we can in essence create a class at runtime that would look something like this:
Code: Select all
/* Item Tree */
( items )
/ | \
A B C
|
(bomber)
/ \
(bombs) (planes)
/
[bomb] <-- Item struct
locked = true
stack = 5
recipe =
[
[*electronic-circuit, 10],
[*iron-gear-wheel, 10],
[*iron-plate, 20 ]
]
enum flags = goes to main inventory
Now lets move on to something more complex: the bomber. we start off the same with the same tags.
Code: Select all
<item name="bomber" icon="__bomber__/graphics/bomber.png" subgroup="planes" locked="true">
<recipe>
<ingredient type="iron-stick" amount=50 />
<ingredient type="electronic-circuit" amount=50 />
<ingredient type="iron-gear-wheel" amount=50 />
<ingredient type="iron-plate" amount=200 />
</recipe>
<flags>
goes-to-quickbar
</flags>
<technology name="bombing" icon="__bomber__/graphics/bomber_tech.png" prerequisite="flying">
<effects>
<unlock recipe="bomber">
<unlock recipe="bombs">
</effects>
<ingredients count=100 time=20>
<ingredient type=science-pack-1 count=2 />
<ingredient type=science-pack-2 count=1 />
<ingredient type=science-pack-2 count=1 />
</ingredients>
</technology>
</item>
So now we have created a bomber class and just like before it's been placed in the item tree. Like the bomb, It's locked and we can't place it yet, but I'll get to that in a minute. So again, at runtime, the file is parsed. Same as the bomb, the bomber an entry is created in the item tree. Although this time there is a technology. When the parser encounters this tag it will call a create_technology function which in turn will create a new entry in the tech tree with the parent entity of "flying".
Now here we are in the game. Our new items have been created and placed in the crafting menu int their own tab. We can't see them yet because they are locked. So lets do our research the same way it's always been done. When the research is done, the internal functions of the game go back to the XML file, skips straight to the "technology" tag, and the parses only this tag looking for the "effects" tag. Inside the "effects" tag there are two "unlock" tags. so for each of the unlock tags the parser calls the unlock_technology function. Our bomber and bomb is now unlocked and able to be crafted. Let's craft the bomber. We click the craft button and the craft_item function traverses the item tree, finds the bomber, deducts all the items needed for crafting, and crafts the item which is placed on our quickbar because our flag mask has the bit for goes-to-quickbar set to 1. Now we have the item in our quickbar but when we try to place it we can't because there is no place result.
So then lets get back to our XML and create one.
Code: Select all
<place-result>
<inventory type="storage" size=80 />
<flags>
pushable,
placeable-neutral,
player-creation
</flags>
<minable time=1 result="bomber" />
<health max=2000 />
<corpse type="medium-remnants" />
<selection-box top-left="-0.7, -1.2" bottom-right="0.7, 1.2" />
<acceleration per-energy=0.48 energy-consumption="6W" friction=0.01 weight=250 breaking-speed=0.09>
<inventory type="fuel" size=2 />
<energy-source type="burn" effectivity=0.5 />
<emission type="pollution" amount=0.01 />
<smoke deviation="0.1, 0.1" frequency=0.5 position="0, 0" vertical-speed=0.05 >
<start frame=3 deviation=4 speed=0 speed-deviation=5 />
</smoke>
<animation filename="__bomber__/graphics/bomber-sheet.png" priority=1>
<line length = 1 />
<frame width=211 height=211 shift="0.5, 0" symmetrical="false" directions=9 />
<rotation speed = 0.01>
</animation>
<inventory type="storage" size=80 />
<on-keypress key="q" script="__bomber__/control/drop_bomb.lua" />
</place-result>
Now we go back into our game. This time, because there is a "place-result" tag to parse, we can place the item in the world. When we do the game opens the XML file and skips to the "place-result" tag and then starts parsing it all the while looking for the relevant parts. At this point we can now start to think of the placed item as an object without a class. This object exists only in the scene graph with it's own unique properties. What are the properties of this object? The first tag the parser come to is "inventory". So a storage type inventory is created with a size of 80. Next the parser sees the "flags" tag and sets the bits in the flag mask for pushable, placeable-neutral, and player-creation to 1. The parser skips the "minable" tag. This is because the item is being placed not mined. Next is the parser hits the "health" tag and sets health to max (2000). Next comes the selection box, which just calls a function to set the data values in a struct. Acceleration is also ignored because we are not accelerating. The next "inventory" tag tells the parser to call a function that creates a fuel inventory of size 2. We aren't moving any where so we don't need to know about the energy source. The animation tag is read just so the starting frame can be set. The parser then moves on to the final inventory and calls a function to create a storage inventory of size 80. The "on-keypress" tag is ignored since no one pressed a key. With the parsing now complete a functioning struct now exists in memory and our bomber appears on screen and it's current state is now idle. Now we click on the plane and, because the parser found 2 inventory tags, we should see a fuel inventory and a storage inventory. We then load the plane with fuel and bombs and hop in. Soon as we press the w key the plane starts to accelerate now the game looks at the XML again and skips to the "accelerate" tag. Now the "acceleration" tag has the "energy-consumption" property, so this tells the parser to look for a source of energy. The "energy-source" tag says that this object burns fuel to accelerate, so the consume_fuel function is called by the parser when that function exits we return to the accelerate function and now the plane begins to accelerate forward. The "animation" and "smoke" tags are parsed and relevant functions are called to animate the place and smoke. The parser also came across an "emission" tag, so every frame the emission function is called and pollution is created. Now we're flying around and we see some biters that we want to bomb. The way the game exists now the closest we can come to the is just waiting for 5 biters to happen to move into range. This time though we have an "on-keypress" tag, so now all we have to do is press a key, in this case q, and the game calls the on_keypress function. Since q does not have a predefined behavior, the XML parser checks the mods for the "on-keypress" tag and executes the associated script. This is the only place where a lua script should exist. "Why?" you ask. Well the answer is simple. The game does not have a drop_bomb function. We need new functionality to drop a bomb. This is also the proper use of a scripting language.
My larger point is this:
I have nothing against lua or C++ classes but every thing has a time and a place. Right now the existing modding system is not very extensible(No XML pun intended). The main reason is the true power of a language like C++ is not being utilized. Everything outside of contorl.lua basically amounts to this:
Code: Select all
bomber = new car();
bomber.name = "bomber";
bomber.stack_size = 1;
bomber.recipe_item1 = 50 iron sticks;
bomber.recipe_item2 = 50 electronic-circuit;
etc
Calling what happens now moding is a bit of a misnomer. To explain the way things are now, lets pretend that Factorio has this class:
Code: Select all
class Apple {
protected
char *m_color;
public
Apple(const char *color) {
strcpy(m_color, color);
}
set_color(const char *color, FILE *image) {
strcpy(m_color, color);
}
eat_apple()
~Apple()
};
Now lets say we wanted to eat a red apple. Because there is an apple class we can do this:
Code: Select all
(Apple = new Apple("red", apple_pic)).eat_apple();
Now lets eat a green apple.
Code: Select all
(Apple = new Apple("green", apple_pic)).eat_apple();
Now I'm tired of eating apples and I want a banana. Factorio only has apples so I have to "mod" the game to create a banana. I can't create a new class so I write out all the lua code that describes a banana, but there now exists a problem. What would actually happen in C++ code is:
Code: Select all
(Banana = new Apple("yellow", banana_pic)).eat_apple();
It looks like a banana, It's the same color as a banana, but we're still eating apples. No actual banana ever existed in this mod and because of the highly restrictive nature of hard coded classes, and there never will be.
Now lets try things my way. Instead of a class we would have something like this:
Code: Select all
struct Internal_Item_Structure {
const char *name;
const char *color;
FILE *pic;
} item;
void eat_item(item *item);
item *apple = allocate(sizeof(item));
strcpy(apple.name, "Apple");
strcpy(apple.color, "Red);
apple.pic = fopen("graphics/apple_pic.png");
Now we have the same as before but instead of an apple class we just have a struct of item to work with. When we eat the apple, the resulting code is:
Now lets try to create a banana. We create an XML that describes the banana.
Code: Select all
<item name="Banana" pic="__banana__/graphics/banana_pic.png">
<color>yellow</color>
</item>
When the XML parser looks at the XML file the resulting behavior in C++ is this:
Code: Select all
item *banana = allocate(sizeof(item));
strcpy(banana.name, "banana");
strcpy(banana.color, "yellow");
banana.pic = fopen("__banana__/graphics/banana_pic.png");
Now that the creation of a new item is complete, lets try to eat the banana;
This time there are no apples anywhere in sight. We've succeeded in eating a banana.
Now in the real factorio the item struct would be quite large and encompass every variable that any item in the game could possibly need. However, the functions available to the modders would be global and type independent. The sky is the limit not the preexisting classes.