Jobs
What it is
A job in the game is a place where the player can wait out a stretch of time and get paid for it. Each job has an hourly wage, may test one of the player’s skills, and has a hardness. This API lets your mod list the jobs in the scene you are in, and read their wage, skill and hardness. It tells you each time the player finishes working, with how long they worked and how much money it paid.
When to use it
- Show a message or write a log line when the player finishes a shift.
- Pay a bonus on top of a job’s wage.
- Show the wages of the jobs in a scene.
- Count how many hours the player has worked.
Quick example
This mod writes a log line each time the player finishes working, with the hours worked and the pay. When the game is ready, it also logs every job in the scene with its hourly wage.
using ObenseuerCore;
[ModInfo("example.jobwatch", "Job Watch", "0.1.0")]
[ModDependency("obenseuer.moddingframework", "0.11.0")]
public sealed class JobWatch : FrameworkMod
{
protected override void OnLoad()
{
Jobs.Worked += args => Log.Info("worked " + args.Hours + " hours and earned " + args.PayOc + " OC and " + args.PayRm + " RM");
Events.WhenReady(() =>
{
foreach (JobInfo job in Jobs.All())
Log.Info(job.Id + " pays " + job.SalaryPerHourOc + " OC an hour");
});
}
}
Worked is an event: the framework calls your code each time it happens. OC is Open Sewer Coins and RM is Real Money (see the glossary). See Using events for more on events and WhenReady.
Common tasks
Look up one job by its id. Get returns null when no job has that id.
JobInfo job = Jobs.Get("some-job-id");
if (job != null) Log.Info(job.SalaryPerHourOc + " OC an hour, hardness " + job.Hardness + ", skill " + job.Skill);
Find the best paying job in the scene.
JobInfo best = null;
foreach (JobInfo job in Jobs.All())
if (best == null || job.SalaryPerHourOc > best.SalaryPerHourOc) best = job;
if (best != null) Log.Info("best job: " + best.Id);
Pay a bonus of 10 OC every time the player finishes work that paid something.
Jobs.Worked += args => { if (args.PayOc > 0f) Cash.Give(MoneyCurrency.OC, 10f); };
Reference
This is the full list of what the API gives you.
Jobs list jobs and hear when work is finished
Call All() or Get(id) to read the jobs in the scene, and subscribe to Worked to hear when the player finishes work.
public static class Jobs
{
public static bool IsAvailable { get; }
public static List<JobInfo> All();
public static JobInfo Get(string id);
public static event Action<WorkedArgs> Worked;
}
| Property | Type | What it means |
|---|---|---|
IsAvailable | bool | false when the Worked event could not be set up. |
| Method | Returns | What it does |
|---|---|---|
All() | List<JobInfo> | Every job in the scene you are in. |
Get(id) | JobInfo | The job with this id. null when no job has it. |
| Event | Gives you | When it fires |
|---|---|---|
Worked | WorkedArgs | The player finished working. |
JobInfo a snapshot of one job
All() and Get(id) give you one of these for each job. It is built at the moment you call, so keep it if you need the values later. (It is built from the game’s InteractableWaitWork component on each job.)
public sealed class JobInfo
{
public string Id { get; }
public int SalaryPerHourOc { get; }
public float SalaryPerHourRm { get; }
public Skill Skill { get; }
public float Hardness { get; }
}
| Property | Type | What it means |
|---|---|---|
Id | string | Names this job. See the notes below. |
SalaryPerHourOc | int | The wage in OC for each hour of work. |
SalaryPerHourRm | float | The wage in RM for each hour of work. |
Skill | Skill | The player skill the job tests. Skill.None when it tests none. |
Hardness | float | How hard the job is, as the game sets it. |
WorkedArgs what you get when the player finishes working
Worked gives you one of these each time the player finishes a stretch of work. Id matches it to a JobInfo, and PayOc and PayRm show what the work paid.
public sealed class WorkedArgs
{
public string Id { get; }
public float Hours { get; }
public float PayOc { get; }
public float PayRm { get; }
}
| Property | Type | What it means |
|---|---|---|
Id | string | The id of the job, as JobInfo.Id. |
Hours | float | How many hours the player worked. |
PayOc | float | The OC the work added to the player’s balance. 0 when none. |
PayRm | float | The RM the work added to the player’s balance, to 0.1. 0 when none. |
Things to know
- A job’s
Idnames one job in the scene you are in. If the game gave the job a save id, that is the id. If not, the id is the job’s place in the scene, and it changes if someone edits the objects around it. (The id is the object’s GUID when it has one, otherwise a scene path such asScene:Root[0]/Child[2].) All()lists the jobs in the scene you are in, and it is slow, so do not call it every frame.Getis just as slow, because both search every object in the scene each time. Call them when something happens, or on a timer, and keep the result. (Both scanFindObjectsByType<InteractableWaitWork>on each call. Objects that are switched off are left out.)Skillis the player skill the job tests, orSkill.Nonewhen it tests none. It is the sameSkilllist the framework uses everywhere else for the player’s skills. (It is cast from the game’sPlayerStats.PlayerSkillType, the same way the skills API does it.)Workedfires when the player finishes a stretch of work, after the game has already paid the wage. It fires after the game has applied the skill and addiction changes too.Workedalso fires for a wait that was cut short, with the time actually worked. It does not fire for a wait of zero seconds. (It fires after the game callsInteractableWaitWork.WaitDone, which the game calls fromWaitingController.Wakeup.Hoursis the game’stimeWaitedin seconds divided by 3600.)PayOcandPayRmare how much the player’s balance went up during the work, not a recalculated wage. Both are 0 when the job pays nothing.PayRmis rounded to 0.1, because that is how finely the game counts RM. If a scene gives the player other money in the same moment, that money is counted too. (They are the change in the player’s OC and RM balance acrossWaitDone. The game code does not show such a scene relay (theonWaitDoneoutput), because it is set per scene.)Workeddoes not fire when a save loads. Loading a save does not finish any work. (Nothing on the load path callsWaitingController.Wakeup.)- The framework does not hold back
Workedwhile a save is loading. If your handler should ignore events raised during a load, checkSaveData.Busyfirst. It istruewhile a save is loading or being written.
Limits
PayOcandPayRmcan include money that did not come from the wage. They are the difference between two balances, not the game’s wage formula, so any other money change that happens during the same work is counted too.- A job without a save id gets a new id if its object is renamed or moved. Do not store those ids between sessions.
- You can’t start or stop a job from your mod. The game lets the player start work through its own steps, and it can refuse to let the player wait. A call from a mod would have to repeat those checks, so it is not offered. (
InteractandWaitare the game’s own flow, and the wait can be refused (CanWait).)
Details
Target: Obenseuer 0.5.08 (buildid 25969064), Unity 6000.0.77f1 Mono, BepInEx 6 Unity.Mono (be.788, the tested build) or BepInEx 5 (5.4.23 or later). Namespace: ObenseuerCore. Logs go to BepInEx\logs\obenseuer.moddingframework.jobs.log.
Everything not listed in this reference is internal and may change without notice. Items marked experimental may change too.
The framework changes one of the game’s methods when it starts, so it can tell your mod when work is finished. This page’s feature group is obenseuer.moddingframework.jobs, the name you will see in the log.
When you can call it
- Call everything from your mod’s normal code, not from a background thread you started. The game and Unity only run safely on the main thread (see the glossary). This goes for every member on this page and for the code that handles
Worked. - It is safe to call any of it at any time, even on the main menu or while a save is loading. If there is nothing to read yet, you get an empty list or
null. - Your
Workedhandlers run inside the game’s own call, one at a time. If one of your handlers throws an error, the framework logs it and still runs the other handlers. (The event is raised synchronously from the game’s wait completion.)
If something fails to set up
The framework changes some of the game’s methods when it starts. If a game update breaks one of them, the framework turns off the feature group that depends on it, and writes error lines and a warning to the group’s log file in BepInEx\logs. The rest of the framework keeps working.
- If the change behind
Workedcannot be made,Jobs.IsAvailablebecomesfalseandWorkednever fires. The feature group is turned off and error lines and a warning go toBepInEx\logs\obenseuer.moddingframework.jobs.log.AllandGetkeep working. (The change is onInteractableWaitWork.WaitDone.Workedrelies onWaitDonebeing the only way a wait finishes. The game code has one override of it and one call, inWaitingController.Wakeup.)