Services

From PMDOWiki

Services are special files whose purpose is to listen to specific actions the game can perform and then run some code whenever they occur.
The base game already has a few built-in services that can be used for reference, but this page will go over how to make a new service from scratch and how to register new callbacks.

How to make a service

First, navigate to your mod's Data>Script>[namespace] folder. Once there, add a services folder if there isn't one already and, inside of it, create yet another folder. This final folder can have any name you want as long as it has no special characters in it, but it's best to stick to something that is as close as possible to the name you want to give to your service. For Example, the base game's DebugTools service is inside the debug_tools folder. Finally, create a text file inside of this folder and call it init.lua. Make sure that .lua is part of the file's extension and not still a portion of its name.

Once you're done, open this file with your text editor of choice. You will want to copy and paste this whole block inside of it:

require 'origin.services.baseservice'

local MyService = Class('MyService', BaseService)

function MyService:initialize()
end

function BaseService:Subscribe(med)
end

SCRIPT:AddService("MyService", MyService:new())
return MyService 

This is the smallest possible service you can make. It simply creates the class, registers it, and then returns it. You can change "MyService" into whatever name you want, of course.
Right now, though, this service of ours literally does nothing. Let's change that.
You can use the initialize function to define starting parameters or other global data related to your service. Keep in mind, however, that these values are not kept in between game sessions. Always remember to store your save data inside your SV structure if you want to be able to retrieve it after closing the game.
What we need to use to make a service work, however, is the Subscribe function. This is the function responsible for registering callbacks so that the game knows to call them whenever a specific event takes place. You can find a list of all Engine Events present in the game and their parameters here.

Creating a Callback

The first thing we need is the function we want to call. This can have any name you like, but it's best if its name references the event it is subscribed to in some way.
We will use this simple print function as an example. Make sure to put it below your service's declaration but before its Subscribe function:

function MyService:OnInit()
  assert(self, 'MyService:OnInit() : self is null!')
	PrintInfo("\n<!> MyService: Init..")
end

All this function does is print whether or not our service has been registered correctly when the service engine is loaded. To make it run, all we need to do is to add this line inside the service's Subscribe function:

  med:Subscribe("MyService", EngineServiceEvents.Init, function() self:OnInit() end)

If you run the game now, you will notice that nothing happens still. This is because this file has never been loaded by the game. To make it load, create a main.lua file in your mod's Data>Script>[namespace] folder and write require "[namespace].services.[service_folder]" in it. Now the game will know it needs to load your service and you will see its OnInit function being called as expected.

Callbacks with parameters

Many events contain parameters that get sent alongside their service messages so that services can reference and interact with them. These parameters are packaged into an argument array with starting index 0. An example of this is LossPenalty, whose argument 0 is the current GameProgress instance, which is to say, the save data itself. We need to pass this parameter along if we want to make use of it.
If you're wondering what that discaded parameter is, it's your service's class. At no point you'll ever need this, since you're already working with it anyway.

function MyService:OnLossPenalty(save)
[...]
end

function MyService:Subscribe(med)
  med:Subscribe("MyService", EngineServiceEvents.LossPenalty, function(_, args) self:OnLossPenalty(args[0]) end )
end

And that's all there is to it! Every service callback can be reduced to these simple steps!