# BagLootFramework 1.0

> **This README is developer documentation.** If you only want to customise
> monster-bag loot, read `CONFIGURATION.md`, edit `loot_config.lua`, and use
> `item_catalogue.md` to find ITEM_IDs. You do not need to open `Scripts`.

BagLootFramework customises loot from RuneScape: Dragonwilds monster bags such as Bird Nests, Goblin Packs, and Garou Packs. It is also intended as a readable example for people maintaining this mod or investigating other loot systems such as chests and ordinary monster drops. Maybe this will help?

RSDWTools/Dev kit is the only reason this mod could be made and can continue to exist! If you are looking into modding dragonwilds, or want to make edits/expansions of this mod, you **need** to know what RSDW_ can and cannot do. It will save you many, many hours. Applause for `Primitive Nomand` for his insane work! [YouTube/PrimitiveNomad](https://www.youtube.com/@PrimitiveNomad)

## Design goals

- Keep all player-facing files in the mod root.
- Keep `loot_config.lua` simple enough for a non-programmer to edit.
- Separate ordinary Lua from UE4SS and Unreal Engine interactions.
- Resolve configured items directly rather than borrowing existing bag loot.
- Fail safely and print useful messages when something is invalid.
- Let the source code serve as the detailed developer documentation.
- Catalogue generation and sorting is disabled in `main.lua` and could be re-enabled after major updates. Hopefully it will work even with new content, though sorting is bound to mess up.

## Folder layout

```text
BagLootFramework/
├── enabled.text           Enables the mod. Equivalent to "BagLootFramework : 1" in mods.txt
├── loot_config.lua        Player's loot choices
├── item_catalogue.md      Searchable ITEM_ID catalogue
├── CONFIGURATION.md       Player guide
├── README.md              Developer guide (this file)
└── Scripts/
    ├── main.lua                    Small entry point. Hardcoded to disable catalogue generation and sorting.
    ├── logger.lua                  Consistent one-line UE4SS logging.
    ├── paths.lua                   Locates root and Scripts files.
    ├── config_loader.lua           Loads and validates player configuration.
    ├── item_database.lua           ITEM_ID to Unreal asset-path lookup.
    ├── item_reference_resolver.lua Direct Unreal object lookup.
    ├── loot_builder.lua            Unreal loot-table modification.
    ├── scanner.lua                 (Dormant) Catalogue generator.
    ├── scanner_sorting.lua         (Dormant) Current-game classification rules (sorting the catalogue during generation).
    ├── json_decoder.lua            Simple JSON plumbing.
    └── items.json                  Table of all(?) items in the game. Exported from RSDW Dev kit > Item services.
```

UE4SS requires `enabled.txt` in the mod root and `main.lua` inside `Scripts`. `Items.json` limits the user to this set of ~1.300 items. Sadly, you can not recieve an ash square foundation as loot. Or could you...? `Hey, ChatGPT, I have a question...`

## Normal gameplay flow

```text
loot_config.lua
    -> config_loader.lua
    -> items.json + item_database.lua
    -> item_reference_resolver.lua
    -> loot_builder.lua
    -> Dragonwilds monster-bag loot edited
```

1. `main.lua` starts with a 5 second delay, then loads the root `loot_config.lua`.
2. `items.json` is a file exported from RSDW Dev kit (Item Services) and serves two purposes;
   1. gives a link between ITEM_IDs and UnReal Engine Objects (required during runtime), and
   2. used as a source to create the catalogue from (see below).
3. `config_loader.lua` checks syntax, ITEM_ID strings, probabilities, and item amounts before the game is changed. Bad bags or drops are removed from the runtime copy while valid entries continue.
4. `item_database.lua` reads `items.json` and derives the Unreal asset path for every ITEM_ID. It performs no sorting and touches no game objects.
5. `item_reference_resolver.lua` uses UE4SS `StaticFindObject()` for only the unique items requested by the configuration, then converts each item object into the `TSoftObjectPtr` required by the loot struct.
6. `loot_builder.lua` matches configured bag IDs, omits unresolved drops, and replaces the matching loot arrays with the usable remainder.

The delay exists because Dragonwilds' loot objects are not guaranteed to exist yet when UE4SS first loads the mod. The Unreal and loot changes run together on the game thread.

## Optional catalogue flow

Manually edit `main.lua` to enable (simple un-comment).

```text
Scripts/items.json
    -> json_decoder.lua
    -> scanner.lua
    -> scanner_sorting.lua
    -> item_catalogue.md
```

This generates a fresh catalogue, for example after a major game update. If `Items.json` is **NOT** new from RSDW Dev kit, the catalogue can not update and rnning this only give a chance for the catalogue to break! Only run once you have backups.

Catalogue is in main for the novice user who wants to go for a lucky update of the mod. Nothing here is actually connected to runtime, or requires the game to run, so probably just bad coding practice on my part to try and give novices a chanse.

`scanner.lua` and `scanner_sorting.lua` are not loaded during normal play.
`items.json` and `json_decoder.lua` are shared with the runtime path lookup.

To regenerate the catalogue **after** getting a new `Scripts/items.json` from RSDW Dev kit:

1. Open `Scripts/main.lua`.
2. Find this line near the top:

   ```lua
   -- require("scanner").Scan()
   ```

3. Remove the two dashes, start Dragonwilds once, and wait for `Catalogue generated successfully` in `UE4SS.log`.
4. Put the two dashes back.

Leaving the line commented means there is no classification or catalogue writing on game boot. Runtime still reads ITEM_IDs and source paths from the JSON because those are now what make direct item-object lookup possible.

## Runtime source guide

### `main.lua`

The entry point shows the entire startup sequence and contains the one-line catalogue-generator switch.

### `paths.lua`

Finds the `Scripts` directory and mod root without hardcoded installation paths.

### `config_loader.lua`

Ordinary Lua. It loads the returned configuration table, copies only the four supported fields, and reports common editing mistakes before any Unreal object is touched. Syntax/root errors stop startup; an invalid bag or drop is skipped. This is a good first file to read after `main.lua`.

### `item_database.lua`

Ordinary Lua. This is the bridge from RSDWTools provenance to Unreal paths:

```text
.../RSDragonwilds/Content/<path>/ITEM_ID.json
    -> /Game/<path>/ITEM_ID

.../RSDragonwilds/Plugins/GameFeatures/<Plugin>/Content/<path>/ITEM_ID.json
    -> /<Plugin>/<path>/ITEM_ID
```

All 1.373 records in the included JSON follow one of these two forms.

### `item_reference_resolver.lua`

Unreal Engine backend. An ITEM_ID is readable text, while the loot struct's `ItemDataClass` field needs a `TSoftObjectPtr`. Dragonwilds already registers its item-data objects during startup. This module derives the full path, retrieves the `UObject` with `StaticFindObject()`, and converts it with Unreal's `KismetSystemLibrary.Conv_ObjectToSoftObjectReference()`.

It never scans existing bag entries and never calls `LoadAsset()`. Adamant Platebody can therefore resolve from `/UmbralSands/.../ITEM_Armour_T7_Body_Adamant` even if no normal monster bag contains it. `pcall`, `IsValid()`, and `StaticFindObject()`, the Kismet conversion, and soft-pointer inspection are integration plumbing rather than patterns configuration contributors need to learn.

### `loot_builder.lua`

Unreal Engine backend. It performs the mutation:

1. Find emitters whose full name contains the configured bag ID.
2. Remove requested drops whose item references did not resolve.
3. Retain one original entry as the Unreal struct template.
4. Clear the Unreal `Items to Drop` array.
5. Copy the template and replace `ItemDataClass`, minimum, maximum, and drop probability for every configured item.

Assignments such as `items[newIndex] = template` operate on an Unreal `TArray`, not an ordinary Lua table. An unresolved item is logged and omitted. If no usable drops remain, the builder preserves that bag's original loot so a typo or unavailable plugin cannot clear the bag accidentally. Calls that may raise Lua errors are protected and report the affected bag/emitter in `UE4SS.log`.

## Catalogue source guide

### `items.json`

Shared data exported with RSDWTools. During normal play, the runtime uses only ITEM_IDs and source paths. Catalogue generation uses names, categories, equipment metadata, and power levels only when explicitly enabled.

### `scanner.lua`

The readable orchestration layer: read JSON, collect unique items, classify them, and format Markdown.

### `scanner_sorting.lua`

The intentionally explicit current-game rules. Dragonwilds contains outliers, obsolete assets, misleading names, oddly named vestiges, and duplicate-looking entries. The large top comment identifies the tables maintainers normally edit. This file is long because the catalogue is curated for the current game data; it does not participate in runtime item loading.

### `json_decoder.lua`

Shared plumbing. UE4SS Lua does not provide the JSON decoder needed here, so a small local decoder is included. It contains no Dragonwilds sorting decisions.

## RSDWTools foundation

RSDWTools was foundational to this project:

- Its extraction created the comprehensive `items.json` and human-readable
  `item_catalogue.md`.
- Its retained source paths let the runtime derive direct Unreal asset paths.
- Its live inspection tools revealed the emitter class, item-data objects, loot arrays, and fields changed by this mod.

Without that bridge between extracted data and runtime behaviour, this project would have required substantially more reverse engineering.

## Inspiration for another loot mod

The reusable pieces are configuration validation, ITEM_ID-to-asset resolution, isolated Unreal mutation, and delayed game-thread startup.

The non-reusable assumption is `BP_Consumables_ItemEmitter_C` with its `Items to Drop` array. Chest loot or ordinary monster loot may use another class, property, struct, or generation time. For another system:

1. Use RSDWTools/UE4SS to identify the owning class and loot property.
2. Reuse direct item resolution, but write a new builder for that loot class.
3. Keep configuration and validation independent from the new builder.
4. Resolve and validate requested values before clearing an existing game array; if none are usable, leave the original array alone.

## After a Dragonwilds update

1. Search `UE4SS.log` for `[BagLootFramework]` and Lua errors.
2. Confirm `enabled.txt` and `Scripts/main.lua` are still in place.
3. Check whether the emitter class, loot-array name, or entry fields changed.
4. Check `RESOLVER WARNING` lines for renamed mounts or unavailable assets.
5. Export a fresh `items.json` with RSDWTools if available items changed.
6. Enable the scanner for one boot, audit the catalogue, then disable it again.

The most update-sensitive files are `item_database.lua`, `item_reference_resolver.lua`, and `loot_builder.lua`.

## Current test status

Offline tests cover JSON parsing, all 1.373 path conversions, configured-ID lookup, the UE4SS call sequence, safe failure behaviour, and loot-array reconstruction. In-game testing confirmed an initial set of  30 configured Bird Nest items can be found, converted to soft pointers, written into the live Unreal array, and read back/verified with the configured quantities and probabilities.

For the first real startup, search `UE4SS.log` for something similar to:

```text
[BagLootFramework] Item database loaded: 1373 ITEM_IDs
[BagLootFramework] Configured item soft pointers resolved: 30/30
[BagLootFramework] ITEM_Consumable_BirdNest: applied 30 drop(s) to 1 of 1 matching emitter(s); skipped 0 unresolved drop(s)
[BagLootFramework] BagLootFramework v1.0 loaded: changed 1 of 1 configured bag(s)
```

If resolution is not every item, the framework logs and omits the unresolved drops, then applies the usable remainder. If none resolve, it preserves the original bag loot. The `RESOLVER WARNING` includes the relevant ITEM_ID and asset path needed for the next correction - hopefully.