# Mod Authoring Guide

English | [中文](README.zh-CN.md)

## 1. Quick Start

- Copy `ModTemplate` and rename it to your mod folder. The folder name must match `ModName`.
- Edit `ModConfig.json`:
  - `ModName`: mod name
  - `ModAuthor`: author name
  - `ModVersion`: version
  - `ModDescription`: description
  - `IconPath`: icon path, usually `Icon.png`
  - `Enabled`: whether the mod is enabled
  - `Dependencies` (optional): dependency list in the format `ModName.ModAuthor`

## 2. Directory Conventions

- `Scripts/Entry.lua`: entry script, executed when the mod loads.
- `Data/`: new gameplay/config data tables.
- `Text/`: new text and localization tables.
- `ModResource/`: resource files such as images and animations.

## 3. Entry Script Methods

- Recommended entry point: `function ModConfig:Setup() ... end`
- In `Setup`, you can:
  - Change tables: `ModifyDataConfig` / `SetDataConfig`
  - Redirect resources: `RedirectSourcePath`
  - Merge tables: `MergeDataConfig`
  - Hook methods: `AddMethodHookBefore` / `AddMethodHookAfter`

Example:

```lua
function ModConfig:Setup()
  self:ModifyDataConfig("career_3", "Name", "Defect Robot")
  self:RedirectSourcePath("AnimationLib/支配魔女/Idle", "Mods/YourMod/ModResource/AnimationLib/Defect/Idle")
  self:AddMethodHookBefore("SettingUI.OnEnable", function(ctx)
    CS.UnityEngine.Debug.Log("[YourMod] SettingUI.OnEnable")
  end)
end
```

## 4. Available Lua API (`self` / `ModConfig`)

- `SetDataConfig(id, table)`: update an entire row, except the `Id` field.
- `ModifyDataConfig(id, key, value)`: update one field.
- `RedirectSourcePath(originalPath, newPath)`: redirect a resource path.
- `AddDynamicMethod(methodName, fn)`: register a global Lua method.
- `AddMethodHookBefore(typeDotMethod, fn)`: add a before hook.
- `AddMethodHookAfter(typeDotMethod, fn)`: add an after hook.

## 5. Data/Text CSV Rules

- The header row (row 1) must match the corresponding CSV in the main game project.
- The comment row (row 2) describes fields and should usually be kept.
- `Data` contains gameplay/effect configuration. `Text` contains display text and localization.
- Columns ending with `Script` contain Lua logic. `self` is a `ScriptExecutor`.
- Localization columns can use suffixes such as `_en`, `_zh_hant`, and `_jp`.

## 6. Item Authoring Rules

