A lot of game functionality (for example, several of the mission types) is implemented as script files, written in Lua. The reference for the Lua game API can be browsed at http://eatenbyagrue.org/f/pioneer/codedoc/files/LuaGame-cpp.html.
This page describes a few basic guidelines to use when designing interfaces to expose parts of the game engine to the Lua environment. Remember, these are guidelines, not rules. If you need to do something different, you can, but please think hard about it and/or talk to someone else about if first.
API users are not programmers
Well they might be, but for maximum reach we need to make sure that its possible for anyone with half an idea to get something off the ground, even if they aren't "proper" programmers. Expect that users will borrow heavily from other examples, cut and pasting without necessarily knowing what the code does. They probably won't have a good understanding of performance consideration. Build your interface in such a way that it protects users and the game engine from each other.
What, not how
Building on the above point, design the API such that the user is expressing what they want, not how to achieve what they want. Consider the example of spawning a ship. A typical sequence to achieve this in the engine might be:
- Select a ship type
- Create a ship
- Set its frame to that of the desired body "near" which the ship should spawn
- Set an appropriate position
- Set its velocity to 0
- Add it to the world
The consider how this has been exposed to Lua
- Four seperate methods: SpawnShip, SpawnShipNear, SpawnShipDocked and SpawnShipParked.
- Each takes the name of a ship type and a simple "distance from" representation to determine where to spawn.
With these methods, there is no way for a script author to screw up the game state. Yes, it potentially costs some flexiblity, but the more flexible an interface is the more difficult it becomes to understand and maintain.
You should attempt to cover the majority of use cases with the smallest interface possible. Users can always ask for the interface to be extended if it doesn't meet their needs.
Think carefully about API stability
For new interfaces, there are really only two states that make sense: stable and experimental. Aim for stable in your first cut if you can, but remember that stable is a commitment to support that interface for at least two releases. If you don't feel confident that that can be achieved, make it experimental instead.
This is particularly important at this early stage of development when so much of Pioneer's internals are in flux. Its for this reason that anything to do with equipment, politics, commerce and crime is marked experimental. We know these systems will change.
Don't make undocumented interfaces. People will find it and will use it. Use the existing docs as an example, and document your interface clearly and simply. Remember the above: your audience are not programmers, and you should not burden them with how things work.
Try harder to succeed, fail gracefully
Currently a hard error from the Lua error causes Pioneer to crash. Don't program hard assertions (Lua's error or C's luaL_error) unless they're very necessary.
Try hard to succeed if you can. That's not to say you should allow things that are not documented (we want to encourage code correctness after all) but we should also try to be forgiving where things are complicated to make life easier for users.
A good example of this is the DistanceTo method that both StarSystem and SystemPath have. Both can take either a StarSystem or a SystemPath as their argument. Technically SystemPath is the right place for this method, as that class is the one that holds the sector coordinates for the system used to compute the distance. The difference between StarSystem and SystemPath isn't always clear to the user, and sometimes its just going to be easier to compare two systems directly rather than extracting their paths first.