Robbers
Status: experimental.
What it is
Some NPCs in the game are robbers. A robber hangs around, follows the player and may demand money. This API lists the robbers, reads what each one is doing, lets your mod make a robber attack, rob the player or accept a bribe, and tells you when the player has been robbed. Robbers are NPCs, so you name one with the NPC id from the NPCs page (NpcInfo.Id).
When to use it
- Log or react when the player is robbed, for example to give a reward or a penalty.
- Check which NPCs are robbers and whether one is chasing or attacking the player.
- Start a robbery, an attack or a bribe from your own quest or event.
Quick example
This mod logs the name of the robber each time the player is robbed. It also logs how many robbers the game has once a save is loaded.
using ObenseuerCore;
[ModInfo("example.robberwatch", "Robber Watch", "0.1.0")]
[ModDependency("obenseuer.moddingframework", "0.11.0")]
public sealed class RobberWatch : FrameworkMod
{
protected override void OnLoad()
{
Robbers.Robbed += npcId =>
{
NpcInfo npc = Npcs.Get(npcId);
Log.Info("The player was robbed by " + (npc == null ? "NPC " + npcId : npc.DisplayName ?? npc.Name));
};
Events.WhenReady(() => Log.Info("The game has " + Robbers.All().Count + " robbers."));
}
}
Robbed is an event: the framework calls your code each time it happens. WhenReady runs your code once the game is ready. See Using events for more on both.
Common tasks
List every robber and what each one is doing.
foreach (RobberInfo robber in Robbers.All())
Log.Info("NPC " + robber.NpcId + " is in the state " + robber.State);
Make a robber attack the player. The first argument is your mod’s id. Aggro returns true when the robber is now attacking.
int npcId = 123;
bool attacking = Robbers.Aggro(Mod.Id, npcId);
Log.Info("Robber is attacking: " + attacking);
Try to bribe a robber so it leaves.
int npcId = 123;
if (Robbers.Negotiate(Mod.Id, npcId)) Log.Info("The robber took the bribe and left.");
Reference
Text in brackets at the end of a note is extra detail for experienced modders, and you can skip it on a first read.
Reading robbers
Robbers.All() and Robbers.State() tell you which NPCs are robbers and what they are doing. They also see a robber that is far away and not in the scene you are in.
Robbers reading robbers
This is the class to start from when you want to list robbers with All() or check one with State(npcId).
public static class Robbers
{
public static bool IsAvailable { get; }
public static List<RobberInfo> All();
public static string State(int npcId);
}
| Method | Returns | What it does |
|---|---|---|
All() | List<RobberInfo> | Every robber the game knows. |
State(npcId) | string | What that robber is doing. It is null when the NPC is not a robber. |
| Property | Type | What it means |
|---|---|---|
IsAvailable | bool | false when the Robbed event could not be set up. |
RobberInfo one robber
Robbers.All() gives you one of these for each robber. NpcId is the id to pass to the other Robbers members, and State is what the robber is doing right now. (It wraps the game’s robber activity, Act_Robber, and State is the name of that activity’s state.)
public sealed class RobberInfo
{
public int NpcId { get; }
public string State { get; }
}
| Property | Type | What it means |
|---|---|---|
NpcId | int | The robber’s NPC id. Use it with the other members. |
State | string | What the robber is doing: Idle, Stand, Patrol, Wander, Chase or Aggro. |
- A robber is an NPC whose current activity is the game’s robber activity. When a robber is doing something else, such as another job, it is not listed. (
Alllooks at each NPC’sstate.CurrentActivityinNPCDirector.globalDatabaseand keeps the ones that are anAct_Robber.) - The states mean what the robber is doing now.
IdleandStandare waiting,Patrolis walking its route,Wanderis walking around,Chaseis following the player, andAggrois attacking the player. IsAvailableisfalsewhen theRobbedevent does not work. The reads and the other writes keep working.
Making a robber act
These calls make a robber do something. The first argument is your mod’s id (Mod.Id). They only work on a robber that is in the scene you are in. For a robber that is far away, they return false.
Robbers making a robber act
Use these to make a robber attack, rob the player, take a bribe or change its aloof flag. Each one takes your mod’s id first, and each returns true only when it worked.
public static class Robbers
{
public static bool Aggro(string modId, int npcId);
public static bool Rob(string modId, int npcId);
public static bool Negotiate(string modId, int npcId);
public static bool SetAloof(string modId, int npcId, bool aloof);
}
| Method | Returns | What it does |
|---|---|---|
Aggro(modId, npcId) | bool | Makes the robber attack the player. It is true when it is now attacking. |
Rob(modId, npcId) | bool | Robs the player now. It is true when the robbery ran. |
Negotiate(modId, npcId) | bool | Tries to bribe the robber. It is true when it worked. |
SetAloof(modId, npcId, aloof) | bool | Changes the robber’s “aloof” dialogue flag. It is true when it was set. |
Aggromakes the robber attack the player, the same as the game’s own attack call. It returnstrueonly when the robber is now attacking. If the robber has seen a police officer, the game does not let it attack, and you getfalse. (It callsAct_Robber.Aggro()and checksstate == Aggro. The game does nothing whenseenPoliceis set.)Robrobs the player the way the game does, then ends the robber’s job. It only runs when the player holds at least the robber’s minimum in both currencies, which is 1000 OC and 33 RM in this game version. Otherwise it returnsfalseand nothing happens. The game leaves the player a random amount below that minimum in each currency. It returnstrueonce the robbery has run. (The check isMoney.CheckMoney(minimumMoney)on the privateAct_Robber.minimumMoneyfield, so a player holding at least the minimum in both currencies is only ever robbed, never given money. The game’s own robbery talk still starts on either currency alone and can add money for a player who holds less. The framework does not change that.RobPlayeralso sets the robber’s aloof flag and finishes its activity.)Negotiatetries the bribe the way the game does. The chance depends on the player’s bribery skill and on how much money the player holds: the more money, the less likely it works. It returnstruewhen it works. Then the game takes a small share of the player’s money and the robber leaves. (It callsAct_Robber.TryNegotiate(). On success the game removes 5% of the player’s money, finishes the activity and sets the aloof flag.)SetAloofchanges a flag that the robber’s dialogue reads. The game sets this flag itself after a robbery or a bribe. It returnsfalsewhen the robber has no conversation to set it on. (It callsChangeRobberAloofStatus, which sets the actor fieldAlooffor the NPC’sConversationTrigger.)- Your mod’s id must be valid. It must be 1 to 64 letters, digits, dots, underscores or dashes. A bad id throws an
ArgumentException. UseMod.Idand you are fine. - These calls return
falsewhile a save is loading or being written.
Knowing when the player is robbed
Robbed tells you each time a robbery finishes.
Robbers knowing when the player is robbed
Subscribe to Robbed to run code once a robbery has finished. The handler gets the robber’s NPC id, which you can pass to Npcs.Get (see the NPCs page) to find its name.
public static class Robbers
{
public static event Action<int> Robbed;
}
| Event | Gives you | When it fires |
|---|---|---|
Robbed | int, the robber’s NPC id | A robbery just finished. |
Robbedfires when a robbery finishes, whether the game or a mod started it. Your handler gets the robber’s NPC id. It does not fire when the robber has no NPC id. (It is raised from a Harmony postfix onAct_Robber.RobPlayer, on the main thread.)- A
Robbedhandler that throws an error does not stop the others. The framework logs the error and still runs the other handlers.
What you can’t do (yet)
- There is no event for a robber changing state. The game changes a robber’s state in several places, and some of them do not go through the shared method the framework could watch, so one watch would miss changes. To follow a robber, ask
Stateon a timer. (Some state changes skipStartState, so a hook on it would miss them.)
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.robbers.log.
Everything not listed in this reference is internal and may change without notice. Items marked experimental may change too.
The framework changes one game method when it starts, so it can tell your mod when a robbery finishes. This page calls that a feature group: its name is obenseuer.moddingframework.robbers, the name you will see in the log.
When you can call it
- Call everything from your mod’s normal code (
OnLoad, event handlers,Update), not from a background thread you started. The game and Unity only work safely on one thread, the main thread (see the glossary). This goes for the code that handlesRobbedtoo. - It is safe to call any of it at any time, even on the main menu or while a save is loading. Reads give an empty list or
nullwhen there is nothing to read. Calls that make a robber act givefalse.
What to check for and what comes back
- Bad values give
falseornullinstead of an error. The only error is theArgumentExceptionfor an invalid mod id. If the game itself fails, the first failure of each member goes to the log as an error and you get the same plain default.
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 removes all the changes of the feature group that depends on it, turns the group off, and writes error lines to the group’s log file in BepInEx\logs. The rest of the framework keeps working.
- If the framework cannot change the game’s robbery method,
Robbedstops and the rest keeps working.IsAvailablebecomesfalse. (The method isAct_Robber.RobPlayer.)