Getting Started with Trosnoth Development
The purpose of this page is to help new Trosnoth coders begin to understand the inner workings of Trosnoth, and the best practices when contributing code.
Code style
New Trosnoth code should adhere to PEP 8 with a maximum line length of 99 characters. When naming things, variables and methods should be lowercase_with_underscores
, classes should be UpperCamelCase
and constants should be UPPERCASE_WITH_UNDERSCORES
. Files should generally be lowercase
with no underscores unless they're needed to avoid ambiguous readings.
Twisted Reactor
Trosnoth currently uses Twisted to provide its main loop. You don't need to understand everything about Twisted to work on Trosnoth, but there are a few things worth noting:
- Twisted is an asynchronous framework. This means that you can start a task and say, "When this finishes, call this function." To represent long-running tasks, Twisted uses
Deferred
objects fromtwisted.internet.defer
. To indicate what should happen when a task completes or fails, you used.addCallback(callbackFunction)
ord.addErrback(errorHandler)
respectively. - Twisted doesn't use threading for its multitasking, but instead uses cooperative multitasking. The up-side of this approach is that you don't have to worry about locks and semaphores: you're always guaranteed that your code will run atomically and uninterrupted. The down-side is that chunks of code that take a long time will block the whole process. Therefore never use
time.sleep()
, and avoid blocking I/O calls (try to use Twisted's asynchronous alternative). If you have to perform complex calculations, try to find ways to break them up.pause - Twisted provides time-related functions like
reactor.callLater()
andtwisted.internet.task.LoopingCall()
. These are useful if you want your timing to be based on real time (e.g. for animations in the UI). But what you care about is game time, please useworld.callLater()
orworld.sleep()
. - Throughout the code, you might see functions decorated with
@defer.inlineCallbacks
. When reading these functions, please note that:-
defer.returnValue(...)
will return from the function -
yield d
will interrupt the function and return control to the Twisted reactor until theDeferred
taskd
has finished - calling a function decorated as
inlineCallbacks
will return aDeferred
object
-
Glossary
Here are some terms you may come across in the Trosnoth code.
- world or universe: this encapsulates the state of an entire Trosnoth game, including details of the current map, and locations of all units
- unit: anything that moves, including players, shots, grenades and stars
Internal Structure
If you're planning to delve deep into the Trosnoth internals, you may come across some of the following interfaces. Here's a quick explanation.
Message classes
Because Trosnoth operates over the network, information about player actions and changes in universe state need to be able to be serialised. To do this, we use Message
classes (defined in trosnoth/messages/*.py
). Messages typically subclass one of:
-
AgentRequest
if it represents a request being sent from a client to the server -
ServerCommand
if it represents a command being sent from the server to one or all clients -
ClientCommand
if the same message class will be used for both the client to server request, and the subsequent command that informs all clients that this event has happened -
ServerResponse
if it represents a response that is sent back to the client who send anAgentRequest
, usually to inform them why their request was denied
If you create a new Message
class, it is important to add it to trosnoth.network.client.clientMsg
if it's ever going to be received by a client, and to trosnoth.network.server.serverMsg
if it's ever going to be received by a server. This tells the client and server how to decode this message kind.
Game / Agents
A Game
represents a Trosnoth game. It might be a LocalGame
, which is being controlled within this process, or a RemoteGame
, which is talking over the network to get its instructions.
A Game
may have a number of **Agent
**s connected to it. An Agent
represents something or someone who wants to receive game events, and who may want to control a player. For instance, the Trosnoth user interface is represented by a subclass of Agent
, which sends requests to the game whenever the user presses a key, clicks to shoot, etc.. Trosnoth bots communicate with the the game using the AIAgent
class, which is also a subclass of Agent
. Within a Trosnoth server process, an Agent
subclass is also used to represent players over the network.
Regardless of the class of Game
or Agent
, the same interface is used to send messages between them. This interface is defined in the base Game
and Agent
classes.