# Protocols ``` Internet│ Protocol ┌─────┐ │ | ┌──────┐ │Text │ │ ┌──────────┐ ┌────────────┐ ┌─────┐ │Client◄────┤JSON ├─┼──┤outputfunc◄────┤commandtuple◄───┤msg()│ └──────┘ │etc │ │ └──────────┘ └────────────┘ └─────┘ └─────┘ │ │Evennia ``` The _Protocol_ describes how Evennia sends and receives data over the wire to the client. Each connection-type (telnet, ssh, webclient etc) has its own protocol. Some protocols may also have variations (such plain-text Telnet vs Telnet SSL). See the [Message Path](./Messagepath.md) for the bigger picture of how data flows through Evennia. In Evennia, the `PortalSession` represents the client connection. The session is told to use a particular protocol. When sending data out, the session must provide an "Outputfunc" to convert the generic `commandtuple` to a form the protocol understands. For ingoing data, the server must also provide suitable [Inputfuncs](../Components/Inputfuncs.md) to handle the instructions sent to the server. ## Adding a new Protocol Evennia has a plugin-system that add the protocol as a new "service" to the application. To add a new service of your own (for example your own custom client protocol) to the Portal or Server, expand `mygame/server/conf/server_services_plugins` and `portal_services_plugins`. To expand where Evennia looks for plugins, use the following settings: ```python # add to the Server SERVER_SERVICES_PLUGIN_MODULES.append('server.conf.my_server_plugins') # or, if you want to add to the Portal PORTAL_SERVICES_PLUGIN_MODULES.append('server.conf.my_portal_plugins') ``` > When adding a new client connection you'll most likely only need to add new things to the Portal-plugin files. The plugin module must contain a function `start_plugin_services(app)`, where the `app` arguments refers to the Portal/Server application itself. This is called by the Server or Portal when it starts up. It must contatin all startup code needed. Example: ```python # mygame/server/conf/portal_services_plugins.py # here the new Portal Twisted protocol is defined class MyOwnFactory( ... ): # [...] # some configs MYPROC_ENABLED = True # convenient off-flag to avoid having to edit settings all the time MY_PORT = 6666 def start_plugin_services(portal): "This is called by the Portal during startup" if not MYPROC_ENABLED: return # output to list this with the other services at startup print(f" myproc: {MY_PORT}") # some setup (simple example) factory = MyOwnFactory() my_service = internet.TCPServer(MY_PORT, factory) # all Evennia services must be uniquely named my_service.setName("MyService") # add to the main portal application portal.services.addService(my_service) ``` Once the module is defined and targeted in settings, just reload the server and your new protocol/services should start with the others. ### Writing your own Protocol ```{important} This is considered an advanced topic. ``` Writing a stable communication protocol from scratch is not something we'll cover here, it's no trivial task. The good news is that Twisted offers implementations of many common protocols, ready for adapting. Writing a protocol implementation in Twisted usually involves creating a class inheriting from an already existing Twisted protocol class and from `evennia.server.session.Session` (multiple inheritance), then overloading the methods that particular protocol uses to link them to the Evennia-specific inputs. Here's a example to show the concept: ```python # In module that we'll later add to the system through PORTAL_SERVICE_PLUGIN_MODULES # pseudo code from twisted.something import TwistedClient # this class is used both for Portal- and Server Sessions from evennia.server.session import Session from evennia.server.portal.portalsessionhandler import PORTAL_SESSIONS class MyCustomClient(TwistedClient, Session): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.sessionhandler = PORTAL_SESSIONS # these are methods we must know that TwistedClient uses for # communication. Name and arguments could vary for different Twisted protocols def onOpen(self, *args, **kwargs): # let's say this is called when the client first connects # we need to init the session and connect to the sessionhandler. The .factory # is available through the Twisted parents client_address = self.getClientAddress() # get client address somehow self.init_session("mycustom_protocol", client_address, self.factory.sessionhandler) self.sessionhandler.connect(self) def onClose(self, reason, *args, **kwargs): # called when the client connection is dropped # link to the Evennia equivalent self.disconnect(reason) def onMessage(self, indata, *args, **kwargs): # called with incoming data # convert as needed here self.data_in(data=indata) def sendMessage(self, outdata, *args, **kwargs): # called to send data out # modify if needed super().sendMessage(self, outdata, *args, **kwargs) # these are Evennia methods. They must all exist and look exactly like this # The above twisted-methods call them and vice-versa. This connects the protocol # the Evennia internals. def disconnect(self, reason=None): """ Called when connection closes. This can also be called directly by Evennia when manually closing the connection. Do any cleanups here. """ self.sessionhandler.disconnect(self) def at_login(self): """ Called when this session authenticates by the server (if applicable) """ def data_in(self, **kwargs): """ Data going into the server should go through this method. It should pass data into `sessionhandler.data_in`. THis will be called by the sessionhandler with the data it gets from the approrpriate send_* method found later in this protocol. """ self.sessionhandler.data_in(self, text=kwargs['data']) def data_out(self, **kwargs): """ Data going out from the server should go through this method. It should hand off to the protocol's send method, whatever it's called. """ # we assume we have a 'text' outputfunc self.onMessage(kwargs['text']) # 'outputfuncs' are defined as `send_`. From in-code, they are called # with `msg(outfunc_name=)`. def send_text(self, txt, *args, **kwargs): """ Send text, used with e.g. `session.msg(text="foo")` """ # we make use of the self.data_out(text=txt) def send_default(self, cmdname, *args, **kwargs): """ Handles all outputfuncs without an explicit `send_*` method to handle them. """ self.data_out(**{cmdname: str(args)}) ``` The principle here is that the Twisted-specific methods are overridden to redirect inputs/outputs to the Evennia-specific methods. ### Sending data out To send data out through this protocol, you'd need to get its Session and then you could e.g. ```python session.msg(text="foo") ``` The message will pass through the system such that the sessionhandler will dig out the session and check if it has a `send_text` method (it has). It will then pass the "foo" into that method, which in our case means sending "foo" across the network. ### Receiving data Just because the protocol is there, does not mean Evennia knows what to do with it. An [Inputfunc](../Components/Inputfuncs.md) must exist to receive it. In the case of the `text` input exemplified above, Evennia alredy handles this input - it will parse it as a Command name followed by its inputs. So handle that you need to simply add a cmdset with commands on your receiving Session (and/or the Object/Character it is puppeting). If not you may need to add your own Inputfunc (see the [Inputfunc](../Components/Inputfuncs.md) page for how to do this. These might not be as clear-cut in all protocols, but the principle is there. These four basic components - however they are accessed - links to the *Portal Session*, which is the actual common interface between the different low-level protocols and Evennia.