

Outward mod that allows you to assign loots to enemies using Mods Communicator to send events.
Lootable.OnDeath method and evaluates the
provided LootRules. These rules determine whether your custom loot
should be applied to a dying enemy.
The mod acts as a central Loot Manager that stores simple, abstracted logic and handles more complex game mechanics internally. Its state can be saved or loaded using XML.
Other mods can inspect, modify, or react to the LootManager’s
dynamic state and see how different mods interact with it.
Firstly, install Mods
Communicator
After that, you can publish and subscribe to LootManager events.
All events are registered and visible in Mods Communicator’s logging system. However, it is still recommended to read the event descriptions below and review the examples. Many event fields are optional and give you extra control, but they are not required.
maxDiceValue also forces weight mode.In weight mode, Outward uses its original loot system, where each item is
assigned a dice-roll range based on its dropChance and its position
in the list. The system builds contiguous ranges as follows:
minDiceRollValue is the sum of all previous items’ dropChance.maxDiceRollValue is minDiceRollValue + dropChance.When the game rolls a number, the item whose range contains that number is the one that drops.
Example: If each item has a dropChance of 10, the ranges will be
0–10, 10–20, 20–30, etc.
The seventh item will therefore have
minDiceRollValue = 60 and maxDiceRollValue = 70.
You can adjust the behavior further using emptyDropChance and
maxDiceValue.
Check the parameter tables to understand which values are required. Event descriptions below are intentionally abstract — always refer to the tables for the exact field requirements.
gymmed.loot_manager_*
Make sure to publish only after LootManager has initialized. I
recommend adding it as a dependency in your mod, or publishing it before/after
ResourcesPrefabManager.Load has finished.
AddLoot
Requires at least one of the following:
item
itemDropChance
ListItemDropChance
Also requires one or more of the filtering fields: enemy / faction / area / uniqueness.
This event is the most flexible one — you can do everything with it. Other events exist only for convenience.
using OutwardModsCommunicator;
...
int myItemId = 4000360; // Dreamers root
var payload = new EventPayload
{
["itemId"] = myItemId,
["enemyName"] = "Hyena", //Character.Name
};
EventBus.Publish("gymmed.loot_manager_*", "AddLoot", payload);
dropChance to item it will internally
make emptyDropChance
using OutwardModsCommunicator;
...
int myItemId = 4000360; // Dreamers root
string myEnemyId = "eCz766tEIEOWfK81om19wg"; // Calixa Boss
var payload = new EventPayload
{
["itemId"] = myItemId,
["dropChance"] = 50,
["enemyId"] = myEnemyId,
};
EventBus.Publish("gymmed.loot_manager_*", "AddLoot", payload);
LootManager
tries to build ItemDropChance internally).
using OutwardModsCommunicator;
...
int myItemId = 4000360; // Dreamers root
var payload = new EventPayload
{
["itemId"] = myItemId,
["minDropCount"] = 1,
["maxDropCount"] = 3,
["faction"] = Character.Factions.Bandits,
};
EventBus.Publish("gymmed.loot_manager_*", "AddLoot", payload);
using OutwardModsCommunicator;
...
int myItemId = 4000360; // Dreamers root
var payload = new EventPayload
{
["itemId"] = myItemId,
["area"] = AreaManager.AreaEnum.Abrassar,
};
EventBus.Publish("gymmed.loot_manager_*", "AddLoot", payload);
AreaFamily by enum/name.AreaManager.AreaFamilies.
using OutwardModsCommunicator;
...
int myItemId = 4000360; // Dreamers root
var payload = new EventPayload
{
["itemId"] = myItemId,
["areaFamily"] = AreaManager.AreaFamilies[2], //Levant Region includes abrassar and all dungeons
// you can combine requirements
["faction"] = Character.Factions.Bandits,
["listExceptNames"] = new List<string>() { "Hyena" },
};
EventBus.Publish("gymmed.loot_manager_*", "AddLoot", payload);
AddLootByEnemyNameOnly listExceptIds work.
using OutwardModsCommunicator;
...
ItemDropChance myItem = new ItemDropChance();
myItem.ItemID = 4000360; // Dreamers root
myItem.DropChance = 30;
var payload = new EventPayload
{
["itemDropChance"] = myItem,
["enemyName"] = "Hyena", //Character.Name
["listExceptIds"] = new List<string>() { "yourEnemyId" },
};
EventBus.Publish("gymmed.loot_manager_*", "AddLootByEnemyName", payload);
AddLootByEnemyIdusing OutwardModsCommunicator;
...
List<ItemDropChance> drops = new();
ItemDropChance myFirstItem = new ItemDropChance();
myFirstItem.ItemID = 4000360; // Dreamers root
myFirstItem.DropChance = 30;
ItemDropChance mySecondItem = new ItemDropChance();
mySecondItem.ItemID = 6000170; // Purifying quartz
mySecondItem.DropChance = 30;
drops.Add(myFirstItem);
drops.Add(mySecondItem);
var payload = new EventPayload
{
["listOfItemDropChances"] = drops,
["enemyId"] = "JmeufMpL_E6eYnqCYP2r3w", // Elite Burning Man
};
EventBus.Publish("gymmed.loot_manager_*", "AddLootByEnemyId", payload);
AddLootForUniquesisForBosses/isForBossPawns/
isForStoryBosses/isForUniqueArenaBosses/
isForUniqueEnemies parameters.
using OutwardModsCommunicator;
...
int myItemId = 4000360; // Dreamers root
var payload = new EventPayload
{
["itemId"] = myItemId,
["isForBosses"] = true, // is for all bosses
};
EventBus.Publish("gymmed.loot_manager_*", "AddLootForUniques", payload);
LootRulesSerializer@LoadCustomLootsfilePath parameter.using OutwardModsCommunicator;
...
var payload = new EventPayload
{
["filePath"] = "assemblyLocation/filePath.xml",
};
EventBus.Publish("gymmed.loot_manager", "LootRulesSerializer@LoadCustomLoots", payload);
LootManager loot rules into an XML document.LootRulesSerializer@SaveLootRulesToXmlfilePath
parameter. If omitted, the file will be stored at:
BepInEx/config/gymmed.Mods_Communicator/Loot_Manager/LootRules-date.xml
using OutwardModsCommunicator;
...
var payload = new EventPayload
{
//["filePath"] = "",
};
EventBus.Publish("gymmed.loot_manager", "LootRulesSerializer@SaveLootRulesToXml", payload);
gymmed.loot_managerLootRuleRegistryManager@AppendLootRuleusing OutwardModsCommunicator;
...
public awake()
{
...
EventBus.Subscribe(
"gymmed.loot_manager",
"LootRuleRegistryManager@AppendLootRule",
YourMethod
);
}
...
public static void YourMethod(EventPayload payload)
{
if (payload == null) return;
int lootRuleId = payload.Get<string>("lootRuleId", null);
// You would compare it with your registered loot rule id that you can provide
// Your code...
}
LootRuleRegistryManager@RemoveLootRuleusing OutwardModsCommunicator;
...
public awake()
{
...
EventBus.Subscribe(
"gymmed.loot_manager",
"LootRuleRegistryManager@RemoveLootRule",
YourMethod
);
}
...
public static void YourMethod(EventPayload payload)
{
if (payload == null) return;
int lootRuleId = payload.Get<string>("lootRuleId", null);
// You would compare it with your registered loot rule id that you can provide
// Your code...
}
enemyName and that enemy is unique
make sure to provide one of isForBosses,
isForBossPawns, isForStoryBosses,
isForUniqueArenaBosses, isForUniqueEnemies parameters.UID + _ + Area.GetName().Trim().Replace(' ', '_')
because even UID repeat(is not truly unique) in different scenes and adding
location prevents collision.isForBosses parameter.
isForStoryBosses. They are all compared by UID. Djinn has dummy data and is not tested, but he never enters the state to be looted?
isForBossPawns. They are all compared by UID.
isForUniqueArenaBosses. They are all compared by UID.
isForUniqueEnemies. They are all compared by UID.
| Required atleast one of type | Parameter | Type | Description | |
|---|---|---|---|---|
| Item | Builds ItemDropChance Internally | itemId | int | Required if itemDropChance/listOfItemDropChances is not provided. Loot item ID. |
| dropChance | int | Optional. Default is 10. Determines chance of dropping item. You can provide ItemDropChance instead if you like. | ||
| minDropCount | int | Optional. Default is 1. Provides minimum amount of items could be dropped. You can provide ItemDropChance instead if you like. | ||
| maxDropCount | int | Optional. Default is 1. Provides maximum amount of items could be dropped. You can provide ItemDropChance instead if you like. | ||
| minDiceRollValue | int | Optional. Default is 0. Sets the lowest dice roll value at which item drop chances begin to count. Use together with 'maxDiceRollValue' and 'maxDiceValue'. You can provide ItemDropChance instead if you like. | ||
| maxDiceRollValue | int | Optional. Default is 0. Sets the highest dice roll value considered when calculating item drop chances. Use together with 'minDiceRollValue' and 'maxDiceValue'. You can provide ItemDropChance instead if you like. | ||
| Choice to provide instead | listOfItemDropChances | List<ItemDropChance> | Optional. Default null. Provide your created list of your ItemDropChance instances to be dropped. | |
| itemDropChance | ItemDropChance | Optional. Default null. Provide your created ItemDropChance instance to be dropped. | ||
| Enemy | enemyId | string | Default null. Determines if drop should be appliead for enemy. You can get this from UnityExplorer mod or logs. | |
| enemyName | string | Default null. Determines if drop should be appliead for enemy. You can get this from UnityExplorer mod or logs. | ||
| area | AreaManager.AreaEnum? | Optional. Default nullable. Determines if drop should be appliead for specific area. You can get this from AreaManager.AreaEnum enum. | ||
| areaFamily | AreaFamily | Optional. Default null. Determines if drop should be appliead for specific area family(region). You can get this from AreaManager.AreaFamilies variable. | ||
| faction | Character.Factions? | Optional. Default nullable. Determines if drop should be appliead for specific faction. You can get this from Character.Factions enum. | ||
| isForBosses | bool | Optional. Default false. Determines if drop should be appliead for all game bosses and pawns. | ||
| isForBossPawns | bool | Optional. Default false. Should drop be applied for bosses pawns? | ||
| isForStoryBosses | bool | Optional. Default false. Should drop be applied for story bosses? | ||
| isForUniqueArenaBosses | bool | Optional. Default false. Should drop be applied for unique arena bosses? | ||
| isForUniqueEnemies | bool | Optional. Default false. Should drop be applied for unique enemies? | ||
| Not Required | lootId | string | Optional. You will need loot id if you planning to remove loot later. | |
| listExceptIds | List<string> | Optional. Default null. List of enemy ids that will not receive loot. You can get this from Character.UID.Value . | ||
| listExceptNames | List<string> | Optional. Default null. List of enemy names that will not receive loot. You can get this from Character.Name . | ||
| minNumberOfDrops | int | Optional. Default is 1. Determines minimum amout of drops for same provided items(ItemDropChance). | ||
| maxNumberOfDrops | int | Optional. Default is 1. Determines maximum amout of drops for same provided items(ItemDropChance). | ||
| emptyDropChance | int | Optional. Default is 0. Defines the percentage chance for a drop to be empty. Used together with 'maxDiceValue'. | ||
| maxDiceValue | int | Optional. Default 1. Is the limit of dice rolls range on DropTable. | ||
| Parameters used for rules control | |||
|---|---|---|---|
| Events | Parameter | Type | Description |
LootRulesSerializer@LoadCustomLoots | filePath | string | Required. Used for loading custom loots from xml file. |
LootRulesSerializer@SaveLootRulesToXml | filePath | string | Optional. Default "BepInEx/config/gymmed.Mods_Communicator/Loot_Manager". Used for storing custom loots to xml file. |
LootRuleRegistryManager@AppendLootRule,
LootRuleRegistryManager@RemoveLootRule
| lootRuleId | string | Provides loot rule id. |
Lootable.OnDeath event. Not a priority for me right now to fix it.
You can view outward enchantments balancer pack here.
To manually set up, do the following
Outward\BepInEx\plugins\OutwardLootManager\.BepInEx\plugins\OutwardLootManager\ directory you created.Outward\BepInEx\plugins\OutwardSceneTester\OutwardLootManager.dll
Launch the game.