- On load, the game reads `Data` and `Text` from the mod root and merges tables with the same names.
- In-game item IDs are generated as `ModName_FileName_Id`, so `Data` and `Text` entries are for adding content, not overriding existing rows.
- If an ID contains `*`, it will not enter the random pool. Otherwise, it can appear normally in the pool.
- Effect authoring reference: [C# effect authoring guide](https://www.cnblogs.com/DLSinnocence/articles/18948776)
- Mods only recognize Lua scripts. Convert C# syntax to Lua syntax. For example, use `self:AddBuff(buffId, level)` instead of `AddBuff(buffId, level)`.
- Lua syntax reference: [Lua tutorial](https://www.runoob.com/lua/lua-basic-syntax.html)
- Full game configs: [web version](https://dlsinnocence.github.io/gamewiki/) and [local version](Scripts/Lib/DataConfigs)
- Vanilla effect examples are under `Scripts/Lib/DataConfigs`. They are written in C# style and are for reference only; mod scripts must be written in Lua.
- In scripts, reference vanilla IDs with `DataId.Id`; reference mod IDs as string literals.
- Cards must set `BaseScript` to either `AttackCardItem` or `CommonCardItem`, depending on whether the card selects a target.
- Persistent card effects should be written as buffs.

## 7. FAQ

- Mod does not take effect
  - Check whether `Enabled` in `ModConfig.json` is `true`, and whether dependencies are correct.

- Cannot set a dictionary
  - String dictionaries cannot be accessed with `[]`. Use `dict:get_Item` and `dict:set_Item`. See XLua details.

- What is the resource path?
  - The mod path is `Mods/<your mod name>/...`
  - Check the game config tables for vanilla paths.

- Why do some IDs contain `*`?
  - It controls whether the item enters the random pool. Content that should never be randomly obtained, such as career cards, should use `*`.

- How do I add parameterized events?
  - Combat currently supports four parameterized event types: `HurtData`, `ActionData`, `NewEnemyData`, and `DamageData`. Use calls such as `AddEvent_HurtData`.

- What is `Scripts/Lib` for?
  - It contains EmmyLua type hints and references for all vanilla configs.

- Why cannot a method hook find the method?
  - Check whether you added the `CS` prefix, whether instance methods use `self:`, and whether the target class implements `Modifiable`.

## 8. Fight Event List

Add listeners in `ScriptExecutor`.

| Event | Description |
| --- | --- |
| `Attack` | Attack event |
| `AddEnemy` | Add enemy event |
| `AttackDone` | Attack completed |
| `CostPower` | Consume power |
| `NoPower` | Not enough power |
| `AddPower` | Add power |
| `Dead` | Death |
| `ToughCountZero` | Toughness reaches zero |
| `OnEnemyDead` | Enemy death |
| `Resurrection` | Resurrection |
| `EndRound` | End of round |
| `ICreateCardItem` | Create card event, triggered N times |
| `CreateCardItem` | Create card item |
| `EndCreateCardItem` | Finish creating card item |
| `NoPowerWhenTry` | Not enough power when trying to use a card |
| `Action` | Action event |
| `BurnCard` | Burn card |
| `Init` | Initialize |
| `OnDiceCheck` | Dice check |
| `OnDiceValue` | Dice value |
| `Win` | Win |
| `Escape` | Escape |
| `StartRound` | Start of round |
| `Shuffle` | Shuffle |
| `OnCameraMove` | Camera movement |
| `FightStart` | Fight start |
| `Hurt` | Hurt |
| `Heal` | Heal |
| `SelectCardEnd` | Card selection ended |
| `OnTriggerEffect` | Trigger effect |
| `ScriptExecute` | `ScriptExecutor` executes a method |

### Global Events (Non-Fight)

Add listeners in `EventCenter`.

| Event | Description |
| --- | --- |
| `UIOpen` | UI open event. Method key format: `UIOpen-Name` |
| `UIHelp` | UI help event |
| `UIClose` | UI close event |
| `LanguageChange` | Language change event |
| ... | ... |

### Other Events

For example, `RoleTable` implements `INotifyPropertyChanged`; find and listen to the relevant event when needed.

## 9. Starter Exercise

1. Delete folders under `Data` and `Text`, leaving only `Card`.
2. Open the tables under `Data/Card` and `Text/Card`, then write effects by referring to `ScriptExecutor`.

## 10. Publish Your Mod

The upload tool is shipped with the game. The default path is:

`D:\Witch's Apocalyptic Journey\Witch's Apocalyptic Journey_Data\StreamingAssets\Mod Upload Tool\WorkshopUploader.exe`

### Before Uploading

1. Make sure the mod root contains `ModConfig.json`.
2. Fill in at least these fields in `ModConfig.json`:
   - `ModName`: mod name, recommended to match the mod folder name.
   - `ModAuthor`: author name.
   - `ModVersion`: version number.
   - `ModDescription`: default Workshop description. You can still edit it in the upload tool UI.
   - `IconPath`: preview image path, usually `Icon.png`.
   - `Enabled`: set to `true` for local testing.
3. For first upload, `PublishedFileId` can be omitted or empty. The tool writes it back after a successful upload.
4. Optionally set `WorkshopVisibility` for the default visibility:
   - `Public`
   - `FriendsOnly`
   - `Private`
   - `Unlisted`

Example:

```json
{
  "ModName": "YourMod",
  "ModVersion": "1.0",
  "ModAuthor": "YOUR_NAME",
  "ModDescription": "Your workshop description",
  "IconPath": "Icon.png",
  "Enabled": true,
  "Dependencies": null,
  "WorkshopVisibility": "Private",
  "PublishedFileId": ""
}
```

### Start the Upload Tool

1. Start Steam and log in to the account that will publish the mod.
2. Open the upload tool folder under the game directory:

`D:\Witch's Apocalyptic Journey\Witch's Apocalyptic Journey_Data\StreamingAssets\Mod Upload Tool`

3. Run `WorkshopUploader.exe`.

### In the Tool

1. Click "Select Mod Folder" and select the mod root folder that contains `ModConfig.json`.
2. Click "Reload ModConfig", or wait for the tool to read the config.
3. Fill in "Title". The title is entered manually and is not auto-filled from `ModName`.
4. Check "Description", "Preview image", and "Visibility".
5. To update an existing Workshop item, keep `PublishedFileId`. To create a new item, clear `PublishedFileId`.
6. Optionally fill in "Change note" for the Steam changelog.
7. Click "Upload to Workshop".

### Upload Result

- After the first successful upload, the tool writes the returned Steam `PublishedFileId` back to `ModConfig.json`.
- Later uploads of the same mod use `PublishedFileId` to update the existing Workshop item.
- If `PublishedFileId` does not belong to the current game or current Steam user, the tool clears it and uploads as a new item.
- Upload content is copied to a temporary directory first. `.meta` files and `Scripts/Lib/DataConfigs` are not uploaded.
- If the tool says you need to accept the Steam Workshop legal agreement, confirm it on the Steam page once, then check the item again.

See this example and try writing your first mod.
